@deftai/directive-content 0.109.1 → 0.111.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 (46) hide show
  1. package/.agents/skills/deft-directive-triage/SKILL.md +3 -3
  2. package/SKILL.md +13 -13
  3. package/Taskfile.yml +24 -16
  4. package/commands.md +15 -13
  5. package/contracts/design-critique.md +56 -10
  6. package/contracts/path-write-fence.md +27 -6
  7. package/conventions/machine-generated-banner.md +9 -9
  8. package/conventions/rule-ownership.json +3 -3
  9. package/docs/consumer-issue-label-kit.md +6 -19
  10. package/docs/getting-started.md +1 -1
  11. package/docs/grok-build-subscription-setup.md +146 -0
  12. package/docs/scope-provenance.md +28 -0
  13. package/docs/test-boundary.md +3 -1
  14. package/main.md +22 -22
  15. package/package.json +1 -1
  16. package/packs/rules/rules-pack-0.1.json +1 -1
  17. package/packs/skills/skills-pack-0.1.json +30 -30
  18. package/scm/github.md +1 -1
  19. package/skills/deft-directive-design-critique/SKILL.md +7 -3
  20. package/skills/deft-directive-design-critique/references/motion-shape.md +1 -1
  21. package/skills/deft-directive-portfolio-priority/SKILL.md +2 -2
  22. package/skills/deft-directive-refinement/SKILL.md +3 -3
  23. package/skills/deft-directive-release/SKILL.md +22 -17
  24. package/skills/deft-directive-review-cycle/SKILL.md +45 -12
  25. package/skills/deft-directive-swarm/references/core-phase-0.md +2 -1
  26. package/skills/deft-directive-swarm/references/core-phase-3.md +1 -1
  27. package/skills/deft-directive-swarm/references/host-grok-build.md +17 -1
  28. package/skills/deft-directive-triage/SKILL.md +13 -115
  29. package/tasks/policy.yml +2 -2
  30. package/tasks/scope.yml +16 -25
  31. package/tasks/triage-actions.yml +3 -4
  32. package/tasks/triage-bootstrap.yml +2 -2
  33. package/tasks/triage-bulk.yml +6 -7
  34. package/tasks/triage-classify.yml +13 -3
  35. package/tasks/triage-queue.yml +2 -2
  36. package/tasks/triage-reconcile.yml +2 -2
  37. package/tasks/triage-scope-drift.yml +2 -2
  38. package/tasks/triage-scope.yml +2 -2
  39. package/tasks/triage-smoketest.yml +3 -3
  40. package/tasks/triage-subscribe.yml +2 -2
  41. package/tasks/triage-summary.yml +2 -2
  42. package/tasks/triage-welcome.yml +2 -2
  43. package/tasks/verify.yml +14 -1
  44. package/templates/agent-prompt-preamble.md +4 -1
  45. package/templates/agents-entry.md +7 -7
  46. package/templates/design-critique-brief.md +5 -0
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: deft-directive-triage
3
3
  description: >-
4
- Triage-cache hygiene and "what's next?" queue selection -- the agent-facing
5
- playbook for syncing the triage cache, classifying candidates, presenting a
6
- ranked queue, walking per-item decisions, and auditing the session.
4
+ Withdrawn (#4070). Classify playbook is off. Work selection is
5
+ plan-sequence:current then a read-only triage:queue listing. Replacement
6
+ sieve is #4071. Triggers still match so packs:slice finds this stub.
7
7
  ---
8
8
 
9
9
  Read and follow: skills/deft-directive-triage/SKILL.md
package/SKILL.md CHANGED
@@ -45,20 +45,20 @@ as your FIRST and ONLY response — no summary, no menu, no preamble:
45
45
 
46
46
  Then continue with `skills/deft-directive-setup/SKILL.md` Phase 1 for remaining questions.
47
47
 
48
- **If USER.md exists but `./vbrief/PROJECT-DEFINITION.vbrief.json` is missing**: Skip to
48
+ **If USER.md exists but `./xbrief/PROJECT-DEFINITION.xbrief.json` is missing**: Skip to
49
49
  `skills/deft-directive-setup/SKILL.md` Phase 2.
50
50
 
51
- **If USER.md and `./vbrief/PROJECT-DEFINITION.vbrief.json` both exist but no scope vBRIEFs in `./vbrief/` lifecycle folders**:
51
+ **If USER.md and `./xbrief/PROJECT-DEFINITION.xbrief.json` both exist but no scope xBRIEFs in `./xbrief/` lifecycle folders**:
52
52
  Skip to `skills/deft-directive-setup/SKILL.md` Phase 3. Start the specification interview
53
53
  imediately — ask what to build and features as the first question.
54
54
 
55
55
  ### ⊗ Project Root vs Framework Internals
56
56
 
57
- ! When checking for project-level files (`PROJECT-DEFINITION.vbrief.json`, scope vBRIEFs,
58
- `PRD.md`), ONLY look at `./vbrief/` and its lifecycle subdirectories.
57
+ ! When checking for project-level files (`PROJECT-DEFINITION.xbrief.json`, scope xBRIEFs,
58
+ `PRD.md`), ONLY look at `./xbrief/` and its lifecycle subdirectories.
59
59
 
60
- - ! `./vbrief/PROJECT-DEFINITION.vbrief.json` — the user's project config
61
- - ! `./vbrief/proposed/`, `./vbrief/pending/`, `./vbrief/active/`, `./vbrief/completed/` — scope vBRIEFs in lifecycle folders
60
+ - ! `./xbrief/PROJECT-DEFINITION.xbrief.json` — the user's project config
61
+ - ! `./xbrief/proposed/`, `./xbrief/pending/`, `./xbrief/active/`, `./xbrief/completed/` — scope xBRIEFs in lifecycle folders
62
62
  - ⊗ Count ANY file inside `.deft/core/` (canonical) or `./deft/` (legacy pre-v0.27) as a project-level artifact — those are
63
63
  framework-internal (e.g. `.deft/core/PROJECT.md`, `.deft/core/specs/`, `.deft/core/templates/`
64
64
  are all part of the framework, NOT the user's project)
@@ -75,7 +75,7 @@ USER.md has two sections with different precedence:
75
75
  ```
76
76
  USER.md Personal ← HIGHEST (name, custom rules — always wins)
77
77
 
78
- PROJECT-DEFINITION.vbrief.json ← Project-specific (strategy, coverage, languages, tech stack)
78
+ PROJECT-DEFINITION.xbrief.json ← Project-specific (strategy, coverage, languages, tech stack)
79
79
 
80
80
  USER.md Defaults ← Fallback defaults (used when PROJECT-DEFINITION doesn't specify)
81
81
 
@@ -85,11 +85,11 @@ USER.md Defaults ← Fallback defaults (used when PROJECT-DEFINITION doesn
85
85
 
86
86
  main.md ← General AI behavior
87
87
 
88
- scope vBRIEFs ← LOWEST precedence (requirements in lifecycle folders)
88
+ scope xBRIEFs ← LOWEST precedence (requirements in lifecycle folders)
89
89
  ```
90
90
 
91
91
  **IMPORTANT**: USER.md `Personal` section always wins. For project-scoped settings
92
- (strategy, coverage, languages), `PROJECT-DEFINITION.vbrief.json` overrides USER.md `Defaults`.
92
+ (strategy, coverage, languages), `PROJECT-DEFINITION.xbrief.json` overrides USER.md `Defaults`.
93
93
 
94
94
  ## File Reading Strategy (Lazy Loading)
95
95
 
@@ -97,7 +97,7 @@ scope vBRIEFs ← LOWEST precedence (requirements in lifecycle folders)
97
97
 
98
98
  1. **Always start with**: `.deft/core/main.md` (general guidelines)
99
99
  2. **Check for**: `~/.config/deft/USER.md` (personal overrides - highest precedence)
100
- 3. **Check for**: `./vbrief/PROJECT-DEFINITION.vbrief.json` (project-specific rules)
100
+ 3. **Check for**: `./xbrief/PROJECT-DEFINITION.xbrief.json` (project-specific rules)
101
101
  4. **Then read language-specific** only if working with that language:
102
102
  - `.deft/core/languages/python.md`
103
103
  - `.deft/core/languages/go.md`
@@ -128,8 +128,8 @@ See `.deft/core/tools/taskfile.md` for complete task standards and common comman
128
128
 
129
129
  **Spec-Driven Development (SDD)** for new features/projects:
130
130
  1. Run `skills/deft-directive-setup/SKILL.md` Phase 3 — sizing gate selects Light or Full path
131
- 2. Light: Interview → scope vBRIEFs in `vbrief/proposed/` (embedded requirements) → Implement
132
- 3. Full: Interview → rich narratives in `specification.vbrief.json` (approval gate) → scope vBRIEFs → Implement
131
+ 2. Light: Interview → scope xBRIEFs in `xbrief/proposed/` (embedded requirements) → Implement
132
+ 3. Full: Interview → rich narratives in `specification.xbrief.json` (approval gate) → scope xBRIEFs → Implement
133
133
 
134
134
  See `.deft/core/coding/testing.md` for complete testing standards.
135
135
 
@@ -167,7 +167,7 @@ task project:render # Project config → xbrief/PROJECT-DEFINITION.xbrief.
167
167
  1. **First time?** If USER.md doesn't exist, run `skills/deft-directive-setup/SKILL.md` Phase 1
168
168
  2. Read `.deft/core/main.md` (general guidelines)
169
169
  3. Read `~/.config/deft/USER.md` (personal preferences - highest precedence)
170
- 4. Read `./vbrief/PROJECT-DEFINITION.vbrief.json` (project rules)
170
+ 4. Read `./xbrief/PROJECT-DEFINITION.xbrief.json` (project rules)
171
171
  5. Run `task --list` to see available tasks
172
172
 
173
173
  See `.deft/core/main.md` for complete workflow details.
package/Taskfile.yml CHANGED
@@ -370,11 +370,11 @@ includes:
370
370
  packs:
371
371
  taskfile: ./tasks/packs.yml
372
372
  optional: true
373
- # Maintainer-only Python self-test lanes (#1813 contributor path / #2022
373
+ # Maintainer-only packaging lane (#1813 contributor path / #2022
374
374
  # Phase 2). `internal: true` hides `core:*` / `ci:*` from `task -l` and
375
375
  # blocks direct CLI invocation on consumer installs; only
376
376
  # `check:framework-source` below wires them as deps. Consumer `task check`
377
- # dispatches to `check:consumer` (TS / deft verbs — no uv/python).
377
+ # dispatches to `check:consumer` (TS / deft verbs).
378
378
  core:
379
379
  taskfile: ./tasks/core.yml
380
380
  optional: true
@@ -464,8 +464,11 @@ tasks:
464
464
  - verify-eval-health-relocation-framework-check
465
465
  - verify-eval-triggers-relocation-framework-check
466
466
  - vbrief:validate
467
- - codebase:validate-structure
467
+ - task: codebase:validate-structure
468
+ vars:
469
+ CLI_ARGS: "--enforce"
468
470
  - verify:codebase-map-fresh
471
+ - verify:spec-prd-fresh
469
472
  - verify-strategy-output
470
473
  # #2980 residual: fail-closed product raw-write inventory (allowlist primitives + temporary residual).
471
474
  - verify-contained-writes-enforce
@@ -796,18 +799,16 @@ tasks:
796
799
  # `triage-bootstrap`) -- a single shared `triage:` include namespace
797
800
  # is not supported by go-task v3 (two includes cannot share a key).
798
801
  # The aliases below provide the documented `task triage:<verb>`
799
- # user-facing surface that vBRIEFs / UPGRADING.md / scripts/triage_*.py
800
- # describe. Each alias delegates to the underlying namespaced task
801
- # and forwards `{{.CLI_ARGS}}` so flags (`--repo`, `--reason`, etc.)
802
- # reach the script. The inner tasks in each fragment are
802
+ # user-facing surface that xBRIEFs / UPGRADING.md describe. Each alias
803
+ # delegates to the underlying namespaced task and forwards
804
+ # `{{.CLI_ARGS}}` so flags (`--repo`, `--reason`, etc.) reach
805
+ # engine:invoke / packages/cli/dist. The inner tasks in each fragment are
803
806
  # `internal: true` so the fragment-namespace forms
804
807
  # (`triage-cache:cache`, `triage-actions:accept`, `triage-bulk:bulk-defer`,
805
808
  # `triage-bootstrap:bootstrap`) drop out of `task -l`; only the
806
809
  # documented `triage:*` aliases below appear in the listing. The
807
- # internal tasks remain CALLABLE for legacy invocations (e.g. recap
808
- # text in `scripts/triage_bootstrap.py` still prints the namespaced
809
- # forms, which continue to dispatch correctly via the fragment
810
- # include).
810
+ # internal tasks remain CALLABLE for legacy invocations via the fragment
811
+ # include.
811
812
  #
812
813
  # Aliases are inline at the root Taskfile rather than in a separate
813
814
  # fragment because they cross include namespaces and must exist
@@ -826,10 +827,10 @@ tasks:
826
827
  # categorized verb list grouped by role (Session-start, State verbs,
827
828
  # Read verbs, Lifecycle, Subscription mutation, Archive-rotation /
828
829
  # Promote-demote, Activate-complete, Reversibility, Decomposition).
829
- # The registry + renderer live in scripts/triage_help.py so every
830
- # verb is documented in one place. Each existing `triage:X` /
831
- # `scope:X` alias forwards `--help` to the same registry via the
832
- # `intercept_help` shim called from each verb script's `main()`.
830
+ # The registry + renderer live in
831
+ # packages/core/src/triage/help/registry-data.ts (edit in place; no
832
+ # generator). Each existing `triage:X` / `scope:X` alias forwards
833
+ # `--help` to the same registry via interceptHelp (handler + dispatch).
833
834
  #
834
835
  # Defined at the root Taskfile (not in a fragment) because the bare
835
836
  # target shares the `triage:` / `scope:` prefix with the documented
@@ -992,12 +993,19 @@ tasks:
992
993
  CLI_ARGS: "{{.CLI_ARGS}}"
993
994
 
994
995
  triage:classify:
995
- desc: "Inspect / validate auto-classification + Tier-1 SCM label mirror (#1129 / #1423). -- task triage:classify -- [--list | --validate | --mirror [--apply]]"
996
+ desc: "Inspect / validate auto-classification. --mirror is withdrawn (#4070). -- task triage:classify -- [--list | --validate]"
996
997
  cmds:
997
998
  - task: triage-classify:classify
998
999
  vars:
999
1000
  CLI_ARGS: "{{.CLI_ARGS}}"
1000
1001
 
1002
+ triage:strip-withdrawn-chips:
1003
+ desc: "Remaining-set strip of withdrawn triaged / triage:* chips (#4070). -- task triage:strip-withdrawn-chips -- [--apply] [--emit-digest] [--json] [--repo OWNER/NAME]"
1004
+ cmds:
1005
+ - task: triage-classify:strip-withdrawn-chips
1006
+ vars:
1007
+ CLI_ARGS: "{{.CLI_ARGS}}"
1008
+
1001
1009
  triage:summary:
1002
1010
  desc: "Emit the one-line triage state for the session-start ritual (D2 / #1122). Always exits 0; appends a JSONL record to <lifecycle-root>/.triage-cache/summary-history.jsonl. -- task triage:summary -- [--json] [--no-history]"
1003
1011
  cmds:
package/commands.md CHANGED
@@ -309,7 +309,7 @@ Current status: the validation, extractor, provider, registry, generated MAP, an
309
309
  - `task verify:orphan-active -- --changed-only [--base-ref <ref>]` -- the merge-chokepoint form composed into `check:framework-source` / `check:consumer` (#3893). It evaluates only the active/running briefs the candidate's own diff touches, so a brief stranded by another merge cannot fail an unrelated PR and N stranded briefs no longer make N single-brief lifecycle PRs mutually unmergeable. Base ref defaults to `origin/<deliveryBranch>`. It falls back to the full repo-wide sweep -- never to a narrower scan -- when HEAD is on the delivery line (the delivery-tip check) or when git or the base ref is unresolvable, and every run prints which scope it used. Mutually exclusive with `--issue N` (exit 2). The bare verb stays repo-wide for `deft doctor`, manual runs, and swarm finalize.
310
310
  - `task verify:pr-closeout-attestable -- --pr N` -- fail closed when merging PR `N` would close an issue whose brief is still `running` in `xbrief/active/` with acceptance criteria carrying neither `x-directive/evidence` nor `x-directive/disposition` (#3781). The trigger is the PR's structured closing references, **not** the branch diff: CI runs before the merge and the issue closes on it, so a diff-keyed gate can never fail the PR that creates the orphan, and the brief need not be in the diff at all. Reuses `evaluateAcceptanceEvidenceGate`, the same rule `scope:complete` enforces. The refusal names each unattested criterion and the exact shape it needs, narrowed to the evidence kinds that criterion's axis allows. A PR that leaves an unattested brief **without** closing its issue is unaffected. Reads the working tree at `--project-root`, which at merge time is the PR head checkout. Matching is repository-qualified and case-insensitive on the owner/repo slug: closing references are scoped to the PR's repository, so a same-numbered issue tracked by a brief in another repository cannot block the merge, a mixed-case slug for this repository still matches, and a bare tracking number reads as this repository. Three-state exit (0 attestable or closes nothing / 1 unattested closeout / 2 config or lookup error) -- an unresolved closing-reference lookup, or an unresolvable OWNER/REPO, is 2, never a pass. `task pr:wait-mergeable-and-merge` runs it as the last gate before the merge call.
311
311
  - `task verify:lifecycle-visible` -- warn when a clone's ignore configuration hides `xbrief/` / `vbrief/` lifecycle roots (#3505). Uses `git check-ignore -v` on the stage dirs, a matching-extension sentinel under each, and bounded probes derived from ignore-rule globs in root, exclude, `core.excludesFile`, and nested lifecycle `.gitignore` files (so `2026-06-*.xbrief.json` / `2025-*.xbrief.json` cannot report clean), plus `git ls-files -v` for skip-worktree / assume-unchanged. Names the matching rule and source file. Warn-first from `session:start` (per-clone, not on `task check`). Selective `.triage-cache/*.jsonl` entries do not trip. Pass `--enforce` to fail closed.
312
- - `task verify:completed-write-guard` -- fail closed when a newly added `xbrief/completed/` (or `vbrief/completed/`) artifact was not written by `scope:complete` / `scope:fail` (#3679). Artifacts larger than 1 MiB fail closed before read. Historical corpus is advisory via doctor. Remediation names the leftover land PR after a scope-provenance strip (#3476). `scope:complete` can stamp a brief already in `completed/`. Does not change `verify:completed-tracked`.
312
+ - `task verify:completed-write-guard` -- fail closed when a newly added `xbrief/completed/` (or `vbrief/completed/`) artifact was not written by `scope:complete` / `scope:fail` (#3679), or when a change set deletes or renames away an `xbrief/active/` brief with no paired stamped destination (#3766). Same name-status parser covers A, R, and D. Terminal moves pass as `R active->completed` or `D active` plus `A completed` when the destination has a runTransition stamp. A `cancelled/` destination pairs only when it carries a `lifecycleWrite` cancel stamp. Delete-plus-add pairing also requires the dest `plan.title` to match the deleted source so a copied stamp cannot authorize an unrelated brief. Lone-D untracking cleanup halts and names `scope:complete` or leaving the brief untracked. Artifacts larger than 1 MiB fail closed before read. Historical corpus is advisory via doctor. Remediation names the leftover land PR after a scope-provenance strip (#3476). `scope:complete` can stamp a brief already in `completed/`. Does not change `verify:completed-tracked`.
313
313
  - `task verify:completed-tracked` -- fail closed when closed scoped issues lack a tracked `xbrief/completed/` or `xbrief/cancelled/` artifact on the delivery tip (#3264 / #3476); remediate with `task swarm:finalize-cohort` or a lifecycle PR. `task verify:completed-tracked -- --issue N` is the drive-to DONE form (delivery tip `origin/<deliveryBranch>`, not feature HEAD). Standalone verb (not part of `task check`); use `--tip HEAD` when validating an in-flight land branch. An unresolvable delivery tip fails closed (no silent HEAD fallback) -- fetch the delivery branch or pass an explicit `--tip`. Under `--skip-gh`, a named `--issue` with no cached state fails closed; the unscoped corpus scan keeps the offline allowance. Lifecycle-only lands (completed/cancelled xBRIEFs + optional CHANGELOG) use that verb plus finalize-cohort or the lifecycle PR. ⊗ Full `task check` / the TypeScript suite. ⊗ The drive-to story envelope (pre-pr + review-cycle + suite) for a file-copy land.
314
314
  - `task verify:ac` -- product-first acceptance gate (#3284). Runs `plan.acceptance.commands` (or #3267 literal ledger) **verbatim** before done; records AC-source rung (`stated`/`derived`/`project_floor`). Empty commands require `none_stated: true`. Empty resolution is not a green run when the project has no suite floor (`soft_empty` + stamp-acceptance remedy, #3334). Primary name used first in `task check` (fail-fast); `--soft-missing-xbrief` for check composition. Rapid ceremony = AC-only; pressure/degraded makes hygiene advisory. `--capture-only` lists resolved commands without executing. Extends #3267 / #973.
315
315
  - `task verify:literal-ac` -- #3267 mechanism alias for verbatim stated-command run (same flags/cwd); prefer `verify:ac` for product-first done-gate.
@@ -413,20 +413,22 @@ After CLI/deposit upgrade, disk can show the new generation while a long-lived s
413
413
  - ! Default interactive sessions to **read-only posture** until mutation or implementation intent (questions, research, Plan Mode, ticket-shaping). Load AGENTS.md / main.md / USER.md / PROJECT-DEFINITION; confirm alignment with addressing-name; ⊗ do not write `.deft/ritual-state.json`, run install/build side effects, or emit triage welcome, branch-policy, default-branch sync, sync-skill lifecycle checks, or eval/value readback writes unless the operator asks or the task is implementation-ready.
414
414
  - ! **USER.md path (#2544):** resolve via `deft session:start` output (`USER.md resolved …`); default platform paths: Windows `%APPDATA%\deft\USER.md`, Unix `~/.config/deft/USER.md`; override `$DEFT_USER_PATH`; workspace `<project>/.deft/USER.md`. ⊗ Invent or search `~/.config/deft` on Windows — AppData Roaming is canonical.
415
415
  - ! At mutation boundaries (code-writing, scope lifecycle moves, `start_agent`, commits, pushes, PR-from-local-changes, release work): run the mutable quick tier then gated verifier below before proceeding.
416
- - ? Explicit read-only alignment only: `deft session:start -- --read-only` (no ritual-state write).
417
- - ! **Worktree occupancy contract (#3433 / #3604 / #3611):** mutation `session:start` / `session:ready` claim a gitignored `.deft/occupancy.json` lease. Occupancy is cooperative host-session routing between processes that follow the protocol, not authentication against another same-user process: hook JSON and local owner IDs are forgeable. Name it for what it is (#3755): a **cooperative bearer-id boundary, not a lineage** — the lease admits whoever presents an id the record itself names, nothing observes parentage, and a dispatched child is admitted only because a grant records it. A live lease allows only a matching presented owner or member and a ritual state whose exact verified owner agrees; missing, conflicting, or mismatched identity fails closed. `--read-only` does not claim.
416
+ - ? Explicit read-only alignment only: `deft session:start --read-only` (no ritual-state write). Task: `task session:start -- --read-only`.
417
+ - ! **Worktree occupancy contract (#3433 / #3604 / #3611 / #3926):** mutation `session:start` / `session:ready` claim a gitignored `.deft/occupancy.json` lease. Occupancy is cooperative host-session routing between processes that follow the protocol, not authentication against another same-user process: hook JSON and local owner IDs are forgeable. Name it for what it is (#3755): a **cooperative bearer-id boundary, not a lineage** — the lease admits whoever presents an id the record itself names, nothing observes parentage, and a dispatched child is admitted only because a grant records it. A live lease allows only a matching presented owner or member and a ritual state whose exact verified owner agrees; missing, conflicting, or mismatched identity fails closed. `--read-only` does not claim. A stored lease whose `worktree_path` is not this checkout is residue, not a holder: first mutation `session:start` claims over it. Same-tree two-session conflict still fails closed.
418
418
  - ! **Which tree the lease is read from — root admission (#3794 / #4013):** a gated write is judged against the Git working tree its **target** lands in, not always the tree the host payload names. A target in another worktree sharing `--git-common-dir` is admitted and carries occupancy, ritual and active scope with it; a proven different repository, an unreadable target identity, and a mutation spanning two worktrees are refused. A target with **no Git toplevel at all** (OS temp, a home file, anything outside every checkout) is gated against the payload root — deliberate since #3794, published with its reason and a gate-by-gate disposition in [docs/hook-root-admission.md](./docs/hook-root-admission.md), which also records the three-surface matrix (direct write consults admission; generic server-prefixed MCP and out-of-repo Shell writes do not) and the relative-target canonicalization limitation (#4023).
419
419
  - ! **Host owner namespaces (#3611):** canonical owners are `host:<provider>:v1:<base64url(raw-id)>`. Codex uses payload `session_id` for the parent and its subagents. Claude Code uses `session_id`; `agent_id` does not replace the session-family owner. Cursor uses `conversation_id` and requires a simultaneously supplied `session_id` to agree; no Cursor subagent-granularity claim is made until verified. Grok has no verified payload identity, so its owner is the hook process variable `GROK_SESSION_ID` the host publishes (#3873): the hook is a sibling the host spawns, so an agent shell export cannot reach it, and the payload `session_id` is never read. When that variable is absent the host keeps explicit `--session-id` / `DEFT_SESSION_ID` ownership; a malformed one fails closed, as does an ambient `DEFT_SESSION_ID` that contradicts a resolved host owner.
420
420
  - ! **Occupancy identity on a host with no session derivation (#3877):** the hook process environment (`GROK_SESSION_ID` on Grok, or `DEFT_SESSION_ID` inherited by the hook) or an explicit `--session-id`. ⊗ a worker-shell `export DEFT_SESSION_ID`. A shell export reaches vitest, CLI verbs, and `scope:complete`; it never reaches `deft-hook`, a sibling process. Nothing in `content/templates` or `content/skills` mentions the variable. Do not invent an envelope mandate.
421
- - ! **One actor-resolution chain, four terminals (#3954):** claim, release, heartbeat and grant/revoke resolve the acting session by the same lookup order — explicit `--session-id`, then `DEFT_SESSION_ID`, then the owner the running host published. Only the **terminal** differs, and deliberately: `session:start` / `session:ready` **mint**, because claiming establishes an identity where none exists; `occupancy:release`, `occupancy:heartbeat`, `occupancy:grant` and `--revoke` resolve to **empty** and keep the "this process presented no session identity" diagnosis, because they prove an identity rather than establish one. A shared mint would hand those surfaces a plausible id no later hook will present. The practical effect: on a host that publishes an owner, the printed recovery `occupancy:release` is a command the occupant can actually run, instead of one that resolved an empty caller and refused the owner its own lease. Disagreement is reported, not reordered: when `DEFT_SESSION_ID` and the host owner name different sessions the documented order still stands, and a refusal names both ids and the `--session-id` that would act as the host owner. Anonymous release of the occupant recorded in the lease file stays **refused** — possession of that path is not authority to delete a live lease.
421
+ - ! **One actor-resolution chain, four terminals (#3954 / #4066):** claim, release, heartbeat and grant/revoke resolve the acting session by the same lookup order — explicit `--session-id`, then `DEFT_SESSION_ID`, then the owner the running host published. **Claim** is host-authoritative when those disagree: inherited `DEFT_SESSION_ID` must not beat the host-published owner (mirrors the write-gate env drop). Release, heartbeat and grant/revoke still report disagreement without reordering, and still refuse to mint. Anonymous release of the occupant recorded in the lease file stays **refused** — possession of that path is not authority to delete a live lease.
422
422
  - ! **Where the transport still does not reach (#3873 residue):** the CLI resolves the host owner; the write gate runs in the `deft-hook` sibling process, and on a deployment where the host does not publish `GROK_SESSION_ID` into that sibling the hook presents whatever `DEFT_SESSION_ID` it inherited — possibly another host's session. Nothing in this tree can set that process's environment, and the hook writes no env or payload log, so the condition is inferred from the deny text rather than observed. Recovery meanwhile is the ambient step above: the occupant releases its own lease from its own shell. Closing it needs either host cooperation (publish the variable to the hook sibling) or a hook-process capture facility, and both are separate work.
423
423
  - ! **Lifecycle identity transport (#3611):** for every host with a resolved owner, PreToolUse rewrites only exact, simple canonical `deft` / `directive` lifecycle commands and source-repo `task <verb> [-- ...]` forms to add the host's `--session-id`. Direct CLI spellings are `session:start`, `session:ready`, `session:end`, `occupancy:steal`, `occupancy:release`, `occupancy:heartbeat`, `occupancy:grant` (#3954, its `--revoke` arm included), and `swarm-launch`; the Task spelling for the last verb is `swarm:launch`. For owner-requiring lifecycle commands, the shell execution directory must be absent or realpath-align exactly with the hook project root. Path-bearing executables (`./deft`, `/path/deft`), path/destination flags (`--project-root`, `--paths`, path-valued `--stories`, `--output`, `--worktree-map`, `--gate-clearances`), `--no-audit`, consumer-repo Task indirection, Task aliases (`task deft:<verb>`), and compound, redirected, quoted, aliased, wrapped, or otherwise ambiguous forms are not auto-rewritten. A supported-host hook rejects a recognized lifecycle form outside the narrow rewrite surface until it carries the explicit matching `--session-id`; an execution-root mismatch on an owner-requiring command always denies. With a matching ID on other excluded forms, the original command remains under normal host permission handling. Manual/integrated-terminal callers must likewise pass one matching `--session-id` (or `DEFT_SESSION_ID`) through the complete lifecycle.
424
424
  - ! **Transition, steal, and release (#3611):** an existing live UUID lease cannot silently become a host-namespaced owner. Prefer one aligned transition: `session:start --steal --confirm --occupant <reported-session-id> --session-id=<your-session-id>` (an exact host-mediated command receives the final option automatically). Bare `occupancy:steal --confirm --occupant <reported-session-id> --session-id=<your-session-id>` changes only the lease; direct writes remain denied unless ritual state already names the new owner. If they differ, align the same writer ID with `session:start --rearm --session-id=<same-session-id>` when re-arm is eligible, or cold `session:start --session-id=<same-session-id>` otherwise. The occupant drops a live lease with `occupancy:release` or `session:end`; a non-owner cannot clear it. Confirmed steal remains required for a live occupant and prints existing `claimed_at` / `heartbeat_at` / `last_write_at` when available.
425
425
  - ! **Lease refresh (#3599):** a gated write by the owner re-stamps `heartbeat_at`, so a session that keeps working keeps its lease, up to the absolute age cap below. The re-stamp is floored at a quarter of the TTL to avoid rewriting the lease per event, and it records `last_write_at` separately — `heartbeat_at` moves on any lease touch, `last_write_at` only on a gated product write, which is what tells a would-be stealer that the occupant is mid-work. When the owner's own lease is inside the staleness window (three quarters of the TTL) and the automatic re-stamp could not run, the write gate says so instead of letting the lease lapse silently; a write that did re-stamp stays quiet, because the warning would name a state that write just cleared. A re-stamp blocked by the lock leaves the lease untouched and the gate re-decides against the file on disk: contention alone keeps the rightful owner writing, while a takeover that completed during the wait denies. Long quiet stretches produce no gated write to ride on, so the owner refreshes explicitly with `deft occupancy:heartbeat` (Task: `task occupancy:heartbeat`). Refresh extends a lease the caller already holds — it never claims a free worktree, never takes a foreign lease, and never mints an owner; an unheld lease exits non-zero. Under a supported host the exact command receives the matching `--session-id` automatically; manual and Grok callers pass `--session-id <id>` or `DEFT_SESSION_ID`.
426
426
  - ! **Absolute lease age cap (#3599):** a lease is also dead 12 hours after `claimed_at`, whatever its heartbeat says, and no refresh path may extend that — `claimed_at` survives every re-stamp. Occupancy admits whoever presents the occupant's session id, so refresh alone would let any process holding that string keep a worktree forever and disable the crash-recovery reclaim the TTL exists for. The two dead states call for different responses and read differently: a stale heartbeat means refresh sooner; past the cap the tree is no longer held, so the capped holder's own gated writes are **refused** until it re-claims with `session:start --session-id=<your-session-id>` — allowing them would let the very bearer the cap bounds keep mutating a worktree a peer may claim at any moment. A lease that is both stale and capped reads as capped, because refreshing cannot revive it and the stale wording would send the holder somewhere that does not help. A different session is unaffected: taking over an aged-out tree is the reclaim the cap exists to enable. Twelve hours is sized by the stalled owner, not the busy one: refresh keys on writes, so an agent that finishes overnight and waits for its operator stops refreshing while staying legitimate, and 12 hours spans a 23:00 dispatch to a 09:00 handoff while still bounding reclaim inside a day. Reaching the cap costs one re-claim, never the work. A time cap cannot tell a stalled-but-live owner from a dead one — the only liveness signal on this path is a write; if that bites, the answer is a write-free liveness signal (explicit parked state, or refresh on non-write activity), not a larger number.
427
427
  - ! **Lease membership (#3755):** the occupant admits a dispatched child with `deft occupancy:grant --session-id=<owner> --child-session-id=<child> --role <worker-role> [--ttl-minutes N] [--worktree <path>] [--host <id>] [--address <name>] [--join-protocol none|heartbeat-file|parent-message]` (Task: `task occupancy:grant`), and withdraws it with the same command plus `--revoke`. The grant records owner id, child id, worktree, role and expiry, so a write resolves to a session rather than to whoever held the string. Admission splits: **writes** are admitted for the owner **or** a valid unexpired member; **release, steal, heartbeat and cohort close-out stay owner-only**, so a grant never escalates into the lease itself. An expired grant is refused on read, expiry is clamped to the lease's own absolute age cap, a lease carries at most 32 live grants, and a steal or a fresh claim starts with none — grants belong to the lease that issued them. The child id is checked at grant time (#3954): a value under the reserved `host:` prefix must be a well-formed canonical owner, and one that re-prefixes the owner's own host payload under a second provider is refused as the self-grant it is. Both were granted before, and the write gate then admitted them as `member`, so the lease read as membership while admitting nobody. An id outside that prefix is still accepted, because a child on a host with no identity contract presents whatever `DEFT_SESSION_ID` holds. Withdrawal skips the check, so a malformed grant written earlier stays revocable. A member's write re-stamps `heartbeat_at` and `last_write_at` on the same quarter-TTL floor as the owner's: a tree a granted child is actively writing is in use, and letting it lapse would hand the worktree to a peer mid-edit. `claimed_at` is untouched, so the absolute age cap and the grant's own expiry still bound it. The verb is inside the rewrite surface since #3954, so a supported host adds the matching `--session-id`; a `--worktree` rebind stays outside it and must carry the owner explicitly. All of it is cooperative, like the rest of this lease: it names who may write, it does not authenticate them. Ritual state stays single-owner, so a member writes under the occupant's ceremony: the composite hook write gate measures the verified ritual owner against the occupant that issued the grant rather than against the writer, and a revoked or expired member is refused there as well.
428
- - ! **Parent and child, per identity-source kind (#3954 / #3999):** there is no single answer, so do not look for one. On a **`host-env`** host (Grok) the host publishes a different id into each agent session, so a parent and its dispatched children are **different actors**: each resolves its own owner through the chain above and claims its own worktree, which is where the dispatch envelope already puts it. Membership is **not** automatic there — 32 grants at a four-hour expiry against a twenty-minute lease means granting on every dispatch exhausts a busy parent's lease inside a day — and the revocation trigger stays the owner's own `--revoke` or the grant's expiry. A dispatched child's owner lease is released when orchestration observes `phase: terminal`, using the occupancy owner and exact worktree root the parent recorded at dispatch under `.deft/child-occupancy/` (lease-gated; not `.deft-scratch/**`). Release is compare-and-release under the occupancy lock: only if that recorded child still owns that recorded tree. A successor owner is left alone. On a **`payload`** host (Codex, Claude Code, Cursor) parent and subagents share one id, so there is no foreign child lease to admit and nothing to grant; the live consequence is the inverse one, that a parent's `occupancy:release` removes a working child's lease mid-flight with no denial, because both present the same string. The terminal transition is a no-op on that arm — auto-release would drop the parent's live lease. Swarm close-out still releases only the launcher's `occupancy_session_id`. No new reap verb.
429
- - ! **Lifetime and enforcement boundaries:** expired heartbeat (20 min) is free for claim-over-expired, and the release path may clear residue; no `reap` verb. No host-to-lease map or credential file is persisted; supported hooks re-derive their owner from each payload. Gated writes renew the heartbeat (#3599), so the 20-minute window runs from the owner's last write rather than from its claim, bounded by the absolute age cap above. Missing or drifted hooks remain #3742, and spawn tools remain outside occupancy enforcement. Join negotiation is owner-issued membership (`occupancy:grant`, #3755); the parked child-initiated `occupancy:request` queue stays unimplemented and is no longer named in remediation. `swarm:launch` carries the same explicit owner and persists `occupancy_session_id` in a cohort-keyed record; close-out uses that cohort entry as authority, requires any ambient owner to corroborate it, and never substitutes the current live lease owner. Swarm close-out stays `releaseSwarmOccupancy` on complete-cohort.
428
+ - ! **Parent and child, per identity-source kind (#3954 / #3999 / #4066):** there is no single answer, so do not look for one. On a **`host-env`** host (Grok) the host publishes a different id into each agent session, so a parent and its dispatched children are **different actors**: each resolves its own owner through the chain above and claims **its own worktree**. Membership is **not** automatic there — 32 grants at a four-hour expiry against a twenty-minute lease means granting on every dispatch exhausts a busy parent's lease inside a day — and the revocation trigger stays the owner's own `--revoke` or the grant's expiry. Spawned mutating work takes a reserved linked worktree before occupancy claim; sharing the primary checkout with a live occupant is refuse, not grant-across-host. On a **`payload`** host (Codex, Claude Code, Cursor) parent and subagents share one id, so there is no foreign child lease to admit and nothing to grant on the same tree; own-tree children still record a dispatch lease. A parent's `occupancy:release` on the parent tree does not drop a child tree. No new reap verb.
429
+ - ! **Spawn occupancy seam (#4066):** implement-class spawn (`Task`, `spawn_subagent`, `start_agent`, …) inspects `tool_input.isolation=worktree` / `worktree_path` / `cwd`. Absent field fails closed (do not inherit parent cwd). Unique dispatch reservation (incarnation) is persisted before launch. Occupancy consult is on the destination tree, not a write-gate flip with `actor=null`. Explore and ephemeral spawns stay exempt. Grok PreToolUse cannot rewrite spawn input; hosts that can (`claude`, `cursor`, `codex`) re-root cwd onto the reserved tree. Primary-checkout claim is the exception (`release-cut`, `policy-restore`, `operator-default-branch`) from a trusted producer; `--read-only` never claims.
430
+ - ! **Dispatcher-owned terminal release (#4066 / #3999):** recorded-child close-out is dispatcher lifecycle on a dispatch-provenance `(owner, worktree, incarnation)` tuple. Release requires parent-id match, incarnation match, skip invalid heartbeats, and refuse a tree that is not the heartbeat tree or a dispatcher-allocated tree. Ordinary self-claim records are not close-out. Payload-kind skip remains for same-tree shared identity; a linked worktree distinct from the observer is released. Heartbeats should echo `incarnation` from the dispatch envelope.
431
+ - ! **Lifetime and enforcement boundaries:** expired heartbeat (20 min) is free for claim-over-expired, and the release path may clear residue; no `reap` verb. No host-to-lease map or credential file is persisted; supported hooks re-derive their owner from each payload. Gated writes renew the heartbeat (#3599), so the 20-minute window runs from the owner's last write rather than from its claim, bounded by the absolute age cap above. Missing or drifted hooks remain #3742. Implement-class spawn is occupancy-consulted on the destination worktree (#4066); explore/ephemeral spawn stay outside that consult. Join negotiation is owner-issued membership (`occupancy:grant`, #3755); the parked child-initiated `occupancy:request` queue stays unimplemented and is no longer named in remediation. `swarm:launch` carries the same explicit owner and persists `occupancy_session_id` in a cohort-keyed record; close-out uses that cohort entry as authority, requires any ambient owner to corroborate it, and never substitutes the current live lease owner. Swarm close-out stays `releaseSwarmOccupancy` on complete-cohort.
430
432
  - ~ Operators MAY still explicitly request full `deft session:start`, `deft triage:welcome`, sync, or doctor in read-only sessions.
431
433
 
432
434
  ### Assist / research posture (#1802)
@@ -455,17 +457,17 @@ Cross-link: spawn three postures and deny recoveries live under § Agent-host di
455
457
  ### Mutable ritual (mutation posture)
456
458
 
457
459
  - ! On **mutation** session start, run `deft session:start` (or `task session:start` in framework source) after loading AGENTS.md. Records quick-tier ritual in `.deft/ritual-state.json`: alignment confirmation, branch-policy disclosure, `deft verify:tools` guidance, default-branch sync warnings, and `deft triage:welcome` one-liner. State is worktree- and HEAD-bound; stale after `plan.policy.sessionRitualStalenessHours` hours (default 4). Mutation start also claims the worktree occupancy lease (`.deft/occupancy.json`); see Session routing (#3433).
458
- - ! **Orientation compression Now (#3286):** mutation cold `session:start` composes `doctor` + #3282 toolchain preflight (and deposit-sha fast-paths for `agents:refresh` / `verify:cache-fresh`) as inline sections with per-section status lines — composition of existing steps, not a new monolith. When the deposit fingerprint (payload + templates + engine) is unchanged, refresh surfaces print one-line `unchanged - sha match` no-ops. Opt-in compact output: `deft session:start -- --compact` or `DEFT_SESSION_COMPACT=1` (verbose remains the default). #2176 read-only default is unchanged. Dual-path Later (`deft orient`) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
460
+ - ! **Orientation compression Now (#3286):** mutation cold `session:start` composes `doctor` + #3282 toolchain preflight (and deposit-sha fast-paths for `agents:refresh` / `verify:cache-fresh`) as inline sections with per-section status lines — composition of existing steps, not a new monolith. When the deposit fingerprint (payload + templates + engine) is unchanged, refresh surfaces print one-line `unchanged - sha match` no-ops. Opt-in compact output: `deft session:start --compact` or `DEFT_SESSION_COMPACT=1` (verbose remains the default). #2176 read-only default is unchanged. Dual-path Later (`deft orient`) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
459
461
  - ! Cold `session:start` does **not** run the live agent-hook probe. Functional readiness belongs to the gated mutation path so cold ceremony retains the #2990/#2991 latency profile.
460
462
  - ! **Hot path latency (#2991):** by default, mutation `session:start` does **not** block ritual-state write on optional network. It skips the npm release-availability probe and triage cache empty-hydrate / self-heal (`ensureTriageCacheHydrated` / `maybeSelfHealCache`). Targets (operator-facing, not CI-hard): warm hot path typically under a few seconds once tools are on PATH; cold path dominated by local `verify:tools` and git, usually well under ~30s when optional network is off. Empty-cache GitHub fetch-all and npm `view` previously accounted for multi-minute hangs in the WWYSYDH pilot — those stay off the critical path unless opted in.
461
463
  - ! **Cold vs re-arm ceremony tiers (#2992):** default `session:start` is the **cold** (full) path. After age staleness or compact re-arm (#2113) on the **same worktree** with continuous HEAD and previously-passing quick steps, prefer `deft session:start --rearm` (alias `--tier=rearm`) to refresh the ritual clock + HEAD/worktree bind without `verify:tools`, triage welcome, release probe, or staleness tickler. Full cold remains required for missing/invalid state, worktree change, discontinuous HEAD, first install, or failed/missing quick steps. Compact marks `rearm_needed`; PreToolUse denial and inspect/verify messages prefer re-arm recovery when cold is unnecessary.
462
- - ? Opt into optional network: `deft session:start -- --with-network` or `DEFT_SESSION_START_NETWORK=1`. When enabled, the bounded release-availability probe runs against the public npm registry (skips framework source checkouts, non-release pins, and `DEFT_NO_NETWORK=1`; identical latest-version notices throttle for 24 hours in `xbrief/.triage-cache/release-availability-state.json`). Default-mode triage welcome then also hydrates/self-heals the triage cache. This is separate from `deft doctor`, whose bare and gated invocations remain offline by default (#2182). Refs #1692 / #2991. Re-arm never runs optional network.
464
+ - ? Opt into optional network: `deft session:start --with-network` or `DEFT_SESSION_START_NETWORK=1`. When enabled, the bounded release-availability probe runs against the public npm registry (skips framework source checkouts, non-release pins, and `DEFT_NO_NETWORK=1`; identical latest-version notices throttle for 24 hours in `xbrief/.triage-cache/release-availability-state.json`). Default-mode triage welcome then also hydrates/self-heals the triage cache. This is separate from `deft doctor`, whose bare and gated invocations remain offline by default (#2182). Refs #1692 / #2991. Re-arm never runs optional network.
463
465
  - ~ `session:start --json` includes `steps[]` with `name` + `duration_ms` for major phases (`alignment`, `branch_policy`, `verify_tools`, `triage_welcome`, `release_probe`, `ritual_write`), plus total `duration_ms`, `optional_network`, and `ceremony_tier` (`cold` | `rearm`). Skipped optional steps report `skipped: true` and `duration_ms: 0`. Use this for attribution when investigating ceremony wall-clock.
464
466
  - ~ **Process-cost events (#2994 / #3508):** on mutation `session:start` completion (cold or re-arm), Directive appends a local `session:start` behavioral event to `.deft-cache/events.jsonl` with `ceremony_tier`, `duration_ms`, `exit_code`, and optional `steps[]` (same labels as `--json`). Mutation `session:start` also prints one operator-visible `ceremony <tier> <ms>` line (hidden under `--compact` / `DEFT_SESSION_COMPACT`). When PreToolUse denies for `ritual-not-ready`, a local `session:ritual-blocked` event records `tool_name`, `code`, and optional `recovery_tier` / `detail`. Always-on best-effort (never blocks ceremony or deny path); not gated on `valueFeedback`; **no remote upload** (Product Insights #2603 is a separate opt-in). Pull the rollup with `task value:show` (composed reader; CLI process time, not agent-turn wall clock). See § Process-cost events below. ⊗ Do not use the printed CLI duration as #3286 Later graduation input.
465
467
  - ~ At safe idle points (clean tree, no in-flight story), mutation session start and `deft scope:complete` may also run the staleness tickler: an interactive, consent-based offer to upgrade Directive (`npm i -g @deftai/directive@latest`) and/or migrate xBRIEF (`deft migrate:xbrief`). Escalation tiers, snooze windows, and opt-out live under `plan.policy.stalenessTickler` — inspect with `deft policy:show --field=stalenessTickler`. State persists in `xbrief/.triage-cache/staleness-tickler-state.json`. Skips framework source checkouts, dirty trees, CI/headless (`DEFT_SESSION_RITUAL_SKIP=1`), and typed opt-out. Refs #2488 / #2489.
466
468
  - ! Before any code-writing tool call or `start_agent` implementation dispatch, run `deft verify:session-ritual -- --tier=gated`. Gated tier fails closed unless quick-tier state is fresh; lazily records the non-deferrable `agent_hooks` readiness gate plus `deft doctor` and `deft verify:cache-fresh` entrypoints. Agent-hook correctness is independent of doctor warnings and throttling. Step 0 of the pre-`start_agent` gate stack.
467
469
  - ! **One-shot recovery (#2993 / #3100):** when PreToolUse denies writes for a stale/missing gated ritual, run `deft session:ready` (or `task session:ready`). It composes `session:start` (only when quick-tier is not green) + `verify:session-ritual -- --tier=gated` + `cache fetch-all --force` when `cache_fresh` is the remaining blocker, then re-verifies. Even when gated inspect is already fresh, the fast path forces one live `agent_hooks` check so later drift cannot hide behind cached ritual state; it still avoids unnecessary fetch-all. Flags: `--json`, `--repo OWNER/NAME`, `--with-network` (forwarded to session:start). Prefer this over juggling the multi-step recovery sequence under hook pressure.
468
- - ? Postpone with `deft session:start -- --defer step=reason` (`alignment`, `branch_policy`, `triage_welcome`, `doctor`, `cache_fresh`). `agent_hooks` is non-deferrable.
470
+ - ? Postpone with `deft session:start --defer step=reason` (`alignment`, `branch_policy`, `triage_welcome`, `doctor`, `cache_fresh`). `agent_hooks` is non-deferrable.
469
471
  - Headless workers / CI MAY set `DEFT_SESSION_RITUAL_SKIP=1`; verifier exits 0 but warns when bypass hides failure.
470
472
  - ⊗ Self-report ritual complete without fresh `deft session:start` state; ⊗ bypass `deft verify:session-ritual` before implementation dispatch; ⊗ reorder/skip/merge ritual tiers without operator override.
471
473
 
@@ -564,7 +566,7 @@ User-facing surface for the Phase 0 triage workflow and the unified content cach
564
566
 
565
567
  Directive does not guess your mix. Either you name the next units in order (**ordered plan**), or you let the ranked backlog suggest (**queue**). Labels bias the queue; they do not override an active plan.
566
568
 
567
- **See also (pre-promotion portfolio):** when the need is to cluster competing RFCs/issues into a propose-not-apply priority brief (not single-item queue ranking), use `skills/deft-directive-portfolio-priority/SKILL.md` (#3201 / #3198) classify is filter-only; no SCM label writes.
569
+ **See also (pre-promotion portfolio):** when the need is to cluster competing RFCs/issues into a propose-not-apply priority brief (not single-item queue ranking), use `skills/deft-directive-portfolio-priority/SKILL.md` (#3201 / #3198). Classify `--mirror` is withdrawn (#4070); no SCM label writes from that skill.
568
570
 
569
571
  | Path | When | Who sets it | Bare "what's next?" means |
570
572
  |---|---|---|---|
@@ -592,11 +594,11 @@ Directive does not guess your mix. Either you name the next units in order (**or
592
594
  - `task triage:history -- <issue>` -- show decision history.
593
595
  - `task triage:reset -- <issue>` -- append a reset record so a candidate can be reconsidered.
594
596
  - `task triage:bulk-accept|bulk-reject|bulk-defer|bulk-needs-ac` -- apply predictable decisions over filtered cached candidates.
595
- - `task triage:summary`, `task triage:scope`, `task triage:scope-drift`, `task triage:subscribe`, `task triage:unsubscribe`, `task triage:classify`, `task triage:welcome`, and `task triage:smoketest` -- supporting workflow and onboarding commands.
596
- - `task triage:classify -- --mirror [--apply] [--re-enrich] [--include-closed] [--author LOGIN|@me] [--repo owner/name] [--batch-size N] [--delay-ms N] [--sample-limit N] [--json]` -- **Bootstrap mass-triage / Tier-1 SCM label mirror (#1423 Wave 1–2 / #3125, #3129, #3197).** Runs the existing classify engine over the github-issue cache and mirrors outcomes as labels (`triaged` idempotency marker + optional `plan.policy.triageLabelMirror.actionLabels`). **Default state filter is open-only** (opt-in `--include-closed` for closed archive stamps). Optional `--author` / `--author-mine` scopes plan/apply to matching `author.login` (AND with open-only; digest surfaces the filter) (#3129). Dry-run by default: operator digest with totals (scanned/planned/already_triaged/no_match/closed_skipped/author_skipped/errors), breakdown by **state / rule / action**, and samples (not a full dump as primary UX); `--json` includes the same aggregates. `--apply` writes via the SCM label client / repo-mutation boundary in **batches** (`--batch-size`, default 10) with **rate-limit delay** (`--delay-ms`, default 1000), reports partial failures, and is idempotent on re-run (already-`triaged` skip). **Re-run vs re-enrich (#3124 / #3197):** default re-run keeps the one-shot stamp (`skipped_already_triaged`). After `actionLabels` / auto-classify rule / hold-marker changes, opt in with **`--re-enrich`** (still dry-run by default; pair with `--apply` to write) to re-classify already-stamped issues and plan **additive** label deltas only (v1 never removes obsolete chips; never full reconcile). Digest distinguishes `kind=first-time` vs `kind=re-enrich` planned/applied rows (`planned_kind` / `re_enrich_planned`). Missing labels fail closed per issue with a create-label hint. **Never** calls `triage:accept` and **never** writes `proposed/` xBRIEFs. Wave 3 (agent Tier-2 comments) remains out of scope.
597
+ - `task triage:summary`, `task triage:scope`, `task triage:scope-drift`, `task triage:subscribe`, `task triage:unsubscribe`, `task triage:classify`, `task triage:strip-withdrawn-chips`, `task triage:welcome`, and `task triage:smoketest` -- supporting workflow and onboarding commands.
598
+ - `task triage:classify -- --mirror` -- **Withdrawn (#4070).** Dry-run and `--apply` both fail closed with a pointer at #4070. Replacement sieve is #4071. #3579 is transitively withdrawn for the gap. Do not close #1423, #3579, #2611, or #3923. `--list` / `--validate` remain. Strip leftover chips with `task triage:strip-withdrawn-chips` (remaining-set apply, not classify).
597
599
  - **Triage write-back pairing (#1423 / ADR-005).** When the author stamps that a lean is mechanism-shaped, write **both**: body-text field `mechanism-shaped: true` (the artifact) **and** mirrored label `design-critique:mechanism-shaped` (what `plan.policy.judgmentGates` matches and lists show). Do not compute the classification. No stamp -> the design-critique gate never fires. Clearance is a separate line on the thread: `design-critique: warranted | not warranted, because ...` -- engine evaluates presence, shape, and authority, never the because-clause content. Advisory/observe; `verify:judgment-gates --enforce` stays opt-in unused; `scope:promote` does not gain `--enforce` in this story.
598
600
  - **Design-critique catalog chip (#3642).** Parent attach of `triage-ready` / recut `mechanism-shaped` MUST use `task scm:issue:design-critique-chip -- --issue N --chip triage-ready|mechanism-shaped [--repo OWNER/NAME]` (or `deft scm issue design-critique-chip`). Closed catalog remaining-set replace, one write. ⊗ `gh api POST .../labels`. ⊗ additive `scm:issue:edit --add-label`.
599
- - **Operator discovery for SCM label mirror (#3124).** Cold `session:start` (via `triage:welcome` default mode) surfaces a **throttled** tip until the first successful `--mirror` dry-run or operator ack — **not** on every re-arm. Tip teaches existence **and** get-the-most: dry-run `deft triage:classify -- --mirror` (open-only; `--include-closed` opt-in); `--apply` batches writes and **never** auto-accepts into `proposed/`; defaults only stamp **`triaged` on matches** (control stamp, not disposition); **board usability is greatly decreased without `actionLabels`** — recommend full five-chip map (`defer→triage:deferred`, `archive→triage:archived`, `accept→triage:lifecycle-linked`, `escalate→triage:needs-human` + always `triaged`); more matches → `plan.policy.triageAutoClassify` in PROJECT-DEFINITION; inspect via `deft policy:show --field=plan.policy.triageLabelMirror`; labels must exist on GitHub; applying `triaged` before action chips skips re-enrichment on re-run (use `--re-enrich`); point at consumer kit **#2611** (`content/docs/consumer-issue-label-kit.md`) do not invent vocabulary. Dismiss without dry-run: `deft triage:classify -- --ack-discovery` (production entry for tip ack). Dry-run digests SHOULD footer-hint when `actionLabels` is empty or open `no_match` dominates. **Anti-swallow:** when the tip fires, agents MUST restate existence + get-the-most in the **user-visible** message (not absorb ceremony alone).
601
+ - **Operator discovery for SCM label mirror (#3124).** Withdrawn with classify `--mirror` (#4070). The session/welcome tip does not fire. #3124 discovery and #1423 Wave 1–2 apply are unusable after this lands; those issues stay open.
600
602
 
601
603
  ### Cache Tasks
602
604
 
@@ -47,6 +47,23 @@ The Stop 1 write-back records `refutation-target:` naming the triage author's hi
47
47
 
48
48
  `verify:judgment-gates --enforce` stays opt-in unused in this rollout. Advisory observe first. No marker means the gate never fires. Voluntary critiques stay legal.
49
49
 
50
+ ### Run posture (#4072)
51
+
52
+ The front door is a session-local run-posture field, not a second ingest switch and not a third occupancy concept. Occupancy mechanics stay on #4020. Cite that rule; do not clone it.
53
+
54
+ Closed tokens on the operator utterance: `direct`, `forge-only`, `github-only`, `github only`, `no worktrees` for direct; `checkout` for the mutating checkout path. `parseOperatorRunPosture` (`packages/core/src/design-critique/run-posture.ts`) matches those tokens with word boundaries. Missing token, including `arc <N> yolo`, asks before Stop 1. Yolo does not pick a mode. `ingest` is not a front-door mode; that token asks. ⊗ Substring or NLP classification. ⊗ Front-door mode `ingest`. ⊗ A yolo default-direct.
55
+
56
+ Record `arc-mode: direct` or `arc-mode: checkout` on the Stop 1 write-back. `evaluateDirectDispatch` is a fixture over parent-claimed actions, not a live occupancy observer.
57
+
58
+ - ! Record `arc-mode:` on the Stop 1 write-back before any mutation-capable session start.
59
+ - ! Direct means `session:start --read-only`, or an explicit occupancy release if a mutation lease is already held.
60
+ - ! Recognition of `direct` happens before mutation initialization. Refuse direct when mutation `session:start` has already occurred, unless the lease is released first.
61
+ - ! Direct posting path that survives a foreign lease: `gh issue comment --body-file -` (stdin). Least-authority envelope: bounded GitHub reads and one comment post.
62
+ - ! Direct N>1 process-only critics are not swarm Phase 2 and not a #4066 mutating worker. Family CLIs still spawn. ⊗ `git worktree add`. Pin reads to `git show <dispatch-sha>:` against the recorded SHA. ⊗ A moving branch ref.
63
+ - ! Direct EXIT names ingest as a later operator verb after the completed-arc record. ⊗ Auto-ingest.
64
+ - ⊗ Treat `arc-mode:` as ingest clearance or as a permanent ingest denial.
65
+ - ⊗ Record a front-door mode named `ingest`.
66
+
50
67
  ## Stop 2 — Variant selection
51
68
 
52
69
  Record one line per arc: the **charter** (`refutation` | `open critique`), the **spend** (`N=1`, or `N≥3` when the permission is used), and why.
@@ -155,10 +172,27 @@ panel-deposit
155
172
  round: 1
156
173
  siblings: 3
157
174
  input-ceiling: 5390001612
175
+ families: grok, claude, codex
176
+ ```
177
+
178
+ A sibling-seat record is an allowed substitute for the `families:` line:
179
+
180
+ ```text
181
+ seat: grok launcher: spawn_subagent
182
+ seat: claude launcher: claude
183
+ seat: codex launcher: codex
158
184
  ```
159
185
 
160
186
  **Panel completeness is behavioural.** The deposit MUST above, and every sibling-completeness clause in this document, bind the parent. No code observes them. `evaluateCompletedArcRecord` reads a deposit only as evidence that an arc is in flight; it never counts critic posts and never compares a count against `siblings:`. `evaluateParentAudit` carries no round, sibling, or deposit field. Both halves hold: the obligation on the parent is real, and nothing machine-checks it. A parent that binds on a partial panel breaks this contract and no gate will stop it (#3850).
161
187
 
188
+ **Seat families are a different check (#4067).** Completeness is still unobserved. Family composition is fail-closed over parent-claimed families and a PATH probe, not over live GitHub comments.
189
+
190
+ - ! For spend N≥3, the panel-deposit or a sibling-seat record names three claimed families before the first sibling spawn. Parent claims the family at dispatch. ⊗ Classify family from a model slug.
191
+ - ! A same-family sibling set is not a panel. ⊗ Lean it. Printed recovery is re-seat (or halt), not wait for Stop 5.
192
+ - ! Grok Build launcher tree: Grok seat via `spawn_subagent` (or `grok`); Claude seat via `claude` CLI; Codex seat via `codex exec`. Worktree isolation before each spawn unless `arc-mode: direct`. Probe `claude` / `codex` on PATH before offering paste-ready. Paste-ready is the fallback when a named family's CLI is absent. Close stdin on CLI spawns (Windows `cmd /c "… <nul"`; Unix `… </dev/null`). Pointer: [`docs/grok-build-subscription-setup.md`](../docs/grok-build-subscription-setup.md).
193
+ - ! `evaluatePanelSeatComposition` (`packages/core/src/design-critique/panel-seat-families.ts`) refuses a same-family N≥3 set and refuses paste-ready-first when `claude` / `codex` resolve. It takes claimed families and PATH probe results. It does not observe live comments and does not count siblings.
194
+ - ! After a dispatch-composition miss, offer a prevention issue. On yolo, file it. ⊗ Only re-seat.
195
+
162
196
  ### Comment lead (model then role)
163
197
 
164
198
  Comment-lead field. The first two lines of the triage write-back and of every critic, parent, and #3640 auto-posted comment name the LLM and the posting role. Keep the first line as `model: <slug>`. The second line is `role: triage|critic|parent`.
@@ -211,11 +245,14 @@ Keep the arc in this contract until a verified synthesis is accepted.
211
245
 
212
246
  - ! Each critic dispatch EXITs after posting.
213
247
  - ! Operator (or parent after an operator verb) dispatches the next envelope.
214
- - ! After each critic EXIT, parent posts a successor lean with proposed per-heading takes **before** printing `accept` / `retry differences` / `walk` / `walk all`. That posted lean is the first operator surface. Chat is not the record.
248
+ - ! After this round's same-round siblings are posted, parent posts a successor lean with proposed per-heading takes **before** printing `accept` / `retry differences` / `walk` / `walk all`. That posted lean is the first operator surface. Chat is not the record.
249
+ - ! Do not post the successor lean or print the verb menu while same-round siblings remain unposted. Spend is permission, not the wait rule. Parent dispatch bookkeeping is the trigger; thread posts corroborate.
250
+ - ⊗ Count self-attested `role: critic` lines as panel-complete.
215
251
  - ! Operator confirm or amend binds the proposed takes on that posted lean. Binding takes is not synthesis bind and does not stamp `design-critique:triage-ready`.
216
- - ⊗ Bind synthesis or stamp `design-critique:triage-ready` while same-round siblings remain unposted. The first lean after one critic EXIT is the take-offer, not the bind.
217
- - ! Later successor leans follow accept-X or walk-end, or land before synthesis. This supersedes #3627's "successor lean only after accept-X" for the first lean after critic EXIT. Later leans may still follow accept-X / walk-end.
218
- - ⊗ Print `accept` / `retry differences` / `walk` / `walk all` when no successor lean is posted for this critic EXIT. An empty-lean verb menu is a contract miss.
252
+ - ⊗ Bind synthesis or stamp `design-critique:triage-ready` while same-round siblings remain unposted. The first lean after this round's siblings are posted is the take-offer, not the bind.
253
+ - ! Later successor leans follow accept-X or walk-end, or land before synthesis. This supersedes #3627's "successor lean only after accept-X" for the first lean after this round's siblings are posted. Later leans may still follow accept-X / walk-end.
254
+ - ⊗ Print `accept` / `retry differences` / `walk` / `walk all` when no successor lean is posted for this round. An empty-lean verb menu is a contract miss.
255
+ - ! The halt line remains postable while same-round siblings remain unposted. An unposted sibling with no dispatch-fail evidence is Dual stop envelope exhaustion.
219
256
  - ⊗ Auto-dispatch critics (#3578 / #1702).
220
257
  - ⊗ Hand the arc to `triage:accept` / `scope:promote` until the completed-arc record is present: `design-critique: synthesis accepted, because …` citing the accepted successor lean (and the verified-claims table when posted). Catalog chips (`design-critique:mechanism-shaped` / `design-critique:triage-ready`) are list-visible convenience, not clearance. A lone synthesis-accepted-shaped comment that does not cite an accepted lean does not unblock ingest.
221
258
  - ⊗ Stamp `design-critique:triage-ready` at critic-post.
@@ -225,12 +262,13 @@ Keep the arc in this contract until a verified synthesis is accepted.
225
262
 
226
263
  ## Successor lean
227
264
 
228
- After each critic EXIT, parent posts a successor `**Lean:**` comment with proposed per-heading takes. That posted lean is the first operator surface. Later successor leans follow accept-X or walk-end, or land before synthesis.
265
+ After this round's same-round siblings are posted, parent posts a successor `**Lean:**` comment with proposed per-heading takes. That posted lean is the first operator surface. Later successor leans follow accept-X or walk-end, or land before synthesis.
229
266
 
230
- - ! After critic EXIT, post the successor lean before printing `accept` / `retry differences` / `walk` / `walk all`.
267
+ - ! After this round's same-round siblings are posted, post the successor lean before printing `accept` / `retry differences` / `walk` / `walk all`.
231
268
  - ! Lead that lean with the plain-language summary under the `## In plain English` token. The obligations are in `## Plain-language summary` below.
232
269
  - ! Operator confirm or amend is what makes those takes bindable. An all-accept draft still goes through this offer. Confirming or amending an all-accept first lean binds those takes. It does not auto-stamp synthesis or `design-critique:triage-ready` while same-round siblings remain unposted.
233
270
  - ! Cite accepted critic ids/headings, the still-open residual, and the write-back or prior lean it supersedes.
271
+ - ! A lean that closes a round of two or more names that round's dispatched sibling count and cites each sibling comment id it is leaning over.
234
272
  - ! Carry a per-heading take on the successor lean: `accept-into-contract` | `disagree` | `defer`. Defer is not accepted.
235
273
  - ! The successor lean is the disposition map. Do not post a third map type.
236
274
  - ! The first posted map is an ADR-006 arbitration surface. Record a substantiation token when takes introduce load-bearing premises. Non-self-arbitration applies when the same party authored the triage and the proposed takes.
@@ -328,7 +366,7 @@ audit:<id> sha=<git-sha> pointer=<path:start-end|comment:<id>> reading=measured|
328
366
 
329
367
  ## Operator verbs
330
368
 
331
- Contract stops stay internal. Parent prints these phrases when they apply. They apply only after a successor lean is posted for this critic EXIT. Printing the verb menu with no posted successor lean is a contract miss. The operator does not have to remember them.
369
+ Contract stops stay internal. Parent prints these phrases when they apply. They apply only after a successor lean is posted for this round. Printing the verb menu with no posted successor lean is a contract miss. The operator does not have to remember them.
332
370
 
333
371
  - **accept** (cite findings)
334
372
  - **retry differences**
@@ -446,7 +484,13 @@ Closed catalog (last chip wins): `design-critique:mechanism-shaped` (in-flight,
446
484
  - ⊗ `gh api POST .../labels` or additive `scm:issue:edit --add-label` for this facet.
447
485
  - ⊗ Intercept mixed `scm issue edit` adds/removes for this facet.
448
486
  - ⊗ General-purpose labels CLI.
449
- - ! After the completed-arc record is present, `triage:accept` / `scope:promote` / `issue:ingest` / build may proceed. Any identity may run those verbs. Same-session parent continuation is not required. GitHub Triage on the implementer is not required. They read the accepted verified synthesis (latest successor lean plus the verified-claims table).
487
+ - ! After the completed-arc record is present, `triage:accept` / `scope:promote` / `issue:ingest` / build may proceed unless `evaluateCompletedArcRecord` returns `cancelled` or `unrecut-body` (#4057). Any identity may run those verbs. Same-session parent continuation is not required. ⊗ Treat `arc-mode:` as ingest clearance or as a permanent ingest denial. Ingest stays `issue:ingest` after the completed-arc record. Direct EXIT names it as a later operator verb. GitHub Triage on the implementer is not required. They read the accepted verified synthesis (latest successor lean plus the verified-claims table).
488
+ - ! Per-thread body ingest stays the mapper after a set-level bind. Stories the bind wants are recut bodies or newly filed issues. Derived AC stays on that body.
489
+ - ! Un-recut portfolio members fail closed on `cancelled`. Parent posts `design-critique: cancelled, because ...` on that number. Parent `role: parent` dominate prose is not that record. Critic, quoted, or fenced cancel-shaped prose is not that record. Leftover `mechanism-shaped` without cancel stays `missing-record` and is not this control.
490
+ - ! A later successor lean after cancel starts a recut arc. Completing a superseded thread without recut would mint the harvest.
491
+ - ! A complete record whose latest `target shape:` field is `set-level` is `unrecut-body`. Recut the body (and record a non-set-level shape) or file a new issue. Ingest the anchor only is not supported while that field remains set-level.
492
+ - ⊗ Select an author-blind latest successor lean as CurrentShape.
493
+ - ⊗ Put a set-level graph (dominate / split / corpus parsed from comment English) inside ingest.
450
494
  - ! Ingest clearance cites the latest successor lean. An older completed-arc record does not clear a later recut lean. A panel-deposit is in-flight even when the catalog chip missed and no critic has posted.
451
495
  - ! The lexical form of that citation, and the requirement that the occurrence be affirmative, are published in `## Citation grammar`. Ingest reads that grammar, not prose intent.
452
496
  - ! Keep `plan.policy.judgmentGates` matching only `design-critique:mechanism-shaped`. After `triage-ready` replaces it, the issue leaves the gate match.
@@ -516,7 +560,7 @@ The intake cross-ref scanners (`packages/core/src/intake/markdown-scanners.ts`)
516
560
  - ! Clearance is set membership: the record clears when the cited set contains the latest successor lean id. Position in the body does not select the lean, so citing the prior lean that `## Successor lean` requires cannot block.
517
561
  - ! A block detail reports what was scanned, what was found, and the accepted forms. ⊗ Guess at a cause. A guessed detail sends the operator back to re-post the same body and reproduce the refusal.
518
562
 
519
- `CompletedArcBlockReason` is closed. A block detail names one of these six:
563
+ `CompletedArcBlockReason` is closed. A block detail names one of these eight:
520
564
 
521
565
  | Reason | What it reports |
522
566
  | --- | --- |
@@ -526,6 +570,8 @@ The intake cross-ref scanners (`packages/core/src/intake/markdown-scanners.ts`)
526
570
  | `missing-table-cite` | a typed table claim names an id that is not a comment on this thread |
527
571
  | `unshaped-table-cite` | a typed table claim names a comment on this thread that opens no line with the verified-claims-table heading |
528
572
  | `ambiguous-table-cite` | two typed table claims name different tables |
573
+ | `cancelled` | `design-critique: cancelled, because ...` is the latest terminal record; no later successor lean |
574
+ | `unrecut-body` | completed-arc is present and the latest `target shape:` field is `set-level` |
529
575
 
530
576
  - ! Publish a reason in that table before the evaluator returns it. An unpublished reason code is the same gap as an unpublished citation form.
531
577
  - ⊗ Merge two states under one reason when their remedies differ. `missing-table-cite` and `unshaped-table-cite` were one reason and one detail until #3942, and the shared detail asserted an absent id in both, so an author whose table was on the thread read a true citation being called false and had no path to the missing heading.
@@ -547,4 +593,4 @@ This motion ingests untrusted issue threads by design.
547
593
 
548
594
  ## Test surface
549
595
 
550
- `packages/core/src/content-contracts/standards/design_critique_contract.test.ts` locks required pointer strings, the scaffolds framing, the comment-lead field as model then role from the closed set (not an issue label), the operator-gated loop (successor lean, operator verbs including walk / walk all, dual stop, halt line, exclusive remaining-set replace of the two catalog chips, #3640 auto-stamp on a non-empty all-accept map and no-stamp on stubs, first-lean recording obligation after critic EXIT), the parent-side substantiation token and independence rules, the Stop 1 exclusion (pre-critic premises outside the trigger) and `refutation-target:` field tokens rather than full body sentences, the composed auto-bind conjunct (all-accept map AND zero unresolved audit markers) at Operator verbs and Bind path 1, the variant-table evaluation rule (charter selection and spend permission evaluated independently), the critic-method heading and distinctive obligation tokens (exact class tokens, citations-are-claims, existing mechanisms, injection / swarm trigger nouns, failed-reviewer phrase, finding anatomy) rather than full body sentences, the brief-template forbidden-inputs list, and the thin router skill (existence, line cap, pointer resolution, no-normative-content). `evaluateParentAudit` locks the omission failure modes. This suite locks the SoT MUST and the thin skill pointer for the first-lean recording obligation, including the auto-stamp operator-confirm conjunct and the no-bind-while-unposted-same-round-siblings rule. `evaluateCompletedArcRecord` locks ingest on the completed-arc record rather than a catalog chip. It does not fail-close live parent turns. `packages/core/src/design-critique/citation-grammar.test.ts` locks the `## Citation grammar` closed set, the refused positions, and the diagnostics surface; `packages/core/src/design-critique/completed-arc-record.test.ts` locks one parser for both questions, set membership against the latest lean, and the observation-echoing block details (#3831). Runtime parent-turn detection only if `evaluateParentAudit` is extended; that extension is not required to ship the recording obligation. Panel completeness is locked as contract text only. No predicate observes it on a live arc (#3850). `### The arc` and its derived boundaries, the `### Target shape` axis with its twice-run caveat, and the two bind-path-2 non-empty refusals are locked as contract text (#3797). `## Plain-language summary` is locked the same way: the contract test pins the heading token, the MUST-not-SHOULD reasoning, the ADR-006 exemption and its limit, the non-normative marking, and the per-artifact reserved line-start matrix, and `packages/core/src/design-critique/reserved-line-starts.test.ts` exercises each of the three families on each artifact kind against the exported shape predicates and `evaluateCompletedArcRecord`. No predicate observes a summary on a live arc (#3929). `### Verified-claims table heading`, the closed reason vocabulary, and the re-measured line-start matrix are locked as contract text, and `completed-arc-record.test.ts` exercises the typed refusal partition: the two states, details that differ by more than the id, the untyped null table id, and the seven recorded live arc table ids (#3942).
596
+ `packages/core/src/content-contracts/standards/design_critique_contract.test.ts` locks required pointer strings, the scaffolds framing, the comment-lead field as model then role from the closed set (not an issue label), the operator-gated loop (successor lean, operator verbs including walk / walk all, dual stop, halt line, exclusive remaining-set replace of the two catalog chips, #3640 auto-stamp on a non-empty all-accept map and no-stamp on stubs, first-lean recording obligation after this round's siblings are posted), the parent-side substantiation token and independence rules, the Stop 1 exclusion (pre-critic premises outside the trigger) and `refutation-target:` field tokens rather than full body sentences, the composed auto-bind conjunct (all-accept map AND zero unresolved audit markers) at Operator verbs and Bind path 1, the variant-table evaluation rule (charter selection and spend permission evaluated independently), the critic-method heading and distinctive obligation tokens (exact class tokens, citations-are-claims, existing mechanisms, injection / swarm trigger nouns, failed-reviewer phrase, finding anatomy) rather than full body sentences, the brief-template forbidden-inputs list, and the thin router skill (existence, line cap, pointer resolution, no-normative-content). `evaluateParentAudit` locks the omission failure modes. This suite locks the SoT MUST and the thin skill pointer for the first-lean recording obligation, including the auto-stamp operator-confirm conjunct and the no-bind-while-unposted-same-round-siblings rule. `evaluateCompletedArcRecord` locks ingest on the completed-arc record rather than a catalog chip. It does not fail-close live parent turns. `packages/core/src/design-critique/citation-grammar.test.ts` locks the `## Citation grammar` closed set, the refused positions, and the diagnostics surface; `packages/core/src/design-critique/completed-arc-record.test.ts` locks one parser for both questions, set membership against the latest lean, and the observation-echoing block details (#3831). Runtime parent-turn detection only if `evaluateParentAudit` is extended; that extension is not required to ship the recording obligation. Panel completeness is locked as contract text only. No predicate observes it on a live arc (#3850). `evaluatePanelSeatComposition` locks claimed-family composition and PATH paste-ready-first; it is a fixture over claimed families, not a live sibling-count gate. `### The arc` and its derived boundaries, the `### Target shape` axis with its twice-run caveat, and the two bind-path-2 non-empty refusals are locked as contract text (#3797). `## Plain-language summary` is locked the same way: the contract test pins the heading token, the MUST-not-SHOULD reasoning, the ADR-006 exemption and its limit, the non-normative marking, and the per-artifact reserved line-start matrix, and `packages/core/src/design-critique/reserved-line-starts.test.ts` exercises each of the three families on each artifact kind against the exported shape predicates and `evaluateCompletedArcRecord`. No predicate observes a summary on a live arc (#3929). `### Verified-claims table heading`, the closed reason vocabulary, and the re-measured line-start matrix are locked as contract text, and `completed-arc-record.test.ts` exercises the typed refusal partition: the two states, details that differ by more than the id, the untyped null table id, and the seven recorded live arc table ids (#3942). `### Run posture` and `parseOperatorRunPosture` / `evaluateDirectDispatch` lock the closed-token front door, the missing-token ask including yolo, the `session:start --read-only` start verb, stdin posting, SHA-pinned reads, and the no-worktree direct panel; `run-posture.test.ts` exercises those fixtures. `evaluateCompletedArcRecord` ignores `arc-mode:` (#4072).
@@ -57,10 +57,29 @@ deft policy:show --field=runtimeAuthority
57
57
  ### Active-story seam
58
58
 
59
59
  The hook dispatcher loads `file_scope` from the implementation-eligible active xBRIEF
60
- path when `inspectActiveScope` reports one. Residual gaps (document, not silent):
60
+ path when `inspectActiveScope` reports one.
61
+
62
+ When more than one preflight-eligible artifact is in `xbrief/active/` (a cohort
63
+ sharing one tree), first-wins is **not** used: that would fence every worker to one
64
+ story's `file_scope` and over-permit the others (#4007). Bind the dispatched story:
65
+
66
+ - `DEFT_ACTIVE_SCOPE` (absolute, project-relative, or unique basename) must name
67
+ one eligible running brief; that path's `file_scope` is the story fence
68
+ - On win32, pin matching is case-insensitive (the filesystem is). POSIX pins stay
69
+ case-sensitive, and a backslash in a POSIX pin is a filename character, not a
70
+ separator
71
+ - Missing pin + multiple eligible → fail closed (`scope-not-ready`). Recovery: set
72
+ the pin, or keep one running brief in `xbrief/active/`
73
+ - A pin that does not name an eligible brief → fail closed
74
+
75
+ The filed `__tests__` matcher diagnosis is refuted: `matchAny` already admits that
76
+ exact path and `_` is literal. Do not invent a `__`-segment exception. Pre-`c99f6159`
77
+ worktree relativisation is a separate discriminator (the raw unedited deny string)
78
+ and is not closed here.
79
+
80
+ Residual gaps (document, not silent):
61
81
 
62
82
  - Host / worktree cannot identify the active story → story layer omitted; project fence still applies
63
- - Multiple active artifacts → first preflight-eligible path wins (same as scope gate)
64
83
  - Story JSON unreadable → story layer fail-open; project fence still applies
65
84
 
66
85
  Shell/MCP push/merge scopes remain project-only (`runtimeAuthority.scopes`); they are not
@@ -170,10 +189,12 @@ OS-temp dests and commands with no recognized dest (`git status`,
170
189
  - Destinations that are shell **variables** are not recovered. That is most
171
190
  logged shell: 1,089 of 1,131 calls were dynamic, compound, or emitted no
172
191
  target.
173
- - A **directory junction** created without elevation defeats `provablyExternal`:
174
- the path is lexically outside the root and its realpath is inside. That is
175
- **re-entry** polarity. `#3186` `assertProjectionContained` is **escape**
176
- polarity (in-tree dest whose realpath leaves the tree) and does not close it.
192
+ - A **directory junction** created without elevation is **re-entry** polarity.
193
+ `isInRepoShellWritePath` reuses `isOutsideProjectRootWrite` so a lexically-outside
194
+ dest whose realpath is inside the project is gated. `#3186` `assertProjectionContained`
195
+ remains **escape** polarity (in-tree dest whose realpath leaves the tree) and is
196
+ not this check. The hook still runs before the shell, so a link created between
197
+ check and write is TOCTOU.
177
198
 
178
199
  Fail-open at this predicate is the bound posture (#3997). Inverting it to
179
200
  fail-closed on dests the parser cannot prove external is that issue's refuted