immune-brain 4.1.0 → 4.3.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.
@@ -55,7 +55,12 @@ Continue while the current projection has a valid action:
55
55
  1. For active artifacts, implement only the enrolled acceptance within the
56
56
  `scope_hint` envelope in the current conversation. New helpers or tests
57
57
  inside an approved directory or glob do not require a revision. Run focused
58
- checks. Executor checks are diagnostic evidence, not a QA or Review approval.
58
+ checks. Before `advance_assurance`, inspect ownership and stage only the exact
59
+ task-owned paths needed for delivery. Do not hand routine task-owned staging
60
+ to the user. If a file mixes pre-existing user changes with task changes and
61
+ the task-owned hunks cannot be isolated reliably, stop with that ownership
62
+ conflict instead of staging the whole file. Staging grants no commit or push
63
+ authority. Executor checks are diagnostic evidence, not a QA or Review approval.
59
64
  2. Call `advance_assurance` in the foreground and consume its direct terminal
60
65
  result. The Kernel freezes the artifacts itself before QA: it binds Git
61
66
  content identity in place without relocating source paths. A simple task has
@@ -76,7 +76,8 @@ Then route deterministically:
76
76
  coordination, except a Loop-requested revision follows Enrolled Intent Revision
77
77
  below to prepare a non-authoritative proposal for that same owner;
78
78
  - an active or otherwise nonterminal v3 Plan remains on its existing v3 route;
79
- - no routing policy preserves the legacy v3 Planner behavior;
79
+ - no routing policy on an otherwise unowned workspace triggers automatic policy
80
+ activation below before producing any planning artifact;
80
81
  - a valid `kernel_task_intent` retirement policy produces one TaskIntent draft
81
82
  through the current Host's explicit `imm-planner`;
82
83
  - an invalid, unreadable, untracked, or tracked-deleted policy rejects new
@@ -107,6 +108,46 @@ workspace claim, and final authority preconditions without executing acceptance
107
108
  descriptors. A routine task proceeds from that single confirmation through
108
109
  enrollment, execution and QA without a second human stop.
109
110
 
111
+ **Automatic policy activation**
112
+
113
+ Explicit `imm-planner` entry includes local routing setup, including for plan-only
114
+ requests. When the routing projection reports `policy_status: legacy_v3` and
115
+ `ownership: absent`, and neither Kernel nor nonterminal v3 ownership exists,
116
+ perform these steps without a separate enablement question:
117
+
118
+ 1. Create `docs/plans/` if needed. Require real directories without symlink
119
+ components; create `docs/plans/managed-task-routing-policy.json` exclusively
120
+ (fail if it already exists), using exactly the JSON below with two-space
121
+ indentation, the shown field order, and one trailing newline.
122
+ 2. Run `git add -- docs/plans/managed-task-routing-policy.json` only for the file
123
+ created in this activation. Preserve all other worktree and index changes;
124
+ do not commit, force-add an ignored file, or alter Git configuration.
125
+ 3. Re-run `imm-plan --routing-status --json` through the resolved wrapper.
126
+ Continue canonical TaskIntent authoring only when `policy_status: active`,
127
+ `route: kernel_task_intent`, and `ownership: tracked_clean` all hold. Report
128
+ automatic activation briefly and continue planning in the same turn.
129
+
130
+ ```json
131
+ {
132
+ "contract": "immune_brain/managed_task_routing_policy/v1",
133
+ "revision": 1,
134
+ "new_task_route": "kernel_task_intent",
135
+ "v3_new_plan_sync": "retired",
136
+ "legacy_v3_mode": "drain_read_only",
137
+ "terminal_import": "disabled"
138
+ }
139
+ ```
140
+
141
+ An already active policy needs no write or staging. An existing invalid,
142
+ untracked, unreadable, tracked-deleted, or divergent policy is not an activation
143
+ candidate: preserve it and report `routing_policy_invalid`. If creation,
144
+ staging, or verification fails, report the concrete blocker and retain any
145
+ created file; stop before authoring, without overwriting existing bytes or
146
+ falling back to v3. A repository/user prohibition on policy setup or staging
147
+ blocks only this dependent planning step. Activation grants no execution
148
+ authority; the native Enrollment gate remains required. Read-only routing
149
+ queries and the canonical author command retain their existing runtime behavior.
150
+
110
151
  ## Candidate Authoring
111
152
 
112
153
  Read this section before creating new candidate artifacts, after request routing
@@ -133,6 +174,12 @@ file creation; then it validates the created artifact with
133
174
  continue through Kernel `revise_intent` authority and are not a Planner
134
175
  overwrite path.
135
176
 
177
+ After authoring, inspect ownership and stage only the exact Planner-produced
178
+ Spec and TaskIntent paths before validation and handoff. Do not hand routine
179
+ Planner-owned staging to the user. This grants no commit, push, broad staging,
180
+ or authority over pre-existing user changes; a mixed-change ownership conflict
181
+ stops only the affected handoff.
182
+
136
183
  Before authoring a TaskIntent that adds a field or verdict branch to a state
137
184
  machine, enumerate every consumer of that value and of the version gates around
138
185
  it: the producing side, each branch or switch that reads it, and any migration or
@@ -263,7 +310,12 @@ script or host tool through its literal `command`; do not infer a language,
263
310
  package manager, or runner. Add `environment.prepare` only when the check needs
264
311
  explicit setup, and declare only the generated directories it needs in
265
312
  `environment.writable_paths`. Never hide package installation inside an
266
- acceptance command. Prefer the highest existing observable behavioral test seam
313
+ acceptance command. For every descriptor, identify whether its executable is
314
+ provided by the QA host or by tracked delivery content, where every dependency
315
+ comes from in the disposable delivery, what explicit setup is required, and
316
+ which declared writable paths that setup or check creates. A dependency found
317
+ only in the Planner's live worktree, including an absolute local `node_modules`
318
+ path, is not delivery provenance. Prefer the highest existing observable behavioral test seam
267
319
  and the fewest sufficient seams; never use the full test suite, a build, network
268
320
  access, or redundant heavyweight checks as acceptance. Cite relevant test prior
269
321
  art and explain how the selected seam catches the intended regression. This is
@@ -271,7 +323,9 @@ a planning heuristic: it must not weaken acceptance-specific focused
271
323
  verification descriptors or add a mandatory user confirmation. Use the smallest
272
324
  `timeout_ms` and `max_output_bytes` that cover deterministic post-implementation
273
325
  QA. A v1 descriptor is historical-only and requires explicit Intent revision
274
- before execution.
326
+ before execution. Report `valid` and `enrollment_ready` only as structural and
327
+ Enrollment readiness; only a completed deterministic QA result proves that a
328
+ descriptor executed and passed.
275
329
 
276
330
  ## Core Responsibilities
277
331
 
@@ -410,7 +464,7 @@ optional advisory dispatch fails, continue inline and record the reason.
410
464
 
411
465
  ## Boundary
412
466
 
413
- - **Allowed**: Write a TaskIntent. Add a Spec only for complex work. Initiative planning carriers and necessary domain vocabulary.
467
+ - **Allowed**: Write a TaskIntent. Add a Spec only for complex work. Activate an absent routing policy under Kernel TaskIntent Routing. Initiative planning carriers and necessary domain vocabulary.
414
468
  - **Blocked**: Implementation edits, direct Kernel-store writes, enrolled intent overwrites, and QA/Review decisions.
415
469
  - **Workflow guard**: Execution continues through native Enrollment and explicit `imm-loop`. Planner owns design and decomposition, not execution authority.
416
470
 
@@ -452,7 +506,7 @@ a State Ledger. Keep historical Plan validation strictly read-only.
452
506
 
453
507
  ## Verification
454
508
 
455
- - Validate every candidate through `imm-kernel intent validate <path> --json` after authoring and staging. Require `valid: true` and `enrollment_ready: true` before Enrollment.
509
+ - Validate every candidate through `imm-kernel intent validate <path> --json` after authoring and staging. Require `valid: true` and `enrollment_ready: true` before Enrollment. When `git_base_initialization_required: true`, report that the existing native Enrollment confirmation includes an empty root commit; leave initialization to the Host. An invalid `git_base` blocks Enrollment and requires Git repair.
456
510
  - For complex work, verify Spec design metadata, document language, reference closure, concrete descriptor paths, and complete Brainstorm traceability before handoff. Simple TaskIntent-only work verifies those properties on the Intent.
457
511
  - Enrollment validates descriptor structure only. Deterministic QA owns descriptor execution after implementation; planning does not run the acceptance suite.
458
512
  - Managed execution handoff is Git-tracked TaskIntent author/validate plus current-Host native Enrollment. Do not sync a v3 State Ledger or invoke a missing dispatcher.
@@ -0,0 +1,83 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import { preparePiCanary, readGitHead, revalidatePiCanary, type PiCanaryPreparation, type PiCanaryPrepareInput } from "../kernel/pi_canary_prepare";
3
+ import { withKernelStoreLock } from "../kernel/storage";
4
+
5
+ export type EnrollmentGitBase = { state: "committed"; head: string } | { state: "unborn"; branch: string };
6
+
7
+ function git(root: string, args: string[], input?: string, env = process.env): string {
8
+ return execFileSync("git", args, { cwd: root, encoding: "utf8", input, env, stdio: ["pipe", "pipe", "pipe"] }).trim();
9
+ }
10
+
11
+ /** Read-only. A missing branch is bootstrap-able; a broken existing ref is not. */
12
+ export function inspectEnrollmentGitBase(root: string): EnrollmentGitBase {
13
+ if (git(root, ["rev-parse", "--is-inside-work-tree"]) !== "true")
14
+ throw new Error("Enrollment requires a Git working tree");
15
+ try { return { state: "committed", head: readGitHead(root) }; } catch { /* Inspect the unborn case below. */ }
16
+ const branch = git(root, ["symbolic-ref", "--no-recurse", "HEAD"]);
17
+ if (!branch.startsWith("refs/heads/")) throw new Error("Enrollment HEAD must name a local branch");
18
+ const ref = spawnSync("git", ["show-ref", "--verify", "--quiet", branch], { cwd: root });
19
+ const symbolic = spawnSync("git", ["symbolic-ref", "-q", branch], { cwd: root });
20
+ if (ref.status !== 1 || symbolic.status !== 1)
21
+ throw new Error("Enrollment Git HEAD is invalid; only an absent branch can be initialized");
22
+ return { state: "unborn", branch };
23
+ }
24
+
25
+ export function enrollmentGitBaseNotice(base: EnrollmentGitBase): string | undefined {
26
+ return base.state === "unborn"
27
+ ? `Git: create an empty initial commit on ${base.branch} before Enrollment; staged files and working tree remain unchanged. This commit remains if later Enrollment fails or is cancelled.`
28
+ : undefined;
29
+ }
30
+
31
+ function identityEnv(root: string): NodeJS.ProcessEnv {
32
+ const configured = (key: string): string => {
33
+ const result = spawnSync("git", ["config", "--get", key], { cwd: root, encoding: "utf8" });
34
+ if (result.status !== 0 && result.status !== 1) throw new Error(`Cannot read Git identity: ${key}`);
35
+ return result.stdout?.trim() ?? "";
36
+ };
37
+ const name = configured("user.name") || "Immune-Brain Enrollment";
38
+ const email = configured("user.email") || "enrollment@immune-brain.local";
39
+ return { ...process.env,
40
+ GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || name, GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL || email,
41
+ GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || name, GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || email,
42
+ };
43
+ }
44
+
45
+ /** Called only after native confirmation. Never stages files or writes Git config. */
46
+ export function initializeEnrollmentGitBase(
47
+ root: string, input: PiCanaryPrepareInput, previous: PiCanaryPreparation,
48
+ base: EnrollmentGitBase, signal?: AbortSignal,
49
+ ): PiCanaryPreparation {
50
+ const assertUnchanged = () => {
51
+ signal?.throwIfAborted();
52
+ if (!revalidatePiCanary(root, input, previous).unchanged
53
+ || JSON.stringify(inspectEnrollmentGitBase(root)) !== JSON.stringify(base))
54
+ throw new Error("Workspace or Git branch changed after confirmation; retry Enrollment");
55
+ };
56
+ assertUnchanged();
57
+ if (base.state === "committed") return previous;
58
+ return withKernelStoreLock(root, () => {
59
+ // CAS the captured branch itself, never HEAD: a concurrent symbolic HEAD
60
+ // switch must not redirect the write to another branch. Recheck HEAD after
61
+ // the write and report any created base if the workspace changed.
62
+ let created: string | undefined;
63
+ try {
64
+ assertUnchanged();
65
+ if (!previous.intent || previous.backend_claim.present || previous.task_record_v3?.present || previous.workspace.current_working !== null)
66
+ throw new Error("Empty Git base initialization requires an unowned workspace and a tracked TaskIntent");
67
+ const tree = git(root, ["hash-object", "-t", "tree", "-w", "--stdin"], "");
68
+ const commit = git(root, ["commit-tree", tree, "-m", "chore: initialize empty Enrollment base"], undefined, identityEnv(root));
69
+ assertUnchanged();
70
+ git(root, ["update-ref", "--no-deref", base.branch, commit, "0".repeat(commit.length)]);
71
+ created = commit;
72
+ const current = preparePiCanary(root, input);
73
+ const withoutGit = ({ digest, git_base_head, git_error, ...owners }: PiCanaryPreparation) => owners;
74
+ if (git(root, ["symbolic-ref", "--no-recurse", "HEAD"]) !== base.branch || current.git_base_head !== commit
75
+ || JSON.stringify(withoutGit(current)) !== JSON.stringify(withoutGit(previous)))
76
+ throw new Error("Workspace changed during empty Git base initialization; retry Enrollment");
77
+ return current;
78
+ } catch (error) {
79
+ if (created) throw new Error(`${error instanceof Error ? error.message : String(error)}; empty initial commit ${created} remains`);
80
+ throw error;
81
+ }
82
+ });
83
+ }
@@ -74,6 +74,7 @@ export interface NativeConfirmationInput {
74
74
  intentRevision?: number;
75
75
  intentContentHash?: string;
76
76
  bindingDigest?: string;
77
+ gitBaseNotice?: string;
77
78
  planDigest?: string;
78
79
  batchDetails?: {
79
80
  initiative_slug: string;
@@ -39,6 +39,7 @@ import {
39
39
  } from "../kernel/enrollment_authority";
40
40
  import { enrollCanaryTask, runEnrollmentRehearsal } from "../kernel/enrollment";
41
41
  import { reconcileKernelAuthority, repairKernelAuthority } from "../kernel/storage";
42
+ import { inspectEnrollmentGitBase, enrollmentGitBaseNotice, initializeEnrollmentGitBase } from "../assurance/enrollment_git_base";
42
43
  import { preparePiCanary, revalidatePiCanary } from "../kernel/pi_canary_prepare";
43
44
  import { runDeterministicQa } from "../assurance/qa";
44
45
  import { taskDiffIdentity, taskRevisionIdentity } from "../workspace_scope";
@@ -524,6 +525,7 @@ export class ClaudeRuntime {
524
525
  intentRevision?: number;
525
526
  intentContentHash?: string;
526
527
  bindingDigest?: string;
528
+ gitBaseNotice?: string;
527
529
  } = {}): Promise<{ confirmation_ref: string }> {
528
530
  throwIfCancelled(meta.signal);
529
531
  const probe = probeHost(this.env, process.platform, this.hostVersion);
@@ -557,45 +559,55 @@ export class ClaudeRuntime {
557
559
 
558
560
  async enroll(taskId: string, meta: ToolMeta) {
559
561
  const now = new Date().toISOString();
560
- const preparation = await preparePiCanary(this.cwd, { task_id: taskId, now });
562
+ let preparation = await preparePiCanary(this.cwd, { task_id: taskId, now });
561
563
  const intent = await readTaskIntentForRecord(this.cwd, taskId);
564
+ const gitBase = inspectEnrollmentGitBase(this.cwd);
562
565
  const gate = await this.gate("enroll", { ...meta, taskId }, {
563
566
  risk: intent.intent.risk,
564
567
  intentRevision: preparation.intent?.revision,
565
568
  intentContentHash: preparation.intent?.content_hash,
566
569
  bindingDigest: preparation.digest,
570
+ gitBaseNotice: enrollmentGitBaseNotice(gitBase),
567
571
  });
568
572
  const { unchanged } = await revalidatePiCanary(this.cwd, { task_id: taskId, now }, preparation);
569
573
  if (!unchanged) throw new NativeAuthorityError("workspace_changed", "workspace changed after native confirmation");
570
574
  if (!preparation.intent) throw new Error("enrollment requires a readable TaskIntent");
571
575
  throwIfCancelled(meta.signal);
572
- const nonce = enrollmentNonce();
573
- const binding: EnrollmentCapabilityBinding = {
574
- task_id: taskId,
575
- intent_path: preparation.intent.path,
576
- intent_revision: preparation.intent.revision,
577
- intent_content_hash: preparation.intent.content_hash,
578
- preparation_digest: preparation.digest,
579
- actor_id: LITERAL_USER_ACTOR_ID,
580
- confirmation_ref: gate.confirmation_ref,
581
- expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
582
- nonce,
583
- };
584
- const capability = this.enrollmentRegistry.issue(binding);
585
- const input = {
586
- task_id: taskId,
587
- intent_path: binding.intent_path,
588
- intent_revision: binding.intent_revision,
589
- preparation_digest: binding.preparation_digest,
590
- capability,
591
- capability_binding: binding,
592
- now,
593
- };
594
- const rehearsal = runEnrollmentRehearsal(this.cwd, input, capability, this.enrollmentRegistry);
595
- if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready") {
596
- throw new Error(`Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`);
576
+ preparation = initializeEnrollmentGitBase(this.cwd, { task_id: taskId, now }, preparation, gitBase, meta.signal);
577
+ const gitBaseNote = gitBase.state === "unborn" ? `; empty initial commit ${preparation.git_base_head} remains` : "";
578
+ try {
579
+ if (!preparation.intent) throw new Error("Enrollment requires a readable TaskIntent");
580
+ const nonce = enrollmentNonce();
581
+ const binding: EnrollmentCapabilityBinding = {
582
+ task_id: taskId,
583
+ intent_path: preparation.intent.path,
584
+ intent_revision: preparation.intent.revision,
585
+ intent_content_hash: preparation.intent.content_hash,
586
+ preparation_digest: preparation.digest,
587
+ actor_id: LITERAL_USER_ACTOR_ID,
588
+ confirmation_ref: gate.confirmation_ref,
589
+ expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
590
+ nonce,
591
+ };
592
+ const capability = this.enrollmentRegistry.issue(binding);
593
+ const input = {
594
+ task_id: taskId,
595
+ intent_path: binding.intent_path,
596
+ intent_revision: binding.intent_revision,
597
+ preparation_digest: binding.preparation_digest,
598
+ capability,
599
+ capability_binding: binding,
600
+ now,
601
+ };
602
+ const rehearsal = runEnrollmentRehearsal(this.cwd, input, capability, this.enrollmentRegistry);
603
+ if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready") {
604
+ throw new Error(`Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`);
605
+ }
606
+ return enrollCanaryTask(this.cwd, input, this.enrollmentRegistry);
607
+ } catch (error) {
608
+ if (gitBaseNote) throw new Error(`${error instanceof Error ? error.message : String(error)}${gitBaseNote}`);
609
+ throw error;
597
610
  }
598
- return enrollCanaryTask(this.cwd, input, this.enrollmentRegistry);
599
611
  }
600
612
 
601
613
  async advance(taskId: string, signal?: AbortSignal) {
@@ -378,6 +378,7 @@ export function elicitationParams(input: NativeConfirmationInput) {
378
378
  input.intentRevision !== undefined ? `Intent revision: ${input.intentRevision}` : null,
379
379
  input.intentContentHash ? `Intent hash: ${input.intentContentHash}` : null,
380
380
  input.bindingDigest ? `Binding digest: ${input.bindingDigest}` : null,
381
+ input.gitBaseNotice,
381
382
  ].filter(Boolean);
382
383
  return {
383
384
  mode: "form",
@@ -40,6 +40,7 @@ import { projectLegacyAudit } from "../kernel/legacy_audit";
40
40
  import { inspectStorageLayout } from "../kernel/storage_paths";
41
41
  import { migrateLegacyLayout } from "../kernel/storage_layout_migration";
42
42
  import { readBackendClaim } from "../kernel/backend_claim";
43
+ import { inspectEnrollmentGitBase, type EnrollmentGitBase } from "../assurance/enrollment_git_base";
43
44
  import { probeKernelStore } from "../kernel";
44
45
 
45
46
  export interface KernelCommandResult {
@@ -1134,8 +1135,11 @@ function runIntentValidate(args: string[], root: string): KernelExecution {
1134
1135
  gitTracked = false;
1135
1136
  }
1136
1137
 
1138
+ let gitBase: EnrollmentGitBase | { state: "invalid"; reason: string };
1139
+ try { gitBase = inspectEnrollmentGitBase(canonicalRoot); }
1140
+ catch (error) { gitBase = { state: "invalid", reason: error instanceof Error ? error.message : String(error) }; }
1137
1141
  const contentHash = canonicalIntentHash(intent);
1138
- const enrollmentReady = gitTracked && allEligible;
1142
+ const enrollmentReady = gitTracked && allEligible && gitBase.state !== "invalid";
1139
1143
  return {
1140
1144
  result: jsonResult(
1141
1145
  {
@@ -1148,6 +1152,8 @@ function runIntentValidate(args: string[], root: string): KernelExecution {
1148
1152
  acceptance_ids: intent.acceptance.map((item) => item.id),
1149
1153
  verification: verification,
1150
1154
  git_ownership: gitTracked ? "tracked" : "untracked",
1155
+ git_base: gitBase,
1156
+ git_base_initialization_required: gitBase.state === "unborn",
1151
1157
  enrollment_ready: enrollmentReady,
1152
1158
  },
1153
1159
  0,
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/plugin_versioning.ts from the root package.json.
2
- export const PLUGIN_VERSION = "4.1.0" as const;
2
+ export const PLUGIN_VERSION = "4.3.0" as const;
@@ -6,7 +6,9 @@
6
6
  from `dist/`; nested modes, examples, recovery, and references load on demand.
7
7
  - Ask only when missing information would change the goal, scope, observable behavior, compatibility, risk acceptance, a protected effect, or a fact only the user can supply. Resolve repository facts and delegated technical choices with bounded evidence instead of asking.
8
8
  - Keep edits inside the user-requested Direct scope or the enrolled TaskIntent acceptance and `scope_hint`.
9
- - Stage only explicit task-owned paths. Never use `git add .` or `git add -A` in a dirty worktree.
9
+ - Stage only explicit task-owned paths. This staging authority does not grant
10
+ commit, push, broad staging, or authority over pre-existing user changes.
11
+ Never use `git add .` or `git add -A` in a dirty worktree.
10
12
  - Do not create, switch, or delete Git worktrees; operate only in the Host launch directory.
11
13
  - Record reproducible evidence before reporting closure.
12
14
  - 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.
@@ -46,7 +48,9 @@ routing. A new Managed workflow starts only from explicit `imm-brainstorm`,
46
48
  Managed owner remains authoritative; the user resumes it with `imm-loop`.
47
49
  2. **Start explicitly**: the selected Immune-Brain Skill owns its planning or
48
50
  coordination work. It creates only requested artifacts and their required
49
- parent directories; it does not install project-wide contract files.
51
+ parent directories. Explicit Planner entry also owns absent routing-policy
52
+ activation under Planner's Kernel TaskIntent Routing section;
53
+ it does not otherwise install project-wide contract files.
50
54
  3. **Preserve authority**: Planner output is a candidate for later literal-user
51
55
  Enrollment, and Fast-Track preserves TaskIntent scope, Enrollment, QA,
52
56
  Review, authorization, and completion boundaries.
@@ -89,6 +93,8 @@ Privileged effects include:
89
93
 
90
94
  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.
91
95
 
96
+ 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.
97
+
92
98
  ## Parallel Read-Only Dispatch
93
99
 
94
100
  State mutations, step activations, QA decisions, and plan switches remain