{"version":3,"file":"acceptance.d.ts","sourceRoot":"","sources":["../../../../src/runs/shared/acceptance.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,gBAAgB,EAEhB,eAAe,EACf,gBAAgB,EAEhB,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EAKd,aAAa,EACb,wBAAwB,EAExB,YAAY,EACZ,eAAe,EACf,MAAM,uBAAuB,CAAC;AAiK/B,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,gBAAgB,CAK7F;AAED,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,eAAe,GAAG,SAAS,GACrC;IAAE,UAAU,CAAC,EAAE,eAAe,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAMlD;AAWD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,SAAe,GAAG,MAAM,EAAE,CAsJ1F;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC;QACb,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC,GAAG;YAAE,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;KACtE,CAAC,CAAC;CACH,GAAG,MAAM,EAAE,CAkBX;AA0BD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IACjD,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B,GAAG,wBAAwB,CA+D3B;AAMD,wBAAgB,sBAAsB,CACrC,UAAU,EAAE,wBAAwB,EACpC,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,MAAM,CA2DR;AAkRD,eAAO,MAAM,2BAA2B,4CAA4C,CAAC;AAErF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAoDnG;AAuBD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAuB5D;AAiRD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAChD,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC,GAAG;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACnG,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,gBAAgB,CAgDnB;AA+OD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC/C,UAAU,EAAE,wBAAwB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACxE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgI5B;AAED,wBAAgB,4BAA4B,CAC3C,UAAU,EAAE,wBAAwB,EACpC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpC,gBAAgB,CAYlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAQrF","sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n\tAcceptanceConfig,\n\tAcceptanceEvidenceKind,\n\tAcceptanceInput,\n\tAcceptanceLedger,\n\tAcceptanceLevel,\n\tAcceptanceReport,\n\tAcceptanceReviewResult,\n\tAcceptanceRole,\n\tAcceptanceRuntimeCheck,\n\tAcceptanceRuntimeCheckStatus,\n\tAcceptanceVerifyCommand,\n\tAcceptanceVerifyResult,\n\tAgentContract,\n\tResolvedAcceptanceConfig,\n\tResolvedAcceptanceGate,\n\tSingleResult,\n\tSubagentRunMode,\n} from \"../../shared/types.ts\";\nimport { isAgentContractV1 } from \"./agent-contract.ts\";\nimport { classifyTaskMutationIntent, taskMayMutate } from \"./task-intent.ts\";\n\nconst LEVEL_RANK: Record<Exclude<AcceptanceLevel, \"auto\">, number> = {\n\tnone: 0,\n\tattested: 1,\n\tchecked: 2,\n\tverified: 3,\n};\n\nconst VALID_LEVELS = new Set<AcceptanceLevel>([\"auto\", \"none\", \"attested\", \"checked\", \"verified\"]);\nconst VALID_EVIDENCE_KINDS: AcceptanceEvidenceKind[] = [\n\t\"changed-files\",\n\t\"tests-added\",\n\t\"commands-run\",\n\t\"validation-output\",\n\t\"residual-risks\",\n\t\"no-staged-files\",\n\t\"diff-summary\",\n\t\"review-findings\",\n\t\"manual-notes\",\n];\nconst VALID_EVIDENCE = new Set<AcceptanceEvidenceKind>(VALID_EVIDENCE_KINDS);\nconst ACCEPTANCE_EVIDENCE_HELP = `Supported evidence kinds: ${VALID_EVIDENCE_KINDS.join(\", \")}. Example: { level: \"checked\", evidence: [\"commands-run\", \"changed-files\"] }.`;\nconst ACCEPTANCE_OBJECT_EXAMPLE = 'Example: { level: \"checked\", evidence: [\"commands-run\", \"changed-files\"] }.';\nconst ACCEPTANCE_CONFIG_KEYS = new Set([\"level\", \"criteria\", \"evidence\", \"verify\", \"review\", \"stopRules\", \"reason\"]);\nconst ACCEPTANCE_GATE_KEYS = new Set([\"id\", \"must\", \"evidence\", \"severity\"]);\nconst ACCEPTANCE_VERIFY_KEYS = new Set([\"id\", \"command\", \"timeoutMs\", \"cwd\", \"env\", \"allowFailure\"]);\nconst ACCEPTANCE_REVIEW_KEYS = new Set([\"agent\", \"focus\", \"required\"]);\nconst EXPLICIT_REVIEWED_UNAVAILABLE =\n\t\"is an achieved status, not a requestable acceptance level. For a read-only reviewer call, omit acceptance. To require independent review of a writer result, use acceptance.review.required and orchestrate the reviewer separately.\";\n\nfunction normalizeLevel(level: AcceptanceLevel | undefined): Exclude<AcceptanceLevel, \"auto\"> | \"auto\" {\n\treturn level ?? \"auto\";\n}\n\nfunction unique<T>(items: T[]): T[] {\n\treturn [...new Set(items)];\n}\n\nfunction requiredEvidenceForLevel(level: Exclude<AcceptanceLevel, \"auto\">): AcceptanceEvidenceKind[] {\n\tswitch (level) {\n\t\tcase \"none\":\n\t\t\treturn [];\n\t\tcase \"attested\":\n\t\t\treturn [\"manual-notes\", \"residual-risks\"];\n\t\tcase \"checked\":\n\t\t\treturn [\"changed-files\", \"tests-added\", \"commands-run\", \"residual-risks\", \"no-staged-files\"];\n\t\tcase \"verified\":\n\t\t\treturn [\n\t\t\t\t\"changed-files\",\n\t\t\t\t\"tests-added\",\n\t\t\t\t\"commands-run\",\n\t\t\t\t\"validation-output\",\n\t\t\t\t\"residual-risks\",\n\t\t\t\t\"no-staged-files\",\n\t\t\t];\n\t}\n}\n\nfunction inferLevel(input: {\n\tagentName: string;\n\tacceptanceRole?: AcceptanceRole;\n\ttask?: string;\n\tmode?: SubagentRunMode;\n\tasync?: boolean;\n\tdynamic?: boolean;\n\tdynamicGroup?: boolean;\n}): {\n\tlevel: Exclude<AcceptanceLevel, \"auto\">;\n\treasons: string[];\n\tcriteria: string[];\n\tevidence: AcceptanceEvidenceKind[];\n\treview?: { agent?: string; required?: boolean };\n} {\n\tconst agent = input.agentName.toLowerCase();\n\tconst task = input.task?.toLowerCase() ?? \"\";\n\tconst reasons: string[] = [];\n\t// Declared roles replace name heuristics, so use the full writer grammar to detect explicit mutation independently of the actual agent name.\n\tconst intent = classifyTaskMutationIntent(input.acceptanceRole ? \"worker\" : input.agentName, input.task ?? \"\");\n\tconst readOnlyTask =\n\t\tintent.kind === \"read-only\" ||\n\t\t(intent.kind === \"unknown\" &&\n\t\t\t/\\b(?:read[- ]only|review[- ]only|no edits|without edits|inspect|summari[sz]e)\\b/.test(task));\n\tconst rolePatchTask =\n\t\tinput.acceptanceRole !== undefined &&\n\t\tintent.kind !== \"read-only\" &&\n\t\t!/\\b(?:do not|don't|must not)\\s+patch\\b/.test(task) &&\n\t\t/\\bpatch\\s+(?:(?:\\.{0,2}[\\\\/])?(?:[\\w.-]+[\\\\/])+[\\w.-]+|[\\w.-]+\\.[a-z0-9]+\\b|(?:the\\s+)?parser\\b)/.test(task);\n\tconst taskMayWrite = readOnlyTask\n\t\t? false\n\t\t: taskMayMutate(input.task ?? \"\") || intent.kind === \"implementation\" || rolePatchTask;\n\tconst readOnlyAgent =\n\t\tinput.acceptanceRole === \"read-only\" ||\n\t\t(input.acceptanceRole === undefined && /\\b(?:reviewer|oracle|scout|researcher|analyst)\\b/.test(agent));\n\tconst writeTask =\n\t\ttaskMayWrite ||\n\t\t(input.acceptanceRole === \"writer\" && !readOnlyTask) ||\n\t\t(input.acceptanceRole === undefined && /\\bworker\\b/.test(agent) && !readOnlyTask);\n\tconst inferredReadOnly = readOnlyTask || (input.acceptanceRole === \"read-only\" && !taskMayWrite);\n\tconst roleResolvesReadOnly = input.acceptanceRole !== undefined && inferredReadOnly;\n\tconst keywordRiskReadOnly = input.acceptanceRole === undefined ? intent.kind === \"read-only\" : inferredReadOnly;\n\tconst risky =\n\t\tBoolean(input.async && writeTask) ||\n\t\t(Boolean(input.dynamic) && !roleResolvesReadOnly) ||\n\t\t(Boolean(input.dynamicGroup) && !roleResolvesReadOnly) ||\n\t\t(!keywordRiskReadOnly &&\n\t\t\t/\\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\\b/.test(task));\n\n\tif (risky) {\n\t\treasons.push(input.async ? \"async write-capable or risky run\" : \"risky write-capable run\");\n\t\tif (input.dynamic || input.dynamicGroup) reasons.push(\"dynamic fanout context\");\n\t\treturn {\n\t\t\tlevel: \"checked\",\n\t\t\treasons,\n\t\t\tcriteria: [\n\t\t\t\t\"Implement the requested change without widening scope\",\n\t\t\t\t\"Return evidence sufficient for an independent acceptance review\",\n\t\t\t],\n\t\t\tevidence: requiredEvidenceForLevel(\"checked\"),\n\t\t\treview: { agent: \"reviewer\", required: true },\n\t\t};\n\t}\n\tif (writeTask && !readOnlyTask) {\n\t\treasons.push(\n\t\t\tinput.acceptanceRole === \"writer\" && !taskMayWrite\n\t\t\t\t? \"declared writer acceptance role\"\n\t\t\t\t: \"write-capable worker/task\",\n\t\t);\n\t\treturn {\n\t\t\tlevel: \"checked\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Implement the requested change without widening scope\"],\n\t\t\tevidence: requiredEvidenceForLevel(\"checked\"),\n\t\t};\n\t}\n\tif (readOnlyAgent || readOnlyTask) {\n\t\treasons.push(\n\t\t\tinput.acceptanceRole === \"read-only\" && !readOnlyTask\n\t\t\t\t? \"declared read-only acceptance role\"\n\t\t\t\t: readOnlyAgent\n\t\t\t\t\t? \"read-only/reviewer-style agent\"\n\t\t\t\t\t: \"read-only task wording\",\n\t\t);\n\t\treturn {\n\t\t\tlevel: \"attested\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Return concrete findings with file paths and severity when applicable\"],\n\t\t\tevidence: [\"review-findings\", \"residual-risks\"],\n\t\t};\n\t}\n\treasons.push(\"default lightweight attestation\");\n\treturn {\n\t\tlevel: \"attested\",\n\t\treasons,\n\t\tcriteria: [\"Return a concise result and residual risks when applicable\"],\n\t\tevidence: [\"manual-notes\", \"residual-risks\"],\n\t};\n}\n\nexport function normalizeAcceptanceInput(input: AcceptanceInput | undefined): AcceptanceConfig {\n\tif (input === undefined || input === \"auto\") return { level: \"auto\" };\n\tif (input === false) return { level: \"none\", reason: \"disabled by deprecated false shorthand\" };\n\tif (typeof input === \"string\") return { level: input };\n\treturn { ...input };\n}\n\nexport function normalizeGateAcceptance(\n\tgate: unknown,\n\tacceptance: AcceptanceInput | undefined,\n): { acceptance?: AcceptanceInput; error?: string } {\n\tif (gate === undefined) return { acceptance };\n\tif (typeof gate !== \"string\" || !gate.trim()) return { error: \"gate must be a non-empty command string.\" };\n\tif (acceptance !== undefined)\n\t\treturn { error: \"gate cannot be combined with acceptance; use one gate command or acceptance.verify.\" };\n\treturn { acceptance: { level: \"verified\", verify: [{ id: \"gate\", command: gate.trim() }] } };\n}\n\nfunction explicitAcceptanceCanDisable(explicit: AcceptanceConfig): boolean {\n\treturn explicit.level === \"none\" && typeof explicit.reason === \"string\" && explicit.reason.trim().length > 0;\n}\n\nfunction unsupportedEvidenceKindMessage(pathLabel: string, item: unknown): string {\n\tconst value = typeof item === \"string\" ? ` \"${item}\"` : \"\";\n\treturn `${pathLabel}${value} is not a supported evidence kind. ${ACCEPTANCE_EVIDENCE_HELP}`;\n}\n\nexport function validateAcceptanceInput(input: unknown, pathLabel = \"acceptance\"): string[] {\n\tconst errors: string[] = [];\n\tif (input === undefined) return errors;\n\tif (input === false) return errors;\n\tif (typeof input === \"string\") {\n\t\tif (input === \"reviewed\") errors.push(`${pathLabel} ${EXPLICIT_REVIEWED_UNAVAILABLE}`);\n\t\telse if (!VALID_LEVELS.has(input as AcceptanceLevel)) errors.push(`${pathLabel} has invalid level '${input}'.`);\n\t\telse if (input === \"none\")\n\t\t\terrors.push(`${pathLabel} level \"none\" requires a reason; use { level: \"none\", reason: \"...\" }.`);\n\t\telse if (input === \"verified\")\n\t\t\terrors.push(\n\t\t\t\t`${pathLabel} level \"verified\" requires object form with at least one runtime verify command. Use level \"checked\" or provide a non-empty acceptance.verify array.`,\n\t\t\t);\n\t\treturn errors;\n\t}\n\tif (!input || typeof input !== \"object\" || Array.isArray(input)) {\n\t\terrors.push(`${pathLabel} must be a string level, false, or an object. ${ACCEPTANCE_OBJECT_EXAMPLE}`);\n\t\treturn errors;\n\t}\n\tconst value = input as Record<string, unknown>;\n\tfor (const key of Object.keys(value)) {\n\t\tif (!ACCEPTANCE_CONFIG_KEYS.has(key)) errors.push(`${pathLabel}.${key} is not supported.`);\n\t}\n\tif (value.level === \"reviewed\") {\n\t\terrors.push(`${pathLabel}.level ${EXPLICIT_REVIEWED_UNAVAILABLE}`);\n\t} else if (\n\t\tvalue.level !== undefined &&\n\t\t(typeof value.level !== \"string\" || !VALID_LEVELS.has(value.level as AcceptanceLevel))\n\t) {\n\t\terrors.push(`${pathLabel}.level must be one of auto, none, attested, checked, verified.`);\n\t}\n\tif (value.level === \"none\" && (typeof value.reason !== \"string\" || !value.reason.trim())) {\n\t\terrors.push(`${pathLabel}.reason is required when level is none.`);\n\t}\n\tif (value.reason !== undefined && typeof value.reason !== \"string\")\n\t\terrors.push(`${pathLabel}.reason must be a string.`);\n\tif (value.criteria !== undefined && !Array.isArray(value.criteria))\n\t\terrors.push(`${pathLabel}.criteria must be an array.`);\n\tif (Array.isArray(value.criteria)) {\n\t\tconst criterionIds = new Set<string>();\n\t\tfor (const [index, criterion] of value.criteria.entries()) {\n\t\t\tif (typeof criterion === \"string\") continue;\n\t\t\tconst criterionPath = `${pathLabel}.criteria[${index}]`;\n\t\t\tif (!criterion || typeof criterion !== \"object\" || Array.isArray(criterion)) {\n\t\t\t\terrors.push(`${criterionPath} must be a string or an object.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst gate = criterion as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(gate)) {\n\t\t\t\tif (!ACCEPTANCE_GATE_KEYS.has(key)) errors.push(`${criterionPath}.${key} is not supported.`);\n\t\t\t}\n\t\t\tif (typeof gate.id !== \"string\" || !gate.id.trim()) {\n\t\t\t\terrors.push(`${criterionPath}.id is required.`);\n\t\t\t} else {\n\t\t\t\tconst normalizedId = normalizedToken(gate.id);\n\t\t\t\tif (criterionIds.has(normalizedId))\n\t\t\t\t\terrors.push(`${criterionPath}.id duplicates normalized criterion id '${normalizedId}'.`);\n\t\t\t\tcriterionIds.add(normalizedId);\n\t\t\t}\n\t\t\tif (typeof gate.must !== \"string\" || !gate.must.trim()) errors.push(`${criterionPath}.must is required.`);\n\t\t\tif (gate.evidence !== undefined && !Array.isArray(gate.evidence))\n\t\t\t\terrors.push(`${criterionPath}.evidence must be an array. ${ACCEPTANCE_EVIDENCE_HELP}`);\n\t\t\tif (Array.isArray(gate.evidence)) {\n\t\t\t\tfor (const [evidenceIndex, item] of gate.evidence.entries()) {\n\t\t\t\t\tif (typeof item !== \"string\" || !VALID_EVIDENCE.has(item as AcceptanceEvidenceKind)) {\n\t\t\t\t\t\terrors.push(unsupportedEvidenceKindMessage(`${criterionPath}.evidence[${evidenceIndex}]`, item));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (gate.severity !== undefined && gate.severity !== \"required\" && gate.severity !== \"recommended\") {\n\t\t\t\terrors.push(`${criterionPath}.severity must be required or recommended.`);\n\t\t\t}\n\t\t}\n\t}\n\tif (Array.isArray(value.evidence)) {\n\t\tfor (const [index, item] of value.evidence.entries()) {\n\t\t\tif (typeof item !== \"string\" || !VALID_EVIDENCE.has(item as AcceptanceEvidenceKind)) {\n\t\t\t\terrors.push(unsupportedEvidenceKindMessage(`${pathLabel}.evidence[${index}]`, item));\n\t\t\t}\n\t\t}\n\t} else if (value.evidence !== undefined) {\n\t\terrors.push(`${pathLabel}.evidence must be an array. ${ACCEPTANCE_EVIDENCE_HELP}`);\n\t}\n\tif (value.level === \"verified\" && (!Array.isArray(value.verify) || value.verify.length === 0)) {\n\t\terrors.push(\n\t\t\t`${pathLabel}.verify must contain at least one runtime command when level is verified. Use level \"checked\" or provide a non-empty acceptance.verify array.`,\n\t\t);\n\t} else if (value.verify !== undefined && !Array.isArray(value.verify)) {\n\t\terrors.push(`${pathLabel}.verify must be an array.`);\n\t}\n\tif (Array.isArray(value.verify)) {\n\t\tfor (const [index, command] of value.verify.entries()) {\n\t\t\tif (!command || typeof command !== \"object\" || Array.isArray(command)) {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}] must be an object.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst cmd = command as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(cmd)) {\n\t\t\t\tif (!ACCEPTANCE_VERIFY_KEYS.has(key)) errors.push(`${pathLabel}.verify[${index}].${key} is not supported.`);\n\t\t\t}\n\t\t\tif (typeof cmd.id !== \"string\" || !cmd.id.trim()) errors.push(`${pathLabel}.verify[${index}].id is required.`);\n\t\t\tif (typeof cmd.command !== \"string\" || !cmd.command.trim())\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].command is required.`);\n\t\t\tif (\n\t\t\t\tcmd.timeoutMs !== undefined &&\n\t\t\t\t(typeof cmd.timeoutMs !== \"number\" || !Number.isInteger(cmd.timeoutMs) || cmd.timeoutMs < 1)\n\t\t\t) {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].timeoutMs must be an integer >= 1.`);\n\t\t\t}\n\t\t\tif (cmd.cwd !== undefined && typeof cmd.cwd !== \"string\")\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].cwd must be a string.`);\n\t\t\tif (cmd.env !== undefined) {\n\t\t\t\tif (!cmd.env || typeof cmd.env !== \"object\" || Array.isArray(cmd.env)) {\n\t\t\t\t\terrors.push(`${pathLabel}.verify[${index}].env must be an object.`);\n\t\t\t\t} else {\n\t\t\t\t\tfor (const [envKey, envValue] of Object.entries(cmd.env as Record<string, unknown>)) {\n\t\t\t\t\t\tif (typeof envValue !== \"string\")\n\t\t\t\t\t\t\terrors.push(`${pathLabel}.verify[${index}].env.${envKey} must be a string.`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (cmd.allowFailure !== undefined && typeof cmd.allowFailure !== \"boolean\") {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].allowFailure must be a boolean.`);\n\t\t\t}\n\t\t}\n\t}\n\tif (value.review !== undefined && value.review !== false) {\n\t\tif (!value.review || typeof value.review !== \"object\" || Array.isArray(value.review)) {\n\t\t\terrors.push(`${pathLabel}.review must be false or an object.`);\n\t\t} else {\n\t\t\tconst review = value.review as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(review)) {\n\t\t\t\tif (!ACCEPTANCE_REVIEW_KEYS.has(key)) errors.push(`${pathLabel}.review.${key} is not supported.`);\n\t\t\t}\n\t\t\tif (review.agent !== undefined && typeof review.agent !== \"string\")\n\t\t\t\terrors.push(`${pathLabel}.review.agent must be a string.`);\n\t\t\tif (review.focus !== undefined && typeof review.focus !== \"string\")\n\t\t\t\terrors.push(`${pathLabel}.review.focus must be a string.`);\n\t\t\tif (review.required !== undefined && typeof review.required !== \"boolean\")\n\t\t\t\terrors.push(`${pathLabel}.review.required must be a boolean.`);\n\t\t}\n\t}\n\tif (value.stopRules !== undefined && !Array.isArray(value.stopRules))\n\t\terrors.push(`${pathLabel}.stopRules must be an array.`);\n\tif (Array.isArray(value.stopRules)) {\n\t\tfor (const [index, item] of value.stopRules.entries()) {\n\t\t\tif (typeof item !== \"string\") errors.push(`${pathLabel}.stopRules[${index}] must be a string.`);\n\t\t}\n\t}\n\treturn errors;\n}\n\nexport function validateExecutionAcceptance(input: {\n\tacceptance?: unknown;\n\ttasks?: Array<{ acceptance?: unknown }>;\n\tchain?: Array<{\n\t\tacceptance?: unknown;\n\t\tparallel?: Array<{ acceptance?: unknown }> | { acceptance?: unknown };\n\t}>;\n}): string[] {\n\tconst errors = validateAcceptanceInput(input.acceptance, \"acceptance\");\n\tfor (const [index, task] of (input.tasks ?? []).entries()) {\n\t\terrors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));\n\t}\n\tfor (const [stepIndex, step] of (input.chain ?? []).entries()) {\n\t\terrors.push(...validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`));\n\t\tif (Array.isArray(step.parallel)) {\n\t\t\tfor (const [taskIndex, task] of step.parallel.entries()) {\n\t\t\t\terrors.push(\n\t\t\t\t\t...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`),\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (step.parallel) {\n\t\t\terrors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));\n\t\t}\n\t}\n\treturn errors;\n}\n\nfunction normalizeCriteria(\n\tcriteria:\n\t\t| Array<\n\t\t\t\t| string\n\t\t\t\t| { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: \"required\" | \"recommended\" }\n\t\t  >\n\t\t| undefined,\n\tevidence: AcceptanceEvidenceKind[],\n): ResolvedAcceptanceGate[] {\n\treturn (criteria ?? [])\n\t\t.map((criterion, index): ResolvedAcceptanceGate => {\n\t\t\tif (typeof criterion === \"string\") {\n\t\t\t\treturn { id: `criterion-${index + 1}`, must: criterion, evidence, severity: \"required\" };\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: criterion.id?.trim() || `criterion-${index + 1}`,\n\t\t\t\tmust: criterion.must ?? \"\",\n\t\t\t\tevidence: criterion.evidence?.filter((item) => VALID_EVIDENCE.has(item)) ?? evidence,\n\t\t\t\tseverity: criterion.severity ?? \"required\",\n\t\t\t};\n\t\t})\n\t\t.filter((criterion) => criterion.must.trim());\n}\n\nexport function resolveEffectiveAcceptance(input: {\n\texplicit?: AcceptanceInput;\n\tagentName: string;\n\tacceptanceRole?: AcceptanceRole;\n\ttask?: string;\n\tmode?: SubagentRunMode;\n\tasync?: boolean;\n\tdynamic?: boolean;\n\tdynamicGroup?: boolean;\n\tagentContract?: AgentContract;\n}): ResolvedAcceptanceConfig {\n\tconst explicit = normalizeAcceptanceInput(input.explicit);\n\tconst explicitLevel = normalizeLevel(explicit.level);\n\tif (isAgentContractV1(input.agentContract)) {\n\t\tconst level = explicitAcceptanceCanDisable(explicit) || explicitLevel === \"auto\" ? \"none\" : explicitLevel;\n\t\tconst evidence = unique(explicit.evidence ?? []);\n\t\tconst criteria = normalizeCriteria(\n\t\t\texplicit.criteria as\n\t\t\t\t| Array<\n\t\t\t\t\t\t| string\n\t\t\t\t\t\t| {\n\t\t\t\t\t\t\t\tid?: string;\n\t\t\t\t\t\t\t\tmust?: string;\n\t\t\t\t\t\t\t\tevidence?: AcceptanceEvidenceKind[];\n\t\t\t\t\t\t\t\tseverity?: \"required\" | \"recommended\";\n\t\t\t\t\t\t  }\n\t\t\t\t  >\n\t\t\t\t| undefined,\n\t\t\tevidence,\n\t\t);\n\t\treturn {\n\t\t\tlevel,\n\t\t\texplicit: input.explicit !== undefined,\n\t\t\tinferredReason: [],\n\t\t\tcriteria,\n\t\t\tevidence,\n\t\t\tverify: explicit.verify ?? [],\n\t\t\treview: explicit.review,\n\t\t\tstopRules: explicit.stopRules ?? [],\n\t\t\treason: explicit.reason,\n\t\t};\n\t}\n\tconst inferred = inferLevel(input);\n\tconst level = explicitAcceptanceCanDisable(explicit)\n\t\t? \"none\"\n\t\t: explicitLevel === \"auto\"\n\t\t\t? inferred.level\n\t\t\t: LEVEL_RANK[explicitLevel] >= LEVEL_RANK[inferred.level]\n\t\t\t\t? explicitLevel\n\t\t\t\t: inferred.level;\n\tconst evidence = unique([\n\t\t...(level === inferred.level ? inferred.evidence : requiredEvidenceForLevel(level)),\n\t\t...(explicit.evidence ?? []),\n\t]);\n\tconst criteria = normalizeCriteria(\n\t\t(explicit.criteria?.length ? explicit.criteria : inferred.criteria) as Array<\n\t\t\t| string\n\t\t\t| { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: \"required\" | \"recommended\" }\n\t\t>,\n\t\tevidence,\n\t);\n\tconst review = explicit.review !== undefined ? explicit.review : inferred.review;\n\treturn {\n\t\tlevel,\n\t\texplicit: input.explicit !== undefined,\n\t\tinferredReason: inferred.reasons,\n\t\tcriteria,\n\t\tevidence,\n\t\tverify: explicit.verify ?? [],\n\t\treview,\n\t\tstopRules: explicit.stopRules ?? [],\n\t\treason: explicit.reason,\n\t};\n}\n\nfunction acceptanceRequiresChildReport(acceptance: ResolvedAcceptanceConfig): boolean {\n\treturn acceptance.criteria.length > 0 || acceptance.evidence.length > 0;\n}\n\nexport function formatAcceptancePrompt(\n\tacceptance: ResolvedAcceptanceConfig,\n\toptions: { reportOptional?: boolean } = {},\n): string {\n\tif (acceptance.level === \"none\") return \"\";\n\tif (options.reportOptional && !acceptanceRequiresChildReport(acceptance)) return \"\";\n\tconst lines = [\n\t\t\"\",\n\t\t\"## Acceptance Contract\",\n\t\t`Acceptance level: ${acceptance.level}`,\n\t\t\"Completion is not accepted from prose alone. End with a structured acceptance report.\",\n\t\t\"\",\n\t\t\"Criteria:\",\n\t\t...(acceptance.criteria.length\n\t\t\t? acceptance.criteria.map((criterion) => `- ${criterion.id}: ${criterion.must}`)\n\t\t\t: [\"- Return the requested result.\"]),\n\t\t\"\",\n\t\t`Required evidence: ${acceptance.evidence.join(\", \") || \"none\"}`,\n\t];\n\tif (acceptance.verify.length > 0) {\n\t\tlines.push(\"\", \"Runtime verification commands configured by parent:\");\n\t\tfor (const command of acceptance.verify) lines.push(`- ${command.id}: ${command.command}`);\n\t}\n\tif (acceptance.review) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`Review gate: ${acceptance.review.required === false ? \"optional\" : \"required\"}${acceptance.review.agent ? ` by ${acceptance.review.agent}` : \"\"}.`,\n\t\t);\n\t\tif (acceptance.review.focus) lines.push(`Review focus: ${acceptance.review.focus}`);\n\t}\n\tif (acceptance.stopRules.length > 0) {\n\t\tlines.push(\"\", \"Stop rules:\", ...acceptance.stopRules.map((rule) => `- ${rule}`));\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"Finish with a fenced JSON block tagged `acceptance-report` in this shape:\",\n\t\t\"Use empty arrays when no items apply; array fields contain strings unless object entries are shown.\",\n\t\t\"`criteriaSatisfied[].status` must be exactly one of: satisfied, not-satisfied, not-applicable.\",\n\t\t\"`commandsRun[].result` must be exactly one of: passed, failed, not-run.\",\n\t\t\"`manualNotes` and `notes` are optional strings; an empty string means no note and does not satisfy `manual-notes` evidence.\",\n\t\t\"```acceptance-report\",\n\t\tJSON.stringify(\n\t\t\t{\n\t\t\t\tcriteriaSatisfied: acceptance.criteria\n\t\t\t\t\t.filter((criterion) => criterion.severity !== \"recommended\")\n\t\t\t\t\t.map((criterion) => ({ id: criterion.id, status: \"satisfied\", evidence: \"specific proof\" })),\n\t\t\t\tchangedFiles: [\"src/file.ts\"],\n\t\t\t\ttestsAddedOrUpdated: [\"test/file.test.ts\"],\n\t\t\t\tcommandsRun: [{ command: \"command\", result: \"passed\", summary: \"short result\" }],\n\t\t\t\tvalidationOutput: [\"validation output or concise summary\"],\n\t\t\t\tresidualRisks: [\"none\"],\n\t\t\t\tnoStagedFiles: true,\n\t\t\t\tdiffSummary: \"short description of the diff\",\n\t\t\t\treviewFindings: [\"blocker: file.ts:12 - issue found, or no blockers\"],\n\t\t\t\tmanualNotes: \"anything else the parent should know\",\n\t\t\t},\n\t\t\tnull,\n\t\t\t2,\n\t\t),\n\t\t\"```\",\n\t);\n\treturn lines.join(\"\\n\");\n}\n\nfunction extractBalancedJson(text: string, start: number): string | undefined {\n\tlet depth = 0;\n\tlet inString = false;\n\tlet escaped = false;\n\tfor (let i = start; i < text.length; i++) {\n\t\tconst char = text[i]!;\n\t\tif (inString) {\n\t\t\tif (escaped) escaped = false;\n\t\t\telse if (char === \"\\\\\") escaped = true;\n\t\t\telse if (char === '\"') inString = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === '\"') {\n\t\t\tinString = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"{\") depth++;\n\t\tif (char === \"}\") {\n\t\t\tdepth--;\n\t\t\tif (depth === 0) return text.slice(start, i + 1);\n\t\t}\n\t}\n\treturn undefined;\n}\n\nconst ACCEPTANCE_REPORT_WRAPPERS = new Set([\n\t\"acceptance\",\n\t\"acceptance-report\",\n\t\"acceptance_report\",\n\t\"acceptanceReport\",\n]);\n\nconst ACCEPTANCE_REPORT_FIELDS: Record<string, keyof AcceptanceReport> = {\n\tcriteriaSatisfied: \"criteriaSatisfied\",\n\tcriteria_satisfied: \"criteriaSatisfied\",\n\tchangedFiles: \"changedFiles\",\n\tchanged_files: \"changedFiles\",\n\ttestsAddedOrUpdated: \"testsAddedOrUpdated\",\n\ttests_added_or_updated: \"testsAddedOrUpdated\",\n\tcommandsRun: \"commandsRun\",\n\tcommands_run: \"commandsRun\",\n\tvalidationOutput: \"validationOutput\",\n\tvalidation_output: \"validationOutput\",\n\tresidualRisks: \"residualRisks\",\n\tresidual_risks: \"residualRisks\",\n\tnoStagedFiles: \"noStagedFiles\",\n\tno_staged_files: \"noStagedFiles\",\n\tdiffSummary: \"diffSummary\",\n\tdiff_summary: \"diffSummary\",\n\treviewFindings: \"reviewFindings\",\n\treview_findings: \"reviewFindings\",\n\tmanualNotes: \"manualNotes\",\n\tmanual_notes: \"manualNotes\",\n\tnotes: \"notes\",\n};\n\nconst CRITERION_REPORT_FIELDS = new Set([\"id\", \"status\", \"evidence\"]);\nconst COMMAND_REPORT_FIELDS = new Set([\"command\", \"result\", \"summary\"]);\n\nfunction normalizedToken(value: string): string {\n\treturn value\n\t\t.trim()\n\t\t.toLowerCase()\n\t\t.replace(/[\\s_]+/g, \"-\")\n\t\t.replace(/-+/g, \"-\");\n}\n\nfunction normalizeCriterionStatus(value: unknown): unknown {\n\tif (typeof value !== \"string\") return value;\n\tconst token = normalizedToken(value);\n\tif ([\"satisfied\", \"met\", \"complete\", \"completed\", \"done\", \"pass\", \"passed\", \"success\", \"succeeded\"].includes(token))\n\t\treturn \"satisfied\";\n\tif ([\"not-satisfied\", \"not-met\", \"unmet\", \"incomplete\", \"fail\", \"failed\"].includes(token)) return \"not-satisfied\";\n\tif ([\"not-applicable\", \"n-a\", \"na\", \"skip\", \"skipped\"].includes(token)) return \"not-applicable\";\n\treturn value;\n}\n\nfunction normalizeCommandResult(value: unknown): unknown {\n\tif (typeof value !== \"string\") return value;\n\tconst token = normalizedToken(value);\n\tif ([\"passed\", \"pass\", \"success\", \"successful\", \"succeeded\", \"ok\"].includes(token)) return \"passed\";\n\tif ([\"failed\", \"fail\", \"failure\", \"error\"].includes(token)) return \"failed\";\n\tif ([\"not-run\", \"not-executed\", \"skip\", \"skipped\"].includes(token)) return \"not-run\";\n\treturn value;\n}\n\nfunction normalizeCriterionReport(value: unknown, pathLabel: string, errors: string[]): unknown {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return value;\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!CRITERION_REPORT_FIELDS.has(key)) {\n\t\t\terrors.push(`${pathLabel}.${key}: unsupported acceptance criterion field`);\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized[key] =\n\t\t\tkey === \"id\" && typeof fieldValue === \"string\"\n\t\t\t\t? normalizedToken(fieldValue)\n\t\t\t\t: key === \"status\"\n\t\t\t\t\t? normalizeCriterionStatus(fieldValue)\n\t\t\t\t\t: fieldValue;\n\t}\n\treturn normalized;\n}\n\nfunction normalizeCommandReport(value: unknown, pathLabel: string, errors: string[]): unknown {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return value;\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!COMMAND_REPORT_FIELDS.has(key)) {\n\t\t\terrors.push(`${pathLabel}.${key}: unsupported acceptance command field`);\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized[key] = key === \"result\" ? normalizeCommandResult(fieldValue) : fieldValue;\n\t}\n\treturn normalized;\n}\n\nfunction normalizeAcceptanceReportValue(\n\tvalue: unknown,\n\tpathLabel = \"\",\n): { value: unknown; pathLabel: string; errors: string[] } {\n\tconst errors: string[] = [];\n\tlet reportValue = value;\n\tlet reportPath = pathLabel;\n\tif (reportValue && typeof reportValue === \"object\" && !Array.isArray(reportValue)) {\n\t\tconst record = reportValue as Record<string, unknown>;\n\t\tconst wrapperKeys = Object.keys(record).filter((key) => ACCEPTANCE_REPORT_WRAPPERS.has(key));\n\t\tif (wrapperKeys.length > 0) {\n\t\t\tconst wrapperKey = wrapperKeys[0]!;\n\t\t\tif (wrapperKeys.length > 1)\n\t\t\t\terrors.push(`${pathLabel || \"acceptance-report\"}: multiple acceptance report wrappers are ambiguous`);\n\t\t\tfor (const key of Object.keys(record)) {\n\t\t\t\tif (key !== wrapperKey)\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\t`${pathFor(pathLabel, key)}: unsupported alongside acceptance report wrapper '${wrapperKey}'`,\n\t\t\t\t\t);\n\t\t\t}\n\t\t\treportValue = record[wrapperKey];\n\t\t\treportPath = pathFor(pathLabel, wrapperKey);\n\t\t}\n\t}\n\tif (!reportValue || typeof reportValue !== \"object\" || Array.isArray(reportValue))\n\t\treturn { value: reportValue, pathLabel: reportPath, errors };\n\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(reportValue as Record<string, unknown>)) {\n\t\tconst canonical = ACCEPTANCE_REPORT_FIELDS[key];\n\t\tif (!canonical) {\n\t\t\terrors.push(`${pathFor(reportPath, key)}: unsupported acceptance report field`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (Object.hasOwn(normalized, canonical)) {\n\t\t\terrors.push(`${pathFor(reportPath, key)}: duplicates normalized field '${canonical}'`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst fieldPath = pathFor(reportPath, canonical);\n\t\tswitch (canonical) {\n\t\t\tcase \"criteriaSatisfied\": {\n\t\t\t\tconst items = Array.isArray(fieldValue)\n\t\t\t\t\t? fieldValue\n\t\t\t\t\t: fieldValue && typeof fieldValue === \"object\"\n\t\t\t\t\t\t? [fieldValue]\n\t\t\t\t\t\t: fieldValue;\n\t\t\t\tnormalized[canonical] = Array.isArray(items)\n\t\t\t\t\t? items.map((item, index) => normalizeCriterionReport(item, `${fieldPath}[${index}]`, errors))\n\t\t\t\t\t: items;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"commandsRun\": {\n\t\t\t\tconst items = Array.isArray(fieldValue)\n\t\t\t\t\t? fieldValue\n\t\t\t\t\t: fieldValue && typeof fieldValue === \"object\"\n\t\t\t\t\t\t? [fieldValue]\n\t\t\t\t\t\t: fieldValue;\n\t\t\t\tnormalized[canonical] = Array.isArray(items)\n\t\t\t\t\t? items.map((item, index) => normalizeCommandReport(item, `${fieldPath}[${index}]`, errors))\n\t\t\t\t\t: items;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"changedFiles\":\n\t\t\tcase \"testsAddedOrUpdated\":\n\t\t\tcase \"validationOutput\":\n\t\t\tcase \"residualRisks\":\n\t\t\tcase \"reviewFindings\":\n\t\t\t\tnormalized[canonical] = typeof fieldValue === \"string\" ? [fieldValue] : fieldValue;\n\t\t\t\tbreak;\n\t\t\tcase \"noStagedFiles\": {\n\t\t\t\tconst token = typeof fieldValue === \"string\" ? fieldValue.trim().toLowerCase() : undefined;\n\t\t\t\tnormalized[canonical] = token === \"true\" ? true : token === \"false\" ? false : fieldValue;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tnormalized[canonical] = fieldValue;\n\t\t}\n\t}\n\treturn { value: normalized, pathLabel: reportPath, errors };\n}\n\nfunction hasGenericAcceptanceReportSignal(value: unknown): boolean {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\treturn (\n\t\t\"criteriaSatisfied\" in record &&\n\t\t[\n\t\t\t\"changedFiles\",\n\t\t\t\"testsAddedOrUpdated\",\n\t\t\t\"commandsRun\",\n\t\t\t\"validationOutput\",\n\t\t\t\"residualRisks\",\n\t\t\t\"noStagedFiles\",\n\t\t\t\"diffSummary\",\n\t\t\t\"reviewFindings\",\n\t\t\t\"manualNotes\",\n\t\t].some((key) => key in record)\n\t);\n}\n\nfunction parseReportJson(body: string): unknown {\n\tconst trimmed = body.trim();\n\ttry {\n\t\treturn JSON.parse(trimmed) as unknown;\n\t} catch (error) {\n\t\tconst jsonStart = trimmed.indexOf(\"{\");\n\t\tif (jsonStart > 0) {\n\t\t\tconst json = extractBalancedJson(trimmed, jsonStart);\n\t\t\tif (json) return JSON.parse(json) as unknown;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nfunction fencedBlocks(output: string, tag: string): string[] {\n\treturn [...output.matchAll(new RegExp(`\\`\\`\\`${tag}\\\\s*\\\\n([\\\\s\\\\S]*?)\\`\\`\\``, \"gi\"))]\n\t\t.map((match) => match[1]?.trim())\n\t\t.filter((value): value is string => Boolean(value));\n}\n\nfunction parseAcceptanceReportBody(body: string): { report?: AcceptanceReport; errors: string[] } {\n\treturn validateAcceptanceReport(parseReportJson(body));\n}\n\nfunction parseUnterminatedAcceptanceReportFence(output: string): { report?: AcceptanceReport; error?: string } {\n\tconst opener = /```acceptance[-_]report\\b[^\\n]*\\n/gi.exec(output);\n\tif (!opener) return {};\n\tconst bodyStart = opener.index + opener[0].length;\n\tif (output.indexOf(\"```\", bodyStart) !== -1) return {};\n\ttry {\n\t\tconst validation = validateAcceptanceReport(JSON.parse(output.slice(bodyStart).trim()) as unknown);\n\t\treturn validation.report\n\t\t\t? { report: validation.report }\n\t\t\t: { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n\t} catch (error) {\n\t\treturn { error: `Failed to parse acceptance-report: ${error instanceof Error ? error.message : String(error)}` };\n\t}\n}\n\nfunction parseGenericJsonAcceptanceReportBody(body: string): { report?: AcceptanceReport; error?: string } {\n\tconst parsed = parseReportJson(body);\n\tconst normalized = normalizeAcceptanceReportValue(parsed);\n\tconst hasCriteriaMarker =\n\t\tnormalized.value !== null &&\n\t\ttypeof normalized.value === \"object\" &&\n\t\t!Array.isArray(normalized.value) &&\n\t\t\"criteriaSatisfied\" in normalized.value;\n\tif (!hasGenericAcceptanceReportSignal(normalized.value) && !(hasCriteriaMarker && normalized.errors.length > 0))\n\t\treturn {};\n\tconst validation = validateAcceptanceReport(parsed);\n\treturn validation.report\n\t\t? { report: validation.report }\n\t\t: { error: `Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n}\n\nexport const ACCEPTANCE_REPORT_NOT_FOUND = \"Structured acceptance report not found.\";\n\nexport function parseAcceptanceReport(output: string): { report?: AcceptanceReport; error?: string } {\n\tconst explicitFencePresent = /```acceptance[-_]report\\b/i.test(output);\n\tconst fenced = fencedBlocks(output, \"acceptance[-_]report\");\n\tconst parseErrors: string[] = [];\n\tfor (const body of fenced) {\n\t\ttry {\n\t\t\tconst validation = parseAcceptanceReportBody(body);\n\t\t\tif (validation.report) return { report: validation.report };\n\t\t\tparseErrors.push(`Invalid acceptance-report: ${validation.errors.join(\"; \")}`);\n\t\t} catch (error) {\n\t\t\tparseErrors.push(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\tif (parseErrors.length > 0) return { error: `Failed to parse acceptance-report: ${parseErrors.join(\"; \")}` };\n\tif (explicitFencePresent) {\n\t\tconst recovered = parseUnterminatedAcceptanceReportFence(output);\n\t\tif (recovered.report || recovered.error) return recovered;\n\t\treturn { error: \"Failed to parse acceptance-report: Empty or unterminated acceptance-report fence.\" };\n\t}\n\tfor (const body of fencedBlocks(output, \"(?:json|jsonc|json5)\")) {\n\t\ttry {\n\t\t\tconst parsed = parseGenericJsonAcceptanceReportBody(body);\n\t\t\tif (parsed.report) return { report: parsed.report };\n\t\t\tif (parsed.error) return { error: `Failed to parse acceptance-report: ${parsed.error}` };\n\t\t} catch {\n\t\t\t// Ignore unrelated malformed generic JSON. A recognizable report shape\n\t\t\t// returns exact validation errors above instead of being mistaken for prose.\n\t\t}\n\t}\n\tconst markerIndex = output.search(/ACCEPTANCE_REPORT\\s*:/i);\n\tif (markerIndex !== -1) {\n\t\tconst jsonStart = output.indexOf(\"{\", markerIndex);\n\t\tif (jsonStart === -1) {\n\t\t\treturn { error: \"Failed to parse acceptance-report: Expected a JSON object after ACCEPTANCE_REPORT:.\" };\n\t\t}\n\t\tconst json = extractBalancedJson(output, jsonStart);\n\t\tif (!json) {\n\t\t\treturn { error: \"Failed to parse acceptance-report: Unterminated JSON object after ACCEPTANCE_REPORT:.\" };\n\t\t}\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(json) as unknown;\n\t\t\tconst validation = validateAcceptanceReport(parsed);\n\t\t\tif (validation.report) return { report: validation.report };\n\t\t\treturn {\n\t\t\t\terror: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join(\"; \")}`,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn { error: `Failed to parse acceptance-report: ${message}` };\n\t\t}\n\t}\n\treturn { error: ACCEPTANCE_REPORT_NOT_FOUND };\n}\n\nfunction parseAcceptanceReportSources(\n\toutput: string,\n\tfileOutput: { content: string; path: string; authoritative?: boolean } | undefined,\n): { report?: AcceptanceReport; error?: string } {\n\tconst fromText = () => parseAcceptanceReport(output);\n\tconst fromFile = () => {\n\t\tif (!fileOutput) return { error: ACCEPTANCE_REPORT_NOT_FOUND };\n\t\tconst parsed = parseAcceptanceReport(fileOutput.content);\n\t\treturn parsed.report || parsed.error === ACCEPTANCE_REPORT_NOT_FOUND\n\t\t\t? parsed\n\t\t\t: { error: `${parsed.error} (in configured output ${fileOutput.path})` };\n\t};\n\tconst [primary, secondary] = fileOutput?.authoritative ? [fromFile, fromText] : [fromText, fromFile];\n\tconst first = primary();\n\t// A malformed report in the primary source is a defect to surface, not a\n\t// miss to paper over with the secondary source; only a genuinely absent\n\t// report falls through.\n\tif (first.report || first.error !== ACCEPTANCE_REPORT_NOT_FOUND) return first;\n\treturn secondary();\n}\n\nexport function stripAcceptanceReport(output: string): string {\n\tconst trailingFencePattern = /\\n?```(acceptance[-_]report|json|jsonc|json5)\\s*\\n([\\s\\S]*?)```\\s*/gi;\n\tlet trailingFence: { index: number; tag: string; body: string } | undefined;\n\tfor (const match of output.matchAll(trailingFencePattern)) {\n\t\tconst end = (match.index ?? 0) + match[0].length;\n\t\tif (output.slice(end).trim().length === 0 && match[1] && match[2]) {\n\t\t\ttrailingFence = { index: match.index ?? 0, tag: match[1].toLowerCase(), body: match[2] };\n\t\t}\n\t}\n\tif (trailingFence) {\n\t\tif (trailingFence.tag === \"acceptance-report\" || trailingFence.tag === \"acceptance_report\")\n\t\t\treturn output.slice(0, trailingFence.index).trimEnd();\n\t\ttry {\n\t\t\tif (parseGenericJsonAcceptanceReportBody(trailingFence.body).report)\n\t\t\t\treturn output.slice(0, trailingFence.index).trimEnd();\n\t\t} catch {\n\t\t\t// Leave unrelated or malformed generic JSON fences visible.\n\t\t}\n\t}\n\treturn output\n\t\t.replace(/\\n?```acceptance[-_]report\\s*\\n[\\s\\S]*?```\\s*$/i, \"\")\n\t\t.replace(/\\n?ACCEPTANCE_REPORT\\s*:\\s*\\{[\\s\\S]*\\}\\s*$/i, \"\")\n\t\t.trimEnd();\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n\treturn Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction pathFor(base: string, segment: string): string {\n\treturn base ? `${base}.${segment}` : segment;\n}\n\nfunction describeValidationValue(value: unknown): string {\n\tif (value === undefined) return \"missing\";\n\tif (value === null) return \"null\";\n\tif (Array.isArray(value)) return \"array\";\n\tif (typeof value === \"object\") return \"object\";\n\tif (typeof value === \"string\") {\n\t\tconst short = value.length > 80 ? `${value.slice(0, 77)}...` : value;\n\t\treturn JSON.stringify(short);\n\t}\n\treturn `${typeof value} ${String(value)}`;\n}\n\nfunction pushTypeError(errors: string[], pathLabel: string, expected: string, value: unknown): void {\n\terrors.push(`${pathLabel}: expected ${expected}; got ${describeValidationValue(value)}`);\n}\n\nfunction validateStringArrayField(errors: string[], value: unknown, pathLabel: string): void {\n\tif (!Array.isArray(value)) {\n\t\tpushTypeError(errors, pathLabel, \"string[]\", value);\n\t\treturn;\n\t}\n\tfor (const [index, item] of value.entries()) {\n\t\tif (typeof item !== \"string\" || !item.trim())\n\t\t\tpushTypeError(errors, `${pathLabel}[${index}]`, \"non-empty string\", item);\n\t}\n}\n\nfunction validateAcceptanceReport(value: unknown, pathLabel = \"\"): { report?: AcceptanceReport; errors: string[] } {\n\tconst normalized = normalizeAcceptanceReportValue(value, pathLabel);\n\tvalue = normalized.value;\n\tpathLabel = normalized.pathLabel;\n\tconst errors = normalized.errors;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tpushTypeError(errors, pathLabel || \"acceptance-report\", \"object\", value);\n\t\treturn { errors };\n\t}\n\tconst report = value as AcceptanceReport;\n\tif (report.criteriaSatisfied !== undefined) {\n\t\tif (!Array.isArray(report.criteriaSatisfied)) {\n\t\t\tpushTypeError(errors, pathFor(pathLabel, \"criteriaSatisfied\"), \"array\", report.criteriaSatisfied);\n\t\t} else {\n\t\t\tconst criterionIds = new Set<string>();\n\t\t\tfor (const [index, item] of report.criteriaSatisfied.entries()) {\n\t\t\t\tconst itemPath = `${pathFor(pathLabel, \"criteriaSatisfied\")}[${index}]`;\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\tpushTypeError(errors, itemPath, \"object\", item);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst criterion = item as { id?: unknown; status?: unknown; evidence?: unknown };\n\t\t\t\tif (criterion.id !== undefined && typeof criterion.id !== \"string\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.id`, \"string\", criterion.id);\n\t\t\t\t} else if (typeof criterion.id === \"string\" && criterion.id) {\n\t\t\t\t\tif (criterionIds.has(criterion.id))\n\t\t\t\t\t\terrors.push(`${itemPath}.id: duplicate normalized criterion id '${criterion.id}'`);\n\t\t\t\t\tcriterionIds.add(criterion.id);\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tcriterion.status !== \"satisfied\" &&\n\t\t\t\t\tcriterion.status !== \"not-satisfied\" &&\n\t\t\t\t\tcriterion.status !== \"not-applicable\"\n\t\t\t\t) {\n\t\t\t\t\tpushTypeError(\n\t\t\t\t\t\terrors,\n\t\t\t\t\t\t`${itemPath}.status`,\n\t\t\t\t\t\t'one of \"satisfied\", \"not-satisfied\", \"not-applicable\"',\n\t\t\t\t\t\tcriterion.status,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (typeof criterion.evidence !== \"string\" || !criterion.evidence.trim())\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.evidence`, \"non-empty string\", criterion.evidence);\n\t\t\t}\n\t\t}\n\t}\n\tif (report.changedFiles !== undefined)\n\t\tvalidateStringArrayField(errors, report.changedFiles, pathFor(pathLabel, \"changedFiles\"));\n\tif (report.testsAddedOrUpdated !== undefined)\n\t\tvalidateStringArrayField(errors, report.testsAddedOrUpdated, pathFor(pathLabel, \"testsAddedOrUpdated\"));\n\tif (report.commandsRun !== undefined) {\n\t\tif (!Array.isArray(report.commandsRun)) {\n\t\t\tpushTypeError(errors, pathFor(pathLabel, \"commandsRun\"), \"array\", report.commandsRun);\n\t\t} else {\n\t\t\tfor (const [index, item] of report.commandsRun.entries()) {\n\t\t\t\tconst itemPath = `${pathFor(pathLabel, \"commandsRun\")}[${index}]`;\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\tpushTypeError(errors, itemPath, \"object\", item);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst command = item as { command?: unknown; result?: unknown; summary?: unknown };\n\t\t\t\tif (typeof command.command !== \"string\" || !command.command.trim())\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.command`, \"non-empty string\", command.command);\n\t\t\t\tif (command.result !== \"passed\" && command.result !== \"failed\" && command.result !== \"not-run\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.result`, 'one of \"passed\", \"failed\", \"not-run\"', command.result);\n\t\t\t\t}\n\t\t\t\tif (typeof command.summary !== \"string\" || !command.summary.trim())\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.summary`, \"non-empty string\", command.summary);\n\t\t\t}\n\t\t}\n\t}\n\tif (report.validationOutput !== undefined)\n\t\tvalidateStringArrayField(errors, report.validationOutput, pathFor(pathLabel, \"validationOutput\"));\n\tif (report.residualRisks !== undefined)\n\t\tvalidateStringArrayField(errors, report.residualRisks, pathFor(pathLabel, \"residualRisks\"));\n\tif (report.noStagedFiles !== undefined && typeof report.noStagedFiles !== \"boolean\")\n\t\tpushTypeError(errors, pathFor(pathLabel, \"noStagedFiles\"), \"boolean\", report.noStagedFiles);\n\tif (report.diffSummary !== undefined && (typeof report.diffSummary !== \"string\" || !report.diffSummary.trim()))\n\t\tpushTypeError(errors, pathFor(pathLabel, \"diffSummary\"), \"non-empty string\", report.diffSummary);\n\tif (report.reviewFindings !== undefined)\n\t\tvalidateStringArrayField(errors, report.reviewFindings, pathFor(pathLabel, \"reviewFindings\"));\n\tif (report.manualNotes !== undefined && typeof report.manualNotes !== \"string\")\n\t\tpushTypeError(errors, pathFor(pathLabel, \"manualNotes\"), \"string\", report.manualNotes);\n\tif (report.notes !== undefined && typeof report.notes !== \"string\")\n\t\tpushTypeError(errors, pathFor(pathLabel, \"notes\"), \"string\", report.notes);\n\tif (errors.length > 0) return { errors };\n\tconst hasReportField =\n\t\treport.criteriaSatisfied !== undefined ||\n\t\treport.changedFiles !== undefined ||\n\t\treport.testsAddedOrUpdated !== undefined ||\n\t\treport.commandsRun !== undefined ||\n\t\treport.validationOutput !== undefined ||\n\t\treport.residualRisks !== undefined ||\n\t\treport.noStagedFiles !== undefined ||\n\t\treport.diffSummary !== undefined ||\n\t\treport.manualNotes !== undefined ||\n\t\treport.notes !== undefined ||\n\t\treport.reviewFindings !== undefined;\n\treturn hasReportField\n\t\t? { report, errors }\n\t\t: { errors: [`${pathLabel || \"acceptance-report\"}: expected at least one acceptance report field`] };\n}\n\nfunction checkCriteriaSatisfied(\n\tcriteria: ResolvedAcceptanceGate[],\n\treport: AcceptanceReport,\n): AcceptanceRuntimeCheck[] {\n\tconst reports = new Map(\n\t\t(report.criteriaSatisfied ?? []).filter((item) => item.id).map((item) => [normalizedToken(item.id!), item]),\n\t);\n\treturn criteria\n\t\t.filter((criterion) => criterion.severity !== \"recommended\")\n\t\t.map((criterion) => {\n\t\t\tconst item = reports.get(normalizedToken(criterion.id));\n\t\t\tif (!item)\n\t\t\t\treturn {\n\t\t\t\t\tid: `criterion:${criterion.id}`,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\tmessage: `Required criterion '${criterion.id}' was not reported.`,\n\t\t\t\t};\n\t\t\tif (item.status !== \"satisfied\")\n\t\t\t\treturn {\n\t\t\t\t\tid: `criterion:${criterion.id}`,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\tmessage: `Required criterion '${criterion.id}' was reported as ${item.status}.`,\n\t\t\t\t};\n\t\t\treturn {\n\t\t\t\tid: `criterion:${criterion.id}`,\n\t\t\t\tstatus: \"passed\",\n\t\t\t\tmessage: `Required criterion '${criterion.id}' satisfied.`,\n\t\t\t};\n\t\t});\n}\n\nfunction reportEvidenceStatus(report: AcceptanceReport, kind: AcceptanceEvidenceKind): AcceptanceRuntimeCheckStatus {\n\tswitch (kind) {\n\t\tcase \"changed-files\":\n\t\t\tif (!isStringArray(report.changedFiles)) return \"failed\";\n\t\t\treturn report.changedFiles.length === 0 ? \"not-applicable\" : \"passed\";\n\t\tcase \"tests-added\":\n\t\t\tif (!isStringArray(report.testsAddedOrUpdated)) return \"failed\";\n\t\t\treturn report.testsAddedOrUpdated.length === 0 ? \"not-applicable\" : \"passed\";\n\t\tcase \"commands-run\":\n\t\t\treturn Array.isArray(report.commandsRun) && report.commandsRun.length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"validation-output\":\n\t\t\treturn isStringArray(report.validationOutput) && report.validationOutput.length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"residual-risks\":\n\t\t\treturn isStringArray(report.residualRisks) ? \"passed\" : \"failed\";\n\t\tcase \"no-staged-files\":\n\t\t\treturn report.noStagedFiles === true ? \"passed\" : \"failed\";\n\t\tcase \"diff-summary\":\n\t\t\treturn typeof report.diffSummary === \"string\" && report.diffSummary.trim().length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"review-findings\":\n\t\t\treturn isStringArray(report.reviewFindings) ? \"passed\" : \"failed\";\n\t\tcase \"manual-notes\":\n\t\t\treturn (report.manualNotes ?? report.notes)?.trim() ? \"passed\" : \"failed\";\n\t}\n}\n\nfunction checkNoStagedFiles(cwd: string): AcceptanceRuntimeCheck {\n\tconst result = spawnSync(\"git\", [\"status\", \"--short\"], { cwd, encoding: \"utf-8\" });\n\tif (result.status !== 0) {\n\t\treturn {\n\t\t\tid: \"no-staged-files\",\n\t\t\tstatus: \"not-applicable\",\n\t\t\tmessage: \"git status unavailable; no staged-files check skipped\",\n\t\t};\n\t}\n\tconst staged = result.stdout.split(/\\r?\\n/).filter((line) => line.length >= 2 && line[0] !== \" \" && line[0] !== \"?\");\n\treturn staged.length === 0\n\t\t? { id: \"no-staged-files\", status: \"passed\", message: \"No staged files detected.\" }\n\t\t: { id: \"no-staged-files\", status: \"failed\", message: `Staged files present: ${staged.join(\", \")}` };\n}\n\nfunction runStructuralChecks(\n\tacceptance: ResolvedAcceptanceConfig,\n\treport: AcceptanceReport,\n\tcwd: string,\n): AcceptanceRuntimeCheck[] {\n\tconst checks: AcceptanceRuntimeCheck[] = [];\n\tfor (const kind of acceptance.evidence) {\n\t\tconst status = reportEvidenceStatus(report, kind);\n\t\tchecks.push({\n\t\t\tid: `evidence:${kind}`,\n\t\t\tstatus,\n\t\t\tmessage:\n\t\t\t\tstatus === \"passed\"\n\t\t\t\t\t? `${kind} evidence present.`\n\t\t\t\t\t: status === \"not-applicable\"\n\t\t\t\t\t\t? `${kind} evidence explicitly reported as not applicable.`\n\t\t\t\t\t\t: `${kind} evidence missing from child report.`,\n\t\t});\n\t}\n\tif (acceptance.evidence.includes(\"no-staged-files\")) checks.push(checkNoStagedFiles(cwd));\n\treturn checks;\n}\n\nfunction trimOutput(value: string): string | undefined {\n\tconst trimmed = value.trim();\n\tif (!trimmed) return undefined;\n\treturn trimmed.length > 12_000 ? `${trimmed.slice(0, 12_000)}\\n...[truncated]` : trimmed;\n}\n\nconst SENSITIVE_ENV_KEY_PATTERN =\n\t/(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASS|AUTH|CREDENTIAL|COOKIE|SESSION|PRIVATE|API_KEY|ACCESS_KEY)(?:_|$)/i;\n\nfunction effectiveVerifyEnv(env: Record<string, string> | undefined): Record<string, string> {\n\tconst inherited = Object.fromEntries(\n\t\tObject.entries(process.env).flatMap(([key, value]) => {\n\t\t\treturn typeof value === \"string\" ? [[key, value]] : [];\n\t\t}),\n\t);\n\treturn { ...inherited, ...(env ?? {}) };\n}\n\nfunction verifyRedactionEnv(env: Record<string, string> | undefined): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(effectiveVerifyEnv(env)).filter(([key, value]) => {\n\t\t\treturn value.length >= 4 && SENSITIVE_ENV_KEY_PATTERN.test(key);\n\t\t}),\n\t);\n}\n\nfunction redactVerifyEnv(value: string, env: Record<string, string> | undefined): string {\n\tlet redacted = value;\n\tconst secrets = [...new Set(Object.values(verifyRedactionEnv(env)).filter(Boolean))].sort(\n\t\t(left, right) => right.length - left.length,\n\t);\n\tfor (const secret of secrets) redacted = redacted.replaceAll(secret, \"[REDACTED]\");\n\treturn redacted;\n}\n\nfunction uniqueStrings(items: Array<string | undefined>): string[] {\n\treturn unique(items.map((item) => item?.trim()).filter((item): item is string => Boolean(item)));\n}\n\nexport function aggregateAcceptanceReport(input: {\n\tresults: Array<Pick<SingleResult, \"agent\" | \"acceptance\" | \"error\"> & { exitCode: number | null }>;\n\tnotes?: string;\n}): AcceptanceReport {\n\tconst childReports = input.results\n\t\t.map((result) => result.acceptance?.childReport)\n\t\t.filter((report): report is AcceptanceReport => Boolean(report));\n\tconst blockers = input.results.filter((result) => result.exitCode !== 0 || result.acceptance?.status === \"rejected\");\n\tconst successfulChildren = input.results.length > 0 && blockers.length === 0;\n\treturn {\n\t\tcriteriaSatisfied: [\n\t\t\t{\n\t\t\t\tid: \"criterion-1\",\n\t\t\t\tstatus: successfulChildren ? \"satisfied\" : \"not-satisfied\",\n\t\t\t\tevidence: successfulChildren\n\t\t\t\t\t? `All ${input.results.length} dynamic child run(s) completed without child or acceptance blockers.`\n\t\t\t\t\t: \"Dynamic fanout produced no accepted child evidence.\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"criterion-2\",\n\t\t\t\tstatus: successfulChildren ? \"satisfied\" : \"not-satisfied\",\n\t\t\t\tevidence: successfulChildren\n\t\t\t\t\t? \"Collected child acceptance evidence for aggregate review.\"\n\t\t\t\t\t: \"Dynamic fanout produced no aggregate review evidence.\",\n\t\t\t},\n\t\t\t...input.results.map(\n\t\t\t\t(\n\t\t\t\t\tresult,\n\t\t\t\t\tindex,\n\t\t\t\t): { id?: string; status: \"satisfied\" | \"not-satisfied\" | \"not-applicable\"; evidence: string } => ({\n\t\t\t\t\tid: `child-${index + 1}`,\n\t\t\t\t\tstatus:\n\t\t\t\t\t\tresult.exitCode === 0 && result.acceptance?.status !== \"rejected\" ? \"satisfied\" : \"not-satisfied\",\n\t\t\t\t\tevidence: `${result.agent}: acceptance ${result.acceptance?.status ?? \"unreported\"}${result.error ? ` (${result.error})` : \"\"}`,\n\t\t\t\t}),\n\t\t\t),\n\t\t],\n\t\tchangedFiles: uniqueStrings(childReports.flatMap((report) => report.changedFiles ?? [])),\n\t\ttestsAddedOrUpdated: uniqueStrings(childReports.flatMap((report) => report.testsAddedOrUpdated ?? [])),\n\t\tcommandsRun: childReports.flatMap((report) => report.commandsRun ?? []),\n\t\tvalidationOutput: uniqueStrings(childReports.flatMap((report) => report.validationOutput ?? [])),\n\t\tresidualRisks: uniqueStrings([\n\t\t\t...childReports.flatMap((report) => report.residualRisks ?? []),\n\t\t\t...blockers.map((result) => `${result.agent}: ${result.error ?? \"child or acceptance gate failed\"}`),\n\t\t]),\n\t\tnoStagedFiles: childReports.length > 0 && childReports.every((report) => report.noStagedFiles === true),\n\t\treviewFindings: uniqueStrings(childReports.flatMap((report) => report.reviewFindings ?? [])),\n\t\tmanualNotes:\n\t\t\tinput.notes ?? `Aggregated acceptance evidence from ${input.results.length} dynamic fanout child run(s).`,\n\t\tnotes: input.notes,\n\t};\n}\n\nconst DEFAULT_VERIFY_TIMEOUT_MS = 120_000;\n\nfunction hash(value: string): string {\n\treturn createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\ninterface VerifyWorkspaceState {\n\tkind: \"git-tracked\";\n\trepoRoot: string;\n\tcwdRelative: string;\n\thead: string;\n\tdiffHash: string;\n}\n\nfunction readVerifyWorkspaceState(cwd: string): VerifyWorkspaceState | undefined {\n\tconst repo = spawnSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], { cwd, encoding: \"utf-8\" });\n\tif (repo.status !== 0 || !repo.stdout.trim()) return undefined;\n\tconst repoRoot = fs.realpathSync(repo.stdout.trim());\n\tconst head = spawnSync(\"git\", [\"rev-parse\", \"HEAD\"], { cwd: repoRoot, encoding: \"utf-8\" });\n\tconst diff = spawnSync(\"git\", [\"diff\", \"--binary\", \"--full-index\", \"HEAD\", \"--\"], {\n\t\tcwd: repoRoot,\n\t\tencoding: \"utf-8\",\n\t\tmaxBuffer: 50 * 1024 * 1024,\n\t});\n\tif (head.status !== 0 || diff.status !== 0 || !head.stdout.trim()) return undefined;\n\treturn {\n\t\tkind: \"git-tracked\",\n\t\trepoRoot,\n\t\tcwdRelative: path.relative(repoRoot, fs.realpathSync(cwd)) || \".\",\n\t\thead: head.stdout.trim(),\n\t\tdiffHash: hash(diff.stdout),\n\t};\n}\n\nfunction isCachedVerifyResult(value: unknown): value is AcceptanceVerifyResult {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst result = value as Partial<AcceptanceVerifyResult>;\n\treturn (\n\t\ttypeof result.id === \"string\" &&\n\t\ttypeof result.command === \"string\" &&\n\t\t(typeof result.exitCode === \"number\" || result.exitCode === null) &&\n\t\t(result.status === \"passed\" ||\n\t\t\tresult.status === \"failed\" ||\n\t\t\tresult.status === \"timed-out\" ||\n\t\t\tresult.status === \"allowed-failure\") &&\n\t\ttypeof result.durationMs === \"number\"\n\t);\n}\n\nasync function runMemoizedVerifyCommand(\n\tcommand: AcceptanceVerifyCommand,\n\tdefaultCwd: string,\n\toptions: {\n\t\tsignal?: AbortSignal;\n\t\tabortMessage?: string;\n\t\tartifactsDir?: string;\n\t\trunId?: string;\n\t} = {},\n): Promise<AcceptanceVerifyResult> {\n\tconst cwd = command.cwd ? path.resolve(defaultCwd, command.cwd) : defaultCwd;\n\tlet workspaceState: VerifyWorkspaceState | undefined;\n\ttry {\n\t\tworkspaceState = readVerifyWorkspaceState(cwd);\n\t} catch {\n\t\tworkspaceState = undefined;\n\t}\n\tif (!workspaceState || !options.artifactsDir || !options.runId) {\n\t\treturn runVerifyCommand(command, defaultCwd, options);\n\t}\n\tconst envKeys = Object.keys(command.env ?? {}).sort();\n\tconst envHash = hash(\n\t\tJSON.stringify(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.entries(effectiveVerifyEnv(command.env)).sort(([left], [right]) => left.localeCompare(right)),\n\t\t\t),\n\t\t),\n\t);\n\tconst timeoutMs = command.timeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS;\n\tconst cacheKey = hash(\n\t\tJSON.stringify({\n\t\t\tversion: 1,\n\t\t\tcommand: command.command,\n\t\t\tcwdRelative: workspaceState.cwdRelative,\n\t\t\tenvKeys,\n\t\t\tenvHash,\n\t\t\ttimeoutMs,\n\t\t\tallowFailure: command.allowFailure === true,\n\t\t\thead: workspaceState.head,\n\t\t\tdiffHash: workspaceState.diffHash,\n\t\t}),\n\t);\n\tconst artifactPath = path.join(options.artifactsDir, \"acceptance\", \"verify\", options.runId, `${cacheKey}.json`);\n\ttry {\n\t\tconst cached = JSON.parse(fs.readFileSync(artifactPath, \"utf-8\")) as { cacheKey?: unknown; result?: unknown };\n\t\tif (cached.cacheKey === cacheKey && isCachedVerifyResult(cached.result)) {\n\t\t\treturn {\n\t\t\t\t...cached.result,\n\t\t\t\tid: command.id,\n\t\t\t\tcommand: command.command,\n\t\t\t\tcwd,\n\t\t\t\tartifactPath,\n\t\t\t\tcacheKey,\n\t\t\t\tmemoized: true,\n\t\t\t\tenvKeys,\n\t\t\t\tenvHash,\n\t\t\t\tworkspaceState,\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// A cache miss or unreadable artifact must not prevent host verification.\n\t}\n\tconst result = await runVerifyCommand(command, defaultCwd, options);\n\tconst evidenced: AcceptanceVerifyResult = {\n\t\t...result,\n\t\tartifactPath,\n\t\tcacheKey,\n\t\tmemoized: false,\n\t\tenvKeys,\n\t\tenvHash,\n\t\tworkspaceState,\n\t};\n\ttry {\n\t\tfs.mkdirSync(path.dirname(artifactPath), { recursive: true });\n\t\tfs.writeFileSync(\n\t\t\tartifactPath,\n\t\t\tJSON.stringify(\n\t\t\t\t{\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tcacheKey,\n\t\t\t\t\tcommand: command.command,\n\t\t\t\t\tcwdRelative: workspaceState.cwdRelative,\n\t\t\t\t\tenvKeys,\n\t\t\t\t\tenvHash,\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t\tallowFailure: command.allowFailure === true,\n\t\t\t\t\tworkspaceState,\n\t\t\t\t\tresult: evidenced,\n\t\t\t\t},\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t),\n\t\t\t\"utf-8\",\n\t\t);\n\t} catch (error) {\n\t\tevidenced.artifactError = error instanceof Error ? error.message : String(error);\n\t\tdelete evidenced.artifactPath;\n\t}\n\treturn evidenced;\n}\n\nfunction runVerifyCommand(\n\tcommand: AcceptanceVerifyCommand,\n\tdefaultCwd: string,\n\toptions: { signal?: AbortSignal; abortMessage?: string } = {},\n): Promise<AcceptanceVerifyResult> {\n\treturn new Promise((resolve) => {\n\t\tconst startedAt = Date.now();\n\t\tconst cwd = command.cwd ? path.resolve(defaultCwd, command.cwd) : defaultCwd;\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet timedOut = false;\n\t\tlet settled = false;\n\t\tlet hardKill: NodeJS.Timeout | undefined;\n\t\tconst child = spawn(command.command, {\n\t\t\tcwd,\n\t\t\tenv: effectiveVerifyEnv(command.env),\n\t\t\tshell: true,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst finish = (result: Omit<AcceptanceVerifyResult, \"id\" | \"command\" | \"cwd\" | \"durationMs\">) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\tif (hardKill) clearTimeout(hardKill);\n\t\t\toptions.signal?.removeEventListener(\"abort\", abortVerification);\n\t\t\tresolve({\n\t\t\t\tid: command.id,\n\t\t\t\tcommand: command.command,\n\t\t\t\tcwd,\n\t\t\t\tdurationMs: Date.now() - startedAt,\n\t\t\t\t...result,\n\t\t\t});\n\t\t};\n\t\tconst abortVerification = () => {\n\t\t\tif (settled || timedOut) return;\n\t\t\ttimedOut = true;\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\thardKill = setTimeout(() => {\n\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\tfinish({\n\t\t\t\t\texitCode: null,\n\t\t\t\t\tstatus: \"timed-out\",\n\t\t\t\t\tstdout: trimOutput(redactVerifyEnv(stdout, command.env)),\n\t\t\t\t\tstderr: trimOutput(\n\t\t\t\t\t\tredactVerifyEnv(stderr || options.abortMessage || \"Acceptance verification timed out.\", command.env),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}, 1000);\n\t\t\thardKill.unref?.();\n\t\t};\n\t\tconst timeout = setTimeout(abortVerification, command.timeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS);\n\t\ttimeout.unref?.();\n\t\tif (options.signal?.aborted) abortVerification();\n\t\telse options.signal?.addEventListener(\"abort\", abortVerification, { once: true });\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tchild.on(\"close\", (exitCode) => {\n\t\t\tconst passed = exitCode === 0 && !timedOut;\n\t\t\tfinish({\n\t\t\t\texitCode,\n\t\t\t\tstatus: timedOut ? \"timed-out\" : passed ? \"passed\" : command.allowFailure ? \"allowed-failure\" : \"failed\",\n\t\t\t\tstdout: trimOutput(redactVerifyEnv(stdout, command.env)),\n\t\t\t\tstderr: trimOutput(redactVerifyEnv(stderr || (timedOut ? (options.abortMessage ?? \"\") : \"\"), command.env)),\n\t\t\t});\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tfinish({\n\t\t\t\texitCode: timedOut ? null : 1,\n\t\t\t\tstatus: timedOut ? \"timed-out\" : command.allowFailure ? \"allowed-failure\" : \"failed\",\n\t\t\t\tstderr: timedOut\n\t\t\t\t\t? trimOutput(\n\t\t\t\t\t\t\tredactVerifyEnv(\n\t\t\t\t\t\t\t\tstderr || options.abortMessage || \"Acceptance verification timed out.\",\n\t\t\t\t\t\t\t\tcommand.env,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t: redactVerifyEnv(error instanceof Error ? error.message : String(error), command.env),\n\t\t\t});\n\t\t});\n\t});\n}\n\nexport async function evaluateAcceptance(input: {\n\tacceptance: ResolvedAcceptanceConfig;\n\toutput: string;\n\tcwd: string;\n\t/**\n\t * Content the child sent to its configured output file (from its own write\n\t * tool calls, not from disk, so a concurrent writer to the same path cannot\n\t * be misattributed). Searched for the acceptance report; searched before\n\t * the assistant output when `authoritative` (outputMode \"file-only\").\n\t */\n\tfileOutput?: { content: string; path: string; authoritative?: boolean };\n\treport?: AcceptanceReport;\n\treviewResult?: AcceptanceReviewResult;\n\tsignal?: AbortSignal;\n\tabortMessage?: string;\n\treportOptional?: boolean;\n\tartifactsDir?: string;\n\trunId?: string;\n}): Promise<AcceptanceLedger> {\n\tconst acceptance = input.acceptance;\n\tconst initialStatus = acceptance.level === \"none\" ? \"not-required\" : \"claimed\";\n\tconst ledger: AcceptanceLedger = {\n\t\tstatus: initialStatus,\n\t\tevidenceStatus: initialStatus,\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: [],\n\t\tverifyRuns: [],\n\t};\n\tif (acceptance.level === \"none\") return ledger;\n\n\tconst parsed = input.report\n\t\t? (() => {\n\t\t\t\tconst validation = validateAcceptanceReport(input.report);\n\t\t\t\treturn validation.report\n\t\t\t\t\t? { report: validation.report }\n\t\t\t\t\t: {\n\t\t\t\t\t\t\terror: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join(\"; \")}`,\n\t\t\t\t\t\t};\n\t\t\t})()\n\t\t: parseAcceptanceReportSources(input.output, input.fileOutput);\n\tconst needsReport = acceptanceRequiresChildReport(acceptance);\n\tif (parsed.report) {\n\t\tledger.childReport = parsed.report;\n\t\tledger.status = \"attested\";\n\t\tledger.evidenceStatus = \"attested\";\n\t} else if (!input.reportOptional || needsReport || parsed.error !== ACCEPTANCE_REPORT_NOT_FOUND) {\n\t\tledger.childReportParseError = parsed.error;\n\t\tledger.runtimeChecks.push({\n\t\t\tid: \"attestation\",\n\t\t\tstatus: \"failed\",\n\t\t\tmessage: parsed.error ?? \"Structured acceptance report missing.\",\n\t\t});\n\t\tif (!input.reportOptional) {\n\t\t\tledger.status = \"rejected\";\n\t\t\tledger.evidenceStatus = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t} else {\n\t\tledger.childReportParseError = parsed.error;\n\t}\n\n\tif (parsed.report && LEVEL_RANK[acceptance.level] >= LEVEL_RANK.checked) {\n\t\tledger.runtimeChecks = [\n\t\t\t...ledger.runtimeChecks,\n\t\t\t...checkCriteriaSatisfied(acceptance.criteria, parsed.report),\n\t\t\t...runStructuralChecks(acceptance, parsed.report, input.cwd),\n\t\t];\n\t\tif (!ledger.runtimeChecks.some((check) => check.status === \"failed\")) {\n\t\t\tledger.status = \"checked\";\n\t\t\tledger.evidenceStatus = \"checked\";\n\t\t}\n\t}\n\n\tif (\n\t\tLEVEL_RANK[acceptance.level] >= LEVEL_RANK.verified &&\n\t\t(acceptance.level === \"verified\" || acceptance.verify.length > 0)\n\t) {\n\t\tif (acceptance.level === \"verified\" && acceptance.verify.length === 0) {\n\t\t\tledger.runtimeChecks.push({\n\t\t\t\tid: \"verification-config\",\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tmessage: \"verified acceptance requires runtime verify commands.\",\n\t\t\t});\n\t\t\tledger.status = \"rejected\";\n\t\t\tledger.evidenceStatus = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tledger.verifyRuns = [];\n\t\tfor (const command of acceptance.verify) {\n\t\t\tledger.verifyRuns.push(\n\t\t\t\tawait runMemoizedVerifyCommand(command, input.cwd, {\n\t\t\t\t\tsignal: input.signal,\n\t\t\t\t\tabortMessage: input.abortMessage,\n\t\t\t\t\tartifactsDir: input.artifactsDir,\n\t\t\t\t\trunId: input.runId,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (input.signal?.aborted) break;\n\t\t}\n\t\tif (ledger.verifyRuns.some((run) => run.status === \"failed\" || run.status === \"timed-out\")) {\n\t\t\tledger.status = \"rejected\";\n\t\t\tledger.evidenceStatus = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tif (!ledger.runtimeChecks.some((check) => check.status === \"failed\")) {\n\t\t\tledger.status = \"verified\";\n\t\t\tledger.evidenceStatus = \"verified\";\n\t\t}\n\t}\n\n\tif (ledger.runtimeChecks.some((check) => check.status === \"failed\")) {\n\t\tledger.status = \"rejected\";\n\t\tledger.evidenceStatus = \"rejected\";\n\t\treturn ledger;\n\t}\n\tif (ledger.status === \"claimed\") {\n\t\tledger.status = acceptance.level === \"verified\" ? \"verified\" : acceptance.level;\n\t\tledger.evidenceStatus = ledger.status;\n\t}\n\n\tif (acceptance.review) {\n\t\tif (input.reviewResult?.status === \"reviewed\") {\n\t\t\tledger.reviewResult = input.reviewResult;\n\t\t\tledger.status = \"reviewed\";\n\t\t} else if (input.reviewResult?.status === \"blockers\") {\n\t\t\tledger.reviewResult = input.reviewResult;\n\t\t\tledger.status = \"rejected\";\n\t\t} else if (acceptance.review.required !== false) {\n\t\t\tledger.reviewResult = input.reviewResult ?? {\n\t\t\t\tstatus: \"review-required\",\n\t\t\t\tfindings: [\n\t\t\t\t\t{\n\t\t\t\t\t\tseverity: \"non-blocking\",\n\t\t\t\t\t\tissue: \"Independent review has not been supplied.\",\n\t\t\t\t\t\trationale: \"The run cannot be marked reviewed from child evidence alone.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t\tledger.status = \"review-required\";\n\t\t}\n\t}\n\n\treturn ledger;\n}\n\nexport function buildSkippedAcceptanceLedger(\n\tacceptance: ResolvedAcceptanceConfig,\n\tinput: { id: string; message: string },\n): AcceptanceLedger {\n\tconst status = acceptance.level === \"none\" ? \"not-required\" : \"rejected\";\n\treturn {\n\t\tstatus,\n\t\tevidenceStatus: status,\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: acceptance.level === \"none\" ? [] : [{ id: input.id, status: \"failed\", message: input.message }],\n\t\tverifyRuns: [],\n\t};\n}\n\nexport function acceptanceFailureMessage(ledger: AcceptanceLedger): string | undefined {\n\tif (ledger.status !== \"rejected\") return undefined;\n\tconst failedCheck = ledger.runtimeChecks.find((check) => check.status === \"failed\");\n\tif (failedCheck) return `Acceptance rejected: ${failedCheck.message}`;\n\tconst failedVerify = ledger.verifyRuns.find((run) => run.status === \"failed\" || run.status === \"timed-out\");\n\tif (failedVerify) return `Acceptance verification '${failedVerify.id}' ${failedVerify.status}.`;\n\tif (ledger.reviewResult?.status === \"blockers\") return \"Acceptance review found blockers.\";\n\treturn \"Acceptance rejected.\";\n}\n"]}