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
@@ -15,8 +15,10 @@ import {
15
15
  VerificationAbortedError,
16
16
  } from "./verification";
17
17
  import { createInvocationRegistry, type InvocationState, type InvocationToken } from "./invocations";
18
- import type { ReviewBundle, ReviewManifestV5, ReviewRevision } from "./review_evidence";
19
- import { buildRoleDelegationPacket } from "../role_prompt_bridge";
18
+ import type { ReviewBundle, ReviewManifestV5 } from "./review_evidence";
19
+ import { createVerdictAuthority, type VerdictAuthority } from "./verdict_authority";
20
+ import type { ConfirmationReferenceSource } from "./host_port";
21
+ import { buildRoleDelegationPacket, STATIC_REVIEW_RULES } from "../role_prompt_bridge";
20
22
  import type { AssuranceProjectionResult } from "../kernel/assurance_projection";
21
23
  import type { TaskIntentIdentityToken } from "../kernel/intent_token_registry";
22
24
  import type { AssuranceHostPort, HostReviewReservation } from "./host_port";
@@ -346,7 +348,7 @@ export type AssuranceSubmitReviewResult = AssuranceRecoveryFields & (
346
348
  | { state: "review_preparation_failed"; operation: "review"; operation_id: string; reason: string }
347
349
  | { state: "completed" }
348
350
  | { state: "settlement_unknown"; operation: "qa" | "review"; operation_id: string; reason: string }
349
- | { state: "blocked"; reason: string; code?: "verdict_invalid" });
351
+ | { state: "blocked"; reason: string; code?: "verdict_invalid"; recovery_action?: string });
350
352
 
351
353
  export type ActiveAssuranceState =
352
354
  | { state: "running"; operation: "qa"; operation_id: string; deadline_seconds: number }
@@ -355,51 +357,17 @@ export type ActiveAssuranceState =
355
357
 
356
358
  export interface AssuranceCoordinatorPorts {
357
359
  host: AssuranceHostPort;
360
+ confirmationReference: ConfirmationReferenceSource;
361
+ onReworkApplied?: (ctx: HostContext, taskId: string, findingsCount: number) => void;
358
362
  projectTask(root: string, taskId: string): Promise<AssuranceProjectionResult>;
359
363
  readTaskRecord(root: string, taskId: string): Promise<TaskRecordRead>;
360
364
  readTaskIntent(root: string, taskId: string): Promise<TaskIntentRead>;
361
- buildAssurance(
362
- root: string,
363
- taskId: string,
364
- role: AssuranceRole,
365
- projection: AssuranceProjectionResult,
366
- ): Promise<{
367
- snapshot: SnapshotDescriptor;
368
- descriptors: Map<string, VerificationDescriptor>;
369
- reviewBundle: ReviewBundle | null;
370
- reviewManifest?: ReviewManifestV5 | null;
371
- }>;
372
- /**
373
- * Publish and prove the deterministic task-scoped revision before QA runs, so
374
- * a preparation failure can never consume or fake a settled QA attestation.
375
- * Returns null for records still on the legacy v3 bundle path.
376
- */
377
- ensureReviewRevision?(
378
- root: string,
379
- taskId: string,
380
- projection: AssuranceProjectionResult,
381
- ): Promise<ReviewRevision | null>;
382
365
  runQa(
383
366
  snapshot: SnapshotDescriptor,
384
367
  descriptors: Map<string, VerificationDescriptor>,
385
368
  options: { signal?: AbortSignal; onProgress?: (progress: QaVerificationProgress) => void },
386
369
  ): Promise<AssuranceVerdict>;
387
370
  writeReviewEvidence(input: { snapshot: SnapshotDescriptor; evidence: ReviewBundle | ReviewManifestV5 }): { path: string; remove(): void };
388
- applyVerdict(
389
- ctx: HostContext,
390
- input: {
391
- taskId: string;
392
- snapshot: SnapshotDescriptor;
393
- verdict: AssuranceVerdict;
394
- invocation: InvocationToken;
395
- actorId: string;
396
- hooks?: {
397
- beforeCommit?: () => Promise<void>;
398
- onCommit?: () => void;
399
- afterCommit?: () => Promise<void>;
400
- };
401
- },
402
- ): Promise<void>;
403
371
  applyOrdinaryOperation(ctx: HostContext, input: { taskId: string; operation: { op: string; actor_id: string } }): Promise<unknown>;
404
372
  advanceBeforeProjection?: () => Promise<void>;
405
373
  qaBeforeProjection?: () => Promise<void>;
@@ -478,19 +446,19 @@ export function snapshotDigest(snapshot: SnapshotDescriptor): string {
478
446
  return `sha256:${createHash("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
479
447
  }
480
448
 
481
- export function buildReviewPrompt(snapshot: SnapshotDescriptor, evidencePath?: string): string {
449
+ /**
450
+ * The per-dispatch Review facts: which evidence to read, which revision it pins,
451
+ * what the acceptances assert, and the exact verdict shapes.
452
+ *
453
+ * This is the whole prompt for a Host whose reviewer definition already carries
454
+ * the role contract and `STATIC_REVIEW_RULES` — the Claude Host. A Host whose
455
+ * dispatched agent carries no instructions (Pi) gets `buildReviewPrompt`, which
456
+ * prepends both.
457
+ */
458
+ export function buildReviewSnapshotPrompt(snapshot: SnapshotDescriptor, evidencePath?: string): string {
482
459
  if (snapshot.role !== "review") throw new Error("native review prompt requires review role");
483
460
  const acceptance = snapshot.acceptance.map((item) => `- ${item.id}: ${item.assertion}`).join("\n");
484
461
  const digest = snapshotDigest(snapshot);
485
- const rolePacket = buildRoleDelegationPacket({
486
- role: "code-review",
487
- context: {
488
- task_id: snapshot.task_id,
489
- review_gate: "imm-code-review",
490
- changed_files_signature: snapshot.diff_hash,
491
- snapshot_digest: digest,
492
- },
493
- });
494
462
  const revision = snapshot.review_revision;
495
463
  const evidenceContract = revision
496
464
  ? [
@@ -506,25 +474,44 @@ export function buildReviewPrompt(snapshot: SnapshotDescriptor, evidencePath?: s
506
474
  `The user-selected worktree may contain staged task changes that are absent from the isolated reviewer worktree. Review authority is bound only to the bundle dirty_files current_content bytes and committed HEAD provenance. Analyze code exclusively from those bundle bytes; repository file reads are permitted only for the provenance git commands above. A symbol present in current_content but absent from HEAD is the task change, not an absence.`,
507
475
  ];
508
476
  return [
509
- rolePacket.prompt,
510
477
  ...evidenceContract,
511
- `Do not edit files, create files, run mutating commands, or change Git state. Focus on correctness, regressions, security, and missing tests.`,
512
- `Execution outcomes for every acceptance were verified deterministically by the Kernel QA layer before this review and are embedded in this bundle under outcomes (the immutable evidence file, acceptance_id -> {status, summary}); do not re-execute descriptors and do not treat the absence of local test runs as a finding. Your review covers evidence provenance, code correctness, regressions, security, and missing tests against the embedded assertions and code.`,
513
478
  `Snapshot digest: ${digest}`,
514
479
  `TaskRecord revision: ${snapshot.record_revision}`,
515
480
  revision
516
481
  ? `Intent revision ${snapshot.intent_revision} (hash ${snapshot.intent_content_hash}), diff ${snapshot.diff_hash}, review revision ${revision.review_commit} (base ${revision.base_head}, tree ${revision.review_tree}, manifest ${revision.manifest_digest}), state ${snapshot.lifecycle}:${snapshot.artifact_state}.`
517
482
  : `Intent revision ${snapshot.intent_revision} (hash ${snapshot.intent_content_hash}), diff ${snapshot.diff_hash}, review bundle ${snapshot.review_bundle_digest}, state ${snapshot.lifecycle}:${snapshot.artifact_state}.`,
518
483
  "Acceptance assertions:", acceptance,
519
- "Reserve the final turn for exactly one strict JSON verdict. Reply with ONLY that object, without markdown fences or commentary.",
520
- `Every rework finding must carry machine-checkable provenance: evidence.trigger (the concrete inputs or state that reach the defect), a non-empty evidence.caller_chain (ordered repository paths or symbols), and evidence.violated {kind: "acceptance"|"security_boundary", ref}. The anchor is derived from that evidence; a finding without it is rejected and the correction must be resubmitted.`,
521
- `PASS shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"pass","approval":{"kind":"review","authority_role":"reviewer","summary":"<one line>"}}`,
522
- `A pass verdict may carry non-blocking notes as findings, but every one of them must set kind "advisory"; a blocking finding is a rework verdict and must omit approval: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"pass","approval":{"kind":"review","authority_role":"reviewer","summary":"<one line>"},"findings":[{"id":"review-1","kind":"advisory","acceptance_id":"<id|null>","summary":"<one line>","evidence":{"trigger":"<concrete inputs or state>","caller_chain":["<path-or-symbol>"],"violated":{"kind":"acceptance|security_boundary","ref":"<acceptance id or boundary>"}}}]}`,
484
+ `PASS shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"pass","approval":{"kind":"review","authority_role":"reviewer","summary":"<one line>","inspected_paths":["<every changed path of the reviewed change set>"]}}`,
485
+ `A pass verdict may carry non-blocking notes as findings, but every one of them must set kind "advisory"; a blocking finding is a rework verdict and must omit approval: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"pass","approval":{"kind":"review","authority_role":"reviewer","summary":"<one line>","inspected_paths":["<every changed path of the reviewed change set>"]},"findings":[{"id":"review-1","kind":"advisory","acceptance_id":"<id|null>","summary":"<one line>","evidence":{"trigger":"<concrete inputs or state>","caller_chain":["<path-or-symbol>"],"violated":{"kind":"acceptance|security_boundary","ref":"<acceptance id or boundary>"}}}]}`,
523
486
  `REWORK shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"rework","findings":[{"id":"review-1","kind":"blocking|advisory","acceptance_id":"<id|null>","summary":"<one line>","evidence":{"trigger":"<concrete inputs or state>","caller_chain":["<path-or-symbol>"],"violated":{"kind":"acceptance|security_boundary","ref":"<acceptance id or boundary>"}}}]}`,
524
487
  `REWORK verdicts must omit the approval field entirely; do not emit "approval": null.`,
525
488
  ].join("\n");
526
489
  }
527
490
 
491
+ /**
492
+ * The complete Review prompt: the internal role contract, the static review
493
+ * rules, and the per-dispatch facts. Dispatched by a Host whose agent carries no
494
+ * instructions of its own.
495
+ */
496
+ export function buildReviewPrompt(snapshot: SnapshotDescriptor, evidencePath?: string): string {
497
+ if (snapshot.role !== "review") throw new Error("native review prompt requires review role");
498
+ const digest = snapshotDigest(snapshot);
499
+ const rolePacket = buildRoleDelegationPacket({
500
+ role: "code-review",
501
+ context: {
502
+ task_id: snapshot.task_id,
503
+ review_gate: "imm-code-review",
504
+ changed_files_signature: snapshot.diff_hash,
505
+ snapshot_digest: digest,
506
+ },
507
+ });
508
+ return [
509
+ rolePacket.prompt,
510
+ ...STATIC_REVIEW_RULES,
511
+ buildReviewSnapshotPrompt(snapshot, evidencePath),
512
+ ].join("\n");
513
+ }
514
+
528
515
  function parseVerdictEvidence(value: unknown, index: number): FindingEvidence {
529
516
  if (!value || typeof value !== "object" || Array.isArray(value))
530
517
  throw new Error(`finding ${index} evidence is required`);
@@ -593,7 +580,28 @@ export function parseAssuranceVerdict(input: unknown, snapshot: SnapshotDescript
593
580
  const expectedKind = snapshot.role === "qa" ? "qa" : "review";
594
581
  const expectedRole = snapshot.role === "qa" ? "qa" : "reviewer";
595
582
  if (!approval || approval.kind !== expectedKind || approval.authority_role !== expectedRole || typeof approval.summary !== "string" || !approval.summary.trim()) throw new Error("pass verdict approval is invalid");
596
- const unknownApproval = Object.keys(approval).find((key) => !["kind", "authority_role", "summary"].includes(key));
583
+ // A Review pass must claim the paths it inspected: BR-DEC-3 converts silent
584
+ // partial coverage into an explicit false statement. The list is checked
585
+ // here against the reviewed change set and removed before settlement, so
586
+ // the Kernel approval and TaskRecord never see it. QA verdicts treat the
587
+ // field as unknown, exactly as before.
588
+ if (snapshot.role === "review") {
589
+ const required = snapshot.dirty_files;
590
+ const listed = approval.inspected_paths;
591
+ if (listed === undefined) throw new Error(`review pass verdict approval.inspected_paths is required; it must list every reviewed changed path${required.length ? `: ${required.join(", ")}` : " (none for this change set)"}`);
592
+ if (!Array.isArray(listed) || listed.some((entry) => typeof entry !== "string" || !entry.trim())) throw new Error("review pass verdict approval.inspected_paths must be an array of repository-relative path strings");
593
+ const seen = new Set<string>();
594
+ for (const entry of listed as string[]) {
595
+ if (seen.has(entry)) throw new Error(`review pass verdict approval.inspected_paths lists a duplicate path: ${entry}`);
596
+ seen.add(entry);
597
+ }
598
+ const foreign = (listed as string[]).filter((entry) => !required.includes(entry));
599
+ if (foreign.length) throw new Error(`review pass verdict approval.inspected_paths lists a path outside the reviewed change set: ${foreign.join(", ")}`);
600
+ const missing = required.filter((entry) => !seen.has(entry));
601
+ if (missing.length) throw new Error(`review pass verdict approval.inspected_paths omits reviewed changed paths: ${missing.join(", ")}`);
602
+ }
603
+ const allowedApproval = snapshot.role === "review" ? ["kind", "authority_role", "summary", "inspected_paths"] : ["kind", "authority_role", "summary"];
604
+ const unknownApproval = Object.keys(approval).find((key) => !allowedApproval.includes(key));
597
605
  if (unknownApproval) throw new Error(`pass verdict approval has unknown field: ${unknownApproval}`);
598
606
  const passFindings = raw.findings === undefined ? [] : parseVerdictFindings(raw.findings as unknown[], snapshot);
599
607
  if (passFindings.some((finding) => finding.kind !== "advisory")) throw new Error("pass verdict findings must all be advisory");
@@ -726,7 +734,14 @@ export class AssuranceCoordinator {
726
734
  private sessionActive = true;
727
735
  private sessionGeneration = 0;
728
736
 
729
- constructor(private readonly ports: AssuranceCoordinatorPorts) {}
737
+ readonly authority: VerdictAuthority;
738
+ constructor(private readonly ports: AssuranceCoordinatorPorts, authorityOverrides: Partial<VerdictAuthority> = {}) {
739
+ this.authority = { ...createVerdictAuthority({
740
+ confirmationReference: ports.confirmationReference,
741
+ commitInvocation: token => this.commitInvocation(token),
742
+ onReworkApplied: ports.onReworkApplied,
743
+ }), ...authorityOverrides };
744
+ }
730
745
 
731
746
  onSessionStart(): void {
732
747
  this.sessionActive = true;
@@ -973,9 +988,9 @@ export class AssuranceCoordinator {
973
988
  progress("preparing_review_revision", "Proving the immutable Review revision before QA");
974
989
  try {
975
990
  if (parked.record?.contract === "assurance_kernel/task_record/v4") {
976
- if (!this.ports.ensureReviewRevision)
991
+ if (!this.authority.ensureReviewRevision)
977
992
  throw new Error("v4 Review revision preparation is unavailable");
978
- await this.ports.ensureReviewRevision(ctx.cwd, taskId, projection);
993
+ await this.authority.ensureReviewRevision(ctx.cwd, taskId, projection);
979
994
  }
980
995
  } catch (error) {
981
996
  return { state: "blocked", reason: `review revision preparation failed: ${boundedAssuranceError(error)}` };
@@ -989,7 +1004,7 @@ export class AssuranceCoordinator {
989
1004
  progress("capturing_snapshot", "Capturing the immutable QA snapshot");
990
1005
  await this.ports.qaBeforeProjection?.();
991
1006
  ensureOperationLive();
992
- const assurance = await this.ports.buildAssurance(ctx.cwd, taskId, "qa", projection);
1007
+ const assurance = await this.authority.buildAssurance(ctx.cwd, taskId, "qa", projection);
993
1008
  ensureOperationLive();
994
1009
  // Preparation counts against the same ceiling as the checks, so the
995
1010
  // aggregate clock starts before the first prepared group runs. A host
@@ -1027,7 +1042,7 @@ export class AssuranceCoordinator {
1027
1042
  try {
1028
1043
  progress("settling_qa", "Settling deterministic QA through the Kernel revision boundary");
1029
1044
  authorityBoundaryStarted = true;
1030
- await this.ports.applyVerdict(ctx, {
1045
+ await this.authority.applyVerdict(ctx, {
1031
1046
  taskId,
1032
1047
  snapshot: assurance.snapshot,
1033
1048
  verdict: qaVerdict,
@@ -1090,10 +1105,10 @@ export class AssuranceCoordinator {
1090
1105
  authorityBoundaryStarted = false;
1091
1106
  if (aborted()) return this.reviewPreparationFailed(taskId, operationId, "host cancellation after QA authority settlement");
1092
1107
  progress("preparing_review", "Preparing the reserved foreground Review evidence");
1093
- let review: Awaited<ReturnType<AssuranceCoordinatorPorts["buildAssurance"]>>;
1108
+ let review: Awaited<ReturnType<VerdictAuthority["buildAssurance"]>>;
1094
1109
  let evidence: { path: string; remove(): void };
1095
1110
  try {
1096
- review = await this.ports.buildAssurance(ctx.cwd, taskId, "review", fresh);
1111
+ review = await this.authority.buildAssurance(ctx.cwd, taskId, "review", fresh);
1097
1112
  const payload = review.reviewManifest ?? review.reviewBundle;
1098
1113
  if (!payload) throw new Error("review evidence is missing after QA settlement");
1099
1114
  evidence = this.ports.writeReviewEvidence({ snapshot: review.snapshot, evidence: payload });
@@ -1116,6 +1131,7 @@ export class AssuranceCoordinator {
1116
1131
  taskId,
1117
1132
  operationId,
1118
1133
  prompt: buildReviewPrompt(review.snapshot, evidence.path),
1134
+ snapshotPrompt: buildReviewSnapshotPrompt(review.snapshot, evidence.path),
1119
1135
  evidencePath: evidence.path,
1120
1136
  maxTurns: reviewTurnBudget(workload),
1121
1137
  });
@@ -1202,9 +1218,9 @@ export class AssuranceCoordinator {
1202
1218
  }
1203
1219
  if (reservation.snapshot.review_revision) {
1204
1220
  try {
1205
- if (!this.ports.ensureReviewRevision)
1221
+ if (!this.authority.ensureReviewRevision)
1206
1222
  throw new Error("v4 Review revision verification is unavailable");
1207
- const revision = await this.ports.ensureReviewRevision(ctx.cwd, taskId, fresh);
1223
+ const revision = await this.authority.ensureReviewRevision(ctx.cwd, taskId, fresh);
1208
1224
  if (!revision || revision.base_head !== reservation.snapshot.review_revision.base_head || revision.review_commit !== reservation.snapshot.review_revision.review_commit || revision.review_tree !== reservation.snapshot.review_revision.review_tree || revision.manifest_digest !== reservation.snapshot.review_revision.manifest_digest)
1209
1225
  throw new Error("Review revision changed before submission");
1210
1226
  } catch (error) {
@@ -1220,7 +1236,7 @@ export class AssuranceCoordinator {
1220
1236
  const invocation = this.openInvocation(taskId);
1221
1237
  this.releaseReviewReservation(taskId, reservation);
1222
1238
  try {
1223
- await this.ports.applyVerdict(ctx, {
1239
+ await this.authority.applyVerdict(ctx, {
1224
1240
  taskId,
1225
1241
  snapshot: reservation.snapshot,
1226
1242
  verdict,
@@ -1,7 +1,27 @@
1
+ import type { SnapshotDescriptor } from "./coordinator";
2
+
3
+ export type ConfirmationReferenceSource = (input: {
4
+ snapshot: SnapshotDescriptor;
5
+ actorId: string;
6
+ now: string;
7
+ }) => string;
8
+
1
9
  export interface ReviewRequest {
2
10
  taskId: string;
3
11
  operationId: string;
12
+ /**
13
+ * The complete Review prompt: internal role contract, static review rules and
14
+ * the per-dispatch facts. Dispatched by a Host whose reviewer agent carries no
15
+ * instructions of its own.
16
+ */
4
17
  prompt: string;
18
+ /**
19
+ * The per-dispatch facts alone, a strict suffix of `prompt`. A Host whose
20
+ * reviewer definition already carries the role contract and the static rules
21
+ * dispatches only this, so the static instructions stay generated from one
22
+ * source instead of being sent twice.
23
+ */
24
+ snapshotPrompt: string;
5
25
  evidencePath: string;
6
26
  maxTurns: number;
7
27
  }
@@ -8,14 +8,14 @@ import { tmpdir } from "node:os";
8
8
  import { join, sep } from "node:path";
9
9
  import { qaFindingId } from "./qa_findings";
10
10
  import { captureGitTaskRevisionSnapshot } from "../workspace_scope";
11
- import { readTaskRecord } from "../kernel/storage";
11
+ import { readTaskRecordRaw } from "../kernel/storage";
12
12
 
13
13
  function deliveryTreeForSnapshot(
14
14
  snapshot: SnapshotDescriptor,
15
15
  writeTree: typeof writeDeliveryTree,
16
16
  ): string {
17
17
  if (snapshot.review_revision?.review_tree) return snapshot.review_revision.review_tree;
18
- const record = readTaskRecord(snapshot.root, snapshot.task_id).record;
18
+ const record = readTaskRecordRaw(snapshot.root, snapshot.task_id).record;
19
19
  if (!record || record.contract !== "assurance_kernel/task_record/v4" || !record.git_base_head)
20
20
  throw new Error("QA delivery requires a TaskRecord v4 git_base_head");
21
21
  const captured = captureGitTaskRevisionSnapshot(