immune-brain 5.1.0 → 5.1.1

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 (34) hide show
  1. package/package.json +1 -1
  2. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  3. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +17 -5
  4. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +165 -0
  5. package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +66 -3
  6. package/plugins/immune-brain/agents/immune-brain-reviewer.md +11 -1
  7. package/plugins/immune-brain/dist/claude/mcp-server.mjs +542 -79
  8. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +8 -6
  9. package/plugins/immune-brain/dist/imm-planner.md +4 -1
  10. package/plugins/immune-brain/dist/role-prompts/code-review.md +10 -0
  11. package/plugins/immune-brain/runtime/assurance/coordinator.ts +22 -4
  12. package/plugins/immune-brain/runtime/assurance/review_mediation.ts +107 -0
  13. package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +3 -0
  14. package/plugins/immune-brain/runtime/claude/interaction.ts +10 -0
  15. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +89 -54
  16. package/plugins/immune-brain/runtime/claude/mcp_server.ts +76 -25
  17. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
  18. package/plugins/immune-brain/runtime/kernel/canary_application.ts +9 -2
  19. package/plugins/immune-brain/runtime/kernel/completion.ts +24 -1
  20. package/plugins/immune-brain/runtime/kernel/reducer.ts +15 -2
  21. package/plugins/immune-brain/runtime/kernel/spec_binding.ts +6 -0
  22. package/plugins/immune-brain/runtime/kernel/storage.ts +20 -4
  23. package/plugins/immune-brain/runtime/kernel/storage_paths.ts +33 -3
  24. package/plugins/immune-brain/runtime/kernel/types.ts +7 -0
  25. package/plugins/immune-brain/runtime/kernel/validation.ts +13 -2
  26. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  27. package/plugins/immune-brain/runtime/prompts/code-review.md +10 -0
  28. package/plugins/immune-brain/runtime/role_prompt_bridge.ts +1 -1
  29. package/plugins/immune-brain/runtime/unattended/batch_disposition.ts +222 -0
  30. package/plugins/immune-brain/runtime/unattended/batch_lanes.ts +51 -14
  31. package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +8 -12
  32. package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +13 -1
  33. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +2 -0
  34. package/plugins/immune-brain/runtime/unattended/batch_state.ts +44 -6
@@ -39,7 +39,7 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
39
39
  }
40
40
 
41
41
  // plugins/immune-brain/runtime/plugin_version.ts
42
- var PLUGIN_VERSION = "5.1.0";
42
+ var PLUGIN_VERSION = "5.1.1";
43
43
 
44
44
  // plugins/immune-brain/runtime/claude/interaction.ts
45
45
  import { createHash, randomUUID } from "node:crypto";
@@ -48,7 +48,8 @@ var PRIVILEGED_OPERATIONS = [
48
48
  "request_authorization",
49
49
  "approve_breaking_intent_revision",
50
50
  "stop",
51
- "start_unattended_batch"
51
+ "start_unattended_batch",
52
+ "retire_stale_batch"
52
53
  ];
53
54
  var RECOVERY_ACTIONS = {
54
55
  interaction_not_opened: "retry through a fresh native gate in the current Host",
@@ -732,6 +733,7 @@ var KERNEL_DB_RELATIVE = ".imm/state/kernel.sqlite";
732
733
  var KERNEL_STORE_SCHEMA_VERSION = 1;
733
734
  var FILE_STORE_CLAIM_RELATIVE = ".imm/state/active-claim.json";
734
735
  var FILE_STORE_WORKSPACE_RELATIVE = ".imm/state/workspace.json";
736
+ var FILE_STORE_TASKS_RELATIVE = ".imm/state/tasks";
735
737
  var FILE_STORE_TRANSACTIONS_RELATIVE = ".imm/state/transactions";
736
738
  var BATCH_STATE_RELATIVE = ".imm/state/batches";
737
739
  function stateDatabasePath() {
@@ -790,6 +792,25 @@ function batchCommitEvidencePath(batchId, taskId) {
790
792
  validateTaskId(taskId);
791
793
  return `${BATCH_STATE_RELATIVE}/commits/${batchId}-${taskId}.json`;
792
794
  }
795
+ function entryStatus(root, relativePath) {
796
+ const candidate = resolve(root, relativePath);
797
+ let stat;
798
+ try {
799
+ stat = lstatSync2(candidate);
800
+ } catch (error) {
801
+ const code = error.code;
802
+ if (code === "ENOENT" || code === "ENOTDIR")
803
+ return "absent";
804
+ return "other";
805
+ }
806
+ if (stat.isSymbolicLink())
807
+ return "symlink";
808
+ if (stat.isFile())
809
+ return "file";
810
+ if (stat.isDirectory())
811
+ return "directory";
812
+ return "other";
813
+ }
793
814
  function listEntries(root, relativePath) {
794
815
  const candidate = resolve(root, relativePath);
795
816
  try {
@@ -798,6 +819,44 @@ function listEntries(root, relativePath) {
798
819
  return null;
799
820
  }
800
821
  }
822
+ function readSmallFile(root, relativePath) {
823
+ const candidate = resolve(root, relativePath);
824
+ try {
825
+ const fd = openSync2(candidate, FS_CONSTANTS.O_RDONLY | (FS_CONSTANTS.O_NOFOLLOW ?? 0));
826
+ try {
827
+ const stat = fstatSync2(fd);
828
+ if (stat.size > 4 * 1024 * 1024)
829
+ throw new Error("file exceeds the inspection read bound");
830
+ return readFileSync(fd, "utf8");
831
+ } finally {
832
+ closeSync2(fd);
833
+ }
834
+ } catch {
835
+ return null;
836
+ }
837
+ }
838
+ function inspectRetiredStateResidue(root) {
839
+ const tasksStatus = entryStatus(root, FILE_STORE_TASKS_RELATIVE);
840
+ if (tasksStatus !== "absent" && tasksStatus !== "directory")
841
+ throw new Error(`${FILE_STORE_TASKS_RELATIVE} is not a regular directory`);
842
+ const entries = tasksStatus === "absent" ? [] : listEntries(root, FILE_STORE_TASKS_RELATIVE);
843
+ if (entries === null)
844
+ throw new Error(`${FILE_STORE_TASKS_RELATIVE} is unreadable`);
845
+ const workspaceStatus = entryStatus(root, FILE_STORE_WORKSPACE_RELATIVE);
846
+ let owner = null;
847
+ if (workspaceStatus !== "absent") {
848
+ if (workspaceStatus !== "file")
849
+ throw new Error(`${FILE_STORE_WORKSPACE_RELATIVE} is not a regular file`);
850
+ const content = readSmallFile(root, FILE_STORE_WORKSPACE_RELATIVE);
851
+ if (content === null)
852
+ throw new Error(`${FILE_STORE_WORKSPACE_RELATIVE} is unreadable`);
853
+ const raw = JSON.parse(content);
854
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw) || raw.contract !== "assurance_kernel/workspace/v1" || raw.current_working !== null && (typeof raw.current_working !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(raw.current_working)) || raw.current_working === null && Object.keys(raw).some((key) => !["contract", "current_working"].includes(key)))
855
+ throw new Error(`${FILE_STORE_WORKSPACE_RELATIVE} has invalid workspace content`);
856
+ owner = raw.current_working;
857
+ }
858
+ return { task_entries: entries, workspace_owner: owner };
859
+ }
801
860
 
802
861
  // plugins/immune-brain/runtime/kernel/sqlite_store.ts
803
862
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -2144,6 +2203,7 @@ function parseApprovalV2(value, index, violations, allowReviewRevision = false)
2144
2203
  "actor_id",
2145
2204
  "summary",
2146
2205
  "advisory_findings",
2206
+ "reviewer_verdict_sha256",
2147
2207
  ...allowReviewRevision ? ["review_revision"] : []
2148
2208
  ], `record.approvals[${index}]`, violations);
2149
2209
  const intentContentHash = stringAt(item.intent_content_hash, `record.approvals[${index}].intent_content_hash`, violations);
@@ -2164,7 +2224,8 @@ function parseApprovalV2(value, index, violations, allowReviewRevision = false)
2164
2224
  ...allowReviewRevision && item.review_revision !== undefined ? { review_revision: parseReviewRevisionIdentity(item.review_revision, `record.approvals[${index}].review_revision`, violations) } : {},
2165
2225
  ...item.advisory_findings !== undefined ? {
2166
2226
  advisory_findings: arrayAt(item.advisory_findings, `record.approvals[${index}].advisory_findings`, violations).map((entry, advisoryIndex) => parseAdvisoryFinding(entry, `record.approvals[${index}].advisory_findings[${advisoryIndex}]`, violations))
2167
- } : {}
2227
+ } : {},
2228
+ ...item.reviewer_verdict_sha256 !== undefined ? { reviewer_verdict_sha256: stringAt(item.reviewer_verdict_sha256, `record.approvals[${index}].reviewer_verdict_sha256`, violations) } : {}
2168
2229
  };
2169
2230
  }
2170
2231
  function parseAdvisoryFinding(value, path, violations) {
@@ -2211,6 +2272,7 @@ function parseAttestationV3(value, index, acceptanceIds, violations, allowReview
2211
2272
  "summary",
2212
2273
  "acceptance_results",
2213
2274
  "advisory_findings",
2275
+ "reviewer_verdict_sha256",
2214
2276
  ...allowReviewRevision ? ["review_revision"] : []
2215
2277
  ], path, violations);
2216
2278
  const kind = enumAt(item.kind, APPROVAL_KINDS, `${path}.kind`, violations);
@@ -2248,6 +2310,11 @@ function parseAttestationV3(value, index, acceptanceIds, violations, allowReview
2248
2310
  const advisoryFindings = item.advisory_findings === undefined ? undefined : arrayAt(item.advisory_findings, `${path}.advisory_findings`, violations).map((entry, advisoryIndex) => parseAdvisoryFinding(entry, `${path}.advisory_findings[${advisoryIndex}]`, violations));
2249
2311
  if (advisoryFindings !== undefined && kind !== "review")
2250
2312
  violations.push(`${path}.advisory_findings is only valid on review attestations`);
2313
+ const reviewerVerdictSha256 = item.reviewer_verdict_sha256 === undefined ? undefined : stringAt(item.reviewer_verdict_sha256, `${path}.reviewer_verdict_sha256`, violations);
2314
+ if (reviewerVerdictSha256 !== undefined && kind !== "review")
2315
+ violations.push(`${path}.reviewer_verdict_sha256 is only valid on review attestations`);
2316
+ if (reviewerVerdictSha256 !== undefined && !SHA256_HEX.test(reviewerVerdictSha256))
2317
+ violations.push(`${path}.reviewer_verdict_sha256 must be sha256:<64 hex>`);
2251
2318
  return {
2252
2319
  id: stringAt(item.id, `${path}.id`, violations),
2253
2320
  kind,
@@ -2259,7 +2326,8 @@ function parseAttestationV3(value, index, acceptanceIds, violations, allowReview
2259
2326
  summary: stringAt(item.summary, `${path}.summary`, violations),
2260
2327
  acceptance_results: acceptanceResults,
2261
2328
  ...reviewRevision ? { review_revision: reviewRevision } : {},
2262
- ...advisoryFindings !== undefined ? { advisory_findings: advisoryFindings } : {}
2329
+ ...advisoryFindings !== undefined ? { advisory_findings: advisoryFindings } : {},
2330
+ ...reviewerVerdictSha256 !== undefined ? { reviewer_verdict_sha256: reviewerVerdictSha256 } : {}
2263
2331
  };
2264
2332
  }
2265
2333
  function parseTaskRecordAtVersion(raw, version) {
@@ -2643,6 +2711,11 @@ function readBoundActiveSpec(root, intent, required = false) {
2643
2711
  throw error;
2644
2712
  }
2645
2713
  }
2714
+ function assertValidSpecBinding(intent) {
2715
+ const binding = inspectSpecBinding(intent);
2716
+ if (!binding.ok)
2717
+ throw new KernelInvariantError([binding.message]);
2718
+ }
2646
2719
  function inspectSpecBinding(intent) {
2647
2720
  const active = intent.scope_hint.filter((path) => ACTIVE_SPEC_RE.test(path));
2648
2721
  const archived = intent.scope_hint.filter((path) => ARCHIVED_SPEC_RE.test(path));
@@ -2696,6 +2769,8 @@ function asTaskDiffSnapshot(value) {
2696
2769
  return { diff_hash: value.diff_hash, changed_paths: value.changed_paths };
2697
2770
  }
2698
2771
  function resolveProjectedRisk(intent, changedPaths = []) {
2772
+ if (!inspectSpecBinding(intent).ok)
2773
+ return classifyTaskRisk(changedPaths, intent.risk);
2699
2774
  const excluded = ownSidecarPaths(intent);
2700
2775
  return classifyTaskRisk(changedPaths.filter((path) => !excluded.has(path)), intent.risk);
2701
2776
  }
@@ -2754,6 +2829,27 @@ function completionDecision(intent, record, currentDiffHash, currentIntentConten
2754
2829
  };
2755
2830
  }
2756
2831
  function projectTask(intent, record, currentDiffHash, currentIntentContentHash, changedPaths = []) {
2832
+ if (record.lifecycle === "active" && !inspectSpecBinding(intent).ok) {
2833
+ assertKernelInvariantsV3(intent, record);
2834
+ return {
2835
+ contract: "assurance_kernel/projection/v3",
2836
+ task_id: record.task_id,
2837
+ intent_revision: intent.revision,
2838
+ lifecycle: record.lifecycle,
2839
+ artifact_state: record.artifact_state,
2840
+ blocked: true,
2841
+ next_obligation: "revise_intent",
2842
+ complete: false,
2843
+ fresh_acceptance_ids: [],
2844
+ missing_acceptance_ids: intent.acceptance.map((item) => item.id),
2845
+ stale_attestation_ids: record.attestations.map((item) => item.id),
2846
+ missing_approval_kinds: REQUIRED_ATTESTATIONS[resolveProjectedRisk(intent, changedPaths)],
2847
+ blocking_finding_ids: record.findings.filter((item) => item.kind === "blocking" && item.status !== "resolved").map((item) => item.id),
2848
+ unresolved_user_decision_ids: record.findings.filter((item) => item.kind === "unresolved_user_decision" && item.status === "open").map((item) => item.id),
2849
+ replan_required_ids: record.findings.filter((item) => item.kind === "replan_required" && item.status === "open").map((item) => item.id),
2850
+ independence_violations: []
2851
+ };
2852
+ }
2757
2853
  const decision = completionDecision(intent, record, currentDiffHash, currentIntentContentHash, changedPaths);
2758
2854
  const blocked = decision.blocking_finding_ids.length > 0 || decision.unresolved_user_decision_ids.length > 0 || decision.replan_required_ids.length > 0 || decision.independence_violations.length > 0;
2759
2855
  let nextObligation = "none";
@@ -3044,27 +3140,37 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
3044
3140
  const reviewRevision = approval.review_revision;
3045
3141
  if (reviewRevision && approval.kind !== "review")
3046
3142
  throw new KernelInvariantError(["review_revision is only valid on review approvals"]);
3143
+ if (approval.reviewer_verdict_sha256 && approval.kind !== "review")
3144
+ throw new KernelInvariantError(["reviewer_verdict_sha256 is only valid on review approvals"]);
3047
3145
  if (approval.kind === "review") {
3048
3146
  if (!reviewRevision)
3049
3147
  throw new KernelInvariantError(["v4 review approval requires review_revision"]);
3050
3148
  if (reviewRevision.base_head !== record.git_base_head)
3051
3149
  throw new KernelInvariantError(["review_revision.base_head must equal the Enrollment git_base_head"]);
3052
3150
  }
3053
- const { review_revision: storedReviewRevision, ...approvalWithoutRevision } = approval;
3151
+ const {
3152
+ review_revision: storedReviewRevision,
3153
+ advisory_findings: storedAdvisoryFindings,
3154
+ reviewer_verdict_sha256: storedReviewerDigest,
3155
+ ...approvalWithoutExtras
3156
+ } = approval;
3054
3157
  record.attestations.push({
3055
- ...approvalWithoutRevision,
3158
+ ...approvalWithoutExtras,
3056
3159
  acceptance_results: approval.kind === "qa" ? record.intent_snapshot.acceptance.map((item) => ({
3057
3160
  acceptance_id: item.id,
3058
3161
  status: "passed",
3059
3162
  summary: `host-attested QA: ${approval.summary}`
3060
3163
  })) : [],
3061
- ...storedReviewRevision ? { review_revision: storedReviewRevision } : {}
3164
+ ...storedReviewRevision ? { review_revision: storedReviewRevision } : {},
3165
+ ...storedAdvisoryFindings ? { advisory_findings: storedAdvisoryFindings } : {},
3166
+ ...storedReviewerDigest ? { reviewer_verdict_sha256: storedReviewerDigest } : {}
3062
3167
  });
3063
3168
  appendHistory(record, action, from, approval.id, authorityAudit);
3064
3169
  break;
3065
3170
  }
3066
3171
  case "revise_intent":
3067
3172
  case "approve_breaking_intent_revision": {
3173
+ assertValidSpecBinding(action.next_intent);
3068
3174
  if (record.lifecycle !== "active")
3069
3175
  throw new KernelInvariantError([
3070
3176
  `cannot revise intent while lifecycle is ${record.lifecycle}`
@@ -3544,10 +3650,16 @@ function isRetiredFileProvablySuperseded(path, db, taskId) {
3544
3650
  }
3545
3651
  function assertNoRetiredFileStore(root, db, taskId) {
3546
3652
  const canonical = canonicalRoot2(root);
3653
+ let residue;
3654
+ try {
3655
+ residue = inspectRetiredStateResidue(canonical);
3656
+ } catch (error) {
3657
+ throw new KernelStoreSecurityError(`retired file-store residue is invalid: ${error instanceof Error ? error.message : String(error)}`);
3658
+ }
3547
3659
  const retired = [
3548
3660
  [".imm/tasks", "pre-cutover task store"],
3549
3661
  [".imm/workspace.json", "pre-cutover workspace owner"],
3550
- [".imm/state/tasks", "task records"]
3662
+ ...residue.task_entries.length > 0 ? [[".imm/state/tasks", "task records"]] : []
3551
3663
  ];
3552
3664
  for (const [path, label] of retired) {
3553
3665
  if (existsSync2(resolve5(canonical, path)))
@@ -3559,6 +3671,8 @@ function assertNoRetiredFileStore(root, db, taskId) {
3559
3671
  ];
3560
3672
  for (const [path, label] of derived) {
3561
3673
  const full = resolve5(canonical, path);
3674
+ if (path === FILE_STORE_WORKSPACE_RELATIVE && residue.workspace_owner === null)
3675
+ continue;
3562
3676
  if (!existsSync2(full))
3563
3677
  continue;
3564
3678
  if (db === undefined || typeof taskId !== "string" || taskId.length === 0)
@@ -3575,10 +3689,16 @@ function assertNoRetiredFileStore(root, db, taskId) {
3575
3689
  }
3576
3690
  function retiredFileStoreConflict(root, db, taskId) {
3577
3691
  const canonical = canonicalRoot2(root);
3692
+ let residue;
3693
+ try {
3694
+ residue = inspectRetiredStateResidue(canonical);
3695
+ } catch (error) {
3696
+ return `retired file-store residue is invalid: ${error instanceof Error ? error.message : String(error)}`;
3697
+ }
3578
3698
  const authority = [
3579
3699
  [".imm/tasks", "pre-cutover task store"],
3580
3700
  [".imm/workspace.json", "pre-cutover workspace owner"],
3581
- [".imm/state/tasks", "task records"]
3701
+ ...residue.task_entries.length > 0 ? [[".imm/state/tasks", "task records"]] : []
3582
3702
  ];
3583
3703
  for (const [path, label] of authority)
3584
3704
  if (existsSync2(resolve5(canonical, path)))
@@ -3590,7 +3710,7 @@ function retiredFileStoreConflict(root, db, taskId) {
3590
3710
  [FILE_STORE_WORKSPACE_RELATIVE, "workspace owner"]
3591
3711
  ];
3592
3712
  for (const [path, label] of derived)
3593
- if (existsSync2(resolve5(canonical, path)))
3713
+ if ((path !== FILE_STORE_WORKSPACE_RELATIVE || residue.workspace_owner !== null) && existsSync2(resolve5(canonical, path)))
3594
3714
  return `retired file-store authority is present (${label}: ${path}); import it with the supported migration before mutating this worktree`;
3595
3715
  }
3596
3716
  const transactions = resolve5(canonical, FILE_STORE_TRANSACTIONS_RELATIVE);
@@ -5222,7 +5342,7 @@ function projectFromRecord(record, recordRevision, workspaceRevision, snapshot)
5222
5342
  independence_violations: decision.independence_violations,
5223
5343
  open_user_decision_count: openUserDecisionCount,
5224
5344
  completion_ready: decision.complete,
5225
- authorization: deriveAssuranceAuthorization({
5345
+ authorization: !inspectSpecBinding(intent).ok ? { state: "none", blocked: "invalid Spec binding requires a valid breaking intent revision" } : deriveAssuranceAuthorization({
5226
5346
  next_obligation: decision.next_obligation,
5227
5347
  open_user_decision_count: openUserDecisionCount
5228
5348
  })
@@ -5555,6 +5675,7 @@ function createCanaryApplication(registry) {
5555
5675
  const current = readTaskRecordRaw(input.root, input.task_id);
5556
5676
  if (!current.record)
5557
5677
  throw new KernelInvariantError([`task ${input.task_id} has no TaskRecord v3`]);
5678
+ assertValidSpecBinding(current.record.intent_snapshot);
5558
5679
  const workspace = readWorkspaceStateRaw(input.root);
5559
5680
  if (current.record.artifact_state === "frozen")
5560
5681
  return { revision: current.revision, record: current.record, workspace };
@@ -5636,7 +5757,11 @@ function createCanaryApplication(registry) {
5636
5757
  const diffHash = asTaskDiffSnapshot((input.diffProvider ?? taskDeliveryIdentity)(input.root, snapshot.record)).diff_hash;
5637
5758
  if (operation.op === "stop" && !("capability" in operation))
5638
5759
  throw new KernelInvariantError(["stop requires user authority capability"]);
5639
- const hasBoundSpec = boundSpecPath(snapshot.intent_snapshot) !== undefined;
5760
+ if (operation.op === "approve_breaking_intent_revision")
5761
+ assertValidSpecBinding(operation.next_intent);
5762
+ else
5763
+ assertValidSpecBinding(snapshot.intent_snapshot);
5764
+ const hasBoundSpec = operation.op === "approve_breaking_intent_revision" ? boundSpecPath(operation.next_intent) !== undefined : boundSpecPath(snapshot.intent_snapshot) !== undefined;
5640
5765
  if (operation.op === "complete" && hasBoundSpec && snapshot.record.artifact_state !== "frozen")
5641
5766
  throw new KernelInvariantError(["complete requires frozen planning artifacts"]);
5642
5767
  const artifactTransition = snapshot.record.artifact_state === "frozen" && (operation.op === "request_rework" || operation.op === "authorize_rework" || operation.op === "approve_breaking_intent_revision") ? transitionFor(input.root, snapshot.record, "restore") : operation.op === "stop" && snapshot.record.artifact_state !== "frozen" ? transitionFor(input.root, snapshot.record, "freeze", true) : undefined;
@@ -7169,8 +7294,9 @@ function parseVerdictEvidence(value, index) {
7169
7294
  function parseAssuranceVerdict(input, snapshot) {
7170
7295
  let raw;
7171
7296
  if (typeof input === "string") {
7172
- const cleaned = input.split(`
7173
- `).map((line) => line.trim()).filter((line) => line.startsWith("{") && line.endsWith("}")).join("");
7297
+ const first = input.indexOf("{");
7298
+ const last = input.lastIndexOf("}");
7299
+ const cleaned = first >= 0 && last >= first ? input.slice(first, last + 1) : "";
7174
7300
  if (!cleaned)
7175
7301
  throw new Error("reviewer returned no strict JSON verdict");
7176
7302
  try {
@@ -7798,10 +7924,10 @@ class AssuranceCoordinator {
7798
7924
  signal?.removeEventListener("abort", relayExternalAbort);
7799
7925
  }
7800
7926
  }
7801
- async submitReview(taskId, ctx, verdictInput) {
7802
- return this.withRecovery(taskId, ctx, await this.submitReviewOnce(taskId, ctx, verdictInput));
7927
+ async submitReview(taskId, ctx, verdictInput, options) {
7928
+ return this.withRecovery(taskId, ctx, await this.submitReviewOnce(taskId, ctx, verdictInput, options));
7803
7929
  }
7804
- async submitReviewOnce(taskId, ctx, verdictInput) {
7930
+ async submitReviewOnce(taskId, ctx, verdictInput, options) {
7805
7931
  const unknown = this.unknownOperations.get(taskId);
7806
7932
  if (unknown)
7807
7933
  return { state: "settlement_unknown", operation: unknown.operation, operation_id: unknown.operationId, reason: unknown.reason };
@@ -7842,6 +7968,9 @@ class AssuranceCoordinator {
7842
7968
  reservation.verdictCorrectionRequired = true;
7843
7969
  return { state: "blocked", code: "verdict_invalid", reason: boundedAssuranceError(error) };
7844
7970
  }
7971
+ const receiptError = options?.validateReceipt?.();
7972
+ if (receiptError)
7973
+ return { state: "blocked", reason: receiptError, ...options?.receiptRecoveryAction ? { recovery_action: options.receiptRecoveryAction } : {} };
7845
7974
  const invocation = this.openInvocation(taskId);
7846
7975
  this.releaseReviewReservation(taskId, reservation);
7847
7976
  try {
@@ -7850,7 +7979,8 @@ class AssuranceCoordinator {
7850
7979
  snapshot: reservation.snapshot,
7851
7980
  verdict,
7852
7981
  invocation,
7853
- actorId: "parent-mediated-review"
7982
+ actorId: "parent-mediated-review",
7983
+ ...options?.reviewer_verdict_sha256 ? { reviewer_verdict_sha256: options.reviewer_verdict_sha256 } : {}
7854
7984
  });
7855
7985
  } catch (error) {
7856
7986
  const reason = boundedAssuranceError(error);
@@ -8143,7 +8273,8 @@ function createVerdictAuthority(options, registry = createMutationAuthorityRegis
8143
8273
  actor_id: actorId,
8144
8274
  summary: verdict.approval.summary,
8145
8275
  ...snapshot.role === "review" && snapshot.review_revision ? { review_revision: snapshot.review_revision } : {},
8146
- ...snapshot.role === "review" && advisories.length ? { advisory_findings: advisories } : {}
8276
+ ...snapshot.role === "review" && advisories.length ? { advisory_findings: advisories } : {},
8277
+ ...snapshot.role === "review" && input.reviewer_verdict_sha256 ? { reviewer_verdict_sha256: input.reviewer_verdict_sha256 } : {}
8147
8278
  } : undefined;
8148
8279
  const op = verdict.decision === "rework" ? "request_rework" : "record_approval";
8149
8280
  const payload = approval ? { approval } : { findings };
@@ -9489,6 +9620,16 @@ var BATCH_REASONS = Object.freeze({
9489
9620
  state: "rejected",
9490
9621
  reason: (detail) => detail || "batch run rejected",
9491
9622
  recovery_action: "delete or rename the conflicting branch, or commit working changes and retry in the current Host"
9623
+ },
9624
+ stale_batch_absent: {
9625
+ state: "rejected",
9626
+ reason: (detail) => `no retirable batch record to dispose: ${detail}`,
9627
+ recovery_action: "start a batch for this Initiative, or inspect the batch records under .imm/state/batches in the current Host"
9628
+ },
9629
+ stale_batch_in_flight: {
9630
+ state: "blocked",
9631
+ reason: (detail) => `a batch child is still mid-flight, so the record cannot be disposed: ${detail}`,
9632
+ recovery_action: "settle or stop the in-flight child through its own task, then dispose the batch in the current Host"
9492
9633
  }
9493
9634
  });
9494
9635
  function batchReason(key, detail = "") {
@@ -10736,7 +10877,8 @@ var BATCH_RUN_STATES = new Set([
10736
10877
  "completed",
10737
10878
  "budget_stopped",
10738
10879
  "failed",
10739
- "rejected"
10880
+ "rejected",
10881
+ "superseded"
10740
10882
  ]);
10741
10883
  var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
10742
10884
  function isCanonicalTimestamp(value) {
@@ -10817,7 +10959,7 @@ function validateRecordShape(value, batchId) {
10817
10959
  if (child.state === "committed" && child.commit !== null && !record.commits.includes(child.commit))
10818
10960
  throw new Error(`batch run state ${batchId} child ${child.task_id} commit is missing from commits`);
10819
10961
  }
10820
- if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected") && record.children.some((child) => child.state === "enrolled" || child.state === "settled"))
10962
+ if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected" || record.batch_state === "superseded") && record.children.some((child) => child.state === "enrolled" || child.state === "settled"))
10821
10963
  throw new Error(`batch run state ${batchId} is ${String(record.batch_state)} but a child is still mid-flight`);
10822
10964
  if (record.batch_state === "completed" && record.children.some((child) => child.state !== "committed"))
10823
10965
  throw new Error(`batch run state ${batchId} is completed but a child is not committed`);
@@ -10911,6 +11053,21 @@ function replaceBatchRunState(root, expected, next, validate) {
10911
11053
  return stored;
10912
11054
  });
10913
11055
  }
11056
+ function replaceAnyBatchRunState(root, expected, next, validate) {
11057
+ const path = statePath(next.batch_id);
11058
+ if (isLaneBatchRecord(next))
11059
+ validateLaneRecordShape(next, next.batch_id);
11060
+ else
11061
+ validateRecordShape(next, next.batch_id);
11062
+ return withKernelStoreLock(root, () => {
11063
+ if (!readSecureProjectBytes(root, path).equals(expected))
11064
+ throw new Error("batch state CAS mismatch");
11065
+ validate();
11066
+ const stored = withoutRetiredClock({ ...next, updated_at: new Date().toISOString() });
11067
+ writeFileAtomically(root, path, canonicalBytes(stored));
11068
+ return stored;
11069
+ });
11070
+ }
10914
11071
  function writeBatchRunState(root, record) {
10915
11072
  const path = statePath(record.batch_id);
10916
11073
  validateRecordShape(record, record.batch_id);
@@ -10991,7 +11148,7 @@ function validateLaneRecordShape(value, batchId) {
10991
11148
  throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid qa_failures`);
10992
11149
  const lane = child.lane;
10993
11150
  if (lane !== null) {
10994
- if (typeof lane !== "object" || typeof lane.path !== "string" || !lane.path || typeof lane.branch !== "string" || !lane.branch || typeof lane.base_head !== "string" || !lane.base_head || lane.lane_commit !== null && typeof lane.lane_commit !== "string")
11151
+ if (typeof lane !== "object" || typeof lane.path !== "string" || !lane.path || typeof lane.branch !== "string" || !lane.branch || typeof lane.base_head !== "string" || !lane.base_head || lane.lane_commit !== null && typeof lane.lane_commit !== "string" || lane.run_id !== undefined && lane.run_id !== null && (typeof lane.run_id !== "string" || !lane.run_id))
10995
11152
  throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid lane`);
10996
11153
  }
10997
11154
  if (LANE_BOUND_STATES.has(child.state) && lane === null)
@@ -11018,7 +11175,7 @@ function validateLaneRecordShape(value, batchId) {
11018
11175
  throw new Error(`batch run state ${batchId} child ${child.task_id} commit is missing from commits`);
11019
11176
  }
11020
11177
  const inFlight = (child) => child.state === "lane_admitted" || child.state === "enrolled" || child.state === "settled" || child.state === "lane_committed";
11021
- if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected") && record.children.some(inFlight))
11178
+ if ((record.batch_state === "budget_stopped" || record.batch_state === "failed" || record.batch_state === "rejected" || record.batch_state === "superseded") && record.children.some(inFlight))
11022
11179
  throw new Error(`batch run state ${batchId} is ${String(record.batch_state)} but a child is still mid-flight`);
11023
11180
  if (record.batch_state === "completed" && record.children.some((child) => child.state !== "integrated" && child.state !== "released"))
11024
11181
  throw new Error(`batch run state ${batchId} is completed but a child is not integrated`);
@@ -11111,7 +11268,7 @@ function canonicalReportBytes(report) {
11111
11268
  `;
11112
11269
  }
11113
11270
  function isTerminalBatchState(state) {
11114
- return state === "completed" || state === "budget_stopped" || state === "failed" || state === "rejected";
11271
+ return state === "completed" || state === "budget_stopped" || state === "failed" || state === "rejected" || state === "superseded";
11115
11272
  }
11116
11273
 
11117
11274
  // plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts
@@ -11784,15 +11941,7 @@ function findExistingActiveBatch(root, initiativeSlug) {
11784
11941
  continue;
11785
11942
  if (candidate.initiative_slug !== initiativeSlug)
11786
11943
  continue;
11787
- const validStates = new Set([
11788
- "prepared",
11789
- "running",
11790
- "needs_human",
11791
- "completed",
11792
- "budget_stopped",
11793
- "failed",
11794
- "rejected"
11795
- ]);
11944
+ const validStates = new Set(BATCH_RUN_STATES);
11796
11945
  if (typeof candidate.batch_id !== "string" || typeof candidate.base_head !== "string" || !Array.isArray(candidate.children) || !validStates.has(candidate.batch_state)) {
11797
11946
  return { corrupt: true, path: file };
11798
11947
  }
@@ -12149,7 +12298,7 @@ async function projectBatchPreflight(options) {
12149
12298
  try {
12150
12299
  reconfirmation = await captureBatchReconfirmation(root, activeRecord, planSurface.surface.recovery_children);
12151
12300
  } catch {
12152
- return reject("plan_projection_failed", "batch plan reconfirmation is not eligible");
12301
+ return reject("plan_projection_failed", activeRecord.commits.length ? `batch plan changed after ${activeRecord.commits.length} recorded child commit(s). The old authorization cannot execute the revised plan. Preserve the batch record, report, commit evidence and audit; inspect each remaining child's Kernel run before an explicit imm-run handoff. Do not re-enroll settled children or manufacture batch trailers. To close the parked record explicitly, dispose it with the retire_stale_batch Tool for this Initiative; a child that is still mid-flight must settle first. This rejection neither terminates the old batch nor authorizes the revised plan.` : "batch plan reconfirmation is not eligible");
12153
12302
  }
12154
12303
  }
12155
12304
  const projection = {
@@ -13257,14 +13406,23 @@ function serialGitOf(input) {
13257
13406
  return input.git ?? defaultSerialGit();
13258
13407
  }
13259
13408
  function releaseHandoffs(input, record, lanes) {
13409
+ const lineage = classifyBatchLineage({
13410
+ root: input.root,
13411
+ branch: record.branch ?? "",
13412
+ expectedHead: expectedBatchHead(record),
13413
+ childCommits: record.commits,
13414
+ batchId: record.batch_id
13415
+ });
13416
+ if (lineage.kind === "broken")
13417
+ return [];
13260
13418
  const handoffs = [];
13261
13419
  for (const child of record.children) {
13262
13420
  if (child.state !== "integrated" || !child.lane)
13263
13421
  continue;
13264
13422
  const facts = lanes.inspectLane(input.root, child.lane.path);
13265
- if (!facts.exists || !facts.same_repository || !facts.clean || facts.branch !== child.lane.branch)
13423
+ if (!facts.exists || !facts.same_repository || !facts.is_worktree_root || !facts.clean || facts.branch !== child.lane.branch || facts.active_claim_task_id !== null)
13266
13424
  continue;
13267
- if (!lanes.auditReachable({ root: input.root, head: expectedBatchHead(record), task_id: child.task_id }))
13425
+ if (!lanes.auditReachable({ root: input.root, head: lineage.head, task_id: child.task_id }))
13268
13426
  continue;
13269
13427
  handoffs.push({ role: "lane-steward", action: "release", task_id: child.task_id, lane_branch: child.lane.branch });
13270
13428
  }
@@ -13315,6 +13473,7 @@ var TERMINAL_NEXT_ACTIONS = {
13315
13473
  budget_stopped: "The child budget stopped new enrollments; re-confirm to continue under a new authorization.",
13316
13474
  failed: "A lineage failure stopped the batch; inspect the failing child and the branch state.",
13317
13475
  rejected: "The batch was rejected before any enrollment; correct the stated reason and re-confirm.",
13476
+ superseded: "The plan moved past this record and an explicit disposition retired it; it grants no handoff and no child delivery.",
13318
13477
  needs_human: LANE_NEEDS_HUMAN_NEXT_ACTION,
13319
13478
  running: "The batch is still running; no terminal report is due yet.",
13320
13479
  prepared: "The batch is prepared but not started."
@@ -13335,8 +13494,9 @@ function laneReport(record, reason, nextAction, extra = {}) {
13335
13494
  created_at: record.updated_at
13336
13495
  };
13337
13496
  }
13338
- function laneRejectionReport(record, reason, nextAction) {
13339
- return laneReport({ ...record, batch_state: "rejected" }, reason, nextAction);
13497
+ function laneRejectionReport(input, record, reason, nextAction) {
13498
+ const handoffs = releaseHandoffs(input, record, laneGitOf(input));
13499
+ return laneReport({ ...record, batch_state: "rejected" }, reason, handoffs.length ? `${nextAction}. Check each release handoff with the Lane Steward.` : nextAction, { handoffs });
13340
13500
  }
13341
13501
  function finalizeLane(root, record, reason, nextAction, extra = {}) {
13342
13502
  const report = laneReport(record, reason, nextAction, extra);
@@ -13449,8 +13609,34 @@ async function runLaneBatch(input, persisted) {
13449
13609
  return finalizeLane(input.root, record, `terminal state already reached: ${record.batch_state}`, "", {
13450
13610
  handoffs: record.batch_state === "completed" ? releaseHandoffs(input, record, lanes) : []
13451
13611
  });
13452
- if (record.batch_state === "needs_human")
13453
- return finalizeLane(input.root, record, "a parked child needs a human decision", LANE_NEEDS_HUMAN_NEXT_ACTION);
13612
+ if (record.batch_state === "needs_human") {
13613
+ const invalid = validateNewAuthorization(input);
13614
+ if (invalid || Date.parse(input.confirmation_time) <= Date.parse(record.confirmation_time))
13615
+ return laneReport(record, invalid ?? "the parked batch requires a fresh literal-user confirmation", LANE_NEEDS_HUMAN_NEXT_ACTION);
13616
+ const resumed = [];
13617
+ for (const child of record.children) {
13618
+ if (child.state !== "needs_human") {
13619
+ resumed.push(child);
13620
+ continue;
13621
+ }
13622
+ if (!child.lane?.run_id)
13623
+ return laneReport(record, "parked Lane has no recorded run identity; inspect it without re-enrolling", LANE_NEEDS_HUMAN_NEXT_ACTION);
13624
+ const facts = lanes.inspectLane(input.root, child.lane.path);
13625
+ const fresh = await input.kernel.projectTask(child.lane.path, child.task_id);
13626
+ if (!facts.exists || !facts.same_repository || !facts.is_worktree_root || facts.branch !== child.lane.branch || fresh.error !== null || fresh.projection.run_id !== child.lane.run_id || fresh.projection.open_user_decision_count > 0 || fresh.projection.replan_required_ids.length > 0)
13627
+ return laneReport(record, "parked Lane identity or decision is unresolved", LANE_NEEDS_HUMAN_NEXT_ACTION);
13628
+ const state = fresh.projection.lifecycle === "done" && fresh.projection.completion_ready ? child.lane.lane_commit ? "lane_committed" : "settled" : fresh.projection.lifecycle === "active" && fresh.claim?.task_id === child.task_id && (facts.active_claim_task_id === null || facts.active_claim_task_id === child.task_id) ? "enrolled" : null;
13629
+ if (!state)
13630
+ return laneReport(record, "parked child has no recoverable owned run", LANE_NEEDS_HUMAN_NEXT_ACTION);
13631
+ resumed.push({ ...child, state, reason: null, qa_failures: 0 });
13632
+ }
13633
+ record = {
13634
+ ...record,
13635
+ batch_state: "running",
13636
+ confirmation_time: input.confirmation_time,
13637
+ children: resumed.map((child) => child.state === "skipped_blocked" ? { ...child, state: "pending", reason: null } : child)
13638
+ };
13639
+ }
13454
13640
  } else {
13455
13641
  const invalid = validateNewAuthorization(input);
13456
13642
  if (invalid)
@@ -13477,6 +13663,7 @@ async function runLaneBatch(input, persisted) {
13477
13663
  const persist = () => {
13478
13664
  record = writeBatchLaneRunState(input.root, record);
13479
13665
  };
13666
+ const resumedPark = persisted?.batch_state === "needs_human" && record.batch_state === "running";
13480
13667
  if (record.batch_state === "prepared") {
13481
13668
  record.batch_state = "running";
13482
13669
  persist();
@@ -13526,6 +13713,8 @@ async function runLaneBatch(input, persisted) {
13526
13713
  } catch (error) {
13527
13714
  return failLineage(input.root, record, error instanceof Error ? error.message : String(error));
13528
13715
  }
13716
+ if (resumedPark)
13717
+ persist();
13529
13718
  const refusals = [];
13530
13719
  let parkedMessage = null;
13531
13720
  const reviewOpen = [];
@@ -13542,7 +13731,10 @@ async function runLaneBatch(input, persisted) {
13542
13731
  task_id: taskId,
13543
13732
  batch: { registry: input.registry, capability: input.capability, binding: { batch_id: record.batch_id, expected_head: lane.base_head } }
13544
13733
  });
13545
- record.children = record.children.map((c) => c.task_id === taskId ? { ...c, state: "enrolled", reason: null } : c);
13734
+ const fresh = await input.kernel.projectTask(lane.path, taskId);
13735
+ if (fresh.error !== null || !fresh.projection.run_id || fresh.claim?.task_id !== taskId)
13736
+ throw new Error("enrolled Lane has no observable owned run");
13737
+ record.children = record.children.map((c) => c.task_id === taskId ? { ...c, state: "enrolled", reason: null, lane: { ...lane, run_id: fresh.projection.run_id } } : c);
13546
13738
  persist();
13547
13739
  return null;
13548
13740
  } catch (error) {
@@ -13568,10 +13760,14 @@ async function runLaneBatch(input, persisted) {
13568
13760
  parkChild(child.task_id, "batch_lane_lost");
13569
13761
  continue;
13570
13762
  }
13763
+ if (child.lane.run_id && fresh.projection.run_id !== child.lane.run_id) {
13764
+ parkChild(child.task_id, "batch_lane_run_mismatch");
13765
+ continue;
13766
+ }
13571
13767
  const holdsClaim = fresh.claim !== null && fresh.claim.task_id === child.task_id;
13572
13768
  if (child.state === "lane_admitted") {
13573
13769
  if (holdsClaim) {
13574
- record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "enrolled", reason: "adopted existing lane claim after interruption" } : c);
13770
+ record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "enrolled", reason: "adopted existing lane claim after interruption", lane: { ...child.lane, run_id: fresh.projection.run_id } } : c);
13575
13771
  persist();
13576
13772
  } else {
13577
13773
  const failed = await enrollInLane(child.task_id);
@@ -13801,6 +13997,7 @@ var TERMINAL_NEXT_ACTIONS2 = {
13801
13997
  budget_stopped: "The child budget stopped new enrollments; re-confirm to continue under a new authorization.",
13802
13998
  failed: "A commit or lineage failure stopped the batch; inspect the failing child and the branch state.",
13803
13999
  rejected: "The batch was rejected before any enrollment; correct the stated reason and re-confirm.",
14000
+ superseded: "The plan moved past this record and an explicit disposition retired it; it grants no handoff and no child delivery.",
13804
14001
  needs_human: "A parked child needs a human decision; resolve it, then re-confirm to continue.",
13805
14002
  running: "The batch is still running; no terminal report is due yet.",
13806
14003
  prepared: "The batch is prepared but not started."
@@ -13999,7 +14196,7 @@ function rejectionReport(input, reason) {
13999
14196
  }
14000
14197
  })();
14001
14198
  if (persisted !== null && isLaneBatchRecord(persisted))
14002
- return laneRejectionReport(persisted, reason, "settle the reported kernel store condition and retry in the current Host");
14199
+ return laneRejectionReport(input, persisted, reason, "settle the reported kernel store condition and retry in the current Host");
14003
14200
  return reportFor({
14004
14201
  ...persisted ?? prepareBatchRunState({ ...input, children: [], now: input.now }),
14005
14202
  batch_state: "rejected"
@@ -14460,22 +14657,126 @@ function createBatchKernelPort(input) {
14460
14657
  };
14461
14658
  }
14462
14659
 
14463
- // plugins/immune-brain/runtime/claude/kernel_ports.ts
14464
- function diffSnapshotOf(root, record) {
14465
- return taskDeliveryIdentity(root, record);
14660
+ // plugins/immune-brain/runtime/unattended/batch_disposition.ts
14661
+ var SERIAL_IN_FLIGHT = new Set(["enrolled", "settled"]);
14662
+ var LANE_IN_FLIGHT = new Set(["lane_admitted", "enrolled", "settled", "lane_committed"]);
14663
+ var RETIRE_REASON = "Retired by an explicit literal-user disposition: the batch plan moved past this record, so its authorization could neither reconfirm nor continue. " + "Recorded child commits, child states and the batch branch are preserved as evidence. " + "Children this batch never executed carry no commit, no batch trailer and no approval; nothing infers their delivery from this record.";
14664
+ var RETIRE_NEXT_ACTION = "The Initiative has no active batch. Run each remaining child with imm-run under its own authorization, or start a new batch for this Initiative after checking out its batch branch.";
14665
+ function childEvidenceOf(record) {
14666
+ if (record.contract === "assurance_kernel/batch_run_state/v2")
14667
+ return record.children.map((child) => ({
14668
+ task_id: child.task_id,
14669
+ slice_id: child.slice_id,
14670
+ state: child.state,
14671
+ commit: child.commit,
14672
+ lane_branch: child.lane?.branch ?? null
14673
+ }));
14674
+ return record.children.map((child) => ({
14675
+ task_id: child.task_id,
14676
+ slice_id: child.slice_id,
14677
+ state: child.state,
14678
+ commit: child.commit,
14679
+ lane_branch: null
14680
+ }));
14466
14681
  }
14467
- function diffHashOf(root, record) {
14468
- return diffSnapshotOf(root, record).diff_hash;
14682
+ function inFlightChildOf(record) {
14683
+ const inFlight = record.contract === "assurance_kernel/batch_run_state/v2" ? LANE_IN_FLIGHT : SERIAL_IN_FLIGHT;
14684
+ return record.children.find((child) => inFlight.has(child.state)) ?? null;
14469
14685
  }
14470
- function readTaskIntentForRecord(root, taskId) {
14471
- recoverKernelStoreFollowUps(root, taskId);
14472
- const currentPath = readTaskRecordRaw(root, taskId).record?.intent_ref?.path;
14473
- return readTaskIntent(root, taskId, currentPath);
14686
+ function readStaleBatchLookup(root, initiativeSlug) {
14687
+ const found = findExistingActiveBatch(root, initiativeSlug);
14688
+ if (found === null) {
14689
+ const settled = findSettledBatchRecord(root, initiativeSlug);
14690
+ if (settled)
14691
+ return { kind: "terminal", batch_id: settled.batch_id };
14692
+ return { kind: "none" };
14693
+ }
14694
+ if (found.corrupt)
14695
+ return { kind: "corrupt", path: found.path };
14696
+ const record = found.record;
14697
+ if (isTerminalBatchState(record.batch_state))
14698
+ return { kind: "terminal", batch_id: record.batch_id };
14699
+ const inFlight = inFlightChildOf(record);
14700
+ if (inFlight)
14701
+ return { kind: "in_flight", batch_id: record.batch_id, task_id: inFlight.task_id };
14702
+ return {
14703
+ kind: "retirable",
14704
+ record,
14705
+ facts: {
14706
+ batch_id: record.batch_id,
14707
+ initiative_slug: record.initiative_slug,
14708
+ batch_state: record.batch_state,
14709
+ plan_digest: record.plan_digest,
14710
+ branch: record.branch ?? `imm/${record.initiative_slug}`,
14711
+ base_head: record.base_head,
14712
+ confirmation_time: record.confirmation_time,
14713
+ recorded_commits: [...record.commits],
14714
+ children: childEvidenceOf(record)
14715
+ }
14716
+ };
14474
14717
  }
14718
+ async function retireStaleBatch(options) {
14719
+ const { root, initiative_slug: initiativeSlug, now } = options;
14720
+ const lookup = readStaleBatchLookup(root, initiativeSlug);
14721
+ if (lookup.kind === "none")
14722
+ return { outcome: "rejected", rejection: batchReason("stale_batch_absent", `Initiative ${initiativeSlug} has no active batch`) };
14723
+ if (lookup.kind === "corrupt")
14724
+ return { outcome: "rejected", rejection: batchReason("batch_state_unreadable", lookup.path) };
14725
+ if (lookup.kind === "terminal")
14726
+ return {
14727
+ outcome: "rejected",
14728
+ rejection: batchReason("stale_batch_absent", `batch ${lookup.batch_id} already reached a terminal state`)
14729
+ };
14730
+ if (lookup.kind === "in_flight")
14731
+ return {
14732
+ outcome: "rejected",
14733
+ rejection: batchReason("stale_batch_in_flight", `batch ${lookup.batch_id}, child ${lookup.task_id}`)
14734
+ };
14735
+ const { record, facts } = lookup;
14736
+ const expected = readSecureProjectBytes(root, batchStatePath(record.batch_id));
14737
+ const decision = await options.gate(facts);
14738
+ if (decision.kind === "host_rejection")
14739
+ return { outcome: "host_rejection", value: decision.value };
14740
+ if (!readSecureProjectBytes(root, batchStatePath(record.batch_id)).equals(expected))
14741
+ return { outcome: "rejected", rejection: batchReason("plan_changed", record.batch_id) };
14742
+ const retired = { ...record, batch_state: "superseded" };
14743
+ const stored = replaceAnyBatchRunState(root, expected, retired, () => {
14744
+ if (!readSecureProjectBytes(root, batchStatePath(record.batch_id)).equals(expected))
14745
+ throw new Error("batch state changed before the disposition was applied");
14746
+ });
14747
+ const report = stored.contract === "assurance_kernel/batch_run_state/v2" ? writeBatchRunReport(root, {
14748
+ contract: "assurance_kernel/batch_run_report/v1",
14749
+ batch_id: stored.batch_id,
14750
+ initiative_slug: stored.initiative_slug,
14751
+ batch_state: stored.batch_state,
14752
+ max_parallel: stored.max_parallel,
14753
+ children: stored.children,
14754
+ commits: stored.commits,
14755
+ reason: RETIRE_REASON,
14756
+ handoffs: [],
14757
+ next_action: RETIRE_NEXT_ACTION,
14758
+ created_at: now
14759
+ }) : writeBatchRunReport(root, {
14760
+ contract: "assurance_kernel/batch_run_report/v1",
14761
+ batch_id: stored.batch_id,
14762
+ initiative_slug: stored.initiative_slug,
14763
+ batch_state: stored.batch_state,
14764
+ children: stored.children,
14765
+ commits: stored.commits,
14766
+ reason: RETIRE_REASON,
14767
+ next_action: RETIRE_NEXT_ACTION,
14768
+ created_at: now
14769
+ });
14770
+ return { outcome: "retired", batch_id: stored.batch_id, record: stored, report };
14771
+ }
14772
+
14773
+ // plugins/immune-brain/runtime/assurance/review_mediation.ts
14774
+ import { createHash as createHash21 } from "node:crypto";
14475
14775
  function extractVerdictJson(input) {
14476
14776
  if (typeof input === "string") {
14477
- const cleaned = input.split(`
14478
- `).map((line) => line.trim()).filter((line) => line.startsWith("{") && line.endsWith("}")).join("");
14777
+ const first = input.indexOf("{");
14778
+ const last = input.lastIndexOf("}");
14779
+ const cleaned = first >= 0 && last >= first ? input.slice(first, last + 1) : "";
14479
14780
  if (!cleaned)
14480
14781
  return null;
14481
14782
  try {
@@ -14499,36 +14800,72 @@ function verdictFingerprint(raw) {
14499
14800
  findings: raw.findings ?? null
14500
14801
  });
14501
14802
  }
14803
+ function digestOfReviewerBytes(bytes) {
14804
+ return `sha256:${createHash21("sha256").update(bytes).digest("hex")}`;
14805
+ }
14502
14806
  var RELEASED_REVIEW_RECOVERY = "Call advance_assurance to obtain a new Review reservation, then dispatch one fresh reviewer with the returned envelope unchanged";
14503
14807
  var RETAINED_REVIEW_RECOVERY = "Wait for the dispatched reviewer to finish, then call submit_review again with its verdict; do not dispatch or continue another reviewer";
14504
- var MISMATCH_REVIEW_RECOVERY = "Resubmit the reviewer's verdict exactly as the reviewer returned it";
14808
+ var MISMATCH_REVIEW_RECOVERY = "Resubmit without a verdict to apply the observed reviewer receipt, or resubmit the reviewer's verdict exactly as the reviewer returned it";
14505
14809
  function withReviewRecovery(result, recovery_action) {
14506
14810
  if (result.state !== "blocked" || result.code === "verdict_invalid")
14507
14811
  return result;
14508
14812
  return { ...result, recovery_action };
14509
14813
  }
14510
- async function submitClaudeReview(host, coordinator, ctx, taskId, verdictInput) {
14511
- if (verdictInput === undefined)
14512
- throw new Error("verdict is required");
14513
- const observed = host.inspectReviewForTask(taskId);
14814
+ async function submitMediatedReview(coordinator, ctx, taskId, verdictInput, inspect) {
14815
+ if (coordinator.active(taskId)?.state === "settlement_unknown") {
14816
+ return coordinator.submitReview(taskId, ctx, verdictInput);
14817
+ }
14818
+ const observed = inspect();
14514
14819
  if (!observed.ok) {
14515
14820
  if (observed.release)
14516
14821
  return withReviewRecovery(coordinator.abandonReview(taskId, observed.reason), RELEASED_REVIEW_RECOVERY);
14517
14822
  return { state: "blocked", reason: observed.reason, recovery_action: RETAINED_REVIEW_RECOVERY };
14518
14823
  }
14824
+ const receiptBytes = observed.receipt.result;
14825
+ const options = {
14826
+ receiptRecoveryAction: RETAINED_REVIEW_RECOVERY,
14827
+ reviewer_verdict_sha256: digestOfReviewerBytes(receiptBytes),
14828
+ validateReceipt: () => {
14829
+ const current = inspect();
14830
+ if (!current.ok)
14831
+ return current.reason;
14832
+ return current.receipt.actorId === observed.receipt.actorId && current.receipt.result === receiptBytes ? null : "reviewer receipt changed during submission";
14833
+ }
14834
+ };
14835
+ if (verdictInput === undefined) {
14836
+ if (!coordinator.isReviewVerdictValid(taskId, receiptBytes)) {
14837
+ return withReviewRecovery(coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict"), RELEASED_REVIEW_RECOVERY);
14838
+ }
14839
+ return coordinator.submitReview(taskId, ctx, receiptBytes, options);
14840
+ }
14519
14841
  const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
14520
14842
  if (!parentValid)
14521
14843
  return coordinator.submitReview(taskId, ctx, verdictInput);
14522
- const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
14523
- if (!receiptValid) {
14844
+ if (!coordinator.isReviewVerdictValid(taskId, receiptBytes)) {
14524
14845
  return withReviewRecovery(coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict"), RELEASED_REVIEW_RECOVERY);
14525
14846
  }
14526
14847
  const parentJson = extractVerdictJson(verdictInput);
14527
- const receiptJson = extractVerdictJson(observed.receipt.result);
14528
- if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
14848
+ const receiptJson = extractVerdictJson(receiptBytes);
14849
+ if (!parentJson || !receiptJson || verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
14529
14850
  return { state: "blocked", reason: "parent verdict does not match reviewer receipt", recovery_action: MISMATCH_REVIEW_RECOVERY };
14530
14851
  }
14531
- return coordinator.submitReview(taskId, ctx, verdictInput);
14852
+ return coordinator.submitReview(taskId, ctx, verdictInput, options);
14853
+ }
14854
+
14855
+ // plugins/immune-brain/runtime/claude/kernel_ports.ts
14856
+ function diffSnapshotOf(root, record) {
14857
+ return taskDeliveryIdentity(root, record);
14858
+ }
14859
+ function diffHashOf(root, record) {
14860
+ return diffSnapshotOf(root, record).diff_hash;
14861
+ }
14862
+ function readTaskIntentForRecord(root, taskId) {
14863
+ recoverKernelStoreFollowUps(root, taskId);
14864
+ const currentPath = readTaskRecordRaw(root, taskId).record?.intent_ref?.path;
14865
+ return readTaskIntent(root, taskId, currentPath);
14866
+ }
14867
+ async function submitClaudeReview(host, coordinator, ctx, taskId, verdictInput) {
14868
+ return submitMediatedReview(coordinator, ctx, taskId, verdictInput, () => host.inspectReviewForTask(taskId));
14532
14869
  }
14533
14870
  function settledKernelResult(result) {
14534
14871
  const state = result.state;
@@ -15161,6 +15498,81 @@ class ClaudeRuntime {
15161
15498
  report
15162
15499
  };
15163
15500
  }
15501
+ async retireStaleBatch(initiativeSlug, meta) {
15502
+ throwIfCancelled(meta.signal);
15503
+ const probe = probeHost(this.env, process.platform, this.hostVersion);
15504
+ if (!probe.ok)
15505
+ throw new NativeAuthorityError("unsupported_host", probe.reason);
15506
+ if (!this.requestConfirmation)
15507
+ throw new NativeAuthorityError("interaction_not_opened", batchReason("confirmation_port_unavailable").reason);
15508
+ const outcome = await retireStaleBatch({
15509
+ root: this.cwd,
15510
+ initiative_slug: initiativeSlug,
15511
+ now: new Date().toISOString(),
15512
+ gate: async (facts) => {
15513
+ let confirmationResult;
15514
+ try {
15515
+ confirmationResult = await this.requestConfirmation({
15516
+ operation: "retire_stale_batch",
15517
+ initiativeSlug,
15518
+ toolCallId: meta.toolCallId,
15519
+ batchDisposition: {
15520
+ batch_id: facts.batch_id,
15521
+ batch_state: facts.batch_state,
15522
+ batch_branch: facts.branch,
15523
+ plan_digest: facts.plan_digest,
15524
+ recorded_commits: facts.recorded_commits.length,
15525
+ children: facts.children.map((child) => ({
15526
+ task_id: child.task_id,
15527
+ slice_id: child.slice_id,
15528
+ state: child.state,
15529
+ commit: child.commit,
15530
+ lane_branch: child.lane_branch
15531
+ }))
15532
+ },
15533
+ signal: meta.signal
15534
+ });
15535
+ } catch (err) {
15536
+ if (meta.signal?.aborted)
15537
+ return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
15538
+ if (err instanceof NativeAuthorityError) {
15539
+ if (err.reasonCode === "unsupported_host")
15540
+ throw err;
15541
+ if (err.reasonCode === "user_cancelled") {
15542
+ return {
15543
+ kind: "host_rejection",
15544
+ value: { state: "cancelled", reason: err.message, recovery_action: err.recoveryAction }
15545
+ };
15546
+ }
15547
+ return {
15548
+ kind: "host_rejection",
15549
+ value: { state: "rejected", reason: err.message, recovery_action: err.recoveryAction }
15550
+ };
15551
+ }
15552
+ return {
15553
+ kind: "host_rejection",
15554
+ value: batchReason("confirmation_failed", err instanceof Error ? err.message : String(err))
15555
+ };
15556
+ }
15557
+ if (confirmationResult.decision === "cancel" && meta.signal?.aborted)
15558
+ return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
15559
+ if (meta.signal?.aborted)
15560
+ return { kind: "host_rejection", value: batchReason("cancelled_before_execution") };
15561
+ if (confirmationResult.decision === "decline")
15562
+ return { kind: "host_rejection", value: batchReason("confirmation_declined") };
15563
+ if (confirmationResult.decision === "cancel")
15564
+ return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
15565
+ if (confirmationResult.decision !== "accept")
15566
+ return { kind: "host_rejection", value: batchReason("confirmation_no_decision") };
15567
+ return { kind: "confirmed", request_id: confirmationResult.requestId };
15568
+ }
15569
+ });
15570
+ if (outcome.outcome === "host_rejection")
15571
+ return outcome.value;
15572
+ if (outcome.outcome === "rejected")
15573
+ return outcome.rejection;
15574
+ return { state: "retired", batch_id: outcome.batch_id, report: outcome.report };
15575
+ }
15164
15576
  }
15165
15577
 
15166
15578
  // plugins/immune-brain/runtime/claude/mcp_server.ts
@@ -15169,12 +15581,13 @@ var TOOLS = [
15169
15581
  { name: "status", description: "Read the Kernel Assurance Projection for an exact task.", privileged: false },
15170
15582
  { name: "enroll", description: "Enroll a Git-tracked TaskIntent after native confirmation.", privileged: true },
15171
15583
  { name: "advance_assurance", description: "Advance frozen Assurance through deterministic QA and Review reservation.", privileged: false },
15172
- { name: "submit_review", description: "Submit the Parent-mediated Review verdict bound to the correlated receipt.", privileged: false },
15584
+ { name: "submit_review", description: "Submit the Review verdict; omit the verdict to apply the correlated reviewer receipt, or relay the reviewer's structured verdict for fingerprint comparison.", privileged: false },
15173
15585
  { name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
15174
15586
  { name: "revise_intent", description: "Apply a compatible TaskIntent revision.", privileged: false },
15175
15587
  { name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
15176
15588
  { name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
15177
15589
  { name: "start_unattended_batch", description: "Start an unattended serial batch run for an Initiative after native confirmation.", privileged: true },
15590
+ { name: "retire_stale_batch", description: "Retire an Initiative's parked or drifted batch record to the terminal superseded state after native confirmation.", privileged: true },
15178
15591
  { name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false },
15179
15592
  { name: "resolve_finding", description: "Resolve one open blocking or advisory finding whose cause is fixed and verified.", privileged: false },
15180
15593
  { name: "refute_finding", description: "Refute one open finding by binding the fresh passing QA attestation that contradicts it.", privileged: false }
@@ -15186,18 +15599,20 @@ function listMcpTools() {
15186
15599
  inputSchema: {
15187
15600
  type: "object",
15188
15601
  properties: {
15189
- ...tool.name === "start_unattended_batch" ? {
15602
+ ...tool.name === "start_unattended_batch" || tool.name === "retire_stale_batch" ? {
15190
15603
  initiative_slug: { type: "string" },
15191
- max_parallel: { type: "integer", minimum: 1 },
15192
- lane_offers: {
15193
- type: "array",
15194
- items: {
15195
- type: "object",
15196
- properties: { task_id: { type: "string" }, path: { type: "string" } },
15197
- required: ["task_id", "path"],
15198
- additionalProperties: false
15604
+ ...tool.name === "start_unattended_batch" ? {
15605
+ max_parallel: { type: "integer", minimum: 1 },
15606
+ lane_offers: {
15607
+ type: "array",
15608
+ items: {
15609
+ type: "object",
15610
+ properties: { task_id: { type: "string" }, path: { type: "string" } },
15611
+ required: ["task_id", "path"],
15612
+ additionalProperties: false
15613
+ }
15199
15614
  }
15200
- }
15615
+ } : {}
15201
15616
  } : {
15202
15617
  task_id: { type: "string" },
15203
15618
  ...tool.name === "approve_breaking_intent_revision" || tool.name === "revise_intent" ? { next_intent: { type: "object" } } : {},
@@ -15207,7 +15622,7 @@ function listMcpTools() {
15207
15622
  ...tool.name === "refute_finding" ? { finding_id: { type: "string" }, attestation_id: { type: "string" } } : {}
15208
15623
  }
15209
15624
  },
15210
- 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"] : tool.name === "revise_intent" ? ["task_id", "next_intent"] : ["task_id"]
15625
+ required: tool.name === "start_unattended_batch" || tool.name === "retire_stale_batch" ? ["initiative_slug"] : tool.name === "resolve_finding" ? ["task_id", "finding_id"] : tool.name === "refute_finding" ? ["task_id", "finding_id", "attestation_id"] : tool.name === "revise_intent" ? ["task_id", "next_intent"] : ["task_id"]
15211
15626
  },
15212
15627
  annotations: tool.privileged ? privilegedAnnotations() : { readOnlyHint: tool.name === "status" }
15213
15628
  }));
@@ -15276,6 +15691,30 @@ function createMcpRuntime(options = {}) {
15276
15691
  ...laneOffers !== undefined ? { lane_offers: laneOffers } : {}
15277
15692
  });
15278
15693
  }
15694
+ if (name === "retire_stale_batch") {
15695
+ const initiativeSlug = String(args.initiative_slug ?? "");
15696
+ if (!initiativeSlug)
15697
+ throw new Error("initiative_slug is required");
15698
+ if ("native_decision" in args)
15699
+ throw new Error("native_decision cannot be supplied in tool arguments");
15700
+ if (!negotiatedVersion)
15701
+ throw new NativeAuthorityError("unsupported_host", "Claude Code version is unavailable");
15702
+ if (!negotiatedInteractive) {
15703
+ throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
15704
+ }
15705
+ const probe = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
15706
+ if (!probe.ok)
15707
+ throw new NativeAuthorityError("unsupported_host", probe.reason);
15708
+ const toolMeta = {
15709
+ sessionId: meta.sessionId ?? connectionId,
15710
+ toolCallId: meta.toolCallId ?? `call-${name}`,
15711
+ taskId: initiativeSlug,
15712
+ initiativeSlug,
15713
+ interactive: meta.interactive ?? options.interactive ?? negotiatedInteractive,
15714
+ signal: meta.signal
15715
+ };
15716
+ return runtime.retireStaleBatch(initiativeSlug, toolMeta);
15717
+ }
15279
15718
  const taskId = String(args.task_id ?? "");
15280
15719
  if (!taskId)
15281
15720
  throw new Error("task_id is required");
@@ -15303,9 +15742,7 @@ function createMcpRuntime(options = {}) {
15303
15742
  if (name === "advance_assurance")
15304
15743
  return runtime.advance(taskId, toolMeta.signal, toolMeta);
15305
15744
  if (name === "submit_review") {
15306
- if (!Object.hasOwn(args, "verdict"))
15307
- throw new Error("verdict is required");
15308
- return runtime.submitReview(taskId, args.verdict, toolMeta);
15745
+ return runtime.submitReview(taskId, Object.hasOwn(args, "verdict") ? args.verdict : undefined, toolMeta);
15309
15746
  }
15310
15747
  if (name === "revise_intent") {
15311
15748
  if (!Object.hasOwn(args, "next_intent"))
@@ -15488,6 +15925,32 @@ async function writeReply(output, reply) {
15488
15925
  });
15489
15926
  }
15490
15927
  function elicitationParams(input) {
15928
+ if (input.operation === "retire_stale_batch") {
15929
+ const d = input.batchDisposition;
15930
+ const details = [
15931
+ `Operation: retire_stale_batch`,
15932
+ `Initiative: ${input.initiativeSlug}`,
15933
+ d ? `Batch: ${d.batch_id}` : null,
15934
+ d ? `Batch state: ${d.batch_state}` : null,
15935
+ d ? `Batch branch: ${d.batch_branch}` : null,
15936
+ d ? `Plan digest: ${d.plan_digest}` : null,
15937
+ d ? `Recorded child commits: ${d.recorded_commits}` : null,
15938
+ d ? `Children (${d.children.length}):
15939
+ ${d.children.map((child) => ` - ${child.task_id} (${child.slice_id}) [state: ${child.state}]${child.commit ? ` [commit: ${child.commit}]` : " [no commit]"}${child.lane_branch ? ` [lane: ${child.lane_branch}]` : ""}`).join(`
15940
+ `)}` : null,
15941
+ "Retiring writes this record to the terminal superseded state. Child states, recorded commits and the batch branch are preserved as evidence; nothing is deleted and no batch trailer or approval is manufactured.",
15942
+ "A child without a commit was never executed by this batch: it carries no delivery evidence, and the retired authorization is never reused for a revised plan.",
15943
+ "After retiring, run each remaining child with imm-run under its own authorization, or start a new batch for this Initiative."
15944
+ ].filter(Boolean);
15945
+ return {
15946
+ mode: "form",
15947
+ message: `Retire this stale batch record for ${input.initiativeSlug}?
15948
+
15949
+ ${details.join(`
15950
+ `)}`,
15951
+ requestedSchema: { type: "object", properties: {} }
15952
+ };
15953
+ }
15491
15954
  if (input.operation === "start_unattended_batch") {
15492
15955
  const b = input.batchDetails;
15493
15956
  const details = [