immune-brain 4.6.0 → 5.0.0

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 (64) hide show
  1. package/README.md +154 -37
  2. package/README.zh-CN.md +160 -40
  3. package/package.json +1 -1
  4. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +42 -44
  6. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +142 -474
  7. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +54 -84
  8. package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +7 -2
  9. package/plugins/immune-brain/.pi-extension/tsconfig.json +2 -2
  10. package/plugins/immune-brain/agents/immune-brain-advisory-reviewer.md +23 -0
  11. package/plugins/immune-brain/agents/immune-brain-qa.md +42 -0
  12. package/plugins/immune-brain/agents/immune-brain-reviewer.md +90 -1
  13. package/plugins/immune-brain/agents/immune-brain-ui-review.md +21 -0
  14. package/plugins/immune-brain/dist/BASELINE.md +4 -4
  15. package/plugins/immune-brain/dist/claude/mcp-server.mjs +7369 -7016
  16. package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +3 -3
  17. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +30 -1
  18. package/plugins/immune-brain/dist/imm-brainstorm.md +1 -1
  19. package/plugins/immune-brain/dist/imm-doc-prune.md +2 -2
  20. package/plugins/immune-brain/dist/{imm-agent-doc-maintain.md → imm-doc-slim.md} +9 -9
  21. package/plugins/immune-brain/dist/imm-planner.md +23 -8
  22. package/plugins/immune-brain/dist/imm-pr-fix.md +1 -1
  23. package/plugins/immune-brain/dist/{imm-review-retro.md → imm-retro.md} +26 -13
  24. package/plugins/immune-brain/dist/{imm-loop.md → imm-run.md} +33 -7
  25. package/plugins/immune-brain/dist/registry.yaml +9 -9
  26. package/plugins/immune-brain/dist/role-prompts/code-review.md +7 -0
  27. package/plugins/immune-brain/dist/role-prompts/executor.md +35 -2
  28. package/plugins/immune-brain/runtime/assurance/coordinator.ts +83 -67
  29. package/plugins/immune-brain/runtime/assurance/host_port.ts +20 -0
  30. package/plugins/immune-brain/runtime/assurance/qa.ts +2 -2
  31. package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +347 -0
  32. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +62 -369
  33. package/plugins/immune-brain/runtime/claude/mcp_server.ts +2 -0
  34. package/plugins/immune-brain/runtime/claude/review_host.ts +48 -16
  35. package/plugins/immune-brain/runtime/claude/role_agents.ts +83 -0
  36. package/plugins/immune-brain/runtime/commands/kernel.ts +3 -6
  37. package/plugins/immune-brain/runtime/kernel/application.ts +6 -3
  38. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +15 -8
  39. package/plugins/immune-brain/runtime/kernel/canary_application.ts +5 -3
  40. package/plugins/immune-brain/runtime/kernel/enrollment.ts +149 -5
  41. package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
  42. package/plugins/immune-brain/runtime/kernel/storage.ts +170 -11
  43. package/plugins/immune-brain/runtime/kernel/storage_paths.ts +13 -0
  44. package/plugins/immune-brain/runtime/loop_contract.ts +22 -22
  45. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  46. package/plugins/immune-brain/runtime/prompts/code-review.md +7 -0
  47. package/plugins/immune-brain/runtime/prompts/executor.md +35 -2
  48. package/plugins/immune-brain/runtime/role_prompt_bridge.ts +27 -0
  49. package/plugins/immune-brain/runtime/unattended/batch_git.ts +57 -27
  50. package/plugins/immune-brain/runtime/unattended/batch_kernel_port.ts +106 -0
  51. package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +12 -14
  52. package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +34 -29
  53. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +26 -90
  54. package/plugins/immune-brain/runtime/workspace_scope.ts +34 -0
  55. package/plugins/immune-brain/skills/BASELINE.md +4 -4
  56. package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +1 -1
  57. package/plugins/immune-brain/skills/{imm-agent-doc-maintain → imm-doc-slim}/SKILL.md +9 -9
  58. package/plugins/immune-brain/skills/imm-planner/SKILL.md +4 -2
  59. package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +1 -1
  60. package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/SKILL.md +7 -7
  61. package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/scripts/review_retro.ts +46 -21
  62. package/plugins/immune-brain/skills/{imm-loop → imm-run}/SKILL.md +7 -7
  63. package/plugins/immune-brain/skills/registry.yaml +9 -9
  64. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +0 -581
@@ -1,30 +1,23 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { stagePlanningArtifactTransition, type VerdictAuthority } from "../assurance/verdict_authority";
2
+ export { ensureReviewRevision as ensureClaudeReviewRevision } from "../assurance/verdict_authority";
2
3
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
4
  import { execFileSync } from "node:child_process";
4
5
  import { join } from "node:path";
5
6
  import {
6
7
  AssuranceCoordinator,
7
- reviewAdvisoryRecords,
8
- reviewReworkFindings,
9
8
  type AssuranceCoordinatorPorts,
10
9
  type AssuranceSubmitReviewResult,
11
- type AssuranceVerdict,
12
10
  type HostContext,
13
- type SnapshotDescriptor,
14
11
  } from "../assurance/coordinator";
15
12
  import {
16
- type VerificationDescriptor,
17
13
  } from "../assurance/verification";
18
14
  import {
19
- captureReviewManifest,
20
15
  writeNativeReviewEvidence,
21
- type ReviewRevision,
22
16
  } from "../assurance/review_evidence";
23
- import { parseVerificationDescriptor } from "../verification_descriptor";
24
17
  import { projectAssurance, type AssuranceProjection, type AssuranceProjectionResult } from "../kernel/assurance_projection";
25
- import { type TaskApprovalV2, type TaskFinding, type TaskRecord } from "../kernel/types";
18
+ import { type TaskFinding, type TaskRecord } from "../kernel/types";
26
19
  import { findingsDigestV2 } from "../kernel/reducer";
27
- import { readTaskRecord, readTaskRecordRaw, recoverKernelStoreFollowUps } from "../kernel/storage";
20
+ import { readTaskRecordRaw, recoverKernelStoreFollowUps } from "../kernel/storage";
28
21
  import { canonicalIntentHash, parseTaskIntentV1, readTaskIntent } from "../kernel/intent";
29
22
  import { capabilityActionFor, createCanaryApplication } from "../kernel/canary_application";
30
23
  import {
@@ -37,12 +30,12 @@ import {
37
30
  createEnrollmentAuthorityRegistry,
38
31
  type EnrollmentCapabilityBinding,
39
32
  } from "../kernel/enrollment_authority";
40
- import { enrollCanaryTask, runEnrollmentRehearsal } from "../kernel/enrollment";
33
+ import { enrollTask } from "../kernel/enrollment";
41
34
  import { reconcileKernelAuthority, repairKernelAuthority } from "../kernel/storage";
42
35
  import { inspectEnrollmentGitBase, enrollmentGitBaseNotice, initializeEnrollmentGitBase } from "../assurance/enrollment_git_base";
43
36
  import { preparePiCanary, revalidatePiCanary } from "../kernel/pi_canary_prepare";
44
37
  import { runDeterministicQa } from "../assurance/qa";
45
- import { taskDiffIdentity, taskRevisionIdentity } from "../workspace_scope";
38
+ import { taskDeliveryIdentity } from "../workspace_scope";
46
39
  import { batchReason } from "../unattended/batch_reasons";
47
40
  import { deriveAuthorizationOperation } from "../authorization_operation";
48
41
  import { LITERAL_USER_ACTOR_ID, canonicalActorId } from "../kernel/actor_identity";
@@ -56,21 +49,16 @@ import { readTaskTombstone } from "../kernel/backend_claim";
56
49
  import {
57
50
  authorizeBatch,
58
51
  projectBatchPreflight,
59
- readActiveClaimTaskId,
60
- isOwnBatchClaim,
61
52
  findResumableBatchSlugForTask,
62
53
  } from "../unattended/batch_preflight";
63
54
  import {
64
55
  startBatch,
65
- batchQaFailureFacts,
66
56
  type BatchRunnerKernelPort,
67
57
  type BatchRunReport,
68
58
  } from "../unattended/batch_runner";
59
+ import { createBatchKernelPort } from "../unattended/batch_kernel_port";
69
60
  import {
70
61
  createBatchAuthorityRegistry,
71
- deriveChildEnrollment,
72
- type BatchAuthorityRegistry,
73
- type BatchAuthorizationBinding,
74
62
  } from "../kernel/batch_authority";
75
63
  import {
76
64
  type BatchRunnerGitPort,
@@ -96,11 +84,7 @@ export function diffSnapshotOf(root: string, record: TaskRecord): {
96
84
  diff_hash: string;
97
85
  changed_paths: readonly string[];
98
86
  } {
99
- if (record.contract === "assurance_kernel/task_record/v4") {
100
- if (!record.git_base_head) throw new Error("TaskRecord v4 is missing git_base_head");
101
- return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head, record.task_id);
102
- }
103
- return taskDiffIdentity(root, record.intent_snapshot.scope_hint, record.task_id);
87
+ return taskDeliveryIdentity(root, record);
104
88
  }
105
89
 
106
90
  export function diffHashOf(root: string, record: TaskRecord): string {
@@ -144,6 +128,21 @@ function verdictFingerprint(raw: Record<string, unknown>): string {
144
128
  });
145
129
  }
146
130
 
131
+ const RELEASED_REVIEW_RECOVERY =
132
+ "Call advance_assurance to obtain a new Review reservation, then dispatch one fresh reviewer with the returned envelope unchanged";
133
+ const RETAINED_REVIEW_RECOVERY =
134
+ "Wait for the dispatched reviewer to finish, then call submit_review again with its verdict; do not dispatch or continue another reviewer";
135
+ const MISMATCH_REVIEW_RECOVERY =
136
+ "Resubmit the reviewer's verdict exactly as the reviewer returned it";
137
+
138
+ function withReviewRecovery(
139
+ result: AssuranceSubmitReviewResult,
140
+ recovery_action: string,
141
+ ): AssuranceSubmitReviewResult {
142
+ if (result.state !== "blocked" || result.code === "verdict_invalid") return result;
143
+ return { ...result, recovery_action };
144
+ }
145
+
147
146
  export async function submitClaudeReview(
148
147
  host: ClaudeReviewHost,
149
148
  coordinator: AssuranceCoordinator,
@@ -154,19 +153,19 @@ export async function submitClaudeReview(
154
153
  if (verdictInput === undefined) throw new Error("verdict is required");
155
154
  const observed = host.inspectReviewForTask(taskId);
156
155
  if (!observed.ok) {
157
- if (observed.release) return coordinator.abandonReview(taskId, observed.reason);
158
- return { state: "blocked", reason: observed.reason };
156
+ if (observed.release) return withReviewRecovery(coordinator.abandonReview(taskId, observed.reason), RELEASED_REVIEW_RECOVERY);
157
+ return { state: "blocked", reason: observed.reason, recovery_action: RETAINED_REVIEW_RECOVERY };
159
158
  }
160
159
  const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
161
160
  if (!parentValid) return coordinator.submitReview(taskId, ctx, verdictInput);
162
161
  const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
163
162
  if (!receiptValid) {
164
- return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
163
+ return withReviewRecovery(coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict"), RELEASED_REVIEW_RECOVERY);
165
164
  }
166
165
  const parentJson = extractVerdictJson(verdictInput)!;
167
166
  const receiptJson = extractVerdictJson(observed.receipt.result)!;
168
167
  if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
169
- return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
168
+ return { state: "blocked", reason: "parent verdict does not match reviewer receipt", recovery_action: MISMATCH_REVIEW_RECOVERY };
170
169
  }
171
170
  return coordinator.submitReview(taskId, ctx, verdictInput);
172
171
  }
@@ -200,150 +199,6 @@ function assertProjectionBinding(before: AssuranceProjectionResult, after: Assur
200
199
  }
201
200
  }
202
201
 
203
- function qaOutcomes(record: { attestations: Array<{ kind: string; acceptance_results: Array<{ acceptance_id: string; status: "passed" | "failed" | "blocked"; summary: string }> }> }) {
204
- return Object.fromEntries(
205
- record.attestations.filter((item) => item.kind === "qa").flatMap((item) => item.acceptance_results)
206
- .map((result) => [result.acceptance_id, { status: result.status, summary: result.summary }]),
207
- );
208
- }
209
-
210
- /**
211
- * Publish the task-scoped synthetic revision for a v4 record and return the
212
- * exact identity the Review snapshot binds.
213
- *
214
- * `submitReview` re-derives this identity and compares all four fields —
215
- * `manifest_digest` included — against the reservation. Returning the bare
216
- * commit identity therefore compared a real digest against `undefined` and
217
- * failed every v4 submission with "Review revision changed before submission",
218
- * so the manifest is recomputed here rather than only the commit. The outcomes
219
- * come from the same `qaOutcomes` the Review snapshot is built from, which
220
- * makes the two digests equal by construction instead of by coincidence.
221
- *
222
- * v3 records keep the legacy full-source bundle and return null.
223
- */
224
- export async function ensureClaudeReviewRevision(
225
- root: string,
226
- taskId: string,
227
- projection: AssuranceProjectionResult,
228
- ): Promise<ReviewRevision | null> {
229
- const current = await readTaskRecord(root, taskId);
230
- const record = current.record;
231
- if (!record) throw new Error(`task ${taskId} has no TaskRecord`);
232
- if (current.revision !== projection.projection.record_revision)
233
- throw new Error("TaskRecord changed before Review revision preparation");
234
- if (record.contract !== "assurance_kernel/task_record/v4") return null;
235
- if (!record.git_base_head)
236
- throw new Error("Review revision requires a TaskRecord v4 git_base_head");
237
- const manifest = captureReviewManifest(root, {
238
- taskId,
239
- baseHead: record.git_base_head,
240
- scopeHint: record.intent_snapshot.scope_hint,
241
- expectedDiffHash: projection.projection.diff_hash,
242
- intentRevision: projection.projection.intent_revision,
243
- intentContentHash: projection.projection.intent_content_hash,
244
- recordRevision: projection.projection.record_revision,
245
- workspaceRevision: projection.projection.workspace_revision,
246
- lifecycle: projection.projection.lifecycle,
247
- artifactState: projection.projection.artifact_state,
248
- risk: record.intent_snapshot.risk,
249
- outcomes: qaOutcomes(record),
250
- });
251
- return {
252
- contract: "assurance_kernel/review_revision/v1",
253
- base_head: manifest.base_head,
254
- review_tree: manifest.review_tree,
255
- review_commit: manifest.review_commit,
256
- review_ref: manifest.review_ref,
257
- diff_hash: manifest.diff_hash,
258
- manifest_digest: manifest.manifest_digest,
259
- };
260
- }
261
-
262
- async function buildAssuranceSnapshot(
263
- root: string,
264
- taskId: string,
265
- role: "qa" | "review",
266
- projection: AssuranceProjectionResult,
267
- ) {
268
- const read = await readTaskRecord(root, taskId);
269
- const record = read.record;
270
- if (!record || read.revision !== projection.projection.record_revision) throw new Error("TaskRecord changed before assurance snapshot capture");
271
- const intent = record.intent_snapshot;
272
- const descriptors = new Map<string, VerificationDescriptor>();
273
- for (const item of role === "qa" ? intent.acceptance : []) {
274
- const descriptor = parseVerificationDescriptor(item.verification);
275
- descriptors.set(item.id, descriptor);
276
- }
277
- const reviewBundle = null;
278
- const reviewManifest = role === "review"
279
- ? captureReviewManifest(root, {
280
- taskId,
281
- baseHead: record.git_base_head,
282
- scopeHint: intent.scope_hint,
283
- expectedDiffHash: projection.projection.diff_hash,
284
- intentRevision: projection.projection.intent_revision,
285
- intentContentHash: projection.projection.intent_content_hash,
286
- recordRevision: projection.projection.record_revision,
287
- workspaceRevision: projection.projection.workspace_revision,
288
- lifecycle: projection.projection.lifecycle,
289
- artifactState: projection.projection.artifact_state,
290
- risk: intent.risk,
291
- outcomes: qaOutcomes(record),
292
- })
293
- : null;
294
- const dirtyFiles = reviewManifest ? Object.keys(reviewManifest.changed_paths) : [];
295
- const snapshot: SnapshotDescriptor = {
296
- contract: "assurance_kernel/assurance_snapshot/v2",
297
- task_id: taskId,
298
- run_id: projection.projection.run_id,
299
- role,
300
- record_revision: projection.projection.record_revision,
301
- workspace_revision: projection.projection.workspace_revision,
302
- intent_revision: projection.projection.intent_revision,
303
- intent_content_hash: projection.projection.intent_content_hash,
304
- diff_hash: projection.projection.diff_hash,
305
- lifecycle: projection.projection.lifecycle,
306
- artifact_state: projection.projection.artifact_state,
307
- risk: intent.risk,
308
- fresh_acceptance_ids: projection.projection.fresh_acceptance_ids,
309
- missing_acceptance_ids: projection.projection.missing_acceptance_ids,
310
- stale_attestation_ids: projection.projection.stale_attestation_ids,
311
- acceptance: intent.acceptance,
312
- dirty_files: dirtyFiles,
313
- review_bundle_digest: reviewManifest?.manifest_digest ?? null,
314
- root,
315
- ...(reviewManifest
316
- ? {
317
- review_revision: {
318
- contract: "assurance_kernel/review_revision_identity/v1",
319
- base_head: reviewManifest.base_head,
320
- review_commit: reviewManifest.review_commit,
321
- review_tree: reviewManifest.review_tree,
322
- manifest_digest: reviewManifest.manifest_digest,
323
- },
324
- }
325
- : {}),
326
- };
327
- return { snapshot, descriptors, reviewBundle, reviewManifest };
328
- }
329
-
330
- function stagePlanningArtifactTransition(root: string, record: { intent_ref: { path: string }; intent_snapshot: { scope_hint: string[] } }): void {
331
- const intentActive = record.intent_ref.path.replace("docs/plans/archive/", "docs/plans/");
332
- const intentArchive = intentActive.replace("docs/plans/", "docs/plans/archive/");
333
- const specActive = record.intent_snapshot.scope_hint.find((path) =>
334
- /^docs\/specs\/(?!archive\/)[^/]+\.spec\.md$/.test(path)
335
- && record.intent_snapshot.scope_hint.includes(path.replace("docs/specs/", "docs/specs/archive/")),
336
- );
337
- const candidates = [
338
- intentActive,
339
- intentArchive,
340
- ...(specActive ? [specActive, specActive.replace("docs/specs/", "docs/specs/archive/")] : []),
341
- ];
342
- const paths = candidates.filter((path) => existsSync(join(root, path)) || execFileSync("git", ["ls-files", "--cached", "--", path], { cwd: root, encoding: "utf8" }).trim().length > 0);
343
- if (paths.length === 0) return;
344
- execFileSync("git", ["add", "--", ...paths], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
345
- }
346
-
347
202
  async function mintCapability(
348
203
  registry: MutationAuthorityRegistry,
349
204
  input: {
@@ -413,6 +268,8 @@ export interface ClaudeRuntimeOptions {
413
268
  * actually wires was never constructed once.
414
269
  */
415
270
  ports?: Partial<AssuranceCoordinatorPorts>;
271
+ /** Optional authority test seam, separate from host ports. */
272
+ authorityOverrides?: Partial<VerdictAuthority>;
416
273
  interactive?: boolean;
417
274
  permissionMode?: PermissionMode;
418
275
  requestConfirmation?: NativeConfirmationPort;
@@ -469,7 +326,8 @@ export class ClaudeRuntime {
469
326
  ...this.createKernelPorts(),
470
327
  ...options.ports,
471
328
  host: this.host,
472
- });
329
+ confirmationReference: ({ actorId }) => `claude:${actorId}`,
330
+ }, options.authorityOverrides);
473
331
  }
474
332
 
475
333
  observe(event: ClaudeHookEvent): void {
@@ -502,14 +360,12 @@ export class ClaudeRuntime {
502
360
  private createKernelPorts(): AssuranceCoordinatorPorts {
503
361
  return {
504
362
  host: this.host,
363
+ confirmationReference: ({ actorId }) => `claude:${actorId}`,
505
364
  projectTask: (root, taskId) => projectAssurance(root, taskId, diffSnapshotOf),
506
- readTaskRecord: async (root, taskId) => readTaskRecord(root, taskId),
365
+ readTaskRecord: async (root, taskId) => readTaskRecordRaw(root, taskId),
507
366
  readTaskIntent: async (root, taskId) => readTaskIntentForRecord(root, taskId),
508
- buildAssurance: (root, taskId, role, projection) => buildAssuranceSnapshot(root, taskId, role, projection),
509
- ensureReviewRevision: (root, taskId, projection) => ensureClaudeReviewRevision(root, taskId, projection),
510
367
  runQa: (snapshot, descriptors, options) => runDeterministicQa(snapshot, descriptors, options),
511
368
  writeReviewEvidence: (input) => writeNativeReviewEvidence(input.evidence),
512
- applyVerdict: (ctx, input) => this.applyVerdict(ctx, input),
513
369
  applyOrdinaryOperation: (ctx, input) => this.executeOrdinary(ctx, input),
514
370
  };
515
371
  }
@@ -588,21 +444,7 @@ export class ClaudeRuntime {
588
444
  confirmation_ref: gate.confirmation_ref,
589
445
  nonce,
590
446
  };
591
- const capability = this.enrollmentRegistry.issue(binding);
592
- const input = {
593
- task_id: taskId,
594
- intent_path: binding.intent_path,
595
- intent_revision: binding.intent_revision,
596
- preparation_digest: binding.preparation_digest,
597
- capability,
598
- capability_binding: binding,
599
- now,
600
- };
601
- const rehearsal = runEnrollmentRehearsal(this.cwd, input, capability, this.enrollmentRegistry);
602
- if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready") {
603
- throw new Error(`Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`);
604
- }
605
- return enrollCanaryTask(this.cwd, input, this.enrollmentRegistry);
447
+ return await enrollTask(this.cwd, this.enrollmentRegistry, { binding, now });
606
448
  } catch (error) {
607
449
  if (gitBaseNote) throw new Error(`${error instanceof Error ? error.message : String(error)}${gitBaseNote}`);
608
450
  throw error;
@@ -766,7 +608,7 @@ export class ClaudeRuntime {
766
608
  const derived = deriveAuthorizationOperation({ readiness: projection.projection.authorization });
767
609
  if ("blocked" in derived) throw new Error(derived.blocked);
768
610
  if (derived.operation === "resolve-user-decision") {
769
- const record = await readTaskRecord(this.cwd, taskId);
611
+ const record = await readTaskRecordRaw(this.cwd, taskId);
770
612
  const open = (record.record?.findings ?? []).filter(
771
613
  (finding) => finding.kind === "unresolved_user_decision" && finding.status === "open",
772
614
  );
@@ -800,7 +642,7 @@ export class ClaudeRuntime {
800
642
  if (sidecar && nextIntent) {
801
643
  writeFileSync(sidecar, `${JSON.stringify(nextIntent, null, 2)}\n`);
802
644
  execFileSync("git", ["add", "--", priorIntent.intent_ref.path], { cwd: this.cwd, stdio: ["ignore", "pipe", "pipe"] });
803
- const preparedRecord = await readTaskRecord(this.cwd, taskId);
645
+ const preparedRecord = await readTaskRecordRaw(this.cwd, taskId);
804
646
  if (!preparedRecord.record) {
805
647
  throw new NativeAuthorityError("workspace_changed", "TaskRecord changed before the breaking revision digest");
806
648
  }
@@ -823,7 +665,7 @@ export class ClaudeRuntime {
823
665
  });
824
666
  } catch (error) {
825
667
  if (stagedSnapshot) {
826
- const current = await readTaskRecord(this.cwd, taskId);
668
+ const current = await readTaskRecordRaw(this.cwd, taskId);
827
669
  if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
828
670
  restoreStagedIntent();
829
671
  }
@@ -885,7 +727,7 @@ export class ClaudeRuntime {
885
727
  ) stagePlanningArtifactTransition(this.cwd, result.record);
886
728
  } catch (error) {
887
729
  if (stagedSnapshot) {
888
- const current = await readTaskRecord(this.cwd, taskId);
730
+ const current = await readTaskRecordRaw(this.cwd, taskId);
889
731
  if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
890
732
  restoreStagedIntent();
891
733
  }
@@ -899,94 +741,6 @@ export class ClaudeRuntime {
899
741
  return this.withTerminalTracker(taskId, committed);
900
742
  }
901
743
 
902
- private async applyVerdict(
903
- ctx: HostContext,
904
- input: {
905
- taskId: string;
906
- snapshot: SnapshotDescriptor;
907
- verdict: AssuranceVerdict;
908
- invocation: { /* token */ };
909
- actorId: string;
910
- hooks?: { beforeCommit?: () => Promise<void>; onCommit?: () => void; afterCommit?: () => Promise<void> };
911
- },
912
- ): Promise<void> {
913
- const { registry, app } = await this.authority();
914
- const priorIntentToken = (await readTaskIntentForRecord(ctx.cwd, input.taskId)).token;
915
- const now = new Date().toISOString();
916
- const commitAndApply = async <T>(apply: () => Promise<T>): Promise<T> => {
917
- this.coordinator.commitInvocation(input.invocation as never);
918
- const settlement = apply();
919
- input.hooks?.onCommit?.();
920
- const result = await settlement;
921
- await input.hooks?.afterCommit?.();
922
- return result;
923
- };
924
- if (input.verdict.decision === "rework") {
925
- const findings = reviewReworkFindings(input.verdict);
926
- const capability = await mintCapability(registry, {
927
- authority_kind: input.snapshot.role,
928
- task_id: input.taskId,
929
- run_id: input.snapshot.run_id,
930
- action_kind: "request_rework",
931
- expected_record_hash: input.snapshot.record_revision,
932
- intent_revision: input.snapshot.intent_revision,
933
- intent_content_hash: input.snapshot.intent_content_hash,
934
- diff_hash: input.snapshot.diff_hash,
935
- actor_id: input.actorId,
936
- findings,
937
- now,
938
- confirmation_ref: `claude:${input.actorId}`,
939
- });
940
- await input.hooks?.beforeCommit?.();
941
- const result = await commitAndApply(async () => app.execute({
942
- root: ctx.cwd,
943
- task_id: input.taskId,
944
- operation: { op: "request_rework", capability, findings: findings as never[], actor_id: input.actorId },
945
- prior_intent_token: priorIntentToken,
946
- diffProvider: diffSnapshotOf,
947
- now,
948
- }));
949
- stagePlanningArtifactTransition(ctx.cwd, result.record);
950
- return;
951
- }
952
- const advisories = input.verdict.decision === "pass" ? reviewAdvisoryRecords(input.verdict) : [];
953
- const approval: TaskApprovalV2 = {
954
- id: `approval-${input.snapshot.role}-${randomUUID().slice(0, 8)}`,
955
- kind: input.snapshot.role === "qa" ? "qa" : "review",
956
- authority_role: input.snapshot.role === "qa" ? "qa" : "reviewer",
957
- task_revision: input.snapshot.intent_revision,
958
- intent_content_hash: input.snapshot.intent_content_hash,
959
- diff_hash: input.snapshot.diff_hash,
960
- actor_id: input.actorId,
961
- summary: input.verdict.approval!.summary,
962
- ...(input.snapshot.role === "review" && input.snapshot.review_revision ? { review_revision: input.snapshot.review_revision } : {}),
963
- ...(input.snapshot.role === "review" && advisories.length > 0 ? { advisory_findings: advisories } : {}),
964
- };
965
- const capability = await mintCapability(registry, {
966
- authority_kind: input.snapshot.role,
967
- task_id: input.taskId,
968
- run_id: input.snapshot.run_id,
969
- action_kind: "record_approval",
970
- expected_record_hash: input.snapshot.record_revision,
971
- intent_revision: input.snapshot.intent_revision,
972
- intent_content_hash: input.snapshot.intent_content_hash,
973
- diff_hash: input.snapshot.diff_hash,
974
- actor_id: input.actorId,
975
- approval,
976
- now,
977
- confirmation_ref: `claude:${input.actorId}`,
978
- });
979
- await input.hooks?.beforeCommit?.();
980
- await commitAndApply(async () => app.execute({
981
- root: ctx.cwd,
982
- task_id: input.taskId,
983
- operation: { op: "record_approval", capability, approval, actor_id: input.actorId },
984
- prior_intent_token: priorIntentToken,
985
- diffProvider: diffSnapshotOf,
986
- now,
987
- }));
988
- }
989
-
990
744
  private async executeOrdinary(ctx: HostContext, input: { taskId: string; operation: { op: string; actor_id: string; next_intent?: unknown; finding_id?: string; attestation_id?: string } }) {
991
745
  const { app } = await this.authority();
992
746
  const operation = input.operation.op === "revise_intent"
@@ -1019,7 +773,7 @@ export class ClaudeRuntime {
1019
773
  return result;
1020
774
  } catch (error) {
1021
775
  if (priorStaged) {
1022
- const current = await readTaskRecord(ctx.cwd, input.taskId);
776
+ const current = await readTaskRecordRaw(ctx.cwd, input.taskId);
1023
777
  if (current.record?.intent_snapshot.revision === priorIntent.intent.revision)
1024
778
  restoreStagedIntentShared(ctx.cwd, priorStaged);
1025
779
  }
@@ -1179,17 +933,30 @@ export class ClaudeRuntime {
1179
933
  now = new Date().toISOString();
1180
934
  const capability = this.batchRegistry.issue(binding, recoveryChildren as any, now);
1181
935
 
1182
- const basePort = this.createBatchKernelPort(this.batchRegistry, capability, binding);
1183
- const kernelPort: BatchRunnerKernelPort = {
1184
- ...basePort,
1185
- ownsTaskClaim: (taskId) => {
1186
- if (isResuming && taskId === readActiveClaimTaskId(this.cwd)) {
1187
- // Re-verify the CURRENT claim identity synchronously.
1188
- return isOwnBatchClaim(this.cwd, existingBatch!, taskId, batchBranch);
1189
- }
1190
- return basePort.ownsTaskClaim(taskId);
1191
- },
936
+ // review-batch-partial-port-fabricates-enrollment: the production child
937
+ // Kernel port is the shared runtime/unattended one; this Host supplies
938
+ // only its own advanceTask progression seam (D5).
939
+ const advanceTask: BatchRunnerKernelPort["advanceTask"] = async (root, taskId) => {
940
+ const result = await this.coordinator.advance(taskId, { cwd: root });
941
+ const facts = { diagnostics: result.diagnostics, environment_failure: result.environment_failure, recovery: result.recovery };
942
+ if (result.state === "completed") return { state: "completed", ...facts };
943
+ if (result.state === "stopped") return { state: "stopped", ...facts };
944
+ if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary, ...facts };
945
+ if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, ...facts };
946
+ if (result.state === "review_preparation_failed") return { state: result.state, operation: result.operation, operation_id: result.operation_id, reason: result.reason, ...facts };
947
+ if (result.state === "blocked") return { state: "blocked", reason: result.reason, ...facts };
948
+ return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
1192
949
  };
950
+ const kernelPort = createBatchKernelPort({
951
+ root: this.cwd,
952
+ enrollmentRegistry: this.enrollmentRegistry,
953
+ registry: this.batchRegistry,
954
+ capability,
955
+ binding,
956
+ advanceTask,
957
+ resume: { isResuming, existingBatch, batchBranch },
958
+ overrides: this.batchKernel,
959
+ });
1193
960
  const report = await startBatch({
1194
961
  root: this.cwd,
1195
962
  batch_id: batchId,
@@ -1215,80 +982,6 @@ export class ClaudeRuntime {
1215
982
  report,
1216
983
  };
1217
984
  }
1218
-
1219
- private createBatchKernelPort(
1220
- registry: BatchAuthorityRegistry,
1221
- capability: object,
1222
- binding: BatchAuthorizationBinding,
1223
- ): BatchRunnerKernelPort {
1224
- // review-batch-partial-port-fabricates-enrollment: construct real production port first, never fabricate stubs
1225
- const realPort: BatchRunnerKernelPort = {
1226
- enrollTask: async ({ root, task_id, batch }) => {
1227
- const now = new Date().toISOString();
1228
- const derived = deriveChildEnrollment(root, batch.registry, {
1229
- capability: batch.capability,
1230
- binding,
1231
- task_id,
1232
- expected_head: batch.binding.expected_head,
1233
- now,
1234
- });
1235
- const enrollmentCapability = this.enrollmentRegistry.issue(derived.binding);
1236
- const input = {
1237
- task_id,
1238
- intent_path: derived.binding.intent_path,
1239
- intent_revision: derived.binding.intent_revision,
1240
- preparation_digest: derived.binding.preparation_digest,
1241
- capability: enrollmentCapability,
1242
- capability_binding: derived.binding,
1243
- batch: {
1244
- registry: batch.registry,
1245
- capability: batch.capability,
1246
- binding,
1247
- expected_head: batch.binding.expected_head,
1248
- },
1249
- now,
1250
- };
1251
- const rehearsal = runEnrollmentRehearsal(root, input, enrollmentCapability, this.enrollmentRegistry);
1252
- if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready") {
1253
- throw new Error(`Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`);
1254
- }
1255
- // The enrollment itself is the effect; its record is re-read below.
1256
- enrollCanaryTask(root, input, this.enrollmentRegistry);
1257
- const recordRaw = readTaskRecordRaw(root, task_id);
1258
- return { record_revision: recordRaw.revision };
1259
- },
1260
- advanceTask: async (root, taskId) => {
1261
- const result = await this.coordinator.advance(taskId, { cwd: root });
1262
- const facts = { diagnostics: result.diagnostics, environment_failure: result.environment_failure, recovery: result.recovery };
1263
- if (result.state === "completed") return { state: "completed", ...facts };
1264
- if (result.state === "stopped") return { state: "stopped", ...facts };
1265
- if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary, ...facts };
1266
- if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, ...facts };
1267
- if (result.state === "review_preparation_failed") return { state: result.state, operation: result.operation, operation_id: result.operation_id, reason: result.reason, ...facts };
1268
- if (result.state === "blocked") return { state: "blocked", reason: result.reason, ...facts };
1269
- return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
1270
- },
1271
- projectTask: async (root, taskId) => {
1272
- const fresh = await projectAssurance(root, taskId, diffSnapshotOf);
1273
- if (!fresh.claim || fresh.projection.lifecycle !== "active" || fresh.error !== null) return fresh;
1274
- const { record, revision } = readTaskRecordRaw(root, taskId);
1275
- if (!record || revision !== fresh.projection.record_revision) throw new Error(`Kernel recovery projection changed for ${taskId}`);
1276
- return { ...fresh, ...batchQaFailureFacts(record) };
1277
- },
1278
- ownsTaskClaim: (taskId) => {
1279
- return registry.isChildConsumed(capability, taskId);
1280
- },
1281
- validateBatchAuthorization: (input) => {
1282
- return input.registry.inspect(input.capability, input.binding as never);
1283
- },
1284
- };
1285
-
1286
- if (!this.batchKernel) return realPort;
1287
- return {
1288
- ...realPort,
1289
- ...this.batchKernel,
1290
- };
1291
- }
1292
985
  }
1293
986
 
1294
987
  export interface ToolMeta {
@@ -74,6 +74,7 @@ export interface McpRuntimeOptions {
74
74
  cwd?: string;
75
75
  env?: Record<string, string | undefined>;
76
76
  ports?: AssuranceCoordinatorPorts;
77
+ authorityOverrides?: Partial<import("../assurance/verdict_authority").VerdictAuthority>;
77
78
  host?: ClaudeReviewHost;
78
79
  interactive?: boolean;
79
80
  requestConfirmation?: NativeConfirmationPort;
@@ -89,6 +90,7 @@ export function createMcpRuntime(options: McpRuntimeOptions = {}) {
89
90
  env: options.env ?? process.env,
90
91
  host,
91
92
  ports: options.ports,
93
+ authorityOverrides: options.authorityOverrides,
92
94
  interactive: options.interactive,
93
95
  requestConfirmation: options.requestConfirmation,
94
96
  batchKernel: options.batchKernel,