import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; import { mkdir, mkdtemp, readFile, rm, symlink, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import * as path from "node:path"; import test from "node:test"; import { promisify } from "node:util"; import { DEFAULT_WORKFLOW_CONFIG, type AgentProfile, type GateDecision, type GateKind, type WorkflowAssignment, type WorkflowRun, } from "./domain.ts"; import { decisionIsLegal, WorkflowEngine, type AssignmentExecutor, type AssignmentRequest, type HostCommandExecutor, type StartWorkflowCommand, type WorkflowCommand, type WorkspaceManager, } from "./engine.ts"; import { loadAgentProfiles } from "./profiles.ts"; import { runScriptSandbox } from "./script-runtime.ts"; import { WorkflowStore } from "./store.ts"; import { GitWorkspaceManager, WorkspaceSafetyError, type PrepareWorkspaceOptions, type WorkspacePlan, } from "./workspace.ts"; const execFileAsync = promisify(execFile); async function withTempDir(run: (directory: string) => Promise) { const directory = await mkdtemp(path.join(tmpdir(), "pi-workflow-engine-")); try { await run(directory); } finally { await rm(directory, { recursive: true, force: true, maxRetries: 10, retryDelay: 20, }); } } class ScriptedExecutor implements AssignmentExecutor { readonly requests: AssignmentRequest[] = []; readonly outputs = new Map(); enqueue(role: WorkflowAssignment["role"], output: unknown) { const queued = this.outputs.get(role) ?? []; queued.push(output); this.outputs.set(role, queued); } async run(request: AssignmentRequest) { this.requests.push(structuredClone(request)); const queued = this.outputs.get(request.role) ?? []; if (queued.length === 0) throw new Error(`No scripted ${request.role} result`); const output = queued.shift(); if (output instanceof Error) throw output; return structuredClone(output); } } class BlockingExecutor implements AssignmentExecutor { readonly requests: AssignmentRequest[] = []; readonly started: Promise; private readonly output: unknown; private releaseRun?: () => void; private markStarted?: () => void; constructor(output: unknown) { this.output = output; this.started = new Promise((resolve) => { this.markStarted = resolve; }); } async run(request: AssignmentRequest) { this.requests.push(request); this.markStarted?.(); await new Promise((resolve) => { this.releaseRun = resolve; }); return structuredClone(this.output); } release() { this.releaseRun?.(); } } class FakeWorkspace implements WorkspaceManager { prepareCalls = 0; createCalls = 0; reconcileCalls = 0; branchExists = false; createError?: Error; reconcileState: WorkflowRun["workspace"]["state"] = "ready"; beforeCreate?: (plan: WorkspacePlan) => Promise; async prepare(options: PrepareWorkspaceOptions): Promise { this.prepareCalls++; return { sourceRoot: options.sourceRoot, worktreeRoot: path.join(options.sourceRoot, options.configuredRoot), worktreePath: path.join( options.sourceRoot, options.configuredRoot, `pi-workflow-${options.featureName}-${options.runId}`, ), branch: `feature/${options.featureName}`, baseHead: "abc123", branchExists: this.branchExists, }; } async create(plan: WorkspacePlan) { this.createCalls++; await this.beforeCreate?.(plan); if (this.createError) throw this.createError; await mkdir(plan.worktreePath, { recursive: true }); return { state: "ready" as const, sourceRoot: plan.sourceRoot, root: plan.worktreePath, branch: plan.branch, baseHead: plan.baseHead, }; } async reconcile(workspace: WorkflowRun["workspace"]) { this.reconcileCalls++; return { ...workspace, state: this.reconcileState, ...(this.reconcileState === "failed" ? { error: "reconciliation failed" } : { error: undefined }), }; } } interface Fixture { store: WorkflowStore; workspace: FakeWorkspace; executor: ScriptedExecutor; profiles: AgentProfile[]; engine( sessionId?: string, overrides?: Partial[0]>, ): WorkflowEngine; } async function fixture( directory: string, storeOptions: ConstructorParameters[1] = {}, ): Promise { const store = new WorkflowStore(path.join(directory, "state"), storeOptions); const workspace = new FakeWorkspace(); const executor = new ScriptedExecutor(); const profiles = ( await loadAgentProfiles({ cwd: directory, projectTrusted: false }) ).profiles; let id = 0; const createId = () => `id-${++id}`; return { store, workspace, executor, profiles, engine(sessionId = "session-1", overrides = {}) { return new WorkflowEngine({ store, workspace, executor, hostCommands: { async run() { return { result: { code: 0, stdout: "passed", stderr: "" } }; }, }, reviewDiff: { async read() { return "diff --git a/src/index.ts b/src/index.ts"; }, }, prototypeValidator: { async validateCandidate({ output }) { return { artifactFiles: [], visualDigest: createHash("sha256") .update(output.path) .digest("hex"), }; }, async validateCleanup() {}, }, sourceRoot: directory, parentSessionId: sessionId, effectiveConfig: structuredClone(DEFAULT_WORKFLOW_CONFIG), profiles, now: () => 10_000, createId, processId: 100, isProcessAlive: () => true, ...overrides, }); }, }; } function startCommand( overrides: Partial> = {}, ): StartWorkflowCommand { return { action: "start" as const, request: "Build a searchable dashboard", featureName: "searchable-dashboard", idempotencyKey: "start-key", ...overrides, }; } function discoveryWithQuestions() { return { questions: [{ id: "data-source", prompt: "Which data source?" }], candidatePaths: ["src/routes/dashboard.ts"], languages: ["typescript"], tags: ["frontend", "ui"], riskTags: ["migration"], }; } function enqueueSuccessfulPipeline(setup: Fixture) { setup.executor.enqueue("implement", { summary: "Implemented the approved plan", changedPaths: ["src/index.ts"], focusedTests: ["feature behavior"], }); setup.executor.enqueue("verify", { succeeded: true, focusedBehavioralTests: true, summary: "Focused behavior verified", }); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); } function design(uiImpact: "none" | "minor" | "non-trivial" = "minor") { return { plan: "# Approved plan\n\nImplement the dashboard.", uiImpact, uiImpactEvidence: ["Planner classified the dashboard change"], affectedUiSurfaces: ["Dashboard"], acceptanceCriteria: ["Changed behavior has focused tests"], verificationCommands: [ { executable: "npm", args: ["test"], ownedPaths: [], kind: "test", }, ], assignments: [ { role: "implement", profileName: "feature-implementer", rationale: "Implement the approved plan", mutates: true, ownedPaths: ["src"], }, { role: "verify", profileName: "feature-verifier", rationale: "Verify the implementation", mutates: false, ownedPaths: [], }, { role: "review", profileName: "independent-reviewer", rationale: "Review independently", mutates: false, ownedPaths: [], }, ...(uiImpact === "non-trivial" ? [ { role: "prototype", profileName: "ui-prototyper", rationale: "Prototype the non-trivial UI", mutates: true, ownedPaths: ["prototypes"], }, ] : []), ], }; } test("analysis scripts run persisted read-only Assignments with tool intersection", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const engine = setup.engine("session-1", { scriptRuntime: { run: runScriptSandbox }, }); const command = { action: "start-analysis" as const, request: "Audit persistence behavior", source: ` stage("Persistence audit"); const result = await assign("review", "Inspect persistence behavior"); return result.structured; `, args: { scope: "store" }, activeToolNames: ["read", "grep", "bash"], idempotencyKey: "analysis-script-key", }; const completed = await engine.execute(command); assert.equal(completed.status, "completed"); assert.equal(setup.workspace.prepareCalls, 0); const persisted = await setup.store.loadRun(completed.runId); assert.equal(persisted.mode, "analysis"); assert.equal(persisted.script?.status, "completed"); assert.deepEqual(persisted.script?.stages, ["Persistence audit"]); assert.equal(persisted.assignments.length, 1); assert.equal(persisted.assignments[0]?.mutates, false); assert.deepEqual(setup.executor.requests[0]?.allowedReadTools, ["read", "grep"]); assert.deepEqual(await engine.execute(command), completed); assert.equal(setup.executor.requests.length, 1); }); }); test("script Recovery reuses completed request IDs without orphaning interrupted work", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const engine = setup.engine("session-1", { scriptRuntime: { run: runScriptSandbox }, }); const completed = await engine.execute({ action: "start-analysis", request: "Audit recovery", source: `return await assign("review", "First request");`, activeToolNames: ["read"], idempotencyKey: "analysis-recovery-start", }); const interrupted = await setup.store.loadRun(completed.runId); assert.ok(interrupted.script); interrupted.script.source = ` const first = await assign("review", "First request"); const second = await assign("review", "Second request"); return [first, second]; `; interrupted.script.status = "running"; interrupted.script.executed = false; interrupted.status = "waiting"; const firstAssignment = interrupted.assignments[0]!; interrupted.assignments.push({ ...structuredClone(firstAssignment), id: "interrupted-script-request", status: "interrupted", result: undefined, scriptRequestId: 2, scriptRequestFingerprint: "interrupted", }); const recoveryGateId = "script-recovery-gate"; interrupted.gates.push({ id: recoveryGateId, kind: "recovery", status: "open", revision: interrupted.revision + 1, openedAt: 10_000, payload: { interruptedAssignmentIds: ["interrupted-script-request"] }, }); interrupted.revision++; await setup.store.saveRun(interrupted, interrupted.revision - 1); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const recovered = await engine.execute({ action: "decide", runId: interrupted.runId, gateId: recoveryGateId, expectedRevision: interrupted.revision, decision: { kind: "recover", resolution: "retry" }, idempotencyKey: "retry-analysis-script", }); assert.equal(recovered.status, "completed", recovered.error?.message); assert.equal(setup.executor.requests.length, 2); const persisted = await setup.store.loadRun(recovered.runId); assert.equal( persisted.assignments.find( (assignment) => assignment.id === "interrupted-script-request", )?.status, "interrupted", ); assert.equal( persisted.assignments.filter( (assignment) => assignment.scriptRequestId === 1, ).length, 1, ); }); }); test("script abort waits for active Assignments and fails closed on timeout", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const blocking = new BlockingExecutor({ specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const engine = setup.engine("session-1", { executor: blocking, scriptRuntime: { run: runScriptSandbox }, assignmentAbortTimeoutMs: 10, }); const start = engine.execute({ action: "start-analysis", request: "Audit cancellation", source: `return await assign("review", "Block until released");`, activeToolNames: ["read"], idempotencyKey: "blocking-analysis", }); await blocking.started; const [running] = await setup.store.listRuns(); assert.ok(running); await assert.rejects( engine.execute({ action: "abort", runId: running.runId, expectedRevision: running.revision, idempotencyKey: "abort-blocking-analysis", }), /Timed out stopping active script/, ); assert.notEqual((await setup.store.loadRun(running.runId)).status, "aborted"); blocking.release(); const failed = await start; assert.equal(failed.status, "failed"); assert.match(failed.error?.message ?? "", /aborted/); }); }); test("feature scripts are insertion-bound and cannot bypass Plan Approval", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); setup.executor.enqueue("design", design("minor")); const planned = await setup.engine("session-1", { scriptRuntime: { run: runScriptSandbox }, }).execute( startCommand({ script: { source: ` stage("Pre-design audit"); return await assign("review", "Inspect design precedents"); `, insertionPoint: "before-design", activeToolNames: ["read", "grep", "find", "ls", "bash"], }, }), ); assert.equal(planned.status, "waiting", planned.error?.message); assert.equal(planned.openGate?.kind, "plan-approval"); assert.deepEqual( setup.executor.requests.map((request) => request.role), ["discover", "review", "design"], ); const persisted = await setup.store.loadRun(planned.runId); assert.equal(persisted.script?.executed, true); assert.equal( persisted.assignments .filter((assignment) => assignment.selection.rationale.startsWith("Custom read-only script"), ) .some((assignment) => assignment.mutates), false, ); }); }); test("after-design feature scripts still leave Plan Approval mandatory", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const planned = await setup.engine("session-1", { scriptRuntime: { run: runScriptSandbox }, }).execute( startCommand({ script: { source: `return await assign("review", "Audit the completed design");`, insertionPoint: "after-design", activeToolNames: ["read", "grep", "find", "ls"], }, }), ); assert.equal(planned.status, "waiting"); assert.equal(planned.openGate?.kind, "plan-approval"); assert.deepEqual( setup.executor.requests.map((request) => request.role), ["discover", "design", "review"], ); }); }); test("before-review feature scripts cannot replace mandatory verification or review", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); setup.executor.enqueue("implement", { summary: "Implemented feature", changedPaths: ["src/index.ts"], focusedTests: ["feature behavior"], }); setup.executor.enqueue("verify", { succeeded: true, focusedBehavioralTests: true, summary: "Verified feature", }); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const engine = setup.engine("session-1", { scriptRuntime: { run: runScriptSandbox }, }); const planned = await engine.execute( startCommand({ script: { source: `return await assign("review", "Perform pre-review analysis");`, insertionPoint: "before-review", activeToolNames: ["read", "grep", "find", "ls"], }, }), ); assert.equal( (await setup.store.loadRun(planned.runId)).script?.executed, false, ); const approved = await engine.execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-before-review-script", }); const completed = await engine.execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "current-session", }, idempotencyKey: "context-before-review-script", }); assert.equal(completed.status, "completed"); const roles = setup.executor.requests.map((request) => request.role); assert.deepEqual(roles, [ "discover", "design", "implement", "verify", "review", "review", ]); assert.equal( (await setup.store.loadRun(completed.runId)).script?.executed, true, ); }); }); test("feature scripts reject mutating roles and invalid insertion points", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); const failed = await setup.engine("session-1", { scriptRuntime: { run: runScriptSandbox }, }).execute( startCommand({ script: { source: `return await assign("implement", "Mutate before approval");`, insertionPoint: "before-design", activeToolNames: ["read"], }, }), ); assert.equal(failed.status, "failed"); assert.match(failed.error?.message ?? "", /not read-only or supported/); await assert.rejects( setup.engine().execute({ ...startCommand({ idempotencyKey: "invalid-insertion" }), script: { source: "return null", insertionPoint: "during-implementation", activeToolNames: ["read"], }, } as unknown as WorkflowCommand), /insertionPoint is invalid/, ); assert.equal(setup.workspace.prepareCalls, 1); }); }); test("background feature starts return after durable publication and continue to a Gate", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const blocking = new BlockingExecutor(discoveryWithQuestions()); const persistedDeliveries: string[] = []; const engine = setup.engine("session-1", { executor: blocking, afterRunPersist(run) { persistedDeliveries.push( ...run.deliveries .filter((delivery) => delivery.state === "pending") .map((delivery) => delivery.key), ); }, }); const command = startCommand({ background: true, idempotencyKey: "background-start", }); const published = await engine.execute(command); assert.equal(published.background, true); assert.equal(published.workspace.state, "creating"); await blocking.started; blocking.release(); let completed = published; for (let attempt = 0; attempt < 20; attempt++) { completed = await engine.execute({ action: "inspect", runId: published.runId }); if (completed.openGate?.kind === "questions") break; await new Promise((resolve) => setTimeout(resolve, 1)); } assert.equal(completed.openGate?.kind, "questions"); assert.equal(completed.status, "waiting"); for (let attempt = 0; attempt < 20; attempt++) { if ((await setup.store.getStart(command.idempotencyKey))?.status === "completed") { break; } await new Promise((resolve) => setTimeout(resolve, 1)); } assert.equal( (await setup.store.getStart(command.idempotencyKey))?.status, "completed", ); assert.equal( persistedDeliveries.some((key) => key.startsWith("gate:")), true, ); assert.deepEqual(await engine.execute(command), completed); await new Promise((resolve) => setTimeout(resolve, 20)); }); }); test("run concurrency may lower but never raise the configured ceiling", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const started = await setup.engine().execute( startCommand({ concurrency: 2, idempotencyKey: "concurrency-two" }), ); assert.equal( (await setup.store.loadRun(started.runId)).effectiveConfig.concurrency.default, 2, ); assert.equal(setup.executor.requests[0]?.runConcurrency, 2); await assert.rejects( setup.engine().execute( startCommand({ concurrency: DEFAULT_WORKFLOW_CONFIG.concurrency.ceiling + 1, idempotencyKey: "concurrency-too-high", featureName: "other-feature", }), ), /cannot exceed ceiling/, ); }); }); test("Questions and Plan Approval persist and resume across engine instances", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); setup.executor.enqueue("design", design("minor")); setup.workspace.beforeCreate = async () => { const [persisted] = await setup.store.listRuns(); assert.equal(persisted?.workspace.state, "creating"); assert.equal(persisted?.lease?.activeParentSessionId, "session-1"); assert.equal(persisted?.assignments.length, 0); }; const started = await setup.engine().execute(startCommand()); assert.equal(started.status, "waiting"); assert.equal(started.openGate?.kind, "questions"); assert.equal(setup.workspace.createCalls, 1); const answered = await setup.engine().execute({ action: "decide", runId: started.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: started.revision, decision: { kind: "answer-questions", answers: { "data-source": "Repository API" }, }, idempotencyKey: "answer-key", }); assert.equal(answered.status, "waiting"); assert.equal(answered.openGate?.kind, "plan-approval"); assert.equal(answered.uiImpact, "minor"); assert.match( await readFile(answered.planArtifact?.path ?? "missing", "utf8"), /Implement the dashboard/, ); const designRequest = setup.executor.requests.find( (request) => request.role === "design", ); assert.equal(designRequest?.decisions[0]?.value.kind, "answer-questions"); const inspected = await setup.engine().execute({ action: "inspect", runId: answered.runId, }); assert.deepEqual(inspected, answered); const approved = await setup.engine().execute({ action: "decide", runId: answered.runId, gateId: answered.openGate?.id ?? "missing", expectedRevision: answered.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-key", }); assert.equal(approved.status, "waiting"); assert.equal(approved.openGate?.kind, "implementation-context"); assert.equal(approved.implementationContext?.status, "waiting"); assert.equal( approved.stages.find((stage) => stage.kind === "design")?.status, "completed", ); const approvedPlan = await readFile( approved.planArtifact?.path ?? "missing", "utf8", ); assert.match(approvedPlan, /Accepted Decisions/); assert.match(approvedPlan, /Worktree/); assert.match(approvedPlan, /UI Impact Evidence/); assert.match(approvedPlan, /acceptedUiImpact/); const persisted = await setup.store.loadRun(approved.runId); assert.equal(persisted.decisions.length, 2); assert.equal(persisted.deliveries.length, 3); assert.deepEqual( persisted.preflightEligibility.map((entry) => entry.role), ["discover", "design", "implement", "verify", "review"], ); assert.ok( persisted.preflightEligibility.every((entry) => entry.candidates.some((candidate) => candidate.eligible), ), ); }); }); test("Implementation Context choices are persisted and Gate-bound", async () => { for (const strategy of ["fresh-session", "current-session"] as const) { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); if (strategy === "current-session") enqueueSuccessfulPipeline(setup); const planned = await setup.engine().execute(startCommand()); const contextGate = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: `approve-${strategy}`, }); const selected = await setup.engine().execute({ action: "decide", runId: contextGate.runId, gateId: contextGate.openGate?.id ?? "missing", expectedRevision: contextGate.revision, decision: { kind: "select-implementation-context", strategy }, idempotencyKey: `context-${strategy}`, }); if (strategy === "fresh-session") { assert.equal(selected.status, "waiting"); assert.equal(selected.openGate?.kind, "implementation-context"); assert.equal(selected.implementationContext?.status, "handoff-pending"); } else { assert.equal(selected.status, "completed"); assert.equal(selected.openGate, undefined); assert.equal(selected.implementationContext?.status, "ready"); assert.equal(selected.implementationContext?.method, "unchanged"); } }); } }); test("Implementation Context rejects transitions while a child Assignment is active", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-active-context", }); const persisted = await setup.store.loadRun(approved.runId); const queued = persisted.assignments.find( (assignment) => assignment.role === "implement", ); assert.ok(queued); queued.status = "running"; queued.executionToken = "active-context-token"; persisted.revision++; await setup.store.saveRun(persisted, persisted.revision - 1); await assert.rejects( setup.engine().execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: persisted.revision, decision: { kind: "select-implementation-context", strategy: "current-session", }, idempotencyKey: "select-active-context", }), /while an Assignment is active/, ); }); }); test("fresh-session handoff is revision-bound and rotates the active parent only on consume", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); enqueueSuccessfulPipeline(setup); const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-handoff", }); const selected = await setup.engine().execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "fresh-session", }, idempotencyKey: "select-handoff", }); const intent = selected.implementationContext; assert.ok(intent); const continued = await setup.engine().execute({ action: "continue-context", runId: selected.runId, gateId: intent.gateId, preparationId: intent.preparationId, planRevision: intent.planRevision, contextRevision: intent.runRevision, expectedRevision: selected.revision, sourceSessionFile: "/sessions/source.jsonl", }); const pending = continued.implementationContext; assert.ok(pending); const claimed = await setup.engine().execute({ action: "claim-context-handoff", runId: continued.runId, gateId: pending.gateId, preparationId: pending.preparationId, planRevision: pending.planRevision, contextRevision: pending.runRevision, expectedRevision: continued.revision, sourceSessionFile: "/sessions/source.jsonl", targetSessionFile: "/sessions/target.jsonl", reason: "new", }); assert.equal( claimed.implementationContext?.status, "claimed-target-awaiting-withSession", ); const claim = claimed.implementationContext; assert.ok(claim); await assert.rejects( setup.engine("unrelated-session", { processId: 200, isProcessAlive: (processId) => processId === 100, }).execute({ action: "recover-context-intent", runId: claimed.runId, gateId: claim.gateId, preparationId: claim.preparationId, planRevision: claim.planRevision, contextRevision: claim.runRevision, expectedRevision: claimed.revision, reason: "startup", }), /owned by live process 100/, ); assert.equal( (await setup.store.loadRun(claimed.runId)).implementationContext?.status, "claimed-target-awaiting-withSession", ); const consumed = await setup.engine("session-2").execute({ action: "consume-context-handoff", runId: claimed.runId, gateId: claim.gateId, preparationId: claim.preparationId, planRevision: claim.planRevision, contextRevision: claim.runRevision, expectedRevision: claimed.revision, targetSessionFile: "/sessions/target.jsonl", previousSessionFile: "/sessions/source.jsonl", }); assert.equal(consumed.implementationContext?.status, "ready"); assert.equal(consumed.implementationContext?.activeParentSessionId, "session-2"); assert.equal(consumed.openGate, undefined); await assert.rejects( setup.engine("session-1").execute({ action: "abort", runId: consumed.runId, expectedRevision: consumed.revision, idempotencyKey: "historical-abort", }), /Historical parent sessions/, ); }); }); test("smart compaction reports are fenced and unavailable alone enables fallback", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-smart", }); const selected = await setup.engine().execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "smart-compact", }, idempotencyKey: "select-smart", }); const preparation = selected.implementationContext; assert.equal(preparation?.status, "smart-compact-pending"); const unavailable = { action: "decide" as const, runId: selected.runId, gateId: selected.openGate?.id ?? "missing", expectedRevision: selected.revision, decision: { kind: "report-context-preparation" as const, preparationId: preparation?.preparationId ?? "missing", planRevision: preparation?.planRevision ?? -1, method: "smart_compact" as const, outcome: "unavailable" as const, }, idempotencyKey: "smart-unavailable", }; await assert.rejects(setup.engine().execute(unavailable), /activeToolNames/); await assert.rejects( setup.engine().execute({ ...unavailable, activeToolNames: ["smart_compact"], }), /smart_compact is active/, ); const fallback = await setup.engine().execute({ ...unavailable, activeToolNames: ["read", "grep"], }); assert.equal(fallback.status, "waiting"); assert.equal(fallback.openGate?.kind, "implementation-context"); assert.equal(fallback.implementationContext?.status, "normal-compact-pending"); assert.equal(fallback.implementationContext?.outcome, "unavailable"); const fallbackIntent = fallback.implementationContext; assert.ok(fallbackIntent); const compacting = await setup.engine().execute({ action: "continue-context", runId: fallback.runId, gateId: fallbackIntent.gateId, preparationId: fallbackIntent.preparationId, planRevision: fallbackIntent.planRevision, contextRevision: fallbackIntent.runRevision, expectedRevision: fallback.revision, }); assert.equal(compacting.implementationContext?.status, "normal-compact-running"); const compactIntent = compacting.implementationContext; assert.ok(compactIntent); const failed = await setup.engine().execute({ action: "complete-normal-compaction", runId: compacting.runId, gateId: compactIntent.gateId, preparationId: compactIntent.preparationId, planRevision: compactIntent.planRevision, contextRevision: compactIntent.runRevision, expectedRevision: compacting.revision, outcome: "failed", error: "Native compaction failed", }); assert.equal(failed.status, "waiting"); assert.equal(failed.openGate?.kind, "implementation-context"); assert.equal(failed.implementationContext?.status, "failed"); assert.equal(failed.implementationContext?.error, "Native compaction failed"); }); }); test("recovery marks an interrupted normal compaction intent failed", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-interrupted-compact", }); const selected = await setup.engine().execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "smart-compact", }, idempotencyKey: "select-interrupted-compact", }); const intent = selected.implementationContext; assert.ok(intent); const fallback = await setup.engine().execute({ action: "decide", runId: selected.runId, gateId: intent.gateId, expectedRevision: selected.revision, decision: { kind: "report-context-preparation", preparationId: intent.preparationId, planRevision: intent.planRevision, method: "smart_compact", outcome: "unavailable", }, idempotencyKey: "unavailable-interrupted-compact", activeToolNames: ["read"], }); const fallbackIntent = fallback.implementationContext; assert.ok(fallbackIntent); await setup.engine().execute({ action: "continue-context", runId: fallback.runId, gateId: fallbackIntent.gateId, preparationId: fallbackIntent.preparationId, planRevision: fallbackIntent.planRevision, contextRevision: fallbackIntent.runRevision, expectedRevision: fallback.revision, }); const recovered = await setup.engine("session-1", { now: () => 100_000, leaseStaleMs: 1, processId: 200, isProcessAlive: () => false, }).recover(); const view = recovered.find((candidate) => candidate.runId === fallback.runId); assert.equal(view?.implementationContext?.status, "failed"); assert.match(view?.implementationContext?.error ?? "", /interrupted/); assert.equal(view?.openGate?.kind, "implementation-context"); }); }); test("smart compaction success, no-op, and failure outcomes remain distinct", async () => { for (const outcome of ["succeeded", "no-op", "failed"] as const) { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); if (outcome !== "failed") enqueueSuccessfulPipeline(setup); const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: `approve-smart-${outcome}`, }); const selected = await setup.engine().execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "smart-compact", }, idempotencyKey: `select-smart-${outcome}`, }); const intent = selected.implementationContext; assert.ok(intent); const decision: GateDecision = outcome === "failed" ? { kind: "report-context-preparation", preparationId: intent.preparationId, planRevision: intent.planRevision, method: "smart_compact", outcome, error: "smart_compact failed to prepare context", } : { kind: "report-context-preparation", preparationId: intent.preparationId, planRevision: intent.planRevision, method: "smart_compact", outcome, }; const report = await setup.engine().execute({ action: "decide", runId: selected.runId, gateId: intent.gateId, expectedRevision: selected.revision, decision, idempotencyKey: `report-smart-${outcome}`, activeToolNames: ["smart_compact"], }); assert.equal(report.implementationContext?.outcome, outcome); assert.equal(report.implementationContext?.method, "smart_compact"); if (outcome === "failed") { assert.equal(report.status, "waiting"); assert.equal(report.openGate?.kind, "implementation-context"); assert.match(report.implementationContext?.error ?? "", /failed/); } else { assert.equal(report.status, "completed"); assert.equal(report.openGate, undefined); assert.equal(report.implementationContext?.status, "ready"); } }); } }); test("implementation completes only after host verification and two-axis review", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", { ...design("minor"), acceptanceCriteria: ["Search behavior works"], verificationCommands: [ { executable: "npm", args: ["test", "--", "feature"], ownedPaths: ["src"], kind: "test", }, ], }); enqueueSuccessfulPipeline(setup); const hostCalls: string[] = []; const hostCommands: HostCommandExecutor = { async run(options) { hostCalls.push(`${options.executable} ${options.args.join(" ")}`); return { result: { code: 0, stdout: "passed", stderr: "" } }; }, }; const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-lifecycle", }); const completed = await setup.engine("session-1", { hostCommands }).execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "current-session", }, idempotencyKey: "context-lifecycle", }); assert.equal(completed.status, "completed"); assert.deepEqual(hostCalls, ["npm test -- feature"]); const persisted = await setup.store.loadRun(completed.runId); assert.equal(persisted.verificationEvidence?.commands[0]?.status, "succeeded"); assert.equal(persisted.verificationEvidence?.focusedBehavioralTests, true); assert.equal(persisted.reviewVerdict?.specification.verdict, "approved"); assert.equal(persisted.reviewVerdict?.standards.verdict, "approved"); assert.deepEqual(persisted.mutatorProfileNames, ["feature-implementer"]); assert.ok(persisted.reportArtifact); assert.match( await readFile(persisted.reportArtifact.path, "utf8"), /Status: completed[\s\S]*Verification[\s\S]*Review/, ); assert.equal( persisted.deliveries.some((delivery) => delivery.key.startsWith(`terminal:${persisted.runId}:completed:`), ), true, ); const result = JSON.parse( await readFile( path.join(path.dirname(path.dirname(persisted.reportArtifact.path)), "result.json"), "utf8", ), ) as { status?: string; revision?: number; snapshotRevision?: number }; assert.equal(result.status, "completed"); assert.equal(result.revision, persisted.reportArtifact.revision); assert.equal(result.snapshotRevision, persisted.revision - 1); const verifierRequest = setup.executor.requests.find( (request) => request.role === "verify", ); const reviewerRequest = setup.executor.requests.find( (request) => request.role === "review", ); assert.match(verifierRequest?.approvedPlan ?? "", /Acceptance Criteria/); assert.equal( verifierRequest?.verificationEvidence?.commands[0]?.status, "succeeded", ); assert.match(reviewerRequest?.reviewDiff ?? "", /diff --git/); assert.equal( reviewerRequest?.verificationEvidence?.focusedBehavioralTests, true, ); }); }); test("recovery consumes a persisted successful mutator without replay", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-before-crash", }); const crashed = await setup.store.loadRun(approved.runId); for (const gate of crashed.gates) gate.status = "satisfied"; assert.ok(crashed.implementationContext); crashed.implementationContext.status = "ready"; crashed.implementationContext.strategy = "current-session"; crashed.implementationContext.method = "unchanged"; crashed.status = "running"; const implement = crashed.assignments.find( (assignment) => assignment.role === "implement", ); assert.ok(implement); implement.status = "succeeded"; implement.result = { summary: "Mutation was durably completed before the crash", changedPaths: ["src/index.ts"], focusedTests: ["feature behavior"], }; implement.executionToken = undefined; const implementStage = crashed.stages.find( (candidate) => candidate.kind === "implement", ); assert.ok(implementStage); implementStage.status = "running"; crashed.lease = { processId: 999, activeParentSessionId: "session-1", heartbeatAt: 0, worktreeIdentity: crashed.workspace.root, }; crashed.revision++; crashed.implementationContext.runRevision = crashed.revision; await setup.store.saveRun(crashed, crashed.revision - 1); setup.executor.enqueue("verify", { succeeded: true, focusedBehavioralTests: true, summary: "Recovered implementation verified", }); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const [recovered] = await setup.engine("session-1", { processId: 200, now: () => 100_000, isProcessAlive: () => false, }).recover(); assert.equal(recovered?.status, "completed"); assert.equal( setup.executor.requests.filter((request) => request.role === "implement") .length, 0, ); assert.equal( (await setup.store.loadRun(crashed.runId)).assignments.find( (assignment) => assignment.id === implement.id, )?.status, "succeeded", ); }); }); test("Git or ownership safety failures open Recovery before remediation", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); setup.executor.enqueue("implement", { summary: "Implementation", changedPaths: ["src/index.ts"], focusedTests: ["behavior"], }); const hostCommands: HostCommandExecutor = { async run() { throw new WorkspaceSafetyError("HEAD changed during verification"); }, }; const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-safety-recovery", }); const waiting = await setup.engine("session-1", { hostCommands }).execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "current-session", }, idempotencyKey: "context-safety-recovery", }); assert.equal(waiting.status, "waiting"); assert.equal(waiting.openGate?.kind, "recovery"); assert.equal( waiting.assignments.some((assignment) => assignment.role === "remediate"), false, ); }); }); test("changes requested trigger serialized remediation, verification, and fresh review", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); setup.executor.enqueue("implement", { summary: "Initial implementation", changedPaths: ["src/index.ts"], focusedTests: ["initial test"], }); setup.executor.enqueue("verify", { succeeded: true, focusedBehavioralTests: true, summary: "Initial verification", }); setup.executor.enqueue("review", { specification: { verdict: "changes_requested", findings: [{ severity: "concern", message: "Handle the edge case" }], }, standards: { verdict: "approved", findings: [] }, }); setup.executor.enqueue("remediate", { summary: "Handled the edge case", changedPaths: ["src/index.ts"], focusedTests: ["edge case test"], }); setup.executor.enqueue("verify", { succeeded: true, focusedBehavioralTests: true, summary: "Remediation verified", }); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const planned = await setup.engine().execute(startCommand()); const approved = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "approve-remediation", }); const completed = await setup.engine().execute({ action: "decide", runId: approved.runId, gateId: approved.openGate?.id ?? "missing", expectedRevision: approved.revision, decision: { kind: "select-implementation-context", strategy: "current-session", }, idempotencyKey: "context-remediation", }); assert.equal(completed.status, "completed"); const persisted = await setup.store.loadRun(completed.runId); assert.equal( persisted.assignments.filter((assignment) => assignment.role === "remediate") .length, 1, ); assert.equal( persisted.assignments.filter( (assignment) => assignment.role === "review" && assignment.status === "succeeded", ).length, 2, ); assert.equal(persisted.reviewCycles, 2); assert.equal(persisted.verificationCycles, 0); const reviewerNames = persisted.assignments .filter((assignment) => assignment.role === "review") .map((assignment) => assignment.profileName); assert.ok(reviewerNames.every((name) => !persisted.mutatorProfileNames.includes(name))); }); }); test("non-trivial UI requires three prototypes, acceptance, and cleanup", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { questions: [], candidatePaths: ["src/routes/layout.svelte"], languages: ["svelte"], tags: ["frontend", "responsive"], riskTags: ["accessibility"], }); setup.executor.enqueue("design", design("non-trivial")); for (let index = 1; index <= 3; index++) { setup.executor.enqueue("prototype", { path: `prototypes/candidate-${index}`, route: `/prototype?variant=${index}`, structuralRationale: `Distinct structure ${index}`, responsiveChecks: ["mobile 390px", "desktop 1440px"], browserEvidence: [`browser screenshot ${index}`], launchInstructions: `npm run dev then open variant ${index}`, }); } const planned = await setup.engine().execute(startCommand()); const prototypes = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "non-trivial", }, idempotencyKey: "approve-prototype-plan", }); assert.equal(prototypes.openGate?.kind, "prototype-acceptance"); const persistedCandidates = ( await setup.store.loadRun(prototypes.runId) ).prototypeCandidates; assert.equal(persistedCandidates.length, 3); assert.equal( new Set(persistedCandidates.map((candidate) => candidate.path)).size, 3, ); assert.ok( persistedCandidates.every((candidate) => candidate.artifactPath.includes("artifacts/prototypes"), ), ); await assert.rejects( setup.engine().execute({ action: "decide", runId: prototypes.runId, gateId: prototypes.openGate?.id ?? "missing", expectedRevision: prototypes.revision, decision: { kind: "accept-prototype", candidateId: persistedCandidates[0]?.id ?? "missing", candidateRevision: (persistedCandidates[0]?.revision ?? 0) + 1, browserReviewed: true, }, idempotencyKey: "stale-prototype", }), /candidate or revision is stale/, ); const acceptedCandidate = persistedCandidates[1]!; const accepted = await setup.engine().execute({ action: "decide", runId: prototypes.runId, gateId: prototypes.openGate?.id ?? "missing", expectedRevision: prototypes.revision, decision: { kind: "accept-prototype", candidateId: acceptedCandidate.id, candidateRevision: acceptedCandidate.revision, browserReviewed: true, feedback: "Use the second navigation structure", }, idempotencyKey: "accept-prototype", }); assert.equal(accepted.openGate?.kind, "implementation-context"); assert.match( await readFile(accepted.planArtifact?.path ?? "missing", "utf8"), /Accepted Prototype[\s\S]*second navigation structure/, ); setup.executor.enqueue("implement", { summary: "Implemented accepted direction to production standards", changedPaths: ["src/routes/layout.svelte"], focusedTests: ["responsive navigation"], }); setup.executor.enqueue("remediate", { summary: "Removed prototype-only candidates and routes", changedPaths: ["prototypes"], focusedTests: [], }); setup.executor.enqueue("verify", { succeeded: true, focusedBehavioralTests: true, summary: "Responsive browser behavior verified", }); setup.executor.enqueue("review", { specification: { verdict: "approved", findings: [] }, standards: { verdict: "approved", findings: [] }, }); const completed = await setup.engine().execute({ action: "decide", runId: accepted.runId, gateId: accepted.openGate?.id ?? "missing", expectedRevision: accepted.revision, decision: { kind: "select-implementation-context", strategy: "current-session", }, idempotencyKey: "prototype-context", }); assert.equal(completed.status, "completed"); assert.equal( (await setup.store.loadRun(completed.runId)).prototypeCleanupCompleted, true, ); }); }); test("prototype refinement produces a fresh three-candidate revision", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { questions: [], candidatePaths: ["src/routes/layout.svelte"], languages: ["svelte"], tags: ["frontend", "responsive"], riskTags: [], }); setup.executor.enqueue("design", design("non-trivial")); for (let index = 1; index <= 3; index++) { setup.executor.enqueue("prototype", { path: `prototypes/candidate-${index}`, route: `/first?variant=${index}`, structuralRationale: `Initial structure ${index}`, responsiveChecks: ["mobile and desktop"], browserEvidence: [`initial browser evidence ${index}`], launchInstructions: "npm run dev", }); } const planned = await setup.engine().execute(startCommand()); assert.equal(planned.status, "waiting", planned.error?.message); assert.equal(planned.openGate?.kind, "plan-approval"); const firstGate = await setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "non-trivial", }, idempotencyKey: "approve-refinement-plan", }); const refinementRevision = firstGate.revision + 1; for (let index = 1; index <= 3; index++) { setup.executor.enqueue("prototype", { path: `prototypes/refinement-${refinementRevision}-candidate-${index}`, route: `/refined?variant=${index}`, structuralRationale: `Refined structure ${index}`, responsiveChecks: ["mobile and desktop"], browserEvidence: [`refined browser evidence ${index}`], launchInstructions: "npm run dev", }); } const refined = await setup.engine().execute({ action: "decide", runId: firstGate.runId, gateId: firstGate.openGate?.id ?? "missing", expectedRevision: firstGate.revision, decision: { kind: "refine-prototypes", feedback: "Increase information density", }, idempotencyKey: "refine-prototypes", }); assert.equal(refined.openGate?.kind, "prototype-acceptance"); const persisted = await setup.store.loadRun(refined.runId); assert.equal(persisted.prototypeCandidates.length, 6); assert.equal( persisted.gates.find((gate) => gate.id === firstGate.openGate?.id)?.status, "rejected", ); }); }); test("objective UI evidence upgrades a planner downgrade before approval", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { questions: [], candidatePaths: ["src/routes/layout.svelte"], languages: ["svelte"], tags: ["frontend"], riskTags: ["accessibility"], }); const downgraded = design("minor"); downgraded.assignments.push({ role: "prototype", profileName: "ui-prototyper", rationale: "Prototype the objective non-trivial UI change", mutates: true, ownedPaths: ["prototypes"], }); setup.executor.enqueue("design", downgraded); const planned = await setup.engine().execute( startCommand({ request: "Redesign the responsive navigation" }), ); assert.equal(planned.uiImpact, "non-trivial"); assert.ok( planned.uiImpactEvidence.some((evidence) => evidence.includes("responsive"), ), ); await assert.rejects( setup.engine().execute({ action: "decide", runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "minor", }, idempotencyKey: "reject-ui-downgrade", }), /Approved UI impact does not match the current plan/, ); }); }); test("concurrent Plan Approval cannot cross-link immutable plan content", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design("minor")); const planned = await setup.engine().execute(startCommand()); const command = (key: string, comment: string) => ({ action: "decide" as const, runId: planned.runId, gateId: planned.openGate?.id ?? "missing", expectedRevision: planned.revision, decision: { kind: "approve-plan" as const, approved: true as const, acceptedUiImpact: "minor" as const, comment, }, idempotencyKey: key, }); const results = await Promise.allSettled([ setup.engine().execute(command("approve-a", "approval A")), setup.engine().execute(command("approve-b", "approval B")), ]); assert.equal( results.filter((result) => result.status === "fulfilled").length, 1, ); const persisted = await setup.store.loadRun(planned.runId); assert.ok(persisted.planArtifact); const contents = await readFile(persisted.planArtifact.path, "utf8"); assert.equal( createHash("sha256").update(contents).digest("hex"), persisted.planArtifact.digest, ); const accepted = persisted.decisions.find( (decision) => decision.value.kind === "approve-plan", ); assert.ok(accepted?.value.kind === "approve-plan"); assert.match(contents, new RegExp(accepted.value.comment ?? "missing")); }); }); test("plan revision runs a fresh Design Assignment with persisted feedback", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design()); setup.executor.enqueue("design", { ...design(), plan: "# Revised plan\n\nApply the requested changes.", }); const initial = await setup.engine().execute(startCommand()); assert.equal(initial.openGate?.kind, "plan-approval"); const revised = await setup.engine().execute({ action: "decide", runId: initial.runId, gateId: initial.openGate?.id ?? "missing", expectedRevision: initial.revision, decision: { kind: "revise-plan", feedback: "Keep the API smaller" }, idempotencyKey: "revise-plan", }); assert.equal(revised.openGate?.kind, "plan-approval"); assert.notEqual(revised.openGate?.id, initial.openGate?.id); assert.equal( revised.assignments.filter( (assignment) => assignment.role === "design" && assignment.status === "succeeded", ).length, 2, ); const latestDesignRequest = setup.executor.requests .filter((request) => request.role === "design") .at(-1); assert.deepEqual(latestDesignRequest?.decisions.at(-1)?.value, { kind: "revise-plan", feedback: "Keep the API smaller", }); }); }); test("recovery reuses a revised Design result persisted before gate publication", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", design()); setup.executor.enqueue("design", { ...design(), plan: "# Revised durable plan", }); const initial = await setup.engine().execute(startCommand()); const command = { action: "decide" as const, runId: initial.runId, gateId: initial.openGate?.id ?? "missing", expectedRevision: initial.revision, decision: { kind: "revise-plan" as const, feedback: "Use the smaller design", }, idempotencyKey: "durable-revision", }; await assert.rejects( setup.engine("session-1", { afterAssignmentPersist(assignment) { if (assignment.role === "design") { throw new Error("crash before revised gate"); } }, }).execute(command), /crash before revised gate/, ); const resumed = await setup.engine().execute(command); assert.equal(resumed.openGate?.kind, "plan-approval"); assert.equal( setup.executor.requests.filter((request) => request.role === "design").length, 2, ); }); }); test("planner selections are eligibility-checked and persisted as queued Assignments", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const profileFor = (role: "implement" | "verify" | "review") => { const profile = setup.profiles.find((candidate) => candidate.roles.includes(role)); assert.ok(profile); return profile; }; const implementer = profileFor("implement"); const verifier = profileFor("verify"); const reviewer = profileFor("review"); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", { ...design(), assignments: [ { role: "implement", profileName: implementer.name, rationale: "Own the implementation diff", mutates: true, ownedPaths: ["src"], }, { role: "verify", profileName: verifier.name, rationale: "Run focused verification", mutates: false, ownedPaths: [], }, { role: "review", profileName: reviewer.name, rationale: "Review independently", mutates: false, ownedPaths: [], }, ], }); const planned = await setup.engine().execute(startCommand()); assert.equal(planned.openGate?.kind, "plan-approval"); const queued = planned.assignments.filter((assignment) => ["implement", "verify", "review"].includes(assignment.role), ); assert.deepEqual( queued.map((assignment) => assignment.role), ["implement", "verify", "review"], ); assert.ok( queued.every((assignment) => assignment.selection.eligibleProfiles.some( (candidate) => candidate.profileName === assignment.profileName && candidate.eligible, ), ), ); assert.notEqual( queued.find((assignment) => assignment.role === "implement")?.profileName, queued.find((assignment) => assignment.role === "review")?.profileName, ); }); }); test("planner output fails closed when required Assignments are missing", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", { ...design(), assignments: [], }); const failed = await setup.engine().execute(startCommand()); assert.equal(failed.status, "failed"); assert.match(failed.error?.message ?? "", /exactly one implement Assignment/); }); }); test("planner output fails closed on an ineligible profile", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const ineligible = setup.profiles.find((profile) => profile.roles.includes("discover"), ); const verifier = setup.profiles.find((profile) => profile.roles.includes("verify")); const reviewer = setup.profiles.find((profile) => profile.roles.includes("review")); assert.ok(ineligible && verifier && reviewer); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: [], }); setup.executor.enqueue("design", { ...design(), assignments: [ { role: "implement", profileName: ineligible.name, rationale: "Invalid selection", mutates: true, ownedPaths: ["src"], }, { role: "verify", profileName: verifier.name, rationale: "Verify", mutates: false, ownedPaths: [], }, { role: "review", profileName: reviewer.name, rationale: "Review", mutates: false, ownedPaths: [], }, ], }); const failed = await setup.engine().execute(startCommand()); assert.equal(failed.status, "failed"); assert.match(failed.error?.message ?? "", /ineligible implement profile/); }); }); test("production execution metadata and transcripts are persisted", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const executor: AssignmentExecutor = { async run() { return { kind: "pi-workflow-assignment-result" as const, output: discoveryWithQuestions(), metadata: { childSessionId: "child-session-1", provider: "provider-a", model: "model-a", effort: "high", contextCapacity: 128_000, transcript: [{ role: "assistant", text: "Discovery complete" }], }, }; }, }; const started = await setup.engine("session-1", { executor }).execute(startCommand()); const assignment = started.assignments.find( (candidate) => candidate.role === "discover", ); assert.equal(assignment?.childSessionId, "child-session-1"); assert.equal(assignment?.effectiveProvider, "provider-a"); assert.equal(assignment?.effectiveModel, "model-a"); assert.equal(assignment?.effectiveEffort, "high"); assert.equal(assignment?.contextCapacity, 128_000); assert.match( await readFile(assignment?.transcriptArtifact?.path ?? "missing", "utf8"), /Discovery complete/, ); }); }); test("capacity reservation precedes the persisted running transition", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const observed: WorkflowAssignment["status"][] = []; const executor: AssignmentExecutor = { reservesCapacity: true, async run(request) { const before = await setup.store.loadRun(request.runId); observed.push( before.assignments.find((assignment) => assignment.role === request.role) ?.status ?? "failed", ); await request.onReserved?.(); const after = await setup.store.loadRun(request.runId); observed.push( after.assignments.find((assignment) => assignment.role === request.role) ?.status ?? "failed", ); return discoveryWithQuestions(); }, }; await setup.engine("session-1", { executor }).execute(startCommand()); assert.deepEqual(observed, ["queued", "running"]); }); }); test("failed execution metadata and transcripts are persisted", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const executor: AssignmentExecutor = { async run() { throw Object.assign(new Error("provider failed"), { assignmentMetadata: { childSessionId: "child-session-failed", provider: "provider-a", model: "model-a", effort: "low", contextCapacity: 32_000, transcript: [{ role: "assistant", text: "partial response" }], }, }); }, }; const failed = await setup.engine("session-1", { executor }).execute(startCommand()); const assignment = failed.assignments.find( (candidate) => candidate.role === "discover", ); assert.equal(failed.status, "failed"); assert.equal(assignment?.status, "failed"); assert.equal(assignment?.childSessionId, "child-session-failed"); assert.match( await readFile(assignment?.transcriptArtifact?.path ?? "missing", "utf8"), /partial response/, ); }); }); test("start and decision idempotency prevent replay and reject changed commands", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); setup.executor.enqueue("design", design()); const engine = setup.engine(); const command = startCommand(); const first = await engine.execute(command); const repeated = await engine.execute(command); assert.deepEqual(repeated, first); assert.equal(setup.workspace.prepareCalls, 1); assert.equal(setup.workspace.createCalls, 1); assert.equal(setup.executor.requests.length, 1); const decide = { action: "decide" as const, runId: first.runId, gateId: first.openGate?.id ?? "missing", expectedRevision: first.revision, decision: { kind: "answer-questions" as const, answers: { "data-source": "Repository API" }, }, idempotencyKey: "answer-key", }; const decided = await engine.execute(decide); assert.deepEqual(await engine.execute(decide), decided); assert.equal( setup.executor.requests.filter((request) => request.role === "design") .length, 1, ); await assert.rejects( engine.execute({ ...decide, decision: { kind: "answer-questions", answers: { "data-source": "Different answer" }, }, }), /another command/, ); }); }); test("concurrent starts share one authoritative run and workspace mutation", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const blocking = new BlockingExecutor(discoveryWithQuestions()); const first = setup.engine("session-1", { executor: blocking }); const second = setup.engine("session-1", { executor: blocking }); const firstStart = first.execute(startCommand()); await blocking.started; const secondStart = second.execute(startCommand()); assert.equal(setup.workspace.prepareCalls, 1); assert.equal(setup.workspace.createCalls, 1); blocking.release(); const [firstResult, secondResult] = await Promise.all([ firstStart, secondStart, ]); assert.deepEqual(secondResult, firstResult); assert.equal((await setup.store.listRuns()).length, 1); assert.equal(blocking.requests.length, 1); }); }); test("cross-process starts claim the published run before workspace mutation", async () => { await withTempDir(async (directory) => { const state = path.join(directory, "state"); const stateAlias = path.join(directory, "state-alias"); await mkdir(state); await symlink(state, stateAlias); const profiles = ( await loadAgentProfiles({ cwd: directory, projectTrusted: false }) ).profiles; const workspace = new FakeWorkspace(); const executor = new BlockingExecutor(discoveryWithQuestions()); let leftId = 0; let rightId = 0; const makeEngine = ( store: WorkflowStore, processId: number, createId: () => string, ) => new WorkflowEngine({ store, workspace, executor, sourceRoot: directory, parentSessionId: "session-1", effectiveConfig: structuredClone(DEFAULT_WORKFLOW_CONFIG), profiles, now: () => 10_000, createId, processId, isProcessAlive: () => true, }); const left = makeEngine( new WorkflowStore(state), 100, () => `left-${++leftId}`, ); const right = makeEngine( new WorkflowStore(stateAlias), 200, () => `right-${++rightId}`, ); const leftStart = left.execute(startCommand()); const rightStart = right.execute(startCommand()); await executor.started; assert.equal(workspace.createCalls, 1); executor.release(); const [leftResult, rightResult] = await Promise.all([ leftStart, rightStart, ]); assert.equal(leftResult.runId, rightResult.runId); assert.equal(workspace.createCalls, 1); assert.equal(executor.requests.length, 1); assert.equal((await new WorkflowStore(state).listRuns()).length, 1); }); }); test("live workspace creation cannot be stolen after its heartbeat ages", async () => { await withTempDir(async (directory) => { const state = path.join(directory, "state"); const stateAlias = path.join(directory, "state-alias"); await mkdir(state); await symlink(state, stateAlias); const profiles = ( await loadAgentProfiles({ cwd: directory, projectTrusted: false }) ).profiles; const workspace = new FakeWorkspace(); let creationStarted: (() => void) | undefined; let releaseCreation: (() => void) | undefined; const startedCreating = new Promise((resolve) => { creationStarted = resolve; }); const creationWait = new Promise((resolve) => { releaseCreation = resolve; }); workspace.beforeCreate = async () => { creationStarted?.(); await creationWait; }; const executor = new ScriptedExecutor(); executor.enqueue("discover", discoveryWithQuestions()); let now = 10_000; let leftId = 0; let rightId = 0; const makeEngine = ( store: WorkflowStore, processId: number, createId: () => string, ) => new WorkflowEngine({ store, workspace, executor, sourceRoot: directory, parentSessionId: "session-1", effectiveConfig: structuredClone(DEFAULT_WORKFLOW_CONFIG), profiles, now: () => now, createId, processId, leaseStaleMs: 10, isProcessAlive: () => true, }); const owner = makeEngine( new WorkflowStore(state), 100, () => `owner-${++leftId}`, ); const contender = makeEngine( new WorkflowStore(stateAlias), 200, () => `contender-${++rightId}`, ); const ownerStart = owner.execute(startCommand()); await startedCreating; now = 100_000; const contenderResult = await contender.execute(startCommand()); assert.equal(contenderResult.workspace.state, "creating"); assert.equal(workspace.createCalls, 1); assert.deepEqual(await contender.recover(), []); assert.equal(workspace.reconcileCalls, 0); releaseCreation?.(); const completed = await ownerStart; assert.equal(completed.openGate?.kind, "questions"); assert.equal(workspace.createCalls, 1); assert.equal(executor.requests.length, 1); const startRecord = await new WorkflowStore(state).getStart("start-key"); assert.equal( (startRecord?.result as { revision?: number } | undefined)?.revision, completed.revision, ); }); }); test("a pending start resumes after interrupted initial publication", async () => { await withTempDir(async (directory) => { let interrupt = true; const setup = await fixture(directory, { beforeRename(target) { if (interrupt && target.endsWith("request.md")) { throw new Error("simulated initial publication crash"); } }, }); setup.executor.enqueue("discover", discoveryWithQuestions()); await assert.rejects( setup.engine().execute(startCommand()), /simulated initial publication crash/, ); interrupt = false; const resumed = await setup.engine().execute(startCommand()); assert.equal(resumed.status, "waiting"); assert.equal(resumed.openGate?.kind, "questions"); assert.equal(setup.workspace.prepareCalls, 2); assert.equal(setup.workspace.createCalls, 1); assert.equal((await setup.store.listRuns()).length, 1); }); }); test("a durable ready revision survives convenience-snapshot interruption", async () => { await withTempDir(async (directory) => { let workflowWrites = 0; let interrupt = true; const setup = await fixture(directory, { beforeRename(target) { if (target.endsWith("workflow.json")) { workflowWrites++; if (interrupt && workflowWrites === 2) { throw new Error("simulated ready snapshot crash"); } } }, }); setup.executor.enqueue("discover", discoveryWithQuestions()); await assert.rejects( setup.engine().execute(startCommand()), /simulated ready snapshot crash/, ); assert.equal(setup.workspace.createCalls, 1); interrupt = false; const resumed = await setup.engine().execute(startCommand()); assert.equal(resumed.workspace.state, "ready"); assert.equal(resumed.openGate?.kind, "questions"); assert.equal(setup.workspace.createCalls, 1); assert.equal(setup.workspace.reconcileCalls, 0); }); }); test("real Git persisted readiness precedes retry preparation", async () => { await withTempDir(async (directory) => { const repository = path.join(directory, "repository"); await mkdir(repository); const git = (args: string[], cwd = repository) => execFileAsync("git", args, { cwd, encoding: "utf8" }); await git(["init", "-b", "main"]); await git(["config", "user.email", "pi-workflow@example.invalid"]); await git(["config", "user.name", "Pi Workflow Test"]); await writeFile( path.join(repository, ".gitignore"), ".worktrees/\n", "utf8", ); await writeFile(path.join(repository, "README.md"), "# Fixture\n", "utf8"); await git(["add", ".gitignore", "README.md"]); await git(["commit", "-m", "initial fixture"]); let workflowWrites = 0; let interrupt = true; const store = new WorkflowStore(path.join(directory, "state"), { beforeRename(target) { if (target.endsWith("workflow.json")) { workflowWrites++; if (interrupt && workflowWrites === 2) { throw new Error("simulated real ready snapshot crash"); } } }, }); const executor = new ScriptedExecutor(); executor.enqueue("discover", discoveryWithQuestions()); const profiles = ( await loadAgentProfiles({ cwd: repository, projectTrusted: false }) ).profiles; let id = 0; const makeEngine = () => new WorkflowEngine({ store, workspace: new GitWorkspaceManager(), executor, sourceRoot: repository, parentSessionId: "session-1", effectiveConfig: structuredClone(DEFAULT_WORKFLOW_CONFIG), profiles, now: () => 10_000, createId: () => `real-id-${++id}`, processId: 100, isProcessAlive: () => true, }); await assert.rejects( makeEngine().execute(startCommand()), /simulated real ready snapshot crash/, ); interrupt = false; const resumed = await makeEngine().execute(startCommand()); assert.equal(resumed.workspace.state, "ready"); assert.equal(resumed.openGate?.kind, "questions"); assert.equal(executor.requests.length, 1); const sourceStatus = await git(["status", "--porcelain"]); assert.equal(sourceStatus.stdout, ""); }); }); test("persisted discovery output resumes during recovery without replay", async () => { await withTempDir(async (directory) => { let interrupt = true; const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const crashing = setup.engine("session-1", { afterAssignmentPersist(assignment) { if (interrupt && assignment.role === "discover") { interrupt = false; throw new Error("simulated post-assignment crash"); } }, }); await assert.rejects( crashing.execute(startCommand()), /simulated post-assignment crash/, ); const [persisted] = await setup.store.listRuns(); assert.equal(persisted.assignments[0]?.status, "succeeded"); if (persisted.lease) { persisted.lease.processId = 999; persisted.lease.heartbeatAt = 0; } persisted.revision++; await setup.store.saveRun(persisted); const recovery = setup.engine("session-2", { now: () => 100_000, processId: 200, isProcessAlive: () => false, }); const [resumed] = await recovery.recover(); assert.equal(resumed?.openGate?.kind, "questions"); assert.equal(setup.executor.requests.length, 1); const replayedStart = await recovery.execute(startCommand()); assert.equal(replayedStart.openGate?.kind, "questions"); assert.equal(setup.executor.requests.length, 1); }); }); test("pending decision continuation consumes persisted design output once", async () => { await withTempDir(async (directory) => { let interrupt = true; const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); setup.executor.enqueue("design", design()); const crashing = setup.engine("session-1", { afterAssignmentPersist(assignment) { if (interrupt && assignment.role === "design") { interrupt = false; throw new Error("simulated decision continuation crash"); } }, }); const started = await crashing.execute(startCommand()); const command = { action: "decide" as const, runId: started.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: started.revision, decision: { kind: "answer-questions" as const, answers: { "data-source": "Repository API" }, }, idempotencyKey: "answer-key", }; await assert.rejects( crashing.execute(command), /simulated decision continuation crash/, ); const interrupted = await setup.store.loadRun(started.runId); assert.equal( interrupted.commandReceipts.find( (receipt) => receipt.key === "answer-key", )?.status, "pending", ); assert.equal( interrupted.assignments.find((assignment) => assignment.role === "design") ?.status, "succeeded", ); if (interrupted.lease) { interrupted.lease.processId = 999; interrupted.lease.heartbeatAt = 0; } interrupted.revision++; await setup.store.saveRun(interrupted); const recovery = setup.engine("session-2", { now: () => 100_000, processId: 200, isProcessAlive: () => false, }); const [resumed] = await recovery.recover(); assert.equal(resumed?.openGate?.kind, "plan-approval"); assert.equal( (await recovery.execute(command)).openGate?.kind, "plan-approval", ); assert.equal( setup.executor.requests.filter((request) => request.role === "design") .length, 1, ); assert.equal( (await setup.store.loadRun(started.runId)).commandReceipts.find( (receipt) => receipt.key === "answer-key", )?.status, "completed", ); }); }); test("a concurrent duplicate decision cannot complete an active continuation", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const started = await setup.engine().execute(startCommand()); const blocking = new BlockingExecutor(design()); const engine = setup.engine("session-1", { executor: blocking, assignmentHeartbeatMs: 2, }); const command = { action: "decide" as const, runId: started.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: started.revision, decision: { kind: "answer-questions" as const, answers: { "data-source": "Repository API" }, }, idempotencyKey: "answer-key", }; const deciding = engine.execute(command); await blocking.started; const duplicate = await engine.execute(command); assert.equal(duplicate.status, "running"); assert.equal( (await setup.store.loadRun(started.runId)).commandReceipts.find( (receipt) => receipt.key === "answer-key", )?.status, "pending", ); blocking.release(); assert.equal((await deciding).openGate?.kind, "plan-approval"); }); }); test("active assignment leases heartbeat and block concurrent recovery", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const blocking = new BlockingExecutor(discoveryWithQuestions()); let now = 10_000; const engine = setup.engine("session-1", { executor: blocking, now: () => now, processId: 100, leaseStaleMs: 10, assignmentHeartbeatMs: 2, }); const start = engine.execute(startCommand()); await blocking.started; now = 100_000; await new Promise((resolve) => setTimeout(resolve, 10)); const [running] = await setup.store.listRuns(); const assignment = running.assignments[0]; assert.equal(assignment?.status, "running"); const lease = await setup.store.loadAssignmentLease( running.runId, assignment?.id ?? "missing", ); assert.equal(lease?.heartbeatAt, now); assert.deepEqual( await setup .engine("session-2", { now: () => now, processId: 100, leaseStaleMs: 10, isProcessAlive: () => false, }) .recover(), [], ); blocking.release(); assert.equal((await start).openGate?.kind, "questions"); }); }); test("a stale heartbeat fences a live read-only assignment during recovery", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const blocking = new BlockingExecutor(discoveryWithQuestions()); let now = 10_000; const start = setup .engine("session-1", { executor: blocking, now: () => now, processId: 100, leaseStaleMs: 10, assignmentHeartbeatMs: 1_000, }) .execute(startCommand()); const startOutcome = start.then( () => undefined, (error: unknown) => error, ); await blocking.started; now = 100_000; const recovery = setup.engine("session-2", { now: () => now, processId: 200, leaseStaleMs: 10, isProcessAlive: () => true, }); const [recovered] = await recovery.recover(); assert.equal(recovered?.openGate?.kind, "recovery"); assert.equal(recovered?.assignments[0]?.status, "interrupted"); blocking.release(); assert.match(String(await startOutcome), /lost its execution lease/); }); }); test("a live mutating assignment is not reclaimed on heartbeat age alone", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const started = await setup.engine().execute(startCommand()); const run = await setup.store.loadRun(started.runId); const assignment = run.assignments[0]; assert.ok(assignment); assignment.status = "running"; assignment.mutates = true; assignment.executionToken = "live-mutating-token"; for (const gate of run.gates) gate.status = "satisfied"; run.status = "running"; if (run.lease) { run.lease.processId = 100; run.lease.heartbeatAt = 0; } run.revision++; await setup.store.saveRun(run); await setup.store.createAssignmentLease(run.runId, assignment.id, { token: assignment.executionToken, processId: 100, heartbeatAt: 0, }); const recovery = setup.engine("session-2", { now: () => 100_000, processId: 200, leaseStaleMs: 10, isProcessAlive: () => true, }); assert.deepEqual(await recovery.recover(), []); assert.equal( (await setup.store.loadRun(run.runId)).assignments[0]?.status, "running", ); }); }); test("an executor cannot persist after losing its assignment fence", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const blocking = new BlockingExecutor(discoveryWithQuestions()); const start = setup .engine("session-1", { executor: blocking, assignmentHeartbeatMs: 1_000, }) .execute(startCommand()); await blocking.started; const [running] = await setup.store.listRuns(); const assignment = running.assignments[0]; assert.ok(assignment?.executionToken); await setup.store.releaseAssignmentLease( running.runId, assignment.id, assignment.executionToken, ); blocking.release(); await assert.rejects(start, /lost its execution lease/); assert.equal( (await setup.store.loadRun(running.runId)).assignments[0]?.status, "running", ); }); }); test("runtime command schemas reject malformed and open shapes", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const engine = setup.engine(); const malformed: unknown[] = [ { ...startCommand(), unexpected: true, }, { ...startCommand(), background: "yes", }, { action: "inspect", runId: "run-1", extra: true, }, { action: "decide", runId: "run-1", gateId: "gate-1", expectedRevision: 0, decision: { kind: "approve-plan", approved: false, acceptedUiImpact: "none", }, idempotencyKey: "decision-key", }, { action: "decide", runId: "run-1", gateId: "gate-1", expectedRevision: 0, decision: { kind: "recover", resolution: "continue" }, idempotencyKey: "decision-key", }, ]; for (const command of malformed) { await assert.rejects(engine.execute(command as WorkflowCommand)); } assert.equal(setup.workspace.prepareCalls, 0); }); }); test("decisions require the single active Gate and its waiting stage", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const engine = setup.engine(); const started = await engine.execute(startCommand()); const run = await setup.store.loadRun(started.runId); run.gates.push({ id: "second-open-gate", kind: "recovery", status: "open", revision: run.revision, openedAt: 10_000, }); run.revision++; await setup.store.saveRun(run); await assert.rejects( engine.execute({ action: "decide", runId: run.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: run.revision, decision: { kind: "answer-questions", answers: { "data-source": "Repository API" }, }, idempotencyKey: "multiple-gates-key", }), /single active Gate/, ); }); await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const engine = setup.engine(); const started = await engine.execute(startCommand()); const run = await setup.store.loadRun(started.runId); const discover = run.stages.find( (candidate) => candidate.kind === "discover", ); assert.ok(discover); discover.status = "completed"; run.revision++; await setup.store.saveRun(run); await assert.rejects( engine.execute({ action: "decide", runId: run.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: run.revision, decision: { kind: "answer-questions", answers: { "data-source": "Repository API" }, }, idempotencyKey: "wrong-stage-key", }), /waiting Discover stage/, ); }); }); test("required profile eligibility fails before workspace mutation", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const profiles = structuredClone(setup.profiles); for (const profile of profiles) { if (profile.roles.includes("implement")) { profile.selection.allTags = ["not-present"]; } } await assert.rejects( setup.engine("session-1", { profiles }).execute(startCommand()), /No eligible implement Agent Profile/, ); assert.equal(setup.workspace.prepareCalls, 0); assert.deepEqual(await setup.store.listRuns(), []); }); }); test("stale and illegal decisions fail without changing the run", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const engine = setup.engine(); const started = await engine.execute(startCommand()); await assert.rejects( engine.execute({ action: "decide", runId: started.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: started.revision - 1, decision: { kind: "answer-questions", answers: {} }, idempotencyKey: "stale-key", }), /Stale workflow revision/, ); await assert.rejects( engine.execute({ action: "decide", runId: started.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: started.revision, decision: { kind: "answer-questions", answers: {} }, idempotencyKey: "missing-answer-key", }), /requires an answer/, ); await assert.rejects( engine.execute({ action: "decide", runId: started.runId, gateId: started.openGate?.id ?? "missing", expectedRevision: started.revision, decision: { kind: "approve-plan", approved: true, acceptedUiImpact: "none", }, idempotencyKey: "illegal-key", }), /not legal for questions/, ); assert.equal( (await setup.store.loadRun(started.runId)).revision, started.revision, ); }); }); test("the closed Gate Decision matrix is exhaustive", () => { const gateKinds: GateKind[] = [ "questions", "plan-approval", "prototype-acceptance", "implementation-context", "recovery", ]; const decisionKinds: GateDecision["kind"][] = [ "answer-questions", "approve-plan", "revise-plan", "accept-prototype", "refine-prototypes", "select-implementation-context", "report-context-preparation", "recover", ]; const expected: Record = { questions: ["answer-questions"], "plan-approval": ["approve-plan", "revise-plan"], "prototype-acceptance": ["accept-prototype", "refine-prototypes"], "implementation-context": [ "select-implementation-context", "report-context-preparation", ], recovery: ["recover"], }; for (const gateKind of gateKinds) { for (const decisionKind of decisionKinds) { assert.equal( decisionIsLegal(gateKind, decisionKind), expected[gateKind].includes(decisionKind), `${decisionKind} at ${gateKind}`, ); } } }); test("every illegal Gate Decision pair is rejected through execute", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const engine = setup.engine(); const started = await engine.execute(startCommand()); const run = await setup.store.loadRun(started.runId); const gate = run.gates.find( (candidate) => candidate.id === started.openGate?.id, ); assert.ok(gate); const decisions: GateDecision[] = [ { kind: "answer-questions", answers: { "data-source": "Repository API" }, }, { kind: "approve-plan", approved: true, acceptedUiImpact: "none" }, { kind: "revise-plan", feedback: "Revise it" }, { kind: "accept-prototype", candidateId: "candidate-1", candidateRevision: 1, browserReviewed: true, }, { kind: "refine-prototypes", feedback: "Refine it" }, { kind: "select-implementation-context", strategy: "current-session" }, { kind: "report-context-preparation", preparationId: "preparation-1", planRevision: 1, method: "smart_compact", outcome: "unavailable", }, { kind: "recover", resolution: "retry" }, ]; const gateKinds: GateKind[] = [ "questions", "plan-approval", "prototype-acceptance", "implementation-context", "recovery", ]; for (const gateKind of gateKinds) { gate.kind = gateKind; gate.payload = gateKind === "questions" ? { questions: [{ id: "data-source", prompt: "Which data source?" }] } : undefined; const discover = run.stages.find( (candidate) => candidate.kind === "discover", ); const designStage = run.stages.find( (candidate) => candidate.kind === "design", ); assert.ok(discover && designStage); discover.status = gateKind === "questions" ? "waiting" : "completed"; designStage.status = gateKind === "plan-approval" ? "waiting" : "pending"; run.revision++; gate.revision = run.revision; await setup.store.saveRun(run); for (const decision of decisions) { if (decisionIsLegal(gateKind, decision.kind)) continue; await assert.rejects( engine.execute({ action: "decide", runId: run.runId, gateId: gate.id, expectedRevision: run.revision, decision, idempotencyKey: `matrix-${gateKind}-${decision.kind}`, }), new RegExp(`not legal for ${gateKind}`), ); } } }); }); test("abort cancels an active reserved Assignment before persisting terminal state", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); let resolveStarted = () => {}; const started = new Promise((resolve) => { resolveStarted = resolve; }); let cancelled = false; const executor: AssignmentExecutor = { reservesCapacity: true, async run(request) { await request.onReserved?.(); resolveStarted(); return new Promise((resolve, reject) => { request.signal.addEventListener( "abort", () => { cancelled = true; const error = Object.assign(new Error("cancelled"), { assignmentMetadata: { childSessionId: "child-aborted", provider: "provider-a", model: "model-a", effort: "medium", contextCapacity: 64_000, transcript: [{ role: "assistant", text: "cancelled child" }], }, }); reject(error); }, { once: true }, ); }); }, }; const startPromise = setup .engine("session-1", { executor }) .execute(startCommand()); await started; const [active] = await setup.store.listRuns(); assert.ok(active); const aborted = await setup.engine("session-1", { executor }).execute({ action: "abort", runId: active.runId, expectedRevision: active.revision, idempotencyKey: "abort-active", }); const startResult = await startPromise; assert.equal(cancelled, true); assert.equal(startResult.status, "aborted"); assert.equal(aborted.status, "aborted"); const persisted = await setup.store.loadRun(active.runId); const assignment = persisted.assignments.find( (candidate) => candidate.role === "discover", ); assert.equal(persisted.status, "aborted"); assert.equal(assignment?.status, "interrupted"); assert.equal(assignment?.childSessionId, "child-aborted"); assert.match( await readFile(assignment?.transcriptArtifact?.path ?? "missing", "utf8"), /cancelled child/, ); }); }); test("abort times out without persisting terminal state when an executor will not stop", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); let resolveStarted = () => {}; const started = new Promise((resolve) => { resolveStarted = resolve; }); let rejectRun = (_error: Error) => {}; const executor: AssignmentExecutor = { reservesCapacity: true, async run(request) { await request.onReserved?.(); resolveStarted(); return new Promise((_resolve, reject) => { rejectRun = reject; }); }, }; const startPromise = setup.engine("session-1", { executor }).execute(startCommand()); await started; const [active] = await setup.store.listRuns(); assert.ok(active); await assert.rejects( setup.engine("session-1", { executor, assignmentAbortTimeoutMs: 5, }).execute({ action: "abort", runId: active.runId, expectedRevision: active.revision, idempotencyKey: "abort-timeout", }), /Timed out stopping active Assignments/, ); assert.notEqual((await setup.store.loadRun(active.runId)).status, "aborted"); rejectRun(new Error("executor finally stopped")); await startPromise; const stopped = await setup.store.loadRun(active.runId); const retried = await setup.engine("session-1", { executor }).execute({ action: "abort", runId: stopped.runId, expectedRevision: stopped.revision, idempotencyKey: "abort-after-timeout", }); assert.equal(retried.status, "aborted"); assert.equal( retried.assignments.find((assignment) => assignment.role === "discover") ?.status, "interrupted", ); }); }); test("abort fails closed for an Assignment owned by another live process", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const started = await setup.engine().execute(startCommand()); const current = await setup.store.loadRun(started.runId); const profile = setup.profiles.find((candidate) => candidate.roles.includes("implement"), ); const evidence = current.preflightEligibility.find( (candidate) => candidate.role === "implement", ); assert.ok(profile && evidence); current.assignments.push({ id: "external-assignment", stageId: `${current.runId}:implement`, role: "implement", profileName: profile.name, profileDigest: profile.digest, selection: { eligibleProfiles: structuredClone(evidence.candidates), selectedProfileName: profile.name, rationale: "External owner fixture", }, status: "running", mutates: true, ownedPaths: ["src"], executionToken: "external-token", }); current.status = "running"; current.revision++; await setup.store.saveRun(current, current.revision - 1); await setup.store.createAssignmentLease( current.runId, "external-assignment", { token: "external-token", processId: 200, heartbeatAt: 10_000, }, ); await assert.rejects( setup.engine("session-1", { processId: 100, isProcessAlive: (processId) => processId === 200, }).execute({ action: "abort", runId: current.runId, expectedRevision: current.revision, idempotencyKey: "abort-external", }), /Cannot safely abort Assignment external-assignment owned by live process 200/, ); assert.equal((await setup.store.loadRun(current.runId)).status, "running"); }); }); test("abort is revision checked and idempotent", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const engine = setup.engine(); const started = await engine.execute(startCommand()); const abort = { action: "abort" as const, runId: started.runId, expectedRevision: started.revision, idempotencyKey: "abort-key", }; const aborted = await engine.execute(abort); assert.equal(aborted.status, "aborted"); assert.deepEqual(await engine.execute(abort), aborted); await assert.rejects( engine.execute({ ...abort, idempotencyKey: "other-key" }), /Stale workflow revision/, ); }); }); test("stale running assignments become interrupted once and require Recovery", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const started = await setup.engine().execute(startCommand()); const run = await setup.store.loadRun(started.runId); for (const gate of run.gates) gate.status = "satisfied"; const completed = structuredClone(run.assignments[0]); assert.ok(completed); completed.id = "completed-assignment"; completed.status = "succeeded"; const running: WorkflowAssignment = { ...structuredClone(completed), id: "mutating-assignment", role: "implement", status: "running", mutates: true, }; run.assignments = [completed, running]; run.status = "running"; run.revision++; if (run.lease) { run.lease.processId = 999; run.lease.heartbeatAt = 0; } await setup.store.saveRun(run); const recoveryEngine = setup.engine("session-2", { now: () => 100_000, processId: 200, isProcessAlive: () => false, }); const [recovered] = await recoveryEngine.recover(); assert.equal(recovered?.openGate?.kind, "recovery"); const recoveredRun = await setup.store.loadRun(run.runId); assert.equal( recoveredRun.assignments.find((item) => item.id === "mutating-assignment") ?.status, "interrupted", ); assert.equal( recoveredRun.assignments.find( (item) => item.id === "completed-assignment", )?.status, "succeeded", ); assert.deepEqual(await recoveryEngine.recover(), []); await assert.rejects( setup.engine("session-1").execute({ action: "abort", runId: run.runId, expectedRevision: recoveredRun.revision, idempotencyKey: "old-session-abort", }), /Historical parent sessions/, ); const inspected = await recoveryEngine.execute({ action: "decide", runId: run.runId, gateId: recovered?.openGate?.id ?? "missing", expectedRevision: recovered?.revision ?? -1, decision: { kind: "recover", resolution: "inspect" }, idempotencyKey: "inspect-recovery-key", }); assert.equal(inspected.openGate?.kind, "recovery"); const retry = await recoveryEngine.execute({ action: "decide", runId: run.runId, gateId: inspected.openGate?.id ?? "missing", expectedRevision: inspected.revision, decision: { kind: "recover", resolution: "retry" }, idempotencyKey: "recovery-key", }); assert.equal( retry.assignments.find((item) => item.id === "mutating-assignment") ?.status, "interrupted", ); assert.equal(retry.openGate?.kind, "recovery"); }); }); test("workspace creation failures persist recoverable intent", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); setup.workspace.createError = new Error("git worktree failed"); const engine = setup.engine(); const failed = await engine.execute(startCommand()); assert.equal(failed.status, "waiting"); assert.equal(failed.workspace.state, "creating"); assert.equal(failed.openGate?.kind, "recovery"); assert.match(failed.error?.message ?? "", /git worktree failed/); assert.deepEqual(await engine.execute(startCommand()), failed); assert.equal(setup.workspace.createCalls, 1); await assert.rejects( setup .engine("session-1", { processId: 200, isProcessAlive: () => true, }) .execute({ action: "decide", runId: failed.runId, gateId: failed.openGate?.id ?? "missing", expectedRevision: failed.revision, decision: { kind: "recover", resolution: "retry" }, idempotencyKey: "live-owner-retry-key", }), /still owned by a live process/, ); assert.equal( (await setup.store.loadRun(failed.runId)).gates.find( (gate) => gate.id === failed.openGate?.id, )?.status, "open", ); setup.workspace.createError = undefined; const recovered = await engine.execute({ action: "decide", runId: failed.runId, gateId: failed.openGate?.id ?? "missing", expectedRevision: failed.revision, decision: { kind: "recover", resolution: "retry" }, idempotencyKey: "workspace-retry-key", }); assert.equal(recovered.workspace.state, "ready"); assert.equal(recovered.openGate?.kind, "questions"); assert.equal(setup.workspace.reconcileCalls, 1); }); }); test("post-creation discovery failures retain the ready worktree", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { invalid: true }); const failed = await setup.engine().execute(startCommand()); assert.equal(failed.status, "failed"); assert.equal(failed.workspace.state, "ready"); assert.equal(failed.error?.code, "invalid-discovery-output"); }); }); test("discovery question bounds preserve complete model-facing Gate payloads", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", { ...discoveryWithQuestions(), questions: Array.from({ length: 21 }, (_, index) => ({ id: `question-${index}`, prompt: `Question ${index}?`, })), }); const failed = await setup.engine().execute( startCommand({ idempotencyKey: "too-many-questions" }), ); assert.equal(failed.status, "failed"); assert.match(failed.error?.message ?? "", /at most 20 questions/); }); }); test("startup reconciliation resolves persisted workspace creation intent", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); setup.executor.enqueue("discover", discoveryWithQuestions()); const started = await setup.engine().execute(startCommand()); const run = await setup.store.loadRun(started.runId); for (const gate of run.gates) gate.status = "satisfied"; run.workspace.state = "creating"; run.status = "running"; run.revision++; if (run.lease) run.lease.heartbeatAt = 0; await setup.store.saveRun(run); const recovery = setup.engine("session-2", { now: () => 100_000, processId: 200, isProcessAlive: () => false, }); const [recovered] = await recovery.recover(); assert.equal(setup.workspace.reconcileCalls, 1); assert.equal(recovered?.workspace.state, "ready"); assert.equal(recovered?.openGate?.kind, "recovery"); }); }); test("feature names and branch collisions fail with actionable guidance", async () => { await withTempDir(async (directory) => { const setup = await fixture(directory); const engine = setup.engine(); await assert.rejects( engine.execute(startCommand({ featureName: "Bad Feature" })), /lowercase kebab case/, ); assert.equal(setup.workspace.prepareCalls, 0); setup.workspace.branchExists = true; await assert.rejects( engine.execute(startCommand({ idempotencyKey: "collision-key" })), /choose another descriptive feature name/, ); setup.workspace.branchExists = false; setup.executor.enqueue("discover", discoveryWithQuestions()); const started = await engine.execute( startCommand({ idempotencyKey: "owner-key" }), ); setup.workspace.branchExists = true; await assert.rejects( engine.execute(startCommand({ idempotencyKey: "second-owner-key" })), new RegExp(`${started.runId}.*inspect or decide`), ); }); });