Initial curated projectbrain source from 8a7b1e0651dbdab97d744cbd2e653ab3970ac6c5
This commit is contained in:
commit
5aa3e8d685
700 files changed
+132801
No files matched your search
@@ -0,0 +1,189 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const requestIdSchema = z.string().min(1);
|
||||
export const timestampSchema = z.string().datetime({ offset: true });
|
||||
export const validationIssueSchema = z.object({ field: z.string().optional(), message: z.string().min(1), code: z.string().optional() });
|
||||
export const apiMetaSchema = z.object({ requestId: requestIdSchema, timestamp: timestampSchema });
|
||||
export const apiErrorEnvelopeSchema = z.object({
|
||||
error: z.object({ code: z.string().min(1), message: z.string().min(1), validation: z.array(validationIssueSchema).optional() }),
|
||||
meta: apiMetaSchema,
|
||||
});
|
||||
export const successEnvelopeSchema = (dataSchema = z.unknown()) => z.object({
|
||||
data: dataSchema.refine((value) => value !== undefined, { message: "A success envelope must contain data" }),
|
||||
meta: apiMetaSchema,
|
||||
});
|
||||
export const apiEnvelopeSchema = z.union([apiErrorEnvelopeSchema, successEnvelopeSchema()]);
|
||||
|
||||
// Canonical lifecycle values are intentionally language-neutral. Frontends and
|
||||
// operational surfaces may translate them for people, but transport and
|
||||
// backend decisions must use the same finite vocabulary.
|
||||
export const canonicalLifecycleValues = Object.freeze([
|
||||
"ready",
|
||||
"running",
|
||||
"waiting",
|
||||
"needs_user",
|
||||
"recovering",
|
||||
"failed",
|
||||
"done",
|
||||
]);
|
||||
export const canonicalLifecycleSchema = z.enum(canonicalLifecycleValues);
|
||||
export const autonomyProfileSchema = z.enum(["conservative", "normal", "autonomous", "custom"]);
|
||||
export const autonomyProfileSettings = Object.freeze({
|
||||
conservative: Object.freeze({ autoBrainstorm: false, maxBrainstormIterations: 2, maxConsecutiveFailures: 2, leaseMinutes: 20, maxWorkItemsPerRun: 3, maxRunMinutes: 20 }),
|
||||
normal: Object.freeze({ autoBrainstorm: true, maxBrainstormIterations: 5, maxConsecutiveFailures: 3, leaseMinutes: 30, maxWorkItemsPerRun: 8, maxRunMinutes: 45 }),
|
||||
autonomous: Object.freeze({ autoBrainstorm: true, maxBrainstormIterations: 5, maxConsecutiveFailures: 5, leaseMinutes: 45, maxWorkItemsPerRun: 12, maxRunMinutes: 55 }),
|
||||
custom: undefined,
|
||||
});
|
||||
|
||||
// Standard views must explain a blocker without making raw execution payloads
|
||||
// (SHAs, workflow names, leases, etc.) the primary product language. Both the
|
||||
// API portfolio read model and the web client use this same mapping.
|
||||
const normalizeOperationalCopy = (value) => value.replace(/\s+/g, " ").trim();
|
||||
const containsTechnicalOperationalDetail = (value) => /\b(?:[a-f0-9]{7,64}|https?:\/\/|\.gitea\/|workflow|default branch|branch protection|pull request|\bpr\s*#?\d+|executor|lease|run \d+|plan \w{12,})\b/i.test(value);
|
||||
const firstOperationalSentence = (value) => {
|
||||
const sentence = normalizeOperationalCopy(value).split(/(?<=[.!?])\s+/)[0] ?? "";
|
||||
return sentence.length > 220 ? `${sentence.slice(0, 217).trimEnd()}…` : sentence;
|
||||
};
|
||||
export const summarizeHumanBlocker = (reason) => {
|
||||
const normalized = reason ? normalizeOperationalCopy(reason) : "";
|
||||
if (!normalized) return "ProjectBrain heeft jouw beslissing of toegang nodig om veilig verder te gaan.";
|
||||
const lower = normalized.toLowerCase();
|
||||
if (/governance|default branch|branch protection|workflow|pull request|\bpr\s*#?\d+/.test(lower)) return "ProjectBrain heeft jouw expliciete keuze nodig over een repositorywijziging voordat het veilig kan doorgaan.";
|
||||
if (/authori[sz]|permission|access|credential|token|owner/.test(lower)) return "ProjectBrain heeft jouw toestemming of toegang nodig om veilig verder te gaan.";
|
||||
if (/conflict|ambiguous|disagree|inconsistent/.test(lower)) return "ProjectBrain heeft jouw keuze nodig omdat betrouwbare projectinformatie elkaar tegenspreekt.";
|
||||
return containsTechnicalOperationalDetail(normalized) ? "ProjectBrain heeft jouw expliciete beslissing nodig voordat het deze stap verantwoord kan voortzetten." : firstOperationalSentence(normalized);
|
||||
};
|
||||
export const summarizeTechnicalBlocker = (reason) => {
|
||||
const normalized = reason ? normalizeOperationalCopy(reason) : "";
|
||||
if (!normalized) return "ProjectBrain onderzoekt de technische oorzaak en herstelt automatisch waar dat veilig kan.";
|
||||
const lower = normalized.toLowerCase();
|
||||
if (/auth|credential|token|permission|access/.test(lower)) return "ProjectBrain herstelt een verbindings- of toegangsprobleem automatisch waar dat veilig kan.";
|
||||
if (/branch|pull request|workflow|repository/.test(lower)) return "ProjectBrain controleert de repositorytoestand opnieuw voordat het verdergaat.";
|
||||
return containsTechnicalOperationalDetail(normalized) ? "ProjectBrain verwerkt een technische blokkade. Details blijven beschikbaar onder technische diagnose." : firstOperationalSentence(normalized);
|
||||
};
|
||||
|
||||
export const userRoleSchema = z.enum(["owner", "member"]);
|
||||
export const authUserSchema = z.object({ id: z.string().min(1), username: z.string().min(3).max(80), role: userRoleSchema });
|
||||
export const loginCredentialsSchema = z.object({ username: z.string().trim().min(3).max(80), password: z.string().min(12).max(256) });
|
||||
export const rotatePasswordSchema = z.object({ currentPassword: z.string().min(12).max(256), newPassword: z.string().min(12).max(256) }).refine((value) => value.currentPassword !== value.newPassword, { message: "New password must differ from current password", path: ["newPassword"] });
|
||||
export const authSessionDataSchema = z.object({ user: authUserSchema, csrfToken: z.string().min(20), expiresAt: timestampSchema.optional() });
|
||||
export const entraPublicConfigSchema = z.object({ enabled: z.boolean(), provider: z.literal("microsoft-entra") });
|
||||
export const entraLinkStatusSchema = z.object({ enabled: z.boolean(), linked: z.boolean(), displayName: z.string().optional(), preferredUsername: z.string().optional() });
|
||||
|
||||
export const idSchema = z.string().min(1).regex(/^[A-Za-z0-9-_]+$/);
|
||||
export const mutationResponseSchema = z.object({ success: z.boolean(), message: z.string().min(1), updatedEntityId: idSchema.optional(), createdEntityId: idSchema.optional(), affectedEntities: z.array(idSchema) });
|
||||
export const captureCreateSchema = z.object({ type: z.string().min(1), content: z.string().trim().min(1), source: z.string().optional() });
|
||||
export const operatingLogCreateSchema = z.object({ type: z.string().min(1), title: z.string().trim().min(1), summary: z.string().trim().min(1), linkedEntityIds: z.array(idSchema).optional(), source: z.string().optional(), importance: z.string().optional() });
|
||||
|
||||
export const apiPaths = Object.freeze({
|
||||
auth: {
|
||||
login: "/api/auth/login", session: "/api/auth/session", logout: "/api/auth/logout", rotatePassword: "/api/auth/rotate-password",
|
||||
entraConfig: "/api/auth/entra/config", entraStart: "/api/auth/entra/start", entraCallback: "/api/auth/entra/callback",
|
||||
entraStatus: "/api/auth/entra/status", entraLinkStart: "/api/auth/entra/link/start", entraIdentity: "/api/auth/entra/identity",
|
||||
},
|
||||
captureItems: (projectId = ":projectId") => `/api/projects/${projectId}/capture-items`,
|
||||
classifyCapture: (projectId = ":projectId", captureItemId = ":captureItemId") => `/api/projects/${projectId}/capture-items/${captureItemId}/classify`,
|
||||
sendCaptureToReview: (projectId = ":projectId", captureItemId = ":captureItemId") => `/api/projects/${projectId}/capture-items/${captureItemId}/send-to-review`,
|
||||
acceptReview: (projectId = ":projectId", reviewItemId = ":reviewItemId") => `/api/projects/${projectId}/review-queue/${reviewItemId}/accept`,
|
||||
materializeTruth: (projectId = ":projectId", reviewItemId = ":reviewItemId") => `/api/projects/${projectId}/review-queue/${reviewItemId}/materialize-truth`,
|
||||
createProjectUpdate: (projectId = ":projectId", truthItemId = ":truthItemId") => `/api/projects/${projectId}/accepted-truth/${truthItemId}/project-updates`,
|
||||
applyProjectUpdate: (projectId = ":projectId", projectUpdateId = ":projectUpdateId") => `/api/projects/${projectId}/project-updates/${projectUpdateId}/apply`,
|
||||
handoffBundles: (projectId = ":projectId") => `/api/projects/${projectId}/handoff-bundles`,
|
||||
codexTaskFromHandoff: (projectId = ":projectId", handoffBundleId = ":handoffBundleId") => `/api/projects/${projectId}/handoff-bundles/${handoffBundleId}/codex-task`,
|
||||
importCodexOutput: (projectId = ":projectId", codexTaskId = ":codexTaskId") => `/api/projects/${projectId}/codex-tasks/${codexTaskId}/import-output`,
|
||||
projectActivity: (projectId = ":projectId") => `/api/projects/${projectId}/activity`,
|
||||
projectActivityRuntime: (projectId = ":projectId") => `/api/projects/${projectId}/activity/runtime`,
|
||||
repositoryFindings: (projectId = ":projectId") => `/api/projects/${projectId}/findings`,
|
||||
repositoryFinding: (projectId = ":projectId", findingId = ":findingId") => `/api/projects/${projectId}/findings/${findingId}`,
|
||||
findingIssue: (projectId = ":projectId", findingId = ":findingId") => `/api/projects/${projectId}/findings/${findingId}/issue`,
|
||||
findingInvestigation: (projectId = ":projectId", findingId = ":findingId") => `/api/projects/${projectId}/findings/${findingId}/investigations`,
|
||||
findingSuppression: (projectId = ":projectId", findingId = ":findingId") => `/api/projects/${projectId}/findings/${findingId}/suppress`,
|
||||
findingAutomationPolicy: (projectId = ":projectId") => `/api/projects/${projectId}/finding-automation-policy`,
|
||||
portfolioControlPlane: "/api/portfolio/control-plane",
|
||||
portfolioRuntime: "/api/portfolio/runtime",
|
||||
portfolioSourceSync: "/api/portfolio/sources/sync-git",
|
||||
projectSourceSync: (projectId = ":projectId") => `/api/projects/${projectId}/sources/sync-git`,
|
||||
portfolioRepositories: "/api/portfolio/repositories",
|
||||
portfolioReconcile: "/api/portfolio/reconcile",
|
||||
portfolioAnalysisJobs: "/api/portfolio/analysis-jobs",
|
||||
portfolioAnalysisJob: (jobId = ":jobId") => `/api/portfolio/analysis-jobs/${jobId}`,
|
||||
portfolioAnalysisJobResults: (jobId = ":jobId") => `/api/portfolio/analysis-jobs/${jobId}/results`,
|
||||
portfolioAnalysisJobCancel: (jobId = ":jobId") => `/api/portfolio/analysis-jobs/${jobId}/cancel`,
|
||||
portfolioRepositoryExclude: (repositoryId = ":repositoryId") => `/api/portfolio/repositories/${repositoryId}/exclude`,
|
||||
portfolioRepositoryRestore: (repositoryId = ":repositoryId") => `/api/portfolio/repositories/${repositoryId}/restore`,
|
||||
workGraph: (projectId = ":projectId") => `/api/projects/${projectId}/work-graph`,
|
||||
workGraphReady: (projectId = ":projectId") => `/api/projects/${projectId}/work-graph/ready`,
|
||||
workGraphBlockers: (projectId = ":projectId") => `/api/projects/${projectId}/work-graph/blockers`,
|
||||
workGraphHistory: (projectId = ":projectId") => `/api/projects/${projectId}/work-graph/history`,
|
||||
workGraphClaim: (projectId = ":projectId") => `/api/projects/${projectId}/work-graph/claim`,
|
||||
workGraphReclaim: (projectId = ":projectId") => `/api/projects/${projectId}/work-graph/reclaim`,
|
||||
workGraphRelease: (projectId = ":projectId", nodeId = ":nodeId") => `/api/projects/${projectId}/work-graph/${nodeId}/release`,
|
||||
workGraphComplete: (projectId = ":projectId", nodeId = ":nodeId") => `/api/projects/${projectId}/work-graph/${nodeId}/complete`,
|
||||
workGraphDependencies: (projectId = ":projectId", nodeId = ":nodeId") => `/api/projects/${projectId}/work-graph/${nodeId}/dependencies`,
|
||||
definitionOfDone: (projectId = ":projectId") => `/api/projects/${projectId}/definition-of-done`,
|
||||
autonomySimulation: (projectId = ":projectId") => `/api/projects/${projectId}/autonomy/simulation`,
|
||||
projectAttention: (projectId = ":projectId") => `/api/projects/${projectId}/attention`,
|
||||
projectKnowledge: (projectId = ":projectId") => `/api/projects/${projectId}/project-knowledge`,
|
||||
projectKnowledgeConflicts: (projectId = ":projectId") => `/api/projects/${projectId}/project-knowledge/conflicts`,
|
||||
projectEvidenceGraph: (projectId = ":projectId") => `/api/projects/${projectId}/evidence-graph`,
|
||||
diagnostics: "/api/diagnostics",
|
||||
diagnosticsHistory: "/api/diagnostics/history",
|
||||
projectDiagnostics: (projectId = ":projectId") => `/api/projects/${projectId}/diagnostics`,
|
||||
projectPulse: (projectId = ":projectId") => `/api/projects/${projectId}/pulse`,
|
||||
portfolioPulse: "/api/portfolio/pulse",
|
||||
commandInterpret: "/api/commands/interpret",
|
||||
projectCommandInterpret: (projectId = ":projectId") => `/api/projects/${projectId}/commands/interpret`,
|
||||
portfolioViews: "/api/portfolio/views",
|
||||
portfolioView: (viewId = ":viewId") => `/api/portfolio/views/${viewId}`,
|
||||
portfolioPins: "/api/portfolio/pins",
|
||||
portfolioPin: (projectId = ":projectId") => `/api/portfolio/pins/${projectId}`,
|
||||
});
|
||||
|
||||
const openApiPath = (path) => path.replace(/:([A-Za-z][A-Za-z0-9]*)/g, "{$1}");
|
||||
const operations = [
|
||||
["post", apiPaths.auth.login, "Authenticate owner or member"], ["get", apiPaths.auth.session, "Read the active session"], ["post", apiPaths.auth.logout, "Revoke the active session"], ["post", apiPaths.auth.rotatePassword, "Rotate owner credentials"],
|
||||
["get", apiPaths.auth.entraConfig, "Read public Microsoft Entra availability"], ["get", apiPaths.auth.entraStart, "Start Microsoft Entra login"], ["get", apiPaths.auth.entraCallback, "Complete Microsoft Entra login"], ["get", apiPaths.auth.entraStatus, "Read linked Microsoft Entra identity status"], ["post", apiPaths.auth.entraLinkStart, "Start authenticated Microsoft Entra identity linking"], ["delete", apiPaths.auth.entraIdentity, "Unlink Microsoft Entra identity after password confirmation"],
|
||||
["post", apiPaths.captureItems(), "Capture source material"], ["patch", apiPaths.classifyCapture(), "Classify captured source"], ["post", apiPaths.sendCaptureToReview(), "Queue captured source for review"], ["post", apiPaths.acceptReview(), "Accept a review decision"], ["post", apiPaths.materializeTruth(), "Materialize accepted project truth"], ["post", apiPaths.createProjectUpdate(), "Prepare a project update"], ["patch", apiPaths.applyProjectUpdate(), "Apply a project update"], ["post", apiPaths.handoffBundles(), "Create a controlled handoff"], ["post", apiPaths.codexTaskFromHandoff(), "Create a Codex task"], ["post", apiPaths.importCodexOutput(), "Import execution feedback"],
|
||||
["get", apiPaths.projectActivity(), "Read durable project, delivery and repository activity"],
|
||||
["get", apiPaths.projectActivityRuntime(), "Read compact live project, Gitea runner and analysis-worker activity"],
|
||||
["get", apiPaths.portfolioRuntime, "Read platform-wide runner and worker activity"],
|
||||
["post", apiPaths.portfolioSourceSync, "Synchronize safe Git sources for the portfolio"],
|
||||
["post", apiPaths.projectSourceSync(), "Synchronize safe Git sources for one project"],
|
||||
["get", apiPaths.repositoryFindings(), "List durable repository findings"], ["get", apiPaths.repositoryFinding(), "Read one repository finding"],
|
||||
["post", apiPaths.findingIssue(), "Create or synchronize a finding issue"], ["post", apiPaths.findingInvestigation(), "Start a bounded finding investigation"],
|
||||
["post", apiPaths.findingSuppression(), "Suppress a finding with a reason"], ["get", apiPaths.findingAutomationPolicy(), "Read closed-loop automation policy"],
|
||||
["put", apiPaths.findingAutomationPolicy(), "Update closed-loop automation policy"],
|
||||
["get", apiPaths.portfolioControlPlane, "Read the central portfolio control plane"], ["get", apiPaths.portfolioRepositories, "List durable repository onboarding state"],
|
||||
["post", apiPaths.portfolioReconcile, "Preview or apply idempotent repository onboarding"], ["post", apiPaths.portfolioAnalysisJobs, "Queue a bounded portfolio analysis"],
|
||||
["get", apiPaths.portfolioAnalysisJob(), "Read one portfolio analysis job"], ["get", apiPaths.portfolioAnalysisJobResults(), "Read a bounded portfolio analysis result page"],
|
||||
["post", apiPaths.portfolioAnalysisJobCancel(), "Cancel a portfolio analysis job"],
|
||||
["post", apiPaths.portfolioRepositoryExclude(), "Exclude a repository from automatic onboarding"], ["post", apiPaths.portfolioRepositoryRestore(), "Restore a repository for onboarding"],
|
||||
["get", apiPaths.workGraph(), "Read the durable cross-domain project work graph"],
|
||||
["get", apiPaths.workGraphReady(), "List ready cross-domain work"], ["get", apiPaths.workGraphBlockers(), "Inspect work graph blockers"], ["get", apiPaths.workGraphHistory(), "Read work graph audit history"],
|
||||
["post", apiPaths.workGraphClaim(), "Claim the highest-value ready work atomically"], ["post", apiPaths.workGraphReclaim(), "Reclaim expired work leases"], ["post", apiPaths.workGraphRelease(), "Release a work lease"], ["post", apiPaths.workGraphComplete(), "Record work completion and evidence"], ["put", apiPaths.workGraphDependencies(), "Update durable work dependencies"],
|
||||
["get", apiPaths.definitionOfDone(), "Evaluate project definition of done"], ["put", apiPaths.definitionOfDone(), "Save project completion requirements"], ["get", apiPaths.autonomySimulation(), "Simulate the current autonomous plan"], ["get", apiPaths.projectAttention(), "Read the deduplicated project attention inbox"],
|
||||
["get", apiPaths.projectKnowledge(), "Read the unified project knowledge view"], ["get", apiPaths.projectKnowledgeConflicts(), "Read project truth conflicts and reconciliation provenance"],
|
||||
["get", apiPaths.projectEvidenceGraph(), "Read requirement-to-runtime evidence relationships"],
|
||||
["post", apiPaths.diagnostics, "Run safe platform diagnostics"], ["get", apiPaths.diagnosticsHistory, "Read recent diagnostic history"], ["post", apiPaths.projectDiagnostics(), "Run safe project diagnostics"], ["get", apiPaths.projectPulse(), "Read the concise project pulse"], ["get", apiPaths.portfolioPulse, "Read the concise portfolio pulse"],
|
||||
["post", apiPaths.commandInterpret, "Interpret a natural-language control command"], ["post", apiPaths.projectCommandInterpret(), "Interpret a project control command"], ["get", apiPaths.portfolioViews, "List saved portfolio views"], ["post", apiPaths.portfolioViews, "Create a saved portfolio view"], ["patch", apiPaths.portfolioView(), "Update a saved portfolio view"], ["delete", apiPaths.portfolioView(), "Delete a saved portfolio view"], ["get", apiPaths.portfolioPins, "List per-user pinned projects"], ["put", apiPaths.portfolioPin(), "Pin or unpin a project for the current user"],
|
||||
];
|
||||
|
||||
const openApiPaths = () => operations.reduce((paths, [method, path, summary]) => {
|
||||
const normalizedPath = openApiPath(path);
|
||||
paths[normalizedPath] = {
|
||||
...(paths[normalizedPath] ?? {}),
|
||||
[method]: { summary, responses: { 200: { description: "Successful canonical envelope" }, 201: { description: "Created canonical envelope" }, 400: { description: "Validation error envelope" }, 401: { description: "Authentication required" }, 403: { description: "Authorization or CSRF denied" }, 409: { description: "State transition conflict" } } },
|
||||
};
|
||||
return paths;
|
||||
}, {});
|
||||
|
||||
export const buildOpenApiDocument = () => ({
|
||||
openapi: "3.1.0",
|
||||
info: { title: "ProjectBrain API", version: "1.0.0", description: "Authenticated private-network API; generated from @projectbrain/contracts." },
|
||||
servers: [{ url: "/" }],
|
||||
paths: openApiPaths(),
|
||||
components: { schemas: {
|
||||
ApiMeta: { type: "object", required: ["requestId", "timestamp"], properties: { requestId: { type: "string", minLength: 1 }, timestamp: { type: "string", format: "date-time" } } },
|
||||
ApiError: { type: "object", required: ["error", "meta"], properties: { error: { type: "object", required: ["code", "message"], properties: { code: { type: "string" }, message: { type: "string" } } }, meta: { $ref: "#/components/schemas/ApiMeta" } } },
|
||||
} },
|
||||
});
|
||||
Reference in new issue
Block a user