Skip to content

Commit 7338efe

Browse files
fix(pm): dispatch-gates runs the diff-scoped issue-citation verdict locally, off the checker's own local-env declaration (#20314)
Fixes #20278 Clause-②: no ## What this changes `dispatch-gates --commands` now lists the diff-scoped issue-citation verdict, bare `node scripts/check-issue-citations.mjs`. That is the run `Lint & Repo Gates` blocks on, and before this change the tool printed it as `⊘ NOT MEASURED`, so a dev's local sweep could reconcile clean while CI went red (measured on #20268's CI run). The census and every other invocation of the same script keep exactly the classification they had. This implements option A of the seat ruling on the card (ruling comment 5858896081): two files, one declaration and one reader. ## Why the family was NOT MEASURED The `&&` in `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` was already split into two families. The cause is the **env carrier**: the lint.yml step passes `GITHUB_TOKEN: ${{ github.token }}` and `OS_GATE_MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }}` through `env:`. `workflowEnvValues` treated both as values with none outside CI, so `discoverFamilies` set `notRunnable` and `commandsFor` dropped the family. The script needs neither on a pull-request run: - The merge-group base renders empty on `pull_request`, and the checker then uses `merge-base origin/main HEAD`. - The token is used when present, and the diff mode reads the public board with one probe per added number. A rule about the variables alone cannot fix this. Dropping the job token and the merge-group base generically was measured to also admit the same script's `--census` (a report-only enumeration of the whole board) and `check-required-contexts.mjs --verify-required-set` (exit 2 here). The fact belongs to the program and to the mode, so the program declares it. ## The change (2 files) 1. **`scripts/check-issue-citations.mjs`**: one whole-line comment in the existing `dispatch-gates:` marker idiom: `// dispatch-gates: local-env GITHUB_TOKEN OS_GATE_MERGE_GROUP_BASE_SHA -- REASON`. The reason states the file's own facts. It covers the bare invocation only; the census needs the token and is not declared. 2. **`scripts/pm/dispatch-gates.mjs`**: - `local-env` joins `PATH_LIST_MARKER_KEYS`, the LIST-then-reason grammar. It gets the wholeness reading, the lookalike probe and the census by construction. There is no new grammar copy, no table and no gate. - `declaredLocalEnv` reads the declaration. It refuses a cut reason and any token that is not an env name. - `localEnvRefusal` grades the declaration against the tree's workflows. It throws, so the CLI answers `derivation failed` (exit 2), when either: - a declared name is not passed by every step running the bare invocation as an `env:` workflow value; - no workflow runs the bare invocation. - `localEnvAdmitted` spends the declaration on the bare key alone. `--census`, `--self-test`, `check:*` keys and every other argv admit nothing. - `workflowEnvValues` gains one per-NAME limb: only the declared names leave the list, so an undeclared name still keeps the family out. - The matched row carries `localEnv`, in `--json` and in a same-line note in the human block, so no rendering lists the command without saying why. - The docblock states what the reader does NOT do: guess; admit a name the workflow does not pass; admit an argv no workflow runs; touch any other invocation of the same script; repair an argv value. - Self-test: - Direction pins on the lint.yml step text: the bare command is in the runnable union and not in the not-runnable set. - The census invocation stays not-runnable on `GITHUB_TOKEN`. - Controls: an undeclared script keeps the old reading, and a partial declaration keeps the undeclared name. - Refusals for a stale name, a name only some steps pass, and a missing bare run. - Live in-process pins on this tree for the `rls.zod.ts` specimen. - The key roster and marker census moved from six to seven keys and from seven to eight declarations, each row named. - No absolute counts. `package.json` is untouched. Renaming `check:issue-citations` was optional in the triage notes and would not make the diff-scoped run runnable: the derivation keys on what lint.yml runs, not on the alias name. ## Evidence **`--commands` for `packages/spec/src/security/rls.zod.ts`.** Before is base `3f86dc52`, after is head `15ab041f`. Stdout gains exactly one line; nothing else on stdout moves. ```text before dispatch-gates --commands: 71 command(s) — 42 pnpm, 29 direct node (65 matched by path, ...) before + 7 famil(ies) matched by path take a VALUE FROM THE WORKFLOW and are NOT above ... before ⊘ NOT MEASURED — scripts/check-issue-citations.mjs --census before ⊘ NOT MEASURED — scripts/check-issue-citations.mjs after stdout line 12: node scripts/check-issue-citations.mjs after dispatch-gates --commands: 72 command(s) — 42 pnpm, 30 direct node (66 matched by path, ...) after + 6 famil(ies) matched by path take a VALUE FROM THE WORKFLOW and are NOT above ... after ⊘ NOT MEASURED — scripts/check-issue-citations.mjs --census ``` The human block now renders the row as `- node scripts/check-issue-citations.mjs [lint.yml] matched via packages/spec/src/security/rls.zod.ts ⇢ gate source 'packages/**' · runs here without GITHUB_TOKEN, OS_GATE_MERGE_GROUP_BASE_SHA: its script declares local-env for this bare invocation, and CI passes them through the step's env:`. **The command it now lists answers the question here.** Measured in a detached probe worktree at `3f86dc52` with an uncommitted edit, since restored and removed: - `#16608` re-added on `rls.zod.ts:426`: exit 2, `[allocated-but-absent] packages/spec/src/security/rls.zod.ts:426 #16608`, the finding from #20268's CI run. - Control with a resolving number: exit 0. - Control with `GITHUB_TOKEN`/`GH_TOKEN` unset: exit 0. **Self-test, through the verify lock.** - `pnpm check:pm-dispatch-gates` at `15ab041f`: `os-verify-lock: VERDICT command-exit 0 · held the lock 975s (16m15s) · waited 0s · ⚠ SHARED-BOX SECONDS`. Its log reads `✓ dispatch-gates self-test: 1976 cases pass.` and `✓ check:pm-dispatch-gates --self-test: the exit contract holds in all three directions.` - An earlier `node scripts/pm/dispatch-gates.mjs --self-test` at `23973549` also answered `VERDICT command-exit 0`, 1976 cases. **Ablations.** Both legs used `scripts/ablation-replace.mjs`, which checks the anchor hit and that the blob changed, then restores with blob == HEAD and an empty `git diff HEAD`. - Leg 1, declaration disabled (`// dispatch-gates: local-env GITHUB_TOKEN` → `// ablated-marker: local-env GITHUB_TOKEN`, blob `49bf630d` → `64ab1b63`): `--commands` for `rls.zod.ts` loses `node scripts/check-issue-citations.mjs` from stdout (0 hits), and stderr prints `⊘ NOT MEASURED — scripts/check-issue-citations.mjs` again. Restored: blob == HEAD `49bf630d`. - Leg 2, stale name planted (`... OS_GATE_MERGE_GROUP_BASE_SHA PR_BODY --`): the CLI exits 2 with `derivation failed — dispatch-gates: scripts/check-issue-citations.mjs:204 declares local-env PR_BODY, which the step(s) running node scripts/check-issue-citations.mjs do not all pass as a workflow value through env: ...`. Restored: blob == HEAD. **Derived gate sweep for this diff.** Ran `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` with no paths, 2 paths against merge base `3f86dc52`. It derived 33 families, and each was run with its exit code captured before any pipe. Reconciled at head `15ab041f`: ```text Run reconciliation — 33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN. ✓ dispatch-gates --ran: 33 derived famil(ies) accounted for — 33 run, 0 NOT-MEASURED (a DERIVED zero — all 33 recorded an exit code and none of them is 3). ``` All 33 exit 0 at `15ab041f`, among them `node scripts/check-issue-citations.mjs`, `pnpm check:issue-citations`, `pnpm check:pm-dispatch-gates`, `pnpm check:watch-hint-literal`, `pnpm check:nul-bytes` and `pnpm check:declared-population-live`. The first sweep, at `23973549`, was red on one family: `pnpm check:watch-hint-literal` exit 1, `every live declaration is a literal -- scripts/pm/dispatch-gates.mjs:ROOT_DIR_WATCH_HINTS`. A self-test fixture string used that rostered name. `15ab041f` renames the fixture constant, and the whole sweep was then re-run on that head. `pnpm check:pm-skill-id-lint` (named by the dispatch) also ran and exited 0: `34 file(s) clean`. **Lint, a narrowed run.** `eslint --no-inline-config --format json` on the two changed files at `15ab041f` exited 0. The JSON reports 2 files, 0 errors and 0 warnings. The config `--print-config` reports for `scripts/pm/dispatch-gates.mjs` carries no `parserOptions.project` and no `projectService`, so linting is not type-aware and this diff cannot move the verdict on any untouched file. Repo-wide `pnpm lint` is CI's. **Changeset: `skip-changeset`.** - Both files are in the repo-root package, which is `"private": true` with no `files[]`. - `git grep` for `declaredLocalEnv`, `localEnvRefusal` and the marker over `packages/**` and `apps/**`: 0 hits. - Positive control: `defineStack` hits `packages/spec/src/stack.zod.ts`, which `@objectstack/spec`'s `files[]` ships (`src/**/*.zod.ts`). **Line budget.** - `scripts/pm/dispatch-gates.mjs`: 29507 → 29866 lines (1,746,809 → 1,768,400 bytes). - `scripts/check-issue-citations.mjs`: 1213 → 1215 lines (65,982 → 66,904 bytes). No gate added, no ceiling moved, no table added. ## Acceptance notes - The census row, `node scripts/check-issue-citations.mjs --census`, is still printed NOT MEASURED on every card in the checker's `packages/**` population. Its only workflow, `half-state-patrol.yml`, is path-filtered to the checker and patrol files, and the step is report-only. The ruling records this as a note, and it is unchanged here. - `check-required-contexts.mjs --verify-required-set` refuses here for a proxy-route reason (exit 2, HTTP 401), while dispatch-gates names `env GITHUB_TOKEN`. The ruling records this as a note, and it is unchanged here. - The census row's existing sibling note ("a DIFFERENT invocation of the same script …") now names the bare diff run as that sibling. The wording holds as written: a different argv asks another question. --- _Generated by [Claude Code](https://claude.ai/code/session_01MjvgiFAmjHqsxy1XLiVYfH)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4e0f72e commit 7338efe

2 files changed

Lines changed: 372 additions & 11 deletions

File tree

‎scripts/check-issue-citations.mjs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ const PROXY_REARM_GUARD = 'OS_ISSUE_CITATIONS_PROXY_REARMED';
201201
*/
202202
export const ROOT_DIR_WATCH_HINTS = ['content/docs/releases/**', 'packages/**'];
203203

204+
// dispatch-gates: local-env GITHUB_TOKEN OS_GATE_MERGE_GROUP_BASE_SHA -- the BARE invocation, the diff-scoped verdict lint.yml runs on every pull request, answers that run's question on a dev container with neither value: GITHUB_TOKEN (or GH_TOKEN) is used when present and never required, because the diff mode reads the PUBLIC board with one probe per distinct number the change adds (it enumerates the whole board only past 400 of them) and re-arms the session proxy itself (rearmThroughProxy); OS_GATE_MERGE_GROUP_BASE_SHA renders EMPTY on a pull_request run, since only a merge_group build sets it, and with it unset baseSpellings takes merge-base(origin/main, HEAD), the base the pull_request run itself judges against. A board this run cannot read, or a base it cannot resolve, still exits PREREQUISITE NOT MET and never 0. Not declared for --census, which enumerates every page of the board and needs the token.
205+
204206
/* ─────────────────────────── the scope contract ─────────────────────────── */
205207

206208
/**

0 commit comments

Comments
 (0)