Repository navigation
fix(pm): dispatch-gates runs the diff-scoped issue-citation verdict locally, off the checker's own local-env declaration - #20314
Conversation
…ocally, off the checker's own local-env declaration The lint.yml step that runs `node scripts/check-issue-citations.mjs` passes GITHUB_TOKEN and OS_GATE_MERGE_GROUP_BASE_SHA through `env:`, so the env carrier classified the bare run NOT RUNNABLE LOCALLY and `--commands` offered only its self-test. The checker now declares, in the existing dispatch-gates marker idiom, that its bare invocation needs neither; dispatch-gates reads the declaration (`declaredLocalEnv`, path-list grammar), grades it against the workflows (`localEnvRefusal`: a name the step does not pass, or a bare run no workflow makes, is refused) and spends it on the bare key alone (`localEnvAdmitted`). The census and every other invocation keep their classification. Claude-Session: https://claude.ai/code/session_01MjvgiFAmjHqsxy1XLiVYfH Co-authored-by: Claude <noreply@anthropic.com>
…k:watch-hint-literal does not read it as a live declaration Measured by the derived sweep for this diff: `pnpm check:watch-hint-literal` exit 1, "every live declaration is a literal -- scripts/pm/dispatch-gates.mjs: ROOT_DIR_WATCH_HINTS". The fixture needed no watch-hint idiom at all; its first line is now an unrostered constant, and the case still pins the declaration on line 3. Claude-Session: https://claude.ai/code/session_01MjvgiFAmjHqsxy1XLiVYfH Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Face: ① Derived judgments
② Semver levelBoth files sit in the private root package (no ③ Boundary flagsNo Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #20278
Clause-②: no
What this changes
dispatch-gates --commandsnow lists the diff-scoped issue-citation verdict, barenode scripts/check-issue-citations.mjs. That is the runLint & Repo Gatesblocks 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
&&inpnpm check:issue-citations && node scripts/check-issue-citations.mjswas already split into two families. The cause is the env carrier: the lint.yml step passesGITHUB_TOKEN: ${{ github.token }}andOS_GATE_MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }}throughenv:.workflowEnvValuestreated both as values with none outside CI, sodiscoverFamiliessetnotRunnableandcommandsFordropped the family. The script needs neither on a pull-request run:pull_request, and the checker then usesmerge-base origin/main HEAD.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) andcheck-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)
scripts/check-issue-citations.mjs: one whole-line comment in the existingdispatch-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.scripts/pm/dispatch-gates.mjs:local-envjoinsPATH_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.declaredLocalEnvreads the declaration. It refuses a cut reason and any token that is not an env name.localEnvRefusalgrades the declaration against the tree's workflows. It throws, so the CLI answersderivation failed(exit 2), when either:env:workflow value;localEnvAdmittedspends the declaration on the bare key alone.--census,--self-test,check:*keys and every other argv admit nothing.workflowEnvValuesgains one per-NAME limb: only the declared names leave the list, so an undeclared name still keeps the family out.localEnv, in--jsonand in a same-line note in the human block, so no rendering lists the command without saying why.GITHUB_TOKEN.rls.zod.tsspecimen.package.jsonis untouched. Renamingcheck:issue-citationswas 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
--commandsforpackages/spec/src/security/rls.zod.ts. Before is base3f86dc52, after is head15ab041f. Stdout gains exactly one line; nothing else on stdout moves.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
3f86dc52with an uncommitted edit, since restored and removed:#16608re-added onrls.zod.ts:426: exit 2,[allocated-but-absent] packages/spec/src/security/rls.zod.ts:426 #16608, the finding from fix(spec): state the RLS using / check texts as the write gate enforces them #20268's CI run.GITHUB_TOKEN/GH_TOKENunset: exit 0.Self-test, through the verify lock.
pnpm check:pm-dispatch-gatesat15ab041f: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.node scripts/pm/dispatch-gates.mjs --self-testat23973549also answeredVERDICT 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 emptygit diff HEAD.// dispatch-gates: local-env GITHUB_TOKEN→// ablated-marker: local-env GITHUB_TOKEN, blob49bf630d→64ab1b63):--commandsforrls.zod.tslosesnode scripts/check-issue-citations.mjsfrom stdout (0 hits), and stderr prints⊘ NOT MEASURED — scripts/check-issue-citations.mjsagain. Restored: blob == HEAD49bf630d.... OS_GATE_MERGE_GROUP_BASE_SHA PR_BODY --): the CLI exits 2 withderivation 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 --commandswith no paths, 2 paths against merge base3f86dc52. It derived 33 families, and each was run with its exit code captured before any pipe. Reconciled at head15ab041f:All 33 exit 0 at
15ab041f, among themnode scripts/check-issue-citations.mjs,pnpm check:issue-citations,pnpm check:pm-dispatch-gates,pnpm check:watch-hint-literal,pnpm check:nul-bytesandpnpm check:declared-population-live. The first sweep, at23973549, was red on one family:pnpm check:watch-hint-literalexit 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.15ab041frenames 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 jsonon the two changed files at15ab041fexited 0. The JSON reports 2 files, 0 errors and 0 warnings. The config--print-configreports forscripts/pm/dispatch-gates.mjscarries noparserOptions.projectand noprojectService, so linting is not type-aware and this diff cannot move the verdict on any untouched file. Repo-widepnpm lintis CI's.Changeset:
skip-changeset."private": truewith nofiles[].git grepfordeclaredLocalEnv,localEnvRefusaland the marker overpackages/**andapps/**: 0 hits.defineStackhitspackages/spec/src/stack.zod.ts, which@objectstack/spec'sfiles[]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
node scripts/check-issue-citations.mjs --census, is still printed NOT MEASURED on every card in the checker'spackages/**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-setrefuses here for a proxy-route reason (exit 2, HTTP 401), while dispatch-gates namesenv GITHUB_TOKEN. The ruling records this as a note, and it is unchanged here.Generated by Claude Code