// Deterministic Testing Posture contract for Code Generation. // // Practices remain human-authored prose, but code generation needs one stable // execution contract. This module resolves methodology independently from // coverage/tooling notes, builds a methodology-specific plan profile, binds the // result to the active scope/test strategy/project type, and fingerprints the // approved plan + unit test instructions. Both the dispatch guard and autonomous // swarm referee consume the same contract. import { createHash } from "node:crypto"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { isAbsolute, join, relative, resolve } from "node:path"; import { type AcceptedChange, activeIntentUuid, auditBlockField, collectStalePlanApprovalReceipts, contentBeforeTerminalReviewAppendix, docsRoot, getField, isoTimestamp, latestMainWorkflowStageRunFloorForProject, LEGACY_PLAN_APPROVAL_RECOVERY_CHOICE, clearPlanApprovalChallenge, clearPlanApprovalLegacyOffer, clearPlanApprovalOverrideRequest, clearPlanApprovalReceipt, readActiveDirectiveMarker, readAuditShardEvents, readPlanApprovalChallenge, readPlanApprovalLegacyOffer, readPlanApprovalLegacyRecoveryChallenge, readPlanApprovalOverrideRequest, readPlanApprovalReceipt, readPlanApprovalResponse, readPlanApprovalViolation, recordAcceptedChanges, renderChangedPaths, governedChangeControl, resolveBoltDag, resolveChangeControl, resolveProjectDir, stalePlanApprovalReceiptsForTarget, resolveWorkflowSelection, stateFilePath, structuredField, toPosix, stripRecommendedDecorator, UNBINDABLE_FINGERPRINT, validateUnitName, visibleMarkdownLines, withActiveDirectiveLock, withAuditLock, workspaceSourceChangedPaths, workspaceSourceFailureSuffix, workspaceSourceFingerprint, workspaceSourceState, writePlanApprovalChallenge, writePlanApprovalLegacyRecoveryResponse, writePlanApprovalOverrideRequest, writePlanApprovalReceipt, writePlanApprovalResponse, writeWorkspaceSourceSnapshot, type GuardRemedyOp, type PlanApprovalOverrideRequest, type PlanApprovalReceiptKey, type PlanApprovalRuntimeChallenge, type PlanApprovalRuntimeIdentity, type PlanApprovalRuntimeProvenance, type PlanApprovalRuntimeReceipt, type WorkspaceSourceState, } from "./aidlc-lib.ts"; export type TestingMethodology = "tdd" | "bdd" | "atdd" | "test-after" | "custom"; export type TestStrategy = "minimal" | "standard" | "comprehensive"; export type ProjectType = "greenfield" | "brownfield"; export type MemoryLayer = "org" | "team" | "project"; export interface TestingPostureSections { org?: string; team?: string; project?: string; } export interface PlanProfile { methodology: TestingMethodology; runner_step: string; runner_ready_before_first_test: true; testable_layers: string[]; steps: string[]; } export interface TestObligations { strategy: TestStrategy; strategy_volume: string[]; scope_floor: string[]; combination_rule: string; } export interface TestingPostureContractBody { version: 1; methodology: TestingMethodology; source: MemoryLayer | "fallback"; ordering: string; scope: string; test_strategy: TestStrategy; project_type: ProjectType; applicable_notes: Array<{ layer: MemoryLayer; text: string }>; obligations: TestObligations; plan_profile: PlanProfile; input_sha256: string; } export interface TestingPostureContract extends TestingPostureContractBody { contract_sha256: string; } export interface CodeGenerationApproval { ok: boolean; unit: string | null; reason: string; planExists: boolean; instructionsExist: boolean; approved: boolean; contractValid: boolean; fingerprintValid: boolean; receiptValid: boolean; contractHash: string | null; approvalFingerprint: string | null; directiveEpoch: string | null; /** The reason is the strict source-drift refusal; its remedy is PLAN_SOURCE_DRIFT_REMEDY. */ sourceDrift?: true; /** The current receipt is a human break-glass override (content and attempt only). */ override?: true; } export interface CodeGenerationTarget { unit: string | null; } export interface CodeGenerationAuthority extends CodeGenerationTarget { targetId: string; intentId: string; directiveEpoch: string; runFloor: string; stageDir: string; sourceFloor: string; markerRevision: number; } export interface PlanApprovalQuestionEvidence { authority: CodeGenerationAuthority; fingerprint: string; questionsPath: string; questionsRelativePath: string; questionsSha256: string; promptSha256: string; plannedSourceSha256: string; /** * The human lines for source drift this evidence accepted and recorded under * Change Control `relaxed` (the CHANGE_ACCEPTED row is written before any * re-baseline). Empty under `strict` (drift throws) and when nothing moved. */ changeNotices: string[]; } // --- Source drift at the Plan Approval checkpoint -------------------------- // // The plan binds to a workspace source fingerprint. When live source no longer // matches it, Change Control decides the consequence: `strict` refuses with the // human sentence below (the conductor's remedy travels separately), `relaxed` // accepts, records the change once, tells the human once, and re-baselines the // recorded source so the same change is not reported at every later check. const CODE_GENERATION_STAGE = "code-generation"; /** Conductor-only: the command path that reopens approval. Never the human sentence. */ export const PLAN_SOURCE_DRIFT_REMEDY = "Re-run the fingerprint command and re-present the plan."; // The break-glass exit. It is always the LAST remedy listed, it is never // proposed or initiated by the conductor, and it is opened only by the human // typing the phrase below as a prompt (the human-turn hook records that typed // prompt; a picked option never counts). export const PLAN_APPROVAL_OVERRIDE_PHRASE = "Override Plan Approval: "; export const PLAN_APPROVAL_OVERRIDE_PHRASE_RE = /^override plan approval:\s*(\S.*)$/i; export const PLAN_APPROVAL_BREAK_GLASS_REMEDY = "Break glass (human only): type exactly `Override Plan Approval: ` in chat; " + "the conductor then runs answer --override with that reason."; export const PLAN_APPROVAL_OVERRIDE_HUMAN_ONLY = "Plan Approval override is human-only: the human must type exactly " + "`Override Plan Approval: ` in chat; then re-run this command with that reason."; export class PlanApprovalSourceDriftError extends Error { readonly remedy = PLAN_SOURCE_DRIFT_REMEDY; constructor(message: string) { super(message); this.name = "PlanApprovalSourceDriftError"; } } // A source boundary that cannot be bound is not drift: nothing moved, the walk // failed. The refusal names that, and its remedies are ordered: repair the // boundary first (an ordinary re-fingerprint then works), break glass last. export const PLAN_APPROVAL_SOURCE_UNBINDABLE_CODE = "PLAN_APPROVAL_SOURCE_UNBINDABLE"; export const BREAK_GLASS_REMEDY_OP = "break-glass-override"; export interface PlanApprovalRemedy { op: GuardRemedyOp | typeof BREAK_GLASS_REMEDY_OP; action: string; requiresHuman: boolean; executableNow: boolean; } export const PLAN_APPROVAL_REPAIR_SOURCE_BOUNDARY_REMEDY = "Repair the source boundary: shrink or exclude the offending path, declare real " + "source under an excluded directory in .aidlc-source-paths.json, or remove the " + "broken symlink; then re-run the fingerprint command and re-present the plan."; export function planApprovalUnbindableRemedies(): PlanApprovalRemedy[] { return [ { op: "repair-source-boundary", action: PLAN_APPROVAL_REPAIR_SOURCE_BOUNDARY_REMEDY, requiresHuman: false, executableNow: true, }, { op: BREAK_GLASS_REMEDY_OP, action: PLAN_APPROVAL_BREAK_GLASS_REMEDY, requiresHuman: true, executableNow: false, }, ]; } export class PlanApprovalUnbindableError extends Error { readonly code = PLAN_APPROVAL_SOURCE_UNBINDABLE_CODE; readonly remedies: PlanApprovalRemedy[]; constructor(blocked: "presented" | "recorded") { const remedies = planApprovalUnbindableRemedies(); super( `Plan Approval cannot be ${blocked}: the workspace source cannot be bound${workspaceSourceFailureSuffix()}, so ` + `${blocked === "presented" ? "no challenge was minted" : "no receipt was written"}. ` + "Remedies, in order: " + remedies.map((remedy, index) => `(${index + 1}) ${remedy.action}`).join(" "), ); this.name = "PlanApprovalUnbindableError"; this.remedies = remedies; } } // `unbound` is the walk failing now, which is not a change: the reason it // failed is named instead of a file list, and the sentence asks for the // boundary repair rather than a re-approval that could not certify either. function describeSourceDrift(paths: string[] | null, unbound = false): string { if (unbound) { return `The workspace source cannot be bound${workspaceSourceFailureSuffix()}, so the source this plan was approved against cannot be checked.`; } if (paths === null || paths.length === 0) { return "Source files changed since this plan was approved."; } const count = paths.length === 1 ? "1 file" : `${paths.length} files`; return `${count} changed since this plan was approved: ${renderChangedPaths(paths)}.`; } /** The strict human sentence for source drift after the plan was approved. */ export function planSourceDriftStrictMessage(paths: string[] | null, unbound = false): string { return unbound ? `${describeSourceDrift(paths, true)} ${PLAN_APPROVAL_REPAIR_SOURCE_BOUNDARY_REMEDY} ${PLAN_APPROVAL_BREAK_GLASS_REMEDY}` : `${describeSourceDrift(paths)} Look them over and approve the plan again to continue.`; } /** The relaxed human sentence for source drift after the plan was approved. */ export function planSourceDriftRelaxedNotice(paths: string[] | null, unbound = false): string { return ( `${describeSourceDrift(paths, unbound)} Continuing (Change Control: relaxed). ` + "Say 'review the plan again' to reopen approval." ); } /** * The Change Control consequence of the workspace source moving from * `recorded` to `current`: under strict, the refusal to throw; under relaxed, * the change to record. The listed paths come from the snapshot kept for the * recorded fingerprint when one exists; otherwise only the digests speak. * This is the checkpoint's one read of the setting, so it is also where a * memory edit that moved the value is traced: a mutating caller (the decision * and answer records, generation start) passes `trace`, the read-only judge * behind the dispatch guard and `next` does not. An invalid memory value is * the resolver's validation error under both. */ function judgePlanSourceDrift( projectDir: string, unit: string | null, recorded: string, current: WorkspaceSourceState | null, trace: boolean, ): { accepted: AcceptedChange } | { refusal: PlanApprovalSourceDriftError } { const paths = workspaceSourceChangedPaths(projectDir, CODE_GENERATION_STAGE, recorded, current); const unbound = current === null; const resolution = trace ? governedChangeControl(projectDir) : resolveChangeControl(projectDir); if (resolution.value === "strict") { return { refusal: new PlanApprovalSourceDriftError(planSourceDriftStrictMessage(paths, unbound)) }; } return { accepted: { checkpoint: "plan-approval", stage: CODE_GENERATION_STAGE, unit, changed: paths, recorded, current: current?.fingerprint ?? UNBINDABLE_FINGERPRINT, notice: planSourceDriftRelaxedNotice(paths, unbound), }, }; } /** Keep the listing behind the current fingerprint so a later drift can name paths. */ function keepWorkspaceSourceSnapshot( projectDir: string, state: WorkspaceSourceState | null, ): void { if (state !== null) writeWorkspaceSourceSnapshot(projectDir, CODE_GENERATION_STAGE, state); } // Re-baseline the `[Planned Source]` tag in a questions file to `fingerprint`. // Used only before the challenge is minted: after that the prompt hash binds // the file bytes and the receipt's certified source is the baseline instead. function upsertPlannedSourceTag(questions: string, fingerprint: string): string { const eol = questions.includes("\r\n") ? "\r\n" : "\n"; const raw = questions.split(/\r?\n/); const visible = visibleMarkdownLines(questions); for (let index = visible.length - 1; index >= 0; index--) { if (PLANNED_SOURCE_TAG_RE.test(visible[index])) { raw[index] = `[Planned Source]: ${fingerprint}`; return raw.join(eol); } } throw new Error("Plan Approval questions file has no [Planned Source]: tag to re-baseline"); } interface ClassifiedPosture { methodology: TestingMethodology; ordering: string; components: TestingMethodology[]; } const TESTING_HEADING = "## Testing Posture"; const TESTABLE_LAYERS = [ "Data model / database behavior", "Repository / data access", "Business logic", "API / endpoint", "Frontend behavior", ]; const CONTRACT_HEADING = "## Testing Contract"; export const PLAN_APPROVAL_CHECKPOINT = "Code Generation Plan Approval"; const CONTRACT_MARKER_RE = /^[ \t]*AIDLC-TESTING-CONTRACT[ \t]*:[ \t]*(sha256:[0-9a-f]{64})[ \t]*$/; const MARKDOWN_HEADING_RE = /^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/; const ANSWER_TAG_RE = /^\[Answer\]:[ \t]*(.*)$/; // The recorded fingerprint tag. `sha256:v3:` is the current content-bound // format (plan projection plus byte-exact instructions). The `sha256:v2:` // shape (instructions projected like the plan) and the bare `sha256:` shape // (issuance-bound) are still matched so a questions file written under either // is READ and reported as "approve again" rather than looking like a line the // parser does not understand. const FINGERPRINT_TAG_RE = /^\[Approval Fingerprint\]:[ \t]*(sha256:(?:v[23]:)?[0-9a-f]{64})?[ \t]*$/; // The workspace source the plan was written against, recorded by the fingerprint // command so drift between planning and approval is caught with a remedy the // conductor can always execute. const PLANNED_SOURCE_TAG_RE = /^\[Planned Source\]:[ \t]*([0-9a-f]{40}|[0-9a-f]{64}|unbindable)?[ \t]*$/; export const APPROVAL_FINGERPRINT_PREFIX = "sha256:v3:"; export function approvalFingerprintIsCurrentFormat(tag: string | null): boolean { return tag?.startsWith(APPROVAL_FINGERPRINT_PREFIX) === true; } /** * The `[Approval Fingerprint]` tag recorded in a questions file, in any format * this tool has ever written (bare, v2, v3), or null when the file carries no * well-formed tag. The one grammar every consumer of the tag reads through, so * a format bump never strands a reader that copied the regex. */ export function recordedApprovalFingerprint(questions: string): string | null { for (const line of questions.split(/\r?\n/)) { const match = FINGERPRINT_TAG_RE.exec(line); if (match) return match[1] ?? null; } return null; } const APPROVE_PLAN_RE = /^(?:[A-Z][.)][ \t]*)?["']?Approve Plan["']?$/i; const QUESTION_PREFIX_RE = /^(?:(?:q(?:uestion)?[ \t]*)?\d+[ \t]*[:.)-][ \t]*)/i; const NUMBERED_QUESTION_HEADING_RE = /^(?:q(?:uestion)?[ \t]*)?\d+[ \t]*[.:)-]?[ \t]*$/i; function canonicalize(value: unknown): unknown { if (Array.isArray(value)) return value.map(canonicalize); if (value !== null && typeof value === "object") { const record = value as Record; return Object.fromEntries( Object.keys(record) .sort() .map((key) => [key, canonicalize(record[key])]), ); } return value; } function sha256(value: string): string { return `sha256:${createHash("sha256").update(value, "utf-8").digest("hex")}`; } function hashObject(value: unknown): string { return sha256(JSON.stringify(canonicalize(value))); } function normalizeMethodology(value: string): TestingMethodology | null { const normalized = value .toLowerCase() .replace(/[`*_]/g, "") .trim(); if (/\b(custom|mixed)\b/.test(normalized)) return "custom"; if ( /\batdd\b|acceptance[- ]test[- ]driven|acceptance tests? (?:first|before)/.test( normalized, ) ) { return "atdd"; } if ( /\bbdd\b|behaviou?r[- ]driven|(?:behaviou?r )?scenarios? (?:first|before)/.test( normalized, ) ) { return "bdd"; } if ( /\btdd\b|test[- ]driven|(?:unit )?tests? (?:first|before implementation)/.test( normalized, ) ) { return "tdd"; } if ( /\btest[- ]after\b|tests? after implementation|implementation[- ]first|classic/.test( normalized, ) ) { return "test-after"; } return null; } function structuredMethodology(value: string): TestingMethodology { const normalized = value .toLowerCase() .replace(/[`*_]/g, "") .trim(); if ( normalized === "tdd" || normalized === "bdd" || normalized === "atdd" || normalized === "test-after" || normalized === "custom" ) { return normalized; } throw new Error( `Invalid Testing Posture Methodology "${value}". Expected one of: tdd, bdd, atdd, test-after, custom.`, ); } function defaultOrdering(methodology: TestingMethodology): string { switch (methodology) { case "tdd": return "For each testable layer: Red, then Green, then Refactor."; case "bdd": return "Define executable behavior scenarios before implementing each observable feature slice."; case "atdd": return "Write executable acceptance tests before implementing the complete feature across its required layers."; case "test-after": return "Implement each testable layer, then write and run that layer's tests."; case "custom": return "Preserve the explicitly affirmed custom ordering without converting it to another methodology."; } } type MarkdownFence = { marker: "`" | "~"; length: number }; function isEscaped(line: string, offset: number): boolean { let backslashes = 0; for (let index = offset - 1; index >= 0 && line[index] === "\\"; index--) { backslashes++; } return backslashes % 2 === 1; } function hasMatchingTickRun( line: string, from: number, ticks: number, ): boolean { for (let cursor = from; cursor < line.length; cursor++) { if (line[cursor] !== "`" || isEscaped(line, cursor)) continue; let end = cursor + 1; while (line[end] === "`") end++; if (end - cursor === ticks) return true; cursor = end - 1; } return false; } function stripHtmlCommentsFromLine( rawLine: string, state: { inComment: boolean; inlineCodeTicks: number }, ): string { let line = ""; let cursor = 0; while (cursor < rawLine.length) { if (state.inComment) { const end = rawLine.indexOf("-->", cursor); if (end < 0) break; state.inComment = false; cursor = end + 3; continue; } if ( rawLine[cursor] === "`" && (state.inlineCodeTicks > 0 || !isEscaped(rawLine, cursor)) ) { let end = cursor + 1; while (rawLine[end] === "`") end++; const ticks = end - cursor; if ( state.inlineCodeTicks === 0 && hasMatchingTickRun(rawLine, end, ticks) ) { state.inlineCodeTicks = ticks; } else if (state.inlineCodeTicks === ticks) state.inlineCodeTicks = 0; line += rawLine.slice(cursor, end); cursor = end; continue; } if ( state.inlineCodeTicks === 0 && !isEscaped(rawLine, cursor) && rawLine.startsWith(""); return opening >= 0 && (closing < 0 || opening < closing) ? rawLine.slice(0, opening) : ""; }); } function visiblePostureText(section: string): string { return markdownWithoutHtmlComments(section).trim(); } function classifiablePostureText(section: string): string { const lines = markdownWithoutHtmlComments(section).split("\n"); const structuralLines = structuralMarkdownLines(section); let fence: MarkdownFence | null = null; return lines .map((line, index) => { const structuralLine = structuralLines[index]; if (fence) { if (closesFence(structuralLine, fence)) fence = null; return ""; } const opening = fenceOpening(structuralLine); if (opening) { fence = opening; return ""; } return line; }) .join("\n") .trim(); } // Find the real Testing Posture section while ignoring headings hidden inside // HTML comments or fenced examples. Return the original raw lines so comments // and fences remain part of input_sha256 even though classification uses the // visible projection above. function extractTestingPostureSection(content: string): string { const rawLines = content.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").split("\n"); const visibleLines = structuralMarkdownLines(content); let fence: MarkdownFence | null = null; let bodyStart = -1; let bodyEnd = rawLines.length; for (let index = 0; index < visibleLines.length; index++) { const line = visibleLines[index]; if (fence) { if (closesFence(line, fence)) fence = null; continue; } const opening = fenceOpening(line); if (opening) { fence = opening; continue; } if (bodyStart < 0) { if (line.trimEnd() === TESTING_HEADING) bodyStart = index + 1; continue; } if (/^## [^\n]*$/.test(line)) { bodyEnd = index; break; } } return bodyStart < 0 ? "" : rawLines.slice(bodyStart, bodyEnd).join("\n"); } function classifyPosture(section: string): ClassifiedPosture | null { const body = classifiablePostureText(section); if (!body) return null; const structuredMethod = structuredField(body, "Methodology"); const structuredOrdering = structuredField(body, "Ordering"); const structured = structuredMethod ? structuredMethodology(structuredMethod) : null; const scan = `${structuredMethod ?? ""}\n${structuredOrdering ?? body}`.toLowerCase(); const components = new Set(); for (const methodology of ["tdd", "bdd", "atdd", "test-after"] as const) { const detected = normalizeMethodology( methodology === "test-after" ? scan.match( /test[- ]after|tests? after implementation|implementation[- ]first|classic/, )?.[0] ?? "" : scan.match( methodology === "tdd" ? /\btdd\b|test[- ]driven/ : methodology === "bdd" ? /\bbdd\b|behaviou?r[- ]driven/ : /\batdd\b|acceptance[- ]test[- ]driven/, )?.[0] ?? "", ); if (detected) components.add(detected); } const ordering = structuredOrdering ?? body; const mixedOrdering = (/\b(?:tests?|scenarios?)\b[^.\n]{0,80}\bfirst(?!-)\b/i.test(ordering) || /\b(?:tests?|scenarios?)\b[^.\n]{0,80}\bbefore\b[^.\n]{0,40}\bimplement(?:ation|ing)?\b/i.test( ordering, )) && (/\btests?\b[^.\n]{0,80}\bafter\b[^.\n]{0,40}\bimplement(?:ation|ing)?\b/i.test( ordering, ) || /\brefactor(?:ing)?\b[^.\n]{0,80}\bafter\b[^.\n]{0,40}\bgreen\b/i.test( ordering, ) || /\btests?\b[^.\n]{0,80}\bfollow\b[^.\n]{0,40}\bimplement(?:ation|ing)?\b/i.test( ordering, )); const customSignal = /\b(?:custom|mixed)[ -](?:ordering|cadence|posture|methodology)\b|\b(?:ordering|cadence|posture|methodology)[ -](?:custom|mixed)\b/i.test( body, ); if ( structured === null && components.size > 1 && !customSignal && !mixedOrdering ) { return null; } const methodology = structured ?? (customSignal || mixedOrdering ? "custom" : Array.from(components)[0] ?? null); if (methodology === null) return null; if (methodology !== "custom") components.add(methodology); return { methodology, ordering: structuredOrdering ?? (methodology === "custom" ? body.replace(/\s+/g, " ").trim() : defaultOrdering(methodology)), components: Array.from(components), }; } function compatibleSpecialization( broader: ClassifiedPosture, narrower: ClassifiedPosture, ): boolean { if (broader.methodology === narrower.methodology) return true; return ( narrower.methodology === "custom" && narrower.components.includes(broader.methodology) ); } function normalizeStrategy(value: string): TestStrategy { const normalized = value.trim().toLowerCase(); if ( normalized === "minimal" || normalized === "standard" || normalized === "comprehensive" ) { return normalized; } return "standard"; } function normalizeProjectType(value: string): ProjectType { return value.trim().toLowerCase() === "brownfield" ? "brownfield" : "greenfield"; } export function combineTestObligations( scope: string, strategy: TestStrategy, ): TestObligations { const strategyVolume: Record = { minimal: [ "One verifiable test per requirement at the narrowest effective level.", "At least one happy-path unit test per component.", "Unit tests are the default; a bugfix/security scope floor may require an integration or E2E regression when that is the narrowest level that reproduces the defect.", ], standard: [ "Five to eight tests per component.", "Unit tests plus integration tests for key boundaries.", "Add E2E, performance, or security tests when requirements demand them.", ], comprehensive: [ "Ten to fifteen tests per component.", "Unit, integration, and E2E tests.", "Add performance and security tests when NFRs demand them.", ], }; const normalizedScope = scope.trim().toLowerCase(); let scopeFloor: string[]; if (["mvp", "enterprise", "feature", "infra"].includes(normalizedScope)) { scopeFloor = [ "Meet an 80% line-coverage floor.", "Run the selected tests in CI before merge.", ]; } else if (["bugfix", "security-patch"].includes(normalizedScope)) { scopeFloor = [ "Include a targeted regression for the bug or vulnerability.", "Keep the existing test suite green.", ]; } else { scopeFloor = [ "Keep the existing test suite green.", "This scope adds no extra new-test floor beyond the selected test strategy.", ]; } return { strategy, strategy_volume: strategyVolume[strategy], scope_floor: scopeFloor, combination_rule: "Apply every selected-strategy obligation and every scope-floor obligation; neither replaces the other, and a targeted scope regression may add the narrowest necessary test type beyond the strategy default.", }; } export function buildPlanProfile( methodology: TestingMethodology, ordering: string, projectType: ProjectType, ): PlanProfile { const runnerStep = projectType === "greenfield" ? "Bootstrap the minimal test runner/configuration and record the exact unit-scoped command." : "Verify the existing test runner/configuration and record the exact unit-scoped command."; const steps = [ "Project structure and production configuration skeleton.", runnerStep, ]; if (methodology === "tdd") { for (const layer of TESTABLE_LAYERS) { steps.push( `${layer} - Red: write the failing tests and record the failing command output.`, `${layer} - Green: implement only enough behavior to pass.`, `${layer} - Refactor: improve the implementation while tests stay green.`, ); } } else if (methodology === "bdd") { steps.push( "Behavior scenarios - define executable examples for the observable feature slice before implementation.", "Feature slice - implement the required data, repository, business, API, and frontend layers.", "Behavior scenarios - run the scenarios until they pass.", "Feature slice - refactor while the scenarios stay green.", ); } else if (methodology === "atdd") { steps.push( "Acceptance Red - write executable acceptance tests for the complete feature before implementation.", "Feature implementation - implement the required layers against the acceptance contract.", "Acceptance Green - run the acceptance tests until they pass.", "Feature Refactor - improve the cross-layer implementation while acceptance stays green.", ); } else if (methodology === "custom") { steps.push( `Custom ordering - ${ordering}`, "Implementation and tests - preserve that exact ordering; do not convert it to layer-local TDD.", ); } else { for (const layer of TESTABLE_LAYERS) { steps.push( `${layer} - implement.`, `${layer} - write and run its tests after implementation.`, ); } } steps.push( "Environment/build configuration.", "Documentation and traceability.", ); return { methodology, runner_step: runnerStep, runner_ready_before_first_test: true, testable_layers: TESTABLE_LAYERS.slice(), steps, }; } export function resolveTestingPostureFromSections( sections: TestingPostureSections, options: { scope: string; testStrategy: TestStrategy; projectType: ProjectType; }, ): TestingPostureContract { const classified = { org: classifyPosture(sections.org ?? ""), team: classifyPosture(sections.team ?? ""), project: classifyPosture(sections.project ?? ""), }; if ( classified.team && classified.project && !compatibleSpecialization(classified.team, classified.project) ) { throw new Error( `Testing Posture conflict: project methodology "${classified.project.methodology}" ` + `contradicts team methodology "${classified.team.methodology}". Revise the narrower rule; ` + "strict-additive memory does not permit runtime override.", ); } const selected = classified.project ? { layer: "project" as const, value: classified.project } : classified.team ? { layer: "team" as const, value: classified.team } : classified.org ? { layer: "org" as const, value: classified.org } : { layer: "fallback" as const, value: { methodology: "test-after" as const, ordering: defaultOrdering("test-after"), components: ["test-after" as const], }, }; const applicableNotes = (["org", "team", "project"] as const) .map((layer) => ({ layer, text: visiblePostureText(sections[layer] ?? ""), })) .filter((entry) => entry.text.length > 0); const input = { sections: { org: sections.org ?? "", team: sections.team ?? "", project: sections.project ?? "", }, scope: options.scope, test_strategy: options.testStrategy, project_type: options.projectType, }; const body: TestingPostureContractBody = { version: 1, methodology: selected.value.methodology, source: selected.layer, ordering: selected.value.ordering, scope: options.scope, test_strategy: options.testStrategy, project_type: options.projectType, applicable_notes: applicableNotes, obligations: combineTestObligations(options.scope, options.testStrategy), plan_profile: buildPlanProfile( selected.value.methodology, selected.value.ordering, options.projectType, ), input_sha256: hashObject(input), }; return { ...body, contract_sha256: hashObject(body) }; } export function resolveTestingPosture( projectDir: string, ): TestingPostureContract { const space = resolveWorkflowSelection(projectDir).space; const memoryDir = join(projectDir, "aidlc", "spaces", space, "memory"); const sections: TestingPostureSections = {}; for (const layer of ["org", "team", "project"] as const) { const file = join(memoryDir, `${layer}.md`); if (!existsSync(file)) continue; sections[layer] = extractTestingPostureSection(readFileSync(file, "utf-8")); } let state = ""; try { state = readFileSync(stateFilePath(projectDir), "utf-8"); } catch { // Pre-creation and focused tests use deterministic defaults. } return resolveTestingPostureFromSections(sections, { scope: (getField(state, "Scope") ?? "feature").trim().toLowerCase(), testStrategy: normalizeStrategy(getField(state, "Test Strategy") ?? "standard"), projectType: normalizeProjectType(getField(state, "Project Type") ?? "greenfield"), }); } export function renderTestingContract(contract: TestingPostureContract): string { return `${CONTRACT_HEADING}\n\n\`\`\`json\n${JSON.stringify(contract, null, 2)}\n\`\`\`\n`; } function rawMarkdownSection(content: string, heading: string): string { const lines = content.replace(/\r\n/g, "\n").split("\n"); const body: string[] = []; let found = false; let inFence = false; for (const line of lines) { if (/^```/.test(line)) { if (found) body.push(line); inFence = !inFence; continue; } if (!inFence && line.trimEnd() === heading) { found = true; continue; } if (found && !inFence && /^## [^\n]*$/.test(line)) break; if (found) body.push(line); } return found ? body.join("\n") : ""; } export function parseTestingContract(plan: string): TestingPostureContract | null { const section = rawMarkdownSection(plan, CONTRACT_HEADING); const match = section.match(/```json[ \t]*\r?\n([\s\S]*?)\r?\n```/i); if (!match) return null; try { const parsed = JSON.parse(match[1]) as TestingPostureContract; if ( parsed.version !== 1 || !/^sha256:[0-9a-f]{64}$/.test(parsed.contract_sha256 ?? "") ) { return null; } const { contract_sha256: recorded, ...body } = parsed; return hashObject(body) === recorded ? parsed : null; } catch { return null; } } // --- The Plan Approval content projection ------------------------------------- // // The approval must survive the edit the stage itself ORDERS after approval, and // must not survive an edit to the plan. Byte-exact hashing cannot do both: Step 4 // tells the developer agent to tick the plan's checkboxes as it works, so hashing // raw bytes invalidated every approval as soon as the approved work started. // // So the fingerprint is taken over a projection that erases exactly these // mutations and nothing else: // // 1. A TERMINAL `## Review` appendix is removed, using the engine's own // appendix locator (a `## Review` inside a fence or an HTML comment, a // lower-case or unspaced variant, and a mid-plan section are all NOT an // appendix and stay material). This is a legacy-compatibility step: the // reviewer used to append its verdict to the plan because the plan is the // stage's review artifact. Reviews live in review records now and nothing // appends to the plan, but a plan reviewed under the earlier protocol may // still carry that section, and its approval must not depend on it. // 2. List task markers are reset: `[x]`, `[X]` and `[-]` become `[ ]`, outside // fenced blocks and HTML comments. A tick is a claim about execution, not a // change to the plan. // 3. Line endings become LF, trailing whitespace per line is dropped OUTSIDE // fences, runs of blank lines outside fences and comments collapse to one, and // trailing blank lines are dropped. These are editor artifacts, not content. // Inside a fence every byte is kept, because there a whitespace-only line can // be the difference between two patches. // // Everything else is byte-exact, INCLUDING the fenced `## Testing Contract` JSON // and any text inside code fences. Reordering, rewording, adding or deleting a // step, changing a number, a path, or the contract hash all change the // projection. // // The one thing the projection cannot see is an edit made INSIDE a terminal // review appendix. That is closed elsewhere: the worker brief carries the plan // BODY (this projection's input), produced by the `brief` command and checked by // the dispatch guard, so a step smuggled into the appendix is never delivered as // work. // // This projection is for the PLAN only. The unit-test instructions are not a // review artifact and have no mandated post-approval mutation, so they bind // byte-exactly (line endings aside): see `projectInstructionsContent`. const PLAN_TASK_MARKER_RE = /^([ \t]*(?:[-*+]|\d+[.)])[ \t]+)\[[xX-]\](?=[ \t]|$)/; export function projectPlanApprovalContent(text: string): string { const retained = contentBeforeTerminalReviewAppendix(text.replace(/^\uFEFF/, "")); const projected: string[] = []; let fence: MarkdownFence | null = null; let inComment = false; let previousBlank = false; for (const rawLine of retained.replace(/\r\n?/g, "\n").split("\n")) { const line = rawLine.replace(/[ \t]+$/, ""); if (fence) { // Verbatim inside a fence: a whitespace-only line in a diff or a Python block // is content, not an editor artifact, and two fences differing only there // apply different patches. projected.push(rawLine); previousBlank = false; if (closesFence(line, fence)) fence = null; continue; } if (inComment) { projected.push(line); previousBlank = false; if (line.includes("-->")) inComment = false; continue; } const opening = fenceOpening(line); if (opening) { fence = opening; projected.push(line); previousBlank = false; continue; } if (/^ {0,3}")) { inComment = true; projected.push(line); previousBlank = false; continue; } const blank = line.length === 0; if (blank && previousBlank) continue; previousBlank = blank; projected.push(line.replace(PLAN_TASK_MARKER_RE, "$1[ ]")); } while (projected.length > 0 && projected[projected.length - 1] === "") { projected.pop(); } return projected.join("\n"); } // The unit-test instructions as the fingerprint binds them and as the worker // brief hands them over: every byte, with only the line endings normalized. No // review strip, no task-marker reset, no whitespace folding, not even a BOM // dropped: the instructions are sent to the developer in full, so anything that // can change what the developer reads must reopen approval. export function projectInstructionsContent(text: string): string { return text.replace(/\r\n?/g, "\n"); } // The value recorded as `[Approval Fingerprint]:`. It binds CONTENT (the // projected plan, the byte-exact unit-test instructions, and the Testing Contract // hash) to PLACE (target, intent) and to ATTEMPT (the run floor). The tag carries // a format version so a value recorded under a previous scheme (issuance-bound, // or instructions projected like the plan) is recognised and answered with // "approve again" instead of an unexplained mismatch. export function approvalFingerprint( plan: string, instructions: string, contractHash: string, authority: Pick, ): string { const digest = hashObject({ plan: projectPlanApprovalContent(plan), instructions: projectInstructionsContent(instructions), testing_contract: contractHash, target: authority.targetId, intent: authority.intentId, run_floor: authority.runFloor, }); return `${APPROVAL_FINGERPRINT_PREFIX}${digest.slice("sha256:".length)}`; } // --- The worker brief ------------------------------------------------------------ // // What a code-generation worker is handed is exactly what the fingerprint bound, // and nothing else: the plan as the approval projection sees it (a terminal // `## Review` appendix removed, task markers reset to `[ ]`, spacing // normalized) and the unit-test instructions exactly as they were hashed. No // byte the fingerprint does not cover reaches the worker, on the interactive // path or the autonomous one, fresh or replayed. The brief is produced here, // from bytes proven to be the approved ones, so no conductor reads the plan // file into a prompt itself. The worker's own progress marks live in the plan // file it ticks as it works, not in the brief. export interface WorkerBrief { unit: string | null; contractHash: string; /** The exact text to hand the worker: marker lines, projected plan, instructions. */ brief: string; /** True when the plan carried a terminal review appendix, which the brief omits. */ appendixStripped: boolean; } /** The terminal `## Review` appendix of a plan, or "" when it carries none. */ export function planReviewAppendix(plan: string): string { const body = contentBeforeTerminalReviewAppendix(plan); return plan.slice(body.length); } export function workerBrief( projectDir: string, target: CodeGenerationTarget, ): WorkerBrief { const approval = evaluateCodeGenerationApproval(projectDir, target); if (!approval.ok || approval.contractHash === null || approval.approvalFingerprint === null) { throw new Error( `Cannot assemble a worker brief for ${ target.unit ? `unit "${target.unit}"` : "the stage-level target" }: ${approval.reason || "Plan Approval is not current"}`, ); } // Read the two files once, then prove THESE bytes are the approved ones by // recomputing the fingerprint over them and matching the validated tag. A // file that changed between the evaluation and this read cannot pass, so the // brief is never assembled from bytes the approval did not cover. const stageDir = codeGenerationRecordDir(projectDir, target.unit); const plan = readFileSync(join(stageDir, "code-generation-plan.md"), "utf-8"); const instructions = readFileSync(join(stageDir, "unit-test-instructions.md"), "utf-8"); const authority = resolveCodeGenerationAuthority(projectDir, target); const snapshotFingerprint = approvalFingerprint( plan, instructions, approval.contractHash, authority, ); if (snapshotFingerprint !== approval.approvalFingerprint) { throw new Error( "Cannot assemble a worker brief: the plan or instructions changed while the brief " + "was being assembled. Re-run the fingerprint command, re-present the plan, and approve again.", ); } const projectedPlan = projectPlanApprovalContent(plan); const marker = target.unit ? `AIDLC-UNIT: ${target.unit}` : "AIDLC-STAGE: code-generation"; const brief = `${marker}\n` + `AIDLC-TESTING-CONTRACT: ${approval.contractHash}\n` + "\n## Approved plan\n\n" + `${projectedPlan}\n` + "\n## Approved unit-test instructions\n\n" + projectInstructionsContent(instructions); return { unit: approval.unit, contractHash: approval.contractHash, brief, appendixStripped: planReviewAppendix(plan.replace(/^\uFEFF/, "")).length > 0, }; } function isPlanApprovalLabel(value: string): boolean { let normalized = value.trim().replace(/[?:][ \t]*$/, "").trim(); for (const marker of ["**", "__", "*", "_"]) { if ( normalized.startsWith(marker) && normalized.endsWith(marker) && normalized.length > marker.length * 2 ) { normalized = normalized.slice(marker.length, -marker.length).trim(); break; } } return normalized.toLowerCase() === "plan approval"; } function latestPlanApproval(body: string): { found: boolean; answer: string | null; fingerprint: string | null; plannedSource: string | null; } { let inPlanApproval = false; let awaitingNumberedQuestionText = false; let foundPlanApproval = false; let latestAnswer: string | null = null; let latestFingerprint: string | null = null; let latestPlannedSource: string | null = null; for (const line of visibleMarkdownLines(body)) { const heading = line.match(MARKDOWN_HEADING_RE); if (heading) { const headingText = heading[2].trim(); inPlanApproval = isPlanApprovalLabel( headingText.replace(QUESTION_PREFIX_RE, ""), ); awaitingNumberedQuestionText = !inPlanApproval && NUMBERED_QUESTION_HEADING_RE.test(headingText); if (inPlanApproval) { foundPlanApproval = true; latestAnswer = null; latestFingerprint = null; latestPlannedSource = null; } continue; } if (awaitingNumberedQuestionText && line.trim().length > 0) { awaitingNumberedQuestionText = false; inPlanApproval = isPlanApprovalLabel(line); if (inPlanApproval) { foundPlanApproval = true; latestAnswer = null; latestFingerprint = null; latestPlannedSource = null; } } if (!inPlanApproval) continue; const answer = line.match(ANSWER_TAG_RE); if (answer) latestAnswer = answer[1].trim(); const fingerprint = line.match(FINGERPRINT_TAG_RE); if (fingerprint) latestFingerprint = fingerprint[1] ?? null; const plannedSource = line.match(PLANNED_SOURCE_TAG_RE); if (plannedSource) latestPlannedSource = plannedSource[1] ?? null; } return { found: foundPlanApproval, answer: latestAnswer, fingerprint: latestFingerprint, plannedSource: latestPlannedSource, }; } export function questionsFileApproved(body: string): boolean { const latest = latestPlanApproval(body); return ( latest.found && latest.answer !== null && APPROVE_PLAN_RE.test(latest.answer) ); } export function questionsFileHasPendingPlanApproval(body: string): boolean { const latest = latestPlanApproval(body); return ( latest.found && latest.answer !== null && /^_*$/.test(latest.answer) ); } export function questionsFileApprovalFingerprint(body: string): string | null { return latestPlanApproval(body).fingerprint; } export function questionsFilePlannedSource(body: string): string | null { return latestPlanApproval(body).plannedSource; } export function promptTestingContractMarkers(text: string): string[] { const hashes = new Set(); for (const line of text.split(/\r?\n/)) { const marker = line.match(CONTRACT_MARKER_RE); if (marker) hashes.add(marker[1]); } return Array.from(hashes); } function normalizeCodeGenerationTarget(target: CodeGenerationTarget): CodeGenerationTarget { if (target.unit === null) return { unit: null }; const unit = target.unit.trim(); const error = validateUnitName(unit); if (error) throw new Error(error); return { unit }; } export function codeGenerationTargetId(target: CodeGenerationTarget): string { const normalized = normalizeCodeGenerationTarget(target); return normalized.unit === null ? "stage:code-generation" : `unit:${normalized.unit}`; } export function resolveCodeGenerationAuthority( projectDir: string, requestedTarget: CodeGenerationTarget, ): CodeGenerationAuthority { const target = normalizeCodeGenerationTarget(requestedTarget); const statePath = stateFilePath(projectDir); if (!existsSync(statePath)) { throw new Error("Code Generation approval authority requires an active workflow state"); } const state = readFileSync(statePath, "utf-8"); const marker = readActiveDirectiveMarker(projectDir, state); if (marker?.version !== 2) { throw new Error( "Code Generation approval authority is unavailable because the active directive is missing, stale, or legacy; run a fresh `next`", ); } if (marker.stage !== "code-generation") { throw new Error( `Code Generation approval authority does not match active directive stage "${marker.stage}"`, ); } if (marker.kind !== "run-stage" && marker.kind !== "invoke-swarm") { throw new Error( `Code Generation approval authority requires a run-stage or invoke-swarm directive, got "${marker.kind}"`, ); } if (target.unit === null) { if (marker.kind !== "run-stage" || marker.unit !== undefined) { throw new Error( "Stage-level Code Generation approval requires a zero-Unit run-stage directive", ); } } else if (marker.kind === "run-stage") { if (marker.unit !== target.unit) { throw new Error( `Code Generation approval target unit "${target.unit}" does not match active directive unit "${marker.unit ?? "(none)"}"`, ); } } else { const dag = resolveBoltDag(projectDir); if ( dag.state !== "ok" || !dag.units.includes(target.unit) || !marker.units?.includes(target.unit) ) { throw new Error( `Code Generation approval target unit "${target.unit}" is not in the active swarm directive and authoritative Unit DAG`, ); } } const issuanceRevision = marker.code_generation_authority_revision ?? marker.active_attempt?.result_revision ?? marker.revision; if (!Number.isInteger(issuanceRevision)) { throw new Error("Code Generation active directive has no stable issuance revision"); } const markerRevision = Number(issuanceRevision); const targetId = codeGenerationTargetId(target); const intentId = marker.intent_uuid ?? "bare-space"; const sourceFloor = marker.code_generation_source_sha256 ?? UNBINDABLE_FINGERPRINT; // Pass the target Unit: a GATE_REJECTED row for one Unit carries that Unit, and // without it a rejected per-Unit gate moved no Unit's approval floor in team // mode while moving it in solo. const runFloor = latestMainWorkflowStageRunFloorForProject( projectDir, "code-generation", getField(state, "Construction Iteration")?.trim() === "unit-major", target.unit ?? undefined, ); const directiveEpoch = hashObject({ version: marker.version, project: marker.project_sha256, intent: marker.intent_uuid, state: marker.state_sha256, stage: marker.stage, directive_unit: marker.unit ?? null, kind: marker.kind, issuance_revision: issuanceRevision, owner_epoch: marker.owner_epoch, context_epoch: marker.context_epoch, continue_token: marker.continue_token_sha256 ?? null, target: targetId, source_floor: sourceFloor, }); return { unit: target.unit, targetId, intentId, directiveEpoch, runFloor, stageDir: codeGenerationRecordDir(projectDir, target.unit), sourceFloor, markerRevision, }; } export function codeGenerationRecordDir( projectDir: string, unit: string | null, ): string { const root = join(docsRoot(projectDir), "construction"); const normalizedUnit = unit?.trim() ?? ""; return normalizedUnit.length > 0 ? join(root, normalizedUnit, "code-generation") : join(root, "code-generation"); } function codeGenerationApprovalArtifacts( projectDir: string, authority: CodeGenerationAuthority, ): { plan: string; instructions: string; questions: string; planExists: boolean; instructionsExist: boolean; approvedAnswer: boolean; contractValid: boolean; contractHash: string | null; expectedFingerprint: string | null; recordedFingerprint: string | null; questionsPath: string; } { const planPath = join(authority.stageDir, "code-generation-plan.md"); const instructionsPath = join(authority.stageDir, "unit-test-instructions.md"); const questionsPath = join(authority.stageDir, "code-generation-questions.md"); const plan = existsSync(planPath) ? readFileSync(planPath, "utf-8") : ""; const instructions = existsSync(instructionsPath) ? readFileSync(instructionsPath, "utf-8") : ""; const questions = existsSync(questionsPath) ? readFileSync(questionsPath, "utf-8") : ""; const planExists = plan.trim().length > 0; const instructionsExist = instructions.trim().length > 0; const approvedAnswer = questionsFileApproved(questions); const embedded = planExists ? parseTestingContract(plan) : null; const current = planExists ? resolveTestingPosture(projectDir) : null; const contractHash = embedded?.contract_sha256 ?? null; const contractValid = embedded !== null && current !== null && embedded.contract_sha256 === current.contract_sha256; const expectedFingerprint = planExists && instructionsExist && contractValid && current ? approvalFingerprint( plan, instructions, current.contract_sha256, authority, ) : null; return { plan, instructions, questions, planExists, instructionsExist, approvedAnswer, contractValid, contractHash, expectedFingerprint, recordedFingerprint: questionsFileApprovalFingerprint(questions), questionsPath, }; } export interface LegacyPlanApprovalGuardState { active: boolean; approved: boolean; pending: boolean; humanAfterDecision: boolean; sourceFloorValid: boolean; violated?: boolean; target: CodeGenerationTarget | null; } /** * Legacy Kiro IDE PreToolUse payloads identify the tool but omit its arguments. * The adapter therefore cannot distinguish a planning-record write from a * workspace mutation. This state lets it preserve the usable workflow: * planning remains available before the exact Plan Approval prompt, every tool * hard-stops while that prompt awaits a human, and workspace source is checked * against the `[Planned Source]` the questions file records, exactly as the * answer path checks it. Before a planned source is recorded there is nothing * to compare; the adapter records the live source when it mediates the * decision. After one is recorded, drift is refused with a remedy the conductor * can always execute (re-present the plan), never with "revert the workspace". */ export function legacyPlanApprovalGuardState( projectDir: string, ): LegacyPlanApprovalGuardState { const inactive: LegacyPlanApprovalGuardState = { active: false, approved: false, pending: false, humanAfterDecision: false, sourceFloorValid: true, violated: false, target: null, }; try { const statePath = stateFilePath(projectDir); if (!existsSync(statePath)) return inactive; const state = readFileSync(statePath, "utf-8"); const marker = readActiveDirectiveMarker(projectDir, state); if ( marker?.version !== 2 || marker.stage !== "code-generation" || (marker.kind !== "run-stage" && marker.kind !== "invoke-swarm") ) { return inactive; } let target: CodeGenerationTarget; if (marker.kind === "run-stage") { target = { unit: marker.unit?.trim() || null }; } else { const units = marker.units ?? []; if (units.length === 0) { throw new Error("active swarm directive carries no authoritative units"); } const pending = units.find( (unit) => !evaluateCodeGenerationApproval(projectDir, { unit }).ok, ); target = { unit: pending ?? units[0] }; } const authority = resolveCodeGenerationAuthority(projectDir, target); const violation = readPlanApprovalViolation(projectDir); const violated = violation?.version === 1 && violation.markerRevision === authority.markerRevision; const approval = evaluateCodeGenerationApproval(projectDir, target); const artifacts = codeGenerationApprovalArtifacts(projectDir, authority); const plannedSource = questionsFilePlannedSource(artifacts.questions); const sourceFloorValid = plannedSource === null || plannedSource === UNBINDABLE_FINGERPRINT || workspaceSourceFingerprint(projectDir) === plannedSource; if (approval.ok) { return { active: true, approved: true, pending: false, humanAfterDecision: false, sourceFloorValid: true, violated, target, }; } if (artifacts.expectedFingerprint === null) { return { active: true, approved: false, pending: false, humanAfterDecision: false, sourceFloorValid, violated, target, }; } const promptSha256 = createHash("sha256") .update( `${artifacts.questions .replace(/^\[Answer\]:[ \t]*.*$/gm, "[Answer]:") .trimEnd()}\n`, "utf-8", ) .digest("hex"); const allEntries = readAuditShardEvents(projectDir); type Entry = (typeof allEntries)[number]; const latestCausal = (candidates: Entry[]): Entry | null => { if (candidates.length === 0) return null; let latestTimestamp = candidates[0].timestamp; for (const candidate of candidates) { if (candidate.timestamp > latestTimestamp) latestTimestamp = candidate.timestamp; } const atLatestTimestamp = candidates.filter( (candidate) => candidate.timestamp === latestTimestamp, ); if (new Set(atLatestTimestamp.map((candidate) => candidate.shard)).size !== 1) { return null; } return atLatestTimestamp.reduce((latest, candidate) => candidate.pos > latest.pos ? candidate : latest ); }; const latestSession = latestCausal( allEntries.filter( (entry) => entry.event === "SESSION_STARTED" || entry.event === "SESSION_RESUMED", ), ); const session = latestSession === null ? null : auditBlockField(latestSession.block, "Session"); const challenge = session === null ? null : readPlanApprovalChallenge(projectDir, session); const response = session === null ? null : readPlanApprovalResponse(projectDir, session); const challengeMatches = challenge !== null && runtimeIdentityMatches(challenge, { targetId: authority.targetId, intentId: authority.intentId, runFloor: authority.runFloor, fingerprint: artifacts.expectedFingerprint, questionsFile: toPosix(relative(projectDir, artifacts.questionsPath)), promptSha256, }); const humanAfterDecision = challengeMatches && response !== null && response.challengeId === challenge.challengeId; return { active: true, approved: false, pending: challengeMatches && !humanAfterDecision, humanAfterDecision, sourceFloorValid, violated, target, }; } catch { return { active: true, approved: false, pending: false, humanAfterDecision: false, sourceFloorValid: false, violated: true, target: null, }; } } function runtimeIdentity( evidence: PlanApprovalQuestionEvidence, ): PlanApprovalRuntimeIdentity { return { targetId: evidence.authority.targetId, intentId: evidence.authority.intentId, runFloor: evidence.authority.runFloor, fingerprint: evidence.fingerprint, questionsFile: evidence.questionsRelativePath, promptSha256: evidence.promptSha256, }; } // Recorded, never compared. The directive epoch and marker revision describe the // directive that happened to be issued when the human answered; the legacy Kiro // IDE window handshake still reads the revision off a challenge, and both help a // human reading the store understand where a receipt came from. function runtimeProvenance( evidence: PlanApprovalQuestionEvidence, ): PlanApprovalRuntimeProvenance { return { directiveEpoch: evidence.authority.directiveEpoch, sourceFloor: evidence.authority.sourceFloor, markerRevision: evidence.authority.markerRevision, plannedSourceSha256: evidence.plannedSourceSha256, }; } function runtimeIdentityMatches( value: PlanApprovalRuntimeIdentity, expected: PlanApprovalRuntimeIdentity, ): boolean { return ( value.targetId === expected.targetId && value.intentId === expected.intentId && value.runFloor === expected.runFloor && value.fingerprint === expected.fingerprint && value.questionsFile === expected.questionsFile && value.promptSha256 === expected.promptSha256 ); } export function recordPlanApprovalChallenge( projectDir: string, evidence: PlanApprovalQuestionEvidence, session: string, options: [string, string] = ["Approve Plan", "Request Changes"], requireExactOptionLabels = false, hashOptionLabels = false, useLegacyDirectiveOffer = false, ): PlanApprovalRuntimeChallenge { if (!session.trim()) { throw new Error("Plan Approval challenge requires a nonblank session"); } const identity = runtimeIdentity(evidence); const provenance = runtimeProvenance(evidence); if ( (hashOptionLabels || useLegacyDirectiveOffer) && readPlanApprovalChallenge(projectDir, session) ) { throw new Error( "a protected legacy Plan Approval challenge is already pending for this session", ); } const createChallenge = (): PlanApprovalRuntimeChallenge => { const offer = useLegacyDirectiveOffer ? readPlanApprovalLegacyOffer(projectDir, session) : null; if ( useLegacyDirectiveOffer && ( !offer || offer.intentId !== identity.intentId || offer.markerRevision !== provenance.markerRevision || !offer.allowedUnits.some((unit) => unit === evidence.authority.unit) ) ) { throw new Error( "legacy Plan Approval requires protected choices from the invoking Code Generation directive", ); } const effectiveHashedOptions = hashOptionLabels || useLegacyDirectiveOffer; const storedOptions: [string, string] = offer ? offer.options : hashOptionLabels ? options.map((option) => createHash("sha256") .update(option.trim().toLowerCase(), "utf-8") .digest("hex") ) as [string, string] : options; const challenge: PlanApprovalRuntimeChallenge = { version: 1, ...identity, ...provenance, session, // The challenge id covers the compared identity, the session, and the exact // options offered. Provenance is deliberately outside it: a challenge that // rotated with every directive re-issue is the churn this change removes. challengeId: hashObject({ ...identity, session, options: storedOptions, requireExactOptionLabels, hashedOptionLabels: effectiveHashedOptions, legacyDirectiveOffer: useLegacyDirectiveOffer, }), options: storedOptions, requireExactOptionLabels, hashedOptionLabels: effectiveHashedOptions, }; writePlanApprovalChallenge(projectDir, challenge); if (useLegacyDirectiveOffer) { clearPlanApprovalLegacyOffer(projectDir, session); } return challenge; }; return useLegacyDirectiveOffer ? withActiveDirectiveLock(projectDir, createChallenge) : createChallenge(); } function offeredPlanApprovalChoice( challenge: PlanApprovalRuntimeChallenge, responseText: string, ): "Approve Plan" | "Request Changes" | null { // One trailing "(Recommended)" is the Codex label decoration, not part of the // human's choice. Nothing else about the match is loosened. const response = stripRecommendedDecorator(responseText); const comparison = challenge.hashedOptionLabels ? createHash("sha256") .update(response.toLowerCase(), "utf-8") .digest("hex") : response.toLowerCase(); const matchedIndex = challenge.options.findIndex((option) => challenge.hashedOptionLabels ? option === comparison : option.toLowerCase() === comparison ); if (matchedIndex >= 0) { return matchedIndex === 0 ? "Approve Plan" : "Request Changes"; } if (challenge.requireExactOptionLabels) return null; if (response === "1") return "Approve Plan"; if (response === "2") return "Request Changes"; if (response.toLowerCase() === "approve plan") return "Approve Plan"; if (response.toLowerCase() === "request changes") return "Request Changes"; return null; } export interface PlanApprovalHumanResponseResult { recorded: boolean; } export function recordPlanApprovalHumanResponse( projectDir: string, session: string, responseText: string, ): PlanApprovalHumanResponseResult { const challenge = readPlanApprovalChallenge(projectDir, session); if (challenge) { const choice = offeredPlanApprovalChoice(challenge, responseText); if (choice) { writePlanApprovalResponse(projectDir, { version: 1, session, challengeId: challenge.challengeId, choice, responseSha256: createHash("sha256") .update(responseText.trim(), "utf-8") .digest("hex"), }); return { recorded: true }; } } const recovery = readPlanApprovalLegacyRecoveryChallenge( projectDir, session, ); if ( recovery && responseText.trim() === LEGACY_PLAN_APPROVAL_RECOVERY_CHOICE ) { writePlanApprovalLegacyRecoveryResponse(projectDir, { version: 1, session, challengeId: recovery.challengeId, responseSha256: createHash("sha256") .update(LEGACY_PLAN_APPROVAL_RECOVERY_CHOICE, "utf-8") .digest("hex"), }); return { recorded: true }; } return { recorded: false }; } export interface PlanApprovalOverrideRequestResult { recorded: boolean; } /** * Half A of the break-glass pairing. Called by the human-turn hook ONLY for a * typed prompt (the UserPromptSubmit text), never for a picked option arriving * through a tool response. The whole trimmed prompt must be the single line * `Override Plan Approval: `; the reason is kept verbatim (trimmed) and * its sha256 is what `answer --override` must match for the same session. */ export function recordPlanApprovalOverrideRequest( projectDir: string, session: string, promptText: string, ): PlanApprovalOverrideRequestResult { if (!session.trim()) return { recorded: false }; const match = PLAN_APPROVAL_OVERRIDE_PHRASE_RE.exec(promptText.trim()); if (!match) return { recorded: false }; const reason = match[1].trim(); if (!reason) return { recorded: false }; const request: PlanApprovalOverrideRequest = { version: 1, session, reason, reasonSha256: planApprovalOverrideReasonSha256(reason), requestedAt: isoTimestamp(), intentId: activeIntentUuid(projectDir) ?? "bare-space", }; writePlanApprovalOverrideRequest(projectDir, request); return { recorded: true }; } export function planApprovalOverrideReasonSha256(reason: string): string { return createHash("sha256").update(reason.trim(), "utf-8").digest("hex"); } export interface PlanApprovalReceiptResult { receipt: PlanApprovalRuntimeReceipt | null; /** Human lines for source drift accepted under `relaxed` while certifying. */ changeNotices: string[]; } export function recordPlanApprovalReceipt( projectDir: string, evidence: PlanApprovalQuestionEvidence, session: string, choice: "Approve Plan" | "Request Changes", ): PlanApprovalReceiptResult { return withActiveDirectiveLock(projectDir, () => certifyPlanApprovalReceipt(projectDir, evidence, session, choice), ); } // The receipt path proper. Caller holds the active-directive lock (it is not // reentrant), so the break-glass path can run this first and its own write // second inside one transaction. function certifyPlanApprovalReceipt( projectDir: string, evidence: PlanApprovalQuestionEvidence, session: string, choice: "Approve Plan" | "Request Changes", ): PlanApprovalReceiptResult { const identity = runtimeIdentity(evidence); const provenance = runtimeProvenance(evidence); const challenge = readPlanApprovalChallenge(projectDir, session); const response = readPlanApprovalResponse(projectDir, session); if ( !challenge || !response || challenge.challengeId !== response.challengeId || response.choice !== choice || !runtimeIdentityMatches(challenge, identity) ) { throw new Error( "Plan Approval requires the actual offered choice from this prompt and session", ); } const receiptBarrier = process.env.AIDLC_TEST_PLAN_APPROVAL_RECEIPT_BARRIER?.trim(); if (receiptBarrier) { writeFileSync(`${receiptBarrier}.snapshotted`, "snapshotted\n", "utf-8"); const waitCell = new Int32Array(new SharedArrayBuffer(4)); const deadline = Date.now() + 30_000; while (!existsSync(`${receiptBarrier}.release`)) { if (Date.now() >= deadline) { throw new Error("timed out waiting at Plan Approval receipt barrier"); } Atomics.wait(waitCell, 0, 0, 10); } } if (choice === "Request Changes") { // Requesting changes withdraws the decision, so it clears BOTH halves: the // challenge AND any receipt for this exact identity. Without the second // clear, identical content could be re-approved by rewriting the answer tag, // because nothing else about the identity had moved. A typed break-glass // request is withdrawn with it. clearPlanApprovalChallenge(projectDir, session); clearPlanApprovalReceipt(projectDir, identity); clearPlanApprovalOverrideRequest(projectDir, session); return { receipt: null, changeNotices: [] }; } // Certify the source twice, then write. The answer path never unlinks a // receipt it just wrote: a mutation that lands between the two reads is // refused before anything exists on disk, and one that lands after the // second read is caught by generation start, which keeps the receipt and // asks for re-approval. Source that moved since the plan was fingerprinted // is the governed drift: strict refuses, relaxed records the change and // certifies the source found now, which every later check compares against. const stateBefore = workspaceSourceState(projectDir); const sourceBefore = stateBefore?.fingerprint ?? null; if (sourceBefore === null) { throw new PlanApprovalUnbindableError("recorded"); } const changeNotices: string[] = []; if (sourceBefore !== evidence.plannedSourceSha256) { const judged = judgePlanSourceDrift( projectDir, evidence.authority.unit, evidence.plannedSourceSha256, stateBefore, true, ); if ("refusal" in judged) throw judged.refusal; changeNotices.push(...recordAcceptedChanges(projectDir, [judged.accepted])); } const sourceAfter = workspaceSourceFingerprint(projectDir); if (sourceAfter === null || sourceAfter !== sourceBefore) { throw new Error( "Plan Approval source changed during receipt certification. " + "Re-run the fingerprint command and re-present the plan.", ); } const receipt: PlanApprovalRuntimeReceipt = { version: 1, ...identity, ...provenance, session, challengeId: challenge.challengeId, choice: "Approve Plan", questionsSha256: evidence.questionsSha256, certifiedSourceSha256: sourceBefore, status: "approved", }; writePlanApprovalReceipt(projectDir, receipt); keepWorkspaceSourceSnapshot(projectDir, stateBefore); clearPlanApprovalChallenge(projectDir, session); // A normal receipt spends any typed break-glass request too: the phrase // authorized at most one run, and that run needed no override. clearPlanApprovalOverrideRequest(projectDir, session); // Sweep this target's receipts from attempts that have ended. Nothing deletes a // receipt to invalidate it any more, so the store is tidied here instead. collectStalePlanApprovalReceipts( projectDir, identity.intentId, identity.targetId, identity.runFloor, ); return { receipt, changeNotices }; } export interface PlanApprovalOverrideReceiptResult { receipt: PlanApprovalRuntimeReceipt; /** False when the normal path succeeded and no override was written. */ overridden: boolean; failedChecks: string[]; changeNotices: string[]; } export class PlanApprovalOverrideHumanOnlyError extends Error { constructor() { super(PLAN_APPROVAL_OVERRIDE_HUMAN_ONLY); this.name = "PlanApprovalOverrideHumanOnlyError"; } } /** * The typed request that authorizes `answer --override` for this session and * reason, or null. Half A must have written it (the human typed the phrase), * its digest must be the digest of the reason given now, its stored reason must * hash to its own stored digest (an edited file is not a request), and, once * the evidence names the intent, it must have been typed under that intent. */ export function authorizingPlanApprovalOverrideRequest( projectDir: string, session: string, reason: string, intentId: string | null, ): PlanApprovalOverrideRequest | null { const request = readPlanApprovalOverrideRequest(projectDir, session); if (request === null) return null; const expected = planApprovalOverrideReasonSha256(reason); if (request.reasonSha256 !== expected) return null; if (planApprovalOverrideReasonSha256(request.reason) !== expected) return null; if (intentId !== null && request.intentId !== intentId) return null; return request; } /** * Half B of the break-glass pairing. The normal receipt path runs first; when * it succeeds there was nothing to override and its receipt stands. When it * refuses, its refusal (plus the source-boundary state when the workspace * cannot be bound) is kept as the failed checks, the typed request is * re-validated and consumed inside this same transaction (a request that * vanished or changed since the caller's precheck is not a request), then * `recordOverride` is called so the ledger row lands FIRST, and only then is a * receipt bound to content and attempt only written: no challenge/response * pairing and no source certification. A row that cannot be appended leaves no * receipt and spends no request, so the human's phrase is still there for the * retry. The certified source is whatever binds now, or `unbindable`; * downstream checks skip the source comparison for an override receipt. */ export function recordPlanApprovalOverrideReceipt( projectDir: string, evidence: PlanApprovalQuestionEvidence, session: string, reason: string, recordOverride: (failedChecks: readonly string[]) => void, ): PlanApprovalOverrideReceiptResult { return withActiveDirectiveLock(projectDir, () => { if ( authorizingPlanApprovalOverrideRequest( projectDir, session, reason, evidence.authority.intentId, ) === null ) { throw new PlanApprovalOverrideHumanOnlyError(); } const failedChecks: string[] = []; try { const normal = certifyPlanApprovalReceipt(projectDir, evidence, session, "Approve Plan"); if (normal.receipt !== null) { return { receipt: normal.receipt, overridden: false, failedChecks, changeNotices: normal.changeNotices, }; } failedChecks.push("the normal receipt path recorded nothing"); } catch (error) { failedChecks.push(error instanceof Error ? error.message : String(error)); } const current = workspaceSourceState(projectDir); if (current === null) { failedChecks.push(`workspace source cannot be bound${workspaceSourceFailureSuffix()}`); } const identity = runtimeIdentity(evidence); const challenge = readPlanApprovalChallenge(projectDir, session); const receipt: PlanApprovalRuntimeReceipt = { version: 1, ...identity, ...runtimeProvenance(evidence), session, challengeId: challenge?.challengeId ?? "", choice: "Approve Plan", questionsSha256: evidence.questionsSha256, certifiedSourceSha256: current?.fingerprint ?? UNBINDABLE_FINGERPRINT, status: "approved", override: { reason, failedChecks: [...failedChecks] }, }; recordOverride(failedChecks); writePlanApprovalReceipt(projectDir, receipt); keepWorkspaceSourceSnapshot(projectDir, current); clearPlanApprovalChallenge(projectDir, session); clearPlanApprovalOverrideRequest(projectDir, session); collectStalePlanApprovalReceipts( projectDir, identity.intentId, identity.targetId, identity.runFloor, ); return { receipt, overridden: true, failedChecks, changeNotices: [] }; }); } export interface PlanApprovalEvidenceOptions { /** * Break-glass evidence binds to content and attempt only: the `[Planned * Source]` tag is read for provenance but never compared, so an unbindable or * moved workspace does not refuse the override. Every content check (plan, * instructions, Testing Contract, fingerprint, `[Answer]`) still applies. */ breakGlass?: boolean; } export function codeGenerationPlanApprovalQuestionEvidence( projectDir: string, target: CodeGenerationTarget, suppliedQuestionsFile: string, expectedAnswer: "" | "Approve Plan" | "Request Changes", options: PlanApprovalEvidenceOptions = {}, ): PlanApprovalQuestionEvidence { const authority = resolveCodeGenerationAuthority(projectDir, target); const expectedPath = resolve( authority.stageDir, "code-generation-questions.md", ); const suppliedPath = isAbsolute(suppliedQuestionsFile) ? resolve(suppliedQuestionsFile) : resolve(projectDir, suppliedQuestionsFile); if (suppliedPath !== expectedPath) { throw new Error( `Plan Approval questions file must be the active target's canonical file: ${toPosix(relative(projectDir, expectedPath))}`, ); } const artifacts = codeGenerationApprovalArtifacts(projectDir, authority); if (!artifacts.planExists || !artifacts.instructionsExist) { throw new Error("Plan Approval requires non-empty plan and unit-test instructions"); } if (!artifacts.contractValid || artifacts.expectedFingerprint === null) { throw new Error("Plan Approval requires the current Testing Contract"); } if (artifacts.recordedFingerprint !== artifacts.expectedFingerprint) { throw new Error( artifacts.recordedFingerprint !== null && !approvalFingerprintIsCurrentFormat(artifacts.recordedFingerprint) ? "The recorded Plan Approval fingerprint was written under an earlier format. " + "Re-run the fingerprint command, re-present the plan, and approve again." : "Plan Approval fingerprint does not match the active intent, target, stage attempt, plan, instructions, and Testing Contract. " + "Re-run the fingerprint command, re-present the plan, and approve again.", ); } const latest = latestPlanApproval(artifacts.questions); if (!latest.found || latest.answer === null || latest.answer !== expectedAnswer) { throw new Error( `Plan Approval questions file must contain exactly [Answer]: ${expectedAnswer || "(blank)"}`, ); } // The source the plan was written against, recorded by the fingerprint command. // The approval binds to THIS value rather than to the directive's sticky floor, // so drift is always answerable by re-fingerprinting and re-presenting; the // sticky floor could only be rotated by a receipt that required the floor to // match already, which is the loop that made an out-of-band `git pull` permanent. const plannedSource = latest.plannedSource ?? UNBINDABLE_FINGERPRINT; if (latest.plannedSource === null && !options.breakGlass) { throw new Error( "Plan Approval requires a [Planned Source]: tag in the Plan Approval section. " + "Re-run the fingerprint command, record both tags it prints, and re-present the plan.", ); } // A workspace that cannot be bound now is refused before anything is minted: // the challenge such a decision would create can never be accepted by the // normal receipt path, so the human would approve into a dead end. A planned // source recorded as `unbindable` while the workspace binds now is treated // as drift from that recording: strict asks for a re-fingerprint (which now // records a real source), relaxed re-baselines the tag before the challenge. const currentState = options.breakGlass ? null : workspaceSourceState(projectDir); const currentSource = currentState?.fingerprint ?? null; if (!options.breakGlass && currentSource === null) { throw new PlanApprovalUnbindableError(expectedAnswer === "" ? "presented" : "recorded"); } let questions = artifacts.questions; let boundSource = plannedSource; const changeNotices: string[] = []; if (!options.breakGlass && currentSource !== plannedSource) { const judged = judgePlanSourceDrift(projectDir, authority.unit, plannedSource, currentState, true); if ("refusal" in judged) throw judged.refusal; // The row is written BEFORE anything is re-baselined: a ledger that cannot // take it refuses here, with the drift still visible to the next attempt. changeNotices.push(...recordAcceptedChanges(projectDir, [judged.accepted])); // Before the challenge is minted (the decision record) the questions file // is still the conductor's draft, so the tag itself is re-baselined and the // human sees the plan against the source it will be approved on. At the // answer the prompt hash already binds these bytes; the receipt certifies // the current source instead, and that certified value is the baseline // every later check compares against. if (expectedAnswer === "" && currentSource !== null) { questions = upsertPlannedSourceTag(questions, currentSource); writeFileSync(suppliedPath, questions, "utf-8"); keepWorkspaceSourceSnapshot(projectDir, currentState); boundSource = currentSource; } } return { authority, fingerprint: artifacts.expectedFingerprint, questionsPath: suppliedPath, questionsRelativePath: toPosix(relative(projectDir, suppliedPath)), questionsSha256: createHash("sha256") .update(questions, "utf-8") .digest("hex"), promptSha256: createHash("sha256") .update( `${questions .replace(/^\[Answer\]:[ \t]*.*$/gm, "[Answer]:") .trimEnd()}\n`, "utf-8", ) .digest("hex"), plannedSourceSha256: boundSource, changeNotices, }; } export function evaluateCodeGenerationApproval( projectDir: string, target: CodeGenerationTarget, ): CodeGenerationApproval { let normalizedUnit: string | null = null; const empty: CodeGenerationApproval = { ok: false, unit: null, reason: "", planExists: false, instructionsExist: false, approved: false, contractValid: false, fingerprintValid: false, receiptValid: false, contractHash: null, approvalFingerprint: null, directiveEpoch: null, }; try { const normalizedTarget = normalizeCodeGenerationTarget(target); normalizedUnit = normalizedTarget.unit; empty.unit = normalizedUnit; const authority = resolveCodeGenerationAuthority(projectDir, normalizedTarget); empty.directiveEpoch = authority.directiveEpoch; const artifacts = codeGenerationApprovalArtifacts(projectDir, authority); empty.planExists = artifacts.planExists; empty.instructionsExist = artifacts.instructionsExist; empty.approved = artifacts.approvedAnswer; empty.contractValid = artifacts.contractValid; empty.contractHash = artifacts.contractHash; empty.approvalFingerprint = artifacts.expectedFingerprint; if (!empty.planExists) { empty.reason = "code-generation-plan.md is missing or empty"; return empty; } if (!empty.instructionsExist) { empty.reason = "unit-test-instructions.md is missing or empty"; return empty; } if (artifacts.contractHash === null) { empty.reason = "code-generation-plan.md has no valid ## Testing Contract JSON block"; return empty; } if (!empty.contractValid) { empty.reason = "the approved Testing Contract is stale because memory, scope, test strategy, or project type changed"; return empty; } if (!empty.approved) { empty.reason = "Plan Approval is not explicitly answered Approve Plan"; return empty; } empty.fingerprintValid = artifacts.expectedFingerprint !== null && artifacts.recordedFingerprint === artifacts.expectedFingerprint; if (!empty.fingerprintValid) { empty.reason = artifacts.recordedFingerprint !== null && !approvalFingerprintIsCurrentFormat(artifacts.recordedFingerprint) ? "the recorded Plan Approval fingerprint was written under an earlier format; re-run the fingerprint command, re-present the plan, and approve again" : "the Plan Approval fingerprint does not match the active intent, target, stage attempt, plan, test instructions, and Testing Contract; re-run the fingerprint command, re-present the plan, and approve again"; return empty; } // The raw questions-file digest is provenance on the audit row, not part of // validity: the prompt hash below binds what the human saw, and a note // appended to the file after approval must not retire the decision. const promptSha256 = createHash("sha256") .update( `${artifacts.questions .replace(/^\[Answer\]:[ \t]*.*$/gm, "[Answer]:") .trimEnd()}\n`, "utf-8", ) .digest("hex"); const identity: PlanApprovalRuntimeIdentity = { targetId: authority.targetId, intentId: authority.intentId, runFloor: authority.runFloor, fingerprint: artifacts.expectedFingerprint!, questionsFile: toPosix(relative(projectDir, artifacts.questionsPath)), promptSha256, }; const violation = readPlanApprovalViolation(projectDir); if ( violation?.version === 1 && violation.markerRevision === authority.markerRevision ) { empty.reason = `legacy Plan Approval authority was poisoned by unsupported write target "${violation.target}"`; return empty; } const receipt = readPlanApprovalReceipt(projectDir, identity); // Source that moved after the receipt certified it is the governed drift: // strict retires the approval until the human approves again; relaxed keeps // it current (generation start records the change and re-baselines the // receipt). This evaluation reads and never writes, so it only judges. A // break-glass receipt is bound to content and attempt only, so its source // is never compared: the human already accepted that the source could not // be certified when they typed the override. let sourceDrift: string | null = null; if ( receipt !== null && receipt.status !== "generation" && receipt.override === undefined ) { const current = workspaceSourceState(projectDir); if (current === null || current.fingerprint !== receipt.certifiedSourceSha256) { const judged = judgePlanSourceDrift( projectDir, normalizedUnit, receipt.certifiedSourceSha256, current, false, ); if ("refusal" in judged) sourceDrift = judged.refusal.message; } } const sourceCurrent = receipt !== null && sourceDrift === null; empty.receiptValid = receipt !== null && runtimeIdentityMatches(receipt, identity) && receipt.choice === "Approve Plan" && sourceCurrent; if (!empty.receiptValid) { if (receipt !== null && sourceDrift !== null) { empty.reason = sourceDrift; empty.sourceDrift = true; return empty; } // Distinguish "never approved" from "approved in an attempt that has since // ended". The second is the case a redo jump or a rejected gate produces, // and it has a different instruction. const stale = stalePlanApprovalReceiptsForTarget( projectDir, authority.intentId, authority.targetId, authority.runFloor, ); empty.reason = stale.length > 0 ? "the Plan Approval receipt for this target belongs to an earlier stage attempt; present the plan again and approve it for the current attempt" : "no current protected Plan Approval receipt matches this prompt, session response, target, stage attempt, and plan content"; return empty; } return { ...empty, ok: true, reason: "approved", ...(receipt?.override !== undefined ? { override: true as const } : {}), }; } catch (error) { return { ...empty, unit: normalizedUnit, reason: error instanceof Error ? error.message : String(error), }; } } export function beginCodeGeneration( projectDir: string, target: CodeGenerationTarget, ): string[] { return withAuditLock(projectDir, () => withActiveDirectiveLock(projectDir, () => { const approval = evaluateCodeGenerationApproval(projectDir, target); if (!approval.ok || !approval.approvalFingerprint) { if (approval.sourceDrift) throw new PlanApprovalSourceDriftError(approval.reason); throw new Error(approval.reason || "Code Generation requires Plan Approval"); } const authority = resolveCodeGenerationAuthority(projectDir, target); const receiptKey: PlanApprovalReceiptKey = { targetId: authority.targetId, runFloor: authority.runFloor, fingerprint: approval.approvalFingerprint, }; const receipt = readPlanApprovalReceipt(projectDir, receiptKey); if (!receipt) { throw new Error("Code Generation has no protected approval receipt"); } if (receipt.status === "generation") return []; if (receipt.override !== undefined) { // A break-glass receipt is bound to content and attempt only. There is // no certified source to compare or re-certify, and no race window to // close, so the generation boundary is published as the receipt stands. // This is the one place an override could have been downgraded to // "approve again": it is not. writePlanApprovalReceipt(projectDir, { ...receipt, status: "generation" }); collectStalePlanApprovalReceipts( projectDir, authority.intentId, authority.targetId, authority.runFloor, ); return []; } const stateBefore = workspaceSourceState(projectDir); const sourceBefore = stateBefore?.fingerprint ?? null; if (sourceBefore === null) { throw new PlanApprovalSourceDriftError(planSourceDriftStrictMessage(null, true)); } const changeNotices: string[] = []; if (sourceBefore !== receipt.certifiedSourceSha256) { // Strict refuses and KEEPS the receipt: deleting the human's recorded // decision because the workspace moved turned a recoverable drift into // a state with no way back, and a fresh approval re-baselines the // source this plan is bound to. Relaxed records the change and moves // that baseline to the source found now, so generation begins and the // same change is not reported again. const judged = judgePlanSourceDrift( projectDir, authority.unit, receipt.certifiedSourceSha256, stateBefore, true, ); if ("refusal" in judged) throw judged.refusal; changeNotices.push(...recordAcceptedChanges(projectDir, [judged.accepted])); keepWorkspaceSourceSnapshot(projectDir, stateBefore); } // Publication is the generation boundary. It sits between two source // fingerprints while both authority locks are held: neither another // guard nor directive publication can retire this receipt mid-start. writePlanApprovalReceipt(projectDir, { ...receipt, certifiedSourceSha256: sourceBefore, status: "generation", }); const publicationBarrier = process.env.AIDLC_TEST_PLAN_APPROVAL_PUBLICATION_BARRIER?.trim(); if (publicationBarrier) { writeFileSync(`${publicationBarrier}.published`, "published\n", "utf-8"); const waitCell = new Int32Array(new SharedArrayBuffer(4)); const deadline = Date.now() + 30_000; while (!existsSync(`${publicationBarrier}.release`)) { if (Date.now() >= deadline) { writePlanApprovalReceipt(projectDir, { ...receipt, status: "approved" }); throw new Error( "timed out waiting for the Plan Approval publication test barrier", ); } Atomics.wait(waitCell, 0, 0, 5); } } const sourceAfter = workspaceSourceFingerprint(projectDir); if (sourceAfter === null || sourceAfter !== sourceBefore) { // Revert the generation boundary rather than delete the approval: the // human's decision is still a fact, only the start is not. This is the // race window, not the governed drift, so both Change Control values // ask for the step again. writePlanApprovalReceipt(projectDir, { ...receipt, status: "approved" }); throw new Error( "Source files changed while code generation was starting. Retry the step.", ); } collectStalePlanApprovalReceipts( projectDir, authority.intentId, authority.targetId, authority.runFloor, ); return changeNotices; }), ); } function flagValue(args: string[], name: string): string | undefined { const index = args.indexOf(name); return index >= 0 ? args[index + 1] : undefined; } function targetFromArgs( args: string[], subcommand: "fingerprint" | "verify" | "begin" | "brief", ): CodeGenerationTarget { const unitIndex = args.indexOf("--unit"); const stageLevel = args.includes("--stage-level"); if (unitIndex >= 0 && stageLevel) { throw new Error(`${subcommand} accepts exactly one of --unit or --stage-level`); } if (unitIndex >= 0) { const unit = args[unitIndex + 1]; if (!unit || unit.startsWith("--") || unit.trim().length === 0) { throw new Error(`${subcommand} requires a non-blank --unit `); } return normalizeCodeGenerationTarget({ unit }); } if (stageLevel) return { unit: null }; throw new Error(`${subcommand} requires exactly one of --unit or --stage-level`); } export function main(argv: string[]): void { const subcommand = argv.find((arg) => ["resolve", "render", "fingerprint", "verify", "begin", "brief"].includes(arg) ); const projectDir = resolveProjectDir(flagValue(argv, "--project-dir")); try { switch (subcommand) { case "resolve": console.log(JSON.stringify(resolveTestingPosture(projectDir), null, 2)); return; case "render": process.stdout.write(renderTestingContract(resolveTestingPosture(projectDir))); return; case "fingerprint": { const target = targetFromArgs(argv, "fingerprint"); const authority = resolveCodeGenerationAuthority(projectDir, target); const approval = evaluateCodeGenerationApproval(projectDir, target); const stageDir = authority.stageDir; const plan = readFileSync(join(stageDir, "code-generation-plan.md"), "utf-8"); const instructions = readFileSync( join(stageDir, "unit-test-instructions.md"), "utf-8", ); const questionsPath = join(stageDir, "code-generation-questions.md"); if ( existsSync(questionsPath) && questionsFileApproved(readFileSync(questionsPath, "utf-8")) ) { throw new Error( "reset the Plan Approval [Answer]: to blank before regenerating its fingerprint", ); } const embedded = parseTestingContract(plan); const current = resolveTestingPosture(projectDir); if ( !embedded || embedded.contract_sha256 !== current.contract_sha256 ) { throw new Error( approval.reason || "plan Testing Contract does not match the current effective posture", ); } // Print the two tag lines the Plan Approval section must carry, ready to // copy: the content fingerprint, and the workspace source this plan was // written against. Recording the source here is what makes drift between // planning and approval answerable - re-run this command and re-present. // The listing behind the source is kept so a later drift can be told to // the human as the files that changed. const plannedState = workspaceSourceState(projectDir); keepWorkspaceSourceSnapshot(projectDir, plannedState); const plannedSource = plannedState?.fingerprint ?? UNBINDABLE_FINGERPRINT; console.log( `[Approval Fingerprint]: ${ approvalFingerprint( plan, instructions, current.contract_sha256, authority, ) }`, ); console.log(`[Planned Source]: ${plannedSource}`); if (plannedState === null) { // The tag stays machine-readable; the reason rides on stderr so the // conductor can relay which budget or path failed before presenting. console.error( JSON.stringify({ note: `the workspace source cannot be bound${workspaceSourceFailureSuffix()}; ` + "a decision on this plan will be refused until the boundary is repaired. " + PLAN_APPROVAL_REPAIR_SOURCE_BOUNDARY_REMEDY, }), ); } return; } case "verify": { const target = targetFromArgs(argv, "verify"); const result = evaluateCodeGenerationApproval(projectDir, target); console.log(JSON.stringify(result, null, 2)); process.exit(result.ok ? 0 : 2); return; } case "begin": { const target = targetFromArgs(argv, "begin"); const changeNotices = beginCodeGeneration(projectDir, target); console.log( JSON.stringify({ status: "generation", target, ...(changeNotices.length > 0 ? { change_notices: changeNotices } : {}), }), ); return; } case "brief": { // The worker brief, verbatim on stdout: the two marker lines, the plan // BODY, and the byte-exact instructions. Refuses unless approval is // current, so the brief can never precede the authority it carries. const target = targetFromArgs(argv, "brief"); const assembled = workerBrief(projectDir, target); if (assembled.appendixStripped) { console.error( JSON.stringify({ note: "the plan carries a terminal review appendix from an earlier protocol; " + "it is not part of the approved body and was left out of the brief", }), ); } process.stdout.write(assembled.brief); return; } default: throw new Error( `Unknown subcommand: ${subcommand ?? "(none)"}. Valid: resolve, render, fingerprint, verify, begin, brief`, ); } } catch (error) { // The human sentence is the error; the conductor's remedy (which command // reopens approval) rides beside it, never inside it. console.error( JSON.stringify({ error: error instanceof Error ? error.message : String(error), ...(error instanceof PlanApprovalSourceDriftError ? { remedy: error.remedy } : {}), }), ); process.exit(1); } } if (import.meta.main) main(process.argv.slice(2));