immune-brain 4.4.1 → 4.6.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 (51) hide show
  1. package/README.md +3 -2
  2. package/package.json +1 -1
  3. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  4. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +0 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +47 -2
  6. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +23 -18
  7. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +0 -3
  8. package/plugins/immune-brain/dist/BASELINE.md +18 -1
  9. package/plugins/immune-brain/dist/claude/mcp-server.mjs +1401 -1101
  10. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +30 -0
  11. package/plugins/immune-brain/dist/imm-brainstorm.md +21 -0
  12. package/plugins/immune-brain/dist/imm-loop.md +52 -6
  13. package/plugins/immune-brain/dist/imm-planner.md +102 -13
  14. package/plugins/immune-brain/dist/role-prompts/code-review.md +19 -0
  15. package/plugins/immune-brain/dist/role-prompts/qa.md +13 -1
  16. package/plugins/immune-brain/runtime/assurance/coordinator.ts +55 -2
  17. package/plugins/immune-brain/runtime/assurance/review_evidence.ts +4 -0
  18. package/plugins/immune-brain/runtime/claude/interaction.ts +1 -2
  19. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +73 -27
  20. package/plugins/immune-brain/runtime/claude/mcp_server.ts +3 -4
  21. package/plugins/immune-brain/runtime/claude/review_host.ts +15 -0
  22. package/plugins/immune-brain/runtime/github_issue_tracker.ts +956 -634
  23. package/plugins/immune-brain/runtime/kernel/application.ts +2 -5
  24. package/plugins/immune-brain/runtime/kernel/authority_port.ts +5 -14
  25. package/plugins/immune-brain/runtime/kernel/batch_authority.ts +30 -49
  26. package/plugins/immune-brain/runtime/kernel/canary_application.ts +1 -2
  27. package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +0 -1
  28. package/plugins/immune-brain/runtime/kernel/capability_registry.ts +7 -9
  29. package/plugins/immune-brain/runtime/kernel/enrollment.ts +21 -12
  30. package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +5 -11
  31. package/plugins/immune-brain/runtime/kernel/storage.ts +8 -3
  32. package/plugins/immune-brain/runtime/kernel/types.ts +2 -1
  33. package/plugins/immune-brain/runtime/kernel/validation.ts +3 -1
  34. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  35. package/plugins/immune-brain/runtime/prompts/code-review.md +19 -0
  36. package/plugins/immune-brain/runtime/prompts/qa.md +13 -1
  37. package/plugins/immune-brain/runtime/unattended/batch_git.ts +9 -1
  38. package/plugins/immune-brain/runtime/unattended/batch_plan.ts +3 -9
  39. package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +152 -46
  40. package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +0 -6
  41. package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +193 -0
  42. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +112 -122
  43. package/plugins/immune-brain/runtime/unattended/batch_state.ts +57 -16
  44. package/plugins/immune-brain/runtime/unattended/types.ts +0 -2
  45. package/plugins/immune-brain/runtime/v4_runtime.ts +54 -0
  46. package/plugins/immune-brain/runtime/workspace_scope.ts +75 -12
  47. package/plugins/immune-brain/skills/BASELINE.md +18 -1
  48. package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +5 -0
  49. package/plugins/immune-brain/skills/imm-loop/SKILL.md +4 -2
  50. package/plugins/immune-brain/skills/imm-planner/SKILL.md +10 -1
  51. package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +0 -33
package/README.md CHANGED
@@ -301,7 +301,8 @@ When an Initiative has several ready children, you can run them as one serial ba
301
301
  - **Entry is explicit:** the Host's privileged `start_unattended_batch` tool, taking the Initiative slug. Nothing batch-related exists until it is called — without it, `imm-loop` behaves exactly like per-task enrollment and creates no batch state, branch, or authorization.
302
302
  - **One confirmation, one digest:** the native gate (Pi TUI dialog or Claude MCP elicitation) shows the ordered child list and the shared plan digest; that single literal-user act is the whole Batch Authorization.
303
303
  - **Per-child authority survives:** every child is still enrolled, frozen, QA'd, reviewed, and settled by the Kernel on its own `TaskRecord`. The batch is the scope of one authorization, never a new authority layer.
304
- - **Bounds:** only published, non-`critical` children run, serially on a dedicated batch branch. The run parks when a child needs a human decision or a budget, deadline, authorization, or commit failure stops it, and dependents of a blocked child are skipped rather than reordered. The runner never pushes, opens PRs, resolves user decisions, or creates, switches, or deletes Git worktrees.
304
+ - **Closeout is automatic:** when a child reaches `done` in the foreground, the same call commits it and enrolls the next child, or marks the batch `completed`, with no new gate. A parked or stopped child is never committed for you, and a failed continuation is reported beside the result with `start_unattended_batch` as the retry. A fast-forward commit you add to the batch branch is adopted; any other HEAD movement still stops the run.
305
+ - **Bounds:** only published, non-`critical` children run, serially on a dedicated batch branch. The run parks when a child needs a human decision or a budget, authorization, or commit failure stops it; the budget is a child count and a QA failure limit, and nothing expires with time, and dependents of a blocked child are skipped rather than reordered. The runner never pushes, opens PRs, resolves user decisions, or creates, switches, or deletes Git worktrees.
305
306
 
306
307
  ---
307
308
 
@@ -361,7 +362,7 @@ docs/specs/ # Living specs (updated in place)
361
362
 
362
363
  **A review finding stopped blocking — why?** It was refuted: fresh deterministic QA evidence shows the acceptance it names passes. The refutation is bound to that exact evidence, so the finding blocks again the moment the evidence goes stale for the current revision, intent hash, or diff.
363
364
 
364
- **Can it run a whole Initiative without me?** Only as far as you authorize. Confirm `start_unattended_batch` with the Initiative slug and the runner works through the published, non-`critical` children serially on one batch branch — parking as soon as a child needs a human decision or the run hits a budget, deadline, authorization, or commit failure. It never pushes, opens PRs, or settles user decisions for you.
365
+ **Can it run a whole Initiative without me?** Only as far as you authorize. Confirm `start_unattended_batch` with the Initiative slug and the runner works through the published, non-`critical` children serially on one batch branch — parking as soon as a child needs a human decision or the run hits a budget, authorization, or commit failure. A parked run waits for you indefinitely: neither the confirmation dialog nor the authorization it grants times out. It never pushes, opens PRs, or settles user decisions for you.
365
366
 
366
367
  **Can I switch between hosts (e.g. plan in Claude Code, code in Pi)?** Yes. Immune-Brain's contracts and state live entirely on disk in the repository, decoupled from conversation sessions. You can leverage Claude Code for deep architectural thinking and Spec planning, then switch to Pi to run `imm-loop` for code execution and deterministic QA. Interrupted tasks can be resumed in either host at any time.
367
368
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "4.4.1",
3
+ "version": "4.6.0",
4
4
  "description": "Immune-Brain agent skill system",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "4.4.1",
3
+ "version": "4.6.0",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -557,7 +557,6 @@ async function executeForegroundEnrollment(
557
557
  preparation_digest: preparation.digest,
558
558
  actor_id: LITERAL_USER_ACTOR_ID,
559
559
  confirmation_ref: `pi-confirm-${createHash("sha256").update(`${taskId}\0${now}\0${nonce}`).digest("hex").slice(0, 16)}`,
560
- expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
561
560
  nonce,
562
561
  };
563
562
  const capability = registry.issue(binding);
@@ -21,6 +21,8 @@ import { join, resolve } from "node:path";
21
21
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
22
22
  import { Type } from "typebox";
23
23
  import { PLUGIN_VERSION } from "../runtime/plugin_version";
24
+ import { findResumableBatchSlugForTask } from "../runtime/unattended/batch_preflight";
25
+ import { executePiUnattendedBatch } from "./imm-unattended-batch";
24
26
  import {
25
27
  parseVerificationDescriptor,
26
28
  type VerificationDescriptor,
@@ -581,7 +583,11 @@ export default function (
581
583
  : action.op === "request_stop"
582
584
  ? await authorizeExactOperation(taskId, "stop", { ...ctx, signal: signal && ctx.signal ? AbortSignal.any([signal, ctx.signal]) : signal ?? ctx.signal })
583
585
  : await requestAuthorization(taskId, ctx);
584
- const enriched = await enrichAssuranceResult(ctx, taskId, result as unknown as Record<string, unknown>);
586
+ const enriched = await closeOutBatchChild(
587
+ ctx,
588
+ taskId,
589
+ await enrichAssuranceResult(ctx, taskId, result as unknown as Record<string, unknown>),
590
+ );
585
591
  presentTaskRailResult(ctx, taskId, enriched);
586
592
  throwIfCanaryToolFailure(taskId, action.op, enriched);
587
593
  return toolResult(JSON.stringify(enriched, null, 2), enriched);
@@ -1648,7 +1654,6 @@ async function mintCapability(
1648
1654
  confirmation_ref: `pi-confirm-${createHash("sha256").update(
1649
1655
  `${input.task_id}\0${now}\0${input.intent_revision}\0${input.intent_content_hash}\0${input.diff_hash}`,
1650
1656
  ).digest("hex").slice(0, 16)}`,
1651
- expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
1652
1657
  findings_digest:
1653
1658
  input.action_kind === "request_rework"
1654
1659
  ? await findingsDigestV2(input.findings as never[])
@@ -1732,6 +1737,46 @@ async function reconcileRefsQuietly(root: string): Promise<void> {
1732
1737
  }
1733
1738
  }
1734
1739
 
1740
+ /**
1741
+ * Gate-free closeout: a batch child that reached Kernel `done` in the foreground
1742
+ * re-enters the batch in the same call, reusing its authorization. Only `done`
1743
+ * continues; stopped, parked and Review-pending children end where they do
1744
+ * today. The continuation is transport: the Kernel result is returned
1745
+ * unchanged, and any failure is reported beside it with one retry action.
1746
+ */
1747
+ async function closeOutBatchChild(
1748
+ ctx: ExtensionContext,
1749
+ taskId: string,
1750
+ enriched: Record<string, unknown>,
1751
+ ): Promise<Record<string, unknown>> {
1752
+ const taskState = enriched.task_state as { lifecycle?: unknown } | undefined;
1753
+ if (taskState?.lifecycle !== "done") return enriched;
1754
+ let slug: string | null;
1755
+ try {
1756
+ slug = findResumableBatchSlugForTask(ctx.cwd, taskId);
1757
+ } catch {
1758
+ return enriched;
1759
+ }
1760
+ if (!slug) return enriched;
1761
+ try {
1762
+ // The batch commit takes exactly the scope envelope plus this task's own
1763
+ // audit evidence and refuses unstaged changes: stage only that evidence.
1764
+ if (existsSync(join(ctx.cwd, ".imm", "audit", taskId)))
1765
+ execFileSync("git", ["-C", ctx.cwd, "add", "--", `.imm/audit/${taskId}`], { stdio: "ignore" });
1766
+ const batch = await executePiUnattendedBatch({ root: ctx.cwd, initiativeSlug: slug, reuseOnly: true, signal: ctx.signal });
1767
+ return { ...enriched, batch };
1768
+ } catch (error) {
1769
+ return {
1770
+ ...enriched,
1771
+ batch: {
1772
+ state: "rejected",
1773
+ reason: `batch continuation failed: ${error instanceof Error ? error.message : String(error)}`,
1774
+ recovery_action: "call start_unattended_batch with the same Initiative to continue the batch",
1775
+ },
1776
+ };
1777
+ }
1778
+ }
1779
+
1735
1780
  async function enrichAssuranceResult(
1736
1781
  ctx: ExtensionContext,
1737
1782
  taskId: string,
@@ -19,7 +19,6 @@ import {
19
19
  type InitiativeObservationReader,
20
20
  } from "./runtime-stub";
21
21
  import { batchReason } from "../runtime/unattended/batch_reasons";
22
- import { startConfirmationDeadline } from "../runtime/unattended/confirmation_deadline";
23
22
  import {
24
23
  authorizeBatch,
25
24
  projectBatchPreflight,
@@ -69,12 +68,16 @@ export interface PiBatchExecutionOptions {
69
68
  root: string;
70
69
  initiativeSlug: string;
71
70
  interactive?: boolean;
71
+ /**
72
+ * Gate-free continuation after a foreground child: never opens the
73
+ * confirmation, and an authorization that cannot be reused is reported as a
74
+ * blocked result with the one action that reopens the gate.
75
+ */
76
+ reuseOnly?: boolean;
72
77
  signal?: AbortSignal;
73
78
  batchKernel?: Partial<BatchRunnerKernelPort>;
74
79
  batchGit?: BatchRunnerGitPort;
75
80
  readInitiative?: InitiativeObservationReader;
76
- /** Environment the confirmation deadline reads; production passes process.env. */
77
- env?: Record<string, string | undefined>;
78
81
  confirmBatch?: (details: {
79
82
  title: string;
80
83
  summary: string;
@@ -107,9 +110,10 @@ export async function executePiUnattendedBatch(
107
110
  options: PiBatchExecutionOptions,
108
111
  ): Promise<PiBatchExecutionResult> {
109
112
  const { root, initiativeSlug, signal } = options;
113
+ const reuseOnly = options.reuseOnly === true;
110
114
  const interactive = options.interactive ?? true;
111
115
 
112
- if (!interactive) return { state: "rejected", ...nonInteractiveRefusal() };
116
+ if (!reuseOnly && !interactive) return { state: "rejected", ...nonInteractiveRefusal() };
113
117
 
114
118
  // 1. Host-independent batch preflight: claim ownership, branch availability,
115
119
  // working-tree cleanliness against the authorized scope, recovery children,
@@ -132,7 +136,7 @@ export async function executePiUnattendedBatch(
132
136
  const planDigest = preflight.projection.plan_digest;
133
137
  const recoveryChildren = preflight.projection.recovery_children;
134
138
 
135
- // 4. Literal-user gate plus the shared reuse/expiry decision, the post-gate
139
+ // 4. Literal-user gate plus the shared reuse decision, the post-gate
136
140
  // claim/drift cascade, and the Batch Authorization binding. The Host supplies
137
141
  // only its gate, its confirmation reference, and its binding nonce.
138
142
  const authorization = await authorizeBatch<PiBatchExecutionResult>({
@@ -143,36 +147,40 @@ export async function executePiUnattendedBatch(
143
147
  readInitiative: options.readInitiative,
144
148
  nonce: randomUUID(),
145
149
  gate: async (facts) => {
150
+ if (reuseOnly)
151
+ return {
152
+ kind: "host_rejection",
153
+ value: {
154
+ state: "blocked",
155
+ reason: `batch authorization cannot be reused (${facts.reuse_blockers.join(", ")})`,
156
+ recovery_action: "call start_unattended_batch with the same Initiative to confirm a fresh authorization",
157
+ },
158
+ };
146
159
  // review-2: fail closed with zero writes when confirmation port is missing
147
160
  if (!options.confirmBatch) {
148
161
  return { kind: "host_rejection", value: batchReason("confirmation_port_unavailable") };
149
162
  }
150
163
  const confirmDetails = {
151
164
  title: `Authorize Unattended Batch: ${facts.initiative_slug}`,
152
- summary: `Initiative: ${facts.initiative_slug}\nBatch branch: ${facts.batch_branch}\nPlan digest: ${facts.plan_digest}\nBudget: max_children=${facts.budget.max_children}, deadline_at=${facts.budget.deadline_at}, qa_failure_limit=${facts.budget.qa_failure_limit}\nExpires at: ${facts.expires_at}`,
165
+ summary: `Initiative: ${facts.initiative_slug}\nBatch branch: ${facts.batch_branch}\nPlan digest: ${facts.plan_digest}\nBudget: max_children=${facts.budget.max_children}, qa_failure_limit=${facts.budget.qa_failure_limit}`,
153
166
  details: `Ordered children (${facts.children.length}):\n${facts.children.map((child) => ` - ${child.task_id} (${child.slice_id}) [risk: ${child.risk}] [status: ${child.status === "already_settled" ? "completed" : "pending execution"}]`).join("\n")}${facts.excluded.length > 0 ? `\n\nExcluded children:\n${facts.excluded.map((child) => ` - ${child.task_id} (${child.slice_id}): ${child.reason}`).join("\n")}` : ""}${facts.reuse_blockers.length > 0 ? `\n\nRe-confirmation required: ${facts.reuse_blockers.join(", ")}.\nRecovery: confirm to issue a fresh authorization bound to the current plan and HEAD.` : ""}`,
154
167
  planDigest: facts.plan_digest,
155
168
  signal,
156
169
  };
157
170
 
158
- // The bounded elicitation deadline is shared behavior: an unanswered native
159
- // confirmation is bounded by the same setting on both Hosts.
160
- const deadline = startConfirmationDeadline({ env: options.env ?? process.env, signal });
171
+ // The gate settles only on the literal user's answer or the caller's
172
+ // cancellation signal.
161
173
  let decision: "accept" | "decline" | "cancel";
162
174
  try {
163
- decision = await options.confirmBatch({ ...confirmDetails, signal: deadline.signal });
175
+ decision = await options.confirmBatch(confirmDetails);
164
176
  } catch (err) {
165
- if (deadline.timedOut()) return { kind: "host_rejection", value: batchReason("confirmation_timed_out") };
166
177
  if (signal?.aborted) return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
167
178
  return {
168
179
  kind: "host_rejection",
169
180
  value: batchReason("confirmation_failed", err instanceof Error ? err.message : String(err)),
170
181
  };
171
- } finally {
172
- deadline.clear();
173
182
  }
174
183
 
175
- if (deadline.timedOut()) return { kind: "host_rejection", value: batchReason("confirmation_timed_out") };
176
184
  if (decision === "cancel" || signal?.aborted) {
177
185
  return { kind: "host_rejection", value: batchReason("confirmation_cancelled") };
178
186
  }
@@ -195,7 +203,7 @@ export async function executePiUnattendedBatch(
195
203
  });
196
204
  if (authorization.outcome === "host_rejection") return authorization.value;
197
205
  if (authorization.outcome === "rejected") return authorization.rejection;
198
- const { binding, batch_id: batchId, expires_at: expiresAt } = authorization;
206
+ const { binding, batch_id: batchId } = authorization;
199
207
 
200
208
  // 6. Issue Batch Authorization through Kernel registry and startBatch
201
209
  const batchRegistry: BatchAuthorityRegistry = await createBatchAuthorityRegistry();
@@ -205,8 +213,6 @@ export async function executePiUnattendedBatch(
205
213
  if (signal?.aborted) return batchReason("cancelled_before_execution");
206
214
 
207
215
  now = new Date().toISOString();
208
- if (Date.parse(expiresAt) <= Date.parse(now))
209
- return batchReason("confirmation_failed", "confirmed batch deadline expired before authorization issuance");
210
216
  const capability = batchRegistry.issue(binding, recoveryChildren as any, now);
211
217
 
212
218
  let activeReviewDispatch: { operation_id: string; agent_params: Record<string, unknown> } | null = null;
@@ -300,7 +306,6 @@ export async function executePiUnattendedBatch(
300
306
  plan_digest: planDigest,
301
307
  base_head: binding.base_head,
302
308
  confirmation_time: now,
303
- authorization_expires_at: expiresAt,
304
309
  budget,
305
310
  now,
306
311
  kernel: kernelPort,
@@ -21,7 +21,6 @@ export interface EnrollmentCapabilityBinding {
21
21
  preparation_digest: string;
22
22
  actor_id: string;
23
23
  confirmation_ref: string;
24
- expires_at: string;
25
24
  nonce: string;
26
25
  }
27
26
  export interface EnrollmentAuthorityRegistry {
@@ -36,7 +35,6 @@ export interface CanaryWaiver {
36
35
  reason: string;
37
36
  actor: string;
38
37
  confirmation_ref: string;
39
- expires_at: string;
40
38
  nonce: string;
41
39
  }
42
40
  export interface PiCanaryPrepareInput {
@@ -66,7 +64,6 @@ export interface CapabilityBindingV2 {
66
64
  diff_hash: string;
67
65
  actor_id: string;
68
66
  confirmation_ref: string;
69
- expires_at: string;
70
67
  findings_digest: string | null;
71
68
  }
72
69
  export interface MutationAuthorityRegistry {
@@ -9,6 +9,20 @@
9
9
  - Stage only explicit task-owned paths. This staging authority does not grant
10
10
  commit, push, broad staging, or authority over pre-existing user changes.
11
11
  Never use `git add .` or `git add -A` in a dirty worktree.
12
+ - Grant effects separately: staging, commit, push, publication, and outbound
13
+ model/data calls are distinct operations. Recovering a stage-only instruction
14
+ restores staging and nothing more; commit, push, and publication each keep
15
+ their own existing grant. An explicit approval already covering the same
16
+ operation, target, and impact — including a valid batch capability — stays
17
+ usable without another gate.
18
+ - Before a new outbound effect, check the project's existing research channel and
19
+ data scope. A public article, a mock, a documentation example, or a local
20
+ read-only database gives no authority to call an arbitrary provider or send new
21
+ data; authorize only a genuinely new channel/data/effect delta. Distinguish
22
+ documentation, mock, real-channel, and model-quality evidence when reporting.
23
+ - These text rules are instruction contracts, not a hard bash sandbox. They
24
+ guide behavior and are checked as contracts; they do not technically prevent an
25
+ arbitrary shell command, so authority gates and review remain the real boundary.
12
26
  - Do not create, switch, or delete Git worktrees; operate only in the Host launch directory.
13
27
  - Record reproducible evidence before reporting closure.
14
28
  - Required verification must pass before reporting completion; disclosing a gap is not a substitute. Autonomously diagnose, repair, and rerun failing conventional local checks within the authorized scope; never delete, skip, or weaken a valid check to manufacture a pass. If a required check remains failing or cannot run, report the work as incomplete with the concrete blocker.
@@ -81,7 +95,6 @@ non-Kernel operations, an explicit user approval already covering the same
81
95
  operation, target, and impact is sufficient; ask again only for a material delta.
82
96
  A generic continuation or configured preference is not blanket authorization.
83
97
  Mandatory native gates and hash-bound manifest approvals still apply.
84
-
85
98
  Privileged effects include:
86
99
 
87
100
  - publish, release, deployment, or remote-system mutation;
@@ -91,6 +104,10 @@ Privileged effects include:
91
104
  override; and
92
105
  - external writes whose target or impact cannot be safely reversed locally.
93
106
 
107
+ An existing grant for one of these never inherits another: a stage-only
108
+ instruction stays stage-only, a commit is not push or publication authority,
109
+ and a valid batch capability commits only its own scope-bounded child.
110
+
94
111
  Routine Managed enrollment uses one current-Host native confirmation bound to the TaskIntent content hash after Planner validation. Explicit Plan-only requests stop with candidate artifacts and do not invoke Enrollment; execution-bearing requests open the native gate directly without chat pre-confirmation. Enrollment validates intent, Git ownership, scope, workspace claim, and final authority preconditions without executing acceptance descriptors; deterministic QA executes them after implementation. The routine task proceeds from that single confirmation through enrollment, execution, and QA without a second human stop. Do not request confirmation for local in-scope edits, local verification, ordinary Direct rework, scoped diff review, or completion reporting. Managed evidence, QA, Review, and completion authority remain governed by their Managed contracts; R2 does not weaken them. Managed native-authority failures fail closed with one stable reason and exactly one same-Host recovery action; never offer a Pi, Direct Path, cross-Host/worktree, unmanaged, or automatic-retry fallback.
95
112
 
96
113
  For an unborn symbolic Git HEAD, the same Enrollment confirmation discloses an empty root commit. After approval, the Host creates that commit without staging project content or changing the index/worktree, then revalidates ownership and rehearses Enrollment against the new base. Git identity uses existing environment/config values with an explicit automation fallback, never config writes. Decline or pre-initialization cancellation leaves HEAD unborn; if later Enrollment fails or is cancelled, report the remaining commit. Invalid Git refs are errors, not initialization candidates.