@deftai/directive-runtime 0.129.6 → 0.129.7

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 (85) hide show
  1. package/Taskfile.yml +94 -13
  2. package/commands.md +5 -3
  3. package/dist/session-start.d.ts +8 -2
  4. package/dist/session-start.js +33 -12
  5. package/dist/vbrief-preflight.d.ts +4 -0
  6. package/dist/vbrief-preflight.js +34 -0
  7. package/dist/verify-scope-provenance.d.ts +13 -1
  8. package/dist/verify-scope-provenance.js +44 -1
  9. package/docs/getting-started.md +2 -0
  10. package/docs/scope-provenance.md +15 -1
  11. package/package.json +1 -1
  12. package/packs/skills/skills-pack-0.1.json +1 -1
  13. package/skills/deft-directive-review-cycle/SKILL.md +15 -6
  14. package/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  15. package/vendor/directive-content/Taskfile.yml +94 -13
  16. package/vendor/directive-content/commands.md +5 -3
  17. package/vendor/directive-content/docs/getting-started.md +2 -0
  18. package/vendor/directive-content/docs/scope-provenance.md +15 -1
  19. package/vendor/directive-content/packs/skills/skills-pack-0.1.json +1 -1
  20. package/vendor/directive-content/skills/deft-directive-review-cycle/SKILL.md +15 -6
  21. package/vendor/directive-content/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  22. package/vendor/directive-core/dist/.tsbuildinfo +1 -1
  23. package/vendor/directive-core/dist/fs/contained-write.d.ts +8 -2
  24. package/vendor/directive-core/dist/fs/contained-write.d.ts.map +1 -1
  25. package/vendor/directive-core/dist/fs/contained-write.js +22 -6
  26. package/vendor/directive-core/dist/fs/contained-write.js.map +1 -1
  27. package/vendor/directive-core/dist/hooks/dispatcher.d.ts +1 -1
  28. package/vendor/directive-core/dist/hooks/dispatcher.d.ts.map +1 -1
  29. package/vendor/directive-core/dist/hooks/dispatcher.js +116 -4
  30. package/vendor/directive-core/dist/hooks/dispatcher.js.map +1 -1
  31. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts +26 -1
  32. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts.map +1 -1
  33. package/vendor/directive-core/dist/init-deposit/agent-hooks.js +52 -3
  34. package/vendor/directive-core/dist/init-deposit/agent-hooks.js.map +1 -1
  35. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts +43 -0
  36. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts.map +1 -1
  37. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js +148 -0
  38. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js.map +1 -1
  39. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts +10 -5
  40. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts.map +1 -1
  41. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js +41 -18
  42. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js.map +1 -1
  43. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts +3 -2
  44. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts.map +1 -1
  45. package/vendor/directive-core/dist/preflight-gh/pre-push.js +19 -18
  46. package/vendor/directive-core/dist/preflight-gh/pre-push.js.map +1 -1
  47. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts +202 -19
  48. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts.map +1 -1
  49. package/vendor/directive-core/dist/scope/delivery-evidence.js +948 -107
  50. package/vendor/directive-core/dist/scope/delivery-evidence.js.map +1 -1
  51. package/vendor/directive-core/dist/scope/transition.d.ts +4 -1
  52. package/vendor/directive-core/dist/scope/transition.d.ts.map +1 -1
  53. package/vendor/directive-core/dist/scope/transition.js +2 -2
  54. package/vendor/directive-core/dist/scope/transition.js.map +1 -1
  55. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts +11 -2
  56. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts.map +1 -1
  57. package/vendor/directive-core/dist/scope-provenance/base-fence.js +16 -1
  58. package/vendor/directive-core/dist/scope-provenance/base-fence.js.map +1 -1
  59. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts +12 -0
  60. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts.map +1 -1
  61. package/vendor/directive-core/dist/scope-provenance/evaluate.js +41 -5
  62. package/vendor/directive-core/dist/scope-provenance/evaluate.js.map +1 -1
  63. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts +106 -0
  64. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts.map +1 -0
  65. package/vendor/directive-core/dist/scope-provenance/forge-approval.js +382 -0
  66. package/vendor/directive-core/dist/scope-provenance/forge-approval.js.map +1 -0
  67. package/vendor/directive-core/dist/session/orientation-compression.d.ts +18 -2
  68. package/vendor/directive-core/dist/session/orientation-compression.d.ts.map +1 -1
  69. package/vendor/directive-core/dist/session/orientation-compression.js +38 -5
  70. package/vendor/directive-core/dist/session/orientation-compression.js.map +1 -1
  71. package/vendor/directive-core/dist/session/session-start.d.ts +47 -2
  72. package/vendor/directive-core/dist/session/session-start.d.ts.map +1 -1
  73. package/vendor/directive-core/dist/session/session-start.js +149 -1
  74. package/vendor/directive-core/dist/session/session-start.js.map +1 -1
  75. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts +18 -4
  76. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts.map +1 -1
  77. package/vendor/directive-core/dist/swarm/approach1-babysitter.js +28 -4
  78. package/vendor/directive-core/dist/swarm/approach1-babysitter.js.map +1 -1
  79. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts +29 -0
  80. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts.map +1 -1
  81. package/vendor/directive-core/dist/swarm/finalize-cohort.js +122 -11
  82. package/vendor/directive-core/dist/swarm/finalize-cohort.js.map +1 -1
  83. package/vendor/directive-core/dist/triage/evaluate/worktrees.d.ts.map +1 -1
  84. package/vendor/directive-core/dist/triage/evaluate/worktrees.js +219 -60
  85. package/vendor/directive-core/dist/triage/evaluate/worktrees.js.map +1 -1
@@ -536,13 +536,21 @@ REDISPATCH_OK: yes|no
536
536
  ⊗ Treat `ci_never_scheduled` as ordinary Greptile latency (`sha_match` / `terminal_check_run` only).
537
537
  ⊗ Silent `--skip-ci` / admin merge without the outage playbook audit trail below.
538
538
 
539
- ### SLizard advisory-only for merge-ready wait (#3167)
539
+ ### SLizard advisory-only for merge-ready wait (#3167) — weather vs residual (#5102 / #4387)
540
540
 
541
- ! Required bot for **merge-ready wait** / Step 6 exit ownership is **Greptile**. SLizard (Gemini Verify) is **advisory only**: timeouts, retries, missing check, or flaky SLizard conclusions MUST NOT gate the review-cycle wait loop or authorize unbounded re-push.
541
+ ! SLizard is advisory only for the Greptile merge-ready confidence floor; parseable blocking tip fingerprints are unfinished residual (#5102 / #3167).
542
542
 
543
- ! `task pr:merge-ready` may still surface SLizard structure when present (#2189); agents MUST NOT idle-poll or thrash solely for SLizard when Greptile Step 6 is clean. Prefer continue / BLOCKED on CI weather / human decision.
543
+ ! Required bot for **merge-ready wait** / Step 6 exit ownership is **Greptile**. SLizard is **advisory only** for weather (timeout / cancelled / unparseable / zero-finding / missing-check): those MUST NOT gate the review-cycle wait loop (#3167). Parseable blocking tip fingerprints are unfinished residual (#5102).
544
544
 
545
- ⊗ Block merge-ready babysit on SLizard alone when Greptile Step 6 all-of is satisfied on HEAD.
545
+ ! When `pr:merge-ready` / wait-mergeable reports **SLizard blocked** (parseable `request_changes` or `merge_impact=blocking` with non-zero findings) — especially Greptile CLEAN + SLizard blocked — before re-arming merge-ready wait or `pr:wait-mergeable-and-merge` the owner MUST read the tip SLizard **review body** in full, enumerate new and Previously mentioned fingerprints / deterministic tool names, and for each open P0/P1 falsify with `git show <head>:<path>` (slizard#2694) or fix in one residual batch. Do not idle-poll. Keep `pr:watch` Greptile ownership unchanged (#5102 / #2189 / #4387).
546
+
547
+ ! Compose existing head-blob check, same-fingerprint stop (#3448), and `--skip-slizard` override. Do not invent a third poller. `--skip-slizard` is explicit override only — not the default babysit escape.
548
+
549
+ ! Parseable blocking findings with open tip fingerprints are **unfinished residual**, not weather. Cross-link: `packages/core/src/pr-merge-readiness/slizard-gate.ts` (`evaluateSlizardGate`).
550
+
551
+ ⊗ Re-arm wait-mergeable / idle-poll while SLizard still has parseable blocking findings with open tip fingerprints (#5102).
552
+ ⊗ Treat parseable blocking SLizard findings as #3167 weather when Greptile Step 6 is clean (#5102 / #4387).
553
+ ⊗ Default to `--skip-slizard` to clear a merge-ready SLizard block (#5102).
546
554
 
547
555
  ### Outage admin-merge playbook (opt-in / human, #3167)
548
556
 
@@ -725,7 +733,7 @@ Cross-links: swarm decision tree `skills/deft-directive-swarm/references/core-ph
725
733
 
726
734
  ! When the platform descriptor indicates Tier 1 (sub-agent support) **and this agent owns the primitive as parent/orchestrator**, spawn a review-monitor sub-agent using the primitive matching the descriptor. ⊗ A grok-build / grok-bot / Cursor / Claude Code implementation leaf MUST NOT run these spawn steps.
727
735
 
728
- 1. ! Launch via the matching primitive: `start_agent` (Warp), `spawn_subagent` (grok-build / TUI / non-Warp **parent**), the Cursor `Task` tool with `run_in_background: true` (`cursor-composer` / `cursor-cloud-agent`, #1877), the Claude Code `Agent` tool with `run_in_background: true` (`claude-code` / register `claude-agent`, #3134), OpenClaw `sessions_spawn` (`openclaw`, #2876), **or Grok Bot Task/executor/CloudAgent (`grok-bot` / register `grok-bot-executor`, #4201)** with a prompt that instructs it to poll for Greptile completion. For `spawn_subagent`, Cursor `Task`, Claude Code `Agent`, OpenClaw `sessions_spawn`, and Grok Bot executor the prompt MUST reference the canonical poller template `templates/swarm-greptile-poller-prompt.md` (with placeholders filled) plus the agent preamble; the working directory / context must be the PR branch (worktree or equivalent for hybrid).
736
+ 1. ! Launch via the matching primitive: `start_agent` (Warp), `spawn_subagent` (grok-build / TUI / non-Warp **parent**), the Cursor `Task` tool with `run_in_background: true` (`cursor-composer` / `cursor-cloud-agent`, #1877), the Claude Code `Agent` tool with `run_in_background: true` (`claude-code` / register `claude-agent`, #3134), OpenClaw `sessions_spawn` (`openclaw`, #2876), **or Grok Bot Task/executor/CloudAgent (`grok-bot` / register `grok-bot-executor`, #4201)** with a prompt that instructs it to poll for Greptile completion. For `spawn_subagent`, Cursor `Task`, Claude Code `Agent`, OpenClaw `sessions_spawn`, and Grok Bot executor the prompt MUST reference the canonical poller template `templates/swarm-greptile-poller-prompt.md` (with placeholders filled) plus the agent preamble; the working directory / context must be the PR branch (worktree or equivalent for hybrid). **Grok Approach 1 watch/report (#4781):** before `review-monitor:register` / child `pr:watch`, spawn with advertised `process_only: true` + dest `cwd` for `stop-at: pr-open` + `worker_role: review-monitor` seats only (`formatApproach1ProcessOnlySpawnShape` / `approach1BabysitterCommands`). Implement-class never sets `process_only`; dest-path is not the skip class; do not blanket-flag the fix-capable greptile poller.
729
737
  2. ! The sub-agent polls using the mechanism for its primitive: for `spawn_subagent` the **child** owns `pr:watch` / gh poll in its own process (adaptive cadence: ~20-30s first check after push, ~60s second, ~90s thereafter; Greptile typically lands in 3-7 min) — the user-facing parent MUST NOT enter `get_command_or_subagent_output`; a named durable-owner session MAY pull-wait (`host-grok-build.md` Gap D); for `start_agent` the native messaging path; for the Cursor `Task` tool the backgrounded-task completion-notification path; for Claude Code `Agent` the backgrounded-agent completion-notification path; for OpenClaw `sessions_spawn` the host session completion / messaging channel (prefer `task pr:watch` inside the child when available). Front-load the first check to catch fast reviews.
730
738
  3. ! When the exit condition is met (Greptile review current on the HEAD commit SHA, confidence meets resolved min (policy/dogfood/default; see Step 6 #3095), no P0/P1 remaining), the sub-agent reports completion back to the parent (via `send_message_to_agent`, the spawn_subagent result channel, or the OpenClaw sessions completion channel).
731
739
  4. ! The main conversation pane stays fully interactive during monitoring -- the user (or parent monitor) can continue other work. On Grok Build this is conditional until a measured completion-notify exists: it holds when the parent returns the turn after background spawn, not when the parent pull-waits (`host-grok-build.md` Gap D / #4796).
@@ -1029,7 +1037,8 @@ task lifecycle:event -- emit plan:approved \
1029
1037
  - ⊗ Workflow thrash or empty-commit spam during attributed platform outage without status-page probe (#3180)
1030
1038
  - ⊗ Tight forge-outage retry / empty-commit thrash without a one-shot human report (#3422)
1031
1039
  - ⊗ Merge or `--skip-ci` solely because a status page is red without check-run evidence (#3180)
1032
- - ⊗ Block merge-ready wait on SLizard alone when Greptile Step 6 is clean (#3167)
1040
+ - ⊗ Re-arm wait-mergeable or treat parseable blocking SLizard tip fingerprints as #3167 weather when Greptile Step 6 is clean (#5102 / #3167)
1041
+ - ⊗ Idle-poll / skip reading tip SLizard review body while open Previously mentioned fingerprints remain (#5102)
1033
1042
  - ⊗ Silent admin / `--skip-ci` merge under Actions outage without audit comment and opt-in authority (#3167)
1034
1043
  - ⊗ Leave a deliberate `stop-at: pr-open` (or thin-DONE recovery) open PR without spawning/retaining one review-cycle babysit owner + lease continuity and post-merge `scope:complete` plan (#3153)
1035
1044
  - ⊗ Stand down at CLEAN under human-merge policy without a durable owner (sticky lease + live parent/monitor/Phase 6 closer) **and** a post-CLEAN observe path (poller / parent wake / Phase 6 re-poll) for post-merge `scope:complete` (#3153 / #1193 / #2321)
@@ -92,14 +92,15 @@ Until a measured callback exists, Grok wording is conditional: background `spawn
92
92
 
93
93
  ! **Grok through-merge (#4529 / #4821 / #5219):** implement MUST use (b) `stop-at: pr-open`. Path (a) is for non-through-merge Grok leaves only. Named leftover (class A) requires a dest-cwd one-shot residual, stop-at after the push; then Approach 1 wait owner is a **`spawn_subagent` review-monitor sibling** whose `monitor_agent_id` is registered (`review-monitor:register --platform-primitive spawn_subagent`) and whose child-bound `pr:watch --monitor-agent-id <id>` satisfies `verify:review-monitor --merge-path-arm --live-wait`; then Phase 6 / parent-retained squash-merges via `pr:wait-mergeable-and-merge` only after CLEAN. Pass a declared wait budget when the envelope states one (#3984). Parent-retained is the **closer after CLEAN**, not the pre-CLEAN poller/fixer. Dest spawn deny is `BLOCKED` or dest-cwd residual via native implementation-capable spawn (#4215) — not parent-primary. ⊗ Send class-A residual through process-only CLI `grok --cwd`. ⊗ Choose Approach 1 sibling or parent-retained as the first partner without dest residual. ⊗ Dispatch a Grok through-merge implement as (a). ⊗ Harvest a Grok `drive-to: merge-ready` continuation as this closer. ⊗ Treat a parent-owned shell/`bash` `pr:watch` (heartbeat `parent_id=pr-watch`) as Approach 1 babysit when `spawn_subagent` is available (#5219).
94
94
 
95
- ! **Exclusive lawful waits / cheapest admitted Approach 1 (#5219 P3 / #5229 / #5727):** after `pr-open`, when Tier-1 `spawn_subagent` is available, the **only lawful** pre-CLEAN wait is Approach 1 child `pr:watch --monitor-agent-id` after `review-monitor:register --platform-primitive spawn_subagent` with `verify:review-monitor --merge-path-arm --live-wait` green; after CLEAN, parent-retained `pr:wait-mergeable-and-merge`. Approach 1 is also the **cheapest admitted** path vs parent shell `pr:watch`, host `monitor`, forge-poll, or bespoke `%TEMP%` pollers. Prefer one background `spawn_subagent` babysitter that owns watch → class A residual handoff → re-watch until CLEAN, then hand CLEAN to the parent closer. Child register + watch first (heartbeat live); parent verify after:
95
+ ! **Exclusive lawful waits / cheapest admitted Approach 1 (#5219 P3 / #5229 / #5727 / #4781):** after `pr-open`, when Tier-1 `spawn_subagent` is available, the **only lawful** pre-CLEAN wait is Approach 1 child `pr:watch --monitor-agent-id` after `review-monitor:register --platform-primitive spawn_subagent` with `verify:review-monitor --merge-path-arm --live-wait` green; after CLEAN, parent-retained `pr:wait-mergeable-and-merge`. Approach 1 is also the **cheapest admitted** path vs parent shell `pr:watch`, host `monitor`, forge-poll, or bespoke `%TEMP%` pollers. Prefer one background `spawn_subagent` babysitter that owns watch → class A residual handoff → re-watch until CLEAN, then hand CLEAN to the parent closer. **Spawn shape before register/pr:watch (#4781):** for monitor-only seats (`stop-at: pr-open` + `worker_role: review-monitor` watch/report), pass advertised `process_only: true` with dest `cwd` (`formatApproach1ProcessOnlySpawnShape` / `approach1BabysitterCommands`). Implement-class never sets `process_only`; dest-path is not the skip class; do not blanket-flag the fix-capable greptile poller. Dest convention by write need: shared cwd-without-occupy for non-writing monitor/critic seats; one dest per validation seat that writes a report root. Specs/report writes stay on requirements posture (#4444) — do not fold report roots into the spawn skip class. Child register + watch first (heartbeat live); parent verify after:
96
96
  ```
97
+ # process_only+cwd spawn (monitor-only), then:
97
98
  task review-monitor:register -- --pr <N> --monitor-agent-id <id> --platform-primitive spawn_subagent
98
99
  DEFT_MONITOR_AGENT_ID=<id> task pr:watch -- <N> --monitor-agent-id <id>
99
100
  # parent after child watch is live:
100
101
  task verify:review-monitor -- --pr <N> --merge-path-arm --live-wait
101
102
  ```
102
- Core helper: `formatApproach1BabysitterOneLiner` / `formatApproach1CheapestAdmissionCard` / `admitClaimedBabysitPath` (`packages/core/src/swarm/approach1-babysitter.ts`). Bounded-form deny: `evaluateBoundedPrWatchDeny` refuses Directive `pr:watch` / `task deft:pr:watch` without `--monitor-agent-id` when Tier 1 is provable. Claimed-wait deny: `evaluateClaimedBabysitPathDeny` / `admitClaimedBabysitPath` refuse `BabysitPathKind` forge-poll / bespoke-temp-poller / host-monitor / directive-pr-watch-bare and Approach 1 without bound evidence / different-PR arm. ⊗ Parent forge-poll / `gh` check-runs+mergeable_state sleep / bespoke `%TEMP%` PowerShell watchers as pre-CLEAN babysit (#5727). ⊗ Parent-shell `pr:watch --monitor-agent-id <leased id>` without spawn-injected `DEFT_MONITOR_AGENT_ID` (or CLI matching `GROK_SESSION_ID`) — that impersonation does not arm. ⊗ Start `pr:wait-mergeable-and-merge` before CLEAN (#4822). ⊗ Treat host `monitor` or bare shell `pr:watch` as the sole babysit when `spawn_subagent` is available (#5229).
103
+ Core helper: `formatApproach1BabysitterOneLiner` / `formatApproach1CheapestAdmissionCard` / `formatApproach1ProcessOnlySpawnShape` / `admitClaimedBabysitPath` (`packages/core/src/swarm/approach1-babysitter.ts`). Bounded-form deny: `evaluateBoundedPrWatchDeny` refuses Directive `pr:watch` / `task deft:pr:watch` without `--monitor-agent-id` when Tier 1 is provable. Claimed-wait deny: `evaluateClaimedBabysitPathDeny` / `admitClaimedBabysitPath` refuse `BabysitPathKind` forge-poll / bespoke-temp-poller / host-monitor / directive-pr-watch-bare and Approach 1 without bound evidence / different-PR arm. ⊗ Parent forge-poll / `gh` check-runs+mergeable_state sleep / bespoke `%TEMP%` PowerShell watchers as pre-CLEAN babysit (#5727). ⊗ Parent-shell `pr:watch --monitor-agent-id <leased id>` without spawn-injected `DEFT_MONITOR_AGENT_ID` (or CLI matching `GROK_SESSION_ID`) — that impersonation does not arm. ⊗ Start `pr:wait-mergeable-and-merge` before CLEAN (#4822). ⊗ Treat host `monitor` or bare shell `pr:watch` as the sole babysit when `spawn_subagent` is available (#5229).
103
104
 
104
105
  ! **Durable host→CLI capability stamp (#5229):** Before any CLI `verify:review-monitor` / `pr:watch` subprocess, write `.deft-scratch/host-capability-stamp.json` via `writeHostCapabilityStamp(projectRoot)` / `ensureHostCapabilityStampForBabysit` (and prefer exporting `GROK_BUILD=1` / `DEFT_HAS_SPAWN_SUBAGENT=1` into that subprocess). `review-monitor:register` also writes the stamp on successful claim/renew. Stamps older than 8h or bound to a different `host_session_id` are ignored so a later plain-terminal session cannot inherit Tier 1. Without a fresh stamp, bare `node … verify:review-monitor` can mis-detect Tier 3 / `generic-terminal` and fail-open with "no active review-monitor required" even though this parent owns `spawn_subagent`. `probeMonitoringTier` merges the stamp; `DEFT_MONITOR_TIER=3` is labeled `override-tier3` (not honest `generic-terminal`) and refused when stamp/env already proves Tier 1. `evaluateReviewMonitorGate` consults sticky lease `platform_primitive` before Approach 3 READY and before `!isTier1` READY. Bounded `pr:watch` without `--monitor-agent-id` is refused at the watch entry when Tier 1 is provable.
105
106
 
@@ -945,10 +945,10 @@ tasks:
945
945
  ;;
946
946
  esac
947
947
  print_deposit_recovery() {
948
- echo "Phase 7 local closeout INCOMPLETE (#5953)." >&2
949
- echo "Recovery: merge the version-matched agent-hook deposit for $VERSION onto tip, then re-run:" >&2
948
+ echo "Phase 7 local closeout INCOMPLETE (#5953 / #6163 Prefer-A Bound 6772886751)." >&2
949
+ echo "Recovery: on the pin candidate, repair/write agent-hook deposit then force-add tip-required Cursor paths (.cursor/hooks.json + .cursor/hooks/deft-hook-<sha8>.mjs) so they co-travel with the pin (preparePinCandidateCursorDeposit / git add -f bounded set); merge that pin+deposit tip, then re-run:" >&2
950
950
  echo " task release:local-update -- $VERSION --checkout $CHECKOUT" >&2
951
- echo "Expected managed deposit paths include: .grok/hooks/ .cursor/hooks.json .claude/settings.json .codex/ (and related deposit files)." >&2
951
+ echo "Expected managed deposit paths include: .grok/hooks/ .cursor/hooks.json .cursor/hooks/deft-hook-<sha8>.mjs .claude/settings.json .codex/ (and related deposit files)." >&2
952
952
  echo "Do not auto-commit deposit bytes inside the release flow; do not npm i -g." >&2
953
953
  }
954
954
  OCC_FILE="$CHECKOUT/.deft/occupancy.json"
@@ -1015,8 +1015,16 @@ tasks:
1015
1015
  git status --short >&2
1016
1016
  exit 1
1017
1017
  fi
1018
- DEFAULT_BRANCH="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed "s@^refs/remotes/origin/@@" || true)"
1019
- if [ -z "$DEFAULT_BRANCH" ]; then DEFAULT_BRANCH=master; fi
1018
+ # #6164: shell parameter expansion strip (zero sed/awk/cut/grep); then git verify.
1019
+ ORIGIN_HEAD="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)"
1020
+ DEFAULT_BRANCH="${ORIGIN_HEAD#refs/remotes/origin/}"
1021
+ if [ -z "$DEFAULT_BRANCH" ] || [ "$DEFAULT_BRANCH" = "$ORIGIN_HEAD" ]; then
1022
+ DEFAULT_BRANCH=master
1023
+ fi
1024
+ if ! git rev-parse --verify --quiet "refs/remotes/origin/$DEFAULT_BRANCH" >/dev/null; then
1025
+ echo "release:local-update: refuse; cannot verify refs/remotes/origin/$DEFAULT_BRANCH; run: git remote set-head origin -a" >&2
1026
+ exit 1
1027
+ fi
1020
1028
  CURRENT="$(git rev-parse --abbrev-ref HEAD)"
1021
1029
  if [ "$CURRENT" != "$DEFAULT_BRANCH" ]; then
1022
1030
  echo "release:local-update: refuse; checkout must be on default branch $DEFAULT_BRANCH (on $CURRENT)" >&2
@@ -1024,14 +1032,54 @@ tasks:
1024
1032
  fi
1025
1033
  echo "release:local-update: ff-only pull on $DEFAULT_BRANCH at $CHECKOUT"
1026
1034
  git pull --ff-only
1027
- if [ -f pnpm-workspace.yaml ]; then
1028
- if grep -q "minimumReleaseAgeExclude" pnpm-workspace.yaml; then
1029
- if ! grep -q "@deftai/directive-runtime@$VERSION" pnpm-workspace.yaml; then
1030
- echo "release:local-update: AgeExclude missing @deftai/directive-runtime@$VERSION before frozen install (#5953 footnote)" >&2
1031
- exit 1
1032
- fi
1033
- fi
1034
- fi
1035
+ # #6164: unconditional dependency-free AgeExclude gate (exact list-item; fail closed; no soft-skip).
1036
+ node -e '
1037
+ const fs = require("fs");
1038
+ const version = process.argv[1] || "";
1039
+ const want = "@deftai/directive-runtime@" + version;
1040
+ let text;
1041
+ try {
1042
+ text = fs.readFileSync("pnpm-workspace.yaml", "utf8");
1043
+ } catch (err) {
1044
+ const detail = err && err.message ? err.message : String(err);
1045
+ console.error("release:local-update: AgeExclude cannot read pnpm-workspace.yaml before frozen install (#6164): " + detail);
1046
+ process.exit(1);
1047
+ }
1048
+ const lines = text.split(/\r?\n/);
1049
+ const KEY_RE = /^(\s*)minimumReleaseAgeExclude:\s*(#.*)?$/;
1050
+ const ITEM_RE = /^(\s*)-\s+(.*)$/;
1051
+ const keyIdx = lines.findIndex((l) => KEY_RE.test(l));
1052
+ if (keyIdx < 0) process.exit(0);
1053
+ const keyLine = lines[keyIdx];
1054
+ const keyIndent = keyLine.length - keyLine.trimStart().length;
1055
+ const itemValue = (raw) => {
1056
+ let v = String(raw || "").trim();
1057
+ const SQ = String.fromCharCode(39);
1058
+ const DQ = String.fromCharCode(34);
1059
+ if (v.startsWith(SQ) || v.startsWith(DQ)) {
1060
+ const q = v[0];
1061
+ const end = v.indexOf(q, 1);
1062
+ return end > 0 ? v.slice(1, end) : v;
1063
+ }
1064
+ const hash = v.search(/\s#/);
1065
+ if (hash >= 0) v = v.slice(0, hash);
1066
+ return v.trim();
1067
+ };
1068
+ let found = false;
1069
+ for (let i = keyIdx + 1; i < lines.length; i++) {
1070
+ const line = lines[i];
1071
+ const trimmed = line.trim();
1072
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
1073
+ const m = ITEM_RE.exec(line);
1074
+ const indent = line.length - line.trimStart().length;
1075
+ if (m === null || indent < keyIndent) break;
1076
+ if (itemValue(m[2]) === want) { found = true; break; }
1077
+ }
1078
+ if (!found) {
1079
+ console.error("release:local-update: AgeExclude missing " + want + " before frozen install (#5953 footnote / #6164)");
1080
+ process.exit(1);
1081
+ }
1082
+ ' "$VERSION"
1035
1083
  echo "release:local-update: pnpm install --frozen-lockfile"
1036
1084
  pnpm install --frozen-lockfile
1037
1085
  PIN_PKG="$(node -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); const v=(p.dependencies||{})["@deftai/directive-runtime"]||(p.devDependencies||{})["@deftai/directive-runtime"]||""; console.log(String(v).replace(/^[^0-9]*/, ""));')"
@@ -1051,6 +1099,39 @@ tasks:
1051
1099
  print_deposit_recovery
1052
1100
  exit 1
1053
1101
  fi
1102
+ # #6163 Prefer-A Bound 6772886751: committed-byte HEAD association (no repair/staging).
1103
+ echo "release:local-update: require committed Cursor deposit association on HEAD"
1104
+ if ! node --input-type=module -e '
1105
+ import { pathToFileURL } from "node:url";
1106
+ import { join, resolve } from "node:path";
1107
+ const checkout = process.argv[1];
1108
+ const vendor = join(
1109
+ checkout,
1110
+ "node_modules/@deftai/directive-runtime/vendor/directive-core/dist/init-deposit/agent-hooks.js",
1111
+ );
1112
+ let validate;
1113
+ try {
1114
+ ({ validateCursorDepositAssociation: validate } = await import(pathToFileURL(resolve(vendor)).href));
1115
+ } catch (err) {
1116
+ const detail = err && err.message ? err.message : String(err);
1117
+ console.error("release:local-update: cannot load validateCursorDepositAssociation from installed runtime (#6163): " + detail);
1118
+ process.exit(1);
1119
+ }
1120
+ if (typeof validate !== "function") {
1121
+ console.error("release:local-update: installed runtime lacks validateCursorDepositAssociation (#6163); pin must co-travel the Bound helper");
1122
+ process.exit(1);
1123
+ }
1124
+ const result = validate(checkout, { source: "HEAD" });
1125
+ if (!result || result.ok !== true) {
1126
+ console.error("release:local-update: committed Cursor deposit association FAILED on HEAD (#6163)");
1127
+ console.error(result && result.detail ? result.detail : "unknown association failure");
1128
+ process.exit(1);
1129
+ }
1130
+ console.log("release:local-update: committed Cursor deposit association ok (" + result.sha8 + ")");
1131
+ ' "$CHECKOUT"; then
1132
+ print_deposit_recovery
1133
+ exit 1
1134
+ fi
1054
1135
  UPGRADE_BIN=""
1055
1136
  if [ -x "node_modules/.bin/directive" ]; then
1056
1137
  UPGRADE_BIN="node_modules/.bin/directive"
@@ -149,6 +149,7 @@ Common commands:
149
149
  - Bare `evidence` / `disposition` keys are **not** valid (#1620 / Option B #3305). They fail `verify:vbrief-conformance` and are treated as missing by `scope:complete` (no dual-read). Migrate bare keys or narrative-only `Result`/`Verification` workarounds to the namespaced fields above while items are still non-terminal.
150
150
  - Already-terminal items (`completed`/`failed`/`cancelled`/…) are not re-checked for typed evidence; pre-marking items complete to skip the gate is unsupported for the typed path.
151
151
  - `merge`/`review` alone cannot satisfy smoke/UAT/deploy/observed_behavior criteria (title/Acceptance text or explicit axis).
152
+ - **Delivery identity (#5710):** with an issue origin, the PR must be associated with the issue as of `merged_at` by one of: (a) a unique sole `deft-story: N` mark in the merge-time PR body (PRs that change only briefs and `CHANGELOG.md` are excluded and never count), (b) a closing keyword (`Closes #N`, `owner/repo#N`, or the issue URL) in the merge-time body, or (c) a Connected issue link at `merged_at`. Brief-in-PR-diff still counts. A mark, keyword or link added after merge does not count, and current `closingIssuesReferences` alone is not enough. Basis (a) scans every merged PR within 20 minutes, `graphql.remaining` ≥ 200 and REST core ≥ 100; any gap refuses. With no issue origin, stamp `plan.metadata.productPullRequest` and complete with `--pr <that number> --merge-commit <sha>`; the repository is the `origin` remote. `--non-delivery` refuses when the stamped PR is merged, and refuses when its merge state cannot be read. `xbrief:preflight` prints one note for a code-bearing brief with no issue origin.
152
153
  - `task scope:fail -- xbrief/active/<file>.xbrief.json` -- mark running work failed when the scope cannot complete.
153
154
  - `task scope:cancel -- <path>` -- move a scope to `cancelled/`.
154
155
  - `task scope:restore`, `task scope:block`, `task scope:unblock`, `task scope:demote`, and `task scope:undo:*` -- repair or reverse lifecycle transitions.
@@ -494,13 +495,14 @@ Cross-link: spawn three postures and deny recoveries live under § Agent-host di
494
495
  ### Mutable ritual (mutation posture)
495
496
 
496
497
  - ! 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 8). Mutation start also claims the worktree occupancy lease (`.deft/occupancy.json`); see Session routing (#3433).
497
- - ! **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 (orient) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
498
+ - ! **Quiet-default success stdout (#4966):** mutation `session:start` success human output defaults to one trusted go-signal line with stable prefix `[deft session] ready=…` (ready + branch/HEAD + occupancy). Routine dial/host-surface/effort/orientation/ceremony prose is verbose-only via `deft session:start --verbose` or `DEFT_SESSION_VERBOSE=1`. Non-exhaustive always-on allowlist keeps actionable zero-exit exceptions, non-default policy/bypass and human-merge disclosures, finalize-owed unknown/deferred qualifications, and actionable lifecycle/work-claim notices on the quiet path. Fail/deny/steal/non-zero stay loud. Tip `#3286` `--compact` / `DEFT_SESSION_COMPACT` are deprecated as whole-stdout volume controls (no-op for human stdout selection; `--json` / run-summary unchanged). Exit 0 is not the mutation go-signal — render from structured ready/posture/occupancy state.
499
+ - ! **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. Section formatting may still honor `--compact` / `DEFT_SESSION_COMPACT=1` inside the orientation bundle; whole-stdout volume is `#4966` quiet-default / `--verbose`. #2176 read-only default is unchanged. Dual-path Later (orient) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
498
500
  - ! 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.
499
501
  - ! **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.
500
502
  - ! **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.
501
503
  - ? 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.
502
504
  - ~ `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.
503
- - ~ **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.
505
+ - ~ **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 emits one operator-visible `ceremony <tier> <ms>` line on the verbose human path (`--verbose` / `DEFT_SESSION_VERBOSE`; quiet-default hides it per #4966). 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.
504
506
  - ~ 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.
505
507
  - ! 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 `task verify:cache-fresh` entrypoints. Agent-hook correctness is independent of doctor warnings and throttling. Step 0 of the pre-`start_agent` gate stack.
506
508
  - ! **One-shot recovery (#2993 / #3100 / #4399):** 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` with check-class `cache_fresh` (age + live drift, no `--skip-drift-probe`) + `cache fetch-all --force` when `cache_fresh` is the remaining blocker, then re-verifies. Skip-drift ritual argv is not the ready bar — that is the `task check` / FRAMEWORK_CHECK_GATES evaluate, so stale-by-drift can still fire fetch-all. Even when gated inspect is already fresh, the fast path forces one live `agent_hooks` check and the same check-class `cache_fresh`; it still avoids unnecessary fetch-all when that evaluate is green. Flags: `--json`, `--repo OWNER/NAME`, `--with-network` (forwarded to session:start). Prefer this over juggling the multi-step recovery sequence under hook pressure.
@@ -550,7 +552,7 @@ Local ceremony cost signal for WWYSYDH / weekly process rollups. Emits to `.deft
550
552
  | PreToolUse ritual deny | `session:ritual-blocked` | Hook blocks write/spawn because gated ritual is not ready | `tool_name`, `code=ritual-not-ready`, `recovery_tier` (`cold`\|`rearm`) |
551
553
  | Per-step wall-clock | (field on `session:start`) | Same emit as session start | `steps[].name` + `steps[].duration_ms` (`alignment`, `scm_readiness`, `branch_policy`, `verify_tools`, `triage_welcome`, `release_probe`, `ritual_write`) |
552
554
 
553
- CLI mirror (no JSONL required): `deft session:start --json` already exposes the same `steps` / `duration_ms` / `ceremony_tier` fields for one-shot inspection. Human mutation output adds one `ceremony <tier> <ms>` line unless compact hides it (#3508).
555
+ CLI mirror (no JSONL required): `deft session:start --json` already exposes the same `steps` / `duration_ms` / `ceremony_tier` fields for one-shot inspection. Human mutation output adds one `ceremony <tier> <ms>` line on the verbose path (#3508 / #4966).
554
556
 
555
557
  Composed reader (no new verb): `task value:show` / `deft value:show` prints last cold vs re-arm duration, last per-step breakdown, blocked-ritual count, and recovery-tier distribution from the same ledger. This is **CLI process time**, not agent-turn wall clock (#3500). ⊗ Do not feed the printed CLI duration into #3286 Later graduation.
556
558
 
@@ -58,6 +58,8 @@ directive doctor
58
58
 
59
59
  After clone or a new worktree: run the project package manager (`npm install` / `pnpm install`), then the first eligible runtime command or registered hook completes local activation.
60
60
 
61
+ **Dependabot / pin-only bumps (AC14.10):** a PR that changes only the `@deftai/directive-runtime` pin + lockfile is valid. After `pnpm install` / `npm install` brings `node_modules` to the new pin, the first eligible skill, hook, or ordinary runtime command runs local automatic setup (wiring / `GENERATION` / readiness). `directive doctor` stays read-only — dirty doctor output before that eligible invocation is expected. CI may either rely on that first eligible command or run explicit `directive install --repair --yes`. Do not treat "deposit bytes must land in the same pin PR" as product law. Human-test mirror: HT-17 in `docs/installer-refactor-ovpr/install-ovpr-human-test-plan.md`. Schema/data behind the new pin still needs `directive upgrade` (AC14.11), not repair alone.
62
+
61
63
  **Repair (pin unchanged):** `npm i -g @deftai/directive`, then `directive install --repair`.
62
64
 
63
65
  **Upgrade version or migrate data:** `directive upgrade` (alias: `update`).
@@ -45,10 +45,24 @@ Allowance = clamp(count of **concrete** production files on the merge-base `file
45
45
  When the bound story is in the change set:
46
46
 
47
47
  1. A human-stamped merge-base mint for the **continuity-resolved** story is the allowlist when present (planId match ignores stale `xbriefRelPath`; no-`plan.id` is path-first + basename-keyed mint with matching `xbriefRelPath`).
48
- 2. Otherwise membership evaluates against the merge-base brief's **concrete** `file_scope` as agent precommitment. Head brief cannot widen.
48
+ 2. Otherwise membership evaluates against the merge-base brief's **concrete** `file_scope` as agent precommitment. Head brief cannot widen, except under a qualifying forge approval (#6203, below).
49
49
  3. Free paths: bound brief, verified peer briefs, `CHANGELOG.md`, and continuity lifecycle paths of the bound identity. An xBRIEF-only change set may omit a mint.
50
50
  4. Empty / invalid / unreadable mint records fail closed (they are not absence).
51
51
 
52
+ #### Forge-approved head brief (#6203)
53
+
54
+ A PR can carry a new or widened brief plus its code when a human approves it on GitHub. With PR context (`--pr N` / `--repo OWNER/NAME`, or `GITHUB_PR_NUMBER` / `GITHUB_REPOSITORY` in CI), a failing membership check under precommitment or missing authority consults the forge. When a qualifying approval exists, membership evaluates against the **head** brief's concrete `file_scope` instead of the merge-base brief.
55
+
56
+ - **Mint wins.** A present mint keeps the mint path. The forge is never consulted.
57
+ - **Auth-hash v1** (`sha256:` over canonical JSON; strings byte-exact, no Unicode normalization) covers `plan.id`, the xbrief path, the concrete `file_scope`, and the acceptanceCriteria array: `plan.acceptance`, `plan.narratives.AcceptanceCriteria`, and each item's `narrative.Acceptance`, in document order. `productPullRequest` and other delivery metadata stay out, so a PPR stamp keeps the approval. Any change to the covered fields drops it.
58
+ - **Qualifying approval:** the reviewer's latest deciding review (APPROVED / CHANGES_REQUESTED / DISMISSED) is APPROVED; the reviewer is a `User` (not Bot or App), not the PR author, and has `write`, `maintain` or `admin` on the repo; the repo is the checkout's `origin` (a `--repo` value may only confirm it, so a fork's approval never counts) and is the PR's base repo; the evaluated HEAD is the PR head (or GitHub's merge commit whose second parent is the PR head); and the brief at the reviewed commit hashes the same as the head brief.
59
+ - **Fallback:** missing PR context, no qualifying approval, a hash mismatch, or an unavailable forge keeps today's Path B or mint finding unchanged. The result message adds one `forge approval (#6203)` line with the reason.
60
+ - **Membership only.** The production fence still reads the merge-base brief. A widened brief can pass membership and still fail `production-scope-over-budget`. A brand-new brief has no production fence (as today).
61
+ - **Re-run after approval.** Approval does not re-trigger CI. Re-run the merge gate (or push) after the approval lands.
62
+ - **Residual (#6207):** an agent that holds a second human forge token can satisfy every predicate above. The hash binds the approved bytes; it does not prove independent human intent and does not close that confused-deputy risk. That posture stays open on #6207.
63
+
64
+ Code: `packages/core/src/scope-provenance/forge-approval.ts`.
65
+
52
66
  #### Path A planning split (#5551)
53
67
 
54
68
  When an active implementation brief is **outside** the change set, membership is a no-op for that identity (Path A). Supported planning publication is two change sets, not one: