immune-brain 3.6.5 → 3.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +69 -3
  2. package/README.zh-CN.md +23 -3
  3. package/package.json +2 -1
  4. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +60 -21
  6. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +932 -0
  7. package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +38 -10
  8. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +200 -2
  9. package/plugins/immune-brain/dist/BASELINE.md +48 -15
  10. package/plugins/immune-brain/dist/claude/mcp-server.mjs +3415 -228
  11. package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
  12. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
  13. package/plugins/immune-brain/dist/imm-agent-doc-maintain.md +9 -1
  14. package/plugins/immune-brain/dist/imm-brainstorm.md +49 -35
  15. package/plugins/immune-brain/dist/imm-doc-prune.md +7 -1
  16. package/plugins/immune-brain/dist/imm-loop.md +45 -11
  17. package/plugins/immune-brain/dist/imm-planner.md +65 -28
  18. package/plugins/immune-brain/dist/imm-pr-fix.md +6 -2
  19. package/plugins/immune-brain/dist/role-prompts/code-review.md +9 -1
  20. package/plugins/immune-brain/dist/role-prompts/executor.md +18 -10
  21. package/plugins/immune-brain/dist/role-prompts/pr-fix.md +5 -2
  22. package/plugins/immune-brain/runtime/assurance/coordinator.ts +101 -3
  23. package/plugins/immune-brain/runtime/assurance/verification.ts +13 -2
  24. package/plugins/immune-brain/runtime/claude/interaction.ts +16 -3
  25. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +840 -17
  26. package/plugins/immune-brain/runtime/claude/mcp_server.ts +72 -10
  27. package/plugins/immune-brain/runtime/github_issue_tracker.ts +1018 -17
  28. package/plugins/immune-brain/runtime/kernel/canary_application.ts +16 -1
  29. package/plugins/immune-brain/runtime/kernel/completion.ts +19 -1
  30. package/plugins/immune-brain/runtime/kernel/reducer.ts +165 -12
  31. package/plugins/immune-brain/runtime/kernel/refutation.ts +82 -0
  32. package/plugins/immune-brain/runtime/kernel/types.ts +34 -1
  33. package/plugins/immune-brain/runtime/kernel/validation.ts +202 -9
  34. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  35. package/plugins/immune-brain/runtime/prompts/code-review.md +9 -1
  36. package/plugins/immune-brain/runtime/prompts/executor.md +18 -10
  37. package/plugins/immune-brain/runtime/prompts/pr-fix.md +5 -2
  38. package/plugins/immune-brain/runtime/unattended/batch_git.ts +775 -0
  39. package/plugins/immune-brain/runtime/unattended/batch_plan.ts +203 -0
  40. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +1224 -0
  41. package/plugins/immune-brain/runtime/unattended/batch_state.ts +360 -0
  42. package/plugins/immune-brain/runtime/unattended/types.ts +60 -0
  43. package/plugins/immune-brain/skills/BASELINE.md +48 -15
  44. package/plugins/immune-brain/skills/imm-agent-doc-maintain/SKILL.md +20 -4
  45. package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +24 -64
  46. package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +18 -3
  47. package/plugins/immune-brain/skills/imm-loop/SKILL.md +21 -6
  48. package/plugins/immune-brain/skills/imm-planner/SKILL.md +35 -8
  49. package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +17 -3
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // plugins/immune-brain/runtime/claude/mcp_server.ts
5
- import { randomUUID as randomUUID6 } from "node:crypto";
5
+ import { randomUUID as randomUUID8 } from "node:crypto";
6
6
  import { createInterface } from "node:readline";
7
7
  import { stdin, stdout } from "node:process";
8
8
 
@@ -42,7 +42,7 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
42
42
  }
43
43
 
44
44
  // plugins/immune-brain/runtime/plugin_version.ts
45
- var PLUGIN_VERSION = "3.6.5";
45
+ var PLUGIN_VERSION = "3.6.7";
46
46
 
47
47
  // plugins/immune-brain/runtime/claude/interaction.ts
48
48
  import { createHash, randomUUID } from "node:crypto";
@@ -50,7 +50,8 @@ var PRIVILEGED_OPERATIONS = [
50
50
  "enroll",
51
51
  "request_authorization",
52
52
  "approve_breaking_intent_revision",
53
- "stop"
53
+ "stop",
54
+ "start_unattended_batch"
54
55
  ];
55
56
  var RECOVERY_ACTIONS = {
56
57
  interaction_not_opened: "retry through a fresh native gate in the current Host",
@@ -91,7 +92,7 @@ function evaluateNativeGate(input) {
91
92
  return { ok: true };
92
93
  }
93
94
  function confirmationRef(input) {
94
- return `claude-confirm-${createHash("sha256").update(`${input.connectionId}\x00${input.toolCallId}\x00${input.requestId}\x00${input.operation}\x00${input.taskId}\x00${input.intentRevision ?? ""}\x00${input.intentContentHash ?? ""}\x00${input.bindingDigest ?? ""}`).digest("hex").slice(0, 16)}`;
95
+ return `claude-confirm-${createHash("sha256").update(`${input.connectionId}\x00${input.toolCallId}\x00${input.requestId}\x00${input.operation}\x00${input.taskId ?? ""}\x00${input.initiativeSlug ?? ""}\x00${input.intentRevision ?? ""}\x00${input.intentContentHash ?? ""}\x00${input.bindingDigest ?? ""}\x00${input.planDigest ?? ""}`).digest("hex").slice(0, 16)}`;
95
96
  }
96
97
  function enrollmentNonce() {
97
98
  return randomUUID();
@@ -678,17 +679,65 @@ function parseHookStdin(raw) {
678
679
  }
679
680
 
680
681
  // plugins/immune-brain/runtime/claude/kernel_ports.ts
681
- import { randomUUID as randomUUID5 } from "node:crypto";
682
- import { existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
683
- import { execFileSync as execFileSync4 } from "node:child_process";
684
- import { join as join7 } from "node:path";
682
+ import { randomUUID as randomUUID7 } from "node:crypto";
683
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
684
+ import { execFileSync as execFileSync4, spawnSync as spawnSync6 } from "node:child_process";
685
+ import { join as join10 } from "node:path";
685
686
 
686
687
  // plugins/immune-brain/runtime/assurance/coordinator.ts
687
- import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
688
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
688
689
 
689
- // plugins/immune-brain/runtime/assurance/verification.ts
690
+ // plugins/immune-brain/runtime/kernel/refutation.ts
690
691
  import { createHash as createHash3 } from "node:crypto";
691
- import { execFileSync, spawn } from "node:child_process";
692
+
693
+ // plugins/immune-brain/runtime/canonical_json.ts
694
+ function stableStringify(value) {
695
+ if (value === null || value === undefined)
696
+ return "null";
697
+ if (typeof value !== "object")
698
+ return JSON.stringify(value);
699
+ if (Array.isArray(value)) {
700
+ return `[${value.map(stableStringify).join(", ")}]`;
701
+ }
702
+ const obj = value;
703
+ return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}: ${stableStringify(obj[key])}`).join(", ")}}`;
704
+ }
705
+
706
+ // plugins/immune-brain/runtime/kernel/refutation.ts
707
+ function refutationIdentity(record, diffHash) {
708
+ return {
709
+ intent_revision: record.intent_snapshot.revision,
710
+ intent_content_hash: record.intent_ref.content_hash,
711
+ diff_hash: diffHash
712
+ };
713
+ }
714
+ function anchorForEvidence(evidence) {
715
+ return `sha256:${createHash3("sha256").update(stableStringify({
716
+ violated: evidence.violated,
717
+ caller_chain: evidence.caller_chain
718
+ })).digest("hex")}`;
719
+ }
720
+ function isFreshPassingQaAttestation(attestation, acceptanceId, identity) {
721
+ if (!attestation || attestation.kind !== "qa")
722
+ return false;
723
+ if (attestation.task_revision !== identity.intent_revision)
724
+ return false;
725
+ if (attestation.intent_content_hash !== identity.intent_content_hash)
726
+ return false;
727
+ if (attestation.diff_hash !== identity.diff_hash)
728
+ return false;
729
+ return attestation.acceptance_results.some((result) => result.acceptance_id === acceptanceId && result.status === "passed");
730
+ }
731
+ function refutationIsLive(finding, attestations, identity) {
732
+ const counterevidence = finding.counterevidence ?? null;
733
+ if (!counterevidence?.attestation_id || !counterevidence.acceptance_id)
734
+ return false;
735
+ return isFreshPassingQaAttestation(attestations.find((item) => item.id === counterevidence.attestation_id), counterevidence.acceptance_id, identity);
736
+ }
737
+
738
+ // plugins/immune-brain/runtime/assurance/verification.ts
739
+ import { createHash as createHash4 } from "node:crypto";
740
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
692
741
  import { realpathSync as realpathSync2, statSync } from "node:fs";
693
742
  import { isAbsolute as isAbsolute2, resolve, sep as sep2, relative } from "node:path";
694
743
 
@@ -792,7 +841,15 @@ function resolveBunRunner() {
792
841
  }
793
842
  let real;
794
843
  try {
795
- real = realpathSync2(executable);
844
+ const execPath = spawnSync(executable, ["-e", "console.log(process.execPath)"], {
845
+ encoding: "utf8",
846
+ stdio: ["ignore", "pipe", "ignore"]
847
+ });
848
+ if (execPath.status === 0 && execPath.stdout.trim().length > 0) {
849
+ real = realpathSync2(execPath.stdout.trim());
850
+ } else {
851
+ real = realpathSync2(executable);
852
+ }
796
853
  } catch {
797
854
  throw new VerificationDescriptorError("bun runner realpath is unresolvable");
798
855
  }
@@ -805,7 +862,7 @@ function resolveBunRunner() {
805
862
  if (!stat.isFile())
806
863
  throw new VerificationDescriptorError("bun runner is not a regular file");
807
864
  const { readFileSync } = __require("node:fs");
808
- const contentHash = `sha256:${createHash3("sha256").update(readFileSync(real)).digest("hex")}`;
865
+ const contentHash = `sha256:${createHash4("sha256").update(readFileSync(real)).digest("hex")}`;
809
866
  let version = "";
810
867
  try {
811
868
  version = execFileSync(real, ["--version"], { encoding: "utf8" }).trim();
@@ -963,7 +1020,7 @@ function findingsDigest(findings) {
963
1020
  kind: f.kind,
964
1021
  summary: f.summary
965
1022
  }));
966
- return `sha256:${createHash3("sha256").update(`[${normalized.join(",")}]`).digest("hex")}`;
1023
+ return `sha256:${createHash4("sha256").update(`[${normalized.join(",")}]`).digest("hex")}`;
967
1024
  }
968
1025
 
969
1026
  // plugins/immune-brain/runtime/assurance/invocations.ts
@@ -1017,25 +1074,10 @@ function createInvocationRegistry() {
1017
1074
  }
1018
1075
 
1019
1076
  // plugins/immune-brain/runtime/role_prompt_bridge.ts
1020
- import { createHash as createHash4 } from "node:crypto";
1077
+ import { createHash as createHash5 } from "node:crypto";
1021
1078
  import { existsSync, readFileSync } from "node:fs";
1022
1079
  import { join as join3, dirname as dirname2 } from "node:path";
1023
1080
  import { fileURLToPath } from "node:url";
1024
-
1025
- // plugins/immune-brain/runtime/canonical_json.ts
1026
- function stableStringify(value) {
1027
- if (value === null || value === undefined)
1028
- return "null";
1029
- if (typeof value !== "object")
1030
- return JSON.stringify(value);
1031
- if (Array.isArray(value)) {
1032
- return `[${value.map(stableStringify).join(", ")}]`;
1033
- }
1034
- const obj = value;
1035
- return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}: ${stableStringify(obj[key])}`).join(", ")}}`;
1036
- }
1037
-
1038
- // plugins/immune-brain/runtime/role_prompt_bridge.ts
1039
1081
  var RUNTIME_DIR = dirname2(fileURLToPath(import.meta.url));
1040
1082
  var INTERNAL_ROLE_PROMPTS = {
1041
1083
  qa: { file: "qa.md", authority: "qa", tool_policy: "no tools" },
@@ -1123,7 +1165,7 @@ function buildRoleDelegationPacket(input) {
1123
1165
  ].join(`
1124
1166
 
1125
1167
  `);
1126
- const promptDigest = `sha256:${createHash4("sha256").update(prompt).digest("hex")}`;
1168
+ const promptDigest = `sha256:${createHash5("sha256").update(prompt).digest("hex")}`;
1127
1169
  return {
1128
1170
  contract: "immune_brain/role_delegation/v1",
1129
1171
  role: input.role,
@@ -1136,6 +1178,22 @@ function buildRoleDelegationPacket(input) {
1136
1178
  }
1137
1179
 
1138
1180
  // plugins/immune-brain/runtime/assurance/coordinator.ts
1181
+ function reviewReworkFindings(verdict) {
1182
+ if (verdict.decision !== "rework" || !verdict.findings?.length)
1183
+ throw new Error("review rework findings require a rework verdict");
1184
+ return verdict.findings.map((finding) => ({
1185
+ id: finding.id,
1186
+ kind: finding.kind,
1187
+ status: "open",
1188
+ acceptance_id: finding.acceptance_id,
1189
+ source: "review",
1190
+ review_round: null,
1191
+ summary: finding.summary,
1192
+ anchor: finding.anchor ?? null,
1193
+ evidence: finding.evidence ?? null,
1194
+ counterevidence: null
1195
+ }));
1196
+ }
1139
1197
  var QA_MIN_JOB_TIMEOUT_SECONDS = 15 * 60;
1140
1198
  var QA_MAX_JOB_TIMEOUT_SECONDS = 60 * 60;
1141
1199
  var QA_JOB_OVERHEAD_SECONDS = 2 * 60;
@@ -1163,7 +1221,7 @@ function reviewTurnBudget(workload) {
1163
1221
  return workload === "quick" ? 12 : workload === "standard" ? 16 : 24;
1164
1222
  }
1165
1223
  function snapshotDigest(snapshot) {
1166
- return `sha256:${createHash5("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
1224
+ return `sha256:${createHash6("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
1167
1225
  }
1168
1226
  function buildReviewPrompt(snapshot, evidencePath) {
1169
1227
  if (snapshot.role !== "review")
@@ -1203,12 +1261,45 @@ function buildReviewPrompt(snapshot, evidencePath) {
1203
1261
  "Acceptance assertions:",
1204
1262
  acceptance,
1205
1263
  "Reserve the final turn for exactly one strict JSON verdict. Reply with ONLY that object, without markdown fences or commentary.",
1264
+ `Every rework finding must carry machine-checkable provenance: evidence.trigger (the concrete inputs or state that reach the defect), a non-empty evidence.caller_chain (ordered repository paths or symbols), and evidence.violated {kind: "acceptance"|"security_boundary", ref}. The anchor is derived from that evidence; a finding without it is rejected and the correction must be resubmitted.`,
1206
1265
  `PASS shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"pass","approval":{"kind":"review","authority_role":"reviewer","summary":"<one line>"}}`,
1207
- `REWORK shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"rework","findings":[{"id":"review-1","kind":"blocking|advisory","acceptance_id":"<id|null>","summary":"<one line>"}]}`,
1266
+ `REWORK shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"rework","findings":[{"id":"review-1","kind":"blocking|advisory","acceptance_id":"<id|null>","summary":"<one line>","evidence":{"trigger":"<concrete inputs or state>","caller_chain":["<path-or-symbol>"],"violated":{"kind":"acceptance|security_boundary","ref":"<acceptance id or boundary>"}}}]}`,
1208
1267
  `REWORK verdicts must omit the approval field entirely; do not emit "approval": null.`
1209
1268
  ].join(`
1210
1269
  `);
1211
1270
  }
1271
+ function parseVerdictEvidence(value, index) {
1272
+ if (!value || typeof value !== "object" || Array.isArray(value))
1273
+ throw new Error(`finding ${index} evidence is required`);
1274
+ const evidence = value;
1275
+ const unknown = Object.keys(evidence).find((key) => !["trigger", "caller_chain", "violated"].includes(key));
1276
+ if (unknown)
1277
+ throw new Error(`finding ${index} evidence has unknown field: ${unknown}`);
1278
+ if (typeof evidence.trigger !== "string" || !evidence.trigger.trim())
1279
+ throw new Error(`finding ${index} evidence.trigger must be a non-empty string`);
1280
+ const chain = evidence.caller_chain;
1281
+ if (!Array.isArray(chain) || chain.length === 0 || chain.some((entry) => typeof entry !== "string" || !entry.trim()))
1282
+ throw new Error(`finding ${index} evidence.caller_chain must be a non-empty list of paths or symbols`);
1283
+ const violated = evidence.violated;
1284
+ if (!violated || typeof violated !== "object" || Array.isArray(violated))
1285
+ throw new Error(`finding ${index} evidence.violated is required`);
1286
+ const violatedRecord = violated;
1287
+ const unknownViolated = Object.keys(violatedRecord).find((key) => !["kind", "ref"].includes(key));
1288
+ if (unknownViolated)
1289
+ throw new Error(`finding ${index} evidence.violated has unknown field: ${unknownViolated}`);
1290
+ if (violatedRecord.kind !== "acceptance" && violatedRecord.kind !== "security_boundary")
1291
+ throw new Error(`finding ${index} evidence.violated.kind must be acceptance or security_boundary`);
1292
+ if (typeof violatedRecord.ref !== "string" || !violatedRecord.ref.trim())
1293
+ throw new Error(`finding ${index} evidence.violated.ref must be a non-empty string`);
1294
+ return {
1295
+ trigger: evidence.trigger,
1296
+ caller_chain: chain,
1297
+ violated: {
1298
+ kind: violatedRecord.kind,
1299
+ ref: violatedRecord.ref
1300
+ }
1301
+ };
1302
+ }
1212
1303
  function parseAssuranceVerdict(input, snapshot) {
1213
1304
  let raw;
1214
1305
  if (typeof input === "string") {
@@ -1259,14 +1350,24 @@ function parseAssuranceVerdict(input, snapshot) {
1259
1350
  throw new Error("rework verdict must omit approval");
1260
1351
  const findings = raw.findings.map((item, index) => {
1261
1352
  const finding = item;
1262
- const unknownFinding = Object.keys(finding).find((key) => !["id", "kind", "acceptance_id", "summary"].includes(key));
1353
+ const allowedFindingKeys = snapshot.role === "review" ? ["id", "kind", "acceptance_id", "summary", "evidence"] : ["id", "kind", "acceptance_id", "summary"];
1354
+ const unknownFinding = Object.keys(finding).find((key) => !allowedFindingKeys.includes(key));
1263
1355
  if (unknownFinding)
1264
1356
  throw new Error(`finding ${index} has unknown field: ${unknownFinding}`);
1265
1357
  if (typeof finding.id !== "string" || !finding.id.trim() || finding.kind !== "blocking" && finding.kind !== "advisory" || finding.acceptance_id !== null && typeof finding.acceptance_id !== "string" || typeof finding.summary !== "string" || !finding.summary.trim())
1266
1358
  throw new Error(`finding ${index} is invalid`);
1267
1359
  const id = `review-${snapshotDigest(snapshot).slice(7, 19)}-${index + 1}-${finding.id.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 48)}`;
1268
1360
  const normalized = { id, kind: finding.kind, acceptance_id: finding.acceptance_id, summary: finding.summary };
1269
- return { ...normalized, findings_digest: findingsDigest([normalized]) };
1361
+ if (snapshot.role !== "review")
1362
+ return { ...normalized, findings_digest: findingsDigest([normalized]) };
1363
+ const evidence = parseVerdictEvidence(finding.evidence, index);
1364
+ const anchor = anchorForEvidence(evidence);
1365
+ return {
1366
+ ...normalized,
1367
+ anchor,
1368
+ evidence,
1369
+ findings_digest: findingsDigest([normalized])
1370
+ };
1270
1371
  });
1271
1372
  return { contract: "assurance_kernel/assurance_verdict/v2", role: snapshot.role, task_id: snapshot.task_id, snapshot_digest: snapshotDigest(snapshot), decision: "rework", findings };
1272
1373
  }
@@ -1809,7 +1910,7 @@ function boundedAssuranceError(error) {
1809
1910
 
1810
1911
  // plugins/immune-brain/runtime/assurance/review_evidence.ts
1811
1912
  import { execFileSync as execFileSync2 } from "node:child_process";
1812
- import { createHash as createHash7 } from "node:crypto";
1913
+ import { createHash as createHash8 } from "node:crypto";
1813
1914
  import {
1814
1915
  mkdtempSync,
1815
1916
  rmSync as rmSync2,
@@ -1823,8 +1924,8 @@ import { tmpdir as tmpdir2 } from "node:os";
1823
1924
  import { join as join4 } from "node:path";
1824
1925
 
1825
1926
  // plugins/immune-brain/runtime/workspace_scope.ts
1826
- import { spawnSync } from "node:child_process";
1827
- import { createHash as createHash6 } from "node:crypto";
1927
+ import { spawnSync as spawnSync2 } from "node:child_process";
1928
+ import { createHash as createHash7 } from "node:crypto";
1828
1929
  import {
1829
1930
  existsSync as existsSync2,
1830
1931
  lstatSync as lstatSync2,
@@ -1834,7 +1935,7 @@ import {
1834
1935
  } from "node:fs";
1835
1936
  import { resolve as resolve2 } from "node:path";
1836
1937
  function git(root, args) {
1837
- const result = spawnSync("git", ["-C", root, ...args], {
1938
+ const result = spawnSync2("git", ["-C", root, ...args], {
1838
1939
  encoding: "utf8",
1839
1940
  stdio: ["ignore", "pipe", "pipe"]
1840
1941
  });
@@ -1858,7 +1959,7 @@ var portablePathCollator = new Intl.Collator("und", {
1858
1959
  });
1859
1960
  var gitTaskSnapshotTestHook;
1860
1961
  function gitBytes(root, args) {
1861
- const result = spawnSync("git", ["-C", root, ...args], {
1962
+ const result = spawnSync2("git", ["-C", root, ...args], {
1862
1963
  encoding: null,
1863
1964
  stdio: ["ignore", "pipe", "pipe"],
1864
1965
  maxBuffer: 8 * 1024 * 1024
@@ -2051,7 +2152,7 @@ function captureGitTaskSnapshot(projectRoot, scopeHint) {
2051
2152
  return before;
2052
2153
  }
2053
2154
  function hashTaskSnapshot(snapshot) {
2054
- return `sha256:${createHash6("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
2155
+ return `sha256:${createHash7("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
2055
2156
  }
2056
2157
  function taskDiffIdentity(projectRoot, scopeHint) {
2057
2158
  const snapshot = captureGitTaskSnapshot(projectRoot, scopeHint);
@@ -2196,7 +2297,7 @@ function pathMatchesScope(path, scopePath) {
2196
2297
  // plugins/immune-brain/runtime/assurance/review_evidence.ts
2197
2298
  var MAX_REVIEW_BUNDLE_BYTES = 2 * 1024 * 1024;
2198
2299
  function bundleDigest(bundle) {
2199
- return `sha256:${createHash7("sha256").update(JSON.stringify(bundle)).digest("hex")}`;
2300
+ return `sha256:${createHash8("sha256").update(JSON.stringify(bundle)).digest("hex")}`;
2200
2301
  }
2201
2302
  var reviewUtf8 = new TextDecoder("utf-8", { fatal: true });
2202
2303
  function readIndexBlob(root, path, entry) {
@@ -2346,7 +2447,7 @@ var SNAPSHOT_IDENTITY = {
2346
2447
  date: "1970-01-01T00:00:00 +0000"
2347
2448
  };
2348
2449
  function manifestDigest(manifest) {
2349
- return `sha256:${createHash7("sha256").update(JSON.stringify(manifest)).digest("hex")}`;
2450
+ return `sha256:${createHash8("sha256").update(JSON.stringify(manifest)).digest("hex")}`;
2350
2451
  }
2351
2452
  function gitEvidenceBytes(root, args, extraEnv = {}) {
2352
2453
  return execFileSync2("git", args, {
@@ -2472,7 +2573,7 @@ function publishInput(root, input) {
2472
2573
  if (!REVISION_DIFF_HASH.test(input.expectedDiffHash))
2473
2574
  throw new Error("review task revision hash has invalid identity");
2474
2575
  const snapshot = captureGitTaskRevisionSnapshot(root, input.scopeHint, input.baseHead);
2475
- const recomputed = `sha256:${createHash7("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
2576
+ const recomputed = `sha256:${createHash8("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
2476
2577
  if (recomputed !== input.expectedDiffHash)
2477
2578
  throw new Error("review task revision does not match assurance snapshot");
2478
2579
  return { snapshot, revision: publishReviewRevision(snapshot.repository_root, snapshot, recomputed, input.taskId) };
@@ -2705,7 +2806,7 @@ function serializeTaskTombstone(tombstone) {
2705
2806
  }
2706
2807
 
2707
2808
  // plugins/immune-brain/runtime/kernel/storage.ts
2708
- import { createHash as createHash9, randomUUID as randomUUID3 } from "node:crypto";
2809
+ import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
2709
2810
  import {
2710
2811
  closeSync as closeSync3,
2711
2812
  constants as constants2,
@@ -2739,7 +2840,7 @@ var REDUCED_MUTATION_BRAND = Symbol("assurance-kernel-reduced-mutation-v2");
2739
2840
  var MUTATION_AUTHORITY_CAPABILITY_BRAND = Symbol("assurance-kernel-mutation-authority-capability");
2740
2841
 
2741
2842
  // plugins/immune-brain/runtime/kernel/intent.ts
2742
- import { createHash as createHash8 } from "node:crypto";
2843
+ import { createHash as createHash9 } from "node:crypto";
2743
2844
  import {
2744
2845
  closeSync as closeSync2,
2745
2846
  constants as fsConstants,
@@ -2870,7 +2971,7 @@ var portablePathCollator2 = new Intl.Collator("und", {
2870
2971
  ignorePunctuation: false
2871
2972
  });
2872
2973
  function sha256Hex(bytes) {
2873
- return createHash8("sha256").update(bytes).digest("hex");
2974
+ return createHash9("sha256").update(bytes).digest("hex");
2874
2975
  }
2875
2976
  function objectAt(value, path, violations) {
2876
2977
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -3170,6 +3271,10 @@ function readTaskIntentSource(root, taskId, requestedPath) {
3170
3271
  }
3171
3272
  };
3172
3273
  }
3274
+ function observeTaskIntent(root, taskId, requestedPath) {
3275
+ const { identity: _identity, ...observed } = readTaskIntentSource(root, taskId, requestedPath);
3276
+ return observed;
3277
+ }
3173
3278
  function readTaskIntent(root, taskId, requestedPath) {
3174
3279
  const { identity, ...observed } = readTaskIntentSource(root, taskId, requestedPath);
3175
3280
  return { ...observed, token: mintToken(identity) };
@@ -3263,7 +3368,7 @@ var FINDING_KINDS = [
3263
3368
  "unresolved_user_decision",
3264
3369
  "replan_required"
3265
3370
  ];
3266
- var FINDING_STATUSES = ["open", "resolved"];
3371
+ var FINDING_STATUSES = ["open", "resolved", "refuted"];
3267
3372
  var FINDING_SOURCES = [
3268
3373
  "execution",
3269
3374
  "review",
@@ -3276,10 +3381,52 @@ var APPROVAL_AUTHORITY_ROLES = [
3276
3381
  "qa",
3277
3382
  "user"
3278
3383
  ];
3384
+ function nullableAnchor(value, path, violations) {
3385
+ if (value === null || value === undefined)
3386
+ return null;
3387
+ return stringAt(value, path, violations);
3388
+ }
3389
+ function parseFindingEvidence(value, path, violations) {
3390
+ if (value === null || value === undefined)
3391
+ return null;
3392
+ const item = objectAt2(value, path, violations);
3393
+ rejectUnknown2(item, ["trigger", "caller_chain", "violated"], path, violations);
3394
+ const trigger = stringAt(item.trigger, `${path}.trigger`, violations);
3395
+ const chain = arrayAt(item.caller_chain, `${path}.caller_chain`, violations);
3396
+ if (chain.length === 0)
3397
+ violations.push(`${path}.caller_chain must contain at least one entry`);
3398
+ const caller_chain = chain.map((entry, index) => stringAt(entry, `${path}.caller_chain[${index}]`, violations));
3399
+ const violatedRaw = objectAt2(item.violated, `${path}.violated`, violations);
3400
+ rejectUnknown2(violatedRaw, ["kind", "ref"], `${path}.violated`, violations);
3401
+ const kind = enumAt(violatedRaw.kind, ["acceptance", "security_boundary"], `${path}.violated.kind`, violations);
3402
+ const ref = stringAt(violatedRaw.ref, `${path}.violated.ref`, violations);
3403
+ return { trigger, caller_chain, violated: { kind, ref } };
3404
+ }
3405
+ function parseFindingCounterevidence(value, path, violations) {
3406
+ if (value === null || value === undefined)
3407
+ return null;
3408
+ const item = objectAt2(value, path, violations);
3409
+ rejectUnknown2(item, ["attestation_id", "acceptance_id"], path, violations);
3410
+ return {
3411
+ attestation_id: stringAt(item.attestation_id, `${path}.attestation_id`, violations),
3412
+ acceptance_id: stringAt(item.acceptance_id, `${path}.acceptance_id`, violations)
3413
+ };
3414
+ }
3279
3415
  function parseFinding(value, index, violations) {
3280
3416
  const path = `record.findings[${index}]`;
3281
3417
  const item = objectAt2(value, path, violations);
3282
- rejectUnknown2(item, ["id", "kind", "status", "acceptance_id", "source", "review_round", "summary"], path, violations);
3418
+ rejectUnknown2(item, [
3419
+ "id",
3420
+ "kind",
3421
+ "status",
3422
+ "acceptance_id",
3423
+ "source",
3424
+ "review_round",
3425
+ "summary",
3426
+ "anchor",
3427
+ "evidence",
3428
+ "counterevidence"
3429
+ ], path, violations);
3283
3430
  return {
3284
3431
  id: stringAt(item.id, `${path}.id`, violations),
3285
3432
  kind: enumAt(item.kind, FINDING_KINDS, `${path}.kind`, violations),
@@ -3287,7 +3434,12 @@ function parseFinding(value, index, violations) {
3287
3434
  acceptance_id: nullableString(item.acceptance_id, `${path}.acceptance_id`, violations),
3288
3435
  source: enumAt(item.source, FINDING_SOURCES, `${path}.source`, violations),
3289
3436
  review_round: nullablePositiveInteger(item.review_round, `${path}.review_round`, violations),
3290
- summary: stringAt(item.summary, `${path}.summary`, violations)
3437
+ summary: stringAt(item.summary, `${path}.summary`, violations),
3438
+ ...item.anchor !== undefined ? { anchor: nullableAnchor(item.anchor, `${path}.anchor`, violations) } : {},
3439
+ ...item.evidence !== undefined ? { evidence: parseFindingEvidence(item.evidence, `${path}.evidence`, violations) } : {},
3440
+ ...item.counterevidence !== undefined ? {
3441
+ counterevidence: parseFindingCounterevidence(item.counterevidence, `${path}.counterevidence`, violations)
3442
+ } : {}
3291
3443
  };
3292
3444
  }
3293
3445
  function parseHistoryV2(value, index, violations) {
@@ -3622,6 +3774,31 @@ function parseTaskRecordAtVersion(raw, version) {
3622
3774
  uniqueIds(attestations, "record.attestations", violations);
3623
3775
  uniqueIds(findings, "record.findings", violations);
3624
3776
  uniqueIds(history, "record.history", violations);
3777
+ for (const [index, finding] of findings.entries()) {
3778
+ const path = `record.findings[${index}]`;
3779
+ if (finding.anchor != null && !SHA256_HEX.test(finding.anchor))
3780
+ violations.push(`${path}.anchor must be sha256:<64 hex>`);
3781
+ const anchor = finding.anchor ?? null;
3782
+ const evidence = finding.evidence ?? null;
3783
+ if (anchor === null !== (evidence === null))
3784
+ violations.push(`${path} must carry anchor and evidence together`);
3785
+ if (anchor !== null && evidence !== null && anchor !== anchorForEvidence(evidence))
3786
+ violations.push(`${path}.anchor must equal the digest of its evidence`);
3787
+ if (finding.status === "refuted" && (finding.kind === "unresolved_user_decision" || finding.kind === "replan_required"))
3788
+ violations.push(`${path} refuted requires a blocking or advisory finding`);
3789
+ const counterevidence = finding.counterevidence ?? null;
3790
+ if (finding.status === "refuted" && !counterevidence)
3791
+ violations.push(`${path} status refuted requires counterevidence`);
3792
+ if (!counterevidence)
3793
+ continue;
3794
+ if (finding.status === "open")
3795
+ violations.push(`${path} open finding cannot carry counterevidence`);
3796
+ const attestation = attestations.find((item) => item.id === counterevidence.attestation_id);
3797
+ if (!attestation || attestation.kind !== "qa")
3798
+ violations.push(`${path}.counterevidence.attestation_id must reference a qa attestation`);
3799
+ else if (!attestation.acceptance_results.some((result) => result.acceptance_id === counterevidence.acceptance_id && result.status === "passed"))
3800
+ violations.push(`${path}.counterevidence.acceptance_id must be an acceptance its attestation passed`);
3801
+ }
3625
3802
  if (violations.length > 0)
3626
3803
  throw new KernelValidationError(violations);
3627
3804
  const record = {
@@ -3672,6 +3849,7 @@ function assertKernelInvariantsV3(intentRaw, recordRaw) {
3672
3849
  var ACTION_V2_TYPES = [
3673
3850
  "record_finding",
3674
3851
  "resolve_finding",
3852
+ "refute_finding",
3675
3853
  "record_approval",
3676
3854
  "revise_intent",
3677
3855
  "approve_breaking_intent_revision",
@@ -3736,6 +3914,16 @@ function parseTaskAction(raw) {
3736
3914
  };
3737
3915
  break;
3738
3916
  }
3917
+ case "refute_finding": {
3918
+ rejectUnknown2(value, [...ACTION_BASE_FIELDS, "finding_id", "attestation_id"], "action", violations);
3919
+ action = {
3920
+ ...base,
3921
+ type: "refute_finding",
3922
+ finding_id: stringAt(value.finding_id, "action.finding_id", violations),
3923
+ attestation_id: stringAt(value.attestation_id, "action.attestation_id", violations)
3924
+ };
3925
+ break;
3926
+ }
3739
3927
  case "record_approval": {
3740
3928
  rejectUnknown2(value, [...ACTION_BASE_FIELDS, "approval"], "action", violations);
3741
3929
  const approval = parseApprovalV2(value.approval, 0, violations, true);
@@ -3846,12 +4034,17 @@ function assertTaskRecordUpdateV3(previousRaw, nextRaw, action) {
3846
4034
  violations.push(`attestation ${prior.id} was rewritten`);
3847
4035
  }
3848
4036
  const resolvingFindingIds = action.type === "resolve_finding" ? [action.finding_id] : action.type === "resolve_user_decision" ? [action.finding_id] : action.type === "authorize_rework" || action.type === "approve_breaking_intent_revision" ? previous.findings.filter((item) => item.kind === "replan_required" && item.status === "open").map((item) => item.id) : [];
3849
- const reworkFindingIds = action.type === "request_rework" ? new Set(action.findings.map((item) => item.id)) : new Set;
4037
+ const withoutFields = (finding, fields) => {
4038
+ const copy = { ...finding };
4039
+ for (const field of fields)
4040
+ delete copy[field];
4041
+ return JSON.stringify(copy);
4042
+ };
3850
4043
  for (const prior of previous.findings) {
3851
4044
  const current = next.findings.find((item) => item.id === prior.id);
3852
4045
  if (!current)
3853
4046
  violations.push(`finding item ${prior.id} was removed`);
3854
- else if (JSON.stringify(current) !== JSON.stringify(prior) && !(resolvingFindingIds.includes(prior.id) && prior.status === "open" && current.status === "resolved") && !(reworkFindingIds.has(prior.id) && current.review_round !== null))
4047
+ else if (JSON.stringify(current) !== JSON.stringify(prior) && !(resolvingFindingIds.includes(prior.id) && prior.status !== "resolved" && current.status === "resolved" && (prior.status === "open" || !refutationIsLive(prior, previous.attestations, refutationIdentity(previous, action.diff_hash))) && withoutFields(prior, ["status"]) === withoutFields(current, ["status"])) && !(action.type === "refute_finding" && prior.id === action.finding_id && prior.status !== "resolved" && current.status === "refuted" && (prior.status === "open" || !refutationIsLive(prior, previous.attestations, refutationIdentity(previous, action.diff_hash))) && current.counterevidence?.attestation_id === action.attestation_id && current.counterevidence?.acceptance_id === prior.acceptance_id && isFreshPassingQaAttestation(previous.attestations.find((item) => item.id === action.attestation_id), current.counterevidence?.acceptance_id ?? "", refutationIdentity(previous, action.diff_hash)) && withoutFields(prior, ["status", "counterevidence"]) === withoutFields(current, ["status", "counterevidence"])))
3855
4048
  violations.push(`finding item ${prior.id} was rewritten`);
3856
4049
  }
3857
4050
  if (violations.length > 0)
@@ -3877,7 +4070,7 @@ class KernelStoreSecurityError extends Error {
3877
4070
  }
3878
4071
  }
3879
4072
  function revisionFor(content) {
3880
- return `sha256:${createHash9("sha256").update(content).digest("hex")}`;
4073
+ return `sha256:${createHash10("sha256").update(content).digest("hex")}`;
3881
4074
  }
3882
4075
  function canonicalRoot(root) {
3883
4076
  try {
@@ -5017,7 +5210,12 @@ function completionDecision(intent, record, currentDiffHash, currentIntentConten
5017
5210
  }
5018
5211
  }
5019
5212
  const independenceViolations = candidates.filter((item) => repeatedActors.has(item.actor_id)).map((item) => item.id);
5020
- const blockingFindingIds = record.findings.filter((item) => item.status === "open" && item.kind === "blocking").map((item) => item.id);
5213
+ const refutationState = {
5214
+ intent_revision: intent.revision,
5215
+ intent_content_hash: currentIntentContentHash,
5216
+ diff_hash: currentDiffHash
5217
+ };
5218
+ const blockingFindingIds = record.findings.filter((item) => item.kind === "blocking" && (item.status === "open" || item.status === "refuted" && !refutationIsLive(item, record.attestations, refutationState))).map((item) => item.id);
5021
5219
  const unresolvedUserDecisionIds = record.findings.filter((item) => item.status === "open" && item.kind === "unresolved_user_decision").map((item) => item.id);
5022
5220
  const replanRequiredIds = record.findings.filter((item) => item.status === "open" && item.kind === "replan_required").map((item) => item.id);
5023
5221
  return {
@@ -5245,7 +5443,7 @@ async function projectAssurance(root, taskId, diffProvider) {
5245
5443
  }
5246
5444
 
5247
5445
  // plugins/immune-brain/runtime/kernel/reducer.ts
5248
- import { createHash as createHash10 } from "node:crypto";
5446
+ import { createHash as createHash11 } from "node:crypto";
5249
5447
  var RISK_RANK2 = {
5250
5448
  routine: 0,
5251
5449
  material: 1,
@@ -5272,13 +5470,13 @@ function stableJson(value) {
5272
5470
  return primitive === undefined ? "null" : primitive;
5273
5471
  }
5274
5472
  function canonicalRecordHash(record) {
5275
- return `sha256:${createHash10("sha256").update(`${JSON.stringify(record, null, 2)}
5473
+ return `sha256:${createHash11("sha256").update(`${JSON.stringify(record, null, 2)}
5276
5474
  `).digest("hex")}`;
5277
5475
  }
5278
5476
  function actionFingerprint(action, intentRevision, intentContentHash, audit) {
5279
5477
  const { expected_record_hash: _r, expected_workspace_hash: _w, diff_hash: _d, ...payload } = action;
5280
5478
  const base = audit ? { action: payload, intentRevision, intentContentHash, audit } : { action: payload, intentRevision, intentContentHash };
5281
- return createHash10("sha256").update(stableJson(base)).digest("hex");
5479
+ return createHash11("sha256").update(stableJson(base)).digest("hex");
5282
5480
  }
5283
5481
  function historyReason(action, intentRevision, intentContentHash, audit, detail) {
5284
5482
  const fingerprint = `action_v2_sha256:${actionFingerprint(action, intentRevision, intentContentHash, audit)}`;
@@ -5323,7 +5521,9 @@ function appendHistory(record, action, from, detail, audit) {
5323
5521
  record.history.push(entry);
5324
5522
  }
5325
5523
  function intentRefMatches(intent, ref) {
5326
- return ref.path === `docs/plans/${intent.task_id}.intent.json` && ref.content_hash === canonicalIntentHash(intent);
5524
+ const activePath = `docs/plans/${intent.task_id}.intent.json`;
5525
+ const archivedPath = `docs/plans/archive/${intent.task_id}.intent.json`;
5526
+ return (ref.path === activePath || ref.path === archivedPath) && ref.content_hash === canonicalIntentHash(intent);
5327
5527
  }
5328
5528
  function hasPrivilegedKind(action) {
5329
5529
  return action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "authorize_rework" || action.type === "stop" || action.type === "resolve_user_decision";
@@ -5333,9 +5533,20 @@ function findingsDigestV2(findings) {
5333
5533
  id: finding.id,
5334
5534
  kind: finding.kind,
5335
5535
  acceptance_id: finding.acceptance_id,
5336
- summary: finding.summary
5536
+ summary: finding.summary,
5537
+ ...finding.anchor !== undefined ? { anchor: finding.anchor ?? null } : {},
5538
+ ...finding.evidence !== undefined ? { evidence: finding.evidence ?? null } : {}
5337
5539
  }));
5338
- return `sha256:${createHash10("sha256").update(stableJson(normalized)).digest("hex")}`;
5540
+ return `sha256:${createHash11("sha256").update(stableJson(normalized)).digest("hex")}`;
5541
+ }
5542
+ function sharesAcceptanceBoundary(left, right) {
5543
+ if (left.acceptance_id !== null && right.acceptance_id !== null)
5544
+ return left.acceptance_id === right.acceptance_id;
5545
+ if (left.acceptance_id !== null || right.acceptance_id !== null)
5546
+ return false;
5547
+ const leftRef = left.evidence?.violated.ref ?? null;
5548
+ const rightRef = right.evidence?.violated.ref ?? null;
5549
+ return leftRef !== null && leftRef === rightRef;
5339
5550
  }
5340
5551
  function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
5341
5552
  const previous = parseTaskRecord(recordRaw);
@@ -5420,10 +5631,52 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
5420
5631
  throw new KernelInvariantError([
5421
5632
  `finding ${action.finding_id} is already resolved`
5422
5633
  ]);
5634
+ if (finding.status === "refuted" && refutationIsLive(finding, record.attestations, refutationIdentity(record, action.diff_hash)))
5635
+ throw new KernelInvariantError([
5636
+ `finding ${action.finding_id} is still refuted by live counterevidence`
5637
+ ]);
5423
5638
  finding.status = "resolved";
5424
5639
  appendHistory(record, action, from, action.finding_id, authorityAudit);
5425
5640
  break;
5426
5641
  }
5642
+ case "refute_finding": {
5643
+ if (record.lifecycle !== "active")
5644
+ throw new KernelInvariantError([
5645
+ `cannot refute findings while lifecycle is ${record.lifecycle}`
5646
+ ]);
5647
+ const finding = record.findings.find((item) => item.id === action.finding_id);
5648
+ if (!finding)
5649
+ throw new KernelInvariantError([
5650
+ `finding ${action.finding_id} does not exist`
5651
+ ]);
5652
+ if (finding.kind === "unresolved_user_decision" || finding.kind === "replan_required")
5653
+ throw new KernelInvariantError([
5654
+ "refute_finding cannot refute a user decision or replan boundary"
5655
+ ]);
5656
+ if (finding.status === "resolved")
5657
+ throw new KernelInvariantError([
5658
+ `finding ${action.finding_id} is already resolved`
5659
+ ]);
5660
+ if (!finding.acceptance_id)
5661
+ throw new KernelInvariantError([
5662
+ "refute_finding requires a finding bound to an acceptance id"
5663
+ ]);
5664
+ if (finding.status === "refuted" && refutationIsLive(finding, record.attestations, refutationIdentity(record, action.diff_hash)))
5665
+ throw new KernelInvariantError([
5666
+ `finding ${action.finding_id} is already refuted by live counterevidence`
5667
+ ]);
5668
+ if (!isFreshPassingQaAttestation(record.attestations.find((item) => item.id === action.attestation_id), finding.acceptance_id, refutationIdentity(record, action.diff_hash)))
5669
+ throw new KernelInvariantError([
5670
+ `refute_finding requires a fresh passing QA attestation covering ${finding.acceptance_id ?? "null"}`
5671
+ ]);
5672
+ finding.status = "refuted";
5673
+ finding.counterevidence = {
5674
+ attestation_id: action.attestation_id,
5675
+ acceptance_id: finding.acceptance_id
5676
+ };
5677
+ appendHistory(record, action, from, action.finding_id, authorityAudit);
5678
+ break;
5679
+ }
5427
5680
  case "record_approval": {
5428
5681
  if (record.lifecycle !== "active" || record.artifact_state !== "frozen")
5429
5682
  throw new KernelInvariantError([
@@ -5544,28 +5797,55 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
5544
5797
  "request_rework requires review, qa, or user authority"
5545
5798
  ]);
5546
5799
  const round = reviewRound(record);
5547
- const hasPriorBlockingReviewRework = record.findings.some((finding) => finding.source === "review" && finding.kind === "blocking" && finding.review_round !== null);
5548
- const parkForReplan = authorityAudit.authority_kind === "review" && hasPriorBlockingReviewRework && action.findings.some((finding) => finding.kind === "blocking");
5800
+ for (const finding of action.findings) {
5801
+ const anchor = finding.anchor ?? null;
5802
+ const evidence = finding.evidence ?? null;
5803
+ if (anchor === null !== (evidence === null))
5804
+ throw new KernelInvariantError([
5805
+ `finding ${finding.id} must carry anchor and evidence together`
5806
+ ]);
5807
+ if (evidence && anchor !== anchorForEvidence(evidence))
5808
+ throw new KernelInvariantError([
5809
+ `finding ${finding.id} anchor must equal the digest of its evidence`
5810
+ ]);
5811
+ if (anchor !== null && authorityAudit.authority_kind !== "review")
5812
+ throw new KernelInvariantError([
5813
+ `${authorityAudit.authority_kind} rework cannot carry review provenance`
5814
+ ]);
5815
+ }
5816
+ const identity = refutationIdentity(record, action.diff_hash);
5817
+ const priorBlockingReviewFindings = record.findings.filter((finding) => finding.source === "review" && finding.kind === "blocking" && finding.review_round !== null && !(finding.status === "refuted" && refutationIsLive(finding, record.attestations, identity)));
5818
+ const admissions = action.findings.map((finding) => {
5819
+ const inherited = finding.anchor != null ? record.findings.find((prior) => prior.source === "review" && prior.status === "refuted" && prior.anchor === finding.anchor && refutationIsLive(prior, record.attestations, identity)) : undefined;
5820
+ return { finding, inherited };
5821
+ });
5822
+ const disputed = admissions.find(({ finding, inherited }) => finding.kind === "blocking" && inherited === undefined && priorBlockingReviewFindings.some((prior) => sharesAcceptanceBoundary(finding, prior)))?.finding;
5823
+ const parkForReplan = authorityAudit.authority_kind === "review" && disputed !== undefined;
5549
5824
  if (!parkForReplan) {
5550
5825
  record.artifact_state = "active";
5551
5826
  record.intent_ref.path = `docs/plans/${record.task_id}.intent.json`;
5552
5827
  }
5553
5828
  const findingIds = new Set(record.findings.map((item) => item.id));
5554
- for (const finding of action.findings) {
5829
+ for (const { finding, inherited } of admissions) {
5555
5830
  if (findingIds.has(finding.id))
5556
5831
  throw new KernelInvariantError([
5557
5832
  `findings contains duplicate id ${finding.id}`
5558
5833
  ]);
5559
5834
  findingIds.add(finding.id);
5560
5835
  record.findings.push({
5561
- ...finding,
5562
- status: "open",
5836
+ id: finding.id,
5837
+ kind: finding.kind,
5838
+ status: inherited ? "refuted" : "open",
5839
+ acceptance_id: finding.acceptance_id,
5563
5840
  source: authorityAudit.authority_kind === "review" ? "review" : "execution",
5564
- review_round: authorityAudit.authority_kind === "review" ? round : null
5841
+ review_round: authorityAudit.authority_kind === "review" ? round : null,
5842
+ summary: finding.summary,
5843
+ ...finding.anchor !== undefined ? { anchor: finding.anchor } : {},
5844
+ ...finding.evidence !== undefined ? { evidence: finding.evidence } : {},
5845
+ counterevidence: inherited ? { ...inherited.counterevidence } : null
5565
5846
  });
5566
5847
  }
5567
5848
  if (parkForReplan && !record.findings.some((item) => item.status === "open" && item.kind === "replan_required")) {
5568
- const disputed = action.findings.find((item) => item.kind === "blocking") ?? action.findings[0];
5569
5849
  const boundary = {
5570
5850
  id: `${action.event_id}:replan-required`,
5571
5851
  kind: "replan_required",
@@ -6037,6 +6317,14 @@ function createCanaryApplication(registry) {
6037
6317
  case "resolve_finding":
6038
6318
  action = { ...base, type: "resolve_finding", finding_id: operation.finding_id };
6039
6319
  break;
6320
+ case "refute_finding":
6321
+ action = {
6322
+ ...base,
6323
+ type: "refute_finding",
6324
+ finding_id: operation.finding_id,
6325
+ attestation_id: operation.attestation_id
6326
+ };
6327
+ break;
6040
6328
  case "request_rework":
6041
6329
  capability = operation.capability;
6042
6330
  action = { ...base, type: "request_rework", findings: operation.findings };
@@ -6051,7 +6339,7 @@ function createCanaryApplication(registry) {
6051
6339
  type: "revise_intent",
6052
6340
  next_intent: operation.next_intent,
6053
6341
  next_intent_ref: {
6054
- path: `docs/plans/${operation.next_intent.task_id}.intent.json`,
6342
+ path: snapshot.record.artifact_state === "frozen" ? `docs/plans/archive/${operation.next_intent.task_id}.intent.json` : `docs/plans/${operation.next_intent.task_id}.intent.json`,
6055
6343
  content_hash: canonicalIntentHash(operation.next_intent)
6056
6344
  }
6057
6345
  };
@@ -6163,7 +6451,7 @@ function createCanaryApplication(registry) {
6163
6451
  }
6164
6452
 
6165
6453
  // plugins/immune-brain/runtime/kernel/authority_port.ts
6166
- import { createHash as createHash11 } from "node:crypto";
6454
+ import { createHash as createHash12 } from "node:crypto";
6167
6455
 
6168
6456
  // plugins/immune-brain/runtime/kernel/capability_registry.ts
6169
6457
  function createCapabilityRegistry(capabilityBrand, hooks, domainLabel) {
@@ -6211,7 +6499,7 @@ function createCapabilityRegistry(capabilityBrand, hooks, domainLabel) {
6211
6499
  // plugins/immune-brain/runtime/kernel/authority_port.ts
6212
6500
  function digestOfAction(action) {
6213
6501
  const { expected_record_hash: _r, expected_workspace_hash: _w, diff_hash: _d, ...rest } = action;
6214
- return createHash11("sha256").update(JSON.stringify(rest)).digest("hex");
6502
+ return createHash12("sha256").update(JSON.stringify(rest)).digest("hex");
6215
6503
  }
6216
6504
  function createMutationAuthorityRegistry() {
6217
6505
  const inner = createCapabilityRegistry(MUTATION_AUTHORITY_CAPABILITY_BRAND, {
@@ -6335,13 +6623,13 @@ function createEnrollmentAuthorityRegistry() {
6335
6623
  }
6336
6624
 
6337
6625
  // plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts
6338
- import { createHash as createHash12 } from "node:crypto";
6339
- import { spawnSync as spawnSync2 } from "node:child_process";
6626
+ import { createHash as createHash13 } from "node:crypto";
6627
+ import { spawnSync as spawnSync3 } from "node:child_process";
6340
6628
  import { resolve as resolve6 } from "node:path";
6341
6629
  var SOURCE_PATH = ".imm/state/workspace.json";
6342
6630
  var GIT_OBJECT_ID4 = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
6343
6631
  function readGitHead(root) {
6344
- const result = spawnSync2("git", ["-C", root, "rev-parse", "--verify", "HEAD^{commit}"], {
6632
+ const result = spawnSync3("git", ["-C", root, "rev-parse", "--verify", "HEAD^{commit}"], {
6345
6633
  encoding: "utf8"
6346
6634
  });
6347
6635
  const head = typeof result.stdout === "string" ? result.stdout.trim() : "";
@@ -6350,7 +6638,7 @@ function readGitHead(root) {
6350
6638
  return head.toLowerCase();
6351
6639
  }
6352
6640
  function sha256Hex2(bytes) {
6353
- return createHash12("sha256").update(bytes).digest("hex");
6641
+ return createHash13("sha256").update(bytes).digest("hex");
6354
6642
  }
6355
6643
  function stableStringify2(value) {
6356
6644
  if (value === null || typeof value !== "object")
@@ -6673,113 +6961,2294 @@ async function runDeterministicQa(snapshot, descriptors, runner, options = {}) {
6673
6961
  };
6674
6962
  }
6675
6963
 
6676
- // plugins/immune-brain/runtime/claude/kernel_ports.ts
6677
- function diffSnapshotOf(root, record) {
6678
- if (record.contract === "assurance_kernel/task_record/v4") {
6679
- if (!record.git_base_head)
6680
- throw new Error("TaskRecord v4 is missing git_base_head");
6681
- return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head);
6964
+ // plugins/immune-brain/runtime/unattended/batch_plan.ts
6965
+ import { createHash as createHash14 } from "node:crypto";
6966
+
6967
+ // plugins/immune-brain/runtime/github_issue_tracker.ts
6968
+ import { spawn as spawn2 } from "node:child_process";
6969
+ import { basename as basename2, relative as relative3, resolve as resolve7, sep as sep5 } from "node:path";
6970
+ var CONTRACT = "immune_brain/github_issue_tracker_result/v1";
6971
+ var PROTOCOL_MARKER = "<!-- immune-brain-tracker:v1 -->";
6972
+ var KIND_INITIATIVE_MARKER = "<!-- immune-brain:kind=initiative -->";
6973
+ var KIND_TASK_MARKER = "<!-- immune-brain:kind=task -->";
6974
+ var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
6975
+ var MAX_GH_OUTPUT = 8 * 1024 * 1024;
6976
+ var MAX_DIAGNOSTIC = 512;
6977
+ var GH_TIMEOUT_MS = 20000;
6978
+ var MAX_SNAPSHOT_PAGES = 100;
6979
+ var MAX_TERMINAL_EVENT_ID = 500;
6980
+ function countLiteral(value, needle) {
6981
+ if (!needle)
6982
+ return 0;
6983
+ let count = 0;
6984
+ let index = 0;
6985
+ while ((index = value.indexOf(needle, index)) !== -1) {
6986
+ count += 1;
6987
+ index += needle.length;
6682
6988
  }
6683
- return taskDiffIdentity(root, record.intent_snapshot.scope_hint);
6989
+ return count;
6684
6990
  }
6685
- function diffHashOf(root, record) {
6686
- return diffSnapshotOf(root, record).diff_hash;
6991
+ function marker(name, value) {
6992
+ return `<!-- immune-brain:${name}=${value} -->`;
6687
6993
  }
6688
- function readTaskIntentForRecord(root, taskId) {
6689
- const currentPath = readTaskRecordRaw(root, taskId).record?.intent_ref?.path;
6690
- return readTaskIntent(root, taskId, currentPath);
6994
+ function terminalMarker(eventId) {
6995
+ return `<!-- immune-brain:terminal-event=${eventId} -->`;
6691
6996
  }
6692
- function extractVerdictJson(input) {
6693
- if (typeof input === "string") {
6694
- const cleaned = input.split(`
6695
- `).map((line) => line.trim()).filter((line) => line.startsWith("{") && line.endsWith("}")).join("");
6696
- if (!cleaned)
6697
- return null;
6698
- try {
6699
- return JSON.parse(cleaned);
6700
- } catch {
6701
- return null;
6702
- }
6703
- }
6704
- if (typeof input === "object" && input !== null && !Array.isArray(input))
6705
- return input;
6706
- return null;
6997
+ function terminalSuffix(eventId) {
6998
+ return `
6999
+
7000
+ ${terminalMarker(eventId)}
7001
+ Terminal event: \`${eventId}\`
7002
+ `;
6707
7003
  }
6708
- function verdictFingerprint(raw) {
6709
- return JSON.stringify({
6710
- contract: raw.contract ?? null,
6711
- role: raw.role ?? null,
6712
- task_id: raw.task_id ?? null,
6713
- snapshot_digest: raw.snapshot_digest ?? null,
6714
- decision: raw.decision ?? null,
6715
- approval: raw.approval ?? null,
6716
- findings: raw.findings ?? null
6717
- });
7004
+ var MAX_TERMINAL_SUFFIX_BYTES = Buffer.byteLength(terminalSuffix("x".repeat(MAX_TERMINAL_EVENT_ID)), "utf8");
7005
+ function redactSecrets(value) {
7006
+ return value.replace(/\bgh[pousr]_[A-Za-z0-9_]+\b/g, "[REDACTED_GITHUB_TOKEN]").replace(/\bgithub_pat_[A-Za-z0-9_]+\b/g, "[REDACTED_GITHUB_TOKEN]").replace(/\bBearer\s+\S+/gi, "Bearer [REDACTED]").replace(/\b(?:token|secret|password)\s*[=:]\s*\S+/gi, "credential=[REDACTED]");
6718
7007
  }
6719
- async function submitClaudeReview(host, coordinator, ctx, taskId, verdictInput) {
6720
- if (verdictInput === undefined)
6721
- throw new Error("verdict is required");
6722
- const observed = host.inspectReviewForTask(taskId);
6723
- if (!observed.ok) {
6724
- if (observed.release)
6725
- return coordinator.abandonReview(taskId, observed.reason);
6726
- return { state: "blocked", reason: observed.reason };
6727
- }
6728
- const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
6729
- if (!parentValid)
6730
- return coordinator.submitReview(taskId, ctx, verdictInput);
6731
- const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
6732
- if (!receiptValid) {
6733
- return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
6734
- }
6735
- const parentJson = extractVerdictJson(verdictInput);
6736
- const receiptJson = extractVerdictJson(observed.receipt.result);
6737
- if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
6738
- return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
6739
- }
6740
- return coordinator.submitReview(taskId, ctx, verdictInput);
7008
+ function identifier(value, name) {
7009
+ if (typeof value !== "string" || !ID_PATTERN.test(value))
7010
+ throw new Error(`${name} must match ${ID_PATTERN}`);
7011
+ if (/^(?:gh[pousr]_|github_pat_)/i.test(value))
7012
+ throw new Error(`${name} must not contain a token-like value`);
7013
+ return value;
6741
7014
  }
6742
- function stopReason(value) {
6743
- return typeof value === "string" && value.length > 0 ? value : "user stop";
7015
+ function redactGithubDiagnostic(value) {
7016
+ return redactSecrets(value).replace(/\s+/g, " ").trim().slice(0, MAX_DIAGNOSTIC);
6744
7017
  }
6745
- function assertProjectionBinding(before, after, allowDiffChange = false) {
6746
- const fields = allowDiffChange ? ["record_revision", "workspace_revision", "intent_revision", "intent_content_hash"] : ["record_revision", "workspace_revision", "intent_revision", "intent_content_hash", "diff_hash"];
6747
- if (before.error || !before.claim || after.error || !after.claim || before.claim.task_id !== after.claim.task_id || fields.some((field) => before.projection[field] !== after.projection[field])) {
6748
- throw new Error("Task changed after native confirmation; authority aborted before capability issuance");
6749
- }
7018
+ function result(operation, status, message, issue) {
7019
+ return {
7020
+ contract: CONTRACT,
7021
+ operation,
7022
+ status,
7023
+ association_found: issue !== undefined,
7024
+ ...issue ? { issue_number: issue.number, issue_url: issue.url, node_id: String(issue.id) } : {},
7025
+ message: redactGithubDiagnostic(message)
7026
+ };
6750
7027
  }
6751
- function qaOutcomes(record) {
6752
- return Object.fromEntries(record.attestations.filter((item) => item.kind === "qa").flatMap((item) => item.acceptance_results).map((result) => [result.acceptance_id, { status: result.status, summary: result.summary }]));
7028
+ function ghFailure(operation, execution, message) {
7029
+ const retryable = execution.timed_out || /timeout|timed out|network|connection|temporar|rate limit|502|503|504/i.test(execution.stderr);
7030
+ return result(operation, retryable ? "retryable_failure" : "permanent_failure", `${message}: ${execution.output_exceeded ? "gh output limit exceeded" : execution.stderr || `gh exited ${execution.exit_code}`}`);
6753
7031
  }
6754
- async function ensureClaudeReviewRevision(root, taskId, projection) {
6755
- const current = await readTaskRecord(root, taskId);
6756
- const record = current.record;
6757
- if (!record)
6758
- throw new Error(`task ${taskId} has no TaskRecord`);
6759
- if (current.revision !== projection.projection.record_revision)
6760
- throw new Error("TaskRecord changed before Review revision preparation");
6761
- if (record.contract !== "assurance_kernel/task_record/v4")
6762
- return null;
6763
- if (!record.git_base_head)
6764
- throw new Error("Review revision requires a TaskRecord v4 git_base_head");
6765
- const manifest = captureReviewManifest(root, {
6766
- taskId,
6767
- baseHead: record.git_base_head,
6768
- scopeHint: record.intent_snapshot.scope_hint,
6769
- expectedDiffHash: projection.projection.diff_hash,
6770
- intentRevision: projection.projection.intent_revision,
6771
- intentContentHash: projection.projection.intent_content_hash,
6772
- recordRevision: projection.projection.record_revision,
6773
- workspaceRevision: projection.projection.workspace_revision,
6774
- lifecycle: projection.projection.lifecycle,
6775
- artifactState: projection.projection.artifact_state,
6776
- risk: record.intent_snapshot.risk,
6777
- outcomes: qaOutcomes(record)
6778
- });
7032
+ function createGhTransport(binary = "gh") {
6779
7033
  return {
6780
- contract: "assurance_kernel/review_revision/v1",
6781
- base_head: manifest.base_head,
6782
- review_tree: manifest.review_tree,
7034
+ run(args, options = {}) {
7035
+ return new Promise((complete) => {
7036
+ let stdout = Buffer.alloc(0);
7037
+ let stderr = Buffer.alloc(0);
7038
+ let timedOut = false;
7039
+ let outputExceeded = false;
7040
+ let timer;
7041
+ let settled = false;
7042
+ const finish = (exitCode, spawnError = "") => {
7043
+ if (settled)
7044
+ return;
7045
+ settled = true;
7046
+ if (timer)
7047
+ clearTimeout(timer);
7048
+ complete({
7049
+ exit_code: exitCode,
7050
+ stdout: stdout.toString("utf8"),
7051
+ stderr: `${stderr.toString("utf8")}${spawnError}`,
7052
+ timed_out: timedOut,
7053
+ output_exceeded: outputExceeded
7054
+ });
7055
+ };
7056
+ let child;
7057
+ try {
7058
+ child = spawn2(binary, args, {
7059
+ cwd: options.cwd,
7060
+ stdio: ["pipe", "pipe", "pipe"],
7061
+ env: process.env
7062
+ });
7063
+ } catch (error) {
7064
+ finish(1, error instanceof Error ? error.message : String(error));
7065
+ return;
7066
+ }
7067
+ const append = (current, chunk) => {
7068
+ const available = Math.max(0, MAX_GH_OUTPUT - stdout.length - stderr.length);
7069
+ if (chunk.length > available) {
7070
+ outputExceeded = true;
7071
+ child.kill("SIGKILL");
7072
+ }
7073
+ return available > 0 ? Buffer.concat([current, chunk.subarray(0, available)]) : current;
7074
+ };
7075
+ const { stdout: childOut, stderr: childErr, stdin: childIn } = child;
7076
+ if (!childOut || !childErr || !childIn) {
7077
+ finish(1, "gh was spawned without the stdio pipes this reader requires");
7078
+ return;
7079
+ }
7080
+ childOut.on("data", (chunk) => {
7081
+ stdout = append(stdout, chunk);
7082
+ });
7083
+ childErr.on("data", (chunk) => {
7084
+ stderr = append(stderr, chunk);
7085
+ });
7086
+ child.once("error", (error) => {
7087
+ finish(1, error.message);
7088
+ });
7089
+ childIn.once("error", (error) => {
7090
+ finish(1, error.message);
7091
+ });
7092
+ timer = setTimeout(() => {
7093
+ timedOut = true;
7094
+ child.kill("SIGKILL");
7095
+ }, GH_TIMEOUT_MS);
7096
+ child.once("close", (code) => {
7097
+ finish(code ?? 1);
7098
+ });
7099
+ try {
7100
+ childIn.end(options.stdin ?? "");
7101
+ } catch (error) {
7102
+ finish(1, error instanceof Error ? error.message : String(error));
7103
+ }
7104
+ });
7105
+ }
7106
+ };
7107
+ }
7108
+ function parseRepository(raw) {
7109
+ const value = JSON.parse(raw);
7110
+ if (!Number.isSafeInteger(value.id) || typeof value.full_name !== "string" || !value.full_name.includes("/"))
7111
+ throw new Error("gh returned malformed repository identity");
7112
+ return { id: value.id, name_with_owner: value.full_name };
7113
+ }
7114
+ function parseIssues(raw) {
7115
+ const parsed = JSON.parse(raw);
7116
+ const pages = Array.isArray(parsed) && parsed.every(Array.isArray) ? parsed.flat() : parsed;
7117
+ if (!Array.isArray(pages))
7118
+ throw new Error("gh returned malformed Issue list");
7119
+ return pages.filter((item) => Boolean(item) && typeof item === "object" && !("pull_request" in item)).map((item) => {
7120
+ if (!Number.isSafeInteger(item.id) || !Number.isSafeInteger(item.number) || typeof item.html_url !== "string" || typeof item.title !== "string" || typeof item.body !== "string" && item.body !== null || item.state !== "open" && item.state !== "closed")
7121
+ throw new Error("gh returned a malformed Issue");
7122
+ return {
7123
+ id: item.id,
7124
+ number: item.number,
7125
+ url: item.html_url,
7126
+ title: item.title,
7127
+ body: typeof item.body === "string" ? item.body : "",
7128
+ state: item.state,
7129
+ state_reason: typeof item.state_reason === "string" ? item.state_reason.toLowerCase() : null
7130
+ };
7131
+ });
7132
+ }
7133
+ function parseSubIssueNumbers(raw) {
7134
+ const parsed = JSON.parse(raw);
7135
+ const pages = Array.isArray(parsed) && parsed.every(Array.isArray) ? parsed.flat() : parsed;
7136
+ if (!Array.isArray(pages))
7137
+ throw new Error("gh returned malformed Sub-issue list");
7138
+ return pages.map((item, index) => {
7139
+ const number = item?.number;
7140
+ if (typeof number !== "number" || !Number.isSafeInteger(number))
7141
+ throw new Error(`gh returned a malformed Sub-issue entry at ${index}`);
7142
+ return number;
7143
+ });
7144
+ }
7145
+ async function snapshot(root, gh, operation) {
7146
+ const repositoryExecution = await gh.run(["api", "repos/{owner}/{repo}"], { cwd: root });
7147
+ if (repositoryExecution.exit_code !== 0 || repositoryExecution.output_exceeded)
7148
+ return ghFailure(operation, repositoryExecution, "cannot resolve GitHub repository");
7149
+ let repository;
7150
+ try {
7151
+ repository = parseRepository(repositoryExecution.stdout);
7152
+ } catch (error) {
7153
+ return result(operation, "permanent_failure", error instanceof Error ? error.message : String(error));
7154
+ }
7155
+ const issues = [];
7156
+ for (let page = 1;page <= MAX_SNAPSHOT_PAGES; page += 1) {
7157
+ const issuesExecution = await gh.run(["api", `repos/${repository.name_with_owner}/issues?state=all&per_page=100&page=${page}`], { cwd: root });
7158
+ if (issuesExecution.exit_code !== 0 || issuesExecution.output_exceeded)
7159
+ return ghFailure(operation, issuesExecution, "cannot query GitHub Issues");
7160
+ let raw;
7161
+ try {
7162
+ raw = JSON.parse(issuesExecution.stdout);
7163
+ } catch (error) {
7164
+ return result(operation, "permanent_failure", error instanceof Error ? error.message : String(error));
7165
+ }
7166
+ if (!Array.isArray(raw))
7167
+ return result(operation, "permanent_failure", "gh returned malformed Issue list");
7168
+ const pageCount = raw.length;
7169
+ try {
7170
+ issues.push(...parseIssues(issuesExecution.stdout));
7171
+ } catch (error) {
7172
+ return result(operation, "permanent_failure", error instanceof Error ? error.message : String(error));
7173
+ }
7174
+ if (pageCount < 100)
7175
+ break;
7176
+ if (page === MAX_SNAPSHOT_PAGES)
7177
+ return result(operation, "permanent_failure", "too many GitHub Issues to snapshot");
7178
+ }
7179
+ return { repository, issues };
7180
+ }
7181
+ function findIssue(issues, primary, required) {
7182
+ const candidates = issues.filter((issue2) => primary.every((needle) => issue2.body.includes(needle)));
7183
+ if (candidates.length === 0)
7184
+ return { kind: "missing" };
7185
+ if (candidates.length !== 1)
7186
+ return { kind: "ambiguous", message: `multiple Issues contain identity marker ${primary[0]}` };
7187
+ const issue = candidates[0];
7188
+ for (const expected of [PROTOCOL_MARKER, ...required]) {
7189
+ if (countLiteral(issue.body, expected) !== 1)
7190
+ return { kind: "ambiguous", message: `Issue #${issue.number} has missing or duplicate identity markers` };
7191
+ }
7192
+ return { kind: "found", issue };
7193
+ }
7194
+ function initiativeLookup(issues, repositoryId, initiativeId) {
7195
+ const initiative = marker("initiative-id", initiativeId);
7196
+ return findIssue(issues, [initiative, KIND_INITIATIVE_MARKER], [marker("repo-id", repositoryId), initiative]);
7197
+ }
7198
+ function ownershipMarkerValue(body, name) {
7199
+ const values = [...body.matchAll(new RegExp(`<!-- immune-brain:${name}=([A-Za-z0-9][A-Za-z0-9._-]{0,127}) -->`, "g"))];
7200
+ return values.length === 1 ? values[0][1] : null;
7201
+ }
7202
+ function taskLookup(issues, repositoryId, taskId) {
7203
+ const task = marker("task-id", taskId);
7204
+ const base = findIssue(issues, [task, KIND_TASK_MARKER], [marker("repo-id", repositoryId), task]);
7205
+ if (base.kind !== "found")
7206
+ return base;
7207
+ for (const name of ["task-id", "initiative-id", "slice-id"]) {
7208
+ if (!ownershipMarkerValue(base.issue.body, name))
7209
+ return { kind: "ambiguous", message: `Issue #${base.issue.number} has missing or duplicate ${name} ownership markers` };
7210
+ }
7211
+ return base;
7212
+ }
7213
+ function ownedTaskLookup(issues, repositoryId, taskId, initiativeId, sliceId) {
7214
+ const base = taskLookup(issues, repositoryId, taskId);
7215
+ if (base.kind !== "found")
7216
+ return base;
7217
+ return base.issue.body.includes(marker("initiative-id", initiativeId)) && base.issue.body.includes(marker("slice-id", sliceId)) ? base : { kind: "ambiguous", message: `Issue #${base.issue.number} belongs to another Initiative or Slice; Task ownership is immutable` };
7218
+ }
7219
+ async function readSubIssueNumbers(root, gh, operation, repository, parentNumber) {
7220
+ const listed = await gh.run(["api", "--paginate", "--slurp", `repos/${repository.name_with_owner}/issues/${parentNumber}/sub_issues?per_page=100`], { cwd: root });
7221
+ if (listed.exit_code !== 0 || listed.output_exceeded)
7222
+ return ghFailure(operation, listed, "cannot read native Sub-issue relations");
7223
+ try {
7224
+ return parseSubIssueNumbers(listed.stdout);
7225
+ } catch (error) {
7226
+ return result(operation, "permanent_failure", error instanceof Error ? error.message : String(error));
7227
+ }
7228
+ }
7229
+ async function readBlockedByIds(root, gh, operation, repository, childNumber) {
7230
+ const listed = await gh.run(["api", "--paginate", "--slurp", `repos/${repository.name_with_owner}/issues/${childNumber}/dependencies/blocked_by?per_page=100`], { cwd: root });
7231
+ if (listed.exit_code !== 0 || listed.output_exceeded)
7232
+ return ghFailure(operation, listed, "cannot read native blocked_by relations");
7233
+ try {
7234
+ const pages = JSON.parse(listed.stdout);
7235
+ if (!Array.isArray(pages) || pages.some((page) => !Array.isArray(page)))
7236
+ throw new Error("gh returned malformed blocked_by pages");
7237
+ const ids = pages.flat().map((item, index) => {
7238
+ const id = item?.issue_id ?? item?.id;
7239
+ if (!Number.isSafeInteger(id))
7240
+ throw new Error(`gh returned malformed blocked_by entry at ${index}`);
7241
+ return id;
7242
+ });
7243
+ if (new Set(ids).size !== ids.length)
7244
+ return result(operation, "ambiguous_remote_state", `Issue #${childNumber} has duplicate native blocked_by relations`);
7245
+ return ids;
7246
+ } catch (error) {
7247
+ return result(operation, "permanent_failure", error instanceof Error ? error.message : String(error));
7248
+ }
7249
+ }
7250
+ async function observeGithubInitiative(root, initiativeId, gh = createGhTransport()) {
7251
+ const id = identifier(initiativeId, "initiative_id");
7252
+ const source = await snapshot(resolve7(root), gh, "create-initiative");
7253
+ if ("contract" in source)
7254
+ throw new Error(source.message);
7255
+ const parent = initiativeLookup(source.issues, source.repository.id, id);
7256
+ if (parent.kind === "missing")
7257
+ throw new Error(`Initiative ${id} is not published`);
7258
+ if (parent.kind === "ambiguous")
7259
+ throw new Error(parent.message);
7260
+ const subIssueNumbers = await readSubIssueNumbers(root, gh, "create-initiative", source.repository, parent.issue.number);
7261
+ if (!Array.isArray(subIssueNumbers))
7262
+ throw new Error(subIssueNumbers.message);
7263
+ if (new Set(subIssueNumbers).size !== subIssueNumbers.length)
7264
+ throw new Error(`Initiative ${id} has duplicate native Sub-issue relations`);
7265
+ const tasks = subIssueNumbers.map((issueNumber) => {
7266
+ const matches = source.issues.filter((issue2) => issue2.number === issueNumber);
7267
+ if (matches.length !== 1)
7268
+ throw new Error(`Initiative ${id} references an unreadable Sub-issue #${issueNumber}`);
7269
+ const issue = matches[0];
7270
+ const taskId = ownershipMarkerValue(issue.body, "task-id");
7271
+ const sliceId = ownershipMarkerValue(issue.body, "slice-id");
7272
+ if (!taskId || !sliceId || ownershipMarkerValue(issue.body, "initiative-id") !== id)
7273
+ throw new Error(`Sub-issue #${issueNumber} has invalid Initiative ownership markers`);
7274
+ const owned = ownedTaskLookup(source.issues, source.repository.id, taskId, id, sliceId);
7275
+ if (owned.kind !== "found" || owned.issue.number !== issueNumber)
7276
+ throw new Error(owned.kind === "ambiguous" ? owned.message : `Sub-issue #${issueNumber} has invalid Task ownership`);
7277
+ return { task_id: taskId, slice_id: sliceId, issue_number: issueNumber, issue_id: issue.id };
7278
+ });
7279
+ if (new Set(tasks.map((task) => task.task_id)).size !== tasks.length)
7280
+ throw new Error(`Initiative ${id} has duplicate Task identities`);
7281
+ if (new Set(tasks.map((task) => task.slice_id)).size !== tasks.length)
7282
+ throw new Error(`Initiative ${id} has duplicate Slice identities`);
7283
+ const taskByIssueId = new Map(tasks.map((task) => [task.issue_id, task.task_id]));
7284
+ const observed = [];
7285
+ for (const task of tasks.sort((left, right) => left.task_id < right.task_id ? -1 : left.task_id > right.task_id ? 1 : 0)) {
7286
+ const blockerIds = await readBlockedByIds(root, gh, "create-initiative", source.repository, task.issue_number);
7287
+ if (!Array.isArray(blockerIds))
7288
+ throw new Error(blockerIds.message);
7289
+ const blockedBy = blockerIds.map((blockerId) => {
7290
+ const blocker = taskByIssueId.get(blockerId);
7291
+ if (!blocker)
7292
+ throw new Error(`Task ${task.task_id} depends on an Issue outside Initiative ${id}`);
7293
+ return blocker;
7294
+ }).sort();
7295
+ observed.push({
7296
+ task_id: task.task_id,
7297
+ slice_id: task.slice_id,
7298
+ issue_number: task.issue_number,
7299
+ blocked_by: blockedBy
7300
+ });
7301
+ }
7302
+ return {
7303
+ contract: "immune_brain/github_initiative_observation/v1",
7304
+ initiative_id: id,
7305
+ issue_number: parent.issue.number,
7306
+ tasks: observed
7307
+ };
7308
+ }
7309
+
7310
+ // plugins/immune-brain/runtime/unattended/batch_plan.ts
7311
+ var ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
7312
+ var DEFAULT_DEADLINE_MS = 8 * 60 * 60 * 1000;
7313
+ var DEFAULT_QA_FAILURE_LIMIT = 2;
7314
+ function compareIds(left, right) {
7315
+ return left < right ? -1 : left > right ? 1 : 0;
7316
+ }
7317
+ function positiveSafeInteger(value, name) {
7318
+ if (!Number.isSafeInteger(value) || value <= 0)
7319
+ throw new Error(`${name} must be a positive safe integer`);
7320
+ return value;
7321
+ }
7322
+ var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
7323
+ function timestamp(value, name) {
7324
+ if (typeof value !== "string" || !ISO_TIMESTAMP_PATTERN.test(value))
7325
+ throw new Error(`${name} must be an ISO timestamp`);
7326
+ const milliseconds = Date.parse(value);
7327
+ if (!Number.isFinite(milliseconds))
7328
+ throw new Error(`${name} must be an ISO timestamp`);
7329
+ return { milliseconds, iso: new Date(milliseconds).toISOString() };
7330
+ }
7331
+ function normalizeObservation(value, initiativeSlug) {
7332
+ if (!value || value.contract !== "immune_brain/github_initiative_observation/v1")
7333
+ throw new Error("tracker returned an invalid Initiative observation contract");
7334
+ if (value.initiative_id !== initiativeSlug)
7335
+ throw new Error("tracker returned an observation for another Initiative");
7336
+ if (!Number.isSafeInteger(value.issue_number) || value.issue_number <= 0)
7337
+ throw new Error("tracker returned an invalid Initiative Issue number");
7338
+ if (!Array.isArray(value.tasks))
7339
+ throw new Error("tracker returned an invalid Initiative Task list");
7340
+ const taskIds = new Set;
7341
+ const sliceIds = new Set;
7342
+ const tasks = value.tasks.map((task, index) => {
7343
+ if (!task || typeof task !== "object")
7344
+ throw new Error(`tracker Task ${index} is invalid`);
7345
+ if (!ID_PATTERN2.test(task.task_id))
7346
+ throw new Error(`tracker Task ${index} has an invalid task_id`);
7347
+ if (!ID_PATTERN2.test(task.slice_id))
7348
+ throw new Error(`tracker Task ${index} has an invalid slice_id`);
7349
+ if (!Number.isSafeInteger(task.issue_number) || task.issue_number <= 0)
7350
+ throw new Error(`tracker Task ${task.task_id} has an invalid Issue number`);
7351
+ if (!Array.isArray(task.blocked_by) || task.blocked_by.some((id) => typeof id !== "string" || !ID_PATTERN2.test(id)))
7352
+ throw new Error(`tracker Task ${task.task_id} has invalid blocked_by dependencies`);
7353
+ if (taskIds.has(task.task_id))
7354
+ throw new Error(`tracker returned duplicate Task ${task.task_id}`);
7355
+ if (sliceIds.has(task.slice_id))
7356
+ throw new Error(`tracker returned duplicate Slice ${task.slice_id}`);
7357
+ taskIds.add(task.task_id);
7358
+ sliceIds.add(task.slice_id);
7359
+ const blockedBy = [...task.blocked_by].sort();
7360
+ if (new Set(blockedBy).size !== blockedBy.length || blockedBy.includes(task.task_id))
7361
+ throw new Error(`tracker Task ${task.task_id} has invalid blocked_by dependencies`);
7362
+ return { ...task, blocked_by: blockedBy };
7363
+ });
7364
+ for (const task of tasks) {
7365
+ const unknown = task.blocked_by.find((id) => !taskIds.has(id));
7366
+ if (unknown)
7367
+ throw new Error(`tracker Task ${task.task_id} depends on unknown Task ${unknown}`);
7368
+ }
7369
+ return { ...value, tasks: tasks.sort((left, right) => compareIds(left.task_id, right.task_id)) };
7370
+ }
7371
+ function dependencyOrder(observation) {
7372
+ const remaining = new Map(observation.tasks.map((task) => [task.task_id, task]));
7373
+ const done = new Set;
7374
+ const order = [];
7375
+ while (remaining.size) {
7376
+ const ready = [...remaining.values()].filter((task) => task.blocked_by.every((id) => done.has(id))).sort((left, right) => compareIds(left.task_id, right.task_id));
7377
+ if (!ready.length)
7378
+ throw new Error("Initiative Task dependencies must form an acyclic graph");
7379
+ for (const task of ready) {
7380
+ remaining.delete(task.task_id);
7381
+ done.add(task.task_id);
7382
+ order.push(task);
7383
+ }
7384
+ }
7385
+ return order;
7386
+ }
7387
+ function dependencyClosures(order) {
7388
+ const closures = new Map;
7389
+ for (const task of order) {
7390
+ const closure = new Set;
7391
+ for (const dependency of task.blocked_by) {
7392
+ closure.add(dependency);
7393
+ for (const transitive of closures.get(dependency) ?? [])
7394
+ closure.add(transitive);
7395
+ }
7396
+ closures.set(task.task_id, [...closure].sort());
7397
+ }
7398
+ return closures;
7399
+ }
7400
+ function budget(input, enrollableCount, confirmationTime) {
7401
+ const maxChildren = input.budget?.max_children === undefined ? positiveSafeInteger(enrollableCount, "budget.max_children") : positiveSafeInteger(input.budget.max_children, "budget.max_children");
7402
+ if (maxChildren > enrollableCount)
7403
+ throw new Error("budget.max_children exceeds the enrollable Child count");
7404
+ const qaFailureLimit = input.budget?.qa_failure_limit === undefined ? DEFAULT_QA_FAILURE_LIMIT : positiveSafeInteger(input.budget.qa_failure_limit, "budget.qa_failure_limit");
7405
+ const deadline = input.budget?.deadline_at === undefined ? new Date(confirmationTime.milliseconds + DEFAULT_DEADLINE_MS).toISOString() : timestamp(input.budget.deadline_at, "budget.deadline_at").iso;
7406
+ if (Date.parse(deadline) <= confirmationTime.milliseconds)
7407
+ throw new Error("budget.deadline_at must be later than confirmation_time");
7408
+ return { max_children: maxChildren, deadline_at: deadline, qa_failure_limit: qaFailureLimit };
7409
+ }
7410
+ async function projectBatchPlan(root, initiativeSlug, input, readInitiative = observeGithubInitiative) {
7411
+ if (!ID_PATTERN2.test(initiativeSlug))
7412
+ throw new Error("initiative_slug is invalid");
7413
+ if (!input || typeof input !== "object")
7414
+ throw new Error("batch plan input is required");
7415
+ const confirmationTime = timestamp(input.confirmation_time, "confirmation_time");
7416
+ const trackerObservation = normalizeObservation(await readInitiative(root, initiativeSlug), initiativeSlug);
7417
+ const order = dependencyOrder(trackerObservation);
7418
+ const closures = dependencyClosures(order);
7419
+ const children = [];
7420
+ for (const task of order) {
7421
+ const blockedBy = closures.get(task.task_id);
7422
+ const child = { task_id: task.task_id, slice_id: task.slice_id, blocked_by: blockedBy };
7423
+ const tombstone = readTaskTombstone(root, task.task_id);
7424
+ const record = readTaskRecordRaw(root, task.task_id).record;
7425
+ if (tombstone && record)
7426
+ throw new Error(`Task ${task.task_id} has conflicting settled and active state`);
7427
+ if (tombstone) {
7428
+ children.push({ ...child, status: "already_settled", reason: null, intent_path: null, intent_revision: null, intent_content_hash: null });
7429
+ continue;
7430
+ }
7431
+ if (record) {
7432
+ children.push({ ...child, status: "already_owned", reason: null, intent_path: null, intent_revision: null, intent_content_hash: null });
7433
+ continue;
7434
+ }
7435
+ const intentPath = `docs/plans/${task.task_id}.intent.json`;
7436
+ try {
7437
+ const read = observeTaskIntent(root, task.task_id, intentPath);
7438
+ children.push({
7439
+ ...child,
7440
+ status: read.intent.risk === "critical" ? "needs_human" : "enrollable",
7441
+ reason: read.intent.risk === "critical" ? "critical" : null,
7442
+ intent_path: read.intent_ref.path,
7443
+ intent_revision: read.intent_ref.revision,
7444
+ intent_content_hash: read.content_hash
7445
+ });
7446
+ } catch (error) {
7447
+ if (!(error instanceof TaskIntentObservationError))
7448
+ throw error;
7449
+ children.push({ ...child, status: "needs_human", reason: "invalid_intent", intent_path: null, intent_revision: null, intent_content_hash: null });
7450
+ }
7451
+ }
7452
+ const directDependencies = new Map(order.map((task) => [task.task_id, task.blocked_by]));
7453
+ const childById = new Map(children.map((child) => [child.task_id, child]));
7454
+ for (const child of children) {
7455
+ if (child.status !== "enrollable")
7456
+ continue;
7457
+ if (directDependencies.get(child.task_id).some((id) => {
7458
+ const status = childById.get(id).status;
7459
+ return status === "already_owned" || status === "needs_human" || status === "blocked";
7460
+ })) {
7461
+ child.status = "blocked";
7462
+ child.reason = "dependency_unavailable";
7463
+ }
7464
+ }
7465
+ const enrollable = children.filter((child) => child.status === "enrollable").map((child) => ({
7466
+ task_id: child.task_id,
7467
+ intent_path: child.intent_path,
7468
+ intent_revision: child.intent_revision,
7469
+ intent_content_hash: child.intent_content_hash,
7470
+ blocked_by: child.blocked_by.filter((id) => childById.get(id)?.status === "enrollable")
7471
+ }));
7472
+ if (!enrollable.length)
7473
+ throw new Error("batch plan has no enrollable children; nothing to confirm");
7474
+ const planDigest = `sha256:${createHash14("sha256").update(stableStringify(enrollable)).digest("hex")}`;
7475
+ return {
7476
+ contract: "assurance_kernel/batch_plan/v1",
7477
+ initiative_slug: initiativeSlug,
7478
+ confirmation_time: confirmationTime.iso,
7479
+ tracker_observation: trackerObservation,
7480
+ children,
7481
+ enrollable,
7482
+ plan_digest: planDigest,
7483
+ budget: budget(input, enrollable.length, confirmationTime)
7484
+ };
7485
+ }
7486
+
7487
+ // plugins/immune-brain/runtime/unattended/batch_runner.ts
7488
+ import { spawnSync as spawnSync5 } from "node:child_process";
7489
+ import { existsSync as existsSync5 } from "node:fs";
7490
+ import { join as join9 } from "node:path";
7491
+
7492
+ // plugins/immune-brain/runtime/kernel/batch_authority.ts
7493
+ import { createHash as createHash15 } from "node:crypto";
7494
+ var BATCH_AUTHORITY_CAPABILITY_BRAND = Symbol.for("assurance-kernel.batch-authority-capability-brand");
7495
+ var GIT_COMMIT_ID2 = /^[a-f0-9]{40}$/;
7496
+
7497
+ class BatchAuthorizationExpiryError extends Error {
7498
+ constructor(message) {
7499
+ super(message);
7500
+ this.name = "BatchAuthorizationExpiryError";
7501
+ }
7502
+ }
7503
+ function sha256Hex3(bytes) {
7504
+ return createHash15("sha256").update(bytes).digest("hex");
7505
+ }
7506
+ function stableStringify3(value) {
7507
+ if (value === null || typeof value !== "object")
7508
+ return JSON.stringify(value);
7509
+ if (Array.isArray(value))
7510
+ return `[${value.map((entry) => stableStringify3(entry)).join(",")}]`;
7511
+ const record = value;
7512
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify3(record[key])}`).join(",")}}`;
7513
+ }
7514
+ function computeBatchPlanDigest(children) {
7515
+ const canonical = children.map((child) => ({
7516
+ blocked_by: [...child.blocked_by],
7517
+ intent_content_hash: child.intent_content_hash,
7518
+ intent_path: child.intent_path,
7519
+ intent_revision: child.intent_revision,
7520
+ task_id: child.task_id
7521
+ }));
7522
+ return `sha256:${sha256Hex3(stableStringify3(canonical))}`;
7523
+ }
7524
+ function requireNonEmpty(binding) {
7525
+ const missing = [];
7526
+ for (const key of [
7527
+ "batch_id",
7528
+ "initiative_slug",
7529
+ "plan_digest",
7530
+ "branch",
7531
+ "base_head",
7532
+ "actor_id",
7533
+ "confirmation_ref",
7534
+ "expires_at",
7535
+ "nonce"
7536
+ ]) {
7537
+ const value = binding[key];
7538
+ if (value === undefined || value === null || value === "")
7539
+ missing.push(key);
7540
+ }
7541
+ if (!binding.budget || typeof binding.budget !== "object")
7542
+ missing.push("budget");
7543
+ if (missing.length > 0)
7544
+ throw new Error(`batch authorization binding is incomplete: ${missing.join(", ")}`);
7545
+ }
7546
+ function validateBudget(budget2, issuedAt) {
7547
+ if (!Number.isInteger(budget2.max_children) || budget2.max_children <= 0)
7548
+ throw new Error("batch budget max_children must be a positive integer");
7549
+ if (!Number.isInteger(budget2.qa_failure_limit) || budget2.qa_failure_limit <= 0)
7550
+ throw new Error("batch budget qa_failure_limit must be a positive integer");
7551
+ const deadline = Date.parse(budget2.deadline_at);
7552
+ if (Number.isNaN(deadline) || deadline <= Date.parse(issuedAt))
7553
+ throw new Error("batch budget must have a future deadline_at");
7554
+ }
7555
+ function validateChildren(children, planDigest) {
7556
+ if (!Array.isArray(children) || children.length === 0)
7557
+ throw new Error("batch authorization requires a non-empty child plan");
7558
+ const seen = new Set;
7559
+ for (const child of children) {
7560
+ if (!child || typeof child !== "object")
7561
+ throw new Error("batch plan child must be an object");
7562
+ for (const key of ["task_id", "intent_path", "intent_content_hash"]) {
7563
+ if (typeof child[key] !== "string" || child[key] === "")
7564
+ throw new Error(`batch plan child ${key} must be a non-empty string`);
7565
+ }
7566
+ if (!Number.isInteger(child.intent_revision) || child.intent_revision <= 0)
7567
+ throw new Error("batch plan child intent_revision must be a positive integer");
7568
+ if (!Array.isArray(child.blocked_by) || child.blocked_by.some((id) => typeof id !== "string" || id === ""))
7569
+ throw new Error("batch plan child blocked_by must be an array of task ids");
7570
+ if (seen.has(child.task_id))
7571
+ throw new Error(`batch plan child ${child.task_id} appears more than once`);
7572
+ seen.add(child.task_id);
7573
+ }
7574
+ for (const child of children) {
7575
+ for (const blocker of child.blocked_by) {
7576
+ if (!seen.has(blocker))
7577
+ throw new Error(`batch plan child ${child.task_id} is blocked by ${blocker}, which is not in the confirmed plan`);
7578
+ }
7579
+ }
7580
+ const dependencies = new Map(children.map((child) => [child.task_id, child.blocked_by]));
7581
+ const visiting = new Set;
7582
+ const visited = new Set;
7583
+ function visit(taskId) {
7584
+ if (visiting.has(taskId))
7585
+ throw new Error(`batch plan dependency cycle at ${taskId}`);
7586
+ if (visited.has(taskId))
7587
+ return;
7588
+ visiting.add(taskId);
7589
+ for (const blocker of dependencies.get(taskId))
7590
+ visit(blocker);
7591
+ visiting.delete(taskId);
7592
+ visited.add(taskId);
7593
+ }
7594
+ for (const child of children)
7595
+ visit(child.task_id);
7596
+ if (computeBatchPlanDigest(children) !== planDigest)
7597
+ throw new Error("batch plan digest does not match the confirmed child plan");
7598
+ }
7599
+ function createBatchAuthorityRegistry() {
7600
+ const inner = createCapabilityRegistry(BATCH_AUTHORITY_CAPABILITY_BRAND, {
7601
+ validateBinding(binding, issuedAt) {
7602
+ requireNonEmpty(binding);
7603
+ if (binding.actor_id !== "user")
7604
+ throw new Error("batch authorization requires a literal-user actor_id");
7605
+ if (!GIT_COMMIT_ID2.test(binding.base_head))
7606
+ throw new Error("batch authorization base_head must be a committed 40-hex commit id");
7607
+ const expires = Date.parse(binding.expires_at);
7608
+ if (Number.isNaN(expires) || expires <= Date.parse(issuedAt))
7609
+ throw new Error("batch authorization must have a future expiry");
7610
+ validateBudget(binding.budget, issuedAt);
7611
+ },
7612
+ validateAndProject(state, expected, now) {
7613
+ if (!Number.isFinite(now))
7614
+ throw new Error("batch authorization requires a valid clock");
7615
+ const expires = Date.parse(state.expires_at);
7616
+ if (Number.isNaN(expires) || expires <= now)
7617
+ throw new BatchAuthorizationExpiryError("batch authorization has expired");
7618
+ if (Date.parse(state.budget.deadline_at) <= now)
7619
+ throw new BatchAuthorizationExpiryError("batch authorization deadline has expired");
7620
+ for (const key of Object.keys(expected)) {
7621
+ if (key === "budget") {
7622
+ const a = state.budget ?? {};
7623
+ const b = expected.budget ?? {};
7624
+ if (a.max_children !== b.max_children || a.deadline_at !== b.deadline_at || a.qa_failure_limit !== b.qa_failure_limit)
7625
+ throw new Error("batch authorization budget mismatch");
7626
+ continue;
7627
+ }
7628
+ if (state[key] !== expected[key])
7629
+ throw new Error(`batch authorization ${key} mismatch`);
7630
+ }
7631
+ return {
7632
+ batch_id: state.batch_id,
7633
+ initiative_slug: state.initiative_slug,
7634
+ plan_digest: state.plan_digest,
7635
+ branch: state.branch,
7636
+ base_head: state.base_head,
7637
+ budget: { ...state.budget },
7638
+ actor_id: state.actor_id,
7639
+ confirmation_ref: state.confirmation_ref,
7640
+ issued_at: state.issued_at,
7641
+ expires_at: state.expires_at,
7642
+ nonce: state.nonce
7643
+ };
7644
+ }
7645
+ }, "batch authorization");
7646
+ const plans = new WeakMap;
7647
+ const consumed = new WeakMap;
7648
+ function planOf(capability) {
7649
+ const plan = plans.get(capability);
7650
+ if (!plan)
7651
+ throw new Error("batch authorization capability is not recognized by this registry");
7652
+ return plan;
7653
+ }
7654
+ function slotsOf(capability) {
7655
+ const slots = consumed.get(capability);
7656
+ if (!slots)
7657
+ throw new Error("batch authorization capability is not recognized by this registry");
7658
+ return slots;
7659
+ }
7660
+ return {
7661
+ brand: inner.brand,
7662
+ issue(binding, children, issuedAt = new Date().toISOString()) {
7663
+ requireNonEmpty(binding);
7664
+ validateChildren(children, binding.plan_digest);
7665
+ const capability = inner.issue(binding, issuedAt);
7666
+ plans.set(capability, children.map((child) => ({ ...child, blocked_by: [...child.blocked_by] })));
7667
+ consumed.set(capability, new Set);
7668
+ return capability;
7669
+ },
7670
+ inspect(capability, expected, now = Date.now()) {
7671
+ planOf(capability);
7672
+ return inner.inspect(capability, expected, now);
7673
+ },
7674
+ children(capability) {
7675
+ return planOf(capability).map((child) => ({ ...child, blocked_by: [...child.blocked_by] }));
7676
+ },
7677
+ consumedChildren(capability) {
7678
+ return [...slotsOf(capability)];
7679
+ },
7680
+ isChildConsumed(capability, taskId) {
7681
+ return slotsOf(capability).has(taskId);
7682
+ },
7683
+ consumeChild(capability, expected, taskId, now = Date.now()) {
7684
+ const validated = this.inspect(capability, expected, now);
7685
+ const plan = planOf(capability);
7686
+ if (!plan.some((child) => child.task_id === taskId))
7687
+ throw new Error(`batch_child_not_in_plan: ${taskId}`);
7688
+ const slots = slotsOf(capability);
7689
+ if (slots.has(taskId))
7690
+ throw new Error(`batch_child_slot_consumed: ${taskId}`);
7691
+ slots.add(taskId);
7692
+ return validated;
7693
+ },
7694
+ releaseChild(capability, taskId) {
7695
+ slotsOf(capability).delete(taskId);
7696
+ },
7697
+ isExhausted(capability) {
7698
+ return slotsOf(capability).size >= planOf(capability).length;
7699
+ }
7700
+ };
7701
+ }
7702
+ function deriveChildEnrollment(root, registry, input) {
7703
+ const validated = registry.inspect(input.capability, input.binding, Date.parse(input.now));
7704
+ const child = registry.children(input.capability).find((entry) => entry.task_id === input.task_id);
7705
+ if (!child)
7706
+ throw new Error(`batch_child_not_in_plan: ${input.task_id}`);
7707
+ if (registry.isChildConsumed(input.capability, input.task_id))
7708
+ throw new Error(`batch_child_slot_consumed: ${input.task_id}`);
7709
+ if (!GIT_COMMIT_ID2.test(input.expected_head))
7710
+ throw new Error("batch_head_lineage_broken: expected_head is not a commit id");
7711
+ const preparation = preparePiCanary(root, { task_id: input.task_id, now: input.now });
7712
+ if (!preparation.git_base_head)
7713
+ throw new Error(preparation.git_error ?? "enrollment requires a committed Git HEAD");
7714
+ if (preparation.git_base_head !== input.expected_head)
7715
+ throw new Error(`batch_head_lineage_broken: expected ${input.expected_head}, found ${preparation.git_base_head}`);
7716
+ if (!preparation.intent)
7717
+ throw new Error(`batch_child_intent_changed: ${input.task_id} intent sidecar is unreadable`);
7718
+ if (preparation.intent.path !== child.intent_path || preparation.intent.revision !== child.intent_revision || preparation.intent.content_hash !== child.intent_content_hash)
7719
+ throw new Error(`batch_child_intent_changed: ${input.task_id}`);
7720
+ if (registry.consumedChildren(input.capability).length === 0 && input.expected_head !== validated.base_head)
7721
+ throw new Error(`batch_head_lineage_broken: the first child must enroll on the confirmed base_head ${validated.base_head}, not ${input.expected_head}`);
7722
+ return {
7723
+ child,
7724
+ preparation,
7725
+ binding: {
7726
+ task_id: child.task_id,
7727
+ intent_path: child.intent_path,
7728
+ intent_revision: child.intent_revision,
7729
+ intent_content_hash: child.intent_content_hash,
7730
+ preparation_digest: preparation.digest,
7731
+ actor_id: validated.actor_id,
7732
+ confirmation_ref: validated.confirmation_ref,
7733
+ expires_at: validated.expires_at,
7734
+ nonce: `${validated.nonce}:${child.task_id}`
7735
+ }
7736
+ };
7737
+ }
7738
+
7739
+ // plugins/immune-brain/runtime/unattended/batch_git.ts
7740
+ import { spawnSync as spawnSync4 } from "node:child_process";
7741
+ import { randomUUID as randomUUID5 } from "node:crypto";
7742
+ import {
7743
+ constants as constants3,
7744
+ closeSync as closeSync4,
7745
+ existsSync as existsSync3,
7746
+ lstatSync as lstatSync6,
7747
+ mkdirSync as mkdirSync3,
7748
+ openSync as openSync4,
7749
+ realpathSync as realpathSync7,
7750
+ renameSync as renameSync2,
7751
+ rmSync as rmSync4,
7752
+ writeFileSync as writeFileSync3
7753
+ } from "node:fs";
7754
+ import { dirname as dirname4, join as join7 } from "node:path";
7755
+ var DEFAULT_GIT_ENV = {
7756
+ ...process.env,
7757
+ GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || "Immune-Brain Batch",
7758
+ GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL || "immune-brain@local",
7759
+ GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || "Immune-Brain Batch",
7760
+ GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || "immune-brain@local"
7761
+ };
7762
+ function runBatchGitPreflight(input) {
7763
+ const { root, initiative_slug: initiativeSlug, base_head: baseHead } = input;
7764
+ const branch = `imm/${initiativeSlug}`;
7765
+ const toplevelResult = spawnSync4("git", ["-C", root, "rev-parse", "--show-toplevel"], {
7766
+ encoding: "utf8",
7767
+ stdio: ["ignore", "pipe", "pipe"]
7768
+ });
7769
+ if (toplevelResult.status !== 0 || !toplevelResult.stdout.trim()) {
7770
+ return {
7771
+ ok: false,
7772
+ reason: "not_a_git_repository",
7773
+ message: "root must be a Git repository with a committed HEAD"
7774
+ };
7775
+ }
7776
+ let realToplevel;
7777
+ let realRoot;
7778
+ try {
7779
+ realToplevel = realpathSync7(toplevelResult.stdout.trim());
7780
+ realRoot = realpathSync7(root);
7781
+ } catch {
7782
+ return {
7783
+ ok: false,
7784
+ reason: "not_repository_root",
7785
+ message: "failed to resolve repository root"
7786
+ };
7787
+ }
7788
+ if (realToplevel !== realRoot) {
7789
+ return {
7790
+ ok: false,
7791
+ reason: "not_repository_root",
7792
+ message: "batch root must be the top-level repository root, not a subdirectory"
7793
+ };
7794
+ }
7795
+ const flaggedPreflight = getUnsupportedIndexFlags(root);
7796
+ if (flaggedPreflight.length > 0) {
7797
+ return {
7798
+ ok: false,
7799
+ reason: "dirty_working_tree",
7800
+ message: `unsupported index flags (assume-unchanged/skip-worktree) detected: ${flaggedPreflight.join(", ")}`
7801
+ };
7802
+ }
7803
+ const statusResult = spawnSync4("git", ["-C", root, "status", "--porcelain", "--untracked-files=all", "--ignore-submodules=none"], {
7804
+ encoding: "utf8",
7805
+ stdio: ["ignore", "pipe", "pipe"]
7806
+ });
7807
+ if (statusResult.status !== 0) {
7808
+ return {
7809
+ ok: false,
7810
+ reason: "dirty_working_tree",
7811
+ message: statusResult.stderr?.trim() || "failed to inspect working tree status"
7812
+ };
7813
+ }
7814
+ if (statusResult.stdout.trim().length > 0) {
7815
+ return {
7816
+ ok: false,
7817
+ reason: "dirty_working_tree",
7818
+ message: "working tree is dirty before batch preflight"
7819
+ };
7820
+ }
7821
+ const headResult = spawnSync4("git", ["-C", root, "rev-parse", "--verify", "HEAD^{commit}"], {
7822
+ encoding: "utf8",
7823
+ stdio: ["ignore", "pipe", "pipe"]
7824
+ });
7825
+ if (headResult.status !== 0 || !headResult.stdout.trim()) {
7826
+ return {
7827
+ ok: false,
7828
+ reason: "uncommitted_head",
7829
+ message: "HEAD is uncommitted or not a valid commit"
7830
+ };
7831
+ }
7832
+ const branchCheck = spawnSync4("git", ["-C", root, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`], { stdio: ["ignore", "ignore", "ignore"] });
7833
+ if (branchCheck.status === 0) {
7834
+ return {
7835
+ ok: false,
7836
+ reason: "batch_branch_exists",
7837
+ message: `branch refs/heads/${branch} already exists`
7838
+ };
7839
+ }
7840
+ const originalBranchResult = spawnSync4("git", ["-C", root, "symbolic-ref", "--short", "HEAD"], {
7841
+ encoding: "utf8",
7842
+ stdio: ["ignore", "pipe", "pipe"]
7843
+ });
7844
+ const originalBranch = originalBranchResult.stdout.trim();
7845
+ const checkoutResult = spawnSync4("git", ["-C", root, "-c", "core.hooksPath=/dev/null", "checkout", "-b", branch, baseHead], {
7846
+ encoding: "utf8",
7847
+ stdio: ["ignore", "pipe", "pipe"],
7848
+ env: DEFAULT_GIT_ENV
7849
+ });
7850
+ if (checkoutResult.status !== 0) {
7851
+ const stderr = checkoutResult.stderr?.trim() || "";
7852
+ const branchExists = stderr.includes("already exists") || spawnSync4("git", ["-C", root, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`]).status === 0;
7853
+ const currentBranchCheck = spawnSync4("git", ["-C", root, "symbolic-ref", "--short", "HEAD"], {
7854
+ encoding: "utf8",
7855
+ stdio: ["ignore", "pipe", "pipe"]
7856
+ }).stdout.trim();
7857
+ if (currentBranchCheck === branch && originalBranch && originalBranch !== branch) {
7858
+ spawnSync4("git", ["-C", root, "-c", "core.hooksPath=/dev/null", "checkout", originalBranch], {
7859
+ stdio: ["ignore", "ignore", "ignore"]
7860
+ });
7861
+ }
7862
+ if (branchExists) {
7863
+ return {
7864
+ ok: false,
7865
+ reason: "batch_branch_exists",
7866
+ message: `branch refs/heads/${branch} already exists`
7867
+ };
7868
+ }
7869
+ return {
7870
+ ok: false,
7871
+ reason: "branch_creation_failed",
7872
+ message: stderr || `failed to checkout -b ${branch} ${baseHead}`
7873
+ };
7874
+ }
7875
+ return { ok: true, branch };
7876
+ }
7877
+ function hasBoundaryWhitespace(path) {
7878
+ return path.split("/").some((segment) => segment.trim() !== segment || segment.length === 0);
7879
+ }
7880
+ function getUnsupportedIndexFlags(root) {
7881
+ const result2 = spawnSync4("git", ["-C", root, "ls-files", "-v", "-z", "--"], {
7882
+ encoding: "utf8",
7883
+ stdio: ["ignore", "pipe", "pipe"]
7884
+ });
7885
+ if (result2.status !== 0) {
7886
+ throw new Error("failed to inspect index flags via git ls-files -v");
7887
+ }
7888
+ const entries = result2.stdout.split("\x00").filter((e) => e.length > 0);
7889
+ const flagged = [];
7890
+ for (const entry of entries) {
7891
+ const tag = entry[0];
7892
+ if (tag === "h" || tag === "S" || tag === "s") {
7893
+ flagged.push(entry.slice(2));
7894
+ }
7895
+ }
7896
+ return flagged;
7897
+ }
7898
+ function isPathAllowedForChild(path, taskId, scopeHint) {
7899
+ if (hasBoundaryWhitespace(path))
7900
+ return false;
7901
+ if (path.includes("\\"))
7902
+ return false;
7903
+ const normalized = path.replace(/^\.\//, "");
7904
+ if (normalized === `.imm/audit/${taskId}` || normalized.startsWith(`.imm/audit/${taskId}/`)) {
7905
+ return true;
7906
+ }
7907
+ return scopeHint.some((scopePath) => {
7908
+ if (scopePath.includes("*") || scopePath.includes("?")) {
7909
+ return pathMatchesScope(normalized, scopePath);
7910
+ }
7911
+ return normalized === scopePath || normalized.startsWith(`${scopePath}/`);
7912
+ });
7913
+ }
7914
+ function getChangedProjectPaths(root) {
7915
+ const tracked = spawnSync4("git", ["-C", root, "diff-index", "--name-only", "-z", "--ignore-submodules=none", "HEAD", "--"], {
7916
+ encoding: "utf8",
7917
+ stdio: ["ignore", "pipe", "pipe"]
7918
+ });
7919
+ if (tracked.status !== 0)
7920
+ throw new Error("failed to inspect tracked diff vs HEAD");
7921
+ const untracked = spawnSync4("git", ["-C", root, "ls-files", "--others", "--exclude-standard", "-z", "--"], {
7922
+ encoding: "utf8",
7923
+ stdio: ["ignore", "pipe", "pipe"]
7924
+ });
7925
+ if (untracked.status !== 0)
7926
+ throw new Error("failed to inspect untracked files");
7927
+ const splitZ = (s) => s.split("\x00").filter((p) => p.length > 0);
7928
+ return [...new Set([...splitZ(tracked.stdout), ...splitZ(untracked.stdout)])];
7929
+ }
7930
+ function getStagedProjectPaths(root) {
7931
+ const staged = spawnSync4("git", ["-C", root, "diff-index", "--cached", "--name-only", "-z", "--ignore-submodules=none", "HEAD", "--"], {
7932
+ encoding: "utf8",
7933
+ stdio: ["ignore", "pipe", "pipe"]
7934
+ });
7935
+ if (staged.status !== 0)
7936
+ throw new Error("failed to inspect staged diff vs HEAD");
7937
+ return staged.stdout.split("\x00").filter((p) => p.length > 0);
7938
+ }
7939
+ function getUnstagedProjectPaths(root) {
7940
+ const diffFiles = spawnSync4("git", ["-C", root, "diff-files", "--name-only", "-z", "--ignore-submodules=none", "--"], {
7941
+ encoding: "utf8",
7942
+ stdio: ["ignore", "pipe", "pipe"]
7943
+ });
7944
+ if (diffFiles.status !== 0)
7945
+ throw new Error("failed to inspect unstaged tracked changes");
7946
+ const untracked = spawnSync4("git", ["-C", root, "ls-files", "--others", "--exclude-standard", "-z", "--"], {
7947
+ encoding: "utf8",
7948
+ stdio: ["ignore", "pipe", "pipe"]
7949
+ });
7950
+ if (untracked.status !== 0)
7951
+ throw new Error("failed to inspect untracked files");
7952
+ const splitZ = (s) => s.split("\x00").filter((p) => p.length > 0);
7953
+ return [...new Set([...splitZ(diffFiles.stdout), ...splitZ(untracked.stdout)])];
7954
+ }
7955
+ function getCommittedDeltaPaths(root) {
7956
+ const delta = spawnSync4("git", ["-C", root, "diff-tree", "--no-commit-id", "--name-only", "-z", "-r", "HEAD~1", "HEAD"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
7957
+ if (delta.status !== 0)
7958
+ throw new Error("failed to inspect committed tree delta");
7959
+ return delta.stdout.split("\x00").filter((p) => p.length > 0);
7960
+ }
7961
+ function commitEvidencePath(batchId, taskId) {
7962
+ return join7(".imm", "state", "batches", "commits", `${batchId}-${taskId}.json`);
7963
+ }
7964
+ function ensureSecureDirectory2(root, relativePath) {
7965
+ const segments = relativePath.split("/").filter(Boolean);
7966
+ let current = root;
7967
+ for (const segment of segments) {
7968
+ current = join7(current, segment);
7969
+ if (existsSync3(current)) {
7970
+ const stats = lstatSync6(current);
7971
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
7972
+ throw new Error(`${segment} exists but is not a real directory`);
7973
+ }
7974
+ } else {
7975
+ mkdirSync3(current);
7976
+ }
7977
+ }
7978
+ return current;
7979
+ }
7980
+ function writeFileAtomically(root, relativePath, bytes) {
7981
+ const target = join7(root, relativePath);
7982
+ const targetDir = dirname4(target);
7983
+ ensureSecureDirectory2(root, dirname4(relativePath));
7984
+ const stats = lstatSync6(targetDir);
7985
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
7986
+ throw new Error(`${dirname4(relativePath)} is not a real directory`);
7987
+ }
7988
+ if (existsSync3(target)) {
7989
+ const targetStats = lstatSync6(target);
7990
+ if (targetStats.isSymbolicLink()) {
7991
+ throw new Error(`${relativePath} is a symlink`);
7992
+ }
7993
+ }
7994
+ const tempPath = `${target}.${randomUUID5()}.tmp`;
7995
+ let fd = null;
7996
+ try {
7997
+ fd = openSync4(tempPath, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL, 384);
7998
+ writeFileSync3(fd, bytes, "utf8");
7999
+ closeSync4(fd);
8000
+ fd = null;
8001
+ renameSync2(tempPath, target);
8002
+ } finally {
8003
+ if (fd !== null)
8004
+ closeSync4(fd);
8005
+ if (existsSync3(tempPath)) {
8006
+ try {
8007
+ rmSync4(tempPath);
8008
+ } catch {}
8009
+ }
8010
+ }
8011
+ }
8012
+ function writeBatchCommitEvidence(root, evidence) {
8013
+ const path = commitEvidencePath(evidence.batchId, evidence.taskId);
8014
+ const payload = {
8015
+ contract: "assurance_kernel/batch_commit_evidence/v1",
8016
+ batch_id: evidence.batchId,
8017
+ task_id: evidence.taskId,
8018
+ commit: evidence.commit,
8019
+ parent_head: evidence.parentHead,
8020
+ created_at: new Date().toISOString()
8021
+ };
8022
+ writeFileAtomically(root, path, `${JSON.stringify(payload, null, 2)}
8023
+ `);
8024
+ }
8025
+ function readBatchCommitEvidence(root, batchId, taskId) {
8026
+ const path = commitEvidencePath(batchId, taskId);
8027
+ const fullPath = join7(root, path);
8028
+ if (!existsSync3(fullPath))
8029
+ return null;
8030
+ try {
8031
+ const content = readSecureProjectFile(root, path);
8032
+ const parsed = JSON.parse(content);
8033
+ if (parsed.contract === "assurance_kernel/batch_commit_evidence/v1" && parsed.batch_id === batchId && parsed.task_id === taskId && typeof parsed.commit === "string" && typeof parsed.parent_head === "string") {
8034
+ return { commit: parsed.commit, parent_head: parsed.parent_head };
8035
+ }
8036
+ } catch {
8037
+ return null;
8038
+ }
8039
+ return null;
8040
+ }
8041
+ async function commitBatchChild(input) {
8042
+ const { root, taskId, batchId, expectedHead, branch: expectedBranch } = input;
8043
+ const auditPair = readAuditTaskPair(root, taskId);
8044
+ if (!auditPair) {
8045
+ throw new Error(`cannot commit child ${taskId}: task is not settled done (audit pair missing)`);
8046
+ }
8047
+ const lifecycle = "lifecycle" in auditPair.record ? auditPair.record.lifecycle : auditPair.record.phase;
8048
+ if (lifecycle !== "done") {
8049
+ throw new Error(`cannot commit child ${taskId}: task is not settled done (lifecycle is ${lifecycle})`);
8050
+ }
8051
+ if (auditPair.proof.terminal_lifecycle !== "done") {
8052
+ throw new Error(`cannot commit child ${taskId}: terminal proof lifecycle is not done`);
8053
+ }
8054
+ if (auditPair.record.task_id !== taskId || auditPair.proof.task_id !== taskId) {
8055
+ throw new Error(`cannot commit child ${taskId}: audit task id mismatch`);
8056
+ }
8057
+ const intentSnapshot = auditPair.record.intent_snapshot;
8058
+ const goal = typeof intentSnapshot.goal === "string" ? intentSnapshot.goal : "";
8059
+ const scopeHint = Array.isArray(intentSnapshot.scope_hint) ? intentSnapshot.scope_hint.filter((s) => typeof s === "string") : [];
8060
+ if (expectedBranch !== undefined) {
8061
+ const currentBranchResult = spawnSync4("git", ["-C", root, "symbolic-ref", "--short", "HEAD"], {
8062
+ encoding: "utf8",
8063
+ stdio: ["ignore", "pipe", "pipe"]
8064
+ });
8065
+ const currentBranch = currentBranchResult.stdout.trim();
8066
+ if (currentBranchResult.status !== 0 || currentBranch !== expectedBranch) {
8067
+ throw new Error(`batch_head_lineage_broken: current branch ${currentBranch} does not match expected branch ${expectedBranch}`);
8068
+ }
8069
+ }
8070
+ const currentHeadResult = spawnSync4("git", ["-C", root, "rev-parse", "HEAD"], {
8071
+ encoding: "utf8",
8072
+ stdio: ["ignore", "pipe", "pipe"]
8073
+ });
8074
+ const currentHead = currentHeadResult.stdout.trim();
8075
+ if (currentHeadResult.status !== 0 || !currentHead || currentHead !== expectedHead) {
8076
+ throw new Error(`batch_head_lineage_broken: current HEAD ${currentHead} does not match expected_head ${expectedHead}`);
8077
+ }
8078
+ const flaggedCommit = getUnsupportedIndexFlags(root);
8079
+ if (flaggedCommit.length > 0) {
8080
+ throw new Error("dirty_outside_scope");
8081
+ }
8082
+ const changedPaths = getChangedProjectPaths(root);
8083
+ const outsideScope = changedPaths.filter((p) => !isPathAllowedForChild(p, taskId, scopeHint));
8084
+ if (outsideScope.length > 0) {
8085
+ throw new Error("dirty_outside_scope");
8086
+ }
8087
+ const pathsToStage = getUnstagedProjectPaths(root);
8088
+ if (pathsToStage.length > 0) {
8089
+ const addResult = spawnSync4("git", ["-C", root, "--literal-pathspecs", "add", "-A", "--", ...pathsToStage], {
8090
+ encoding: "utf8",
8091
+ stdio: ["ignore", "pipe", "pipe"]
8092
+ });
8093
+ if (addResult.status !== 0) {
8094
+ throw new Error(`failed to stage changed paths: ${addResult.stderr?.trim() || "git add failed"}`);
8095
+ }
8096
+ }
8097
+ const residualUnstaged = getUnstagedProjectPaths(root);
8098
+ if (residualUnstaged.length > 0) {
8099
+ throw new Error(`residual unstaged changes cannot be captured by the parent repository: ${residualUnstaged.join(", ")}`);
8100
+ }
8101
+ const staged = getStagedProjectPaths(root);
8102
+ const stagedOutside = staged.filter((p) => !isPathAllowedForChild(p, taskId, scopeHint));
8103
+ if (stagedOutside.length > 0) {
8104
+ spawnSync4("git", ["-C", root, "reset", "--quiet"], { stdio: ["ignore", "ignore", "ignore"] });
8105
+ throw new Error("dirty_outside_scope");
8106
+ }
8107
+ const goalFirstLine = goal.trim().split(/\r?\n/)[0]?.trim() || taskId;
8108
+ const commitMessage = `imm(${taskId}): ${goalFirstLine}
8109
+
8110
+ Immune-Brain-Batch: ${batchId}
8111
+ `;
8112
+ const commitResult = spawnSync4("git", ["-C", root, "-c", "core.hooksPath=/dev/null", "commit", "--no-verify", "-F", "-"], {
8113
+ input: commitMessage,
8114
+ encoding: "utf8",
8115
+ stdio: ["pipe", "pipe", "pipe"],
8116
+ env: DEFAULT_GIT_ENV
8117
+ });
8118
+ if (commitResult.status !== 0) {
8119
+ throw new Error(`commit failed for child ${taskId}: ${commitResult.stderr?.trim() || "git commit failed"}`);
8120
+ }
8121
+ const newHeadResult = spawnSync4("git", ["-C", root, "rev-parse", "HEAD"], {
8122
+ encoding: "utf8",
8123
+ stdio: ["ignore", "pipe", "pipe"]
8124
+ });
8125
+ const newHead = newHeadResult.stdout.trim();
8126
+ if (newHeadResult.status !== 0 || !newHead || newHead === expectedHead) {
8127
+ throw new Error("commit_failed");
8128
+ }
8129
+ const parentsResult = spawnSync4("git", ["-C", root, "rev-parse", "HEAD^@"], {
8130
+ encoding: "utf8",
8131
+ stdio: ["ignore", "pipe", "pipe"]
8132
+ });
8133
+ const parents = parentsResult.stdout.trim().split(/\s+/).filter(Boolean);
8134
+ if (parents.length !== 1 || parents[0] !== expectedHead) {
8135
+ throw new Error(`batch_head_lineage_broken: commit parent ${parents.join(",")} does not match expected_head ${expectedHead}`);
8136
+ }
8137
+ const committedDelta = getCommittedDeltaPaths(root);
8138
+ const deltaOutside = committedDelta.filter((p) => !isPathAllowedForChild(p, taskId, scopeHint));
8139
+ if (deltaOutside.length > 0) {
8140
+ throw new Error("dirty_outside_scope");
8141
+ }
8142
+ writeBatchCommitEvidence(root, { batchId, taskId, commit: newHead, parentHead: expectedHead });
8143
+ return { commit: newHead };
8144
+ }
8145
+ async function lookupBatchCommit(input) {
8146
+ const { root, taskId, batchId, expectedHead, branch: expectedBranch } = input;
8147
+ const FORMAT = "%H%x00%(trailers:key=Immune-Brain-Batch,valueonly)%x00%an%x00%ae%x00%s";
8148
+ const subjectPrefix = `imm(${taskId}):`;
8149
+ const parseEntry = (entry) => {
8150
+ const parts = entry.split("\x00");
8151
+ return {
8152
+ commit: parts[0]?.trim() ?? "",
8153
+ trailer: parts[1]?.trim() ?? "",
8154
+ authorName: parts[2]?.trim() ?? "",
8155
+ subject: parts[4]?.trim() ?? ""
8156
+ };
8157
+ };
8158
+ const evidence = readBatchCommitEvidence(root, batchId, taskId);
8159
+ let candidates = [];
8160
+ let evidenceBacked = false;
8161
+ if (evidence && evidence.commit) {
8162
+ const direct = spawnSync4("git", ["-C", root, "log", "-n", "1", `--format=${FORMAT}`, evidence.commit, "--"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
8163
+ if (direct.status !== 0) {
8164
+ throw new Error(`failed to inspect batch commit for ${taskId}: ${direct.stderr?.trim() || "git log failed"}`);
8165
+ }
8166
+ if (direct.stdout?.trim()) {
8167
+ const parsed = parseEntry(direct.stdout);
8168
+ if (parsed.commit === evidence.commit) {
8169
+ candidates = [parsed];
8170
+ evidenceBacked = true;
8171
+ }
8172
+ }
8173
+ }
8174
+ if (!candidates.length) {
8175
+ const result2 = spawnSync4("git", [
8176
+ "-C",
8177
+ root,
8178
+ "log",
8179
+ "--fixed-strings",
8180
+ `--grep=${subjectPrefix}`,
8181
+ "-n",
8182
+ "20",
8183
+ `--format=%x1e${FORMAT}`
8184
+ ], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
8185
+ if (result2.status !== 0) {
8186
+ throw new Error(`failed to lookup batch commit for ${taskId}: ${result2.stderr?.trim() || "git log failed"}`);
8187
+ }
8188
+ candidates = (result2.stdout ?? "").split("\x1E").filter((entry) => entry.trim()).map(parseEntry);
8189
+ }
8190
+ let match = null;
8191
+ for (const candidate of candidates) {
8192
+ if (!candidate.commit || candidate.trailer !== batchId)
8193
+ continue;
8194
+ if (!candidate.subject.startsWith(subjectPrefix))
8195
+ continue;
8196
+ match = candidate;
8197
+ break;
8198
+ }
8199
+ if (!match)
8200
+ return null;
8201
+ const commit = match.commit;
8202
+ if (!evidenceBacked) {
8203
+ const evidenceForMatch = readBatchCommitEvidence(root, batchId, taskId);
8204
+ if (!evidenceForMatch || evidenceForMatch.commit !== commit) {
8205
+ throw new Error(`batch_head_lineage_broken: commit ${commit} lacks durable batch runner production evidence`);
8206
+ }
8207
+ }
8208
+ if (match.authorName && match.authorName !== DEFAULT_GIT_ENV.GIT_AUTHOR_NAME) {
8209
+ throw new Error(`batch_head_lineage_broken: adopted commit author ${match.authorName} does not match batch authority ${DEFAULT_GIT_ENV.GIT_AUTHOR_NAME}`);
8210
+ }
8211
+ if (expectedBranch !== undefined) {
8212
+ const currentBranchResult = spawnSync4("git", ["-C", root, "symbolic-ref", "--short", "HEAD"], {
8213
+ encoding: "utf8",
8214
+ stdio: ["ignore", "pipe", "pipe"]
8215
+ });
8216
+ const currentBranch = currentBranchResult.stdout.trim();
8217
+ if (currentBranchResult.status !== 0 || currentBranch !== expectedBranch) {
8218
+ throw new Error(`batch_head_lineage_broken: current branch ${currentBranch} does not match expected branch ${expectedBranch}`);
8219
+ }
8220
+ }
8221
+ if (expectedHead !== undefined) {
8222
+ const currentHeadResult = spawnSync4("git", ["-C", root, "rev-parse", "HEAD"], {
8223
+ encoding: "utf8",
8224
+ stdio: ["ignore", "pipe", "pipe"]
8225
+ });
8226
+ const currentHead = currentHeadResult.stdout.trim();
8227
+ if (currentHead !== commit) {
8228
+ throw new Error(`batch_head_lineage_broken: current HEAD ${currentHead} diverged from adopted commit ${commit}`);
8229
+ }
8230
+ const parentsResult = spawnSync4("git", ["-C", root, "rev-parse", `${commit}^@`], {
8231
+ encoding: "utf8",
8232
+ stdio: ["ignore", "pipe", "pipe"]
8233
+ });
8234
+ const parents = parentsResult.stdout.trim().split(/\s+/).filter(Boolean);
8235
+ if (parents.length !== 1 || parents[0] !== expectedHead) {
8236
+ throw new Error(`batch_head_lineage_broken: adopted commit parent ${parents.join(",")} does not match expected_head ${expectedHead}`);
8237
+ }
8238
+ const auditPair = readAuditTaskPair(root, taskId);
8239
+ if (!auditPair) {
8240
+ throw new Error(`batch_head_lineage_broken: adopted commit lacks terminal audit pair for ${taskId}`);
8241
+ }
8242
+ const lifecycle = "lifecycle" in auditPair.record ? auditPair.record.lifecycle : auditPair.record.phase;
8243
+ if (lifecycle !== "done") {
8244
+ throw new Error(`batch_head_lineage_broken: adopted commit task lifecycle is not done: ${lifecycle}`);
8245
+ }
8246
+ const scopeHint = Array.isArray(auditPair.record.intent_snapshot.scope_hint) ? auditPair.record.intent_snapshot.scope_hint.filter((s) => typeof s === "string") : [];
8247
+ const deltaResult = spawnSync4("git", ["-C", root, "diff-tree", "--no-commit-id", "--name-only", "-z", "-r", expectedHead, commit], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
8248
+ if (deltaResult.status !== 0) {
8249
+ throw new Error("batch_head_lineage_broken: failed to inspect adopted commit delta");
8250
+ }
8251
+ const changedInCommit = deltaResult.stdout.split("\x00").filter((p) => p.length > 0);
8252
+ const outside = changedInCommit.filter((p) => !isPathAllowedForChild(p, taskId, scopeHint));
8253
+ if (outside.length > 0) {
8254
+ throw new Error(`batch_head_lineage_broken: adopted commit contains out-of-scope changes: ${outside.join(", ")}`);
8255
+ }
8256
+ }
8257
+ return { commit };
8258
+ }
8259
+
8260
+ // plugins/immune-brain/runtime/unattended/batch_state.ts
8261
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, openSync as openSync5, closeSync as closeSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, lstatSync as lstatSync7, constants as constants4, rmSync as rmSync5 } from "node:fs";
8262
+ import { randomUUID as randomUUID6 } from "node:crypto";
8263
+ import { dirname as dirname5, join as join8 } from "node:path";
8264
+ var BATCH_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
8265
+ var CHILD_RUN_STATES = new Set([
8266
+ "pending",
8267
+ "enrolled",
8268
+ "settled",
8269
+ "committed",
8270
+ "needs_human",
8271
+ "skipped_blocked"
8272
+ ]);
8273
+ var BATCH_RUN_STATES = new Set([
8274
+ "prepared",
8275
+ "running",
8276
+ "needs_human",
8277
+ "completed",
8278
+ "budget_stopped",
8279
+ "failed",
8280
+ "rejected"
8281
+ ]);
8282
+ var ISO_TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
8283
+ function isCanonicalTimestamp(value) {
8284
+ if (typeof value !== "string" || !ISO_TIMESTAMP_PATTERN2.test(value))
8285
+ return false;
8286
+ const milliseconds = Date.parse(value);
8287
+ return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === value;
8288
+ }
8289
+ function validateBatchId(batchId) {
8290
+ if (typeof batchId !== "string" || !BATCH_ID_PATTERN.test(batchId))
8291
+ throw new Error("batch id is not a safe file identity");
8292
+ }
8293
+ function statePath(batchId) {
8294
+ validateBatchId(batchId);
8295
+ return join8(".imm", "state", "batches", `${batchId}.json`);
8296
+ }
8297
+ function canonicalBytes(record) {
8298
+ return `${JSON.stringify(record, null, 2)}
8299
+ `;
8300
+ }
8301
+ function validateRecordShape(value, batchId) {
8302
+ if (typeof value !== "object" || value === null)
8303
+ throw new Error(`batch run state ${batchId} is not an object`);
8304
+ const record = value;
8305
+ if (record.contract !== "assurance_kernel/batch_run_state/v1")
8306
+ throw new Error(`batch run state ${batchId} has an unknown contract`);
8307
+ if (record.batch_id !== batchId)
8308
+ throw new Error(`batch run state ${batchId} carries batch_id ${String(record.batch_id)}`);
8309
+ if (typeof record.plan_digest !== "string" || !record.plan_digest)
8310
+ throw new Error(`batch run state ${batchId} has an invalid plan_digest`);
8311
+ if (typeof record.base_head !== "string" || !record.base_head)
8312
+ throw new Error(`batch run state ${batchId} has an invalid base_head`);
8313
+ if (record.branch !== undefined && (typeof record.branch !== "string" || !record.branch))
8314
+ throw new Error(`batch run state ${batchId} has an invalid branch`);
8315
+ if (!isCanonicalTimestamp(record.confirmation_time))
8316
+ throw new Error(`batch run state ${batchId} has an invalid confirmation_time`);
8317
+ if (!isCanonicalTimestamp(record.authorization_expires_at))
8318
+ throw new Error(`batch run state ${batchId} has an invalid authorization_expires_at`);
8319
+ if (!isCanonicalTimestamp(record.created_at) || !isCanonicalTimestamp(record.updated_at))
8320
+ throw new Error(`batch run state ${batchId} has invalid state timestamps`);
8321
+ if (!Array.isArray(record.children) || record.children.length === 0)
8322
+ throw new Error(`batch run state ${batchId} has no children`);
8323
+ if (!BATCH_RUN_STATES.has(String(record.batch_state)))
8324
+ throw new Error(`batch run state ${batchId} has an invalid batch_state`);
8325
+ if (typeof record.consecutive_qa_failures !== "number" || !Number.isInteger(record.consecutive_qa_failures) || record.consecutive_qa_failures < 0)
8326
+ throw new Error(`batch run state ${batchId} has an invalid consecutive_qa_failures`);
8327
+ const budget2 = record.budget;
8328
+ if (typeof record.budget !== "object" || record.budget === null || typeof budget2.max_children !== "number" || !Number.isInteger(budget2.max_children) || budget2.max_children <= 0 || !isCanonicalTimestamp(budget2.deadline_at) || typeof budget2.qa_failure_limit !== "number" || !Number.isInteger(budget2.qa_failure_limit) || budget2.qa_failure_limit <= 0)
8329
+ throw new Error(`batch run state ${batchId} has an invalid budget`);
8330
+ if (!Array.isArray(record.commits) || record.commits.some((c) => typeof c !== "string"))
8331
+ throw new Error(`batch run state ${batchId} has an invalid commits list`);
8332
+ const seenTaskIds = new Set;
8333
+ for (const child of record.children) {
8334
+ if (typeof child !== "object" || child === null || typeof child.task_id !== "string" || !child.task_id || typeof child.slice_id !== "string" || !child.slice_id)
8335
+ throw new Error(`batch run state ${batchId} has an invalid child entry`);
8336
+ if (!CHILD_RUN_STATES.has(String(child.state)))
8337
+ throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid state`);
8338
+ if (!Array.isArray(child.blocked_by) || child.blocked_by.some((b) => typeof b !== "string"))
8339
+ throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid blocked_by`);
8340
+ if (child.reason !== null && typeof child.reason !== "string" || child.commit !== null && typeof child.commit !== "string")
8341
+ throw new Error(`batch run state ${batchId} child ${child.task_id} has invalid terminal fields`);
8342
+ if (child.state === "committed" && child.commit === null)
8343
+ throw new Error(`batch run state ${batchId} child ${child.task_id} is committed without a commit`);
8344
+ if (child.state !== "committed" && child.commit !== null)
8345
+ throw new Error(`batch run state ${batchId} child ${child.task_id} has a commit but is not committed`);
8346
+ if ((child.state === "needs_human" || child.state === "skipped_blocked") && child.reason === null)
8347
+ throw new Error(`batch run state ${batchId} child ${child.task_id} needs a terminal reason`);
8348
+ if (seenTaskIds.has(child.task_id))
8349
+ throw new Error(`batch run state ${batchId} has a duplicate child ${child.task_id}`);
8350
+ seenTaskIds.add(child.task_id);
8351
+ }
8352
+ for (const child of record.children) {
8353
+ if (child.state === "committed" && child.commit !== null && !record.commits.includes(child.commit))
8354
+ throw new Error(`batch run state ${batchId} child ${child.task_id} commit is missing from commits`);
8355
+ }
8356
+ if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected") && record.children.some((child) => child.state === "enrolled" || child.state === "settled"))
8357
+ throw new Error(`batch run state ${batchId} is ${String(record.batch_state)} but a child is still mid-flight`);
8358
+ if (record.batch_state === "completed" && record.children.some((child) => child.state !== "committed"))
8359
+ throw new Error(`batch run state ${batchId} is completed but a child is not committed`);
8360
+ }
8361
+ function prepareBatchRunState(input) {
8362
+ validateBatchId(input.batch_id);
8363
+ const prepared = {
8364
+ contract: "assurance_kernel/batch_run_state/v1",
8365
+ batch_id: input.batch_id,
8366
+ initiative_slug: input.initiative_slug,
8367
+ plan_digest: input.plan_digest,
8368
+ base_head: input.base_head,
8369
+ branch: input.branch ?? `imm/${input.initiative_slug}`,
8370
+ confirmation_time: input.confirmation_time,
8371
+ authorization_expires_at: input.authorization_expires_at,
8372
+ budget: input.budget,
8373
+ batch_state: "prepared",
8374
+ children: input.children.map((child) => ({
8375
+ task_id: child.task_id,
8376
+ slice_id: child.slice_id,
8377
+ blocked_by: [...child.blocked_by],
8378
+ state: "pending",
8379
+ reason: null,
8380
+ commit: null
8381
+ })),
8382
+ consecutive_qa_failures: 0,
8383
+ commits: [],
8384
+ created_at: input.now,
8385
+ updated_at: input.now
8386
+ };
8387
+ return prepared;
8388
+ }
8389
+ function readBatchRunState(root, batchId) {
8390
+ const path = statePath(batchId);
8391
+ if (!existsSync4(join8(root, path)))
8392
+ return null;
8393
+ const parsed = JSON.parse(readSecureProjectFile(root, path));
8394
+ validateRecordShape(parsed, batchId);
8395
+ return parsed;
8396
+ }
8397
+ function ensureSecureDirectory3(root, relative4) {
8398
+ const target = join8(root, relative4);
8399
+ const parent = dirname5(target);
8400
+ if (!existsSync4(parent))
8401
+ mkdirSync4(parent, { recursive: true });
8402
+ if (existsSync4(target)) {
8403
+ const stats = lstatSync7(target);
8404
+ if (!stats.isDirectory())
8405
+ throw new Error(`${relative4} exists but is not a directory`);
8406
+ } else {
8407
+ mkdirSync4(target);
8408
+ }
8409
+ return target;
8410
+ }
8411
+ function writeFileAtomically2(root, relative4, bytes) {
8412
+ const target = join8(root, relative4);
8413
+ const targetDir = dirname5(target);
8414
+ const stats = lstatSync7(targetDir);
8415
+ if (!stats.isDirectory())
8416
+ throw new Error(`${dirname5(relative4)} is not a directory`);
8417
+ const tempPath = `${target}.${randomUUID6()}.tmp`;
8418
+ let fd = null;
8419
+ try {
8420
+ fd = openSync5(tempPath, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL, 384);
8421
+ writeFileSync4(fd, bytes, "utf8");
8422
+ closeSync5(fd);
8423
+ fd = null;
8424
+ renameSync3(tempPath, target);
8425
+ } finally {
8426
+ if (fd !== null)
8427
+ closeSync5(fd);
8428
+ if (existsSync4(tempPath)) {
8429
+ try {
8430
+ rmSync5(tempPath);
8431
+ } catch {}
8432
+ }
8433
+ }
8434
+ }
8435
+ function writeBatchRunState(root, record) {
8436
+ const path = statePath(record.batch_id);
8437
+ validateRecordShape(record, record.batch_id);
8438
+ return withKernelStoreLock(root, () => {
8439
+ const existing = existsSync4(join8(root, path)) ? readSecureProjectFile(root, path) : null;
8440
+ if (existing !== null && existing === canonicalBytes(record))
8441
+ return record;
8442
+ const stored = {
8443
+ ...record,
8444
+ updated_at: new Date().toISOString()
8445
+ };
8446
+ ensureSecureDirectory3(root, join8(".imm", "state", "batches"));
8447
+ writeFileAtomically2(root, path, canonicalBytes(stored));
8448
+ return stored;
8449
+ });
8450
+ }
8451
+ function reportPath(batchId) {
8452
+ validateBatchId(batchId);
8453
+ return join8(".imm", "state", "batches", `${batchId}.report.json`);
8454
+ }
8455
+ function writeBatchRunReport(root, report) {
8456
+ const relative4 = reportPath(report.batch_id);
8457
+ return withKernelStoreLock(root, () => {
8458
+ const path = join8(root, relative4);
8459
+ if (existsSync4(path)) {
8460
+ const original = JSON.parse(readSecureProjectFile(root, relative4));
8461
+ if (typeof original !== "object" || original === null || original.contract !== "assurance_kernel/batch_run_report/v1")
8462
+ throw new Error(`batch run report ${report.batch_id} has an unknown contract`);
8463
+ const prior = original;
8464
+ if (canonicalReportBytes(prior) === canonicalReportBytes(report))
8465
+ return prior;
8466
+ if (prior.batch_state !== "needs_human")
8467
+ return prior;
8468
+ }
8469
+ ensureSecureDirectory3(root, join8(".imm", "state", "batches"));
8470
+ writeFileAtomically2(root, relative4, canonicalReportBytes(report));
8471
+ return report;
8472
+ });
8473
+ }
8474
+ function canonicalReportBytes(report) {
8475
+ return `${JSON.stringify(report, null, 2)}
8476
+ `;
8477
+ }
8478
+ function isTerminalBatchState(state) {
8479
+ return state === "completed" || state === "budget_stopped" || state === "failed" || state === "rejected";
8480
+ }
8481
+
8482
+ // plugins/immune-brain/runtime/unattended/batch_runner.ts
8483
+ function dependentsOf(record, taskId) {
8484
+ return record.children.filter((child) => child.blocked_by.includes(taskId));
8485
+ }
8486
+ function skipDependents(record, taskId, reason) {
8487
+ const skip = new Set([taskId]);
8488
+ const queue = [taskId];
8489
+ while (queue.length > 0) {
8490
+ const current = queue.shift();
8491
+ for (const dependent of dependentsOf(record, current)) {
8492
+ if (skip.has(dependent.task_id))
8493
+ continue;
8494
+ skip.add(dependent.task_id);
8495
+ queue.push(dependent.task_id);
8496
+ }
8497
+ }
8498
+ record.children = record.children.map((child) => skip.has(child.task_id) && child.state === "pending" ? { ...child, state: "skipped_blocked", reason } : child);
8499
+ }
8500
+ function budgetStopReason(record, now) {
8501
+ const enrolledCount = record.children.filter((child) => child.state !== "pending" && child.state !== "skipped_blocked").length;
8502
+ if (enrolledCount >= record.budget.max_children)
8503
+ return `max_children budget exhausted (${record.budget.max_children})`;
8504
+ const deadline = Date.parse(record.budget.deadline_at);
8505
+ if (!Number.isNaN(deadline) && now >= deadline)
8506
+ return `deadline_at reached (${record.budget.deadline_at})`;
8507
+ return null;
8508
+ }
8509
+ function isAuthorizationExpiryError(error) {
8510
+ return error instanceof BatchAuthorizationExpiryError;
8511
+ }
8512
+ function requireFreshProjection(result2, taskId) {
8513
+ if (result2.error !== null)
8514
+ throw new Error(`cannot reconcile Kernel projection for ${taskId}: ${result2.error}`);
8515
+ return result2;
8516
+ }
8517
+ function nextEnrollableChild(record) {
8518
+ return record.children.find((child) => child.state === "pending" && record.children.every((other) => !child.blocked_by.includes(other.task_id) || other.state === "committed")) ?? null;
8519
+ }
8520
+ var TERMINAL_NEXT_ACTIONS = {
8521
+ completed: "The batch settled every enrollable child; review the commits and the tracker.",
8522
+ budget_stopped: "Budget, deadline, or authorization expiry stopped new enrollments; re-confirm to continue under a new authorization.",
8523
+ failed: "A commit or lineage failure stopped the batch; inspect the failing child and the branch state.",
8524
+ rejected: "The batch was rejected before any enrollment; correct the stated reason and re-confirm.",
8525
+ needs_human: "A parked child needs a human decision; resolve it, then re-confirm to continue.",
8526
+ running: "The batch is still running; no terminal report is due yet.",
8527
+ prepared: "The batch is prepared but not started."
8528
+ };
8529
+ function reportFor(record, reason, nextAction) {
8530
+ return {
8531
+ contract: "assurance_kernel/batch_run_report/v1",
8532
+ batch_id: record.batch_id,
8533
+ initiative_slug: record.initiative_slug,
8534
+ batch_state: record.batch_state,
8535
+ children: record.children,
8536
+ commits: record.commits,
8537
+ reason,
8538
+ next_action: nextAction || (TERMINAL_NEXT_ACTIONS[record.batch_state] ?? "Inspect the batch run state."),
8539
+ created_at: record.updated_at
8540
+ };
8541
+ }
8542
+ function finalize(root, record, reason, nextAction) {
8543
+ const report = reportFor(record, reason, nextAction);
8544
+ if (isTerminalBatchState(record.batch_state) || record.batch_state === "needs_human") {
8545
+ writeBatchRunReport(root, report);
8546
+ }
8547
+ return report;
8548
+ }
8549
+ function isLineageBreakError(message) {
8550
+ return message.includes("batch_head_lineage_broken");
8551
+ }
8552
+ function failPersistedLineage(root, existing, message) {
8553
+ const children = existing.children.map((c) => c.state === "enrolled" || c.state === "settled" ? { ...c, state: "needs_human", reason: message } : c);
8554
+ const record = { ...existing, batch_state: "failed", children };
8555
+ for (const child of record.children) {
8556
+ if (child.state === "needs_human") {
8557
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
8558
+ }
8559
+ }
8560
+ return writeBatchRunState(root, record);
8561
+ }
8562
+ function externalHeadDriftMessage(root, record) {
8563
+ if (!existsSync5(join9(root, ".git")))
8564
+ return null;
8565
+ const head = record.commits.length ? record.commits[record.commits.length - 1] : record.base_head;
8566
+ const headCheck = spawnSync5("git", ["-C", root, "rev-parse", "HEAD"], {
8567
+ encoding: "utf8",
8568
+ stdio: ["ignore", "pipe", "pipe"]
8569
+ });
8570
+ if (headCheck.status === 0 && headCheck.stdout.trim() && headCheck.stdout.trim() !== head) {
8571
+ return `batch_head_lineage_broken: current HEAD ${headCheck.stdout.trim()} does not match expected batch head ${head}`;
8572
+ }
8573
+ const branchCheck = spawnSync5("git", ["-C", root, "symbolic-ref", "--short", "HEAD"], {
8574
+ encoding: "utf8",
8575
+ stdio: ["ignore", "pipe", "pipe"]
8576
+ });
8577
+ const currentBranch = branchCheck.stdout.trim();
8578
+ if (branchCheck.status !== 0 || currentBranch !== record.branch) {
8579
+ return `batch_head_lineage_broken: current branch ${currentBranch} does not match expected batch branch ${record.branch}`;
8580
+ }
8581
+ return null;
8582
+ }
8583
+ async function validatePersistedRun(input, record) {
8584
+ const plan = input.registry.children(input.capability);
8585
+ if (record.plan_digest !== computeBatchPlanDigest(plan) || record.children.length !== plan.length || record.children.some((child, index) => {
8586
+ const expected = plan[index];
8587
+ return child.task_id !== expected.task_id || JSON.stringify(child.blocked_by) !== JSON.stringify(expected.blocked_by);
8588
+ }))
8589
+ throw new Error("plan_digest mismatch: persisted children do not match the authorized plan");
8590
+ const commits = [];
8591
+ for (const child of record.children) {
8592
+ if (child.state !== "committed") {
8593
+ if (child.commit !== null)
8594
+ throw new Error("uncommitted batch child has a commit");
8595
+ continue;
8596
+ }
8597
+ const evidence = input.git?.lookupBatchCommit ? await input.git.lookupBatchCommit(input.root, child.task_id, record.batch_id, undefined, record.branch) : input.kernel.lookupBatchCommit ? await input.kernel.lookupBatchCommit(input.root, child.task_id, record.batch_id, undefined, record.branch) : await lookupBatchCommit({
8598
+ root: input.root,
8599
+ taskId: child.task_id,
8600
+ batchId: record.batch_id,
8601
+ branch: record.branch
8602
+ });
8603
+ if (!evidence || evidence.commit !== child.commit) {
8604
+ if (evidence === null && typeof child.commit === "string" && child.commit.length > 0 && existsSync5(join9(input.root, ".git"))) {
8605
+ const reach = spawnSync5("git", ["-C", input.root, "merge-base", "--is-ancestor", child.commit, "HEAD"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
8606
+ if (reach.status !== 0) {
8607
+ throw new Error(`batch_head_lineage_broken: recorded commit ${child.commit} for ${child.task_id} is no longer reachable from HEAD`);
8608
+ }
8609
+ }
8610
+ throw new Error(`persisted batch commit lacks evidence for ${child.task_id}`);
8611
+ }
8612
+ commits.push(evidence.commit);
8613
+ }
8614
+ if (JSON.stringify([...record.commits].sort()) !== JSON.stringify(commits.sort()))
8615
+ throw new Error("persisted batch commit list does not match child evidence");
8616
+ }
8617
+ function replayTerminal(root, existing) {
8618
+ return finalize(root, existing, `terminal state already reached: ${existing.batch_state}`, "");
8619
+ }
8620
+ function validateRunAuthorization(input, existing) {
8621
+ const authorized = input.kernel.validateBatchAuthorization({
8622
+ registry: input.registry,
8623
+ capability: input.capability,
8624
+ binding: {
8625
+ batch_id: input.batch_id,
8626
+ plan_digest: existing?.plan_digest ?? input.plan_digest,
8627
+ base_head: existing?.base_head ?? input.base_head,
8628
+ initiative_slug: existing?.initiative_slug ?? input.initiative_slug,
8629
+ budget: input.budget,
8630
+ expires_at: input.authorization_expires_at
8631
+ }
8632
+ });
8633
+ if (authorized.issued_at !== input.confirmation_time || existing && Date.parse(authorized.issued_at) <= Date.parse(existing.confirmation_time))
8634
+ throw new Error("the parked batch requires a fresh literal-user confirmation");
8635
+ const children = input.children.map((child) => {
8636
+ const { intent_path, intent_revision, intent_content_hash } = child;
8637
+ if (intent_path === null || intent_revision === null || intent_content_hash === null)
8638
+ throw new Error(`batch child ${child.task_id} has no complete intent identity`);
8639
+ return { ...child, intent_path, intent_revision, intent_content_hash };
8640
+ });
8641
+ if (computeBatchPlanDigest(children) !== authorized.plan_digest || input.plan_digest !== authorized.plan_digest || input.base_head !== authorized.base_head)
8642
+ throw new Error("batch run input does not match the authorized plan or base_head");
8643
+ }
8644
+ async function startBatch(input) {
8645
+ if (!input.children.length) {
8646
+ const rejected = prepareBatchRunState({ ...input, children: [], now: input.now });
8647
+ return reportFor({ ...rejected, batch_state: "rejected" }, "batch plan is empty", "Provide a non-empty enrollable child plan.");
8648
+ }
8649
+ let existing = readBatchRunState(input.root, input.batch_id);
8650
+ if (existing) {
8651
+ try {
8652
+ await validatePersistedRun(input, existing);
8653
+ } catch (error) {
8654
+ const message = error instanceof Error ? error.message : String(error);
8655
+ if (isLineageBreakError(message)) {
8656
+ if (isTerminalBatchState(existing.batch_state))
8657
+ return replayTerminal(input.root, existing);
8658
+ const failed = failPersistedLineage(input.root, existing, message);
8659
+ return finalize(input.root, failed, message, "");
8660
+ }
8661
+ throw error;
8662
+ }
8663
+ }
8664
+ if (existing && isTerminalBatchState(existing.batch_state)) {
8665
+ return replayTerminal(input.root, existing);
8666
+ }
8667
+ if (existing?.batch_state === "needs_human") {
8668
+ const priorConfirmation = Date.parse(existing.confirmation_time);
8669
+ const nextConfirmation = Date.parse(input.confirmation_time);
8670
+ const nextExpiry = Date.parse(input.authorization_expires_at);
8671
+ const freshAuthorization = Number.isFinite(nextConfirmation) && nextConfirmation > priorConfirmation && Number.isFinite(nextExpiry) && nextExpiry > Date.now();
8672
+ if (!freshAuthorization) {
8673
+ return finalize(input.root, existing, "the parked batch requires a fresh literal-user confirmation", "Resolve the parked child, then re-confirm the batch to continue.");
8674
+ }
8675
+ validateRunAuthorization(input, existing);
8676
+ prepareBatchRunState({ ...input, now: input.now });
8677
+ const remapped = await Promise.all(existing.children.map(async (child) => {
8678
+ if (child.state !== "needs_human")
8679
+ return child;
8680
+ const fresh = requireFreshProjection(await input.kernel.projectTask(input.root, child.task_id), child.task_id);
8681
+ if (fresh.projection.lifecycle === "done" && fresh.projection.completion_ready)
8682
+ return { ...child, state: "settled", reason: null };
8683
+ if (fresh.error === null && fresh.claim?.task_id === child.task_id) {
8684
+ return input.kernel.ownsTaskClaim(child.task_id) ? { ...child, state: "enrolled", reason: null } : child;
8685
+ }
8686
+ return { ...child, state: "pending", reason: null };
8687
+ }));
8688
+ if (remapped.some((child) => child.state === "needs_human")) {
8689
+ const reparked = { ...existing, children: remapped };
8690
+ for (const parked of remapped) {
8691
+ if (parked.state === "needs_human")
8692
+ skipDependents(reparked, parked.task_id, `dependency ${parked.task_id} parked`);
8693
+ }
8694
+ existing = writeBatchRunState(input.root, {
8695
+ ...reparked,
8696
+ confirmation_time: input.confirmation_time,
8697
+ authorization_expires_at: input.authorization_expires_at,
8698
+ budget: input.budget,
8699
+ batch_state: "needs_human"
8700
+ });
8701
+ return finalize(input.root, existing, "a parked child's Kernel claim is held by a foreign batch", "Resolve the foreign claim, then re-confirm the batch to continue.");
8702
+ }
8703
+ const resumedChildren = remapped.map((child) => child.state === "skipped_blocked" ? { ...child, state: "pending", reason: null } : child);
8704
+ existing = writeBatchRunState(input.root, {
8705
+ ...existing,
8706
+ confirmation_time: input.confirmation_time,
8707
+ authorization_expires_at: input.authorization_expires_at,
8708
+ budget: input.budget,
8709
+ batch_state: "running",
8710
+ consecutive_qa_failures: 0,
8711
+ children: resumedChildren
8712
+ });
8713
+ }
8714
+ if (existing?.batch_state === "running") {
8715
+ const nextConfirmation = Date.parse(input.confirmation_time);
8716
+ const nextExpiry = Date.parse(input.authorization_expires_at);
8717
+ const priorConfirmation = Date.parse(existing.confirmation_time);
8718
+ const persistedExpiry = Date.parse(existing.authorization_expires_at);
8719
+ const renewedAuthorization = Number.isFinite(nextConfirmation) && nextConfirmation > priorConfirmation && Number.isFinite(nextExpiry) && nextExpiry > Date.now() && nextExpiry > persistedExpiry;
8720
+ if (renewedAuthorization) {
8721
+ validateRunAuthorization(input, existing);
8722
+ existing = writeBatchRunState(input.root, {
8723
+ ...existing,
8724
+ confirmation_time: input.confirmation_time,
8725
+ authorization_expires_at: input.authorization_expires_at,
8726
+ budget: input.budget
8727
+ });
8728
+ }
8729
+ }
8730
+ if (existing) {
8731
+ const interruptedChild = existing.children.find((child) => child.state === "enrolled" || child.state === "settled");
8732
+ if (interruptedChild) {
8733
+ return await resumeBatch(input, (root, taskId) => input.kernel.projectTask(root, taskId));
8734
+ }
8735
+ }
8736
+ if (!existing) {
8737
+ try {
8738
+ validateRunAuthorization(input, null);
8739
+ } catch (error) {
8740
+ return reportFor({ ...prepareBatchRunState(input), batch_state: "rejected" }, error instanceof Error ? error.message : String(error), "Correct the authorization or plan, then re-confirm the batch.");
8741
+ }
8742
+ const preflightResult = input.git?.preflight ? await input.git.preflight({
8743
+ root: input.root,
8744
+ initiative_slug: input.initiative_slug,
8745
+ base_head: input.base_head
8746
+ }) : input.kernel.gitPreflight ? await input.kernel.gitPreflight({
8747
+ root: input.root,
8748
+ initiative_slug: input.initiative_slug,
8749
+ base_head: input.base_head
8750
+ }) : runBatchGitPreflight({
8751
+ root: input.root,
8752
+ initiative_slug: input.initiative_slug,
8753
+ base_head: input.base_head
8754
+ });
8755
+ if (!preflightResult.ok) {
8756
+ const rejected = prepareBatchRunState({ ...input, now: input.now });
8757
+ return reportFor({ ...rejected, batch_state: "rejected" }, preflightResult.reason, preflightResult.message || "Correct the preflight condition and re-confirm.");
8758
+ }
8759
+ }
8760
+ let record = existing ?? prepareBatchRunState({
8761
+ batch_id: input.batch_id,
8762
+ initiative_slug: input.initiative_slug,
8763
+ children: input.children,
8764
+ plan_digest: input.plan_digest,
8765
+ base_head: input.base_head,
8766
+ confirmation_time: input.confirmation_time,
8767
+ authorization_expires_at: input.authorization_expires_at,
8768
+ budget: input.budget,
8769
+ now: input.now
8770
+ });
8771
+ const persist = () => {
8772
+ record = writeBatchRunState(input.root, record);
8773
+ };
8774
+ if (record.batch_state === "prepared") {
8775
+ record.batch_state = "running";
8776
+ persist();
8777
+ }
8778
+ let head = record.commits.length ? record.commits[record.commits.length - 1] : record.base_head;
8779
+ const driftMessage = externalHeadDriftMessage(input.root, record);
8780
+ if (driftMessage) {
8781
+ record.batch_state = "failed";
8782
+ persist();
8783
+ return finalize(input.root, record, driftMessage, "");
8784
+ }
8785
+ while (record.batch_state === "running") {
8786
+ const child = nextEnrollableChild(record);
8787
+ if (!child) {
8788
+ record.batch_state = record.children.some((c) => c.state === "needs_human" || c.state === "skipped_blocked") ? "needs_human" : "completed";
8789
+ persist();
8790
+ break;
8791
+ }
8792
+ let adoptedClaim = false;
8793
+ let claimState = "none";
8794
+ const fresh = requireFreshProjection(await input.kernel.projectTask(input.root, child.task_id), child.task_id);
8795
+ if (fresh.claim !== null && fresh.claim.task_id === child.task_id) {
8796
+ claimState = input.kernel.ownsTaskClaim(child.task_id) ? "ours" : "foreign";
8797
+ }
8798
+ if (claimState === "ours") {
8799
+ adoptedClaim = true;
8800
+ } else if (claimState === "foreign") {
8801
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: "claim held by another batch" } : c);
8802
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
8803
+ record.batch_state = "needs_human";
8804
+ persist();
8805
+ return finalize(input.root, record, "claim held by another batch", "needs-human-attention");
8806
+ }
8807
+ if (adoptedClaim) {
8808
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "enrolled", reason: "adopted existing batch claim after interruption" } : c);
8809
+ persist();
8810
+ } else {
8811
+ const now = Date.now();
8812
+ if (now >= Date.parse(record.authorization_expires_at) || budgetStopReason(record, now)) {
8813
+ record.batch_state = record.children.some((c) => c.state === "needs_human" || c.state === "skipped_blocked") ? "needs_human" : "budget_stopped";
8814
+ persist();
8815
+ break;
8816
+ }
8817
+ try {
8818
+ await validatePersistedRun(input, record);
8819
+ const { issued_at: _issuedAt, ...binding } = input.kernel.validateBatchAuthorization({
8820
+ registry: input.registry,
8821
+ capability: input.capability,
8822
+ binding: {
8823
+ batch_id: record.batch_id,
8824
+ plan_digest: record.plan_digest,
8825
+ base_head: record.base_head,
8826
+ initiative_slug: record.initiative_slug,
8827
+ budget: record.budget,
8828
+ expires_at: record.authorization_expires_at
8829
+ }
8830
+ });
8831
+ for (const committed of record.children) {
8832
+ if (committed.state === "committed" && !input.registry.isChildConsumed(input.capability, committed.task_id))
8833
+ input.registry.consumeChild(input.capability, binding, committed.task_id);
8834
+ }
8835
+ await input.kernel.enrollTask({
8836
+ root: input.root,
8837
+ task_id: child.task_id,
8838
+ batch: {
8839
+ registry: input.registry,
8840
+ capability: input.capability,
8841
+ binding: { batch_id: input.batch_id, expected_head: head }
8842
+ }
8843
+ });
8844
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "enrolled" } : c);
8845
+ persist();
8846
+ } catch (error) {
8847
+ const message = error instanceof Error ? error.message : String(error);
8848
+ if (isAuthorizationExpiryError(error)) {
8849
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "pending", reason: message } : c);
8850
+ record.batch_state = "budget_stopped";
8851
+ persist();
8852
+ break;
8853
+ }
8854
+ if (isLineageBreakError(message)) {
8855
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: message } : c);
8856
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
8857
+ record.batch_state = "failed";
8858
+ persist();
8859
+ break;
8860
+ }
8861
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: message } : c);
8862
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
8863
+ record.batch_state = "needs_human";
8864
+ persist();
8865
+ break;
8866
+ }
8867
+ }
8868
+ let childTerminal = false;
8869
+ while (!childTerminal) {
8870
+ const terminal = await input.kernel.advanceTask(input.root, child.task_id);
8871
+ if (terminal.state === "completed") {
8872
+ childTerminal = true;
8873
+ record.consecutive_qa_failures = 0;
8874
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "settled", reason: null } : c);
8875
+ persist();
8876
+ try {
8877
+ const planChild = input.children.find((c) => c.task_id === child.task_id);
8878
+ const intentPath = planChild?.intent_path ?? undefined;
8879
+ const { commit } = input.git?.commitChild ? await input.git.commitChild(input.root, child.task_id, input.batch_id, head, record.branch, intentPath) : input.kernel.commitChild ? await input.kernel.commitChild(input.root, child.task_id, input.batch_id, head, record.branch, intentPath) : await commitBatchChild({
8880
+ root: input.root,
8881
+ taskId: child.task_id,
8882
+ batchId: input.batch_id,
8883
+ expectedHead: head,
8884
+ branch: record.branch,
8885
+ intentPath
8886
+ });
8887
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "committed", commit } : c);
8888
+ record.commits.push(commit);
8889
+ head = commit;
8890
+ persist();
8891
+ } catch (error) {
8892
+ const message = error instanceof Error ? error.message : String(error);
8893
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: message } : c);
8894
+ skipDependents(record, child.task_id, `dependency ${child.task_id} failed to commit`);
8895
+ record.batch_state = "failed";
8896
+ persist();
8897
+ break;
8898
+ }
8899
+ } else if (terminal.state === "stopped") {
8900
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: "Kernel reported the child stopped" } : c);
8901
+ childTerminal = true;
8902
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
8903
+ record.batch_state = "needs_human";
8904
+ persist();
8905
+ } else if (terminal.state === "rework" && terminal.operation === "qa") {
8906
+ record.consecutive_qa_failures += 1;
8907
+ if (record.consecutive_qa_failures >= record.budget.qa_failure_limit) {
8908
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: `QA failure limit reached: ${terminal.summary}` } : c);
8909
+ record.batch_state = "needs_human";
8910
+ childTerminal = true;
8911
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
8912
+ persist();
8913
+ } else {
8914
+ persist();
8915
+ }
8916
+ } else if (terminal.state === "failed" || terminal.state === "blocked" || terminal.state === "rework") {
8917
+ const reason = terminal.state === "rework" ? terminal.summary : terminal.reason;
8918
+ childTerminal = true;
8919
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason } : c);
8920
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
8921
+ record.batch_state = "needs_human";
8922
+ persist();
8923
+ } else if (terminal.state === "review_ready") {
8924
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "enrolled", reason: `review reservation ${terminal.operation_id} open` } : c);
8925
+ persist();
8926
+ return reportFor(record, `child ${child.task_id} holds an open Review reservation`, "Submit the reserved foreground Review verdict, then call startBatch again to continue.");
8927
+ }
8928
+ }
8929
+ }
8930
+ return finalize(input.root, record, stopReasonFor(record), "");
8931
+ }
8932
+ function stopReasonFor(record) {
8933
+ switch (record.batch_state) {
8934
+ case "budget_stopped":
8935
+ return budgetStopReason(record, Date.now()) ?? "budget, deadline, or authorization expiry stopped new enrollments";
8936
+ case "failed": {
8937
+ const failedChild = record.children.find((c) => c.state === "needs_human" && c.reason);
8938
+ return failedChild?.reason ?? "a commit or lineage failure stopped the batch";
8939
+ }
8940
+ case "needs_human":
8941
+ return "a parked child needs a human decision";
8942
+ case "completed":
8943
+ return "all enrollable children committed";
8944
+ default:
8945
+ return null;
8946
+ }
8947
+ }
8948
+ async function resumeBatch(input, projection) {
8949
+ let existing = readBatchRunState(input.root, input.batch_id);
8950
+ if (!existing)
8951
+ return startBatch(input);
8952
+ try {
8953
+ await validatePersistedRun(input, existing);
8954
+ } catch (error) {
8955
+ const message = error instanceof Error ? error.message : String(error);
8956
+ if (isLineageBreakError(message)) {
8957
+ if (isTerminalBatchState(existing.batch_state))
8958
+ return replayTerminal(input.root, existing);
8959
+ const failed = failPersistedLineage(input.root, existing, message);
8960
+ return finalize(input.root, failed, message, "");
8961
+ }
8962
+ throw error;
8963
+ }
8964
+ if (isTerminalBatchState(existing.batch_state))
8965
+ return replayTerminal(input.root, existing);
8966
+ if (existing.batch_state === "needs_human")
8967
+ return startBatch(input);
8968
+ const driven = existing.children.find((child) => child.state === "enrolled" || child.state === "settled");
8969
+ if (driven) {
8970
+ if (driven.state === "enrolled") {
8971
+ const drivenDrift = externalHeadDriftMessage(input.root, existing);
8972
+ if (drivenDrift) {
8973
+ const failed = failPersistedLineage(input.root, existing, drivenDrift);
8974
+ return finalize(input.root, failed, drivenDrift, "");
8975
+ }
8976
+ const fresh = requireFreshProjection(await input.kernel.projectTask(input.root, driven.task_id), driven.task_id);
8977
+ const holdsClaim = fresh.claim !== null && fresh.claim.task_id === driven.task_id && input.kernel.ownsTaskClaim(driven.task_id);
8978
+ if (holdsClaim === false && fresh.claim !== null && fresh.claim.task_id === driven.task_id && !input.kernel.ownsTaskClaim(driven.task_id)) {
8979
+ skipDependents(existing, driven.task_id, `dependency ${driven.task_id} parked`);
8980
+ existing = writeBatchRunState(input.root, {
8981
+ ...existing,
8982
+ batch_state: "needs_human",
8983
+ children: existing.children.map((c) => c.task_id === driven.task_id ? { ...c, state: "needs_human", reason: "claim held by another batch" } : c)
8984
+ });
8985
+ return finalize(input.root, existing, "claim held by another batch", "needs-human-attention");
8986
+ }
8987
+ if (!holdsClaim) {
8988
+ const settledRemotely = fresh.projection.lifecycle === "done" && fresh.projection.completion_ready === true;
8989
+ if (settledRemotely) {
8990
+ existing = writeBatchRunState(input.root, {
8991
+ ...existing,
8992
+ children: existing.children.map((c) => c.task_id === driven.task_id ? { ...c, state: "settled", reason: "crash after settlement; resuming at commit" } : c)
8993
+ });
8994
+ } else {
8995
+ existing = writeBatchRunState(input.root, {
8996
+ ...existing,
8997
+ children: existing.children.map((c) => c.task_id === driven.task_id ? { ...c, state: "pending", reason: "enrollment did not complete; re-enrolling" } : c)
8998
+ });
8999
+ }
9000
+ }
9001
+ }
9002
+ const current = existing.children.find((c) => c.task_id === driven.task_id) ?? driven;
9003
+ if (current.state === "settled" || current.state === "enrolled") {
9004
+ try {
9005
+ const driven2 = await driveInterruptedChild(input, current);
9006
+ if (driven2)
9007
+ return driven2;
9008
+ } catch (error) {
9009
+ if (error instanceof BatchCommitAbortError) {
9010
+ const failed = readBatchRunState(input.root, input.batch_id);
9011
+ return finalize(input.root, failed, error.message, "");
9012
+ }
9013
+ throw error;
9014
+ }
9015
+ }
9016
+ existing = readBatchRunState(input.root, input.batch_id);
9017
+ if (isTerminalBatchState(existing.batch_state))
9018
+ return replayTerminal(input.root, existing);
9019
+ }
9020
+ return startBatch(input);
9021
+ }
9022
+
9023
+ class BatchCommitAbortError extends Error {
9024
+ }
9025
+ async function driveInterruptedChild(input, child) {
9026
+ let record = readBatchRunState(input.root, input.batch_id);
9027
+ const persist = () => {
9028
+ record = writeBatchRunState(input.root, record);
9029
+ };
9030
+ if (record.batch_state === "prepared") {
9031
+ record.batch_state = "running";
9032
+ persist();
9033
+ }
9034
+ let head = record.commits.length ? record.commits[record.commits.length - 1] : record.base_head;
9035
+ while (child.state === "enrolled" || child.state === "settled") {
9036
+ if (child.state === "settled") {
9037
+ let existing = null;
9038
+ try {
9039
+ existing = input.git?.lookupBatchCommit ? await input.git.lookupBatchCommit(input.root, child.task_id, input.batch_id, head, record.branch) : input.kernel.lookupBatchCommit ? await input.kernel.lookupBatchCommit(input.root, child.task_id, input.batch_id, head, record.branch) : await lookupBatchCommit({
9040
+ root: input.root,
9041
+ taskId: child.task_id,
9042
+ batchId: input.batch_id,
9043
+ expectedHead: head,
9044
+ branch: record.branch
9045
+ });
9046
+ } catch (error) {
9047
+ const message = error instanceof Error ? error.message : String(error);
9048
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: `commit lookup failed: ${message}` } : c);
9049
+ skipDependents(record, child.task_id, `dependency ${child.task_id} failed to commit`);
9050
+ const isLineageError = message.includes("batch_head_lineage_broken") || message.includes("lineage");
9051
+ record.batch_state = isLineageError ? "failed" : "needs_human";
9052
+ persist();
9053
+ throw new BatchCommitAbortError(`commit lookup failed: ${message}`);
9054
+ }
9055
+ const planChild = input.children.find((c) => c.task_id === child.task_id);
9056
+ const intentPath = planChild?.intent_path ?? undefined;
9057
+ const doCommit = async () => {
9058
+ if (input.git?.commitChild) {
9059
+ return input.git.commitChild(input.root, child.task_id, input.batch_id, head, record.branch, intentPath);
9060
+ }
9061
+ if (input.kernel.commitChild) {
9062
+ return input.kernel.commitChild(input.root, child.task_id, input.batch_id, head, record.branch, intentPath);
9063
+ }
9064
+ return commitBatchChild({
9065
+ root: input.root,
9066
+ taskId: child.task_id,
9067
+ batchId: input.batch_id,
9068
+ expectedHead: head,
9069
+ branch: record.branch,
9070
+ intentPath
9071
+ });
9072
+ };
9073
+ const adopted = existing ?? await doCommit().catch((error) => {
9074
+ const message = error instanceof Error ? error.message : String(error);
9075
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: message } : c);
9076
+ skipDependents(record, child.task_id, `dependency ${child.task_id} failed to commit`);
9077
+ record.batch_state = "failed";
9078
+ persist();
9079
+ throw new BatchCommitAbortError(message);
9080
+ });
9081
+ const { commit } = adopted;
9082
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "committed", commit } : c);
9083
+ record.commits.push(commit);
9084
+ head = commit;
9085
+ persist();
9086
+ child = { ...child, state: "committed", commit };
9087
+ continue;
9088
+ }
9089
+ const terminal = await input.kernel.advanceTask(input.root, child.task_id);
9090
+ if (terminal.state === "completed") {
9091
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "settled", reason: null } : c);
9092
+ record.consecutive_qa_failures = 0;
9093
+ persist();
9094
+ try {
9095
+ const planChild = input.children.find((c) => c.task_id === child.task_id);
9096
+ const intentPath = planChild?.intent_path ?? undefined;
9097
+ const { commit } = input.git?.commitChild ? await input.git.commitChild(input.root, child.task_id, input.batch_id, head, record.branch, intentPath) : input.kernel.commitChild ? await input.kernel.commitChild(input.root, child.task_id, input.batch_id, head, record.branch, intentPath) : await commitBatchChild({
9098
+ root: input.root,
9099
+ taskId: child.task_id,
9100
+ batchId: input.batch_id,
9101
+ expectedHead: head,
9102
+ branch: record.branch,
9103
+ intentPath
9104
+ });
9105
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "committed", commit } : c);
9106
+ record.commits.push(commit);
9107
+ head = commit;
9108
+ persist();
9109
+ child = { ...child, state: "committed", commit };
9110
+ } catch (error) {
9111
+ const message = error instanceof Error ? error.message : String(error);
9112
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason: message } : c);
9113
+ skipDependents(record, child.task_id, `dependency ${child.task_id} failed to commit`);
9114
+ record.batch_state = "failed";
9115
+ persist();
9116
+ return finalize(input.root, record, message, "");
9117
+ }
9118
+ } else if (terminal.state === "review_ready") {
9119
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, reason: `review reservation ${terminal.operation_id} open` } : c);
9120
+ persist();
9121
+ return reportFor(record, `child ${child.task_id} holds an open Review reservation`, "Submit the reserved foreground Review verdict, then call startBatch again to continue.");
9122
+ } else if (terminal.state === "rework" && terminal.operation === "qa") {
9123
+ record.consecutive_qa_failures += 1;
9124
+ if (record.consecutive_qa_failures >= record.budget.qa_failure_limit) {
9125
+ const reason = `QA failure limit reached: ${terminal.summary}`;
9126
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason } : c);
9127
+ record.batch_state = "needs_human";
9128
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
9129
+ persist();
9130
+ return finalize(input.root, record, reason, "");
9131
+ }
9132
+ persist();
9133
+ } else {
9134
+ const reason = terminal.state === "stopped" ? "Kernel reported the child stopped" : terminal.state === "rework" ? terminal.summary : terminal.reason;
9135
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "needs_human", reason } : c);
9136
+ record.batch_state = "needs_human";
9137
+ skipDependents(record, child.task_id, `dependency ${child.task_id} parked`);
9138
+ persist();
9139
+ return finalize(input.root, record, reason, "");
9140
+ }
9141
+ }
9142
+ return null;
9143
+ }
9144
+
9145
+ // plugins/immune-brain/runtime/claude/kernel_ports.ts
9146
+ function diffSnapshotOf(root, record) {
9147
+ if (record.contract === "assurance_kernel/task_record/v4") {
9148
+ if (!record.git_base_head)
9149
+ throw new Error("TaskRecord v4 is missing git_base_head");
9150
+ return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head);
9151
+ }
9152
+ return taskDiffIdentity(root, record.intent_snapshot.scope_hint);
9153
+ }
9154
+ function diffHashOf(root, record) {
9155
+ return diffSnapshotOf(root, record).diff_hash;
9156
+ }
9157
+ function readTaskIntentForRecord(root, taskId) {
9158
+ const currentPath = readTaskRecordRaw(root, taskId).record?.intent_ref?.path;
9159
+ return readTaskIntent(root, taskId, currentPath);
9160
+ }
9161
+ function extractVerdictJson(input) {
9162
+ if (typeof input === "string") {
9163
+ const cleaned = input.split(`
9164
+ `).map((line) => line.trim()).filter((line) => line.startsWith("{") && line.endsWith("}")).join("");
9165
+ if (!cleaned)
9166
+ return null;
9167
+ try {
9168
+ return JSON.parse(cleaned);
9169
+ } catch {
9170
+ return null;
9171
+ }
9172
+ }
9173
+ if (typeof input === "object" && input !== null && !Array.isArray(input))
9174
+ return input;
9175
+ return null;
9176
+ }
9177
+ function verdictFingerprint(raw) {
9178
+ return JSON.stringify({
9179
+ contract: raw.contract ?? null,
9180
+ role: raw.role ?? null,
9181
+ task_id: raw.task_id ?? null,
9182
+ snapshot_digest: raw.snapshot_digest ?? null,
9183
+ decision: raw.decision ?? null,
9184
+ approval: raw.approval ?? null,
9185
+ findings: raw.findings ?? null
9186
+ });
9187
+ }
9188
+ async function submitClaudeReview(host, coordinator, ctx, taskId, verdictInput) {
9189
+ if (verdictInput === undefined)
9190
+ throw new Error("verdict is required");
9191
+ const observed = host.inspectReviewForTask(taskId);
9192
+ if (!observed.ok) {
9193
+ if (observed.release)
9194
+ return coordinator.abandonReview(taskId, observed.reason);
9195
+ return { state: "blocked", reason: observed.reason };
9196
+ }
9197
+ const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
9198
+ if (!parentValid)
9199
+ return coordinator.submitReview(taskId, ctx, verdictInput);
9200
+ const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
9201
+ if (!receiptValid) {
9202
+ return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
9203
+ }
9204
+ const parentJson = extractVerdictJson(verdictInput);
9205
+ const receiptJson = extractVerdictJson(observed.receipt.result);
9206
+ if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
9207
+ return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
9208
+ }
9209
+ return coordinator.submitReview(taskId, ctx, verdictInput);
9210
+ }
9211
+ function stopReason(value) {
9212
+ return typeof value === "string" && value.length > 0 ? value : "user stop";
9213
+ }
9214
+ function assertProjectionBinding(before, after, allowDiffChange = false) {
9215
+ const fields = allowDiffChange ? ["record_revision", "workspace_revision", "intent_revision", "intent_content_hash"] : ["record_revision", "workspace_revision", "intent_revision", "intent_content_hash", "diff_hash"];
9216
+ if (before.error || !before.claim || after.error || !after.claim || before.claim.task_id !== after.claim.task_id || fields.some((field) => before.projection[field] !== after.projection[field])) {
9217
+ throw new Error("Task changed after native confirmation; authority aborted before capability issuance");
9218
+ }
9219
+ }
9220
+ function qaOutcomes(record) {
9221
+ return Object.fromEntries(record.attestations.filter((item) => item.kind === "qa").flatMap((item) => item.acceptance_results).map((result2) => [result2.acceptance_id, { status: result2.status, summary: result2.summary }]));
9222
+ }
9223
+ async function ensureClaudeReviewRevision(root, taskId, projection) {
9224
+ const current = await readTaskRecord(root, taskId);
9225
+ const record = current.record;
9226
+ if (!record)
9227
+ throw new Error(`task ${taskId} has no TaskRecord`);
9228
+ if (current.revision !== projection.projection.record_revision)
9229
+ throw new Error("TaskRecord changed before Review revision preparation");
9230
+ if (record.contract !== "assurance_kernel/task_record/v4")
9231
+ return null;
9232
+ if (!record.git_base_head)
9233
+ throw new Error("Review revision requires a TaskRecord v4 git_base_head");
9234
+ const manifest = captureReviewManifest(root, {
9235
+ taskId,
9236
+ baseHead: record.git_base_head,
9237
+ scopeHint: record.intent_snapshot.scope_hint,
9238
+ expectedDiffHash: projection.projection.diff_hash,
9239
+ intentRevision: projection.projection.intent_revision,
9240
+ intentContentHash: projection.projection.intent_content_hash,
9241
+ recordRevision: projection.projection.record_revision,
9242
+ workspaceRevision: projection.projection.workspace_revision,
9243
+ lifecycle: projection.projection.lifecycle,
9244
+ artifactState: projection.projection.artifact_state,
9245
+ risk: record.intent_snapshot.risk,
9246
+ outcomes: qaOutcomes(record)
9247
+ });
9248
+ return {
9249
+ contract: "assurance_kernel/review_revision/v1",
9250
+ base_head: manifest.base_head,
9251
+ review_tree: manifest.review_tree,
6783
9252
  review_commit: manifest.review_commit,
6784
9253
  review_ref: manifest.review_ref,
6785
9254
  diff_hash: manifest.diff_hash,
@@ -6814,7 +9283,7 @@ async function buildAssuranceSnapshot(root, taskId, role, projection, runner) {
6814
9283
  outcomes: qaOutcomes(record)
6815
9284
  }) : null;
6816
9285
  const dirtyFiles = reviewManifest ? Object.keys(reviewManifest.changed_paths) : reviewBundle ? Object.keys(reviewBundle.dirty_files) : [];
6817
- const snapshot = {
9286
+ const snapshot2 = {
6818
9287
  contract: "assurance_kernel/assurance_snapshot/v2",
6819
9288
  task_id: taskId,
6820
9289
  role,
@@ -6843,7 +9312,7 @@ async function buildAssuranceSnapshot(root, taskId, role, projection, runner) {
6843
9312
  }
6844
9313
  } : {}
6845
9314
  };
6846
- return { snapshot, descriptors, reviewBundle, reviewManifest };
9315
+ return { snapshot: snapshot2, descriptors, reviewBundle, reviewManifest };
6847
9316
  }
6848
9317
  function stagePlanningArtifactTransition(root, record) {
6849
9318
  const intentActive = record.intent_ref.path.replace("docs/plans/archive/", "docs/plans/");
@@ -6854,7 +9323,7 @@ function stagePlanningArtifactTransition(root, record) {
6854
9323
  intentArchive,
6855
9324
  ...specActive ? [specActive, specActive.replace("docs/specs/", "docs/specs/archive/")] : []
6856
9325
  ];
6857
- const paths = candidates.filter((path) => existsSync3(join7(root, path)) || execFileSync4("git", ["ls-files", "--cached", "--", path], { cwd: root, encoding: "utf8" }).trim().length > 0);
9326
+ const paths = candidates.filter((path) => existsSync6(join10(root, path)) || execFileSync4("git", ["ls-files", "--cached", "--", path], { cwd: root, encoding: "utf8" }).trim().length > 0);
6858
9327
  if (paths.length === 0)
6859
9328
  return;
6860
9329
  execFileSync4("git", ["add", "--", ...paths], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
@@ -6884,7 +9353,7 @@ async function mintCapability(registry, input) {
6884
9353
  actor_id: input.actor_id,
6885
9354
  confirmation_ref: input.confirmation_ref,
6886
9355
  expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
6887
- findings_digest: input.action_kind === "request_rework" ? await findingsDigest(input.findings) : null
9356
+ findings_digest: input.action_kind === "request_rework" ? findingsDigestV2(input.findings) : null
6888
9357
  };
6889
9358
  return registry.issue(binding);
6890
9359
  }
@@ -6892,6 +9361,79 @@ function throwIfCancelled(signal) {
6892
9361
  if (signal?.aborted)
6893
9362
  throw new NativeAuthorityError("user_cancelled", "Tool call was cancelled");
6894
9363
  }
9364
+ function syncIsOwnBatchClaim(cwd, existingBatch, taskId, batchBranch) {
9365
+ let claim = null;
9366
+ let workspace = null;
9367
+ try {
9368
+ claim = JSON.parse(readFileSync8(join10(cwd, ".imm", "state", "active-claim.json"), "utf8"));
9369
+ workspace = JSON.parse(readFileSync8(join10(cwd, ".imm", "state", "workspace.json"), "utf8"));
9370
+ } catch {
9371
+ return false;
9372
+ }
9373
+ const currentTaskId = workspace?.state?.current_working || (claim?.lifecycle_status === "active" ? claim?.task_id : null);
9374
+ if (currentTaskId !== taskId || !claim)
9375
+ return false;
9376
+ const branch = spawnSync6("git", ["-C", cwd, "branch", "--show-current"], { encoding: "utf8" }).stdout.trim();
9377
+ if (branch !== batchBranch)
9378
+ return false;
9379
+ const childInBatch = existingBatch.children.find((c) => c.task_id === taskId);
9380
+ if (!childInBatch || !(childInBatch.state === "enrolled" || childInBatch.state === "needs_human")) {
9381
+ return false;
9382
+ }
9383
+ let rec = null;
9384
+ try {
9385
+ rec = JSON.parse(readFileSync8(join10(cwd, ".imm", "state", "tasks", `${taskId}.json`), "utf8"));
9386
+ } catch {
9387
+ return false;
9388
+ }
9389
+ const lineageHeads = [existingBatch.base_head].concat(Array.isArray(existingBatch.commits) ? existingBatch.commits : []);
9390
+ if (!lineageHeads.includes(rec.git_base_head))
9391
+ return false;
9392
+ if (claim.enrollment_event_id !== `enroll-${taskId}-${claim.created_at}`)
9393
+ return false;
9394
+ const createdAt = Date.parse(claim.created_at);
9395
+ if (!Number.isFinite(createdAt) || createdAt > Date.parse(existingBatch.updated_at))
9396
+ return false;
9397
+ if (claim.task_id !== taskId || claim.lifecycle_status !== "active")
9398
+ return false;
9399
+ if (claim.intent_revision !== rec.intent_snapshot?.revision)
9400
+ return false;
9401
+ if (claim.intent_content_hash !== rec.intent_ref?.content_hash)
9402
+ return false;
9403
+ return true;
9404
+ }
9405
+ function findExistingActiveBatch(root, initiativeSlug) {
9406
+ const batchesDir = join10(root, ".imm", "state", "batches");
9407
+ if (!existsSync6(batchesDir))
9408
+ return null;
9409
+ const files = readdirSync2(batchesDir);
9410
+ for (const file of files) {
9411
+ if (!file.endsWith(".json"))
9412
+ continue;
9413
+ let record;
9414
+ try {
9415
+ record = JSON.parse(readFileSync8(join10(batchesDir, file), "utf8"));
9416
+ } catch {
9417
+ return { corrupt: true, path: file };
9418
+ }
9419
+ if (record.contract === "assurance_kernel/batch_run_state/v1" && record.initiative_slug === initiativeSlug) {
9420
+ const validStates = new Set([
9421
+ "prepared",
9422
+ "running",
9423
+ "needs_human",
9424
+ "completed",
9425
+ "budget_stopped",
9426
+ "failed",
9427
+ "rejected"
9428
+ ]);
9429
+ if (typeof record.batch_id !== "string" || typeof record.base_head !== "string" || !Array.isArray(record.children) || !validStates.has(record.batch_state)) {
9430
+ return { corrupt: true, path: file };
9431
+ }
9432
+ return record;
9433
+ }
9434
+ }
9435
+ return null;
9436
+ }
6895
9437
 
6896
9438
  class ClaudeRuntime {
6897
9439
  host;
@@ -6903,12 +9445,19 @@ class ClaudeRuntime {
6903
9445
  hostVersion;
6904
9446
  mutationRegistry = null;
6905
9447
  enrollmentRegistry = createEnrollmentAuthorityRegistry();
9448
+ batchRegistry = createBatchAuthorityRegistry();
6906
9449
  app = null;
9450
+ batchKernel;
9451
+ batchGit;
9452
+ readInitiative;
6907
9453
  constructor(options) {
6908
9454
  this.cwd = options.cwd;
6909
9455
  this.env = options.env ?? process.env;
6910
9456
  this.interactive = options.interactive ?? true;
6911
9457
  this.requestConfirmation = options.requestConfirmation;
9458
+ this.batchKernel = options.batchKernel;
9459
+ this.batchGit = options.batchGit;
9460
+ this.readInitiative = options.readInitiative;
6912
9461
  this.host = options.host ?? new ClaudeReviewHost(new FileHookEventLog);
6913
9462
  this.coordinator = new AssuranceCoordinator({
6914
9463
  ...this.createKernelPorts(),
@@ -6940,7 +9489,7 @@ class ClaudeRuntime {
6940
9489
  frozenRunner: async () => resolveBunRunner(),
6941
9490
  buildAssurance: (root, taskId, role, projection, runner) => buildAssuranceSnapshot(root, taskId, role, projection, runner),
6942
9491
  ensureReviewRevision: (root, taskId, projection) => ensureClaudeReviewRevision(root, taskId, projection),
6943
- runQa: (snapshot, descriptors, runner, options) => runDeterministicQa(snapshot, descriptors, runner, options),
9492
+ runQa: (snapshot2, descriptors, runner, options) => runDeterministicQa(snapshot2, descriptors, runner, options),
6944
9493
  writeReviewEvidence: (input) => writeNativeReviewEvidence(input.evidence),
6945
9494
  applyVerdict: (ctx, input) => this.applyVerdict(ctx, input),
6946
9495
  applyOrdinaryOperation: (ctx, input) => this.executeOrdinary(ctx, input)
@@ -6963,16 +9512,16 @@ class ClaudeRuntime {
6963
9512
  throw new Error(`unsupported native operation ${operation}`);
6964
9513
  if (!this.requestConfirmation)
6965
9514
  throw new NativeAuthorityError("interaction_not_opened", "native confirmation port is unavailable");
6966
- const result = await this.requestConfirmation({ operation, taskId: meta.taskId, toolCallId: meta.toolCallId, signal: meta.signal, ...binding });
9515
+ const result2 = await this.requestConfirmation({ operation, taskId: meta.taskId, toolCallId: meta.toolCallId, signal: meta.signal, ...binding });
6967
9516
  throwIfCancelled(meta.signal);
6968
- const gate = evaluateNativeGate({ operation, interactive, decision: result.decision });
9517
+ const gate = evaluateNativeGate({ operation, interactive, decision: result2.decision });
6969
9518
  if (!gate.ok)
6970
9519
  throw gate.error;
6971
9520
  return {
6972
9521
  confirmation_ref: confirmationRef({
6973
9522
  connectionId: meta.sessionId,
6974
9523
  toolCallId: meta.toolCallId,
6975
- requestId: result.requestId,
9524
+ requestId: result2.requestId,
6976
9525
  operation,
6977
9526
  taskId: meta.taskId,
6978
9527
  ...binding
@@ -7038,6 +9587,17 @@ class ClaudeRuntime {
7038
9587
  operation: { op: "resolve_finding", finding_id: findingId, actor_id: "executor" }
7039
9588
  });
7040
9589
  }
9590
+ async refuteFinding(taskId, findingId, attestationId) {
9591
+ return this.executeOrdinary({ cwd: this.cwd }, {
9592
+ taskId,
9593
+ operation: {
9594
+ op: "refute_finding",
9595
+ finding_id: findingId,
9596
+ attestation_id: attestationId,
9597
+ actor_id: "executor"
9598
+ }
9599
+ });
9600
+ }
7041
9601
  async authorize(taskId, operation, meta, extra = {}) {
7042
9602
  if (operation === "repair_authority_state") {
7043
9603
  const authority = reconcileKernelAuthority(this.cwd, taskId);
@@ -7076,8 +9636,8 @@ class ClaudeRuntime {
7076
9636
  throw new Error("approve_breaking_intent_revision requires next_intent");
7077
9637
  const nextIntentHash = nextIntent ? canonicalIntentHash(nextIntent) : undefined;
7078
9638
  const nextIntentRef = nextIntent ? { path: `docs/plans/${nextIntent.task_id}.intent.json`, content_hash: nextIntentHash } : undefined;
7079
- const sidecar = nextIntent ? join7(this.cwd, priorIntent.intent_ref.path) : undefined;
7080
- const priorBytes = sidecar ? readFileSync7(sidecar) : undefined;
9639
+ const sidecar = nextIntent ? join10(this.cwd, priorIntent.intent_ref.path) : undefined;
9640
+ const priorBytes = sidecar ? readFileSync8(sidecar) : undefined;
7081
9641
  const priorIndexState = sidecar ? execFileSync4("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
7082
9642
  cwd: this.cwd,
7083
9643
  stdio: ["ignore", "pipe", "pipe"]
@@ -7085,7 +9645,7 @@ class ClaudeRuntime {
7085
9645
  const restoreStagedIntent = () => {
7086
9646
  if (!sidecar || !priorBytes || !priorIndexState)
7087
9647
  return;
7088
- writeFileSync3(sidecar, priorBytes);
9648
+ writeFileSync5(sidecar, priorBytes);
7089
9649
  execFileSync4("git", ["update-index", "--force-remove", "--", priorIntent.intent_ref.path], {
7090
9650
  cwd: this.cwd,
7091
9651
  stdio: ["ignore", "pipe", "pipe"]
@@ -7102,7 +9662,7 @@ class ClaudeRuntime {
7102
9662
  let gate;
7103
9663
  try {
7104
9664
  if (sidecar && nextIntent) {
7105
- writeFileSync3(sidecar, `${JSON.stringify(nextIntent, null, 2)}
9665
+ writeFileSync5(sidecar, `${JSON.stringify(nextIntent, null, 2)}
7106
9666
  `);
7107
9667
  execFileSync4("git", ["add", "--", priorIntent.intent_ref.path], { cwd: this.cwd, stdio: ["ignore", "pipe", "pipe"] });
7108
9668
  const preparedRecord = await readTaskRecord(this.cwd, taskId);
@@ -7165,7 +9725,7 @@ class ClaudeRuntime {
7165
9725
  ...op === "stop" ? { reason: stopReason(extra.reason) } : {}
7166
9726
  });
7167
9727
  throwIfCancelled(meta.signal);
7168
- const result = app.execute({
9728
+ const result2 = app.execute({
7169
9729
  root: this.cwd,
7170
9730
  task_id: taskId,
7171
9731
  operation: {
@@ -7181,8 +9741,8 @@ class ClaudeRuntime {
7181
9741
  now
7182
9742
  });
7183
9743
  if (op === "stop" || op === "authorize_rework" || op === "approve_breaking_intent_revision")
7184
- stagePlanningArtifactTransition(this.cwd, result.record);
7185
- return result;
9744
+ stagePlanningArtifactTransition(this.cwd, result2.record);
9745
+ return result2;
7186
9746
  } catch (error) {
7187
9747
  if (sidecar && priorBytes && priorIndexState) {
7188
9748
  const current = await readTaskRecord(this.cwd, taskId);
@@ -7201,20 +9761,12 @@ class ClaudeRuntime {
7201
9761
  this.coordinator.commitInvocation(input.invocation);
7202
9762
  const settlement = apply();
7203
9763
  input.hooks?.onCommit?.();
7204
- const result = await settlement;
9764
+ const result2 = await settlement;
7205
9765
  await input.hooks?.afterCommit?.();
7206
- return result;
9766
+ return result2;
7207
9767
  };
7208
9768
  if (input.verdict.decision === "rework") {
7209
- const findings = input.verdict.findings.map((finding) => ({
7210
- id: finding.id,
7211
- kind: finding.kind,
7212
- status: "open",
7213
- acceptance_id: finding.acceptance_id,
7214
- source: "review",
7215
- review_round: null,
7216
- summary: finding.summary
7217
- }));
9769
+ const findings = reviewReworkFindings(input.verdict);
7218
9770
  const capability2 = await mintCapability(registry, {
7219
9771
  authority_kind: input.snapshot.role,
7220
9772
  task_id: input.taskId,
@@ -7229,7 +9781,7 @@ class ClaudeRuntime {
7229
9781
  confirmation_ref: `claude:${input.actorId}`
7230
9782
  });
7231
9783
  await input.hooks?.beforeCommit?.();
7232
- const result = await commitAndApply(async () => app.execute({
9784
+ const result2 = await commitAndApply(async () => app.execute({
7233
9785
  root: ctx.cwd,
7234
9786
  task_id: input.taskId,
7235
9787
  operation: { op: "request_rework", capability: capability2, findings, actor_id: input.actorId },
@@ -7237,11 +9789,11 @@ class ClaudeRuntime {
7237
9789
  diffProvider: diffSnapshotOf,
7238
9790
  now
7239
9791
  }));
7240
- stagePlanningArtifactTransition(ctx.cwd, result.record);
9792
+ stagePlanningArtifactTransition(ctx.cwd, result2.record);
7241
9793
  return;
7242
9794
  }
7243
9795
  const approval = {
7244
- id: `approval-${input.snapshot.role}-${randomUUID5().slice(0, 8)}`,
9796
+ id: `approval-${input.snapshot.role}-${randomUUID7().slice(0, 8)}`,
7245
9797
  kind: input.snapshot.role === "qa" ? "qa" : "review",
7246
9798
  authority_role: input.snapshot.role === "qa" ? "qa" : "reviewer",
7247
9799
  task_revision: input.snapshot.intent_revision,
@@ -7278,13 +9830,13 @@ class ClaudeRuntime {
7278
9830
  const { app } = await this.authority();
7279
9831
  const operation = input.operation.op === "revise_intent" ? { ...input.operation, next_intent: await parseTaskIntentV1(input.operation.next_intent) } : input.operation;
7280
9832
  const priorIntent = await readTaskIntentForRecord(ctx.cwd, input.taskId);
7281
- const sidecar = join7(ctx.cwd, priorIntent.intent_ref.path);
7282
- const priorBytes = operation.op === "revise_intent" ? readFileSync7(sidecar) : null;
9833
+ const sidecar = join10(ctx.cwd, priorIntent.intent_ref.path);
9834
+ const priorBytes = operation.op === "revise_intent" ? readFileSync8(sidecar) : null;
7283
9835
  try {
7284
9836
  if (priorBytes)
7285
- writeFileSync3(sidecar, `${JSON.stringify(operation.next_intent, null, 2)}
9837
+ writeFileSync5(sidecar, `${JSON.stringify(operation.next_intent, null, 2)}
7286
9838
  `);
7287
- const result = await app.execute({
9839
+ const result2 = await app.execute({
7288
9840
  root: ctx.cwd,
7289
9841
  task_id: input.taskId,
7290
9842
  operation,
@@ -7293,17 +9845,588 @@ class ClaudeRuntime {
7293
9845
  now: new Date().toISOString()
7294
9846
  });
7295
9847
  if (operation.op === "freeze_artifacts" || operation.op === "stop")
7296
- stagePlanningArtifactTransition(ctx.cwd, result.record);
7297
- return result;
9848
+ stagePlanningArtifactTransition(ctx.cwd, result2.record);
9849
+ return result2;
7298
9850
  } catch (error) {
7299
9851
  if (priorBytes) {
7300
9852
  const current = await readTaskRecord(ctx.cwd, input.taskId);
7301
9853
  if (current.record?.intent_snapshot.revision === priorIntent.intent.revision)
7302
- writeFileSync3(sidecar, priorBytes);
9854
+ writeFileSync5(sidecar, priorBytes);
7303
9855
  }
7304
9856
  throw error;
7305
9857
  }
7306
9858
  }
9859
+ async startUnattendedBatch(initiativeSlug, meta) {
9860
+ throwIfCancelled(meta.signal);
9861
+ const probe = probeHost(this.env, process.platform, this.hostVersion);
9862
+ if (!probe.ok)
9863
+ throw new NativeAuthorityError("unsupported_host", probe.reason);
9864
+ const interactive = meta.interactive ?? this.interactive;
9865
+ if (!interactive)
9866
+ throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
9867
+ if (!this.requestConfirmation)
9868
+ throw new NativeAuthorityError("interaction_not_opened", "native confirmation port is unavailable");
9869
+ if (!/^[a-z0-9][a-z0-9._-]{0,127}$/i.test(initiativeSlug)) {
9870
+ return {
9871
+ state: "rejected",
9872
+ reason: `invalid initiative slug: ${initiativeSlug}`,
9873
+ recovery_action: "specify a valid initiative slug and retry in the current Host"
9874
+ };
9875
+ }
9876
+ const existingBatch = findExistingActiveBatch(this.cwd, initiativeSlug);
9877
+ const isResuming = existingBatch !== null;
9878
+ if (existingBatch?.corrupt) {
9879
+ return {
9880
+ state: "blocked",
9881
+ reason: `batch run state is unreadable or invalid: ${existingBatch.path}`,
9882
+ recovery_action: "resolve or remove the invalid batch state file, then retry in the current Host"
9883
+ };
9884
+ }
9885
+ const batchBranch = `imm/${initiativeSlug}`;
9886
+ const workspaceState = readWorkspaceStateRaw(this.cwd);
9887
+ const claim = readBackendClaim(this.cwd);
9888
+ const activeTaskId = workspaceState.state.current_working || (claim?.lifecycle_status === "active" ? claim.task_id : null);
9889
+ const isOwnClaim = isResuming && activeTaskId !== null && syncIsOwnBatchClaim(this.cwd, existingBatch, activeTaskId, batchBranch);
9890
+ if (activeTaskId && !isOwnClaim) {
9891
+ return {
9892
+ state: "blocked",
9893
+ reason: `an active workspace claim already exists for task: ${activeTaskId}`,
9894
+ recovery_action: "resolve or stop the active task before starting a batch in the current Host"
9895
+ };
9896
+ }
9897
+ let baseHead;
9898
+ try {
9899
+ baseHead = readGitHead(this.cwd);
9900
+ } catch (err) {
9901
+ return {
9902
+ state: "rejected",
9903
+ reason: err instanceof Error ? err.message : String(err),
9904
+ recovery_action: "commit working changes and ensure a committed Git HEAD exists in the current Host"
9905
+ };
9906
+ }
9907
+ const branchExists = spawnSync6("git", ["-C", this.cwd, "show-ref", "--verify", "--quiet", `refs/heads/${batchBranch}`]);
9908
+ if (branchExists.status === 0 && !isResuming) {
9909
+ return {
9910
+ state: "rejected",
9911
+ reason: `branch preflight failed: branch refs/heads/${batchBranch} already exists`,
9912
+ recovery_action: "delete or rename the conflicting branch, or commit working changes in the current Host"
9913
+ };
9914
+ }
9915
+ const statusProc = spawnSync6("git", ["-C", this.cwd, "status", "--porcelain=v1", "-z", "--no-renames", "--untracked-files=all"], {
9916
+ encoding: "utf8"
9917
+ });
9918
+ if (statusProc.status !== 0) {
9919
+ return {
9920
+ state: "rejected",
9921
+ reason: "branch preflight failed: git status is unreadable",
9922
+ recovery_action: "check the repository integrity and retry in the current Host"
9923
+ };
9924
+ }
9925
+ const statusEntries = [];
9926
+ for (const entry of statusProc.stdout.split("\x00")) {
9927
+ if (entry.length === 0)
9928
+ continue;
9929
+ statusEntries.push({ code: entry.slice(0, 2), path: entry.slice(3) });
9930
+ }
9931
+ if (statusEntries.length > 0) {
9932
+ if (!isResuming) {
9933
+ return {
9934
+ state: "rejected",
9935
+ reason: "branch preflight failed: working tree is dirty",
9936
+ recovery_action: "delete or rename the conflicting branch, or commit working changes in the current Host"
9937
+ };
9938
+ }
9939
+ const inFlightChild = existingBatch.children.find((c) => c.state === "enrolled" || c.state === "needs_human" || c.state === "settled");
9940
+ let authorizedScope = [];
9941
+ if (inFlightChild) {
9942
+ try {
9943
+ const recordRead = readTaskRecordRaw(this.cwd, inFlightChild.task_id);
9944
+ authorizedScope = recordRead.record?.intent_snapshot?.scope_hint ?? [];
9945
+ } catch {}
9946
+ if (authorizedScope.length === 0 && inFlightChild.state === "settled") {
9947
+ try {
9948
+ const settled = readAuditTaskPair(this.cwd, inFlightChild.task_id)?.record;
9949
+ authorizedScope = settled?.intent_snapshot?.scope_hint ?? [];
9950
+ } catch {}
9951
+ }
9952
+ if (authorizedScope.length === 0) {
9953
+ try {
9954
+ const read = readTaskIntentForRecord(this.cwd, inFlightChild.task_id);
9955
+ authorizedScope = read.intent.scope_hint ?? [];
9956
+ } catch {}
9957
+ }
9958
+ if (authorizedScope.length === 0) {
9959
+ return {
9960
+ state: "rejected",
9961
+ reason: "branch preflight failed: cannot derive the in-flight child's authorized scope",
9962
+ recovery_action: "resolve the child's intent record, then retry in the current Host"
9963
+ };
9964
+ }
9965
+ }
9966
+ const dirtyBytes = statusEntries.some(({ code }) => code === "??" || code[1] !== " ");
9967
+ if (dirtyBytes) {
9968
+ return {
9969
+ state: "rejected",
9970
+ reason: "branch preflight failed: working tree has unstaged or untracked changes",
9971
+ recovery_action: "stage the in-flight changes with git add, then retry in the current Host"
9972
+ };
9973
+ }
9974
+ const outsideScope = statusEntries.some(({ path }) => {
9975
+ if (path.startsWith(".imm/") || path.startsWith("docs/plans/") || path.startsWith("docs/specs/"))
9976
+ return false;
9977
+ return !authorizedScope.some((scopePath) => pathMatchesScope(path, scopePath));
9978
+ });
9979
+ if (outsideScope) {
9980
+ return {
9981
+ state: "rejected",
9982
+ reason: "branch preflight failed: working tree has changes outside the authorized child scope",
9983
+ recovery_action: "commit or unstage changes outside the active task scope, then retry in the current Host"
9984
+ };
9985
+ }
9986
+ }
9987
+ const now = new Date().toISOString();
9988
+ let recoveryChildren = [];
9989
+ let planDigest;
9990
+ let confirmChildrenDetails = [];
9991
+ let confirmExcludedDetails = [];
9992
+ const recoveryRiskByTask = new Map;
9993
+ let budget2 = existingBatch ? existingBatch.budget : { max_children: 10, deadline_at: new Date(Date.now() + 8 * 3600 * 1000).toISOString(), qa_failure_limit: 2 };
9994
+ if (isResuming) {
9995
+ try {
9996
+ recoveryChildren = await Promise.all(existingBatch.children.map(async (c) => {
9997
+ const intentPath = `docs/plans/${c.task_id}.intent.json`;
9998
+ let read = { intent: { revision: 1, risk: "material" }, content_hash: "" };
9999
+ try {
10000
+ const taskRecordRead = readTaskRecordRaw(this.cwd, c.task_id);
10001
+ if (taskRecordRead.record) {
10002
+ read = {
10003
+ intent: taskRecordRead.record.intent_snapshot,
10004
+ content_hash: taskRecordRead.record.intent_ref.content_hash
10005
+ };
10006
+ } else {
10007
+ read = await readTaskIntent(this.cwd, c.task_id, intentPath);
10008
+ }
10009
+ } catch {
10010
+ const archivePath2 = `docs/plans/archive/${c.task_id}.intent.json`;
10011
+ try {
10012
+ read = await readTaskIntent(this.cwd, c.task_id, archivePath2);
10013
+ } catch {
10014
+ read = await readTaskIntent(this.cwd, c.task_id, intentPath);
10015
+ }
10016
+ }
10017
+ recoveryRiskByTask.set(c.task_id, read.intent?.risk ?? "material");
10018
+ const isDone = c.state === "committed" || c.state === "settled";
10019
+ return {
10020
+ task_id: c.task_id,
10021
+ slice_id: c.slice_id,
10022
+ status: isDone ? "already_settled" : "enrollable",
10023
+ blocked_by: [...c.blocked_by],
10024
+ reason: c.reason ?? null,
10025
+ intent_path: intentPath,
10026
+ intent_revision: read.intent.revision,
10027
+ intent_content_hash: read.content_hash
10028
+ };
10029
+ }));
10030
+ } catch (err) {
10031
+ return {
10032
+ state: "rejected",
10033
+ reason: `failed to project batch plan: ${err instanceof Error ? err.message : String(err)}`,
10034
+ recovery_action: "review initiative issues and planning sidecars in the current Host"
10035
+ };
10036
+ }
10037
+ planDigest = computeBatchPlanDigest(recoveryChildren);
10038
+ for (const c of recoveryChildren) {
10039
+ confirmChildrenDetails.push({
10040
+ task_id: c.task_id,
10041
+ slice_id: c.slice_id,
10042
+ risk: recoveryRiskByTask.get(c.task_id) ?? "material"
10043
+ });
10044
+ }
10045
+ } else {
10046
+ let plan;
10047
+ try {
10048
+ plan = await projectBatchPlan(this.cwd, initiativeSlug, { confirmation_time: now }, this.readInitiative ?? observeGithubInitiative);
10049
+ } catch (err) {
10050
+ const msg = err instanceof Error ? err.message : String(err);
10051
+ if (msg.includes("has no enrollable children")) {
10052
+ return {
10053
+ state: "rejected",
10054
+ reason: "empty enrollable child set: no enrollable child tasks found in the initiative plan",
10055
+ recovery_action: "ensure the initiative has uncompleted, non-critical child tasks in the current Host"
10056
+ };
10057
+ }
10058
+ return {
10059
+ state: "rejected",
10060
+ reason: `failed to project batch plan: ${msg}`,
10061
+ recovery_action: "review initiative issues and planning sidecars in the current Host"
10062
+ };
10063
+ }
10064
+ if (plan.enrollable.length === 0) {
10065
+ return {
10066
+ state: "rejected",
10067
+ reason: "empty enrollable child set: no enrollable child tasks found in the initiative plan",
10068
+ recovery_action: "ensure the initiative has uncompleted, non-critical child tasks in the current Host"
10069
+ };
10070
+ }
10071
+ budget2 = plan.budget;
10072
+ const enrollableChildById = new Map(plan.enrollable.map((c) => [c.task_id, c]));
10073
+ recoveryChildren = plan.children.filter((c) => c.status === "enrollable").map((c) => {
10074
+ const digestChild = enrollableChildById.get(c.task_id);
10075
+ return {
10076
+ ...c,
10077
+ blocked_by: digestChild ? [...digestChild.blocked_by] : c.blocked_by
10078
+ };
10079
+ });
10080
+ planDigest = computeBatchPlanDigest(plan.enrollable);
10081
+ for (const c of plan.children.filter((item) => item.status === "enrollable")) {
10082
+ let childRisk = "material";
10083
+ try {
10084
+ const intentRead = await readTaskIntent(this.cwd, c.task_id, c.intent_path ?? undefined);
10085
+ childRisk = intentRead.intent.risk;
10086
+ } catch {}
10087
+ confirmChildrenDetails.push({
10088
+ task_id: c.task_id,
10089
+ slice_id: c.slice_id,
10090
+ risk: childRisk
10091
+ });
10092
+ }
10093
+ confirmExcludedDetails = plan.children.filter((c) => c.status !== "enrollable").map((c) => ({
10094
+ task_id: c.task_id,
10095
+ slice_id: c.slice_id,
10096
+ reason: c.status === "needs_human" && c.reason === "critical" ? "critical" : c.reason ?? c.status
10097
+ }));
10098
+ }
10099
+ const isExistingExpired = isResuming && Date.parse(existingBatch.authorization_expires_at) <= Date.now();
10100
+ const expiresAt = isResuming && !isExistingExpired && existingBatch.batch_state === "running" ? existingBatch.authorization_expires_at : new Date(Date.now() + 10 * 60 * 1000).toISOString();
10101
+ const batchDetails = {
10102
+ initiative_slug: initiativeSlug,
10103
+ batch_branch: batchBranch,
10104
+ children: confirmChildrenDetails,
10105
+ excluded: confirmExcludedDetails,
10106
+ budget: budget2,
10107
+ expires_at: expiresAt
10108
+ };
10109
+ const configuredTimeout = Number(this.env.IMMUNE_BRAIN_BATCH_TIMEOUT_MS);
10110
+ const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : 60000;
10111
+ const timeoutController = new AbortController;
10112
+ const timeoutTimer = setTimeout(() => {
10113
+ timeoutController.abort(new NativeAuthorityError("user_cancelled", "native confirmation timed out"));
10114
+ }, timeoutMs);
10115
+ const elicitationSignal = meta.signal ? AbortSignal.any([meta.signal, timeoutController.signal]) : timeoutController.signal;
10116
+ let confirmationResult;
10117
+ try {
10118
+ confirmationResult = await this.requestConfirmation({
10119
+ operation: "start_unattended_batch",
10120
+ initiativeSlug,
10121
+ toolCallId: meta.toolCallId,
10122
+ planDigest,
10123
+ batchDetails,
10124
+ signal: elicitationSignal
10125
+ });
10126
+ } catch (err) {
10127
+ if (timeoutController.signal.aborted && !meta.signal?.aborted) {
10128
+ return {
10129
+ state: "rejected",
10130
+ reason: "native confirmation timed out waiting for user interaction",
10131
+ recovery_action: "retry through a fresh native gate in the current Host"
10132
+ };
10133
+ }
10134
+ if (meta.signal?.aborted) {
10135
+ return {
10136
+ state: "cancelled",
10137
+ reason: "user cancelled before batch execution",
10138
+ recovery_action: "wait for a fresh literal-user request"
10139
+ };
10140
+ }
10141
+ if (err instanceof NativeAuthorityError) {
10142
+ if (err.reasonCode === "unsupported_host")
10143
+ throw err;
10144
+ if (err.reasonCode === "user_cancelled") {
10145
+ return { state: "cancelled", reason: err.message, recovery_action: err.recoveryAction };
10146
+ }
10147
+ if (err.reasonCode === "user_denied") {
10148
+ return { state: "rejected", reason: err.message, recovery_action: err.recoveryAction };
10149
+ }
10150
+ return { state: "rejected", reason: err.message, recovery_action: err.recoveryAction };
10151
+ }
10152
+ return {
10153
+ state: "rejected",
10154
+ reason: err instanceof Error ? err.message : String(err),
10155
+ recovery_action: "retry through a fresh native gate in the current Host"
10156
+ };
10157
+ } finally {
10158
+ clearTimeout(timeoutTimer);
10159
+ }
10160
+ if (meta.signal?.aborted) {
10161
+ return {
10162
+ state: "cancelled",
10163
+ reason: "user cancelled before batch execution",
10164
+ recovery_action: "wait for a fresh literal-user request"
10165
+ };
10166
+ }
10167
+ if (confirmationResult.decision === "decline") {
10168
+ return {
10169
+ state: "rejected",
10170
+ reason: "native interaction declined",
10171
+ recovery_action: "wait for a fresh literal-user request"
10172
+ };
10173
+ }
10174
+ if (confirmationResult.decision === "cancel") {
10175
+ return {
10176
+ state: "cancelled",
10177
+ reason: "native interaction cancelled",
10178
+ recovery_action: "wait for a fresh literal-user request"
10179
+ };
10180
+ }
10181
+ if (confirmationResult.decision !== "accept") {
10182
+ return {
10183
+ state: "rejected",
10184
+ reason: "native interaction returned no decision",
10185
+ recovery_action: "retry through a fresh native gate in the current Host"
10186
+ };
10187
+ }
10188
+ const postWorkspaceState = readWorkspaceStateRaw(this.cwd);
10189
+ const postClaim = readBackendClaim(this.cwd);
10190
+ const postActiveTaskId = postWorkspaceState.state.current_working || (postClaim?.lifecycle_status === "active" ? postClaim.task_id : null);
10191
+ const isPostOwnClaim = isResuming && postActiveTaskId !== null && syncIsOwnBatchClaim(this.cwd, existingBatch, postActiveTaskId, batchBranch);
10192
+ if (postActiveTaskId && !isPostOwnClaim) {
10193
+ return {
10194
+ state: "blocked",
10195
+ reason: `an active workspace claim appeared during confirmation for task: ${postActiveTaskId}`,
10196
+ recovery_action: "resolve or stop the active task before starting a batch in the current Host"
10197
+ };
10198
+ }
10199
+ if (isResuming) {
10200
+ let recheckedDigest;
10201
+ try {
10202
+ const recheckedChildren = await Promise.all(existingBatch.children.map(async (c) => {
10203
+ const intentPath = `docs/plans/${c.task_id}.intent.json`;
10204
+ let read = { intent: { revision: 1, risk: "material" }, content_hash: "" };
10205
+ try {
10206
+ const taskRecordRead = readTaskRecordRaw(this.cwd, c.task_id);
10207
+ if (taskRecordRead.record) {
10208
+ read = {
10209
+ intent: taskRecordRead.record.intent_snapshot,
10210
+ content_hash: taskRecordRead.record.intent_ref.content_hash
10211
+ };
10212
+ } else {
10213
+ read = await readTaskIntent(this.cwd, c.task_id, intentPath);
10214
+ }
10215
+ } catch {
10216
+ const archivePath2 = `docs/plans/archive/${c.task_id}.intent.json`;
10217
+ try {
10218
+ read = await readTaskIntent(this.cwd, c.task_id, archivePath2);
10219
+ } catch {
10220
+ read = await readTaskIntent(this.cwd, c.task_id, intentPath);
10221
+ }
10222
+ }
10223
+ const isDone = c.state === "committed" || c.state === "settled";
10224
+ return {
10225
+ task_id: c.task_id,
10226
+ slice_id: c.slice_id,
10227
+ status: isDone ? "already_settled" : "enrollable",
10228
+ blocked_by: [...c.blocked_by],
10229
+ reason: c.reason ?? null,
10230
+ intent_path: intentPath,
10231
+ intent_revision: read.intent.revision,
10232
+ intent_content_hash: read.content_hash
10233
+ };
10234
+ }));
10235
+ recheckedDigest = computeBatchPlanDigest(recheckedChildren);
10236
+ } catch (err) {
10237
+ return {
10238
+ state: "rejected",
10239
+ reason: "batch plan became unreadable after native confirmation",
10240
+ recovery_action: "review the current workspace and retry through a fresh native gate in the current Host"
10241
+ };
10242
+ }
10243
+ if (recheckedDigest !== planDigest) {
10244
+ return {
10245
+ state: "rejected",
10246
+ reason: "batch plan changed after native confirmation",
10247
+ recovery_action: "review the current workspace and retry through a fresh native gate in the current Host"
10248
+ };
10249
+ }
10250
+ } else {
10251
+ let revalidatedPlan;
10252
+ try {
10253
+ revalidatedPlan = await projectBatchPlan(this.cwd, initiativeSlug, { confirmation_time: now }, this.readInitiative ?? observeGithubInitiative);
10254
+ } catch (err) {
10255
+ return {
10256
+ state: "rejected",
10257
+ reason: "batch plan became unreadable after native confirmation",
10258
+ recovery_action: "review the current workspace and retry through a fresh native gate in the current Host"
10259
+ };
10260
+ }
10261
+ const revalidatedDigest = computeBatchPlanDigest(revalidatedPlan.enrollable);
10262
+ if (revalidatedDigest !== planDigest) {
10263
+ return {
10264
+ state: "rejected",
10265
+ reason: "batch plan changed after native confirmation",
10266
+ recovery_action: "review the current workspace and retry through a fresh native gate in the current Host"
10267
+ };
10268
+ }
10269
+ }
10270
+ let postHead;
10271
+ try {
10272
+ postHead = readGitHead(this.cwd);
10273
+ } catch (err) {
10274
+ return {
10275
+ state: "rejected",
10276
+ reason: "Git repository became unreadable after native confirmation",
10277
+ recovery_action: "review the current workspace and retry through a fresh native gate in the current Host"
10278
+ };
10279
+ }
10280
+ if (postHead !== baseHead) {
10281
+ return {
10282
+ state: "rejected",
10283
+ reason: "Git HEAD moved after native confirmation",
10284
+ recovery_action: "review the current workspace and retry through a fresh native gate in the current Host"
10285
+ };
10286
+ }
10287
+ const finalWorkspaceState = readWorkspaceStateRaw(this.cwd);
10288
+ const finalClaim = readBackendClaim(this.cwd);
10289
+ const finalActiveTaskId = finalWorkspaceState.state.current_working || (finalClaim?.lifecycle_status === "active" ? finalClaim.task_id : null);
10290
+ const finalIsOwnClaim = isResuming && finalActiveTaskId !== null && syncIsOwnBatchClaim(this.cwd, existingBatch, finalActiveTaskId, batchBranch);
10291
+ if (finalActiveTaskId && !finalIsOwnClaim) {
10292
+ return {
10293
+ state: "blocked",
10294
+ reason: `an active workspace claim appeared during confirmation for task: ${finalActiveTaskId}`,
10295
+ recovery_action: "resolve or stop the active task before starting a batch in the current Host"
10296
+ };
10297
+ }
10298
+ if (meta.signal?.aborted) {
10299
+ return {
10300
+ state: "cancelled",
10301
+ reason: "user cancelled before batch execution",
10302
+ recovery_action: "wait for a fresh literal-user request"
10303
+ };
10304
+ }
10305
+ const batchId = existingBatch ? existingBatch.batch_id : `batch-${initiativeSlug}-${Date.now()}`;
10306
+ const confirmation = confirmationRef({
10307
+ connectionId: meta.sessionId,
10308
+ toolCallId: meta.toolCallId,
10309
+ requestId: confirmationResult.requestId,
10310
+ operation: "start_unattended_batch",
10311
+ initiativeSlug,
10312
+ planDigest
10313
+ });
10314
+ const binding = {
10315
+ batch_id: batchId,
10316
+ initiative_slug: initiativeSlug,
10317
+ plan_digest: planDigest,
10318
+ branch: batchBranch,
10319
+ base_head: isResuming ? existingBatch.base_head : baseHead,
10320
+ budget: budget2,
10321
+ actor_id: "user",
10322
+ confirmation_ref: confirmation,
10323
+ expires_at: expiresAt,
10324
+ nonce: enrollmentNonce()
10325
+ };
10326
+ const capability = this.batchRegistry.issue(binding, recoveryChildren, now);
10327
+ const basePort = this.createBatchKernelPort(this.batchRegistry, capability, binding);
10328
+ const kernelPort = {
10329
+ ...basePort,
10330
+ ownsTaskClaim: (taskId) => {
10331
+ if (isResuming && taskId === activeTaskId) {
10332
+ return syncIsOwnBatchClaim(this.cwd, existingBatch, taskId, batchBranch);
10333
+ }
10334
+ return basePort.ownsTaskClaim(taskId);
10335
+ }
10336
+ };
10337
+ const report = await startBatch({
10338
+ root: this.cwd,
10339
+ batch_id: batchId,
10340
+ initiative_slug: initiativeSlug,
10341
+ registry: this.batchRegistry,
10342
+ capability,
10343
+ children: recoveryChildren,
10344
+ plan_digest: planDigest,
10345
+ base_head: isResuming ? existingBatch.base_head : baseHead,
10346
+ confirmation_time: now,
10347
+ authorization_expires_at: expiresAt,
10348
+ budget: budget2,
10349
+ now,
10350
+ kernel: kernelPort,
10351
+ git: this.batchGit
10352
+ });
10353
+ if (report.batch_state === "rejected") {
10354
+ return {
10355
+ state: "rejected",
10356
+ reason: report.reason ?? "batch run rejected",
10357
+ recovery_action: "delete or rename the conflicting branch, or commit working changes and retry in the current Host"
10358
+ };
10359
+ }
10360
+ return {
10361
+ state: "started",
10362
+ batch_id: batchId,
10363
+ report
10364
+ };
10365
+ }
10366
+ createBatchKernelPort(registry, capability, binding) {
10367
+ const realPort = {
10368
+ enrollTask: async ({ root, task_id, batch }) => {
10369
+ const now = new Date().toISOString();
10370
+ const derived = deriveChildEnrollment(root, batch.registry, {
10371
+ capability: batch.capability,
10372
+ binding,
10373
+ task_id,
10374
+ expected_head: batch.binding.expected_head,
10375
+ now
10376
+ });
10377
+ const enrollmentCapability = this.enrollmentRegistry.issue(derived.binding);
10378
+ const input = {
10379
+ task_id,
10380
+ intent_path: derived.binding.intent_path,
10381
+ intent_revision: derived.binding.intent_revision,
10382
+ preparation_digest: derived.binding.preparation_digest,
10383
+ capability: enrollmentCapability,
10384
+ capability_binding: derived.binding,
10385
+ batch: {
10386
+ registry: batch.registry,
10387
+ capability: batch.capability,
10388
+ binding,
10389
+ expected_head: batch.binding.expected_head
10390
+ },
10391
+ now
10392
+ };
10393
+ const rehearsal = runEnrollmentRehearsal(root, input, enrollmentCapability, this.enrollmentRegistry);
10394
+ if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready") {
10395
+ throw new Error(`Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`);
10396
+ }
10397
+ const enrolled = enrollCanaryTask(root, input, this.enrollmentRegistry);
10398
+ const recordRaw = readTaskRecordRaw(root, task_id);
10399
+ return { record_revision: recordRaw.revision };
10400
+ },
10401
+ advanceTask: async (root, taskId) => {
10402
+ const result2 = await this.coordinator.advance(taskId, { cwd: root });
10403
+ if (result2.state === "completed")
10404
+ return { state: "completed" };
10405
+ if (result2.state === "stopped")
10406
+ return { state: "stopped" };
10407
+ if (result2.state === "rework")
10408
+ return { state: "rework", operation: result2.operation, summary: result2.summary };
10409
+ if (result2.state === "review_ready")
10410
+ return { state: "review_ready", operation_id: result2.operation_id };
10411
+ if (result2.state === "blocked")
10412
+ return { state: "blocked", reason: result2.reason };
10413
+ return { state: "failed", reason: result2.reason ?? "advance failed" };
10414
+ },
10415
+ projectTask: async (root, taskId) => projectAssurance(root, taskId, diffSnapshotOf),
10416
+ ownsTaskClaim: (taskId) => {
10417
+ return registry.isChildConsumed(capability, taskId);
10418
+ },
10419
+ validateBatchAuthorization: (input) => {
10420
+ return input.registry.inspect(input.capability, input.binding);
10421
+ }
10422
+ };
10423
+ if (!this.batchKernel)
10424
+ return realPort;
10425
+ return {
10426
+ ...realPort,
10427
+ ...this.batchKernel
10428
+ };
10429
+ }
7307
10430
  }
7308
10431
 
7309
10432
  // plugins/immune-brain/runtime/claude/mcp_server.ts
@@ -7316,8 +10439,10 @@ var TOOLS = [
7316
10439
  { name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
7317
10440
  { name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
7318
10441
  { name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
10442
+ { name: "start_unattended_batch", description: "Start an unattended serial batch run for an Initiative after native confirmation.", privileged: true },
7319
10443
  { name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false },
7320
- { name: "resolve_finding", description: "Resolve one open blocking or advisory finding whose cause is fixed and verified.", privileged: false }
10444
+ { name: "resolve_finding", description: "Resolve one open blocking or advisory finding whose cause is fixed and verified.", privileged: false },
10445
+ { name: "refute_finding", description: "Refute one open finding by binding the fresh passing QA attestation that contradicts it.", privileged: false }
7321
10446
  ];
7322
10447
  function listMcpTools() {
7323
10448
  return TOOLS.map((tool) => ({
@@ -7326,13 +10451,16 @@ function listMcpTools() {
7326
10451
  inputSchema: {
7327
10452
  type: "object",
7328
10453
  properties: {
7329
- task_id: { type: "string" },
7330
- ...tool.name === "approve_breaking_intent_revision" ? { next_intent: { type: "object" } } : {},
7331
- ...tool.name === "stop" ? { reason: { type: "string" } } : {},
7332
- ...tool.name === "submit_review" ? { verdict: { type: "object" } } : {},
7333
- ...tool.name === "resolve_finding" ? { finding_id: { type: "string" } } : {}
10454
+ ...tool.name === "start_unattended_batch" ? { initiative_slug: { type: "string" } } : {
10455
+ task_id: { type: "string" },
10456
+ ...tool.name === "approve_breaking_intent_revision" ? { next_intent: { type: "object" } } : {},
10457
+ ...tool.name === "stop" ? { reason: { type: "string" } } : {},
10458
+ ...tool.name === "submit_review" ? { verdict: { type: "object" } } : {},
10459
+ ...tool.name === "resolve_finding" ? { finding_id: { type: "string" } } : {},
10460
+ ...tool.name === "refute_finding" ? { finding_id: { type: "string" }, attestation_id: { type: "string" } } : {}
10461
+ }
7334
10462
  },
7335
- required: tool.name === "submit_review" ? ["task_id", "verdict"] : tool.name === "resolve_finding" ? ["task_id", "finding_id"] : ["task_id"]
10463
+ required: tool.name === "start_unattended_batch" ? ["initiative_slug"] : tool.name === "submit_review" ? ["task_id", "verdict"] : tool.name === "resolve_finding" ? ["task_id", "finding_id"] : tool.name === "refute_finding" ? ["task_id", "finding_id", "attestation_id"] : ["task_id"]
7336
10464
  },
7337
10465
  annotations: tool.privileged ? privilegedAnnotations() : { readOnlyHint: tool.name === "status" }
7338
10466
  }));
@@ -7348,11 +10476,14 @@ function createMcpRuntime(options = {}) {
7348
10476
  host,
7349
10477
  ports: options.ports,
7350
10478
  interactive: options.interactive,
7351
- requestConfirmation: options.requestConfirmation
10479
+ requestConfirmation: options.requestConfirmation,
10480
+ batchKernel: options.batchKernel,
10481
+ batchGit: options.batchGit,
10482
+ readInitiative: options.readInitiative
7352
10483
  });
7353
10484
  let negotiatedVersion;
7354
10485
  let negotiatedInteractive = false;
7355
- const connectionId = randomUUID6();
10486
+ const connectionId = randomUUID8();
7356
10487
  return {
7357
10488
  runtime,
7358
10489
  host,
@@ -7368,6 +10499,30 @@ function createMcpRuntime(options = {}) {
7368
10499
  },
7369
10500
  sessionInteractive: () => negotiatedInteractive,
7370
10501
  async callTool(name, args, meta = {}) {
10502
+ if (name === "start_unattended_batch") {
10503
+ const initiativeSlug = String(args.initiative_slug ?? "");
10504
+ if (!initiativeSlug)
10505
+ throw new Error("initiative_slug is required");
10506
+ if ("native_decision" in args)
10507
+ throw new Error("native_decision cannot be supplied in tool arguments");
10508
+ if (!negotiatedVersion)
10509
+ throw new NativeAuthorityError("unsupported_host", "Claude Code version is unavailable");
10510
+ if (!negotiatedInteractive) {
10511
+ throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
10512
+ }
10513
+ const probe2 = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
10514
+ if (!probe2.ok)
10515
+ throw new NativeAuthorityError("unsupported_host", probe2.reason);
10516
+ const toolMeta2 = {
10517
+ sessionId: meta.sessionId ?? connectionId,
10518
+ toolCallId: meta.toolCallId ?? `call-${name}`,
10519
+ taskId: initiativeSlug,
10520
+ initiativeSlug,
10521
+ interactive: meta.interactive ?? options.interactive ?? negotiatedInteractive,
10522
+ signal: meta.signal
10523
+ };
10524
+ return runtime.startUnattendedBatch(initiativeSlug, toolMeta2);
10525
+ }
7371
10526
  const taskId = String(args.task_id ?? "");
7372
10527
  if (!taskId)
7373
10528
  throw new Error("task_id is required");
@@ -7404,6 +10559,13 @@ function createMcpRuntime(options = {}) {
7404
10559
  throw new Error("finding_id is required");
7405
10560
  return runtime.resolveFinding(taskId, args.finding_id);
7406
10561
  }
10562
+ if (name === "refute_finding") {
10563
+ if (typeof args.finding_id !== "string" || !args.finding_id)
10564
+ throw new Error("finding_id is required");
10565
+ if (typeof args.attestation_id !== "string" || !args.attestation_id)
10566
+ throw new Error("attestation_id is required");
10567
+ return runtime.refuteFinding(taskId, args.finding_id, args.attestation_id);
10568
+ }
7407
10569
  if (name === "request_authorization" || name === "approve_breaking_intent_revision" || name === "stop" || name === "repair_authority_state") {
7408
10570
  return runtime.authorize(taskId, name, toolMeta, args);
7409
10571
  }
@@ -7487,7 +10649,7 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
7487
10649
  }
7488
10650
  const sessionId = mcp.connectionId;
7489
10651
  const toolCallId = identity?.toolCallId ?? String(message.id ?? "stdio");
7490
- const result = await mcp.callTool(name, params.arguments ?? {}, {
10652
+ const result2 = await mcp.callTool(name, params.arguments ?? {}, {
7491
10653
  sessionId,
7492
10654
  toolCallId,
7493
10655
  interactive,
@@ -7497,7 +10659,7 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
7497
10659
  return {
7498
10660
  jsonrpc: "2.0",
7499
10661
  id: message.id ?? null,
7500
- result: { content: [{ type: "text", text: JSON.stringify(result) }] }
10662
+ result: { content: [{ type: "text", text: JSON.stringify(result2) }] }
7501
10663
  };
7502
10664
  } catch (error) {
7503
10665
  return {
@@ -7515,7 +10677,7 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
7515
10677
  }
7516
10678
  async function writeReply(output, reply) {
7517
10679
  const payload = encodeMessage(reply);
7518
- await new Promise((resolve7, reject) => {
10680
+ await new Promise((resolve8, reject) => {
7519
10681
  if (!output.writable) {
7520
10682
  reject(new Error("output stream is not writable"));
7521
10683
  return;
@@ -7531,7 +10693,7 @@ async function writeReply(output, reply) {
7531
10693
  return;
7532
10694
  settled = true;
7533
10695
  cleanup();
7534
- resolve7();
10696
+ resolve8();
7535
10697
  };
7536
10698
  const onError = (error) => {
7537
10699
  if (settled)
@@ -7561,13 +10723,38 @@ async function writeReply(output, reply) {
7561
10723
  if (ok) {
7562
10724
  settled = true;
7563
10725
  cleanup();
7564
- resolve7();
10726
+ resolve8();
7565
10727
  return;
7566
10728
  }
7567
10729
  output.once("drain", onDrain);
7568
10730
  });
7569
10731
  }
7570
10732
  function elicitationParams(input) {
10733
+ if (input.operation === "start_unattended_batch") {
10734
+ const b = input.batchDetails;
10735
+ const details2 = [
10736
+ `Operation: start_unattended_batch`,
10737
+ `Initiative: ${input.initiativeSlug ?? b?.initiative_slug}`,
10738
+ b?.batch_branch ? `Batch branch: ${b.batch_branch}` : null,
10739
+ input.planDigest ? `Plan digest: ${input.planDigest}` : null,
10740
+ b?.children ? `Ordered children (${b.children.length}):
10741
+ ${b.children.map((c) => ` - ${c.task_id} (${c.slice_id}) [risk: ${c.risk ?? "unknown"}]`).join(`
10742
+ `)}` : null,
10743
+ b?.excluded && b.excluded.length > 0 ? `Excluded children (${b.excluded.length}):
10744
+ ${b.excluded.map((e) => ` - ${e.task_id} (${e.slice_id}): ${e.reason}`).join(`
10745
+ `)}` : null,
10746
+ b?.budget ? `Budget: max_children=${b.budget.max_children}, deadline_at=${b.budget.deadline_at}, qa_failure_limit=${b.budget.qa_failure_limit}` : null,
10747
+ b?.expires_at ? `Expires at: ${b.expires_at}` : null
10748
+ ].filter(Boolean);
10749
+ return {
10750
+ mode: "form",
10751
+ message: `Authorize this unattended batch run for ${input.initiativeSlug}?
10752
+
10753
+ ${details2.join(`
10754
+ `)}`,
10755
+ requestedSchema: { type: "object", properties: {} }
10756
+ };
10757
+ }
7571
10758
  const details = [
7572
10759
  `Operation: ${input.operation}`,
7573
10760
  `Task: ${input.taskId}`,
@@ -7610,8 +10797,8 @@ async function serveStdio(options = {}) {
7610
10797
  throw new NativeAuthorityError("user_cancelled", "Tool call was cancelled");
7611
10798
  const requestId = `immune-brain:elicitation:${mcp.connectionId}:${++requestSequence}`;
7612
10799
  let abortListener;
7613
- const response = new Promise((resolve7, reject) => {
7614
- pending.set(requestId, { resolve: resolve7, reject });
10800
+ const response = new Promise((resolve8, reject) => {
10801
+ pending.set(requestId, { resolve: resolve8, reject });
7615
10802
  abortListener = () => {
7616
10803
  pending.delete(requestId);
7617
10804
  reject(new NativeAuthorityError("user_cancelled", "Tool call was cancelled"));
@@ -7632,13 +10819,13 @@ async function serveStdio(options = {}) {
7632
10819
  }
7633
10820
  throw new NativeAuthorityError("correlation_missing", `MCP elicitation failed: ${reply.error.message}`);
7634
10821
  }
7635
- const result = reply.result;
7636
- const action = typeof result === "object" && result !== null && !Array.isArray(result) ? result.action : undefined;
10822
+ const result2 = reply.result;
10823
+ const action = typeof result2 === "object" && result2 !== null && !Array.isArray(result2) ? result2.action : undefined;
7637
10824
  if (action !== "accept" && action !== "decline" && action !== "cancel") {
7638
10825
  throw new NativeAuthorityError("correlation_missing", "MCP elicitation returned an invalid action");
7639
10826
  }
7640
10827
  if (action === "accept") {
7641
- const content = result.content;
10828
+ const content = result2.content;
7642
10829
  if (typeof content !== "object" || content === null || Array.isArray(content) || Object.keys(content).length !== 0) {
7643
10830
  throw new NativeAuthorityError("correlation_missing", "MCP elicitation accept content did not match the requested schema");
7644
10831
  }
@@ -7756,7 +10943,7 @@ async function serveStdio(options = {}) {
7756
10943
  rejectPending(new NativeAuthorityError("user_cancelled", "MCP connection closed during native interaction"));
7757
10944
  await Promise.race([
7758
10945
  Promise.allSettled([...inFlight]),
7759
- new Promise((resolve7) => setTimeout(resolve7, 200))
10946
+ new Promise((resolve8) => setTimeout(resolve8, 200))
7760
10947
  ]);
7761
10948
  await mcp.shutdown();
7762
10949
  if (output.writable && typeof output.end === "function") {
@@ -7768,8 +10955,8 @@ async function serveStdio(options = {}) {
7768
10955
  })();
7769
10956
  return shutdownPromise;
7770
10957
  };
7771
- await new Promise((resolve7) => {
7772
- resolveStdio = resolve7;
10958
+ await new Promise((resolve8) => {
10959
+ resolveStdio = resolve8;
7773
10960
  output.on("error", () => {
7774
10961
  executeShutdown(1);
7775
10962
  });