immune-brain 5.2.0 → 5.5.1

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 (77) hide show
  1. package/README.md +4 -4
  2. package/README.zh-CN.md +4 -4
  3. package/package.json +2 -1
  4. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +54 -70
  6. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +163 -28
  7. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +71 -10
  8. package/plugins/immune-brain/.pi-extension/pi-canary-tool-failure.ts +1 -1
  9. package/plugins/immune-brain/.pi-extension/pi-run-continuation.ts +161 -0
  10. package/plugins/immune-brain/agents/immune-brain-reviewer.md +26 -2
  11. package/plugins/immune-brain/dist/BASELINE.md +17 -1
  12. package/plugins/immune-brain/dist/claude/mcp-server.mjs +16145 -12745
  13. package/plugins/immune-brain/dist/docs/reference/code-quality-guard.md +3 -1
  14. package/plugins/immune-brain/dist/docs/reference/immune-brain-config.md +41 -1
  15. package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
  16. package/plugins/immune-brain/dist/imm-brainstorm.md +1 -1
  17. package/plugins/immune-brain/dist/imm-planner.md +63 -19
  18. package/plugins/immune-brain/dist/imm-run-batch.md +398 -0
  19. package/plugins/immune-brain/dist/imm-run.md +89 -344
  20. package/plugins/immune-brain/dist/role-prompts/code-review.md +26 -2
  21. package/plugins/immune-brain/dist/role-prompts/executor.md +35 -10
  22. package/plugins/immune-brain/hooks/hooks.json +22 -2
  23. package/plugins/immune-brain/runtime/assurance/coordinator.ts +203 -13
  24. package/plugins/immune-brain/runtime/assurance/delivery_workspace.ts +2 -1
  25. package/plugins/immune-brain/runtime/assurance/enrollment_git_base.ts +99 -14
  26. package/plugins/immune-brain/runtime/assurance/phase_entry.ts +10 -26
  27. package/plugins/immune-brain/runtime/assurance/qa.ts +126 -43
  28. package/plugins/immune-brain/runtime/assurance/qa_findings.ts +8 -0
  29. package/plugins/immune-brain/runtime/assurance/review_evidence.ts +48 -0
  30. package/plugins/immune-brain/runtime/assurance/run_continuation.ts +284 -0
  31. package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +46 -4
  32. package/plugins/immune-brain/runtime/assurance/verification.ts +83 -35
  33. package/plugins/immune-brain/runtime/assurance/workflow_autocommit.ts +222 -0
  34. package/plugins/immune-brain/runtime/claude/interaction.ts +11 -2
  35. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +199 -48
  36. package/plugins/immune-brain/runtime/claude/lane_guard.ts +30 -20
  37. package/plugins/immune-brain/runtime/claude/mcp_server.ts +105 -9
  38. package/plugins/immune-brain/runtime/claude/run_continuation.ts +274 -0
  39. package/plugins/immune-brain/runtime/commands/kernel.ts +41 -103
  40. package/plugins/immune-brain/runtime/github_issue_tracker.ts +60 -2
  41. package/plugins/immune-brain/runtime/kernel/application.ts +4 -1
  42. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +103 -20
  43. package/plugins/immune-brain/runtime/kernel/canary_application.ts +57 -7
  44. package/plugins/immune-brain/runtime/kernel/completion.ts +28 -4
  45. package/plugins/immune-brain/runtime/kernel/enrollment.ts +48 -8
  46. package/plugins/immune-brain/runtime/kernel/intent.ts +169 -104
  47. package/plugins/immune-brain/runtime/kernel/reducer.ts +136 -6
  48. package/plugins/immune-brain/runtime/kernel/risk_floor.ts +286 -0
  49. package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +83 -1
  50. package/plugins/immune-brain/runtime/kernel/storage_paths.ts +1 -1
  51. package/plugins/immune-brain/runtime/kernel/types.ts +49 -3
  52. package/plugins/immune-brain/runtime/kernel/validation.ts +147 -7
  53. package/plugins/immune-brain/runtime/loop_contract.ts +5 -0
  54. package/plugins/immune-brain/runtime/plan_core.ts +15 -8
  55. package/plugins/immune-brain/runtime/plugin_source_skew.ts +58 -0
  56. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  57. package/plugins/immune-brain/runtime/prompts/code-review.md +26 -2
  58. package/plugins/immune-brain/runtime/prompts/executor.md +35 -10
  59. package/plugins/immune-brain/runtime/unattended/batch_delegation.ts +5 -3
  60. package/plugins/immune-brain/runtime/unattended/batch_disposition.ts +40 -10
  61. package/plugins/immune-brain/runtime/unattended/batch_final_verification.ts +57 -3
  62. package/plugins/immune-brain/runtime/unattended/batch_git.ts +424 -66
  63. package/plugins/immune-brain/runtime/unattended/batch_integration.ts +2 -1
  64. package/plugins/immune-brain/runtime/unattended/batch_kernel_port.ts +13 -0
  65. package/plugins/immune-brain/runtime/unattended/batch_lanes.ts +43 -24
  66. package/plugins/immune-brain/runtime/unattended/batch_plan.ts +6 -2
  67. package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +102 -41
  68. package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +9 -3
  69. package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +9 -9
  70. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +90 -32
  71. package/plugins/immune-brain/runtime/unattended/batch_state.ts +102 -4
  72. package/plugins/immune-brain/runtime/unattended/parent_delivery_consolidation.ts +248 -0
  73. package/plugins/immune-brain/runtime/workspace_scope.ts +126 -18
  74. package/plugins/immune-brain/skills/BASELINE.md +17 -1
  75. package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +4 -3
  76. package/plugins/immune-brain/skills/imm-planner/SKILL.md +8 -2
  77. package/plugins/immune-brain/skills/imm-run/SKILL.md +12 -8
package/README.md CHANGED
@@ -556,9 +556,9 @@ Key invariants:
556
556
 
557
557
  ## Unattended Batch Runs
558
558
 
559
- When an Initiative contains multiple ready children, developers can run the entire batch unattended via the Host's privileged `start_unattended_batch` tool without manually driving `imm-run` for each child task.
559
+ Enter `imm-run <Parent Issue>` to execute its complete approved plan through the Host's `start_unattended_batch` tool and native authorization. Enter `imm-run <Slice/Child Issue>` to execute only that Slice after prerequisite delivery checks; it starts no siblings or predecessors.
560
560
 
561
- Immune-Brain supports two batch execution modes: **Default Serial Mode** and **Parallel Batch Lanes**.
561
+ The Parent chooses **Managed Serial Mode** or **Parallel Batch Lanes** before confirmation, using dependency readiness, scope conflicts, Lane capacity and supervised Host capability. Without Lane supervision it runs serially in the same Host. Status questions preserve the binding; explicit cancellation releases continuation.
562
562
 
563
563
  ### 1. Batch Execution Modes Overview
564
564
 
@@ -573,9 +573,9 @@ Immune-Brain supports two batch execution modes: **Default Serial Mode** and **P
573
573
 
574
574
  ### 2. Batch Authority & Invariants
575
575
 
576
- - **Explicit Entry & Single Authorization:** No batch state or branch exists before calling `start_unattended_batch`; without it, `imm-run` behaves identically to single-task enrollment. The native gate (Pi TUI dialog or Claude MCP elicitation) displays the ordered child list and global `plan_digest`; this single user confirmation constitutes complete Batch Authorization.
576
+ - **Explicit Entry & Single Authorization:** Parent selection invokes `start_unattended_batch` directly; a standalone Task or Child uses single-task Enrollment. Critical-only and delivered-only Parents use native task gates and final observations without an empty batch grant. The native gate (Pi TUI dialog or Claude MCP elicitation) displays the ordered child list and global `plan_digest`; this single user confirmation constitutes complete Batch Authorization.
577
577
  - **Child Authority Invariance:** A batch merely bundles authorization scope without weakening engineering rigor. Every child is still individually enrolled, scope-frozen, QA-verified via authentic child processes, reviewed in isolation, and settled with its own `TaskRecord`. A batch is a scope of authorization, not a new authority layer.
578
- - **Deterministic Boundaries:** Only runs published, non-`critical` children. Any required human decision (`needs_human`), budget/timeout interruption, or integration error halts the batch safely, marking dependent children as `skipped_blocked` rather than reordering. Runners never push, open PRs, or make arbitrary decisions.
578
+ - **Deterministic Boundaries:** Ordinary children use batch authorization; ready `critical` children use separate native Enrollment. Ordinary failures receive bounded repair. A blocker parks its dependent branch while independent work continues. Parent success and closure require every necessary delivery integrated and final verification passing on the final HEAD; later code defects receive a validated repair candidate and fresh native Enrollment. Runners never push, open PRs, or make arbitrary decisions.
579
579
 
580
580
  ### 3. Parallel Batch Lanes Lifecycle & Architecture
581
581
 
package/README.zh-CN.md CHANGED
@@ -559,9 +559,9 @@ flowchart TD
559
559
 
560
560
  ## 无人值守批次运行
561
561
 
562
- 当一个 Initiative 下包含多个已就绪的 child 任务时,开发者无需逐个手动触发 `imm-run`,可通过 Host 原生 privileged tool `start_unattended_batch` 进行整批无人值守推进。
562
+ 输入 `imm-run <Parent Issue>`,会自动选择完整已批准计划,通过 Host 的 `start_unattended_batch` 和 native 授权执行。输入 `imm-run <Slice/Child Issue>` 则先检查前置交付,只执行该 Slice,不启动前置或兄弟任务。
563
563
 
564
- 系统支持两种批次运行形态:**默认串行模式** 与 **多 Lane 并行模式(Parallel Batch Lanes)**。
564
+ Parent 在确认前根据依赖、scope 冲突、Lane 容量及受监督 Host 能力,选择 **Managed 串行模式** 或 **多 Lane 并行模式**;缺少 Lane supervision 时在同一 Host 串行执行。状态询问保留运行绑定,显式取消解除续接。
565
565
 
566
566
  ### 1. 批次运行模式概览
567
567
 
@@ -576,9 +576,9 @@ flowchart TD
576
576
 
577
577
  ### 2. 批次授权与运行红线
578
578
 
579
- - **入口显式且单一授权:** 未调用 `start_unattended_batch` 之前不存在任何批次状态或分支;未调用时 `imm-run` 行为与逐任务 Enrollment 完全一致。调用时由原生 gate(Pi TUI 弹窗或 Claude MCP elicitation)一次性展示有序任务清单与全局 `plan_digest`,用户的单次确认即为整批执行提供完备授权。
579
+ - **入口显式且单一授权:** Parent 入口直接调用 `start_unattended_batch`;独立 Task 和 Child 走单任务 Enrollment。critical-only 和 delivered-only Parent 使用单任务 native gate 和最终验证观察,不制造空批次授权。调用时由原生 gate(Pi TUI 弹窗或 Claude MCP elicitation)一次性展示有序任务清单与全局 `plan_digest`,用户的单次确认即为整批执行提供完备授权。
580
580
  - **Child Authority 守恒:** 批次仅代表授权范围的聚合,绝非降低工程标准。每个 child 依然由 Kernel 单独执行 Enrollment、物理 Scope 冻结、真实子进程 QA、隔离 Review 与独立的 `TaskRecord` 结算。批次只是一次授权的覆盖范围,不是新的授权层级。
581
- - **确定性执行边界:** 仅推进已发布且非 `critical` 的 child 任务;遇到人工决策挂起(`needs_human`)、预算/超时中断或提交异常时立即安全暂停批次,被阻断任务的下游依赖标记为跳过(`skipped_blocked`)而非无序推进。批次运行器不擅自 push、不开 PR、不代替用户结算决策。
581
+ - **确定性执行边界:** 普通 child 使用批次授权,已就绪的 `critical` child 单独 native Enrollment。普通失败自动进行有界修复;阻塞只停放其依赖分支,独立任务继续。所有必需交付集成、最终 HEAD 验证通过后才报告 Parent 成功并关闭 Issue;结算后发现的代码缺陷先生成、验证修复候选,再打开新的 native Enrollment。批次运行器不擅自 push、不开 PR、不代替用户结算决策。
582
582
 
583
583
  ### 3. 多 Lane 并行流水线与架构(Parallel Batch Lanes)
584
584
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "5.2.0",
3
+ "version": "5.5.1",
4
4
  "description": "Immune-Brain agent skill system",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -83,6 +83,7 @@
83
83
  "plugins/immune-brain/runtime/role_prompt_bridge.ts",
84
84
  "plugins/immune-brain/runtime/loop_contract.ts",
85
85
  "plugins/immune-brain/runtime/plugin_version.ts",
86
+ "plugins/immune-brain/runtime/plugin_source_skew.ts",
86
87
  "plugins/immune-brain/runtime/assurance",
87
88
  "plugins/immune-brain/runtime/unattended",
88
89
  "plugins/immune-brain/runtime/claude",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "5.2.0",
3
+ "version": "5.5.1",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -15,16 +15,20 @@ import { resolve } from "node:path";
15
15
  // from the Kernel's own prepare module.
16
16
  import { LITERAL_USER_ACTOR_ID } from "../runtime/kernel/actor_identity";
17
17
  import { createEnrollmentAuthorityRegistry } from "../runtime/kernel/enrollment_authority";
18
+ import { assertIsolatedTaskPrerequisites } from "../runtime/github_issue_tracker";
18
19
  import { preparePiCanary, revalidatePiCanary } from "../runtime/assurance/enrollment";
19
20
  import { evaluateCanaryEligibility } from "../runtime/kernel/canary_eligibility";
20
- import { enrollTask, EnrollmentRehearsalError } from "../runtime/kernel/enrollment";
21
+ import { enrollmentScopeDisclosure, enrollTask, EnrollmentRehearsalError } from "../runtime/kernel/enrollment";
21
22
  import { reconcileKernelAuthority, withKernelStoreLock } from "../runtime/kernel/storage";
22
23
  import { inspectStorageLayout } from "../runtime/kernel/storage_paths";
23
- import { migrateLegacyLayout } from "../runtime/kernel/storage_layout_migration";
24
+ import { commitPendingMigrationDiff, migrateAndCommitLegacyLayout } from "../runtime/kernel/storage_layout_migration";
24
25
  import {
26
+ commitEnrollmentPreconditions,
27
+ commitUnbornEnrollmentPreconditions,
25
28
  inspectEnrollmentGitBase,
26
29
  enrollmentGitBaseNotice,
27
30
  initializeEnrollmentGitBase,
31
+ preconditionCommitNote,
28
32
  } from "../runtime/assurance/enrollment_git_base";
29
33
  import { piRunEntryRefusal, readTaskIntentForRecord } from "./imm-canary-work";
30
34
  import {
@@ -280,6 +284,8 @@ async function classifyCommitFailure(
280
284
  );
281
285
  }
282
286
 
287
+ const terminalWithoutCommitNote = terminal;
288
+
283
289
  async function executeForegroundEnrollment(
284
290
  root: string,
285
291
  action: EnrollmentAction,
@@ -293,6 +299,14 @@ async function executeForegroundEnrollment(
293
299
  ): Promise<EnrollmentTerminal> {
294
300
  let stage = "preparing";
295
301
  let gitBaseNote = "";
302
+ // Every unsuccessful exit reports the Git effects that preceded authority.
303
+ const terminal: typeof terminalWithoutCommitNote = (action, taskId, state, stage, summary, nextAction) => {
304
+ if (state !== "completed" && gitBaseNote) {
305
+ if (summary.endsWith(gitBaseNote)) summary = summary.slice(0, -gitBaseNote.length);
306
+ summary = `${summary.slice(0, 4_096 - gitBaseNote.length)}${gitBaseNote}`;
307
+ }
308
+ return terminalWithoutCommitNote(action, taskId, state, stage, summary, nextAction);
309
+ };
296
310
  const progress = (nextStage: string, summary: string) => {
297
311
  stage = nextStage;
298
312
  const update = updateResult(action, taskId, nextStage, summary);
@@ -314,75 +328,32 @@ async function executeForegroundEnrollment(
314
328
  const now = new Date().toISOString();
315
329
  let taskIntent: Awaited<ReturnType<typeof readTaskIntentForRecord>>;
316
330
 
317
- // Storage-layout gate (BR-REQ-005/006): enrollment is the stateful
318
- // mutation boundary. Kernel transaction markers recover under the
319
- // store lock; the one-release migrator relocates an owner-free legacy
320
- // layout and stops, and the literal user retries after committing the
321
- // affected migration diff. Repreparation never proceeds on a dirty
322
- // or blocked layout.
331
+ // Recover markers first; only a pending diff or supported legacy import may advance.
323
332
  {
324
- const recoverLayout = async (): Promise<
325
- EnrollmentTerminal | undefined
326
- > => {
327
- try {
328
- await withKernelStoreLock(root, () => undefined);
329
- } catch (error) {
330
- return terminal(
331
- action,
332
- taskId,
333
- "blocked",
334
- stage,
335
- `Kernel transaction recovery failed before enrollment: ${errorMessage(error)}`,
336
- "resolve the pending Kernel transaction marker and retry",
337
- );
338
- }
339
- const inspection = await inspectStorageLayout(root);
340
- if (inspection.layout === "ready") return undefined;
341
- if (
342
- inspection.layout === "migration_required" ||
343
- inspection.layout === "recovery_required"
344
- ) {
345
- const migration = await migrateLegacyLayout(root);
346
- if (migration.outcome === "migrated")
347
- return terminal(
348
- action,
349
- taskId,
350
- "blocked",
351
- stage,
352
- `Legacy storage migrated (${migration.affected_paths.length} paths); commit the affected migration diff and retry enrollment`,
353
- "commit .imm/audit/** and .imm/legacy paths, then retry",
354
- );
355
- if (migration.outcome === "migration_uncommitted")
356
- return terminal(
357
- action,
358
- taskId,
359
- "blocked",
360
- stage,
361
- `Migration completed but affected paths are uncommitted: ${migration.affected_paths.join(", ")}`,
362
- "commit the affected migration diff, then retry",
363
- );
364
- return terminal(
365
- action,
366
- taskId,
367
- "blocked",
368
- stage,
369
- `Enrollment blocked by storage layout (${migration.outcome}): ${migration.reason ?? inspection.reason ?? ""}`,
370
- "resolve the reported layout condition and retry",
371
- );
372
- }
373
- return terminal(
374
- action,
375
- taskId,
376
- "blocked",
377
- stage,
378
- `Enrollment blocked by storage layout (${inspection.layout}): ${inspection.reason ?? ""}`,
379
- "resolve the reported layout condition and retry",
380
- );
381
- };
382
- const blocked = await recoverLayout();
383
- if (blocked) return blocked;
333
+ let inspection = inspectStorageLayout(root);
334
+ if (inspection.layout === "migration_uncommitted") {
335
+ gitBaseNote += preconditionCommitNote(commitPendingMigrationDiff(root, signal));
336
+ inspection = inspectStorageLayout(root);
337
+ }
338
+ if (inspection.layout === "migration_required") {
339
+ const migration = migrateAndCommitLegacyLayout(root, signal);
340
+ for (const commit of migration.commits)
341
+ gitBaseNote += preconditionCommitNote({ committed: true, commit, paths: migration.affected_paths });
342
+ if (migration.outcome !== "migrated" && migration.outcome !== "already_migrated")
343
+ return terminal(action, taskId, "blocked", stage, `Enrollment blocked by storage layout (${migration.outcome}): ${migration.reason ?? ""}`, "resolve the reported layout condition and retry");
344
+ inspection = inspectStorageLayout(root);
345
+ }
346
+ if (inspection.layout !== "ready")
347
+ return terminal(action, taskId, "blocked", stage, `Enrollment blocked by storage layout (${inspection.layout}): ${inspection.reason ?? ""}`, "resolve the reported layout condition and retry");
348
+ await withKernelStoreLock(root, () => undefined);
384
349
  }
385
350
 
351
+ // Commit the task's own dirty preconditions first, so every read and the
352
+ // confirmation below bind the HEAD that results. An owned workspace
353
+ // commits nothing.
354
+ gitBaseNote += preconditionCommitNote(await commitEnrollmentPreconditions(root, taskId, signal));
355
+ signal.throwIfAborted();
356
+
386
357
  try {
387
358
  taskIntent = await readTaskIntentForRecord(root, taskId);
388
359
  } catch (error) {
@@ -438,6 +409,7 @@ async function executeForegroundEnrollment(
438
409
  `task ${taskId} is terminal in this worktree`,
439
410
  "inspect authority state; do not retry enrollment",
440
411
  );
412
+ await assertIsolatedTaskPrerequisites(root, taskId);
441
413
  let preparation = await preparePiCanary(root, { task_id: taskId, now });
442
414
  signal.throwIfAborted();
443
415
  if (!preparation.intent)
@@ -479,6 +451,9 @@ async function executeForegroundEnrollment(
479
451
  // Enrollment binds authority to the validated intent. Acceptance descriptors
480
452
  // execute only during post-implementation deterministic QA.
481
453
  const confirmedContentHash = preparation.intent.content_hash;
454
+ // In-scope changes that already exist are shown in the gate and only the
455
+ // ones shown are claimed as task work on confirmation.
456
+ const disclosure = enrollmentScopeDisclosure(root, taskId);
482
457
  {
483
458
  const confirmationSummary = [
484
459
  `Task: ${taskId}`,
@@ -487,6 +462,7 @@ async function executeForegroundEnrollment(
487
462
  `Scope: ${taskIntent.intent.scope_hint.length > 0 ? taskIntent.intent.scope_hint.join(", ") : "(none)"}`,
488
463
  `Acceptance: ${taskIntent.intent.acceptance.length} descriptor(s)`,
489
464
  ...(gitNotice ? [gitNotice] : []),
465
+ ...(disclosure.notice ? [disclosure.notice] : []),
490
466
  ].join("\n");
491
467
  const confirmationDetails = [
492
468
  `Acceptance descriptors:`,
@@ -510,7 +486,7 @@ async function executeForegroundEnrollment(
510
486
  );
511
487
  if (signal.aborted) return cancelled();
512
488
  if (!confirmed)
513
- return terminal(action, taskId, "rejected", stage, "Enrollment confirmation was rejected; zero authority writes were requested", "invoke the launcher again only if enrollment is still intended");
489
+ return terminal(action, taskId, "rejected", stage, `Enrollment confirmation was rejected; zero authority writes were requested${gitBaseNote}`, "invoke the launcher again only if enrollment is still intended");
514
490
  }
515
491
  {
516
492
  let liveEarly: string | null = null;
@@ -541,7 +517,14 @@ async function executeForegroundEnrollment(
541
517
  signal.throwIfAborted();
542
518
 
543
519
  preparation = await initializeEnrollmentGitBase(root, { task_id: taskId, now }, preparation, gitBase, signal);
544
- if (gitBase.state === "unborn") gitBaseNote = `; empty initial commit ${preparation.git_base_head} remains`;
520
+ if (gitBase.state === "unborn") {
521
+ gitBaseNote += `; empty initial commit ${preparation.git_base_head} remains`;
522
+ // The scope could not be compared with an unborn HEAD; commit it now
523
+ // that a base exists and bind the HEAD that results.
524
+ const late = await commitUnbornEnrollmentPreconditions(root, { task_id: taskId, now }, preparation, signal);
525
+ gitBaseNote += preconditionCommitNote(late.result);
526
+ preparation = late.preparation;
527
+ }
545
528
  if (!preparation.intent) throw new Error("Enrollment requires a readable TaskIntent");
546
529
  signal.throwIfAborted();
547
530
 
@@ -565,6 +548,7 @@ async function executeForegroundEnrollment(
565
548
  // zero writes, and once it accepts, commit is no longer cancellable.
566
549
  const outcome = await enrollTask(root, registry, {
567
550
  binding,
551
+ disclosed_scope_paths: disclosure.paths,
568
552
  now,
569
553
  checkpoint: () => {
570
554
  if (signal.aborted) return false;