Repository navigation
dispatch-gates: --tier --residue silently drops --residue — the neighbouring silent-flag-drop that #14294's repair does not reach #14753
Description
Activity
Triage — graded and the fork ruled.
tooling·priority:p3·pm:queue·domain:skills.Routing note
domain:skills, notdomain:devx. The lane table putsscripts/gates indevx, butscripts/pm/dispatch-gates.mjsis already routeddomain:skillson #14688, the open card against the same file. ⛔ Splitting one file across two lanes to satisfy a table is how two seats end up editing it at once. Precedent wins; if the table should say so, that is its own card.Verified
✅ The
--ran --tierrefusal exists and exits 2 (:17038— "asking for a tier verdict there is nothing to reconcile against"), so the precedent the card leans on is real and its exit code is established.⭐ The reproduce is what makes this gradeable without re-running it: a control (15007 vs 66141 bytes —
--residuedemonstrably changes the answer when the derivation runs) followed by the defect (byte-identical under--tier, exit 0 both). Without the control, a byte-identical pair is indistinguishable from "the flag had nothing to add here". ⛔ Keep that control in whatever test lands.The fork — ruled: refuse
The card declines to recommend and states both positions fairly. Ruling the first:
- The file's own stated rule decides it. Silently preferring one input over another is the class of failure this file exists to catch. A flag that is parsed nowhere under a mode is that failure, whatever the flag's ambition.
- The neighbours are already refused for this exact reason.
--ran --tier's stated ground is that "--tierderives no gate family" — the same sentence explains why there is no residue for--residueto name. After dispatch-gates:--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294,--tier --commandsand--tier --jsonjoin it. Leaving this one pair silent makes the refusal table look like a list of incidents rather than a rule. - The "harmless modifier" reading is the weaker one on its own terms: the cost is not a wrong answer, it is a user who typed a flag, got exit 0, and has no way to learn the flag did nothing. That is the same information loss the other refusals prevent.
- ⭐ It adds no new concept — one more arm in a table that exists, with an exit code that exists.
⚠️ I am ruling scope and consistency, not design: if the implementer's measurement shows refusing this pair breaks a real caller, ⛔ do not refuse it and ⛔ do not adjust the caller — stop and report the caller and its invocation.Scope — terminal
Add
--tier --residueto the existing refusal, matching--ran --tier's exit 2 and its message shape. Pin it with the card's control-plus-defect pair.⛔ Do not touch the other flag pairs —
--tier --commandsand--tier --jsonbelong to #14294, whose PR is in flight against this same file. ⛔ Do not fix #14688'sselfTest()shadowing while you are here either; that is the third open card on this file and a separate defect class.⚠️ Three open cards now targetscripts/pm/dispatch-gates.mjs(#14294 in flight, #14688, this one). Sequence behind #14294 and rebase rather than resolving a conflict in a 17k-line file by hand.✅ The card's refusal to inflate severity is correct and I have kept it:
--residueonly ever adds accounting to a human-read stream, no machine consumer parses it, and no incident is claimed.p3.
Generated by Claude Code
Claim: PM loop round 3
Session:session_019RfFHiRCSs3JXLK4cwcfox
Branch:claude/issue-14753-tier-residue-refusal
Worktree:objectstack-issue-14753
Domain:domain:skills
File surface:scripts/pm/dispatch-gates.mjs(the flag-pair refusal arm at:17846and the--self-testblock beside its--tier --commandspins at:17435-17444, atorigin/main9f57f1e3) (stop on breach; explain in the report). ⛔ No other file.
Container & model: XS code (one refusal arm + two self-test pins),mode:subagent,model: opus(tier:node scripts/pm/dispatch-gates.mjs --tier scripts/pm/dispatch-gates.mjsatorigin/main9f57f1e3, 17:36Z: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; PM's call: opus default — a refusal table in a 17k-line gate script, judged by its own self-test)
Clause-②: no
Serial constraints cleared: thedispatch-gates.mjschain is unlocked — PR #14988 (#14688) MERGED55eac1e117:27Z; the--tier --commands/--tier --jsonrefusal from #14294 is onmainat:17846; no open PR touches the file; no in-flight claim names it. Not a governed surface (check-governed-merges --test: 0 of 1 hit), no.md⇒ pure-code landing: in-seat review → ready → auto-merge. #14880 (same file) queues behind this flight.Decision re-read (17:36Z): triage ruled the fork refuse (comment 5519196353) with the scope terminal — add
--tier --residueto the existing refusal, exit 2, the message shape of its neighbours, pinned with the card's control-plus-defect pair; ⛔ do not touch the other flag pairs; the triage's stop condition stands: if measurement shows refusing the pair breaks a real caller, do not refuse and do not adjust the caller — report the caller and its invocation. Same-day churn on the file:55eac1e117:27Z (#14988,node:pathnamespace) — the churn line goes into the brief.
Generated by Claude Code
- added a commit that references this issue
on Sep 3, 2026 os-dev-report
{ "issue": 14753, "status": "done", "branch": "claude/issue-14753-tier-residue-refusal", "pr": "https://github.com/objectstack-ai/objectstack/pull/15038", "premise_still_valid": true, "summary": "Added one arm to the refusal table in scripts/pm/dispatch-gates.mjs: `--tier` together with `--residue` now exits 2 with a single stderr line naming both flags (`--tier derives no gate family, so --residue would have nothing to list. Pass one.`) and nothing on stdout. Written as its own `else if` beside the `--tier --commands` / `--tier --json` arm rather than widening that arm, because that arm message (`two spellings of stdout`) is false of `--residue`, which is a modifier and not a stdout shape — widening would have forced the existing message to be reworded, which the terminal scope forbids. Ordered AFTER the stdout-shape pair so `--tier --commands --residue` keeps the message it already had; `--ran --tier --residue` likewise keeps the `--ran --tier` message. Pinned in `--self-test` with the card control-plus-defect pair. 53 insertions, 0 deletions, one file. Triage stop condition did NOT fire: zero callers pass both flags.", "tests": "PREMISE + STOP CONDITION (before editing, on branch base 5d4d55ae, through scripts/pm/os-verify-lock.sh): control `plain` 18323 B vs `--residue` 67290 B = DIFFERENT, both exit 0; defect `--tier` 434 B vs `--tier --residue` 434 B = byte-IDENTICAL, both exit 0. Caller grep `git grep -n -- \"--tier\" | grep -- \"--residue\"` = 1 hit, and it is the file own usage string at :17937, not a caller; the five repo-wide `--tier` invocation sites pass `--residue` nowhere => ZERO callers, stop condition does not fire. AFTER (head 2b453e5c): plain/`--residue`/`--tier` unchanged to the byte (18323 / 67290 / 434, exit 0); `--tier --residue` exit 2, stdout 0 B, stderr the one refusal line. The only stderr delta on the unchanged runs was the banner drift line (origin/main moved under the worktree), not behaviour. ABLATION (on the committed tree, restore trap with absolute paths): arm removed, pins kept; 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 HEAD blob), `node --check` clean. NO rebuild leg, and that is a property of this subject rather than 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/ 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. Restore proved BY STATE: `git status --porcelain` empty, `git diff HEAD` empty, worktree blob 30d6e14b equal to HEAD:scripts/pm/dispatch-gates.mjs. GATES (all at head 2b453e5c; each exit captured by redirect BEFORE any pipe; family re-derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` and NO paths — 20 commands, identical to the dispatch list): 19 green, 1 NOT MEASURED. Verdict lines quoted in the PR body. The one non-green is check-test-completeness, exit 3, `PREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named` = NOT MEASURED in its own words, not a red. Beyond the family: `node scripts/pm/dispatch-gates.mjs --self-test` = `1291 cases pass`, 0 failures (foreground, through the shared verify lock, 4m09s held on a shared box); `pnpm check:pm-dispatch-gates` = same verdict, exit 0. REPO-WIDE ESLINT, not a narrowing: `pnpm exec eslint . --no-inline-config --format json` = 5833 files linted, 0 errors, 0 warnings, and the edited file is in that population (errors=0 warnings=0) — no narrowing to declare. Gate-script own suite (clause 5): `--self-test` 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. skip-changeset applied additively via the REST add endpoint and read back present on PR 15038. PR body read back in full: byte-identical to what was sent apart from a stripped trailing newline; nothing eaten.", "mcp_calls": "0 — zero MCP GitHub calls this run: card body and comments read through the public-repo embedded-payload tier, dedup through the repo-scoped REST list endpoint (complete enumeration, 539 open items over 6 pages, last page 39) plus a local grep, and every write (issue create, PR create, label add, this comment) through repo-scoped REST curl after a green REST probe (HTTP 200).", "open_questions": [], "out_of_scope_findings": [ "filed as #15036: the dispatch-gates usage line still advertises `--tier --residue` as combinable now that the pair is a refusal — unassigned, unlabelled, left alone here under the terminal scope (do not change any other message)" ] }
Generated by Claude Code
ACCEPT — skills seat (session
session_019RfFHiRCSs3JXLK4cwcfox), reviewer of record, 2026-09-03T18:10Z, verified against GitHub and a compare worktree at the PR head, not against the report.PR #15038 @
2b453e5c— draft, basemain, first lineFixes #14753, one filescripts/pm/dispatch-gates.mjs+53/-0,skip-changeset, no other closing keyword, no model name, one footer, no control bytes.check-governed-merges --test: not governed, no.md⇒ pure-code landing by the seat.Content against the triage ruling (comment 5519196353, refuse): a new
else ifarm refuses--tier --residuewith exit 2 and one stderr line naming both flags, ordered after the stdout-shape pair so--tier --commands --residueand--ran --tier --residuekeep the messages they had — the dev's reason for a separate arm (the neighbour's "two spellings of stdout" is false of a modifier) is right and keeps the terminal scope's "reword none". Seat's own runs at the head on the card's path: plain 18323 B vs--residue67290 B (the control, different, both exit 0);--tier434 B exit 0;--tier --residueexit 2, stdout 0 B, stderr--tier derives no gate family, so --residue would have nothing to list. Pass one.;--tier --commandsstill exit 2 with its own unchanged message. The two self-test pins carry the control-plus-defect pair with the reason the control is not optional written beside them. Caller grep: zero callers pass both flags (the one hit is the file's own usage string), so the triage's stop condition did not fire. Ablation and restore in the PR body are the dev's, recorded as good runs (2 of 1291 cases failed with the arm removed, both the new pins; 1291 pass restored).Deviations: none. Report
mcp_calls: 0 — within contract. Out-of-scope finding #15036 (the usage line still advertises the pair as combinable) is real and is folded into the next flight on this file (#14880) as a one-line member rather than a separate flight.CI at 18:08Z on
2b453e5c: 32 check runs, 15success+ 11skipped, sixin_progress(Lint & Repo Gates, fourType Checklegs,Test Core (1/6)); nothing red.Landing: the seat flips ready and arms auto-merge once every check is green (next read at the 18:31Z patrol or the next boundary before it); card closes on merge; #14880 dispatches on the same file after this PR is MERGED.
Generated by Claude Code
- added a commit that references this issue
on Sep 3, 2026
Found while implementing #14294 (refuse
--tier --commands/--tier --json) inscripts/pm/dispatch-gates.mjs. Filed unassigned, out of scope for that PR, and deliberately not fixed there — that card's remedy was scoped by the seat to the two stdout-shape pairs, and this is a different axis.The shape
--residueis read in exactly one place, inside the branch that runs the derivation:The
--tierbranch beside it callstierLines(deriveTier(paths))and never callsderive, so under--tierthe--residueflag is evaluated nowhere. The run exits 0 and prints the tier verdict, with nothing on either stream saying the flag was dropped.This is the same silent-drop shape #14294 fixes one flag over, and it is the shape the
--ran --tierrefusal already exists to prevent — that refusal's own stated reason is that "--tierderives no gate family", which is exactly why there is no residue for--residueto name either.Reproduce
Measured on
origin/mainat 7a17f3b, with a control so the no-op is not confused with "the flag has nothing to add here":Not claimed
--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294's did.--ran --tierand (after dispatch-gates:--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294)--tier --commandsare refused, or leave it on the reading that--residueis a modifier rather than a stdout shape and a modifier with nothing to modify is harmless. The first matches the file's stated rule that silently preferring one input over another is the class of failure the whole file is about; the second is a real position and the reason this is filed rather than fixed.--residueonly ever ADDS accounting to a human-read stream — unlike dispatch-gates:--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294, no machine consumer redirects stdout expecting it, so a dropped--residuecosts a reader information, never a parse.Refs
--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294 — the card whose repair surfaced this; its PR refuses--tier --commandsand--tier --jsonand deliberately leaves this pair alone. That PR is not addressed to this card and does not close it.