immune-brain 3.6.8 → 4.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 (82) hide show
  1. package/README.md +11 -4
  2. package/README.zh-CN.md +10 -3
  3. package/package.json +3 -2
  4. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +18 -2
  6. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -121
  7. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +106 -600
  8. package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +1 -0
  9. package/plugins/immune-brain/.pi-extension/pi-canary-verification.ts +3 -3
  10. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +17 -43
  11. package/plugins/immune-brain/dist/claude/mcp-server.mjs +7581 -5042
  12. package/plugins/immune-brain/dist/docs/reference/planning-artifact-retention.md +11 -12
  13. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
  14. package/plugins/immune-brain/dist/imm-loop.md +27 -25
  15. package/plugins/immune-brain/dist/imm-planner.md +53 -32
  16. package/plugins/immune-brain/dist/imm-review-retro.md +123 -0
  17. package/plugins/immune-brain/dist/registry.yaml +9 -0
  18. package/plugins/immune-brain/dist/role-prompts/code-review.md +3 -1
  19. package/plugins/immune-brain/dist/role-prompts/executor.md +4 -4
  20. package/plugins/immune-brain/runtime/assurance/coordinator.ts +183 -40
  21. package/plugins/immune-brain/runtime/assurance/delivery_workspace.ts +240 -0
  22. package/plugins/immune-brain/runtime/assurance/qa.ts +132 -58
  23. package/plugins/immune-brain/runtime/assurance/review_evidence.ts +15 -7
  24. package/plugins/immune-brain/runtime/assurance/verification.ts +246 -206
  25. package/plugins/immune-brain/runtime/authorization_operation.ts +20 -0
  26. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +288 -721
  27. package/plugins/immune-brain/runtime/commands/kernel.ts +158 -67
  28. package/plugins/immune-brain/runtime/github_issue_tracker.ts +254 -29
  29. package/plugins/immune-brain/runtime/kernel/actor_identity.ts +33 -0
  30. package/plugins/immune-brain/runtime/kernel/application.ts +22 -6
  31. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +94 -5
  32. package/plugins/immune-brain/runtime/kernel/authority_port.ts +27 -6
  33. package/plugins/immune-brain/runtime/kernel/backend_claim.ts +43 -16
  34. package/plugins/immune-brain/runtime/kernel/batch_authority.ts +10 -6
  35. package/plugins/immune-brain/runtime/kernel/canary_application.ts +50 -63
  36. package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +13 -4
  37. package/plugins/immune-brain/runtime/kernel/completion.ts +5 -14
  38. package/plugins/immune-brain/runtime/kernel/enrollment.ts +124 -34
  39. package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +13 -5
  40. package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
  41. package/plugins/immune-brain/runtime/kernel/intent.ts +7 -11
  42. package/plugins/immune-brain/runtime/kernel/legacy_audit.ts +4 -1
  43. package/plugins/immune-brain/runtime/kernel/legacy_task_record.ts +323 -0
  44. package/plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts +10 -1
  45. package/plugins/immune-brain/runtime/kernel/reducer.ts +32 -31
  46. package/plugins/immune-brain/runtime/kernel/run_identity.ts +121 -0
  47. package/plugins/immune-brain/runtime/kernel/spec_binding.ts +100 -0
  48. package/plugins/immune-brain/runtime/kernel/sqlite_migration.ts +950 -0
  49. package/plugins/immune-brain/runtime/kernel/sqlite_store.ts +1193 -0
  50. package/plugins/immune-brain/runtime/kernel/storage.ts +1254 -1206
  51. package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +129 -755
  52. package/plugins/immune-brain/runtime/kernel/storage_paths.ts +419 -46
  53. package/plugins/immune-brain/runtime/kernel/types.ts +12 -43
  54. package/plugins/immune-brain/runtime/kernel/validation.ts +60 -274
  55. package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +0 -1
  56. package/plugins/immune-brain/runtime/plan_core.ts +27 -65
  57. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  58. package/plugins/immune-brain/runtime/prompts/code-review.md +3 -1
  59. package/plugins/immune-brain/runtime/prompts/executor.md +4 -4
  60. package/plugins/immune-brain/runtime/staged_intent.ts +58 -0
  61. package/plugins/immune-brain/runtime/unattended/batch_git.ts +37 -7
  62. package/plugins/immune-brain/runtime/unattended/batch_plan.ts +42 -2
  63. package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +771 -0
  64. package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +189 -0
  65. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +35 -0
  66. package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +33 -0
  67. package/plugins/immune-brain/runtime/unattended/types.ts +14 -1
  68. package/plugins/immune-brain/runtime/v4_runtime.ts +19 -23
  69. package/plugins/immune-brain/runtime/verification_descriptor.ts +92 -136
  70. package/plugins/immune-brain/runtime/workspace_scope.ts +98 -13
  71. package/plugins/immune-brain/skills/imm-planner/SKILL.md +3 -3
  72. package/plugins/immune-brain/skills/imm-review-retro/SKILL.md +23 -0
  73. package/plugins/immune-brain/skills/imm-review-retro/scripts/review_retro.ts +355 -0
  74. package/plugins/immune-brain/skills/registry.yaml +9 -0
  75. package/plugins/immune-brain/bin/imm-retire-stale-wrapper +0 -4
  76. package/plugins/immune-brain/bin/imm-retired +0 -4
  77. package/plugins/immune-brain/runtime/authority_commit_receipts.ts +0 -716
  78. package/plugins/immune-brain/runtime/kernel/automatic_observations.ts +0 -451
  79. package/plugins/immune-brain/runtime/kernel/legacy.ts +0 -299
  80. package/plugins/immune-brain/runtime/kernel/observation.ts +0 -397
  81. package/plugins/immune-brain/runtime/kernel/readiness.ts +0 -282
  82. package/plugins/immune-brain/runtime/kernel/readiness_evidence.ts +0 -132
package/README.md CHANGED
@@ -25,7 +25,7 @@ Pi and Claude Code are the supported hosts. Undeclared adapters remain unsupport
25
25
  - [Installation](#installation)
26
26
  - [Quick Start](#quick-start)
27
27
  - [How to Use](#how-to-use)
28
- - [The 6 Skills](#the-6-skills)
28
+ - [The 7 Skills](#the-7-skills)
29
29
  - [Lifecycle](#lifecycle)
30
30
  - [Unattended Batch Runs](#unattended-batch-runs)
31
31
  - [Configuration](#configuration)
@@ -119,6 +119,7 @@ Immune-Brain provides two clean modes: **Host-native** for daily coding, and **M
119
119
  | PR has review comments or failing CI | `/imm-pr-fix` on that PR | → Standalone repair: minimal scoped fix in place, no managed task created |
120
120
  | Project docs out of date | `/imm-doc-prune` | → Read-only audit; deletes only user-approved stale docs from manifest |
121
121
  | Agent instructions bloated | `/imm-agent-doc-maintain` | → Minimizes tracked `AGENTS.md` / `CLAUDE.md` to essential non-discoverable rules |
122
+ | Which model's edits keep coming back for review | `/imm-review-retro` | → Ranks models by review load and reports project usage from session logs |
122
123
 
123
124
  > **Core Principle: Skill-Explicit Entry**
124
125
  > - **Ordinary input stays host-native**: Natural language queries never automatically start planning or task enrollment. You choose when to turn on engineering rigor.
@@ -126,7 +127,7 @@ Immune-Brain provides two clean modes: **Host-native** for daily coding, and **M
126
127
 
127
128
  ---
128
129
 
129
- ## The 6 Skills
130
+ ## The 7 Skills
130
131
 
131
132
  | Skill | Type | When to use | What it does |
132
133
  |---|---|---|---|
@@ -136,10 +137,11 @@ Immune-Brain provides two clean modes: **Host-native** for daily coding, and **M
136
137
  | `imm-pr-fix` | Standalone | CI failed / review comments on a PR | Repairs one PR in place, no managed authority |
137
138
  | `imm-doc-prune` | Standalone | Stale current docs | Deletes only the hash-approved manifest entries |
138
139
  | `imm-agent-doc-maintain` | Standalone | Bloated agent instructions | Minimizes tracked AGENTS/CLAUDE/GEMINI.md to necessary context |
140
+ | `imm-review-retro` | Standalone | Compare models by review load | Ranks authors of reviewed code and reports project usage |
139
141
 
140
142
  Internal roles (Executor, QA, Review, Compounder) are dispatched by `imm-loop` — you never invoke them directly.
141
143
 
142
- All 6 skills are invoked explicitly. For new features, start with `imm-brainstorm` (if requirements are uncertain) or `imm-planner` (if requirements are clear), then proceed to `imm-loop` once enrolled.
144
+ All 7 skills are invoked explicitly. For new features, start with `imm-brainstorm` (if requirements are uncertain) or `imm-planner` (if requirements are clear), then proceed to `imm-loop` once enrolled.
143
145
 
144
146
  ### Managed Path entries (brainstorm → planner → loop)
145
147
 
@@ -187,6 +189,11 @@ The three repair/maintenance skills are host-native: they never create a managed
187
189
  - **Trigger:** explicit request to minimize tracked `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`.
188
190
  - **What it does:** keeps only the necessary non-discoverable rules in agent instruction files, under the same read-only-audit + hash-bound-manifest-approval model as `imm-doc-prune`.
189
191
 
192
+ #### `imm-review-retro` — review load and project usage
193
+
194
+ - **Trigger:** explicit request for a cross-model review retro or project usage look-back.
195
+ - **What it does:** ranks models by how much review their own edits triggered, plus sessions/turns/edits/tool mix, from pi session logs. Read-only. Not a diff review.
196
+
190
197
  ---
191
198
 
192
199
  ## Lifecycle
@@ -265,7 +272,7 @@ See [`docs/reference/immune-brain-config.md`](docs/reference/immune-brain-config
265
272
  package.json # Pi package manifest (skills + extensions)
266
273
  plugins/immune-brain/
267
274
  ├── .pi-extension/ # Pi TUI + Kernel authority extension
268
- ├── skills/ # 6 public Skills (trigger shims)
275
+ ├── skills/ # 7 public Skills (trigger shims)
269
276
  ├── dist/ # Built skill contracts & references
270
277
  ├── runtime/ # Bun + TypeScript runtime & Kernel
271
278
  └── bin/ # CLI wrappers (→ runtime/v4_runtime.ts)
package/README.zh-CN.md CHANGED
@@ -25,7 +25,7 @@ Pi 与 Claude Code 是支持的宿主。未声明的适配器仍不受支持。C
25
25
  - [安装](#安装)
26
26
  - [快速开始](#快速开始)
27
27
  - [如何使用](#如何使用)
28
- - [6 个 Skills](#6-个-skills)
28
+ - [7 个 Skills](#7-个-skills)
29
29
  - [生命周期](#生命周期)
30
30
  - [无人值守批次运行](#无人值守批次运行)
31
31
  - [配置](#配置)
@@ -119,6 +119,7 @@ Immune-Brain 提供两种清晰的工作模式:日常轻量编码走 **Host-na
119
119
  | PR 被评论 / CI 挂了 | 对该 PR 使用 `/imm-pr-fix` | → 独立修复:在当前 PR 内针对性修复,不创建新 managed 任务 |
120
120
  | 文档过时需要清理 | `/imm-doc-prune` | → 只读审计过时文档,仅删除经哈希审批的条目 |
121
121
  | Agent 指令文件膨胀 | `/imm-agent-doc-maintain` | → 将 tracked `AGENTS.md` / `CLAUDE.md` 压到最小必要上下文 |
122
+ | 想知道哪个模型的改动总被审查 | `/imm-review-retro` | → 按模型排名审查负载,并从 session logs 汇报项目使用量 |
122
123
 
123
124
  > **核心原则:Skill 显式调用**
124
125
  > - **普通输入保持 Host-native**:自然语言提问绝不自动绑架流程或发起 Enrollment。你完全自主决定何时开启严格工程保障。
@@ -126,7 +127,7 @@ Immune-Brain 提供两种清晰的工作模式:日常轻量编码走 **Host-na
126
127
 
127
128
  ---
128
129
 
129
- ## 6 个 Skills
130
+ ## 7 个 Skills
130
131
 
131
132
  | Skill | 类型 | 何时使用 | 职责 |
132
133
  |---|---|---|---|
@@ -136,10 +137,11 @@ Immune-Brain 提供两种清晰的工作模式:日常轻量编码走 **Host-na
136
137
  | `imm-pr-fix` | 独立 | PR 需修复 | 原地修复单个 PR,不触及 managed authority |
137
138
  | `imm-doc-prune` | 独立 | 清理过时文档 | 仅删除哈希绑定的 manifest 条目 |
138
139
  | `imm-agent-doc-maintain` | 独立 | Agent instruction 膨胀 | 将 tracked AGENTS/CLAUDE/GEMINI.md 压到最小必要上下文 |
140
+ | `imm-review-retro` | 独立 | 比较模型的审查负载 | 排名被审查代码的作者并汇报项目使用量 |
139
141
 
140
142
  Executor、QA、Review、Compounder 等为 `imm-loop` 内部调度的角色,无需手动调用。
141
143
 
142
- 所有 6 个 Skill 均显式调用。新需求开发时:若需求含糊先调 `imm-brainstorm`,目标清晰直接调 `imm-planner`,完成确认后调 `imm-loop` 推进闭环。
144
+ 所有 7 个 Skill 均显式调用。新需求开发时:若需求含糊先调 `imm-brainstorm`,目标清晰直接调 `imm-planner`,完成确认后调 `imm-loop` 推进闭环。
143
145
 
144
146
  ### Managed Path 入口(brainstorm → planner → loop)
145
147
 
@@ -187,6 +189,11 @@ Executor、QA、Review、Compounder 等为 `imm-loop` 内部调度的角色,
187
189
  - **触发方式:** 显式要求精简版本控制下的 `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`。
188
190
  - **职责:** 遵循与 `imm-doc-prune` 相同的「只读审计 + 哈希清单审批」模式,仅保留无法直接推导的必要规则。
189
191
 
192
+ #### `imm-review-retro` — 审查负载与项目使用回顾
193
+
194
+ - **触发方式:** 显式要求跨模型审查复盘或项目使用量回顾。
195
+ - **职责:** 从 pi session logs 按模型排名被审查代码的作者,并汇报 sessions/turns/编辑量/工具分布。只读;不审查 diff。
196
+
190
197
  ---
191
198
 
192
199
  ## 生命周期
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "3.6.8",
3
+ "version": "4.0.0",
4
4
  "description": "Immune-Brain agent skill system",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -72,9 +72,10 @@
72
72
  "plugins/immune-brain/runtime/plan_core.ts",
73
73
  "plugins/immune-brain/runtime/commands/kernel.ts",
74
74
  "plugins/immune-brain/runtime/kernel",
75
+ "plugins/immune-brain/runtime/authorization_operation.ts",
75
76
  "plugins/immune-brain/runtime/canonical_json.ts",
77
+ "plugins/immune-brain/runtime/staged_intent.ts",
76
78
  "plugins/immune-brain/runtime/workspace_scope.ts",
77
- "plugins/immune-brain/runtime/authority_commit_receipts.ts",
78
79
  "plugins/immune-brain/runtime/managed_task_routing_policy.ts",
79
80
  "plugins/immune-brain/runtime/github_issue_tracker.ts",
80
81
  "plugins/immune-brain/runtime/verification_descriptor.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "3.6.8",
3
+ "version": "4.0.0",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -14,6 +14,7 @@ import { resolve } from "node:path";
14
14
  // can resolve them at runtime), and the stub forwards to the real Kernel
15
15
  // modules via dynamic import.
16
16
  import {
17
+ LITERAL_USER_ACTOR_ID,
17
18
  createEnrollmentAuthorityRegistry,
18
19
  preparePiCanary,
19
20
  revalidatePiCanary,
@@ -408,7 +409,7 @@ async function executeForegroundEnrollment(
408
409
  `Terminal task ${authority.owner_task_id} retains an exactly proven stale backend claim`,
409
410
  `invoke imm_kernel_canary repair_authority_state for ${authority.owner_task_id}`,
410
411
  );
411
- if (authority.state === "authority_conflict" || authority.state === "terminal_owner")
412
+ if (authority.state === "authority_conflict")
412
413
  return terminal(
413
414
  action,
414
415
  taskId,
@@ -417,6 +418,21 @@ async function executeForegroundEnrollment(
417
418
  authority.diagnostic ?? `Kernel authority state is ${authority.state}`,
418
419
  "inspect authority state; do not retry enrollment",
419
420
  );
421
+ // terminal_owner with a local run is this worktree's settled task and
422
+ // forbids re-enrollment. terminal_owner *without* one is historical audit
423
+ // evidence (a fresh clone, or another worktree's run reaching here through
424
+ // Git): evidence is not local authority, and this worktree may enroll the
425
+ // task for the first time. The Kernel's own precondition check enforces
426
+ // the same rule against the local run.
427
+ if (authority.state === "terminal_owner" && authority.owner_run_id !== null)
428
+ return terminal(
429
+ action,
430
+ taskId,
431
+ "authority_conflict",
432
+ stage,
433
+ `task ${taskId} is terminal in this worktree`,
434
+ "inspect authority state; do not retry enrollment",
435
+ );
420
436
  const preparation = await preparePiCanary(root, { task_id: taskId, now });
421
437
  signal.throwIfAborted();
422
438
  if (!preparation.intent)
@@ -523,7 +539,7 @@ async function executeForegroundEnrollment(
523
539
  intent_revision: preparation.intent.revision,
524
540
  intent_content_hash: preparation.intent.content_hash,
525
541
  preparation_digest: preparation.digest,
526
- actor_id: "user",
542
+ actor_id: LITERAL_USER_ACTOR_ID,
527
543
  confirmation_ref: `pi-confirm-${createHash("sha256").update(`${taskId}\0${now}\0${nonce}`).digest("hex").slice(0, 16)}`,
528
544
  expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
529
545
  nonce,
@@ -23,11 +23,6 @@ import { Type } from "typebox";
23
23
  import { PLUGIN_VERSION } from "../runtime/plugin_version";
24
24
  import {
25
25
  parseVerificationDescriptor,
26
- canonicalDescriptorBytes,
27
- resolveBunRunner,
28
- assertRunnerCompatible,
29
- findingsDigest,
30
- type FrozenRunner,
31
26
  type VerificationDescriptor,
32
27
  } from "./pi-canary-verification";
33
28
  import {
@@ -43,9 +38,9 @@ import {
43
38
  import type { InvocationToken } from "./pi-canary-invocations";
44
39
  import { runDeterministicQa } from "../runtime/assurance/qa";
45
40
  import {
46
- reservedAgentParams,
47
- type ReservedAgentParams,
48
- } from "./pi-canary-native-review";
41
+ captureStagedIntent,
42
+ restoreStagedIntent as restoreStagedIntentShared,
43
+ } from "../runtime/staged_intent";
49
44
  import {
50
45
  renderCanaryCall,
51
46
  renderCanaryResult,
@@ -71,13 +66,13 @@ import {
71
66
  } from "./pi-canary-interaction";
72
67
  import { isToolFailureState, throwToolFailure, type ToolFailureV1 } from "./pi-canary-tool-failure";
73
68
  import { taskDiffIdentity, taskRevisionIdentity, captureGitTaskSnapshot } from "../runtime/workspace_scope";
74
- import { reviewReworkFindings } from "../runtime/assurance/coordinator";
69
+ import { reviewAdvisoryRecords, reviewReworkFindings } from "../runtime/assurance/coordinator";
75
70
  import {
76
71
  AssuranceProgression,
77
72
  buildReviewPrompt,
78
73
  classifyReviewWorkload,
79
74
  deriveQaJobTimeoutMs,
80
- deriveGithubTerminalProjectionInput,
75
+ projectTerminalTrackerState,
81
76
  parseAssuranceVerdict,
82
77
  snapshotDigest,
83
78
  QA_JOB_TIMEOUT_SECONDS,
@@ -85,12 +80,9 @@ import {
85
80
  REVIEW_PREPARATION_TIMEOUT_MS,
86
81
  REVIEW_TIMING_PROFILES,
87
82
  REVIEW_VERDICT_VALIDATION_TIMEOUT_MS,
88
- type AssuranceAdvanceResult,
89
83
  type AssuranceProgressionPorts,
90
- type AssuranceSubmitReviewResult,
91
84
  type AssuranceVerdict,
92
85
  type HostContext,
93
- type QaVerificationProgress,
94
86
  type SnapshotDescriptor,
95
87
  } from "./pi-canary-assurance-progression";
96
88
 
@@ -109,27 +101,25 @@ import {
109
101
  reconcileKernelAuthority,
110
102
  repairKernelAuthority,
111
103
  readTaskRecord,
112
- withKernelStoreLock,
104
+ withKernelStoreLockForTask,
113
105
  inspectStorageLayout,
114
106
  migrateLegacyLayout,
115
107
  readTaskIntent,
116
108
  parseTaskIntentV1,
117
109
  canonicalIntentHash,
118
110
  projectAssurance,
119
- deriveAssuranceAuthorization,
120
111
  findingsDigestV2,
121
112
  capabilityActionFor,
122
113
  digestOfAction,
123
- type AssuranceAuthorizationReadiness,
124
114
  type AssuranceProjectionResult,
125
115
  type TaskRecordRead,
126
116
  type CanaryApplication,
127
117
  type MutationAuthorityRegistry,
128
118
  type CapabilityBindingV2,
119
+ LITERAL_USER_ACTOR_ID,
129
120
  } from "./runtime-stub";
130
121
  import { invocationRegistry } from "./pi-canary-assurance-progression";
131
122
 
132
- const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
133
123
  const LOOP_OWNERS = ["plan", "kernel", "brainstorm", "planner", "loop"] as const;
134
124
  const LOOP_TARGETS = [
135
125
  "step",
@@ -186,6 +176,7 @@ export interface SnapshotDescriptorInput {
186
176
  root: string;
187
177
  task_id: string;
188
178
  role: AssuranceRole;
179
+ run_id?: string | null;
189
180
  record_revision: string;
190
181
  workspace_revision: string;
191
182
  intent_revision: number;
@@ -214,6 +205,7 @@ export function buildSnapshot(input: SnapshotDescriptorInput): SnapshotDescripto
214
205
  return {
215
206
  contract: "assurance_kernel/assurance_snapshot/v2",
216
207
  task_id: input.task_id,
208
+ run_id: input.run_id ?? null,
217
209
  role: input.role,
218
210
  record_revision: input.record_revision,
219
211
  workspace_revision: input.workspace_revision,
@@ -282,12 +274,11 @@ export function createPiAssuranceProgressionPorts(
282
274
  projectTask: (root, taskId) => projectAssuranceState(root, taskId),
283
275
  readTaskRecord: (root, taskId) => readTaskRecord(root, taskId),
284
276
  readTaskIntent: (root, taskId) => readTaskIntent(root, taskId),
285
- frozenRunner: () => frozenRunner(),
286
- buildAssurance: (root, taskId, role, projection, runner) =>
287
- (dependencies.buildAssurance ?? buildAssuranceSnapshot)(root, taskId, role, projection, runner),
277
+ buildAssurance: (root, taskId, role, projection) =>
278
+ (dependencies.buildAssurance ?? buildAssuranceSnapshot)(root, taskId, role, projection),
288
279
  ensureReviewRevision: (root, taskId, projection) => ensureTaskReviewRevision(root, taskId, projection),
289
- runQa: (snapshot, descriptors, runner, options) =>
290
- (dependencies.runQa ?? runDeterministicQa)(snapshot, descriptors, runner, options),
280
+ runQa: (snapshot, descriptors, options) =>
281
+ (dependencies.runQa ?? runDeterministicQa)(snapshot, descriptors, options),
291
282
  writeReviewEvidence: (input) =>
292
283
  (dependencies.writeReviewEvidence ?? writeNativeReviewEvidence)(input),
293
284
  applyVerdict: (ctx, input) =>
@@ -491,7 +482,9 @@ export default function (
491
482
  if (ctx.mode !== "tui")
492
483
  return failCanaryTool(taskId, action.op, "blocked", "tui_required", "Kernel mutation is TUI-only", "invoke the TUI Tool");
493
484
  try {
494
- await withKernelStoreLock(ctx.cwd, () => undefined);
485
+ // Recovery also retires a leftover derived claim/owner file whose
486
+ // task the store already holds: that file is inert, not authority.
487
+ await withKernelStoreLockForTask(ctx.cwd, taskId, () => undefined);
495
488
  } catch (error) {
496
489
  return failCanaryTool(taskId, action.op, "blocked", "layout_recovery_failed", `Kernel transaction recovery failed: ${error instanceof Error ? error.message : String(error)}`, "resolve the pending marker and retry");
497
490
  }
@@ -510,6 +503,25 @@ export default function (
510
503
  if (action.op === "repair_authority_state") {
511
504
  if (ctx.mode !== "tui") return failCanaryTool(taskId, action.op, "blocked", "tui_required", "imm_kernel_canary mutation is TUI-only", "invoke the TUI Tool");
512
505
  const authority = await reconcileKernelAuthority(ctx.cwd, taskId);
506
+ if (authority.state === "terminal_owner" || authority.state === "unowned") {
507
+ // Repair is unrepresentable-by-construction removal: a leftover
508
+ // retired claim file is inert, and the store already holds the
509
+ // settled answer. Report the authority and write nothing.
510
+ try {
511
+ const repaired = await repairKernelAuthority(ctx.cwd, taskId, authority.revision);
512
+ const result = {
513
+ state: "recovered_retry",
514
+ operation: action.op,
515
+ authority: repaired,
516
+ result: `No stale authority claim exists for ${taskId}; the store owns authority`,
517
+ next_action: "retry the blocked managed request once",
518
+ };
519
+ return toolResult(JSON.stringify(result, null, 2), result);
520
+ } catch (error) {
521
+ const message = error instanceof Error ? error.message : String(error);
522
+ return failCanaryTool(taskId, action.op, "blocked", "authority_repair_failed", message, "inspect the kernel store authority state and retry");
523
+ }
524
+ }
513
525
  if (
514
526
  authority.state !== "repairable_stale_claim" ||
515
527
  authority.owner_task_id !== taskId
@@ -762,38 +774,11 @@ export default function (
762
774
  const priorIntent = await readTaskIntent(ctx.cwd, taskId);
763
775
  const sidecar = nextIntent ? join(ctx.cwd, priorIntent.intent_ref.path) : undefined;
764
776
  const priorBytes = sidecar ? readFileSync(sidecar) : undefined;
765
- const priorIndexState = sidecar
766
- ? execFileSync("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
767
- cwd: ctx.cwd,
768
- stdio: ["ignore", "pipe", "pipe"],
769
- })
770
- : undefined;
777
+ const stagedSnapshot = sidecar ? captureStagedIntent(ctx.cwd, priorIntent.intent_ref.path) : undefined;
771
778
  let stagedNextDiffHash: string | undefined;
772
779
  const restoreStagedIntent = (): void => {
773
- if (!sidecar || !priorBytes || !priorIndexState) return;
774
- writeFileSync(sidecar, priorBytes);
775
- execFileSync("git", ["update-index", "--force-remove", "--", priorIntent.intent_ref.path], {
776
- cwd: ctx.cwd,
777
- stdio: ["ignore", "pipe", "pipe"],
778
- });
779
- if (priorIndexState.length > 0) {
780
- execFileSync("git", ["update-index", "-z", "--index-info"], {
781
- cwd: ctx.cwd,
782
- input: priorIndexState,
783
- stdio: ["pipe", "ignore", "pipe"],
784
- });
785
- }
786
- const restoredBytes = readFileSync(sidecar);
787
- if (!restoredBytes.equals(priorBytes)) {
788
- throw new Error("failed to restore prior intent bytes");
789
- }
790
- const restoredIndex = execFileSync("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
791
- cwd: ctx.cwd,
792
- stdio: ["ignore", "pipe", "pipe"],
793
- });
794
- if (!restoredIndex.equals(priorIndexState)) {
795
- throw new Error("failed to restore prior intent git index entry");
796
- }
780
+ if (!stagedSnapshot) return;
781
+ restoreStagedIntentShared(ctx.cwd, stagedSnapshot);
797
782
  };
798
783
  if (nextIntent) {
799
784
  try {
@@ -992,12 +977,13 @@ export default function (
992
977
  const capability = await mintCapability(registry, {
993
978
  authority_kind: "user",
994
979
  task_id: taskId,
980
+ run_id: projection.projection.run_id,
995
981
  action_kind: exactOperation.op,
996
982
  expected_record_hash: projection.projection.record_revision,
997
983
  intent_revision: nextIntent?.revision ?? projection.projection.intent_revision,
998
984
  intent_content_hash: nextIntentHash ?? projection.projection.intent_content_hash,
999
985
  diff_hash: operationDiffHash,
1000
- actor_id: "literal-user",
986
+ actor_id: LITERAL_USER_ACTOR_ID,
1001
987
  ...(exactOperation.op === "approve_breaking_intent_revision"
1002
988
  ? { next_intent: exactOperation.next_intent, next_intent_ref: exactOperation.next_intent_ref }
1003
989
  : {}),
@@ -1010,7 +996,7 @@ export default function (
1010
996
  const result = (await app.execute({
1011
997
  root: ctx.cwd,
1012
998
  task_id: taskId,
1013
- operation: { ...exactOperation, capability, actor_id: "literal-user" } as never,
999
+ operation: { ...exactOperation, capability, actor_id: LITERAL_USER_ACTOR_ID } as never,
1014
1000
  prior_intent_token: priorIntent.token,
1015
1001
  diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
1016
1002
  now,
@@ -1023,7 +1009,7 @@ export default function (
1023
1009
  ) stagePlanningArtifactTransition(ctx.cwd, result.record);
1024
1010
  return { state: "applied", operation, lifecycle: result.record.lifecycle };
1025
1011
  } catch (error) {
1026
- if (sidecar && priorBytes && priorIndexState) {
1012
+ if (stagedSnapshot) {
1027
1013
  const current = await readTaskRecord(ctx.cwd, taskId);
1028
1014
  if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) restoreStagedIntent();
1029
1015
  }
@@ -1069,19 +1055,14 @@ export default function (
1069
1055
  // Helpers (module scope; no workflow state)
1070
1056
  // ---------------------------------------------------------------------------
1071
1057
 
1072
- export type DerivedAuthorizationOperation =
1073
- | "resolve-user-decision"
1074
- | "authorize-rework";
1058
+ // The derivation is shared with the Claude adapter so it cannot drift between
1059
+ // Hosts; this module re-exports it for its existing callers and tests.
1060
+ import {
1061
+ deriveAuthorizationOperation,
1062
+ type DerivedAuthorizationOperation,
1063
+ } from "../runtime/authorization_operation";
1075
1064
 
1076
- // Kernel projection is the sole source of authorization readiness.
1077
- export function deriveAuthorizationOperation(input: {
1078
- readiness: AssuranceAuthorizationReadiness;
1079
- }): { operation: DerivedAuthorizationOperation } | { blocked: string } {
1080
- if (input.readiness.state === "resolve_user_decision") return { operation: "resolve-user-decision" };
1081
- if (input.readiness.state === "authorize_rework") return { operation: "authorize-rework" };
1082
- if (input.readiness.blocked) return { blocked: input.readiness.blocked };
1083
- return { blocked: "no unique host-derived authorization operation" };
1084
- }
1065
+ export { deriveAuthorizationOperation, type DerivedAuthorizationOperation };
1085
1066
 
1086
1067
  /**
1087
1068
  * Ordinary Kernel operation mapping, exported so the conformance suite can
@@ -1142,7 +1123,7 @@ export async function recordCancelledUserDecision(
1142
1123
  acceptance_id: null,
1143
1124
  summary: `${operation} confirmation cancelled by literal user; snapshot ${snapshotDigestRef}`,
1144
1125
  },
1145
- actor_id: "literal-user",
1126
+ actor_id: LITERAL_USER_ACTOR_ID,
1146
1127
  } as never,
1147
1128
  prior_intent_token: (await readTaskIntent(ctx.cwd, taskId)).token,
1148
1129
  diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
@@ -1178,9 +1159,9 @@ function diffSnapshotOf(root: string, record: NonNullable<TaskRecordRead["record
1178
1159
  if (record.contract === "assurance_kernel/task_record/v4") {
1179
1160
  if (!record.git_base_head)
1180
1161
  throw new Error("TaskRecord v4 is missing git_base_head");
1181
- return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head);
1162
+ return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head, record.task_id);
1182
1163
  }
1183
- return taskDiffIdentity(root, record.intent_snapshot.scope_hint);
1164
+ return taskDiffIdentity(root, record.intent_snapshot.scope_hint, record.task_id);
1184
1165
  }
1185
1166
 
1186
1167
  function diffHashOf(root: string, record: NonNullable<TaskRecordRead["record"]>): string {
@@ -1402,6 +1383,7 @@ async function applyAssuranceVerdict(
1402
1383
  const capability = await mintCapability(registry, {
1403
1384
  authority_kind: authorityKind,
1404
1385
  task_id: snapshot.task_id,
1386
+ run_id: snapshot.run_id,
1405
1387
  action_kind: "request_rework",
1406
1388
  expected_record_hash: snapshot.record_revision,
1407
1389
  intent_revision: snapshot.intent_revision,
@@ -1438,6 +1420,7 @@ async function applyAssuranceVerdict(
1438
1420
  return;
1439
1421
  }
1440
1422
  const now = new Date().toISOString();
1423
+ const advisories = verdict.decision === "pass" ? reviewAdvisoryRecords(verdict) : [];
1441
1424
  const approval = {
1442
1425
  id: `approval-${snapshot.role}-${randomUUID().slice(0, 8)}`,
1443
1426
  kind: snapshot.role === "qa" ? "qa" : "review",
@@ -1452,10 +1435,14 @@ async function applyAssuranceVerdict(
1452
1435
  ...(snapshot.role === "review" && snapshot.review_revision
1453
1436
  ? { review_revision: snapshot.review_revision }
1454
1437
  : {}),
1438
+ ...(snapshot.role === "review" && advisories.length > 0
1439
+ ? { advisory_findings: advisories }
1440
+ : {}),
1455
1441
  };
1456
1442
  const capability = await mintCapability(registry, {
1457
1443
  authority_kind: snapshot.role,
1458
1444
  task_id: snapshot.task_id,
1445
+ run_id: snapshot.run_id,
1459
1446
  action_kind: "record_approval",
1460
1447
  expected_record_hash: snapshot.record_revision,
1461
1448
  intent_revision: snapshot.intent_revision,
@@ -1481,7 +1468,6 @@ async function buildAssuranceSnapshot(
1481
1468
  taskId: string,
1482
1469
  role: AssuranceRole,
1483
1470
  projection: AssuranceProjectionResult,
1484
- runner: FrozenRunner,
1485
1471
  ): Promise<{
1486
1472
  snapshot: SnapshotDescriptor;
1487
1473
  descriptors: Map<string, VerificationDescriptor>;
@@ -1500,12 +1486,9 @@ async function buildAssuranceSnapshot(
1500
1486
  const intent = record.record.intent_snapshot;
1501
1487
  const acceptance = intent.acceptance;
1502
1488
  const descriptors = new Map<string, VerificationDescriptor>();
1503
- // Every verification string must parse as strict canonical JSON
1504
- // verification_descriptor/v1 and claim the frozen runner version;
1505
- // a free-form or version-mismatched string is ineligible.
1506
- for (const item of acceptance) {
1489
+ // Review resumes from settled outcomes without resolving verification tools.
1490
+ for (const item of role === "qa" ? acceptance : []) {
1507
1491
  const descriptor = parseVerificationDescriptor(item.verification);
1508
- assertRunnerCompatible(descriptor, runner);
1509
1492
  descriptors.set(item.id, descriptor);
1510
1493
  }
1511
1494
  // `git_base_head` is optional on the read shape because v3 records carry
@@ -1561,6 +1544,7 @@ async function buildAssuranceSnapshot(
1561
1544
  root,
1562
1545
  task_id: taskId,
1563
1546
  role,
1547
+ run_id: projection.projection.run_id,
1564
1548
  record_revision: projection.projection.record_revision,
1565
1549
  workspace_revision: projection.projection.workspace_revision,
1566
1550
  intent_revision: projection.projection.intent_revision,
@@ -1602,12 +1586,6 @@ function qaOutcomes(
1602
1586
  );
1603
1587
  }
1604
1588
 
1605
- let frozenRunnerValue: FrozenRunner | undefined;
1606
- async function frozenRunner(): Promise<FrozenRunner> {
1607
- frozenRunnerValue ??= resolveBunRunner();
1608
- return frozenRunnerValue;
1609
- }
1610
-
1611
1589
  // The invocation registry is shared with the progression module's
1612
1590
  // module-scoped registry (see the top-level import above); commit/cancel
1613
1591
  // semantics are identical to the previous extension implementation.
@@ -1617,6 +1595,7 @@ async function mintCapability(
1617
1595
  input: {
1618
1596
  authority_kind: "review" | "qa" | "user";
1619
1597
  task_id: string;
1598
+ run_id: string | null;
1620
1599
  action_kind: string;
1621
1600
  expected_record_hash: string;
1622
1601
  intent_revision: number;
@@ -1654,6 +1633,7 @@ async function mintCapability(
1654
1633
  const binding: CapabilityBindingV2 = {
1655
1634
  authority_kind: input.authority_kind,
1656
1635
  task_id: input.task_id,
1636
+ ...(input.run_id ? { run_id: input.run_id } : {}),
1657
1637
  action_digest: digest,
1658
1638
  expected_record_hash: input.expected_record_hash,
1659
1639
  intent_revision: input.intent_revision,
@@ -1698,13 +1678,8 @@ async function executeOrdinaryOperation(
1698
1678
  // A content-changing revision writes the sidecar before the kernel's drift
1699
1679
  // check runs; an unstaged write is itself scoped drift and deadlocks the
1700
1680
  // revision. Mirror the breaking-revision path: stage the written sidecar
1701
- // (worktree == index) and restore the exact prior index entry on failure.
1702
- const priorIndexState = priorBytes !== null
1703
- ? execFileSync("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
1704
- cwd: ctx.cwd,
1705
- stdio: ["ignore", "pipe", "pipe"],
1706
- })
1707
- : null;
1681
+ // (worktree == index) and restore the exact prior state on failure.
1682
+ const priorStaged = priorBytes !== null ? captureStagedIntent(ctx.cwd, priorIntent.intent_ref.path) : null;
1708
1683
  try {
1709
1684
  if (priorBytes) {
1710
1685
  writeFileSync(sidecar, `${JSON.stringify(operation.next_intent, null, 2)}\n`);
@@ -1730,19 +1705,8 @@ async function executeOrdinaryOperation(
1730
1705
  } catch (error) {
1731
1706
  if (priorBytes) {
1732
1707
  const current = await readTaskRecord(ctx.cwd, input.taskId);
1733
- if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
1734
- writeFileSync(sidecar, priorBytes);
1735
- execFileSync("git", ["update-index", "--force-remove", "--", priorIntent.intent_ref.path], {
1736
- cwd: ctx.cwd,
1737
- stdio: ["ignore", "pipe", "pipe"],
1738
- });
1739
- if (priorIndexState && priorIndexState.length > 0) {
1740
- execFileSync("git", ["update-index", "-z", "--index-info"], {
1741
- cwd: ctx.cwd,
1742
- input: priorIndexState,
1743
- stdio: ["pipe", "ignore", "pipe"],
1744
- });
1745
- }
1708
+ if (current.record?.intent_snapshot.revision === priorIntent.intent.revision && priorStaged) {
1709
+ restoreStagedIntentShared(ctx.cwd, priorStaged);
1746
1710
  }
1747
1711
  }
1748
1712
  throw error;
@@ -1774,24 +1738,15 @@ async function enrichAssuranceResult(
1774
1738
  : projection.projection;
1775
1739
  let tracker: Awaited<ReturnType<typeof markGithubTaskTerminal>> | undefined;
1776
1740
  await reconcileRefsQuietly(ctx.cwd);
1777
- if (!projection.error) {
1778
- try {
1779
- const terminalInput = deriveGithubTerminalProjectionInput(
1780
- taskId,
1781
- projection,
1782
- await readTaskTombstone(ctx.cwd, taskId),
1783
- );
1784
- if (terminalInput) tracker = await markGithubTaskTerminal(ctx.cwd, terminalInput);
1785
- } catch {
1786
- tracker = {
1787
- contract: "immune_brain/github_issue_tracker_result/v1",
1788
- operation: "mark-terminal",
1789
- status: "retryable_failure",
1790
- association_found: false,
1791
- message: "tracker observation failed after authoritative settlement",
1792
- };
1793
- }
1794
- }
1741
+ // One shared projection: the Claude Host runs the same step, so the opted-in
1742
+ // terminal tracker outcome does not depend on which Host settled the task.
1743
+ tracker = await projectTerminalTrackerState({
1744
+ root: ctx.cwd,
1745
+ task_id: taskId,
1746
+ projection,
1747
+ tombstone: await readTaskTombstone(ctx.cwd, taskId),
1748
+ markTerminal: (root, input) => markGithubTaskTerminal(root, input),
1749
+ });
1795
1750
  return {
1796
1751
  ...result,
1797
1752
  task_state: taskState,