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
@@ -3,8 +3,17 @@
3
3
  // eligible when its Git-tracked TaskIntent is readable and no Kernel owner
4
4
  // (workspace, backend claim, TaskRecord v2, tombstone) blocks enrollment.
5
5
 
6
- import type { ReadinessReport } from "./readiness";
7
- import type { ReadinessEvidenceInput } from "./readiness_evidence";
6
+ // The readiness/evidence pipeline is retired with the authority-observation
7
+ // island. These two derived inputs stay on the eligibility input for caller
8
+ // compatibility only: they are ignored below and are never authority, so they
9
+ // are read structurally instead of importing the deleted modules.
10
+ export interface RetiredReadinessInput {
11
+ status: string;
12
+ }
13
+
14
+ export interface RetiredReadinessEvidenceInput {
15
+ status: string;
16
+ }
8
17
 
9
18
  export type WaivableGate = "observation_window_days";
10
19
 
@@ -26,8 +35,8 @@ export interface CanaryTaskIdentity {
26
35
  }
27
36
 
28
37
  export interface CanaryEligibilityInput {
29
- readiness?: ReadinessReport;
30
- evidence?: ReadinessEvidenceInput;
38
+ readiness?: RetiredReadinessInput;
39
+ evidence?: RetiredReadinessEvidenceInput;
31
40
  task: CanaryTaskIdentity;
32
41
  waiver?: CanaryWaiver;
33
42
  now: string;
@@ -1,4 +1,5 @@
1
1
  import { classifyTaskRisk } from "./intent";
2
+ import { archivePath, boundSpecPath } from "./spec_binding";
2
3
  import type {
3
4
  ApprovalKind,
4
5
  CompletionDecision,
@@ -7,7 +8,7 @@ import type {
7
8
  TaskProjectionV3,
8
9
  TaskRecord,
9
10
  } from "./types";
10
- import { assertKernelInvariantsV3, KernelInvariantError } from "./validation";
11
+ import { assertKernelInvariantsV3 } from "./validation";
11
12
  import { refutationIsLive } from "./refutation";
12
13
 
13
14
  const REQUIRED_ATTESTATIONS: Record<TaskIntentV1["risk"], ApprovalKind[]> = {
@@ -23,23 +24,13 @@ function archiveActivePlanningPath(path: string): string | null {
23
24
 
24
25
  function ownSidecarPaths(intent: TaskIntentV1): Set<string> {
25
26
  const activeIntent = `docs/plans/${intent.task_id}.intent.json`;
26
- const archivedIntent = archiveActivePlanningPath(activeIntent);
27
27
  const excluded = new Set<string>([activeIntent]);
28
+ const archivedIntent = archiveActivePlanningPath(activeIntent);
28
29
  if (archivedIntent) excluded.add(archivedIntent);
29
- const specs = intent.scope_hint.filter((path) => {
30
- const archived = archiveActivePlanningPath(path);
31
- return archived !== null && /^docs\/specs\/[^/]+\.spec\.md$/.test(path) && intent.scope_hint.includes(archived);
32
- });
33
- if (specs.length > 1) {
34
- throw new KernelInvariantError([
35
- `artifact transition requires at most one scope-bound Spec; found ${specs.length}`,
36
- ]);
37
- }
38
- const spec = specs[0];
30
+ const spec = boundSpecPath(intent);
39
31
  if (spec) {
40
32
  excluded.add(spec);
41
- const archived = archiveActivePlanningPath(spec);
42
- if (archived) excluded.add(archived);
33
+ excluded.add(archivePath(spec));
43
34
  }
44
35
  return excluded;
45
36
  }
@@ -3,7 +3,9 @@
3
3
  // for one confirmed canary task. Requires a valid EnrollmentCapability.
4
4
  // No CLI, runtime route, or production issuer exists in P2B0.
5
5
 
6
- import { canonicalIntentHash, readTaskIntent } from "./intent";
6
+ import { readRunRowByTask, withKernelRead } from "./sqlite_store";
7
+ import { readTaskIntent } from "./intent";
8
+ import { inspectSpecBinding } from "./spec_binding";
7
9
  import {
8
10
  type EnrollmentAuthorityRegistry,
9
11
  type EnrollmentCapabilityBinding,
@@ -12,12 +14,16 @@ import type {
12
14
  BatchAuthorityRegistry,
13
15
  BatchAuthorizationBinding,
14
16
  } from "./batch_authority";
15
- import { readTaskTombstone, type BackendClaim } from "./backend_claim";
17
+ import type { BackendClaim } from "./backend_claim";
16
18
  import { preparePiCanary, readGitHead } from "./pi_canary_prepare";
19
+ import { writeEnrollmentBaseline } from "../workspace_scope";
20
+ import { enrollmentRequestDigest } from "./run_identity";
17
21
  import {
18
22
  commitEnrollmentLocked,
23
+ readCommittedEnrollmentResult,
19
24
  readTaskRecordRaw,
20
25
  readWorkspaceStateRaw,
26
+ reconcileKernelAuthority,
21
27
  withKernelStoreLock,
22
28
  } from "./storage";
23
29
  import type { TaskRecord, TaskRecordV4, WorkspaceStateLike } from "./types";
@@ -113,10 +119,18 @@ function runEnrollmentPreconditionChecks<T>(
113
119
  blockers.push(report);
114
120
  };
115
121
 
116
- const tombstone = readTaskTombstone(root, input.task_id);
117
- if (tombstone) {
122
+ // Terminal protection is the *local* committed run, never the audit
123
+ // evidence: another worktree's run of the same logical task exports its
124
+ // own audit directory, and that evidence must not forbid a first
125
+ // enrollment here. Audit files remain readable as historical evidence for
126
+ // tasks this worktree has no run for (see reconcileKernelAuthority).
127
+ const localRun = reconcileKernelAuthority(root, input.task_id);
128
+ const localTerminal =
129
+ localRun.state === "terminal_owner" &&
130
+ withKernelRead(root, (db) => readRunRowByTask(db, input.task_id)) !== null;
131
+ if (localTerminal) {
118
132
  fail(
119
- "task tombstone exists; same-task re-enrollment is forbidden",
133
+ "local run is terminal; same-task re-enrollment is forbidden",
120
134
  new Error(`task ${input.task_id} is terminal; same-task re-enrollment is forbidden`),
121
135
  );
122
136
  } else {
@@ -138,6 +152,12 @@ function runEnrollmentPreconditionChecks<T>(
138
152
  } catch (error) {
139
153
  fail(`intent: ${error instanceof Error ? error.message : String(error)}`, error);
140
154
  }
155
+ // A simple Intent binds no Spec. A complex binding that names a Spec must
156
+ // be complete and unambiguous before any Executor turn.
157
+ if (intent) {
158
+ const binding = inspectSpecBinding(intent.intent);
159
+ if (!binding.ok) fail(binding.message, new Error(binding.message));
160
+ }
141
161
  try {
142
162
  gitBaseHead = readGitHead(root);
143
163
  } catch (error) {
@@ -219,20 +239,65 @@ export function runEnrollmentRehearsal(
219
239
  };
220
240
  }
221
241
 
242
+ function enrollmentEventId(taskId: string, now: string): string {
243
+ return `enroll-${taskId}-${now}`;
244
+ }
245
+
246
+ function digestForEnrollment(input: EnrollCanaryInput): {
247
+ eventId: string;
248
+ digest: string;
249
+ } {
250
+ const eventId = enrollmentEventId(input.task_id, input.now);
251
+ return {
252
+ eventId,
253
+ digest: enrollmentRequestDigest({
254
+ task_id: input.task_id,
255
+ intent_path: input.intent_path,
256
+ intent_revision: input.intent_revision,
257
+ intent_content_hash: input.capability_binding.intent_content_hash,
258
+ preparation_digest: input.preparation_digest,
259
+ enrollment_event_id: eventId,
260
+ actor_id: input.capability_binding.actor_id,
261
+ confirmation_ref: input.capability_binding.confirmation_ref,
262
+ nonce: input.capability_binding.nonce,
263
+ }),
264
+ };
265
+ }
266
+
222
267
  /**
223
268
  * Atomic canary enrollment. Runs inside the same store lock as v1/v2
224
269
  * transactions; consumes the capability only after every precondition
225
- * passes, immediately before writing the enrollment marker.
270
+ * passes, immediately before writing the enrollment marker. A lost
271
+ * response of the exact same request returns the committed result
272
+ * without repeating those first-execution checks.
226
273
  */
227
274
  export function enrollCanaryTask(
228
275
  root: string,
229
276
  input: EnrollCanaryInput,
230
277
  registry: EnrollmentAuthorityRegistry,
231
278
  ): EnrollCanaryResult {
279
+ const { eventId, digest } = digestForEnrollment(input);
280
+ // A committed enrollment answers the exact same request: the retry cannot
281
+ // pass the first-execution checks (the capability is consumed and the
282
+ // record now exists), so the durable operation is the only correct answer.
283
+ const replayed = readCommittedEnrollmentResult(root, input.task_id, eventId, digest);
284
+ if (replayed)
285
+ return {
286
+ record: replayed.record,
287
+ backend_claim: replayed.claim,
288
+ workspace: { revision: "", state: replayed.workspace },
289
+ };
290
+
232
291
  let gitBaseHead: string | null = null;
233
- return runEnrollmentPreconditionChecks(
234
- root,
235
- input,
292
+ // The batch child slot is consumed with the capability and handed back when
293
+ // the enrollment call fails. The commit happens at the outer store
294
+ // transaction, so any throw from this call means nothing was committed and
295
+ // the release has to wrap the whole call rather than only the record write.
296
+ let consumed = false;
297
+ try {
298
+ return runEnrollmentPreconditionChecks(
299
+ root,
300
+ input,
236
301
  input.capability,
237
302
  registry,
238
303
  "fail_fast",
@@ -250,6 +315,12 @@ export function enrollCanaryTask(
250
315
  (checks) => {
251
316
  if (!checks.validated || !checks.intent || !checks.workspace || !checks.current)
252
317
  throw new Error("enrollment precondition state incomplete");
318
+ // Recompute under the store lock: a confirmation bound to an older
319
+ // workspace revision must not commit after another enrollment and
320
+ // settlement raced between beforeLock and this callback.
321
+ const locked = preparePiCanary(root, { task_id: input.task_id, now: input.now });
322
+ if (locked.digest !== input.preparation_digest)
323
+ throw new Error("enrollment preparation digest mismatch");
253
324
  if (checks.intent.intent.revision !== input.intent_revision)
254
325
  throw new Error("intent revision mismatch");
255
326
  if (checks.intent.content_hash !== checks.validated.intent_content_hash)
@@ -285,8 +356,9 @@ export function enrollCanaryTask(
285
356
  );
286
357
  }
287
358
 
288
- // consume immediately before the marker write
359
+ // consume immediately before the store transaction
289
360
  registry.consume(input.capability, input.capability_binding);
361
+ consumed = true;
290
362
  if (input.batch)
291
363
  input.batch.registry.consumeChild(
292
364
  input.batch.capability,
@@ -310,37 +382,55 @@ export function enrollCanaryTask(
310
382
  task_id: input.task_id,
311
383
  intent_revision: input.intent_revision,
312
384
  intent_content_hash: checks.intent.content_hash,
313
- enrollment_event_id: `enroll-${input.task_id}-${input.now}`,
385
+ enrollment_event_id: eventId,
314
386
  lifecycle_status: "active",
315
387
  created_at: input.now,
316
388
  updated_at: input.now,
317
389
  };
318
- let mutation: ReturnType<typeof commitEnrollmentLocked>;
319
- try {
320
- mutation = commitEnrollmentLocked(
321
- root,
322
- input.task_id,
323
- {
324
- contract: "assurance_kernel/workspace_transaction/v2",
325
- task_id: input.task_id,
326
- expected_record_hash: checks.current.revision,
327
- next_record_content: `${JSON.stringify(record, null, 2)}\n`,
328
- expected_workspace_hash: checks.workspace.revision,
329
- next_workspace_content: `${JSON.stringify(nextWorkspace, null, 2)}\n`,
330
- },
331
- claim as unknown as Record<string, unknown>,
332
- );
333
- } catch (error) {
334
- // No TaskRecord was written, so the child slot must not stay used.
335
- if (input.batch)
336
- input.batch.registry.releaseChild(input.batch.capability, input.task_id);
337
- throw error;
338
- }
390
+ const mutation = commitEnrollmentLocked(
391
+ root,
392
+ input.task_id,
393
+ {
394
+ contract: "assurance_kernel/workspace_transaction/v2",
395
+ task_id: input.task_id,
396
+ expected_record_hash: checks.current.revision,
397
+ next_record_content: `${JSON.stringify(record, null, 2)}\n`,
398
+ expected_workspace_hash: checks.workspace.revision,
399
+ next_workspace_content: `${JSON.stringify(nextWorkspace, null, 2)}\n`,
400
+ },
401
+ claim as unknown as Record<string, unknown>,
402
+ digest,
403
+ );
404
+ writeEnrollmentBaseline(root);
339
405
  return {
340
406
  record: mutation.record,
341
407
  backend_claim: claim,
342
408
  workspace: { revision: "", state: mutation.workspace },
343
409
  };
344
- },
345
- );
410
+ },
411
+ );
412
+ } catch (error) {
413
+ // A child slot is released only when the enrollment provably did not
414
+ // commit. The envelope can also fail *after* the Kernel transaction
415
+ // committed — a follow-up transaction that cannot start, for example —
416
+ // and releasing then would leave the batch view ahead of an owner the
417
+ // Kernel already recorded. The committed run decides, not the throw.
418
+ const ownership = (() => {
419
+ try {
420
+ return {
421
+ known: true,
422
+ owned:
423
+ reconcileKernelAuthority(root, input.task_id).owner_task_id === input.task_id,
424
+ };
425
+ } catch {
426
+ return { known: false, owned: false };
427
+ }
428
+ })();
429
+ // Unknown is not "not committed". When the store cannot be read the slot
430
+ // stays consumed, so the batch view never runs ahead of a run that may
431
+ // exist, and the operator reconciles from the Kernel's own state.
432
+ if (consumed && input.batch && ownership.known && !ownership.owned)
433
+ input.batch.registry.releaseChild(input.batch.capability, input.task_id);
434
+ throw error;
435
+ }
346
436
  }
@@ -3,19 +3,27 @@
3
3
  // validation and projection remain here.
4
4
 
5
5
  import { createCapabilityRegistry } from "./capability_registry";
6
- import { MUTATION_AUTHORITY_CAPABILITY_BRAND } from "./types";
7
6
 
8
7
  export const ENROLLMENT_CAPABILITY_BRAND = Symbol.for("assurance-kernel.enrollment-capability-brand");
9
8
 
10
- export interface EnrollmentCapabilityBinding {
9
+ /**
10
+ * The three fields every capability binding shares, whichever authority issues
11
+ * it. `nonce` is deliberately not part of this base: the enrollment and batch
12
+ * bindings carry it for their own replay digest, while `CapabilityBindingV2`
13
+ * uses `action_digest` instead and has no `nonce` field at all.
14
+ */
15
+ export interface BaseCapabilityBinding {
16
+ actor_id: string;
17
+ confirmation_ref: string;
18
+ expires_at: string;
19
+ }
20
+
21
+ export interface EnrollmentCapabilityBinding extends BaseCapabilityBinding {
11
22
  task_id: string;
12
23
  intent_path: string;
13
24
  intent_revision: number;
14
25
  intent_content_hash: string;
15
26
  preparation_digest: string;
16
- actor_id: string;
17
- confirmation_ref: string;
18
- expires_at: string;
19
27
  nonce: string;
20
28
  }
21
29
 
@@ -1,8 +1,8 @@
1
1
  export * from "./types";
2
2
  export * from "./intent";
3
3
  export * from "./validation";
4
+ export * from "./legacy_task_record";
4
5
  export * from "./completion";
5
- export * from "./legacy";
6
6
  // v4 storage retirement: the v1 TaskRecord storage entry points are no
7
7
  // longer part of the production kernel surface. Only the v2 store read/commit
8
8
  // primitives (used by enrollment/rehearsal/audit) remain exported from
@@ -14,6 +14,8 @@ export {
14
14
  readSecureProjectFile,
15
15
  commitTaskRecordLocked,
16
16
  withKernelStoreLock,
17
+ withKernelStoreLockForTask,
18
+ probeKernelStore,
17
19
  serializeWorkspace,
18
20
  revisionForContent,
19
21
  appendJournalEntry,
@@ -118,7 +118,6 @@ function riskFloorForScope(scopeHint: string[]): TaskRisk | null {
118
118
  : null;
119
119
  }
120
120
 
121
- const SHA256_HEX = /^sha256:[a-f0-9]{64}$/;
122
121
  const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
123
122
  const portablePathCollator = new Intl.Collator("und", {
124
123
  usage: "search",
@@ -486,11 +485,9 @@ function resolveCanonicalRoot(root: string): string {
486
485
 
487
486
  // Resolve a path-less read to the sidecar that actually exists.
488
487
  //
489
- // The active path stays authoritative whenever it is present: that is the
490
- // pre-freeze layout every path-less caller assumes, and a leftover archived
491
- // sidecar from an earlier task reusing the same id must never shadow it. Only
492
- // when the active path is gone — the post-`freeze_artifacts` layout — does the
493
- // archive answer, which is exactly the case that used to fail with a raw ENOENT.
488
+ // The active path stays authoritative whenever it is present. A leftover
489
+ // archived sidecar from an earlier task reusing the same id must never shadow
490
+ // it. Only when the active path is gone does the historical archive answer.
494
491
  function resolveSidecarPath(
495
492
  canonicalRoot: string,
496
493
  activePath: string,
@@ -554,11 +551,10 @@ function readTaskIntentSource(
554
551
  const canonicalRoot = resolveCanonicalRoot(root);
555
552
  const activePath = `${INTENT_SIDECAR_RELATIVE_PREFIX}${taskId}.intent.json`;
556
553
  const archivedPath = `${INTENT_SIDECAR_RELATIVE_PREFIX}archive/${taskId}.intent.json`;
557
- // `freeze_artifacts` relocates the sidecar from the active path to the archive
558
- // path, so the caller's TaskRecord `intent_ref.path` is the authority. When no
559
- // path is requested, resolve the single sidecar that exists rather than
560
- // assuming the pre-freeze layout; a missing or ambiguous sidecar is a stable
561
- // contract failure, not a raw `lstat` ENOENT.
554
+ // Freeze binds the sidecar in place. The caller's TaskRecord `intent_ref.path`
555
+ // is the authority. When no path is requested, resolve the sidecar that exists
556
+ // (active, else historical archive). A missing sidecar is a stable contract
557
+ // failure, not a raw `lstat` ENOENT.
562
558
  const sidecarPath = requestedPath ?? resolveSidecarPath(canonicalRoot, activePath, archivedPath);
563
559
  if (sidecarPath !== activePath && sidecarPath !== archivedPath)
564
560
  throw new Error("intent sidecar path is not the active or archived task path");
@@ -7,13 +7,16 @@
7
7
  * observation, TaskRecord, or workspace state. It never imports, synthesizes,
8
8
  * or activates a Kernel TaskRecord from legacy data.
9
9
  *
10
+ * Removal milestone: read-only transitional code. It is deleted in the next
11
+ * major release, after the release that removes the retired file store.
12
+ *
10
13
  * Not exported from kernel/index.ts; reached only through the v4 CLI
11
14
  * `imm-kernel audit --legacy` surface.
12
15
  */
13
16
  import { createHash } from "node:crypto";
14
17
  import { lstatSync, readFileSync } from "node:fs";
15
18
  import { join, resolve } from "node:path";
16
- import { LEGACY_V3_RELATIVE, legacyV3Path } from "./storage_paths";
19
+ import { legacyV3Path } from "./storage_paths";
17
20
 
18
21
  const MAX_BYTES = 2 * 1024 * 1024;
19
22