Skip to content

tooling(pm): refuse --tier --residue in dispatch-gates instead of dropping the modifier - #15038

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-14753-tier-residue-refusal
Sep 3, 2026
Merged

tooling(pm): refuse --tier --residue in dispatch-gates instead of dropping the modifier#15038
os-steve merged 1 commit into
mainfrom
claude/issue-14753-tier-residue-refusal

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #14753

--residue is read in exactly one place — the derive(...) call at the bottom of the direct-invocation block. The --tier branch beside that call never reaches it, so under --tier the flag was evaluated nowhere: exit 0, the tier verdict printed, and nothing on either stream saying the flag had been dropped.

Triage (comment 5519196353) ruled the card's fork refuse and made the scope terminal: one more arm in the refusal table that already holds --ran --tier, --commands --json and --tier --commands / --tier --json, exit 2, one line on stderr naming both flags, pinned with the card's control-plus-defect pair. That is exactly what this is — 53 added lines in one file, nothing removed, no other refusal, message or behaviour touched.

The arm, and why it is its own arm rather than a widened condition

The --tier --commands / --tier --json arm's message is --tier and X are two spellings of stdout — pass one. That sentence is false of --residue, which is not a spelling of stdout but a modifier of the derivation, so widening that arm's condition would have forced its message to become conditional and reworded — which the scope forbids. A sibling else if beside it keeps that arm byte-identical and states the correct ground, which is the --ran --tier refusal's, one flag over: --tier reads no workflow and no check script, so it derives no gate family — the same sentence that leaves a run record nothing to reconcile against leaves --residue no residue to list.

dispatch-gates: --tier derives no gate family, so --residue would have nothing to list. Pass one.

Ordering. The new arm is placed AFTER the stdout-shape pair, for the reason that arm states about itself: --tier --commands --residue keeps the message it already had, so this branch adds a refusal and rewords none. --ran --tier --residue likewise keeps the --ran --tier message from the arm above both.

Readings — before and after, measured on this branch

Re-measured here rather than inherited from the card (the card measured at 7a17f3bf; this branch is off 5d4d55ae, so the absolute byte counts differ and only the comparison carries). Target path scripts/measure-partial-retirement-annotation.mjs, stdout bytes, exit codes captured before any pipe:

run before (5d4d55ae) after (2b453e5c)
plain exit 0, 18323 B exit 0, 18323 B
--residue exit 0, 67290 B exit 0, 67290 B
--tier exit 0, 434 B exit 0, 434 B
--tier --residue exit 0, 434 B exit 2, 0 B
CONTROL: plain vs --residue DIFFERENT DIFFERENT
DEFECT: --tier vs --tier --residue byte-IDENTICAL DIFFERENT

The control is what makes the byte-identical reading mean anything: on its own, identical stdout is indistinguishable from "the flag had nothing to add here". --residue grows the derivation's own output by more than 3.6x when the derivation actually runs, and adds nothing at all under --tier.

Three modes are unchanged to the byte. The only stderr delta on the unchanged runs was the banner's drift line (origin/main moved under the worktree between the two measurements, the shared remote-tracking ref doing what AGENTS.md documents), not behaviour.

Stop condition — the caller grep

Triage's stop condition: if refusing the pair breaks a real caller, refuse nothing and adjust nothing.

git grep -n -- "--tier" | grep -- "--residue"

One hit, and it is not a caller: the file's own usage string, scripts/pm/dispatch-gates.mjs:17937. The five repo-wide --tier invocation sites (two skill files, one references file, this file's header example and that usage string) pass --residue nowhere. Zero callers — the stop condition does not fire.

That usage line does state a grammar this change makes inaccurate for one pair. It is left alone under the terminal scope and filed instead: #15036.

Self-test pins, and the ablation

Added beside the --tier --commands / --tier --json pins, in the block that already spawns the plain run on this same card:

  • CONTROL--residue changes the derivation output when the derivation runs. Reuses the block's existing humanOut as the plain side; a second spawn would be a full derivation of this tree to re-measure what that run already measured.
  • --tier --residue refuses: status 2 and empty stdout, because a refusal that still prints an answer is the bug wearing an exit code.
  • and that refusal names both --tier and --residue.

Ablation. The arm was removed and the pins kept, on the committed tree, restore trap armed with absolute paths.

  • mutation proved on disk before any reading: refusal-message occurrences 1 -> 0, arm-condition occurrences 1 -> 0, new pins still present 1, blob 30d6e14b -> e025b7b8 (differs from the HEAD blob), node --check clean.
  • no rebuild leg, and that is a property of this subject, not a skipped step: the self-test spawns this very file by path (runCli -> process.execPath [CLI]), so nothing resolves through a package exports or a dist/ that a stale build could green.
  • result: dispatch-gates self-test: 2 of 1291 case(s) failed, and the two are exactly the new defect pins, naming themselves. The CONTROL stayed green in both legs — which is the point of having it.
  • restore proved by state, not by exit code: git status --porcelain empty, git diff HEAD empty, worktree blob 30d6e14b... equal to HEAD:scripts/pm/dispatch-gates.mjs.

Verification — all at head 2b453e5c

Derived from the real change set (no paths passed; the script reads its own merge base) with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 20 commands, identical to the dispatch's list. Exit codes captured by redirect before any pipe.

gate its own verdict line
check-ci-filter-parity OK: all 135 declared cross-package glob(s) (95 unique) are covered by core or crosspkg...
check-closing-keyword-parity check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators...)
check-comment-mask-corpus comment-mask corpus sweep: 5833 files, 0 disagree, 0 unparseable, 42.6s
check-cross-package-test-inputs (both spellings) OK: 25 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
check-self-test-wired every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-shard-attestation 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).
check-test-completeness PREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named. exit 3NOT MEASURED in the gate's own words, not a red
check-whole-set-label-write 0 violations — 252 file(s) over 3 root(s)...
check:agent-test-spelling 0 violations — 432 file(s) · 5850 bare -- token(s)...
check:bash32-floor 26 tracked shell file(s) ... name no bash 4+ construct (self-test 153 cases pass)
check:cli-command-ids 324 command-id literal(s) across 114 file(s) ... all resolve (self-test 39 cases pass)
check:declared-population-live 159 of 204 famil(ies) declare a path population, and every one of them reaches this tree's 8178 tracked file(s).
check:entry-guard 201 scripts/ file(s) — every entry guard goes through invoked-as.mjs (self-test 54 cases pass)
check:nul-bytes OK (scanned 8171 text file(s) ... no raw ASCII control bytes).
check:parse-guard 200 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs. (self-test 46 cases pass)
check:pm-dispatch-gates dispatch-gates self-test: 1291 cases pass.
check:pnpm-filter-targets 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
check:refd-timer-probe 5828 source file(s) swept; the process-global timer probe is read in ... and nowhere else.
check:watch-hint-literal 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.

19 green, 1 NOT MEASURED (no red). Beyond the derived family:

  • node scripts/pm/dispatch-gates.mjs --self-test1291 cases pass, 0 failures, run in the foreground through scripts/pm/os-verify-lock.sh (4m09s held, shared box).
  • Repo-wide ESLint, not a narrowing: pnpm exec eslint . --no-inline-config --format json5833 files linted, 0 errors, 0 warnings, and the edited file is in that population (errors=0 warnings=0). No narrowing to declare.
  • The gate script's own suite: --self-test above plus check:pm-dispatch-gates, which is what runs it. No vitest suite tests this script — the three *.test.ts files that mention dispatch-gates reference it in prose comments only (packages/cli/test/serve-node-env-production-default.e2e.test.ts, packages/create-objectstack/src/runtime-image.test.ts, packages/types/src/driver-error-classification.callers.test.ts).

skip-changeset: scripts/pm/** publishes from no package.

Not a governed surface: node scripts/pm/check-governed-merges.mjs --test scripts/pm/dispatch-gates.mjs returns 0 hits.

Out-of-scope findings filed, unassigned: #15036 (the usage line above). #14880 and #14870 are not addressed here and remain open; #14294's pairs are untouched.


Generated by Claude Code

…ropping the modifier

`--residue` is read in exactly one place — the `derive(...)` call at the bottom
of the direct-invocation block. The `--tier` branch beside that call never
reaches it, so under `--tier` the flag was evaluated nowhere: exit 0, the tier
verdict printed, and nothing on either stream saying the flag had been dropped.

Adds one more arm to the refusal table that already holds `--ran --tier`,
`--commands --json` and `--tier --commands` / `--tier --json`: exit 2, one line
on stderr naming both flags. Ordered after the stdout-shape pair so
`--tier --commands --residue` keeps the message it already had — this adds a
refusal and rewords none.

Pinned in `--self-test` with the control-plus-defect pair the card measured:
the control (plain vs `--residue` on a real path differ when the derivation
runs) is what makes the byte-identical `--tier` reading mean "the flag was
dropped" rather than "the flag had nothing to add".

Card #14753. Grep for callers passing both flags: zero, outside this file's own
usage string.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions github-actions Bot added the size/s label Sep 3, 2026
@os-steve
os-steve marked this pull request as ready for review September 3, 2026 18:33
@os-steve
os-steve added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 1064186 Sep 3, 2026
36 checks passed
@os-steve
os-steve deleted the claude/issue-14753-tier-residue-refusal branch September 3, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dispatch-gates: --tier --residue silently drops --residue — the neighbouring silent-flag-drop that #14294's repair does not reach

2 participants