{"version":3,"file":"proposal.d.ts","sourceRoot":"","sources":["../src/proposal.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID, timingSafeEqual } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { generateUnifiedPatch } from \"@ch1nyzzz/pi-coding-agent\";\nimport { compileBundle, loadCompiledBundle, materializeBundle } from \"./bundle/compile.ts\";\nimport { assertAssetPath } from \"./bundle/schema.ts\";\nimport {\n\ttype CodeL1Result,\n\ttype CodeValidationExecutor,\n\tcodeApprovalDigest,\n\tresolveEvoSourceRepositoryRoot,\n\trevalidateCodeWorktree,\n\tstageCodeWorktree,\n} from \"./code/worktree.ts\";\nimport { createDefaultEvoAbiRegistry } from \"./components/registry.ts\";\nimport { PREFERENCES_PATH, parsePreferenceMemory } from \"./memory/preferences.ts\";\nimport type { EvoPaths } from \"./paths.ts\";\nimport {\n\tsaveProposalRevisionSnapshot,\n\tvalidateEvaluationArtifact,\n\twriteEvaluationArtifact,\n\twriteRetrospectiveArtifact,\n} from \"./proposal-artifacts.ts\";\nimport { readSessionLog, resolveStoredPayload } from \"./recorder/store.ts\";\nimport { BundleRegistry } from \"./registry/registry.ts\";\nimport { atomicWriteJson, canonicalJson, readJson, sha256, withFileLock } from \"./storage.ts\";\nimport type {\n\tBundlePolicy,\n\tCompiledBundle,\n\tDataApprovalActivation,\n\tEvidenceReference,\n\tProposal,\n\tProposalApproval,\n\tProposalTier,\n\tReplayScenario,\n} from \"./types.ts\";\n\nexport interface DraftChange {\n\tpath: string;\n\tcontent: string | null;\n}\n\nexport interface DraftProposal {\n\tmotivation: string;\n\texpectedEffect: string;\n\trisk: string;\n\tverifyPlan: string;\n\ttrialPlan: string;\n\tsource: \"pattern\" | \"explicit-request\";\n\tevidence: EvidenceReference[];\n\tinboxReferences: string[];\n\treplayScenarios: ReplayScenario[];\n\ttargetAbi?: string;\n\trequiresNewAbi?: boolean;\n\tsuggestedTier?: ProposalTier;\n\tchanges?: DraftChange[];\n\tcodePatch?: string;\n}\n\nexport interface ReflectorOutput {\n\tobservationsMarkdown: string;\n\tobservationEvidence: EvidenceReference[];\n\tproposals: DraftProposal[];\n}\n\nfunction assertCodeDraftReplayScenario(draft: DraftProposal): void {\n\tif (\n\t\t(draft.codePatch !== undefined || draft.changes === undefined) &&\n\t\t!draft.requiresNewAbi &&\n\t\t!draft.replayScenarios[0]\n\t) {\n\t\tthrow new Error(\"Code proposals require at least one replay scenario\");\n\t}\n}\n\nfunction assertDraftAbi(draft: DraftProposal): void {\n\tif (draft.requiresNewAbi) {\n\t\tif (draft.codePatch === undefined && draft.changes !== undefined) {\n\t\t\tthrow new Error(\"A new ABI requires an infrastructure code proposal\");\n\t\t}\n\t\treturn;\n\t}\n\tif (draft.targetAbi) createDefaultEvoAbiRegistry().require(draft.targetAbi);\n}\n\nfunction asRecord(value: unknown, label: string): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value))\n\t\tthrow new Error(`${label} must be an object`);\n\treturn value as Record<string, unknown>;\n}\n\nfunction requiredString(record: Record<string, unknown>, key: string, label: string): string {\n\tconst value = record[key];\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label}.${key} must be a string`);\n\treturn value;\n}\n\nfunction parseEvidence(value: unknown, label: string): EvidenceReference[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\treturn value.map((entry, index) => {\n\t\tconst record = asRecord(entry, `${label}[${index}]`);\n\t\tif (typeof record.sessionId !== \"string\" || !Number.isSafeInteger(record.sequence)) {\n\t\t\tthrow new Error(`${label}[${index}] must contain sessionId and sequence`);\n\t\t}\n\t\tif (record.quote !== undefined && typeof record.quote !== \"string\")\n\t\t\tthrow new Error(`${label}[${index}].quote must be a string`);\n\t\treturn {\n\t\t\tsessionId: record.sessionId,\n\t\t\tsequence: record.sequence as number,\n\t\t\tquote: record.quote as string | undefined,\n\t\t};\n\t});\n}\n\nfunction parseReplays(value: unknown, label: string): ReplayScenario[] {\n\tif (value === undefined) return [];\n\treturn parseEvidence(value, label).map(({ sessionId, sequence }) => ({ sessionId, sequence }));\n}\n\nfunction parseStringArray(value: unknown, label: string): string[] {\n\tif (value === undefined) return [];\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\"))\n\t\tthrow new Error(`${label} must be a string array`);\n\treturn [...(value as string[])];\n}\n\nfunction parseChanges(value: unknown, label: string): DraftChange[] | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\treturn value.map((entry, index) => {\n\t\tconst record = asRecord(entry, `${label}[${index}]`);\n\t\tif (typeof record.path !== \"string\" || (record.content !== null && typeof record.content !== \"string\")) {\n\t\t\tthrow new Error(`${label}[${index}] must contain path and string|null content`);\n\t\t}\n\t\treturn { path: record.path, content: record.content } as DraftChange;\n\t});\n}\n\nfunction extractJsonObject(text: string): unknown {\n\tconst trimmed = text\n\t\t.trim()\n\t\t.replace(/^```(?:json)?\\s*/i, \"\")\n\t\t.replace(/\\s*```$/, \"\");\n\tconst start = trimmed.indexOf(\"{\");\n\tconst end = trimmed.lastIndexOf(\"}\");\n\tif (start === -1 || end < start) throw new Error(\"Reflector did not return a JSON object\");\n\treturn JSON.parse(trimmed.slice(start, end + 1));\n}\n\nexport function parseReflectorOutput(text: string): ReflectorOutput {\n\treturn parseReflectorOutputValue(extractJsonObject(text));\n}\n\nexport function parseReflectorOutputValue(value: unknown): ReflectorOutput {\n\tconst root = asRecord(value, \"reflector output\");\n\tconst observationsMarkdown = requiredString(root, \"observationsMarkdown\", \"reflector output\");\n\tconst observationEvidence = parseEvidence(root.observationEvidence ?? [], \"reflector output.observationEvidence\");\n\tif (!Array.isArray(root.proposals)) throw new Error(\"reflector output.proposals must be an array\");\n\tconst proposals = root.proposals.map((entry, index) => {\n\t\tconst record = asRecord(entry, `proposals[${index}]`);\n\t\tconst source = record.source;\n\t\tif (source !== \"pattern\" && source !== \"explicit-request\") {\n\t\t\tthrow new Error(`proposals[${index}].source must be pattern or explicit-request`);\n\t\t}\n\t\tconst codePatch = record.codePatch;\n\t\tif (codePatch !== undefined && typeof codePatch !== \"string\") {\n\t\t\tthrow new Error(`proposals[${index}].codePatch must be a string`);\n\t\t}\n\t\tconst targetAbi = record.targetAbi;\n\t\tif (targetAbi !== undefined && typeof targetAbi !== \"string\") {\n\t\t\tthrow new Error(`proposals[${index}].targetAbi must be a string`);\n\t\t}\n\t\tconst requiresNewAbi = record.requiresNewAbi;\n\t\tif (requiresNewAbi !== undefined && typeof requiresNewAbi !== \"boolean\") {\n\t\t\tthrow new Error(`proposals[${index}].requiresNewAbi must be boolean`);\n\t\t}\n\t\tconst suggestedTier = record.suggestedTier;\n\t\tif (suggestedTier !== undefined && suggestedTier !== \"T0\" && suggestedTier !== \"T1\" && suggestedTier !== \"T2\") {\n\t\t\tthrow new Error(`proposals[${index}].suggestedTier must be T0, T1, or T2`);\n\t\t}\n\t\treturn {\n\t\t\tmotivation: requiredString(record, \"motivation\", `proposals[${index}]`),\n\t\t\texpectedEffect: requiredString(record, \"expectedEffect\", `proposals[${index}]`),\n\t\t\trisk: requiredString(record, \"risk\", `proposals[${index}]`),\n\t\t\tverifyPlan: requiredString(record, \"verifyPlan\", `proposals[${index}]`),\n\t\t\ttrialPlan: requiredString(record, \"trialPlan\", `proposals[${index}]`),\n\t\t\tsource,\n\t\t\tevidence: parseEvidence(record.evidence, `proposals[${index}].evidence`),\n\t\t\tinboxReferences: parseStringArray(record.inboxReferences, `proposals[${index}].inboxReferences`),\n\t\t\treplayScenarios: parseReplays(record.replayScenarios, `proposals[${index}].replayScenarios`),\n\t\t\ttargetAbi: targetAbi as string | undefined,\n\t\t\trequiresNewAbi: requiresNewAbi as boolean | undefined,\n\t\t\tsuggestedTier: suggestedTier as ProposalTier | undefined,\n\t\t\tchanges: parseChanges(record.changes, `proposals[${index}].changes`),\n\t\t\tcodePatch,\n\t\t} satisfies DraftProposal;\n\t});\n\treturn { observationsMarkdown, observationEvidence, proposals };\n}\n\nfunction formatProposalId(): string {\n\treturn `p-${new Date().toISOString().replace(/[:.]/g, \"-\")}-${randomUUID().slice(0, 8)}`;\n}\n\nfunction normalizeText(content: string): string {\n\treturn content\n\t\t.replace(/\\r\\n?/g, \"\\n\")\n\t\t.split(\"\\n\")\n\t\t.map((line) => line.trimEnd())\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\nfunction stricterTier(determined: ProposalTier, suggested: ProposalTier | undefined): ProposalTier {\n\tif (!suggested) return determined;\n\tconst rank: Record<ProposalTier, number> = { T0: 0, T1: 1, T2: 2 };\n\treturn rank[suggested] > rank[determined] ? suggested : determined;\n}\n\nfunction withoutLayoutPolicy(policy: BundlePolicy): Record<string, unknown> {\n\tconst record = { ...policy } as Record<string, unknown>;\n\tdelete record.promptOrder;\n\tdelete record.stablePromptPaths;\n\tdelete record.dynamicPromptPaths;\n\treturn record;\n}\n\nasync function isReorderOnly(\n\tparentDirectory: string,\n\tcandidateDirectory: string,\n\tparentPolicy: BundlePolicy,\n\tcandidatePolicy: BundlePolicy,\n\tparentPaths: string[],\n\tcandidatePaths: string[],\n): Promise<boolean> {\n\tif (canonicalJson(withoutLayoutPolicy(parentPolicy)) !== canonicalJson(withoutLayoutPolicy(candidatePolicy)))\n\t\treturn false;\n\tconst parentAssets = parentPaths.filter((path) => path !== \"policy.json\");\n\tconst candidateAssets = candidatePaths.filter((path) => path !== \"policy.json\");\n\tif ([...parentAssets, ...candidateAssets].some((path) => !path.startsWith(\"prompts/\"))) return false;\n\tconst contentDigests = async (directory: string, paths: string[]) =>\n\t\tPromise.all(paths.map(async (path) => sha256(normalizeText(await readFile(join(directory, path), \"utf8\")))));\n\tconst parentDigests = (await contentDigests(parentDirectory, parentAssets)).sort();\n\tconst candidateDigests = (await contentDigests(candidateDirectory, candidateAssets)).sort();\n\treturn parentDigests.join(\"\\n\") === candidateDigests.join(\"\\n\");\n}\n\nfunction isCoreChange(parentPolicy: BundlePolicy, candidatePolicy: BundlePolicy, changedPaths: string[]): boolean {\n\tconst coreAssets = new Set([...(parentPolicy.coreAssets ?? []), ...(candidatePolicy.coreAssets ?? [])]);\n\tif (changedPaths.some((path) => coreAssets.has(path))) return true;\n\tconst parentCorePolicy = {\n\t\tcoreAssets: parentPolicy.coreAssets ?? [],\n\t\tenabledFeatures: parentPolicy.enabledFeatures ?? [],\n\t\tenabledTools: parentPolicy.enabledTools ?? [],\n\t\tlimits: parentPolicy.limits ?? {},\n\t\tmanagedSources: parentPolicy.managedSources ?? [],\n\t\tcomponents: parentPolicy.components ?? {},\n\t\tmodelRouting: parentPolicy.modelRouting ?? {},\n\t\tvalidation: parentPolicy.validation ?? {},\n\t};\n\tconst candidateCorePolicy = {\n\t\tcoreAssets: candidatePolicy.coreAssets ?? [],\n\t\tenabledFeatures: candidatePolicy.enabledFeatures ?? [],\n\t\tenabledTools: candidatePolicy.enabledTools ?? [],\n\t\tlimits: candidatePolicy.limits ?? {},\n\t\tmanagedSources: candidatePolicy.managedSources ?? [],\n\t\tcomponents: candidatePolicy.components ?? {},\n\t\tmodelRouting: candidatePolicy.modelRouting ?? {},\n\t\tvalidation: candidatePolicy.validation ?? {},\n\t};\n\treturn canonicalJson(parentCorePolicy) !== canonicalJson(candidateCorePolicy);\n}\n\nfunction withoutComponentSurface(policy: BundlePolicy, surface: string): BundlePolicy {\n\tconst components = { ...(policy.components ?? {}) };\n\tdelete components[surface];\n\tconst stripped = { ...policy };\n\tif (Object.keys(components).length === 0) delete stripped.components;\n\telse stripped.components = components;\n\treturn stripped;\n}\n\nfunction isRegisteredComponentSelectionChange(options: {\n\tparentPolicy: BundlePolicy;\n\tcandidatePolicy: BundlePolicy;\n\tchangedPaths: string[];\n\ttargetAbi?: string;\n\trequiresNewAbi?: boolean;\n}): boolean {\n\tif (\n\t\toptions.requiresNewAbi !== false ||\n\t\toptions.targetAbi === undefined ||\n\t\toptions.changedPaths.length !== 1 ||\n\t\toptions.changedPaths[0] !== \"policy.json\"\n\t) {\n\t\treturn false;\n\t}\n\tconst abi = createDefaultEvoAbiRegistry().get(options.targetAbi);\n\tif (!abi) return false;\n\tconst before = options.parentPolicy.components?.[abi.surface];\n\tconst after = options.candidatePolicy.components?.[abi.surface];\n\tif (!after || after.abi !== abi.id || canonicalJson(before ?? null) === canonicalJson(after)) return false;\n\treturn (\n\t\tcanonicalJson(withoutComponentSurface(options.parentPolicy, abi.surface)) ===\n\t\tcanonicalJson(withoutComponentSurface(options.candidatePolicy, abi.surface))\n\t);\n}\n\nfunction containsText(value: unknown, quote: string): boolean {\n\tif (typeof value === \"string\") return value.includes(quote);\n\tif (Array.isArray(value)) return value.some((entry) => containsText(entry, quote));\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn Object.values(value as Record<string, unknown>).some((entry) => containsText(entry, quote));\n}\n\nasync function evidenceContainsPreferenceSource(\n\tpaths: EvoPaths,\n\tevidence: EvidenceReference[],\n\tsource: { sessionId: string; sequence: number; quote: string },\n): Promise<boolean> {\n\tconst reference = evidence.find(\n\t\t(entry) =>\n\t\t\tentry.sessionId === source.sessionId &&\n\t\t\tentry.sequence === source.sequence &&\n\t\t\tentry.quote !== undefined &&\n\t\t\tsource.quote.includes(entry.quote),\n\t);\n\tif (!reference?.quote) return false;\n\tconst event = (await readSessionLog(paths, source.sessionId)).find((entry) => entry.sequence === source.sequence);\n\tif (!event) return false;\n\tif (event.type === \"explicit_feedback\") {\n\t\treturn containsText(await resolveStoredPayload(paths, event.text), source.quote);\n\t}\n\tif (event.type === \"message\") {\n\t\treturn containsText(await resolveStoredPayload(paths, event.message), source.quote);\n\t}\n\tif (event.type === \"before_agent_start\") {\n\t\treturn containsText(await resolveStoredPayload(paths, event.prompt), source.quote);\n\t}\n\treturn false;\n}\n\nasync function isDirectPreference(\n\tpaths: EvoPaths,\n\tevidence: EvidenceReference[],\n\tchangedPaths: string[],\n\tbefore: Map<string, string>,\n\tafter: Map<string, string>,\n): Promise<boolean> {\n\tif (changedPaths.length !== 1) return false;\n\tif (changedPaths[0] !== PREFERENCES_PATH) {\n\t\tconst path = changedPaths[0];\n\t\tif (!path?.startsWith(\"memory/\") || before.has(path)) return false;\n\t\tconst content = after.get(path);\n\t\tif (!content || !normalizeText(content)) return false;\n\t\tfor (const reference of evidence) {\n\t\t\tif (reference.quote === undefined || normalizeText(reference.quote) !== normalizeText(content)) continue;\n\t\t\tconst event = (await readSessionLog(paths, reference.sessionId)).find(\n\t\t\t\t(candidate) => candidate.sequence === reference.sequence,\n\t\t\t);\n\t\t\tif (event?.type !== \"explicit_feedback\") continue;\n\t\t\tconst text = await resolveStoredPayload(paths, event.text);\n\t\t\tif (typeof text === \"string\" && normalizeText(text) === normalizeText(reference.quote)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\tconst afterContent = after.get(PREFERENCES_PATH);\n\tif (!afterContent) return false;\n\tconst previous = before.has(PREFERENCES_PATH)\n\t\t? parsePreferenceMemory(JSON.parse(before.get(PREFERENCES_PATH) ?? \"\"))\n\t\t: { schemaVersion: 1 as const, preferences: [] };\n\tconst next = parsePreferenceMemory(JSON.parse(afterContent));\n\tif (next.preferences.length <= previous.preferences.length) return false;\n\tfor (const [index, preference] of previous.preferences.entries()) {\n\t\tif (canonicalJson(preference) !== canonicalJson(next.preferences[index])) return false;\n\t}\n\tfor (const preference of next.preferences.slice(previous.preferences.length)) {\n\t\tif (!(\"sessionId\" in preference.source)) return false;\n\t\tif (!preference.source.quote.includes(preference.instruction)) return false;\n\t\tif (!(await evidenceContainsPreferenceSource(paths, evidence, preference.source))) return false;\n\t}\n\treturn true;\n}\n\nasync function readBundleFileMap(directory: string, paths: string[]): Promise<Map<string, string>> {\n\treturn new Map(\n\t\tawait Promise.all(paths.map(async (path) => [path, await readFile(join(directory, path), \"utf8\")] as const)),\n\t);\n}\n\nfunction renderDiff(before: Map<string, string>, after: Map<string, string>, changedPaths: string[]): string {\n\treturn changedPaths\n\t\t.map((path) => generateUnifiedPatch(path, before.get(path) ?? \"\", after.get(path) ?? \"\"))\n\t\t.join(\"\\n\");\n}\n\nasync function classifyDataTier(options: {\n\tpaths: EvoPaths;\n\tevidence: EvidenceReference[];\n\tparentDirectory: string;\n\tcandidateDirectory: string;\n\tparentPolicy: BundlePolicy;\n\tcandidatePolicy: BundlePolicy;\n\tparentPaths: string[];\n\tcandidatePaths: string[];\n\tchangedPaths: string[];\n\tbefore: Map<string, string>;\n\tafter: Map<string, string>;\n\ttargetAbi?: string;\n\trequiresNewAbi?: boolean;\n}): Promise<{ tier: ProposalTier; reason: string }> {\n\tif (isRegisteredComponentSelectionChange(options)) {\n\t\treturn {\n\t\t\ttier: \"T1\",\n\t\t\treason: \"The diff selects a sandboxed component on an existing registered ABI for Canary review\",\n\t\t};\n\t}\n\tif (isCoreChange(options.parentPolicy, options.candidatePolicy, options.changedPaths)) {\n\t\treturn { tier: \"T2\", reason: \"The diff touches a core asset or core policy field\" };\n\t}\n\tif (\n\t\tawait isReorderOnly(\n\t\t\toptions.parentDirectory,\n\t\t\toptions.candidateDirectory,\n\t\t\toptions.parentPolicy,\n\t\t\toptions.candidatePolicy,\n\t\t\toptions.parentPaths,\n\t\t\toptions.candidatePaths,\n\t\t)\n\t) {\n\t\treturn { tier: \"T0\", reason: \"Normalized prompt contents are unchanged; only layout changed\" };\n\t}\n\tif (await isDirectPreference(options.paths, options.evidence, options.changedPaths, options.before, options.after)) {\n\t\treturn { tier: \"T0\", reason: \"A single memory entry copies an explicitly cited user preference\" };\n\t}\n\treturn { tier: \"T1\", reason: \"The diff is a non-core data change\" };\n}\n\ninterface DataCandidateEvaluation {\n\tkind: \"data\";\n\ttier: ProposalTier;\n\tchangedPaths: string[];\n\tdiff: string;\n\tl1: Proposal[\"l1\"];\n}\n\nasync function evaluateDataCandidate(options: {\n\tpaths: EvoPaths;\n\tparent: CompiledBundle;\n\tcandidate: CompiledBundle;\n\tevidence: EvidenceReference[];\n\tsuggestedTier?: ProposalTier;\n\ttargetAbi?: string;\n\trequiresNewAbi?: boolean;\n}): Promise<DataCandidateEvaluation> {\n\tif (options.candidate.manifest.parentDigest !== options.parent.digest) {\n\t\tthrow new Error(\"Candidate bundle parent does not match the proposal parent\");\n\t}\n\tconst parentPaths = options.parent.manifest.files.map((file) => file.path);\n\tconst candidatePaths = options.candidate.manifest.files.map((file) => file.path);\n\tconst before = await readBundleFileMap(options.parent.directory, parentPaths);\n\tconst after = await readBundleFileMap(options.candidate.directory, candidatePaths);\n\tconst changedPaths = [...new Set([...parentPaths, ...candidatePaths])]\n\t\t.filter((path) => before.get(path) !== after.get(path))\n\t\t.sort();\n\tif (changedPaths.length === 0) throw new Error(\"Proposal does not change the bundle\");\n\tconst classification = await classifyDataTier({\n\t\tpaths: options.paths,\n\t\tevidence: options.evidence,\n\t\tparentDirectory: options.parent.directory,\n\t\tcandidateDirectory: options.candidate.directory,\n\t\tparentPolicy: options.parent.policy,\n\t\tcandidatePolicy: options.candidate.policy,\n\t\tparentPaths,\n\t\tcandidatePaths,\n\t\tchangedPaths,\n\t\tbefore,\n\t\tafter,\n\t\ttargetAbi: options.targetAbi,\n\t\trequiresNewAbi: options.requiresNewAbi,\n\t});\n\tconst tier = stricterTier(classification.tier, options.suggestedTier);\n\treturn {\n\t\tkind: \"data\",\n\t\ttier,\n\t\tchangedPaths,\n\t\tdiff: renderDiff(before, after, changedPaths),\n\t\tl1: {\n\t\t\tpassed: true,\n\t\t\treason:\n\t\t\t\ttier === classification.tier ? classification.reason : `${classification.reason}; model requested ${tier}`,\n\t\t\terrors: [],\n\t\t},\n\t};\n}\n\nasync function applyDraftChanges(candidateDirectory: string, changes: DraftChange[]): Promise<void> {\n\tconst seen = new Set<string>();\n\tfor (const change of changes) {\n\t\tassertAssetPath(change.path);\n\t\tif (change.path === \"bundle.json\") throw new Error(\"Reflector cannot edit bundle.json\");\n\t\tif (seen.has(change.path)) throw new Error(`Proposal changes ${change.path} more than once`);\n\t\tseen.add(change.path);\n\t\tconst target = join(candidateDirectory, change.path);\n\t\tif (change.content === null) {\n\t\t\tif (change.path === \"policy.json\") throw new Error(\"Proposal cannot delete policy.json\");\n\t\t\tawait unlink(target);\n\t\t\tcontinue;\n\t\t}\n\t\tawait mkdir(dirname(target), { recursive: true });\n\t\tawait writeFile(target, change.content, \"utf8\");\n\t}\n}\n\nfunction renderCodeValidation(result: CodeL1Result): string {\n\tconst checks = result.checks\n\t\t.map(({ name, result: command }) => {\n\t\t\tconst outcome = command.code === 0 && !command.killed && !command.outputLimitExceeded ? \"passed\" : \"failed\";\n\t\t\treturn `- ${name}: ${outcome} (exit ${command.code ?? \"signal\"})`;\n\t\t})\n\t\t.join(\"\\n\");\n\treturn [\n\t\t\"# Sandboxed L1 validation\",\n\t\t\"\",\n\t\t`Result: ${result.passed ? \"passed\" : \"failed\"}`,\n\t\t...(checks ? [\"\", checks] : []),\n\t\t...(result.errors.length > 0 ? [\"\", \"## Errors\", \"\", ...result.errors.map((error) => `- ${error}`)] : []),\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nexport async function stageProposal(options: {\n\tpaths: EvoPaths;\n\tparentDigest: string;\n\tdraft: DraftProposal;\n\tobservationsMarkdown: string;\n\trepositoryCwd?: string;\n\texpectedCodeBase?: {\n\t\trepositoryRoot: string;\n\t\trepositoryIdentity: string;\n\t\tbaseCommit: string;\n\t};\n\tcodeValidationExecutor?: CodeValidationExecutor;\n\tsignal?: AbortSignal;\n}): Promise<Proposal> {\n\tassertCodeDraftReplayScenario(options.draft);\n\tassertDraftAbi(options.draft);\n\tconst id = formatProposalId();\n\tconst temporaryDirectory = join(options.paths.proposals, `.tmp-${id}`);\n\tconst proposalDirectory = join(options.paths.proposals, id);\n\tawait mkdir(temporaryDirectory, { recursive: true });\n\ttry {\n\t\tif (options.draft.codePatch !== undefined || options.draft.changes === undefined) {\n\t\t\tconst codePatch = options.draft.codePatch ?? \"\";\n\t\t\tawait rm(temporaryDirectory, { recursive: true, force: true });\n\t\t\tconst staged = await stageCodeWorktree({\n\t\t\t\tpaths: options.paths,\n\t\t\t\trepositoryCwd: options.repositoryCwd ?? (await resolveEvoSourceRepositoryRoot()),\n\t\t\t\tproposalId: id,\n\t\t\t\trevision: 1,\n\t\t\t\tparentBundleDigest: options.parentDigest,\n\t\t\t\tpatch: codePatch,\n\t\t\t\t...(options.expectedCodeBase\n\t\t\t\t\t? {\n\t\t\t\t\t\t\texpectedRepositoryRoot: options.expectedCodeBase.repositoryRoot,\n\t\t\t\t\t\t\texpectedRepositoryIdentity: options.expectedCodeBase.repositoryIdentity,\n\t\t\t\t\t\t\texpectedBaseCommit: options.expectedCodeBase.baseCommit,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\t...(options.codeValidationExecutor ? { validationExecutor: options.codeValidationExecutor } : {}),\n\t\t\t\t...(options.signal ? { signal: options.signal } : {}),\n\t\t\t});\n\t\t\tconst diffDigest = staged.diffDigest;\n\t\t\tconst proposal: Proposal = {\n\t\t\t\tschemaVersion: 2,\n\t\t\t\tid,\n\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\trevision: 1,\n\t\t\t\tparentBundleDigest: options.parentDigest,\n\t\t\t\tkind: \"code\",\n\t\t\t\ttier: \"T2\",\n\t\t\t\tmotivation: options.draft.motivation,\n\t\t\t\tdiff: staged.diff,\n\t\t\t\texpectedEffect: options.draft.expectedEffect,\n\t\t\t\trisk: options.draft.risk,\n\t\t\t\tverifyPlan: options.draft.verifyPlan,\n\t\t\t\ttrialPlan: options.draft.trialPlan,\n\t\t\t\tstatus: \"pending\",\n\t\t\t\tsource: options.draft.source,\n\t\t\t\tevidence: options.draft.evidence,\n\t\t\t\tinboxReferences: options.draft.inboxReferences,\n\t\t\t\treplayScenarios: options.draft.replayScenarios,\n\t\t\t\t...(options.draft.targetAbi ? { targetAbi: options.draft.targetAbi } : {}),\n\t\t\t\t...(options.draft.requiresNewAbi !== undefined ? { requiresNewAbi: options.draft.requiresNewAbi } : {}),\n\t\t\t\tchangedPaths: staged.changedPaths,\n\t\t\t\tdiffDigest,\n\t\t\t\tapprovalDigest: staged.approvalDigest,\n\t\t\t\tcodePatch,\n\t\t\t\tcodeWorkspace: {\n\t\t\t\t\trepositoryRoot: staged.repositoryRoot,\n\t\t\t\t\trepositoryId: staged.repositoryIdentity,\n\t\t\t\t\tworktreePath: staged.worktreePath,\n\t\t\t\t\tbranch: staged.branch,\n\t\t\t\t\tbaseCommit: staged.baseCommit,\n\t\t\t\t\tintegrityDigest: staged.approvalDigest,\n\t\t\t\t},\n\t\t\t\tsuggestedTier: options.draft.suggestedTier,\n\t\t\t\tl1: {\n\t\t\t\t\tpassed: staged.l1.passed,\n\t\t\t\t\treason: staged.l1.passed ? \"Sandboxed code validation passed\" : \"Sandboxed code validation failed\",\n\t\t\t\t\terrors: staged.l1.errors,\n\t\t\t\t},\n\t\t\t\tartifacts: {},\n\t\t\t};\n\t\t\tawait writeFile(join(proposalDirectory, \"observations.md\"), options.observationsMarkdown, \"utf8\");\n\t\t\tproposal.artifacts.validation = await writeEvaluationArtifact({\n\t\t\t\tpaths: options.paths,\n\t\t\t\tproposalId: id,\n\t\t\t\trevision: proposal.revision,\n\t\t\t\tdiffDigest,\n\t\t\t\tkind: \"validation\",\n\t\t\t\tcontent: renderCodeValidation(staged.l1),\n\t\t\t});\n\t\t\tawait saveProposal(options.paths, proposal);\n\t\t\tawait saveProposalRevisionSnapshot(options.paths, proposal);\n\t\t\treturn proposal;\n\t\t}\n\n\t\toptions.signal?.throwIfAborted();\n\t\tconst parent = await loadCompiledBundle(options.paths, options.parentDigest);\n\t\tconst candidateDirectory = join(temporaryDirectory, \"candidate\");\n\t\tawait materializeBundle(options.paths, options.parentDigest, candidateDirectory);\n\t\tawait applyDraftChanges(candidateDirectory, options.draft.changes);\n\t\tconst candidate = await compileBundle({\n\t\t\tpaths: options.paths,\n\t\t\tsourceDirectory: candidateDirectory,\n\t\t\tparentDigest: options.parentDigest,\n\t\t\tsummary: options.draft.expectedEffect.slice(0, 240),\n\t\t});\n\t\tconst evaluation = await evaluateDataCandidate({\n\t\t\tpaths: options.paths,\n\t\t\tparent,\n\t\t\tcandidate,\n\t\t\tevidence: options.draft.evidence,\n\t\t\tsuggestedTier: options.draft.suggestedTier,\n\t\t\ttargetAbi: options.draft.targetAbi,\n\t\t\trequiresNewAbi: options.draft.requiresNewAbi,\n\t\t});\n\t\tconst diffDigest = sha256(evaluation.diff);\n\t\tconst proposal: Proposal = {\n\t\t\tschemaVersion: 2,\n\t\t\tid,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\trevision: 1,\n\t\t\tparentBundleDigest: options.parentDigest,\n\t\t\tkind: evaluation.kind,\n\t\t\ttier: evaluation.tier,\n\t\t\tmotivation: options.draft.motivation,\n\t\t\tdiff: evaluation.diff,\n\t\t\texpectedEffect: options.draft.expectedEffect,\n\t\t\trisk: options.draft.risk,\n\t\t\tverifyPlan: options.draft.verifyPlan,\n\t\t\ttrialPlan: options.draft.trialPlan,\n\t\t\tstatus: \"pending\",\n\t\t\tsource: options.draft.source,\n\t\t\tevidence: options.draft.evidence,\n\t\t\tinboxReferences: options.draft.inboxReferences,\n\t\t\treplayScenarios: options.draft.replayScenarios,\n\t\t\t...(options.draft.targetAbi ? { targetAbi: options.draft.targetAbi } : {}),\n\t\t\t...(options.draft.requiresNewAbi !== undefined ? { requiresNewAbi: options.draft.requiresNewAbi } : {}),\n\t\t\tchangedPaths: evaluation.changedPaths,\n\t\t\tdiffDigest,\n\t\t\tapprovalDigest: diffDigest,\n\t\t\tcandidateDigest: candidate.digest,\n\t\t\tsuggestedTier: options.draft.suggestedTier,\n\t\t\tl1: evaluation.l1,\n\t\t\tartifacts: {},\n\t\t};\n\t\tawait rm(candidateDirectory, { recursive: true, force: true });\n\t\tawait writeFile(join(temporaryDirectory, \"observations.md\"), options.observationsMarkdown, \"utf8\");\n\t\tawait atomicWriteJson(join(temporaryDirectory, \"proposal.json\"), proposal);\n\t\tawait rename(temporaryDirectory, proposalDirectory);\n\t\tawait saveProposalRevisionSnapshot(options.paths, proposal);\n\t\treturn proposal;\n\t} catch (error) {\n\t\tawait rm(temporaryDirectory, { recursive: true, force: true }).catch(() => {});\n\t\tthrow error;\n\t}\n}\n\nfunction assertProposalId(id: string): void {\n\tif (!/^p-[A-Za-z0-9._-]+$/.test(id)) throw new Error(`Invalid proposal id: ${id}`);\n}\n\nexport async function loadProposal(paths: EvoPaths, id: string): Promise<Proposal> {\n\tassertProposalId(id);\n\treturn readJson<Proposal>(join(paths.proposals, id, \"proposal.json\"));\n}\n\nexport async function saveProposal(paths: EvoPaths, proposal: Proposal): Promise<void> {\n\tassertProposalId(proposal.id);\n\tawait atomicWriteJson(join(paths.proposals, proposal.id, \"proposal.json\"), proposal);\n}\n\nfunction withProposalLock<T>(paths: EvoPaths, id: string, operation: () => Promise<T>): Promise<T> {\n\tassertProposalId(id);\n\treturn withFileLock(paths, `proposal-${id}`, operation, { staleAfterMs: 4 * 60 * 60 * 1000 });\n}\n\nexport async function attachProposalArtifact(options: {\n\tpaths: EvoPaths;\n\tproposalId: string;\n\texpected: ProposalApproval;\n\tkind: \"review\" | \"replay\" | \"validation\";\n\tcontent: string;\n\tallowedStatuses: readonly Proposal[\"status\"][];\n}): Promise<Proposal> {\n\treturn withProposalLock(options.paths, options.proposalId, async () => {\n\t\tconst proposal = await loadProposal(options.paths, options.proposalId);\n\t\tassertProposalApproval(proposal, options.expected);\n\t\tif (!options.allowedStatuses.includes(proposal.status)) {\n\t\t\tthrow new Error(`Proposal ${proposal.id} is ${proposal.status}; cannot attach ${options.kind}`);\n\t\t}\n\t\tconst existing = proposal.artifacts[options.kind];\n\t\tif (existing) {\n\t\t\tawait validateEvaluationArtifact({\n\t\t\t\tpaths: options.paths,\n\t\t\t\tproposalId: proposal.id,\n\t\t\t\trevision: proposal.revision,\n\t\t\t\tdiffDigest: proposal.diffDigest,\n\t\t\t\tkind: options.kind,\n\t\t\t\treference: existing,\n\t\t\t});\n\t\t\tif (existing.sha256 !== sha256(options.content)) {\n\t\t\t\tthrow new Error(`Proposal ${options.kind} artifact is already fixed for revision ${proposal.revision}`);\n\t\t\t}\n\t\t\treturn proposal;\n\t\t}\n\t\tproposal.artifacts[options.kind] = await writeEvaluationArtifact({\n\t\t\tpaths: options.paths,\n\t\t\tproposalId: proposal.id,\n\t\t\trevision: proposal.revision,\n\t\t\tdiffDigest: proposal.diffDigest,\n\t\t\tkind: options.kind,\n\t\t\tcontent: options.content,\n\t\t});\n\t\tawait saveProposal(options.paths, proposal);\n\t\tawait saveProposalRevisionSnapshot(options.paths, proposal);\n\t\treturn proposal;\n\t});\n}\n\nexport async function attachRetrospectiveArtifact(options: {\n\tpaths: EvoPaths;\n\tproposalId: string;\n\texpected: ProposalApproval;\n\tcontent: string;\n\tevidenceDigest: string;\n\tevidenceCutoff: string;\n}): Promise<Proposal> {\n\treturn withProposalLock(options.paths, options.proposalId, async () => {\n\t\tconst proposal = await loadProposal(options.paths, options.proposalId);\n\t\tassertProposalApproval(proposal, options.expected);\n\t\tif (proposal.status !== \"trialing\") {\n\t\t\tthrow new Error(`Proposal ${proposal.id} is ${proposal.status}; cannot attach retrospective`);\n\t\t}\n\t\tconst existing = proposal.artifacts.retrospective;\n\t\tif (existing) {\n\t\t\tawait validateEvaluationArtifact({\n\t\t\t\tpaths: options.paths,\n\t\t\t\tproposalId: proposal.id,\n\t\t\t\trevision: proposal.revision,\n\t\t\t\tdiffDigest: proposal.diffDigest,\n\t\t\t\tkind: \"retrospective\",\n\t\t\t\treference: existing,\n\t\t\t});\n\t\t\tif (existing.evidence?.digest === options.evidenceDigest) {\n\t\t\t\tif (existing.sha256 !== sha256(options.content)) {\n\t\t\t\t\tthrow new Error(`Proposal retrospective is already fixed for evidence ${options.evidenceDigest}`);\n\t\t\t\t}\n\t\t\t\treturn proposal;\n\t\t\t}\n\t\t}\n\t\tproposal.artifacts.retrospective = await writeRetrospectiveArtifact({\n\t\t\tpaths: options.paths,\n\t\t\tproposalId: proposal.id,\n\t\t\trevision: proposal.revision,\n\t\t\tdiffDigest: proposal.diffDigest,\n\t\t\tcontent: options.content,\n\t\t\tevidenceDigest: options.evidenceDigest,\n\t\t\tevidenceCutoff: options.evidenceCutoff,\n\t\t});\n\t\tawait saveProposal(options.paths, proposal);\n\t\tawait saveProposalRevisionSnapshot(options.paths, proposal);\n\t\treturn proposal;\n\t});\n}\n\nasync function assertApprovalArtifact(paths: EvoPaths, proposal: Proposal, kind: \"review\" | \"replay\" | \"validation\") {\n\tconst reference = proposal.artifacts[kind];\n\tif (!reference) throw new Error(`Proposal ${proposal.id} is missing required ${kind} artifact`);\n\tawait validateEvaluationArtifact({\n\t\tpaths,\n\t\tproposalId: proposal.id,\n\t\trevision: proposal.revision,\n\t\tdiffDigest: proposal.diffDigest,\n\t\tkind,\n\t\treference,\n\t});\n}\n\nfunction matchesApprovalDigest(expected: string, current: string): boolean {\n\tif (!/^[a-f0-9]{64}$/.test(expected) || !/^[a-f0-9]{64}$/.test(current)) return false;\n\treturn timingSafeEqual(Buffer.from(expected, \"hex\"), Buffer.from(current, \"hex\"));\n}\n\nfunction approvalArtifactsDigest(proposal: Proposal): string {\n\treturn sha256(\n\t\tcanonicalJson({\n\t\t\tschemaVersion: 1,\n\t\t\treview: proposal.artifacts.review ?? null,\n\t\t\treplay: proposal.artifacts.replay ?? null,\n\t\t\tvalidation: proposal.artifacts.validation ?? null,\n\t\t}),\n\t);\n}\n\nasync function revalidateDataProposal(\n\tpaths: EvoPaths,\n\tproposal: Proposal,\n): Promise<{ candidate: CompiledBundle; evaluation: DataCandidateEvaluation }> {\n\tif (!proposal.candidateDigest) throw new Error(\"Data proposal has no candidate bundle\");\n\tif (\n\t\t!matchesApprovalDigest(proposal.approvalDigest, proposal.diffDigest) ||\n\t\t!matchesApprovalDigest(proposal.diffDigest, sha256(proposal.diff))\n\t) {\n\t\tthrow new Error(\"Proposal approval digest does not match its displayed diff\");\n\t}\n\tconst [parent, candidate] = await Promise.all([\n\t\tloadCompiledBundle(paths, proposal.parentBundleDigest),\n\t\tloadCompiledBundle(paths, proposal.candidateDigest),\n\t]);\n\tconst evaluation = await evaluateDataCandidate({\n\t\tpaths,\n\t\tparent,\n\t\tcandidate,\n\t\tevidence: proposal.evidence,\n\t\tsuggestedTier: proposal.suggestedTier,\n\t\ttargetAbi: proposal.targetAbi,\n\t\trequiresNewAbi: proposal.requiresNewAbi,\n\t});\n\tconst mismatches: string[] = [];\n\tif (proposal.kind !== evaluation.kind) mismatches.push(\"kind\");\n\tif (proposal.tier !== evaluation.tier) mismatches.push(\"tier\");\n\tif (canonicalJson(proposal.changedPaths) !== canonicalJson(evaluation.changedPaths)) mismatches.push(\"changedPaths\");\n\tif (proposal.diff !== evaluation.diff) mismatches.push(\"diff\");\n\tif (proposal.diffDigest !== sha256(evaluation.diff)) mismatches.push(\"diffDigest\");\n\tif (canonicalJson(proposal.l1) !== canonicalJson(evaluation.l1)) mismatches.push(\"l1\");\n\tif (mismatches.length > 0) {\n\t\tthrow new Error(`Proposal ${proposal.id} does not match immutable bundle audit: ${mismatches.join(\", \")}`);\n\t}\n\treturn { candidate, evaluation };\n}\n\nexport function assertProposalApproval(proposal: Proposal, expected: ProposalApproval): void {\n\tif (proposal.revision !== expected.revision) {\n\t\tthrow new Error(`Proposal ${proposal.id} revision changed from ${expected.revision} to ${proposal.revision}`);\n\t}\n\tif (\n\t\t!matchesApprovalDigest(expected.diffDigest, proposal.diffDigest) ||\n\t\t!matchesApprovalDigest(proposal.diffDigest, sha256(proposal.diff))\n\t) {\n\t\tthrow new Error(`Proposal ${proposal.id} diff digest does not match the confirmed final diff`);\n\t}\n\tif (\n\t\texpected.artifactsDigest !== undefined &&\n\t\t!matchesApprovalDigest(expected.artifactsDigest, approvalArtifactsDigest(proposal))\n\t) {\n\t\tthrow new Error(`Proposal ${proposal.id} approval artifacts changed after confirmation`);\n\t}\n\tif (\n\t\t(proposal.kind === \"code\" || proposal.codeWorkspace !== undefined || expected.approvalDigest !== undefined) &&\n\t\t(proposal.kind !== \"code\" || proposal.tier !== \"T2\")\n\t) {\n\t\tthrow new Error(`Proposal ${proposal.id} code approval requires kind code and tier T2`);\n\t}\n\tif (proposal.kind === \"data\") {\n\t\tif (!matchesApprovalDigest(proposal.approvalDigest, proposal.diffDigest)) {\n\t\t\tthrow new Error(`Proposal ${proposal.id} diff digest does not match the confirmed final diff`);\n\t\t}\n\t\treturn;\n\t}\n\tconst workspace = proposal.codeWorkspace;\n\tif (!workspace || !expected.approvalDigest) {\n\t\tthrow new Error(`Proposal ${proposal.id} requires confirmation of its code approval context`);\n\t}\n\tconst computed = codeApprovalDigest({\n\t\trepositoryRoot: workspace.repositoryRoot,\n\t\trepositoryIdentity: workspace.repositoryId,\n\t\tbaseCommit: workspace.baseCommit,\n\t\tparentBundleDigest: proposal.parentBundleDigest,\n\t\tdiff: proposal.diff,\n\t});\n\tif (\n\t\t!matchesApprovalDigest(expected.approvalDigest, proposal.approvalDigest) ||\n\t\t!matchesApprovalDigest(proposal.approvalDigest, workspace.integrityDigest) ||\n\t\t!matchesApprovalDigest(proposal.approvalDigest, computed)\n\t) {\n\t\tthrow new Error(`Proposal ${proposal.id} code approval context changed after confirmation`);\n\t}\n}\n\nexport function proposalApproval(proposal: Proposal): ProposalApproval {\n\treturn {\n\t\trevision: proposal.revision,\n\t\tdiffDigest: proposal.diffDigest,\n\t\t...(proposal.kind === \"code\" ? { approvalDigest: proposal.approvalDigest } : {}),\n\t\tartifactsDigest: approvalArtifactsDigest(proposal),\n\t};\n}\n\nexport async function reviseProposal(options: {\n\tpaths: EvoPaths;\n\tid: string;\n\texpected: ProposalApproval;\n\tdraft: DraftProposal;\n\tobservationsMarkdown: string;\n\trepositoryCwd?: string;\n\tcodeValidationExecutor?: CodeValidationExecutor;\n}): Promise<Proposal> {\n\tassertCodeDraftReplayScenario(options.draft);\n\tassertDraftAbi(options.draft);\n\tconst stableDigest = await new BundleRegistry(options.paths).readStableDigest();\n\treturn withProposalLock(options.paths, options.id, async () => {\n\t\tconst current = await loadProposal(options.paths, options.id);\n\t\tassertProposalApproval(current, options.expected);\n\t\tif (current.status !== \"pending\") throw new Error(`Proposal ${options.id} is ${current.status}`);\n\t\tif (stableDigest !== current.parentBundleDigest) {\n\t\t\tthrow new Error(\"Proposal parent is no longer stable; regenerate or rebase it\");\n\t\t}\n\t\tconst revision = current.revision + 1;\n\t\tlet proposal: Proposal;\n\t\tif (options.draft.codePatch !== undefined || options.draft.changes === undefined) {\n\t\t\tconst codePatch = options.draft.codePatch ?? \"\";\n\t\t\tconst staged = await stageCodeWorktree({\n\t\t\t\tpaths: options.paths,\n\t\t\t\trepositoryCwd:\n\t\t\t\t\toptions.repositoryCwd ??\n\t\t\t\t\tcurrent.codeWorkspace?.repositoryRoot ??\n\t\t\t\t\t(await resolveEvoSourceRepositoryRoot()),\n\t\t\t\tproposalId: current.id,\n\t\t\t\trevision,\n\t\t\t\tparentBundleDigest: current.parentBundleDigest,\n\t\t\t\tpatch: codePatch,\n\t\t\t\t...(options.codeValidationExecutor ? { validationExecutor: options.codeValidationExecutor } : {}),\n\t\t\t});\n\t\t\tproposal = {\n\t\t\t\tschemaVersion: 2,\n\t\t\t\tid: current.id,\n\t\t\t\tcreatedAt: current.createdAt,\n\t\t\t\trevision,\n\t\t\t\tparentBundleDigest: current.parentBundleDigest,\n\t\t\t\tkind: \"code\",\n\t\t\t\ttier: \"T2\",\n\t\t\t\tmotivation: options.draft.motivation,\n\t\t\t\tdiff: staged.diff,\n\t\t\t\texpectedEffect: options.draft.expectedEffect,\n\t\t\t\trisk: options.draft.risk,\n\t\t\t\tverifyPlan: options.draft.verifyPlan,\n\t\t\t\ttrialPlan: options.draft.trialPlan,\n\t\t\t\tstatus: \"pending\",\n\t\t\t\tsource: options.draft.source,\n\t\t\t\tevidence: options.draft.evidence,\n\t\t\t\tinboxReferences: options.draft.inboxReferences,\n\t\t\t\treplayScenarios: options.draft.replayScenarios,\n\t\t\t\t...(options.draft.targetAbi ? { targetAbi: options.draft.targetAbi } : {}),\n\t\t\t\t...(options.draft.requiresNewAbi !== undefined ? { requiresNewAbi: options.draft.requiresNewAbi } : {}),\n\t\t\t\tchangedPaths: staged.changedPaths,\n\t\t\t\tdiffDigest: staged.diffDigest,\n\t\t\t\tapprovalDigest: staged.approvalDigest,\n\t\t\t\tcodePatch,\n\t\t\t\tcodeWorkspace: {\n\t\t\t\t\trepositoryRoot: staged.repositoryRoot,\n\t\t\t\t\trepositoryId: staged.repositoryIdentity,\n\t\t\t\t\tworktreePath: staged.worktreePath,\n\t\t\t\t\tbranch: staged.branch,\n\t\t\t\t\tbaseCommit: staged.baseCommit,\n\t\t\t\t\tintegrityDigest: staged.approvalDigest,\n\t\t\t\t},\n\t\t\t\tsuggestedTier: options.draft.suggestedTier,\n\t\t\t\tl1: {\n\t\t\t\t\tpassed: staged.l1.passed,\n\t\t\t\t\treason: staged.l1.passed ? \"Sandboxed code validation passed\" : \"Sandboxed code validation failed\",\n\t\t\t\t\terrors: staged.l1.errors,\n\t\t\t\t},\n\t\t\t\tartifacts: {},\n\t\t\t};\n\t\t\tproposal.artifacts.validation = await writeEvaluationArtifact({\n\t\t\t\tpaths: options.paths,\n\t\t\t\tproposalId: proposal.id,\n\t\t\t\trevision,\n\t\t\t\tdiffDigest: proposal.diffDigest,\n\t\t\t\tkind: \"validation\",\n\t\t\t\tcontent: renderCodeValidation(staged.l1),\n\t\t\t});\n\t\t} else {\n\t\t\tconst temporaryDirectory = join(\n\t\t\t\toptions.paths.proposals,\n\t\t\t\t`.tmp-${current.id}-r${revision}-${randomUUID().slice(0, 8)}`,\n\t\t\t);\n\t\t\tconst candidateDirectory = join(temporaryDirectory, \"candidate\");\n\t\t\tawait mkdir(temporaryDirectory, { recursive: true });\n\t\t\ttry {\n\t\t\t\tconst parent = await loadCompiledBundle(options.paths, current.parentBundleDigest);\n\t\t\t\tawait materializeBundle(options.paths, current.parentBundleDigest, candidateDirectory);\n\t\t\t\tawait applyDraftChanges(candidateDirectory, options.draft.changes);\n\t\t\t\tconst candidate = await compileBundle({\n\t\t\t\t\tpaths: options.paths,\n\t\t\t\t\tsourceDirectory: candidateDirectory,\n\t\t\t\t\tparentDigest: current.parentBundleDigest,\n\t\t\t\t\tsummary: options.draft.expectedEffect.slice(0, 240),\n\t\t\t\t});\n\t\t\t\tconst evaluation = await evaluateDataCandidate({\n\t\t\t\t\tpaths: options.paths,\n\t\t\t\t\tparent,\n\t\t\t\t\tcandidate,\n\t\t\t\t\tevidence: options.draft.evidence,\n\t\t\t\t\tsuggestedTier: options.draft.suggestedTier,\n\t\t\t\t\ttargetAbi: options.draft.targetAbi,\n\t\t\t\t\trequiresNewAbi: options.draft.requiresNewAbi,\n\t\t\t\t});\n\t\t\t\tconst diffDigest = sha256(evaluation.diff);\n\t\t\t\tproposal = {\n\t\t\t\t\tschemaVersion: 2,\n\t\t\t\t\tid: current.id,\n\t\t\t\t\tcreatedAt: current.createdAt,\n\t\t\t\t\trevision,\n\t\t\t\t\tparentBundleDigest: current.parentBundleDigest,\n\t\t\t\t\tkind: \"data\",\n\t\t\t\t\ttier: evaluation.tier,\n\t\t\t\t\tmotivation: options.draft.motivation,\n\t\t\t\t\tdiff: evaluation.diff,\n\t\t\t\t\texpectedEffect: options.draft.expectedEffect,\n\t\t\t\t\trisk: options.draft.risk,\n\t\t\t\t\tverifyPlan: options.draft.verifyPlan,\n\t\t\t\t\ttrialPlan: options.draft.trialPlan,\n\t\t\t\t\tstatus: \"pending\",\n\t\t\t\t\tsource: options.draft.source,\n\t\t\t\t\tevidence: options.draft.evidence,\n\t\t\t\t\tinboxReferences: options.draft.inboxReferences,\n\t\t\t\t\treplayScenarios: options.draft.replayScenarios,\n\t\t\t\t\t...(options.draft.targetAbi ? { targetAbi: options.draft.targetAbi } : {}),\n\t\t\t\t\t...(options.draft.requiresNewAbi !== undefined ? { requiresNewAbi: options.draft.requiresNewAbi } : {}),\n\t\t\t\t\tchangedPaths: evaluation.changedPaths,\n\t\t\t\t\tdiffDigest,\n\t\t\t\t\tapprovalDigest: diffDigest,\n\t\t\t\t\tcandidateDigest: candidate.digest,\n\t\t\t\t\tsuggestedTier: options.draft.suggestedTier,\n\t\t\t\t\tl1: evaluation.l1,\n\t\t\t\t\tartifacts: {},\n\t\t\t\t};\n\t\t\t} finally {\n\t\t\t\tawait rm(temporaryDirectory, { recursive: true, force: true }).catch(() => {});\n\t\t\t}\n\t\t}\n\t\tawait writeFile(\n\t\t\tjoin(options.paths.proposals, current.id, \"observations.md\"),\n\t\t\toptions.observationsMarkdown,\n\t\t\t\"utf8\",\n\t\t);\n\t\tawait saveProposal(options.paths, proposal);\n\t\tawait saveProposalRevisionSnapshot(options.paths, proposal);\n\t\treturn proposal;\n\t});\n}\n\nexport async function approveProposal(\n\tpaths: EvoPaths,\n\tid: string,\n\texpected: ProposalApproval,\n\tcodeValidationExecutor?: CodeValidationExecutor,\n\tregistry: BundleRegistry = new BundleRegistry(paths),\n\tidempotencyKey?: string,\n\texpectedStateDigest?: string,\n\tdataActivation?: DataApprovalActivation,\n): Promise<Proposal> {\n\tconst prepared = await withProposalLock(paths, id, async () => {\n\t\tconst proposal = await loadProposal(paths, id);\n\t\tassertProposalApproval(proposal, expected);\n\t\tif (expected.artifactsDigest === undefined) {\n\t\t\tthrow new Error(`Proposal ${id} requires confirmation of its approval artifacts`);\n\t\t}\n\t\tif (proposal.kind === \"code\") {\n\t\t\tif (proposal.status !== \"pending\" && proposal.status !== \"approved\") {\n\t\t\t\tthrow new Error(`Proposal ${id} is ${proposal.status}`);\n\t\t\t}\n\t\t\tif (!proposal.codeWorkspace) throw new Error(`Proposal ${id} has no isolated code workspace`);\n\t\t\tif (!proposal.replayScenarios[0]) {\n\t\t\t\tthrow new Error(`Code proposal ${id} requires a replay scenario`);\n\t\t\t}\n\t\t\tconst workspace = proposal.codeWorkspace;\n\t\t\tconst staged = await revalidateCodeWorktree({\n\t\t\t\tpaths,\n\t\t\t\tproposalId: id,\n\t\t\t\trevision: proposal.revision,\n\t\t\t\texpectedApprovalDigest: workspace.integrityDigest,\n\t\t\t\texpectedRepositoryRoot: workspace.repositoryRoot,\n\t\t\t\texpectedRepositoryIdentity: workspace.repositoryId,\n\t\t\t\texpectedBaseCommit: workspace.baseCommit,\n\t\t\t\texpectedParentBundleDigest: proposal.parentBundleDigest,\n\t\t\t\t...(codeValidationExecutor ? { validationExecutor: codeValidationExecutor } : {}),\n\t\t\t});\n\t\t\tconst mismatches: string[] = [];\n\t\t\tif (staged.repositoryRoot !== workspace.repositoryRoot) mismatches.push(\"repositoryRoot\");\n\t\t\tif (staged.repositoryIdentity !== workspace.repositoryId) mismatches.push(\"repositoryId\");\n\t\t\tif (staged.worktreePath !== workspace.worktreePath) mismatches.push(\"worktreePath\");\n\t\t\tif (staged.branch !== workspace.branch) mismatches.push(\"branch\");\n\t\t\tif (staged.baseCommit !== workspace.baseCommit) mismatches.push(\"baseCommit\");\n\t\t\tif (staged.diff !== proposal.diff) mismatches.push(\"diff\");\n\t\t\tif (staged.diffDigest !== proposal.diffDigest) mismatches.push(\"diffDigest\");\n\t\t\tif (canonicalJson(staged.changedPaths) !== canonicalJson(proposal.changedPaths))\n\t\t\t\tmismatches.push(\"changedPaths\");\n\t\t\tif (mismatches.length > 0) {\n\t\t\t\tthrow new Error(`Proposal ${id} does not match its isolated worktree: ${mismatches.join(\", \")}`);\n\t\t\t}\n\t\t\tif (!staged.l1.passed) throw new Error(`Proposal ${id} failed approval-time L1 revalidation`);\n\t\t\tif (!proposal.l1.passed) throw new Error(`Proposal ${id} did not pass its staged L1 validation`);\n\t\t\tawait assertApprovalArtifact(paths, proposal, \"validation\");\n\t\t\tawait assertApprovalArtifact(paths, proposal, \"replay\");\n\t\t\tawait assertApprovalArtifact(paths, proposal, \"review\");\n\t\t\treturn { kind: \"code\" as const, proposal, workspace };\n\t\t}\n\t\tconst { candidate, evaluation } = await revalidateDataProposal(paths, proposal);\n\t\t// Sandboxed workflow commands carry no product-behavior metrics a Canary\n\t\t// could observe: once the executable dry run passed, approval activates\n\t\t// them directly (still rollbackable and audit-logged as human-direct-keep).\n\t\tconst activation: DataApprovalActivation | undefined =\n\t\t\tdataActivation ?? (proposal.targetAbi === \"workflow/v1\" ? { mode: \"direct\" } : undefined);\n\t\tif (activation?.mode === \"direct\" && !proposal.targetAbi) {\n\t\t\tthrow new Error(\"Direct activation is limited to an existing host-defined component ABI\");\n\t\t}\n\t\tconst targetStatus: Proposal[\"status\"] =\n\t\t\tevaluation.tier === \"T0\" || activation?.mode === \"direct\" ? \"kept\" : \"trialing\";\n\t\tif (proposal.status !== \"pending\" && proposal.status !== targetStatus) {\n\t\t\tthrow new Error(`Proposal ${id} is ${proposal.status}`);\n\t\t}\n\t\tif (evaluation.tier !== \"T0\") await assertApprovalArtifact(paths, proposal, \"review\");\n\t\tif (evaluation.tier === \"T2\") await assertApprovalArtifact(paths, proposal, \"replay\");\n\t\tif (activation?.mode === \"direct\") await assertApprovalArtifact(paths, proposal, \"validation\");\n\t\treturn { kind: \"data\" as const, proposal, candidate, evaluation, targetStatus, activation };\n\t});\n\n\tif (prepared.kind === \"code\") {\n\t\treturn registry.approveCodeProposal({\n\t\t\t...(idempotencyKey ? { idempotencyKey } : {}),\n\t\t\t...(expectedStateDigest ? { expectedStateDigest } : {}),\n\t\t\tproposalId: id,\n\t\t\tparentDigest: prepared.proposal.parentBundleDigest,\n\t\t\trevision: prepared.proposal.revision,\n\t\t\tdiffDigest: prepared.proposal.diffDigest,\n\t\t\tapprovalDigest: prepared.proposal.approvalDigest,\n\t\t\tbranch: prepared.workspace.branch,\n\t\t\treason: `Approved code context ${prepared.proposal.approvalDigest} for manual commit and merge from ${prepared.workspace.branch}`,\n\t\t\tproposalBefore: prepared.proposal,\n\t\t\tproposalAfter: { ...prepared.proposal, status: \"approved\" },\n\t\t});\n\t}\n\tconst result = await registry.approveDataProposal({\n\t\t...(idempotencyKey ? { idempotencyKey } : {}),\n\t\t...(expectedStateDigest ? { expectedStateDigest } : {}),\n\t\tproposalId: id,\n\t\trevision: prepared.proposal.revision,\n\t\tdiffDigest: prepared.proposal.diffDigest,\n\t\tparentDigest: prepared.proposal.parentBundleDigest,\n\t\tcandidateDigest: prepared.candidate.digest,\n\t\ttier: prepared.evaluation.tier,\n\t\tplan: prepared.proposal.trialPlan,\n\t\tactivation: prepared.activation ?? { mode: \"trial\" },\n\t\treason:\n\t\t\tprepared.activation?.mode === \"direct\"\n\t\t\t\t? `Human directly activated exact component diff ${prepared.proposal.diffDigest}`\n\t\t\t\t: `Approved exact data diff ${prepared.proposal.diffDigest}`,\n\t\tproposalBefore: prepared.proposal,\n\t\tproposalAfter: { ...prepared.proposal, status: prepared.targetStatus },\n\t});\n\treturn result.proposal;\n}\n\nexport async function rejectProposal(\n\tpaths: EvoPaths,\n\tid: string,\n\treason: string,\n\tregistry: BundleRegistry = new BundleRegistry(paths),\n\tidempotencyKey?: string,\n\texpectedStateDigest?: string,\n): Promise<Proposal> {\n\tconst prepared = await withProposalLock(paths, id, async () => {\n\t\tconst proposal = await loadProposal(paths, id);\n\t\tif (proposal.status !== \"pending\" && proposal.status !== \"deferred\" && proposal.status !== \"rejected\") {\n\t\t\tthrow new Error(`Proposal ${id} is ${proposal.status}`);\n\t\t}\n\t\tconst proposalAfter: Proposal = { ...proposal, status: \"rejected\" };\n\t\tdelete proposalAfter.defer;\n\t\treturn { proposalBefore: proposal, proposalAfter };\n\t});\n\treturn registry.transitionProposal({\n\t\t...(idempotencyKey ? { idempotencyKey } : {}),\n\t\t...(expectedStateDigest ? { expectedStateDigest } : {}),\n\t\taction: \"reject-proposal\",\n\t\t...prepared,\n\t\treason,\n\t});\n}\n\nexport async function deferProposal(\n\tpaths: EvoPaths,\n\tid: string,\n\treason: string,\n\tuntil?: string,\n\tregistry: BundleRegistry = new BundleRegistry(paths),\n\tidempotencyKey?: string,\n\texpectedStateDigest?: string,\n): Promise<Proposal> {\n\tconst prepared = await withProposalLock(paths, id, async () => {\n\t\tconst proposal = await loadProposal(paths, id);\n\t\tif (proposal.status !== \"pending\" && proposal.status !== \"deferred\") {\n\t\t\tthrow new Error(`Proposal ${id} is ${proposal.status}`);\n\t\t}\n\t\tconst proposalAfter: Proposal =\n\t\t\tproposal.status === \"deferred\"\n\t\t\t\t? proposal\n\t\t\t\t: {\n\t\t\t\t\t\t...proposal,\n\t\t\t\t\t\tstatus: \"deferred\",\n\t\t\t\t\t\tdefer: { deferredAt: new Date().toISOString(), reason, ...(until ? { until } : {}) },\n\t\t\t\t\t};\n\t\treturn { proposalBefore: proposal, proposalAfter };\n\t});\n\treturn registry.transitionProposal({\n\t\t...(idempotencyKey ? { idempotencyKey } : {}),\n\t\t...(expectedStateDigest ? { expectedStateDigest } : {}),\n\t\taction: \"defer-proposal\",\n\t\t...prepared,\n\t\treason,\n\t});\n}\n\nexport async function reopenProposal(\n\tpaths: EvoPaths,\n\tid: string,\n\treason: string,\n\tregistry: BundleRegistry = new BundleRegistry(paths),\n\tidempotencyKey?: string,\n\texpectedStateDigest?: string,\n): Promise<Proposal> {\n\tconst prepared = await withProposalLock(paths, id, async () => {\n\t\tconst proposal = await loadProposal(paths, id);\n\t\tif (proposal.status !== \"deferred\" && proposal.status !== \"pending\") {\n\t\t\tthrow new Error(`Proposal ${id} is ${proposal.status}`);\n\t\t}\n\t\tconst proposalAfter: Proposal = { ...proposal, status: \"pending\" };\n\t\tdelete proposalAfter.defer;\n\t\treturn { proposalBefore: proposal, proposalAfter };\n\t});\n\treturn registry.transitionProposal({\n\t\t...(idempotencyKey ? { idempotencyKey } : {}),\n\t\t...(expectedStateDigest ? { expectedStateDigest } : {}),\n\t\taction: \"reopen-proposal\",\n\t\t...prepared,\n\t\treason,\n\t});\n}\n"]}