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,3 +1,7 @@
1
+ import { buildAssuranceSnapshot, stagePlanningArtifactTransition } from "../runtime/assurance/verdict_authority";
2
+ export { buildSnapshot, ensureReviewRevision as ensureTaskReviewRevision } from "../runtime/assurance/verdict_authority";
3
+ export type { SnapshotDescriptorInput } from "../runtime/assurance/verdict_authority";
4
+ import type { ConfirmationReferenceSource } from "../runtime/assurance/host_port";
1
5
  // P3 Pi lifecycle extension: the only production route for Kernel canary
2
6
  // assurance after enrollment.
3
7
  //
@@ -24,18 +28,11 @@ import { PLUGIN_VERSION } from "../runtime/plugin_version";
24
28
  import { findResumableBatchSlugForTask } from "../runtime/unattended/batch_preflight";
25
29
  import { executePiUnattendedBatch } from "./imm-unattended-batch";
26
30
  import {
27
- parseVerificationDescriptor,
28
- type VerificationDescriptor,
29
31
  } from "./pi-canary-verification";
30
32
  import {
31
- captureReviewBundle,
32
- captureReviewManifest,
33
33
  listReviewRefs,
34
34
  reconcileReviewRefs,
35
35
  writeNativeReviewEvidence,
36
- type ReviewBundle,
37
- type ReviewManifestV5,
38
- type ReviewRevision,
39
36
  } from "./pi-canary-review-bundle";
40
37
  import type { InvocationToken } from "./pi-canary-invocations";
41
38
  import { runDeterministicQa } from "../runtime/assurance/qa";
@@ -46,7 +43,6 @@ import {
46
43
  import {
47
44
  renderCanaryCall,
48
45
  renderCanaryResult,
49
- type AssuranceRole,
50
46
  } from "./pi-canary-assurance";
51
47
  import {
52
48
  USER_ATTENTION_EVENT,
@@ -71,8 +67,7 @@ import { resolveUxLanguage, uxText } from "./ux-language";
71
67
 
72
68
  /** Host-native UI language; see ux-language.ts. Resolved once per process. */
73
69
  const UX_LANG = resolveUxLanguage();
74
- import { taskDiffIdentity, taskRevisionIdentity, captureGitTaskSnapshot } from "../runtime/workspace_scope";
75
- import { reviewAdvisoryRecords, reviewReworkFindings } from "../runtime/assurance/coordinator";
70
+ import { taskDeliveryIdentity } from "../runtime/workspace_scope";
76
71
  import {
77
72
  AssuranceProgression,
78
73
  buildReviewPrompt,
@@ -87,44 +82,54 @@ import {
87
82
  REVIEW_TIMING_PROFILES,
88
83
  REVIEW_VERDICT_VALIDATION_TIMEOUT_MS,
89
84
  type AssuranceProgressionPorts,
90
- type AssuranceVerdict,
91
85
  type HostContext,
92
- type SnapshotDescriptor,
93
86
  } from "./pi-canary-assurance-progression";
94
87
 
95
- // The Kernel runtime graph is never type-checked from this extension: static
96
- // imports resolve to ./runtime-stub.ts (relative so the Pi extension loader
97
- // can resolve them at runtime), and the stub forwards to the real Kernel
98
- // modules via dynamic import.
88
+ // The Pi adapter imports the host-neutral runtime modules statically, exactly
89
+ // as the Claude adapter does; the retired dynamic-import isolation layer is
90
+ // gone. The adapter-owned logic that used to live behind it (the
91
+ // record-aware intent read, the tracker terminal wrapper, the shared batch
92
+ // progression accessors) is defined below, next to the machinery it uses.
93
+ import { LITERAL_USER_ACTOR_ID } from "../runtime/kernel/actor_identity";
99
94
  import {
100
- createMutationAuthorityRegistry,
101
- createCanaryApplication,
102
- buildLoopAction,
103
- buildLoopRoleDispatch,
104
- readBackendClaim,
105
- readTaskTombstone,
106
- markGithubTaskTerminal,
107
95
  reconcileKernelAuthority,
108
96
  repairKernelAuthority,
109
- readTaskRecord,
97
+ readTaskRecordRaw,
110
98
  withKernelStoreLockForTask,
111
- inspectStorageLayout,
112
- migrateLegacyLayout,
99
+ } from "../runtime/kernel/storage";
100
+ import { inspectStorageLayout } from "../runtime/kernel/storage_paths";
101
+ import { migrateLegacyLayout } from "../runtime/kernel/storage_layout_migration";
102
+ import {
113
103
  readTaskIntent,
114
104
  parseTaskIntentV1,
115
105
  canonicalIntentHash,
116
- projectAssurance,
117
- findingsDigestV2,
118
- capabilityActionFor,
106
+ type ReadTaskIntentResult,
107
+ } from "../runtime/kernel/intent";
108
+ import { readBackendClaim, readTaskTombstone } from "../runtime/kernel/backend_claim";
109
+ import {
110
+ createMutationAuthorityRegistry,
119
111
  digestOfAction,
120
- type AssuranceProjectionResult,
121
- type TaskRecordRead,
122
- type CanaryApplication,
123
- type MutationAuthorityRegistry,
124
112
  type CapabilityBindingV2,
125
- LITERAL_USER_ACTOR_ID,
126
- } from "./runtime-stub";
127
- import { invocationRegistry } from "./pi-canary-assurance-progression";
113
+ type MutationAuthorityRegistry,
114
+ } from "../runtime/kernel/authority_port";
115
+ import {
116
+ createCanaryApplication,
117
+ capabilityActionFor,
118
+ type CanaryApplication,
119
+ } from "../runtime/kernel/canary_application";
120
+ import { findingsDigestV2 } from "../runtime/kernel/reducer";
121
+ import {
122
+ projectAssurance,
123
+ type AssuranceProjectionResult,
124
+ } from "../runtime/kernel/assurance_projection";
125
+ import type { TaskRecord } from "../runtime/kernel/types";
126
+ import type { RoleDelegationContext } from "../runtime/role_prompt_bridge";
127
+ import { buildLoopAction, buildLoopRoleDispatch } from "../runtime/loop_contract";
128
+ import {
129
+ runGithubTrackerOperation,
130
+ type GithubTrackerResult,
131
+ } from "../runtime/github_issue_tracker";
132
+ import { reviewReworkFindings } from "../runtime/assurance/coordinator";
128
133
 
129
134
  const LOOP_OWNERS = ["plan", "kernel", "brainstorm", "planner", "loop"] as const;
130
135
  const LOOP_TARGETS = [
@@ -178,60 +183,6 @@ export type AuthorizeOperation =
178
183
  | "authorize-rework"
179
184
  | "stop";
180
185
 
181
- export interface SnapshotDescriptorInput {
182
- root: string;
183
- task_id: string;
184
- role: AssuranceRole;
185
- run_id?: string | null;
186
- record_revision: string;
187
- workspace_revision: string;
188
- intent_revision: number;
189
- intent_content_hash: string;
190
- diff_hash: string;
191
- lifecycle: string;
192
- artifact_state: string;
193
- risk?: "routine" | "material" | "critical";
194
- fresh_acceptance_ids: string[];
195
- missing_acceptance_ids: string[];
196
- stale_attestation_ids: string[];
197
- acceptance: Array<{ id: string; assertion: string; verification: string }>;
198
- dirty_files?: string[];
199
- review_bundle_digest?: string | null;
200
- /** Present only when Review authority binds an immutable Git revision (v4). */
201
- review_revision?: {
202
- contract: "assurance_kernel/review_revision_identity/v1";
203
- base_head: string;
204
- review_commit: string;
205
- review_tree: string;
206
- manifest_digest: string;
207
- };
208
- }
209
-
210
- export function buildSnapshot(input: SnapshotDescriptorInput): SnapshotDescriptor {
211
- return {
212
- contract: "assurance_kernel/assurance_snapshot/v2",
213
- task_id: input.task_id,
214
- run_id: input.run_id ?? null,
215
- role: input.role,
216
- record_revision: input.record_revision,
217
- workspace_revision: input.workspace_revision,
218
- intent_revision: input.intent_revision,
219
- intent_content_hash: input.intent_content_hash,
220
- diff_hash: input.diff_hash,
221
- lifecycle: input.lifecycle,
222
- artifact_state: input.artifact_state,
223
- risk: input.risk ?? "material",
224
- fresh_acceptance_ids: input.fresh_acceptance_ids,
225
- missing_acceptance_ids: input.missing_acceptance_ids,
226
- stale_attestation_ids: input.stale_attestation_ids,
227
- acceptance: input.acceptance,
228
- dirty_files: [...(input.dirty_files ?? [])].sort(),
229
- review_bundle_digest: input.review_bundle_digest ?? null,
230
- ...(input.review_revision ? { review_revision: input.review_revision } : {}),
231
- root: resolve(input.root),
232
- };
233
- }
234
-
235
186
  export interface CanaryWorkExtensionDependencies {
236
187
  buildAssurance?: typeof buildAssuranceSnapshot;
237
188
  runQa?: typeof runDeterministicQa;
@@ -277,25 +228,15 @@ export function createPiAssuranceProgressionPorts(
277
228
  dependencies: CanaryWorkExtensionDependencies = {},
278
229
  ): AssuranceProgressionPorts {
279
230
  return {
280
- projectTask: (root, taskId) => projectAssuranceState(root, taskId),
281
- readTaskRecord: (root, taskId) => readTaskRecord(root, taskId),
282
- readTaskIntent: (root, taskId) => readTaskIntent(root, taskId),
283
- buildAssurance: (root, taskId, role, projection) =>
284
- (dependencies.buildAssurance ?? buildAssuranceSnapshot)(root, taskId, role, projection),
285
- ensureReviewRevision: (root, taskId, projection) => ensureTaskReviewRevision(root, taskId, projection),
231
+ confirmationReference: piConfirmationReference,
232
+ onReworkApplied: (ctx, taskId, count) => notifyHost(ctx, `rework-parked:${taskId}`, `rework applied: review parked for replan with ${count} finding(s)`, "warning"),
233
+ projectTask: (root, taskId) => projectAssuranceForTask(root, taskId),
234
+ readTaskRecord: async (root, taskId) => readTaskRecordRaw(root, taskId),
235
+ readTaskIntent: async (root, taskId) => readTaskIntentForRecord(root, taskId),
286
236
  runQa: (snapshot, descriptors, options) =>
287
237
  (dependencies.runQa ?? runDeterministicQa)(snapshot, descriptors, options),
288
238
  writeReviewEvidence: (input) =>
289
239
  (dependencies.writeReviewEvidence ?? writeNativeReviewEvidence)(input),
290
- applyVerdict: (ctx, input) =>
291
- applyAssuranceVerdict(
292
- ctx,
293
- input.snapshot,
294
- input.verdict,
295
- input.invocation,
296
- input.actorId,
297
- input.hooks,
298
- ),
299
240
  applyOrdinaryOperation: (ctx, input) => executeOrdinaryOperation(ctx, input),
300
241
  advanceBeforeProjection: dependencies.advanceBeforeProjection,
301
242
  qaBeforeProjection: dependencies.qaBeforeProjection,
@@ -303,11 +244,46 @@ export function createPiAssuranceProgressionPorts(
303
244
  qaOnAuthorityCommit: dependencies.qaOnAuthorityCommit,
304
245
  qaAfterAuthorityCommit: dependencies.qaAfterAuthorityCommit,
305
246
  qaJobTimeoutMs: dependencies.qaJobTimeoutMs,
247
+ ...(dependencies.buildAssurance ? { authorityOverrides: { buildAssurance: dependencies.buildAssurance } } : {}),
306
248
  } satisfies AssuranceProgressionPorts;
307
249
  }
308
250
 
309
251
  const GLOBAL_PI_PROGRESSION_KEY = Symbol.for("immune_brain.pi_assurance_progression");
310
252
 
253
+ /**
254
+ * The shared session progression the batch adapter advances children through.
255
+ * The work extension publishes the instance at load; this accessor is the
256
+ * lazy fallback that builds one from the production ports when no extension
257
+ * factory has run (tests, direct batch drives). Both paths publish under the
258
+ * same global key, so a batch Review reservation and the session's
259
+ * submit_review always share one progression instance.
260
+ */
261
+ export async function getSharedPiProgression(): Promise<AssuranceProgression> {
262
+ let progression = (globalThis as any)[GLOBAL_PI_PROGRESSION_KEY] as AssuranceProgression | undefined;
263
+ if (!progression) {
264
+ progression = new AssuranceProgression(createPiAssuranceProgressionPorts());
265
+ (globalThis as any)[GLOBAL_PI_PROGRESSION_KEY] = progression;
266
+ }
267
+ return progression;
268
+ }
269
+
270
+ /**
271
+ * Advance one task through the shared session progression and translate the
272
+ * progression result into the batch runner's advance report shape.
273
+ */
274
+ export async function advancePiTask(root: string, taskId: string): Promise<any> {
275
+ const progression = await getSharedPiProgression();
276
+ const result = await progression.advance(taskId, { cwd: root });
277
+ const facts = { diagnostics: result.diagnostics, environment_failure: result.environment_failure, recovery: result.recovery };
278
+ if (result.state === "completed") return { state: "completed", ...facts };
279
+ if (result.state === "stopped") return { state: "stopped", ...facts };
280
+ if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary, ...facts };
281
+ if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, agent_params: result.agent_params, ...facts };
282
+ if (result.state === "review_preparation_failed") return { state: result.state, operation: result.operation, operation_id: result.operation_id, reason: result.reason, ...facts };
283
+ if (result.state === "blocked") return { state: "blocked", reason: result.reason, ...facts };
284
+ return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed", ...facts };
285
+ }
286
+
311
287
  export default function (
312
288
  pi: ExtensionAPI,
313
289
  dependencies: CanaryWorkExtensionDependencies = {},
@@ -323,7 +299,7 @@ export default function (
323
299
  try {
324
300
  const claim = await readBackendClaim(ctx.cwd);
325
301
  if (!claim) return;
326
- const projection = await projectAssuranceState(ctx.cwd, claim.task_id);
302
+ const projection = await projectAssuranceForTask(ctx.cwd, claim.task_id);
327
303
  if (projection.error) {
328
304
  presentTaskRail(ctx, {
329
305
  task_id: claim.task_id,
@@ -592,7 +568,7 @@ export default function (
592
568
  throwIfCanaryToolFailure(taskId, action.op, enriched);
593
569
  return toolResult(JSON.stringify(enriched, null, 2), enriched);
594
570
  }
595
- const projection = await projectAssuranceState(ctx.cwd, taskId);
571
+ const projection = await projectAssuranceForTask(ctx.cwd, taskId);
596
572
  if (projection.error) {
597
573
  const nextAction = recoveryActionForAssuranceFailure(projection.error) ?? "inspect authority state";
598
574
  const details = {
@@ -634,7 +610,7 @@ export default function (
634
610
  taskId,
635
611
  operation: toCanaryOperation(action, "executor") as { op: string; actor_id: string },
636
612
  })) as unknown as { revision: string; record: { lifecycle: string; artifact_state: string } };
637
- const updated = await projectAssuranceState(ctx.cwd, taskId);
613
+ const updated = await projectAssuranceForTask(ctx.cwd, taskId);
638
614
  const taskState = updated.error
639
615
  ? { lifecycle: result.record.lifecycle, artifact_state: result.record.artifact_state }
640
616
  : updated.projection;
@@ -710,15 +686,18 @@ export default function (
710
686
  _ctx: ExtensionContext,
711
687
  ) => {
712
688
  const { action } = params;
689
+ // The Tool schema is intentionally broader than loop_contract's input
690
+ // types (model-supplied JSON); loop_contract narrows and validates at
691
+ // runtime, so these boundary casts are type-level only.
713
692
  const result = action.op === "route"
714
693
  ? await buildLoopAction({
715
694
  ownership: action.ownership,
716
695
  target: action.target,
717
- context: action.context,
696
+ context: action.context as RoleDelegationContext | undefined,
718
697
  scope_expansion: action.scope_expansion,
719
- kernel_operation: action.kernel_operation,
698
+ kernel_operation: action.kernel_operation as Parameters<typeof buildLoopAction>[0]["kernel_operation"],
720
699
  })
721
- : await buildLoopRoleDispatch({ role: action.role, context: action.context });
700
+ : await buildLoopRoleDispatch({ role: action.role, context: action.context as RoleDelegationContext });
722
701
  const details = loopResultDetails(result, action.op);
723
702
  return toolResult(JSON.stringify(result, null, 2), details);
724
703
  },
@@ -775,14 +754,14 @@ export default function (
775
754
  return { state: "blocked", reason };
776
755
  }
777
756
  try {
778
- const projection = await projectAssuranceState(ctx.cwd, taskId);
757
+ const projection = await projectAssuranceForTask(ctx.cwd, taskId);
779
758
  if (projection.error || !projection.claim) {
780
759
  const reason = projection.error ?? "no active backend claim";
781
760
  notifyOnce(ctx, `authorization-claim:${taskId}:${reason}`, `cannot authorize ${taskId}: ${reason}`, "error");
782
761
  progression.closeInvocation(invocation);
783
762
  return { state: "blocked", reason };
784
763
  }
785
- const priorIntent = await readTaskIntent(ctx.cwd, taskId);
764
+ const priorIntent = await readTaskIntentForRecord(ctx.cwd, taskId);
786
765
  const sidecar = nextIntent ? join(ctx.cwd, priorIntent.intent_ref.path) : undefined;
787
766
  const priorBytes = sidecar ? readFileSync(sidecar) : undefined;
788
767
  const stagedSnapshot = sidecar ? captureStagedIntent(ctx.cwd, priorIntent.intent_ref.path) : undefined;
@@ -803,10 +782,10 @@ export default function (
803
782
  cwd: ctx.cwd,
804
783
  stdio: ["ignore", "pipe", "pipe"],
805
784
  });
806
- const stagedRecord = await readTaskRecord(ctx.cwd, taskId);
785
+ const stagedRecord = await readTaskRecordRaw(ctx.cwd, taskId);
807
786
  if (!stagedRecord.record || stagedRecord.revision !== projection.projection.record_revision)
808
787
  throw new Error("TaskRecord changed while preparing the breaking revision");
809
- stagedNextDiffHash = diffHashOf(ctx.cwd, stagedRecord.record);
788
+ stagedNextDiffHash = taskDeliveryIdentity(ctx.cwd, stagedRecord.record).diff_hash;
810
789
  } catch (error) {
811
790
  let restoreError: unknown;
812
791
  try { restoreStagedIntent(); } catch (err) { restoreError = err; }
@@ -821,7 +800,7 @@ export default function (
821
800
  let userDecisionOperation: ReturnType<typeof buildUserDecisionOperation> | undefined;
822
801
  if (operation === "resolve-user-decision") {
823
802
  try {
824
- const current = await readTaskRecord(ctx.cwd, taskId);
803
+ const current = await readTaskRecordRaw(ctx.cwd, taskId);
825
804
  if (!current.record) throw new Error(`task ${taskId} has no TaskRecord v2`);
826
805
  if (current.revision !== projection.projection.record_revision)
827
806
  throw new Error("task record changed while preparing user decision");
@@ -977,11 +956,11 @@ export default function (
977
956
  if (nextIntent) await dependencies.authorizationAfterSidecarStage?.();
978
957
  // Re-read the owner record after confirmation and compare the staged
979
958
  // candidate digest immediately before capability issuance.
980
- const liveRecord = nextIntent ? await readTaskRecord(ctx.cwd, taskId) : null;
959
+ const liveRecord = nextIntent ? await readTaskRecordRaw(ctx.cwd, taskId) : null;
981
960
  if (nextIntent && (!liveRecord?.record || liveRecord.revision !== projection.projection.record_revision || !stagedNextDiffHash))
982
961
  throw new Error("TaskRecord changed before the breaking revision digest");
983
962
  const operationDiffHash = liveRecord?.record
984
- ? diffHashOf(ctx.cwd, liveRecord.record)
963
+ ? taskDeliveryIdentity(ctx.cwd, liveRecord.record).diff_hash
985
964
  : projection.projection.diff_hash;
986
965
  if (nextIntent && operationDiffHash !== stagedNextDiffHash)
987
966
  throw new Error("staged next-state diff changed after native confirmation");
@@ -1009,7 +988,7 @@ export default function (
1009
988
  task_id: taskId,
1010
989
  operation: { ...exactOperation, capability, actor_id: LITERAL_USER_ACTOR_ID } as never,
1011
990
  prior_intent_token: priorIntent.token,
1012
- diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
991
+ diffProvider: (root: string, record: TaskRecord) => diffSnapshotOf(root, record),
1013
992
  now,
1014
993
  })) as unknown as { record: { lifecycle: string; artifact_state: string; intent_ref: { path: string }; intent_snapshot: { scope_hint: string[] } } };
1015
994
  if (exactOperation.op === "stop") progression.releaseStoppedReview(taskId);
@@ -1021,7 +1000,7 @@ export default function (
1021
1000
  return { state: "applied", operation, lifecycle: result.record.lifecycle };
1022
1001
  } catch (error) {
1023
1002
  if (stagedSnapshot) {
1024
- const current = await readTaskRecord(ctx.cwd, taskId);
1003
+ const current = await readTaskRecordRaw(ctx.cwd, taskId);
1025
1004
  if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) restoreStagedIntent();
1026
1005
  }
1027
1006
  throw error;
@@ -1029,7 +1008,7 @@ export default function (
1029
1008
  } catch (error) {
1030
1009
  const reason = error instanceof Error ? error.message : String(error);
1031
1010
  if (operation === "stop") {
1032
- const terminal = await projectAssuranceState(ctx.cwd, taskId).catch(() => null);
1011
+ const terminal = await projectAssuranceForTask(ctx.cwd, taskId).catch(() => null);
1033
1012
  if (terminal && !terminal.error && terminal.projection.lifecycle === "stopped") {
1034
1013
  progression.releaseStoppedReview(taskId);
1035
1014
  return { state: "applied", operation, lifecycle: "stopped", delivery_error: reason };
@@ -1046,11 +1025,11 @@ export default function (
1046
1025
  if (ctx.mode !== "tui") return { state: "blocked", reason: "imm_kernel_canary mutation is TUI-only" };
1047
1026
  if (progression.isInvocationOpen(taskId))
1048
1027
  return { state: "blocked", reason: `task ${taskId} already has an open invocation; concurrent assure/authorize is rejected` };
1049
- const projection = await projectAssuranceState(ctx.cwd, taskId);
1028
+ const projection = await projectAssuranceForTask(ctx.cwd, taskId);
1050
1029
  if (projection.error || !projection.claim)
1051
1030
  return { state: "blocked", reason: projection.error ?? "no active backend claim" };
1052
1031
  await dependencies.authorizationBeforeRecordRead?.();
1053
- const read = await readTaskRecord(ctx.cwd, taskId);
1032
+ const read = await readTaskRecordRaw(ctx.cwd, taskId);
1054
1033
  if (!read.record) return { state: "blocked", reason: `task ${taskId} has no TaskRecord v3` };
1055
1034
  if (read.revision !== projection.projection.record_revision)
1056
1035
  return { state: "blocked", reason: "TaskRecord changed while deriving authorization operation" };
@@ -1114,7 +1093,7 @@ export async function recordCancelledUserDecision(
1114
1093
  snapshotDigestRef: string,
1115
1094
  ): Promise<{ recorded: boolean; finding_id: string }> {
1116
1095
  const findingId = `user-decision-${operation}`;
1117
- const current = await readTaskRecord(ctx.cwd, taskId);
1096
+ const current = await readTaskRecordRaw(ctx.cwd, taskId);
1118
1097
  const openDecision = current.record?.findings.find(
1119
1098
  (finding) =>
1120
1099
  finding.kind === "unresolved_user_decision" && finding.status === "open",
@@ -1136,8 +1115,8 @@ export async function recordCancelledUserDecision(
1136
1115
  },
1137
1116
  actor_id: LITERAL_USER_ACTOR_ID,
1138
1117
  } as never,
1139
- prior_intent_token: (await readTaskIntent(ctx.cwd, taskId)).token,
1140
- diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
1118
+ prior_intent_token: (await readTaskIntentForRecord(ctx.cwd, taskId)).token,
1119
+ diffProvider: (root: string, record: TaskRecord) => diffSnapshotOf(root, record),
1141
1120
  now: new Date().toISOString(),
1142
1121
  });
1143
1122
  return { recorded: true, finding_id: findingId };
@@ -1159,24 +1138,36 @@ export function buildUserDecisionOperation(record: {
1159
1138
  }
1160
1139
 
1161
1140
  /**
1162
- * One record-aware freshness identity. v4 derives the scoped revision digest
1163
- * from the immutable Enrollment base so committed and staged task work share a
1164
- * single diff_hash with Review; v3 keeps the legacy HEAD -> index digest.
1141
+ * Read the TaskIntent through the TaskRecord's `intent_ref.path`.
1142
+ *
1143
+ * `freeze_artifacts` binds the sidecar in place, so a post-freeze read must
1144
+ * follow the record instead of guessing a default path; with no record yet
1145
+ * (pre-enrollment) the Kernel resolves the sidecar that exists. Same contract
1146
+ * as the Claude adapter's `readTaskIntentForRecord`.
1147
+ */
1148
+ export function readTaskIntentForRecord(root: string, taskId: string): ReadTaskIntentResult {
1149
+ const currentPath = readTaskRecordRaw(root, taskId).record?.intent_ref.path;
1150
+ return readTaskIntent(root, taskId, currentPath);
1151
+ }
1152
+
1153
+ async function markGithubTaskTerminal(
1154
+ root: string,
1155
+ input: { task_id: string; phase: "done" | "stopped"; terminal_event_id: string },
1156
+ ): Promise<GithubTrackerResult> {
1157
+ return runGithubTrackerOperation(root, { op: "mark-terminal", ...input });
1158
+ }
1159
+
1160
+ /**
1161
+ * The shared delivery identity selector: one function in workspace_scope.ts
1162
+ * picks the identity family per TaskRecord contract. This adapter-level alias
1163
+ * only narrows the record type for the Kernel ports that expect it; the dual-host
1164
+ * conformance seam imports it so both hosts' reportable identity is comparable.
1165
1165
  */
1166
- function diffSnapshotOf(root: string, record: NonNullable<TaskRecordRead["record"]>): {
1166
+ export function diffSnapshotOf(root: string, record: TaskRecord): {
1167
1167
  diff_hash: string;
1168
1168
  changed_paths: string[];
1169
1169
  } {
1170
- if (record.contract === "assurance_kernel/task_record/v4") {
1171
- if (!record.git_base_head)
1172
- throw new Error("TaskRecord v4 is missing git_base_head");
1173
- return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head, record.task_id);
1174
- }
1175
- return taskDiffIdentity(root, record.intent_snapshot.scope_hint, record.task_id);
1176
- }
1177
-
1178
- function diffHashOf(root: string, record: NonNullable<TaskRecordRead["record"]>): string {
1179
- return diffSnapshotOf(root, record).diff_hash;
1170
+ return taskDeliveryIdentity(root, record);
1180
1171
  }
1181
1172
 
1182
1173
  // Translation-only adapter for the internal Kernel assurance projection. All
@@ -1196,7 +1187,7 @@ async function buildTaskOverview(root: string): Promise<{
1196
1187
  const claim = await readBackendClaim(root);
1197
1188
  let active: TaskOverviewEntry | null = null;
1198
1189
  if (claim) {
1199
- const projection = await projectAssuranceState(root, claim.task_id);
1190
+ const projection = await projectAssuranceForTask(root, claim.task_id);
1200
1191
  if (!projection.error) {
1201
1192
  const state = projection.projection;
1202
1193
  const obligation = String(state.next_obligation);
@@ -1247,56 +1238,14 @@ function overviewRailState(lifecycle: string, obligation: string): TaskRailState
1247
1238
  return "Working";
1248
1239
  }
1249
1240
 
1250
- async function projectAssuranceState(root: string, taskId: string): Promise<AssuranceProjectionResult> {
1251
- return projectAssurance(root, taskId, diffSnapshotOf);
1241
+ export async function projectAssuranceForTask(root: string, taskId: string): Promise<AssuranceProjectionResult> {
1242
+ return projectAssurance(root, taskId);
1252
1243
  }
1253
1244
 
1254
1245
  /**
1255
1246
  * Publish and prove the task-scoped synthetic revision for a v4 record. v3
1256
1247
  * records keep the legacy full-source bundle and return null here.
1257
1248
  */
1258
- export async function ensureTaskReviewRevision(
1259
- root: string,
1260
- taskId: string,
1261
- projection: AssuranceProjectionResult,
1262
- ): Promise<ReviewRevision | null> {
1263
- const current = await readTaskRecord(root, taskId);
1264
- const record = current.record;
1265
- if (!record) throw new Error(`task ${taskId} has no TaskRecord before Review preparation`);
1266
- if (current.revision !== projection.projection.record_revision)
1267
- throw new Error("TaskRecord changed before Review preparation");
1268
- if (record.contract !== "assurance_kernel/task_record/v4") return null;
1269
- if (!record.git_base_head)
1270
- throw new Error("Review revision requires a TaskRecord v4 git_base_head");
1271
- const manifest = captureReviewManifest(root, {
1272
- taskId,
1273
- baseHead: record.git_base_head,
1274
- scopeHint: record.intent_snapshot.scope_hint,
1275
- expectedDiffHash: projection.projection.diff_hash,
1276
- intentRevision: projection.projection.intent_revision,
1277
- intentContentHash: projection.projection.intent_content_hash,
1278
- recordRevision: projection.projection.record_revision,
1279
- workspaceRevision: projection.projection.workspace_revision,
1280
- lifecycle: projection.projection.lifecycle,
1281
- artifactState: projection.projection.artifact_state,
1282
- risk: record.intent_snapshot.risk,
1283
- // The same outcomes the Review snapshot is built from. A preflight stand-in
1284
- // only matched the settled QA attestation because deterministic QA happens to
1285
- // write that exact summary, so the submit-time digest comparison held by
1286
- // coincidence rather than by construction.
1287
- outcomes: qaOutcomes(record),
1288
- });
1289
- return {
1290
- contract: "assurance_kernel/review_revision/v1",
1291
- base_head: manifest.base_head,
1292
- review_tree: manifest.review_tree,
1293
- review_commit: manifest.review_commit,
1294
- review_ref: manifest.review_ref,
1295
- diff_hash: manifest.diff_hash,
1296
- manifest_digest: manifest.manifest_digest,
1297
- };
1298
- }
1299
-
1300
1249
  /**
1301
1250
  * Review refs are reconstructible evidence transport, never workflow authority.
1302
1251
  * A ref survives only while its task owns a nonterminal TaskRecord.
@@ -1314,7 +1263,7 @@ async function reconcileReviewRevisionRefs(root: string): Promise<{ removed: str
1314
1263
  for (const entry of listed) {
1315
1264
  if (live.has(entry.taskId)) continue;
1316
1265
  try {
1317
- const record = await readTaskRecord(root, entry.taskId);
1266
+ const record = await readTaskRecordRaw(root, entry.taskId);
1318
1267
  if (record.record && record.record.lifecycle === "active" && claim?.task_id === entry.taskId)
1319
1268
  live.add(entry.taskId);
1320
1269
  } catch {
@@ -1340,267 +1289,13 @@ function notifyHost(ctx: HostContext, key: string, message: string, level: "warn
1340
1289
  if (ui) notifyOnce({ ui }, key, message, level);
1341
1290
  }
1342
1291
 
1343
- async function applyAssuranceVerdict(
1344
- // The coordinator port hands these a `HostContext`, not the Pi
1345
- // `ExtensionContext`. Both functions only ever read `cwd`; declaring the
1346
- // wider host type made the port assignment unsound.
1347
- ctx: HostContext,
1348
- snapshot: SnapshotDescriptor,
1349
- verdict: AssuranceVerdict,
1350
- invocation: InvocationToken,
1351
- actorId: string,
1352
- hooks: { beforeCommit?: () => Promise<void>; onCommit?: () => void; afterCommit?: () => Promise<void> } = {},
1353
- authorityKind: "qa" | "review" | "user" = snapshot.role,
1354
- ): Promise<void> {
1355
- const fresh = await projectAssuranceState(ctx.cwd, snapshot.task_id);
1356
- if (
1357
- fresh.error ||
1358
- fresh.claim?.task_id !== snapshot.task_id ||
1359
- fresh.projection.record_revision !== snapshot.record_revision ||
1360
- fresh.projection.workspace_revision !== snapshot.workspace_revision ||
1361
- fresh.projection.intent_revision !== snapshot.intent_revision ||
1362
- fresh.projection.intent_content_hash !== snapshot.intent_content_hash ||
1363
- fresh.projection.diff_hash !== snapshot.diff_hash ||
1364
- fresh.projection.lifecycle !== snapshot.lifecycle ||
1365
- fresh.projection.artifact_state !== snapshot.artifact_state
1366
- ) {
1367
- throw new Error(`assurance snapshot changed before authority application: ${[
1368
- fresh.error,
1369
- fresh.claim?.task_id !== snapshot.task_id ? "claim" : null,
1370
- fresh.projection.record_revision !== snapshot.record_revision ? "record_revision" : null,
1371
- fresh.projection.workspace_revision !== snapshot.workspace_revision ? "workspace_revision" : null,
1372
- fresh.projection.intent_revision !== snapshot.intent_revision ? "intent_revision" : null,
1373
- fresh.projection.intent_content_hash !== snapshot.intent_content_hash ? "intent_content_hash" : null,
1374
- fresh.projection.diff_hash !== snapshot.diff_hash ? "diff_hash" : null,
1375
- fresh.projection.lifecycle !== snapshot.lifecycle ? `lifecycle(${snapshot.lifecycle}->${fresh.projection.lifecycle})` : null,
1376
- fresh.projection.artifact_state !== snapshot.artifact_state ? `artifact_state(${snapshot.artifact_state}->${fresh.projection.artifact_state})` : null,
1377
- ].filter(Boolean).join(", ")}`);
1378
- }
1379
- const { registry, app } = await authorityPair();
1380
- const priorIntentToken = (await readTaskIntent(ctx.cwd, snapshot.task_id)).token;
1381
- const commitAndApply = async <T>(apply: () => Promise<T>): Promise<T> => {
1382
- invocationRegistry.commit(invocation);
1383
- const settlement = apply();
1384
- let hookError: unknown;
1385
- try { hooks.onCommit?.(); } catch (error) { hookError = error; }
1386
- const result = await settlement;
1387
- try { await hooks.afterCommit?.(); } catch (error) { hookError ??= error; }
1388
- if (hookError) throw hookError;
1389
- return result;
1390
- };
1391
- if (verdict.decision === "rework") {
1392
- const findings = reviewReworkFindings(verdict);
1393
- const now = new Date().toISOString();
1394
- const capability = await mintCapability(registry, {
1395
- authority_kind: authorityKind,
1396
- task_id: snapshot.task_id,
1397
- run_id: snapshot.run_id,
1398
- action_kind: "request_rework",
1399
- expected_record_hash: snapshot.record_revision,
1400
- intent_revision: snapshot.intent_revision,
1401
- intent_content_hash: snapshot.intent_content_hash,
1402
- diff_hash: snapshot.diff_hash,
1403
- actor_id: actorId,
1404
- findings,
1405
- now,
1406
- });
1407
- await hooks.beforeCommit?.();
1408
- const result = (await commitAndApply(async () => app.execute({
1409
- root: ctx.cwd,
1410
- task_id: snapshot.task_id,
1411
- operation: {
1412
- op: "request_rework",
1413
- capability,
1414
- findings: findings as never[],
1415
- actor_id: actorId,
1416
- },
1417
- prior_intent_token: priorIntentToken,
1418
- diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
1419
- now,
1420
- }))) as unknown as { record: { lifecycle: string; artifact_state: string; intent_ref: { path: string }; intent_snapshot: { scope_hint: string[] }; findings?: Array<{ kind: string; status: string }> } };
1421
- stagePlanningArtifactTransition(ctx.cwd, result.record);
1422
- const parked = (result.record as { findings?: Array<{ kind: string; status: string }> }).findings?.some(
1423
- (finding) => finding.kind === "replan_required" && finding.status === "open",
1424
- );
1425
- if (parked) notifyHost(
1426
- ctx,
1427
- `rework-parked:${snapshot.task_id}`,
1428
- `rework applied: review parked for replan with ${findings.length} finding(s)`,
1429
- "warning",
1430
- );
1431
- return;
1432
- }
1433
- const now = new Date().toISOString();
1434
- const advisories = verdict.decision === "pass" ? reviewAdvisoryRecords(verdict) : [];
1435
- const approval = {
1436
- id: `approval-${snapshot.role}-${randomUUID().slice(0, 8)}`,
1437
- kind: snapshot.role === "qa" ? "qa" : "review",
1438
- authority_role: snapshot.role === "qa" ? "qa" : "reviewer",
1439
- task_revision: snapshot.intent_revision,
1440
- intent_content_hash: snapshot.intent_content_hash,
1441
- diff_hash: snapshot.diff_hash,
1442
- actor_id: actorId,
1443
- summary: verdict.approval!.summary,
1444
- // Trusted revision identity comes from the host-verified snapshot, never
1445
- // from the reviewer payload.
1446
- ...(snapshot.role === "review" && snapshot.review_revision
1447
- ? { review_revision: snapshot.review_revision }
1448
- : {}),
1449
- ...(snapshot.role === "review" && advisories.length > 0
1450
- ? { advisory_findings: advisories }
1451
- : {}),
1452
- };
1453
- const capability = await mintCapability(registry, {
1454
- authority_kind: snapshot.role,
1455
- task_id: snapshot.task_id,
1456
- run_id: snapshot.run_id,
1457
- action_kind: "record_approval",
1458
- expected_record_hash: snapshot.record_revision,
1459
- intent_revision: snapshot.intent_revision,
1460
- intent_content_hash: snapshot.intent_content_hash,
1461
- diff_hash: snapshot.diff_hash,
1462
- actor_id: actorId,
1463
- approval,
1464
- now,
1465
- });
1466
- await hooks.beforeCommit?.();
1467
- await commitAndApply(async () => app.execute({
1468
- root: ctx.cwd,
1469
- task_id: snapshot.task_id,
1470
- operation: { op: "record_approval", capability, approval, actor_id: actorId },
1471
- prior_intent_token: priorIntentToken,
1472
- diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
1473
- now,
1474
- }));
1475
- }
1476
-
1477
- async function buildAssuranceSnapshot(
1478
- root: string,
1479
- taskId: string,
1480
- role: AssuranceRole,
1481
- projection: AssuranceProjectionResult,
1482
- ): Promise<{
1483
- snapshot: SnapshotDescriptor;
1484
- descriptors: Map<string, VerificationDescriptor>;
1485
- reviewBundle: ReviewBundle | null;
1486
- reviewManifest: ReviewManifestV5 | null;
1487
- }> {
1488
- const record = await readTaskRecord(root, taskId);
1489
- if (
1490
- !record.record ||
1491
- record.revision !== projection.projection.record_revision ||
1492
- record.record.intent_snapshot.revision !== projection.projection.intent_revision ||
1493
- record.record.intent_ref.content_hash !== projection.projection.intent_content_hash
1494
- ) {
1495
- throw new Error("TaskRecord changed before assurance snapshot capture");
1496
- }
1497
- const intent = record.record.intent_snapshot;
1498
- const acceptance = intent.acceptance;
1499
- const descriptors = new Map<string, VerificationDescriptor>();
1500
- // Review resumes from settled outcomes without resolving verification tools.
1501
- for (const item of role === "qa" ? acceptance : []) {
1502
- const descriptor = parseVerificationDescriptor(item.verification);
1503
- descriptors.set(item.id, descriptor);
1504
- }
1505
- // `git_base_head` is optional on the read shape because v3 records carry
1506
- // none, so the contract test alone does not prove it is present.
1507
- const baseHead = record.record.contract === "assurance_kernel/task_record/v4"
1508
- ? record.record.git_base_head
1509
- : undefined;
1510
- if (record.record.contract === "assurance_kernel/task_record/v4" && !baseHead)
1511
- throw new Error("TaskRecord v4 is missing its Enrollment git_base_head");
1512
- const reviewRevision = baseHead
1513
- ? {
1514
- contract: "assurance_kernel/review_revision_identity/v1" as const,
1515
- base_head: baseHead,
1516
- review_commit: "",
1517
- review_tree: "",
1518
- manifest_digest: "",
1519
- }
1520
- : null;
1521
- const reviewBundle = role === "review" && !reviewRevision
1522
- ? captureReviewBundle(
1523
- root,
1524
- intent.scope_hint,
1525
- projection.projection.diff_hash,
1526
- qaOutcomes(record.record),
1527
- )
1528
- : null;
1529
- const reviewManifest = role === "review" && reviewRevision
1530
- ? captureReviewManifest(root, {
1531
- taskId,
1532
- baseHead: reviewRevision.base_head,
1533
- scopeHint: intent.scope_hint,
1534
- expectedDiffHash: projection.projection.diff_hash,
1535
- intentRevision: projection.projection.intent_revision,
1536
- intentContentHash: projection.projection.intent_content_hash,
1537
- recordRevision: projection.projection.record_revision,
1538
- workspaceRevision: projection.projection.workspace_revision,
1539
- lifecycle: projection.projection.lifecycle,
1540
- artifactState: projection.projection.artifact_state,
1541
- risk: intent.risk,
1542
- outcomes: qaOutcomes(record.record),
1543
- })
1544
- : null;
1545
- const taskSnapshot = !reviewBundle && !reviewManifest
1546
- ? captureGitTaskSnapshot(root, intent.scope_hint, taskId)
1547
- : null;
1548
- const dirtyFiles = reviewManifest
1549
- ? Object.keys(reviewManifest.changed_paths)
1550
- : reviewBundle
1551
- ? Object.keys(reviewBundle.dirty_files)
1552
- : Object.keys(taskSnapshot!.staged_files);
1553
- return {
1554
- snapshot: buildSnapshot({
1555
- root,
1556
- task_id: taskId,
1557
- role,
1558
- run_id: projection.projection.run_id,
1559
- record_revision: projection.projection.record_revision,
1560
- workspace_revision: projection.projection.workspace_revision,
1561
- intent_revision: projection.projection.intent_revision,
1562
- intent_content_hash: projection.projection.intent_content_hash,
1563
- diff_hash: projection.projection.diff_hash,
1564
- lifecycle: projection.projection.lifecycle,
1565
- artifact_state: projection.projection.artifact_state,
1566
- risk: intent.risk,
1567
- fresh_acceptance_ids: projection.projection.fresh_acceptance_ids,
1568
- missing_acceptance_ids: projection.projection.missing_acceptance_ids,
1569
- stale_attestation_ids: projection.projection.stale_attestation_ids,
1570
- acceptance,
1571
- dirty_files: dirtyFiles,
1572
- review_bundle_digest: reviewManifest?.manifest_digest ?? reviewBundle?.bundle_digest ?? null,
1573
- review_revision: reviewManifest
1574
- ? {
1575
- contract: "assurance_kernel/review_revision_identity/v1",
1576
- base_head: reviewManifest.base_head,
1577
- review_commit: reviewManifest.review_commit,
1578
- review_tree: reviewManifest.review_tree,
1579
- manifest_digest: reviewManifest.manifest_digest,
1580
- }
1581
- : undefined,
1582
- }),
1583
- descriptors,
1584
- reviewBundle,
1585
- reviewManifest,
1586
- };
1587
- }
1588
-
1589
- function qaOutcomes(
1590
- record: NonNullable<TaskRecordRead["record"]>,
1591
- ): Record<string, { status: "passed" | "failed" | "blocked"; summary: string }> {
1592
- return Object.fromEntries(
1593
- record.attestations
1594
- .filter((item) => item.kind === "qa")
1595
- .flatMap((item) => item.acceptance_results)
1596
- .map((result) => [result.acceptance_id, { status: result.status, summary: result.summary }]),
1597
- );
1598
- }
1599
-
1600
1292
  // The invocation registry is shared with the progression module's
1601
1293
  // module-scoped registry (see the top-level import above); commit/cancel
1602
1294
  // semantics are identical to the previous extension implementation.
1603
1295
 
1296
+ const piConfirmationReference: ConfirmationReferenceSource = ({ snapshot, now }) =>
1297
+ `pi-confirm-${createHash("sha256").update(`${snapshot.task_id}\0${now}\0${snapshot.intent_revision}\0${snapshot.intent_content_hash}\0${snapshot.diff_hash}`).digest("hex").slice(0, 16)}`;
1298
+
1604
1299
  async function mintCapability(
1605
1300
  registry: MutationAuthorityRegistry,
1606
1301
  input: {
@@ -1682,7 +1377,7 @@ async function executeOrdinaryOperation(
1682
1377
  const operation = input.operation.op === "revise_intent"
1683
1378
  ? { ...input.operation, next_intent: await parseTaskIntentV1(input.operation.next_intent) }
1684
1379
  : input.operation;
1685
- const priorIntent = await readTaskIntent(ctx.cwd, input.taskId);
1380
+ const priorIntent = await readTaskIntentForRecord(ctx.cwd, input.taskId);
1686
1381
  const sidecar = join(ctx.cwd, priorIntent.intent_ref.path);
1687
1382
  const priorBytes = operation.op === "revise_intent" ? readFileSync(sidecar) : null;
1688
1383
  // A content-changing revision writes the sidecar before the kernel's drift
@@ -1703,7 +1398,7 @@ async function executeOrdinaryOperation(
1703
1398
  task_id: input.taskId,
1704
1399
  operation: operation as never,
1705
1400
  prior_intent_token: priorIntent.token,
1706
- diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
1401
+ diffProvider: (root: string, record: TaskRecord) => diffSnapshotOf(root, record),
1707
1402
  now: new Date().toISOString(),
1708
1403
  });
1709
1404
  if (operation.op === "freeze_artifacts" || operation.op === "stop")
@@ -1714,7 +1409,7 @@ async function executeOrdinaryOperation(
1714
1409
  return result;
1715
1410
  } catch (error) {
1716
1411
  if (priorBytes) {
1717
- const current = await readTaskRecord(ctx.cwd, input.taskId);
1412
+ const current = await readTaskRecordRaw(ctx.cwd, input.taskId);
1718
1413
  if (current.record?.intent_snapshot.revision === priorIntent.intent.revision && priorStaged) {
1719
1414
  restoreStagedIntentShared(ctx.cwd, priorStaged);
1720
1415
  }
@@ -1787,7 +1482,7 @@ async function enrichAssuranceResult(
1787
1482
  const taskState = { error: typeof result.reason === "string" ? result.reason : "authority projection unavailable" };
1788
1483
  return { ...result, task_state: taskState, next_action: nextActionForAssuranceResult(result, taskState) };
1789
1484
  }
1790
- const projection = await projectAssuranceState(ctx.cwd, taskId);
1485
+ const projection = await projectAssuranceForTask(ctx.cwd, taskId);
1791
1486
  const taskState: AssuranceTaskState = projection.error
1792
1487
  ? { error: projection.error }
1793
1488
  : projection.projection;
@@ -1888,33 +1583,6 @@ function prepareActionArgs<Params>(args: unknown): Params {
1888
1583
  return input as Params;
1889
1584
  }
1890
1585
 
1891
- function stagePlanningArtifactTransition(root: string, record: {
1892
- intent_ref: { path: string };
1893
- intent_snapshot: { scope_hint: string[] };
1894
- }): void {
1895
- const intentActive = record.intent_ref.path.replace("docs/plans/archive/", "docs/plans/");
1896
- const intentArchive = intentActive.replace("docs/plans/", "docs/plans/archive/");
1897
- const specActive = record.intent_snapshot.scope_hint.find((path) =>
1898
- /^docs\/specs\/(?!archive\/)[^/]+\.spec\.md$/.test(path)
1899
- && record.intent_snapshot.scope_hint.includes(path.replace("docs/specs/", "docs/specs/archive/")),
1900
- );
1901
- const candidates = [
1902
- intentActive,
1903
- intentArchive,
1904
- ...(specActive ? [specActive, specActive.replace("docs/specs/", "docs/specs/archive/")] : []),
1905
- ];
1906
- const paths = candidates.filter((path) => existsSync(join(root, path)) || execFileSync(
1907
- "git",
1908
- ["ls-files", "--cached", "--", path],
1909
- { cwd: root, encoding: "utf8" },
1910
- ).trim().length > 0);
1911
- if (paths.length === 0) return;
1912
- execFileSync("git", ["add", "--", ...paths], {
1913
- cwd: root,
1914
- stdio: ["ignore", "pipe", "pipe"],
1915
- });
1916
- }
1917
-
1918
1586
  function failCanaryTool(
1919
1587
  taskId: string,
1920
1588
  operation: string,