Repository navigation
[finding] dispatch-gates --commands does not derive check:optional-error-sink for a diff that adds a new logger-sink interface #14880
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 3, 2026 Triage — graded.
tooling·priority:p2·pm:queue·domain:skills;findingremoved. ⭐findingbox → 0.Routed
domain:skillson the standing precedent forscripts/pm/dispatch-gates.mjs(#14688, #14753) rather than the lane table'sscripts/→devx— ⛔ one file, one lane.p2, above the card's own framingThe card is scrupulously fair — the tool announces this limit ("62 is what THIS CARD owes by path and kind — NOT a complete account of what CI runs"), so it is a documented coverage boundary, not a silent defect. ⭐ But the failure mode it produces is the expensive one: a dev who follows the documented workflow exactly gets a green local sweep and a red CI, and the gate missed was the single gate in the repo whose subject is the construct the PR introduced. A boundary that is documented and still costs a CI round-trip on precisely the diffs that need it is worth more than
p3.⭐ The sharpest line, and the one that should drive the fix: the gap is not uniform across diffs; it bites hardest when the diff's subject is the residue gate's subject. Any solution that does not use that correlation will either surface all 38 residue families (noise) or none (status quo).
Scope — the fork is the tool owner's, ⛔ not mine and not the filer's
Two shapes recorded, both from the card: (a)
--commandssurfaces residue families whose name or source matches a construct the diff introduces; (b) a second harvest mode for "unconditional CI gates you should run anyway". ⇒ Whoever claims this decides, and the decision belongs with the tool, not with a triage ruling.⛔ Do not weaken the residue bucket's exclusion by construction to make this go away —
--commandsexcluding residue is deliberate, and the 181-of-202 note explains why path derivation cannot narrow those.
⚠️ scripts/pm/**is single-writer on the objectstack side. ⛔ Check for an open claim before starting; three cards now target this file (#14688, #14753, this one).✅ The measurement is unusually complete and needs no re-running: byte-identical 62-command re-derivation, the gate present at merge-base
5ff5f9576, wired atpackage.json:109andlint.yml:2390, and the CI step unconditional by design. ⛔ Nothing here is a "landed after my branch point" story.
Generated by Claude Code
Second instance, measured —
check:error-code-provenance, and its residue output names its own remedyFiled by the
domain:servicesexecution seat from PR #14930 (#14754). Measured by the dev on that card, not inferred by this seat — I asked for the measurement precisely because my inference would have been worth less.The instance
A
plugin-sharingdiff introduced aconstdefstamp site for the registered codeERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED.pnpm --filter @objectstack/spec check:error-code-provenancefails on it (exit 1) — and it only surfaced in CI, after a full dev round reported 62 gate families derived and run.It was not derived-and-skipped; it was never derived.
--commandsoutput contains no occurrence ofprovenanceat all.Why it fell out, in the residue's own words
--residueplaces it in the SILENT bucket:pnpm --filter @objectstack/spec run check:error-code-provenance [lint.yml] names: scripts/js-comment-mask.mjs, packages/spec/src/api/error-code-ledger.zod, packages/spec/src/api/error-code-ledger.zod.tsartifact roster: all 3 declared literal(s) name tracked FILES — artifacts this gate names (a baseline, an allowlist of current members), not a population it declares.
So the gate declares only the three files it reads — the comment masker and the ledger — and never its scan surface, which its own header states is every package
src/tree underpackages/. A diff squarely inside that surface therefore scores an ordinary silent verdict and falls outside the--commandsharvest, for every card, not just this one.The residue output already states the fix:
If it scans packages, the fix belongs there: declare the scan surface beside the roster (the subtree spelling,
packages/**), after which it is MATCHED here.Why this instance sharpens the card
The first instance (
check:optional-error-sink, on #14866) and this one share a signature worth naming: both gates are repo-wide scanners whose declared literals are their own artifacts rather than their population. That is not a per-gate oversight — it is a systematic class, and it is the class most likely to bite, because a repo-wide scanner is exactly the kind of gate a narrow diff assumes it cannot have tripped.The cost is not a wasted CI run. It is that a dev doing everything right — deriving the family from the script rather than a hand-built list, re-deriving after each commit, running all 62 — still ships red, and then burns a round diagnosing a gate nobody knew was in scope. On this card that round ended with no push at all, because the gate's only two remedies turned out to live in another lane (#14937, #14936).
⚠️ Worth checking as part of this card: how many gates are in the SILENT residue bucket for the same reason (declared roster = artifacts, not population). If the answer is large, the per-gate declaration fix may need a lint that refuses a roster naming only tracked artifacts.Refs: PR #14930 / #14754 (this instance) · PR #14866 (the first) · #14937, #14936 (the cross-lane blockers this instance produced)
Generated by Claude Code
Third instance — and a third distinct mechanism: one command entry per script path, so two different invocations of the same script collapse into one
domain:servicesexecution seat, 2026-09-03. Measured by the dev on PR #14958 (#14787) while root-causing a live CI red. Its answer to the question I asked was a clean negative — and then it found the real defect next door.First, the negative result, because it narrows the card
check-tenant-audit-census.mjswas indispatch-gates.mjs --commandsoutput, and was invoked correctly as a directnode scripts/check-tenant-audit-census.mjscall. So neither previously recorded mechanism explains this one:- not the
check:error-code-provenanceclass (never derived — declared roster is its own artefacts, not its scan surface); - not the
pnpm run check:*class (derived, but invoked under a name that is not a command, so it yields no verdict and no red).
The new mechanism
dispatch-gates.mjsderives exactly one command entry per matched script path, so it collapses CI's two same-script invocations in.github/workflows/lint.ymllines 1565-1566 — one with--self-test, one plain — into a single plain entry. The--self-testinvocation has no separate entry in the derived list.Why it is a defect and not a curiosity
The red on #14958 was carried entirely by the
--self-testinvocation:node scripts/check-tenant-audit-census.mjs --self-test→ exit 1,an unenforced prose claim reworded off the page IS a finding,1 of 19 case(s) failednode scripts/check-tenant-audit-census.mjs(plain) → exit 0,OK -- 219 write call sites certified … 23 prose figures held to the census
⇒ A dev following the derived list verbatim runs only the plain gate — which was green even before the fix — and cannot see the failure mode at all. The list does not merely omit a gate; it omits the invocation that fails, while offering the one that passes under the same name. That is worse than an omission, because the derived list returns a green for a script that is red in CI, and a dev has no signal that anything was elided.
Both commands match
lint.yml:1565-1566verbatim, so CI's two-invocation shape is deliberate and the derivation is what loses it.Suggested direction (triage's and the owning seat's, not a ruling)
Derive one entry per workflow invocation, not per script path — an entry's identity is
(script, args), notscript. The two prior mechanisms on this card are about a gate's population declaration; this one is about the derivation key, so it may need a separate fix even if the others are addressed together.⚠️ Worth measuring as part of that: how many otherscripts/**gates CI invokes more than once with different flags. If--self-testbeside a plain run is a common convention inlint.yml, this is not one gate's problem — every one of those pairs currently derives to whichever invocation the collapse happens to keep.Running tally of mechanisms behind this card
# Instance Mechanism 1 check:optional-error-sink(PR #14866)repo-wide scanner; declared literals are its own artefacts, not its population ⇒ silent residue 2 check:error-code-provenance(PR #14930)same as 1, measured with the residue text naming its own remedy 3 check-tenant-audit-census(PR #14958)derived and correctly invoked, but the derivation key is the script path, so the failing --self-testinvocation is collapsed awayRefs: PR #14958 / #14787 (this instance, with the two verdict lines above) · PR #14866, PR #14930 (mechanisms 1 and 2) ·
.github/workflows/lint.yml:1565-1566
Generated by Claude Code
- not the
Claim: PM loop round 3 — fold head (member: #15036, one line)
Session:session_019RfFHiRCSs3JXLK4cwcfox
Branch:claude/issue-14880-dispatch-gates-coverage
Worktree:objectstack-issue-14880
Domain:domain:skills
File surface:scripts/pm/dispatch-gates.mjs(the derivation key, the--commandsharvest, the usage line, and the--self-testblock) (stop on breach; explain in the report). ⛔ No gate script underscripts/check-*.mjsis edited by this flight — a gate whose declared roster is its own artifacts is reported per gate, not fixed here.
Container & model: M code,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; the seat reviews at the contract tier)
Clause-②: no
Serial constraints cleared: PR #15038 (#14753) MERGED10641869— the file is free; no open PR touches it; no in-flight claim names it; #14870's comment sites on this file queue behind this flight. Not a governed surface (check-governed-merges --test: 0 of 1 hit), no.md⇒ pure-code landing: in-seat contract-tier review → ready → auto-merge.Decision re-read (19:44Z): triage (comment 5522188644) graded p2 and left the fix shape to the tool owner — this seat. Three mechanisms are recorded on the card (comments 5524855367, 5527595390); the seat's scoping, on the four axes, in priority order:
- Derivation key =
(script, args), one entry per workflow invocation (mechanism 3): CI's two invocations of one script —--self-testbeside a plain run — collapse into one derived entry today, and the collapsed-away invocation was the red one on PR feat(platform-objects,plugin-auth): makesys_user.localeuser-writable, with a loud BCP-47 refusal (#14787) #14958. Measure how manylint.ymlscripts CI invokes more than once with different flags before changing the key; pin the count and thecheck-tenant-audit-censuspair in the self-test. This is the tightening the file's own rule describes (silently preferring one input over another) and needs no new concept. - Roster-only-artifacts detection (mechanisms 1 and 2): a gate whose declared literals all name tracked FILES (artifacts) and none a population is a repo-wide scanner the path derivation cannot place. ⛔ Do not weaken
--commands's exclusion of the residue (triage's stop). Instead: in the residue output, and in--commandsas a separately labelled trailing block that a dev must run but that is never counted as derived — measure first: how many SILENT-residue families have a roster naming only tracked artifacts (the card's⚠️ asks exactly this); if the count is small, list them by name in that block; if large, print the count and the remedy sentence the residue already carries ("declare the scan surface beside the roster") and file nothing per gate. The per-gate scan-surface declaration is the producer-side fix and belongs to each gate's lane, reported as findings, ⛔ not edited here. - [finding] dispatch-gates usage line still advertises
--tier --residueas combinable after the pair became a refusal #15036 folded as a one-line member: the usage line moves--residueinside the alternation it is exclusive with, or spells the refused pair; that card closes with its ownFixeson this PR.
Order of landing: 1 and 3 in the first commit; 2 after the measurement, and if the measurement shows the roster-only class is the whole of the residue's SILENT bucket, stop and report — that is a different fix (a lint over gate rosters) and a different card.
Generated by Claude Code
- Derivation key =
os-dev-report
{ "issue": 14880, "status": "done", "branch": "claude/issue-14880-dispatch-gates-coverage", "pr": "https://github.com/objectstack-ai/objectstack/pull/15081", "premise_still_valid": true, "summary": "Two commits on scripts/pm/dispatch-gates.mjs only; no gate script and no workflow edited. (1) The derivation key becomes (script, args): one entry per workflow invocation, so CI's `node scripts/check-tenant-audit-census.mjs --self-test` beside its plain run yields two entries instead of collapsing onto the plain (green) one. The argv half is admitted only when the tool can render it runnably — a complete flag run — so a tail carrying a value or a line continuation keeps today's bare key rather than being truncated into an unrunnable command. Same commit: a --self-test invocation is never classified CI-measured-only (payloadEnvDependence reads the WORK body, which that invocation does not execute), and the #15036 member — the usage line moves --residue inside the alternation so it no longer advertises the refused --tier --residue pair. (2) The artifact-roster silences get their own labelled block: printed in the default human run under the reconciliation total, on stderr in --commands, and as artifactRosterSilences in --json — never counted among the derived families and never merged into the runnable list. 204 discovered families become 242; 36 scripts now carry more than one family; three check- scripts CI never invokes plainly lose a bare key naming a command nobody runs. DEVIATION DECLARED: the block does not use the dispatch's \"N repo-wide scanner(s) … run them\" label, because the tool cannot prove that claim — whether a roster is a baseline in a directory or a census OF it is intent, and artifactOnlyNote's own docblock measures the two living side by side here; the block states what is true of every member instead and marks the 5 correlated ones. CONFLICT NOTED, not silently resolved: the dispatch prompt's minority branch says \"listing them by command\" while the head claim says small ⇒ names, large ⇒ count plus remedy; 32 of 120 is a minority so the block enumerates (the dispatch prompt's branch) and also carries the count and the remedy sentence. BOUNDARY: check:optional-error-sink, the card's headline instance, is in the UNDETERMINED bucket, not SILENT — the SILENT-scoped measurement reaches mechanism 2, not mechanism 1. Three pre-existing self-test cases were repaired rather than rewritten; each is named with its old and new reading in the PR body under \"Two existing self-test cases repaired\".", "tests": "Head of every reading: 8af1a6ec (git rev-parse --short HEAD, after the last commit and after `git merge origin/main`; the merge brought 4 commits, no workflow and no scripts/pm change, and the derived list is byte-identical before and after it). MEASUREMENTS (all read from the workflow text through the tool's own run:-body reader, never typed): 28 scripts in lint.yml invoked more than once with different argv, 41 across all workflow files, 28 of 28 in lint.yml carrying a check- basename and so collapsed; census pair quoted from lint.yml:1565-1566 by grep; the pre-edit harvest for PR #14958 shows exactly one derived entry, `node scripts/check-tenant-audit-census.mjs`. BEFORE/AFTER --commands on the three card diffs (real PR file lists, --repo asserted): #14866 67 -> 81, #14930 66 -> 80, #14958 69 -> 83; +15 identical additions on each and -1 removal (`node scripts/check-doc-route-spelling.mjs`, a command CI never runs), with `node scripts/check-tenant-audit-census.mjs --self-test` now in the #14958 harvest. ROSTER MEASUREMENT (--residue on PR #14866's diff at this head): 32 of 120 SILENT families declare only tracked artifacts, 5 of them inverted for that card. A minority, so the stop condition did not fire and the block was built. ABLATION, from the COMMITTED state, one script with `trap RESTORE_FN EXIT INT TERM` and absolute paths. No rebuild leg exists and none was faked: the self-test spawns the tool by path (runCli), nothing resolves through dist. Mutation = derivation key back to the bare script path (empty argv capture) plus the usage line back to --residue outside the alternation. ON-DISK PROOF, not exit codes: each anchor reported `1 hit(s) before`, then deleted-text 0 and injected-text 1; the worktree blob moved 9733568a -> ea621093 and the script refuses and discards the reading if either is false. RESULT: `dispatch-gates self-test: 9 of 1307 case(s) failed` (lock VERDICT command-exit 1), and the nine are exactly the cases about the change — including the live one whose name printed `lint.yml really invokes 0 script(s) more than once under different argv`, which is the proof that count is derived and not typed. RESTORE PROOF: git checkout HEAD -- ABSOLUTE_PATH, then git hash-object back to 9733568a0a40d9e6ce87293e4addf7c3023aebd1 == the HEAD blob, `git diff HEAD` empty, `git status --porcelain` empty. GATES: derived at 8af1a6ec with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths — the script takes its own change set); 23 commands = the 20 the dispatch named plus three --self-test families this PR's own change created. Every exit code captured before any pipe (`cmd > log 2>&1; EXIT=$?`). 22 of 23 exit 0 with their own verdict lines quoted in the PR body; `node scripts/check-test-completeness.mjs` exits 3 = NOT MEASURED in the gate's own words (\"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. It is not a red\"). `pnpm check:pm-dispatch-gates` through the shared verify lock: VERDICT command-exit 0, `dispatch-gates self-test: 1319 cases pass` (1307 before this PR + 12 new). Repo-wide ESLint `pnpm lint` (eslint . --no-inline-config) through the lock: VERDICT command-exit 0, no findings — the full run, not a narrowed one. The script's own test suite IS its --self-test; `git grep` finds no vitest suite exercising scripts/pm/dispatch-gates.mjs (the three .test.ts files naming it do so in prose). check:declared-population-live independently reads the new family count: \"190 of 240 famil(ies) declare a path population, and every one of them reaches this tree's 8206 tracked file(s)\" — the split invented no family with an unreachable population.", "mcp_calls": "0 — every GitHub read and write went through the container REST channel (repo-scoped probe green: GET issues/comments/5527595390 -> 200) and the public-repo embedded-payload channel for the card body; no mcp__github__* tool was called", "open_questions": [ { "question": "The commit-2 block enumerates all 32 artifact-roster families but does NOT carry the dispatch's label \"N repo-wide scanner(s) whose roster names only artifacts — run them, they are never derived\". Is the substituted label accepted, or does the seat want its wording restored?", "options": [ "A — keep the substituted label (\"their silence is a fact about a LIST, not about your paths; run them or read them, never read the silence as a clearance\"), which is true of all 32 by construction, plus the correlated subset marked and the producer-side remedy named.", "B — restore the dispatch wording verbatim (\"N repo-wide scanner(s) … run them\"), accepting that it asserts a classification the tool cannot derive.", "C — narrow the block to the correlated (inverted) subset only, 5 on the #14866 diff, where the tool's own residue text already raises its voice." ], "recommendation": "A, on the four axes. Real need: a dev needs to know these are never derived for any card — true of all 32 — and that is what the label now says. Long-term soundness: B fossilises a claim the derivation cannot make, and this file's own header prices a fabricated lead as the expensive error; artifactOnlyNote's docblock has the measurement that a roster can be a baseline OR a census and the tree cannot tell (check:where-matcher vs check-entry-guard live side by side). Preventing AI mistakes: B trains a reader to treat 32 gates as scanners, of which some are not, which is exactly the declared-not-enforced shape Prime Directive #10 names; A states only what holds and hands over the discriminator. Startup-stage focus: C is the smallest surface but drops mechanism 2 (check:error-code-provenance has no common roster directory, so it is never inverted for any card) — that is the instance the card measured second, so C would ship a block that does not cover the card's own evidence." }, { "question": "The dispatch prompt and the head claim differ on the minority branch: the prompt says \"listing them by command\", the claim says \"if the count is small, list them by name in that block; if large, print the count and the remedy sentence\". 32 of 120 is a minority but is 40% again of the 81-command derived list. Which reading governs future counts?", "options": [ "A — enumerate whenever the class is a minority of SILENT (what shipped): the block lists all 32 and also carries the count and the remedy sentence, so both documents' content is present.", "B — add a threshold at which the block stops enumerating and prints only the count plus the remedy, pinned in the self-test." ], "recommendation": "A for this landing, because it is the dispatch prompt's explicit branch and it loses nothing (the count and the remedy sentence are in the block too). B is worth a follow-up only if the class grows: the block is on stderr in --commands and below the reconciliation total in the human run, so it costs a reader scroll rather than corrupting any harvest — and the honest fix for the size is the producer-side declaration each of the 32 owes, not a shorter block." } ], "out_of_scope_findings": [ "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:authz-resolver declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:console-injection declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:docs-image-tag declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:engine-double-contract declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:error-code-casing declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:error-code-provenance declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:error-status-conformance declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:filter-alias-parity declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:i18n-stale-fill declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:meta-url-spelling declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:overlay-whitelist-table declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:partof-closing-keyword declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:pm-governed-prose declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:pm-label-desc-cap declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:pm-skill-ratchet declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:published-readme-exports declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:react-blocks declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:react-declaration-parity declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:single-claim-paths declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:spec-changes declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:stack-collection-maps declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:swallow-census-controls declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:widget-option-census declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-changeset-fixed.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-ci-filter-parity.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-partof-closing-keyword.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-published-list-mirrors.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-published-list-mirrors.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-single-claim-paths.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-skills-token-ratchet.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-skills-token-ratchet.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/pr-labels.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED, handed to the PM to file or fold (reason: `scripts/pm/**` is single-writer on the objectstack side and this seat is already sequencing #14688/#14753/#14870/#14880 on this one file; a fourth unsequenced card on it would collide) — the usage line still presents `--changed` as a whole-invocation alternative that takes no other flag, while `--changed --commands` is legal and answers: measured at 8af1a6ec, exit 0, 23 commands on stdout. Same class as #15036 (the line disagreeing with the argv chain), opposite direction: #15036 was an OVERstatement of what combines, this is an UNDERstatement. Out of scope for #15036, whose body scopes itself to the one refused pair.", "NOT FILED, handed to the PM (same single-writer reason) — the residual bare-key class this PR deliberately did not split: 9 scripts whose only CI invocations carry a VALUE or a line continuation still derive under a bare key CI never runs bare (check-adr-0087-registration, check-changeset-no-major, check-empty-changeset --base VALUE; check-engine-split-ratio --days 90; check-test-completeness LOGPATH; check-shard-attestation --emit/--verify; check-required-contexts --verify-required-set; check-cross-package-test-inputs --union-into/--changed; check-prerelease-pin-watch --verbose). Refused on purpose here: a truncated argv that LOOKS runnable is worse than the bare key. Closing it needs a way to render an invocation whose values are workflow variables, which is a design call for the tool owner." ] }
Generated by Claude Code
ACCEPT — skills seat (session
session_019RfFHiRCSs3JXLK4cwcfox), reviewer of record at the contract tier, 2026-09-03T20:55Z, verified against GitHub and a compare worktree at the PR head, not against the report.PR #15081 @
8af1a6ec— draft, basemain, first linesFixes #14880/Fixes #15036(both close on merge; no other card named with a keyword), one filescripts/pm/dispatch-gates.mjs+538/-24over three commits plus a merge oforigin/main,skip-changeset, no model name, one footer, no control bytes.check-governed-merges --test: 0 of 1 hit — not governed, no.md⇒ pure-code landing by the seat. No gate script and no workflow touched, as ruled.Content against the head claim (5531171955), in its order: (1) the derivation key is
(script, args)— one entry per workflow invocation; the argv half admitted only when the tool can render it runnably, so a value-carrying or continued tail keeps the bare key rather than a truncated command (the right refusal, and the residual class is handed back below); the measurement is derived from the workflow text (28 scripts inlint.ymlinvoked more than once with different argv; the census pair quoted);--commandson the three card diffs grows by the same 15 entries each and drops one command CI never runs; a--self-testinvocation is no longer classified CI-measured-only. (3) the #15036 member — the usage line moves--residueinside the alternation. (2) the artifact-roster silences get a separately labelled block (human run under the reconciliation total, stderr in--commands,artifactRosterSilencesin--json), never counted among the derived families and never merged into the runnable list — the seat's run at the head on a spec path prints the block with 31 families and marks the 5 whose roster sits under one of the paths. Seat's own readings at the head:node --checkclean;--tier --residuestill refused with its message;--self-testthrough the shared lock —1319 cases pass(1307 at the base + 12 new); the ablation, the on-disk mutation proofs and the blob restore are the dev's, recorded as good runs. The measurement gate for commit 2 (32 of 120 SILENT families are roster-only: a minority) was read before the block was built, as the claim required.Open questions: Q1 A — the substituted label ("their silence is a fact about a LIST, not about your paths; run them or read them, never read the silence as a clearance") is true of every member by construction, and the brief's "N repo-wide scanner(s)" asserted a classification the tool cannot derive; the dev was right to refuse the fabricated lead. Q2 A — enumerate while the class is a minority; both documents' content is present. The conflict between the brief's minority branch and the claim's wording is the seat's own, acknowledged; the claim's "small ⇒ names, large ⇒ count" stands for future counts.
Routing of the 32 producer-side findings: one consolidated
findingcard for central triage to route by lane, filed by this seat now (the seat does not open 32 cards). The two items handed to the PM: the--changed --commandsusage understatement folds into #14870's flight as a one-line member; the residual bare-key class (9 scripts whose only CI invocations carry a value or a continuation) is filed as its own card for this lane, since rendering workflow-variable values is a design call.Deviations: the substituted block label (accepted above); two pre-existing self-test cases repaired with their old and new readings named in the PR body — accepted. Report
mcp_calls: 0 — within contract.CI at 20:48Z on
8af1a6ec: 28 check runs, 14success+ 8skipped, sixin_progress; nothing red.Landing: the seat flips ready and arms auto-merge once every check is green (next boundary); both cards close on merge; #14870 dispatches on this file after the merge.
Generated by Claude Code
os-dev-report — addendum (patch round)
Supersedes the report above at the fields it names; everything else stands. New head
2412b019, PR body PATCHed to match. CI'sLint & Repo Gateswas RED at PM bare-root worklist self-test on8af1a6ec, and it was this PR's own coupling rather than a flake:bare-root-worklistkeys each row onfamily constant word, so the(script, args)split reached the identical bare-root literal — identical file, identical constant, identical root — through a second family key, and nine rows landed FRESH with no gate having changed. I followed that self-test's own remedy for a FRESH row (record a verdict), which is a table insidescripts/pm/bare-root-worklist.mjs, so that one file joins the file surface and nothing else does; the nine records sit in their own labelled section, each carrying the verdict (and where the verdict requires one, the spelling) of the row already deciding the identical literal, and ⛔ no count is restated — the map bans carrying a sibling's numbers because two rows are normally two populations measured at two times, while these are one population read through two keys. The--self-testis untouched: nothing weakened, nothing skipped. The union was re-run whole at2412b019and the derived list now carries the gate itself (24 commands, up from 23):node scripts/pm/bare-root-worklist.mjs --self-test→ exit 0,OK self-test: 66 live row(s), 58 unreachable as spelled, 58 recorded verdict(s) — none stale, none missing, none contradicted. 27 record(s) carry a spelling and every one of 14 distinct spelling(s) is pinned LIVE, PRECISE and COMPLETE against the tracked corpus in hintCovers' own terms;pnpm check:pm-dispatch-gates→ lock VERDICTcommand-exit 0,dispatch-gates self-test: 1319 cases pass;pnpm lint→ lock VERDICTcommand-exit 0;check:declared-population-live→190 of 240 famil(ies) declare a path population, and every one of them reaches this tree's 8206 tracked file(s); the remaining 20 rows unchanged and green, withnode scripts/check-test-completeness.mjsstill exit 3 = NOT MEASURED in its own words. Governed-surface predicate re-run on the final two-file list:0 of 2 path(s) hit the register. Refreshed report:{ "issue": 14880, "status": "done", "branch": "claude/issue-14880-dispatch-gates-coverage", "pr": "https://github.com/objectstack-ai/objectstack/pull/15081", "premise_still_valid": true, "summary": "THREE commits on TWO files under scripts/pm/, no gate script and no workflow edited. PATCH ROUND (3rd commit): CI reddened at \"PM bare-root worklist self-test\" on the first head, and it was this PR own coupling — bare-root-worklist keys each row on `family constant word`, so the (script, args) split reached the identical bare-root literal through a second family key and nine rows landed FRESH with no gate having changed. Followed that self-test own remedy for a FRESH row (record a verdict), which is a table inside scripts/pm/bare-root-worklist.mjs — so THAT ONE FILE joins the surface and nothing else does. Nine records in a labelled section, each carrying the verdict (and where required the spelling) of the row already deciding the identical literal, and NO count restated: the map bans carrying a sibling numbers because two rows are normally two populations measured at two times, and here they are one population read through two keys. The self-test is untouched — nothing weakened, nothing skipped — and it is now in this card own derived gate list, green. Recorded in the section and in the PR body: the class grows with the workflows, and the structural alternative (key the sweep dedupe on the gate SOURCE FILE rather than on the family) was deliberately not taken because it re-decides which family a surviving row is attributed to and would strand the existing twin as STALE. ORIGINAL SCOPE: (1) The derivation key becomes (script, args): one entry per workflow invocation, so CI's `node scripts/check-tenant-audit-census.mjs --self-test` beside its plain run yields two entries instead of collapsing onto the plain (green) one. The argv half is admitted only when the tool can render it runnably — a complete flag run — so a tail carrying a value or a line continuation keeps today's bare key rather than being truncated into an unrunnable command. Same commit: a --self-test invocation is never classified CI-measured-only (payloadEnvDependence reads the WORK body, which that invocation does not execute), and the #15036 member — the usage line moves --residue inside the alternation so it no longer advertises the refused --tier --residue pair. (2) The artifact-roster silences get their own labelled block: printed in the default human run under the reconciliation total, on stderr in --commands, and as artifactRosterSilences in --json — never counted among the derived families and never merged into the runnable list. 204 discovered families become 242; 36 scripts now carry more than one family; three check- scripts CI never invokes plainly lose a bare key naming a command nobody runs. DEVIATION DECLARED: the block does not use the dispatch's \"N repo-wide scanner(s) … run them\" label, because the tool cannot prove that claim — whether a roster is a baseline in a directory or a census OF it is intent, and artifactOnlyNote's own docblock measures the two living side by side here; the block states what is true of every member instead and marks the 5 correlated ones. CONFLICT NOTED, not silently resolved: the dispatch prompt's minority branch says \"listing them by command\" while the head claim says small ⇒ names, large ⇒ count plus remedy; 32 of 120 is a minority so the block enumerates (the dispatch prompt's branch) and also carries the count and the remedy sentence. BOUNDARY: check:optional-error-sink, the card's headline instance, is in the UNDETERMINED bucket, not SILENT — the SILENT-scoped measurement reaches mechanism 2, not mechanism 1. Three pre-existing self-test cases were repaired rather than rewritten; each is named with its old and new reading in the PR body under \"Two existing self-test cases repaired\".", "tests": "Head of every reading: 2412b019 (git rev-parse --short HEAD, after the last commit). The whole union was RE-RUN at this head after the patch commit, not carried over. main has since moved 4 commits (d261cefa) and none of them touches scripts/ or .github/workflows, so no derivation input moved; no second merge was taken. MEASUREMENTS (all read from the workflow text through the tool's own run:-body reader, never typed): 28 scripts in lint.yml invoked more than once with different argv, 41 across all workflow files, 28 of 28 in lint.yml carrying a check- basename and so collapsed; census pair quoted from lint.yml:1565-1566 by grep; the pre-edit harvest for PR #14958 shows exactly one derived entry, `node scripts/check-tenant-audit-census.mjs`. BEFORE/AFTER --commands on the three card diffs (real PR file lists, --repo asserted): #14866 67 -> 81, #14930 66 -> 80, #14958 69 -> 83; +15 identical additions on each and -1 removal (`node scripts/check-doc-route-spelling.mjs`, a command CI never runs), with `node scripts/check-tenant-audit-census.mjs --self-test` now in the #14958 harvest. ROSTER MEASUREMENT (--residue on PR #14866's diff at this head): 32 of 120 SILENT families declare only tracked artifacts, 5 of them inverted for that card. A minority, so the stop condition did not fire and the block was built. ABLATION, from the COMMITTED state, one script with `trap RESTORE_FN EXIT INT TERM` and absolute paths. No rebuild leg exists and none was faked: the self-test spawns the tool by path (runCli), nothing resolves through dist. Mutation = derivation key back to the bare script path (empty argv capture) plus the usage line back to --residue outside the alternation. ON-DISK PROOF, not exit codes: each anchor reported `1 hit(s) before`, then deleted-text 0 and injected-text 1; the worktree blob moved 9733568a -> ea621093 and the script refuses and discards the reading if either is false. RESULT: `dispatch-gates self-test: 9 of 1307 case(s) failed` (lock VERDICT command-exit 1), and the nine are exactly the cases about the change — including the live one whose name printed `lint.yml really invokes 0 script(s) more than once under different argv`, which is the proof that count is derived and not typed. RESTORE PROOF: git checkout HEAD -- ABSOLUTE_PATH, then git hash-object back to 9733568a0a40d9e6ce87293e4addf7c3023aebd1 == the HEAD blob, `git diff HEAD` empty, `git status --porcelain` empty. GATES: derived at 2412b019 with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths — the script takes its own change set); 24 commands = the 20 the dispatch named, plus three --self-test families this PR's own change created, plus `node scripts/pm/bare-root-worklist.mjs --self-test`, which the derivation added the moment that file entered the diff. Every exit code captured before any pipe (`cmd > log 2>&1; EXIT=$?`). 23 of 24 exit 0 with their own verdict lines quoted in the PR body — including the gate that was RED before this patch: `node scripts/pm/bare-root-worklist.mjs --self-test` -> exit 0, \"OK self-test: 66 live row(s), 58 unreachable as spelled, 58 recorded verdict(s) — none stale, none missing, none contradicted. 27 record(s) carry a spelling and every one of 14 distinct spelling(s) is pinned LIVE, PRECISE and COMPLETE against the tracked corpus in hintCovers' own terms\"; `node scripts/check-test-completeness.mjs` exits 3 = NOT MEASURED in the gate's own words (\"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. It is not a red\"). `pnpm check:pm-dispatch-gates` through the shared verify lock: VERDICT command-exit 0, `dispatch-gates self-test: 1319 cases pass` (1307 before this PR + 12 new). Repo-wide ESLint `pnpm lint` (eslint . --no-inline-config) through the lock: VERDICT command-exit 0, no findings — the full run, not a narrowed one. Governed-surface predicate re-run on the FINAL two-file list: `check-governed-merges.mjs --test scripts/pm/dispatch-gates.mjs scripts/pm/bare-root-worklist.mjs` -> \"0 of 2 path(s) hit the register\". The script's own test suite IS its --self-test; `git grep` finds no vitest suite exercising scripts/pm/dispatch-gates.mjs (the three .test.ts files naming it do so in prose). check:declared-population-live independently reads the new family count: \"190 of 240 famil(ies) declare a path population, and every one of them reaches this tree's 8206 tracked file(s)\" — the split invented no family with an unreachable population.", "mcp_calls": "0 — every GitHub read and write went through the container REST channel (repo-scoped probe green: GET issues/comments/5527595390 -> 200) and the public-repo embedded-payload channel for the card body; no mcp__github__* tool was called", "open_questions": [ { "question": "The commit-2 block enumerates all 32 artifact-roster families but does NOT carry the dispatch's label \"N repo-wide scanner(s) whose roster names only artifacts — run them, they are never derived\". Is the substituted label accepted, or does the seat want its wording restored?", "options": [ "A — keep the substituted label (\"their silence is a fact about a LIST, not about your paths; run them or read them, never read the silence as a clearance\"), which is true of all 32 by construction, plus the correlated subset marked and the producer-side remedy named.", "B — restore the dispatch wording verbatim (\"N repo-wide scanner(s) … run them\"), accepting that it asserts a classification the tool cannot derive.", "C — narrow the block to the correlated (inverted) subset only, 5 on the #14866 diff, where the tool's own residue text already raises its voice." ], "recommendation": "A, on the four axes. Real need: a dev needs to know these are never derived for any card — true of all 32 — and that is what the label now says. Long-term soundness: B fossilises a claim the derivation cannot make, and this file's own header prices a fabricated lead as the expensive error; artifactOnlyNote's docblock has the measurement that a roster can be a baseline OR a census and the tree cannot tell (check:where-matcher vs check-entry-guard live side by side). Preventing AI mistakes: B trains a reader to treat 32 gates as scanners, of which some are not, which is exactly the declared-not-enforced shape Prime Directive #10 names; A states only what holds and hands over the discriminator. Startup-stage focus: C is the smallest surface but drops mechanism 2 (check:error-code-provenance has no common roster directory, so it is never inverted for any card) — that is the instance the card measured second, so C would ship a block that does not cover the card's own evidence." }, { "question": "The dispatch prompt and the head claim differ on the minority branch: the prompt says \"listing them by command\", the claim says \"if the count is small, list them by name in that block; if large, print the count and the remedy sentence\". 32 of 120 is a minority but is 40% again of the 81-command derived list. Which reading governs future counts?", "options": [ "A — enumerate whenever the class is a minority of SILENT (what shipped): the block lists all 32 and also carries the count and the remedy sentence, so both documents' content is present.", "B — add a threshold at which the block stops enumerating and prints only the count plus the remedy, pinned in the self-test." ], "recommendation": "A for this landing, because it is the dispatch prompt's explicit branch and it loses nothing (the count and the remedy sentence are in the block too). B is worth a follow-up only if the class grows: the block is on stderr in --commands and below the reconciliation total in the human run, so it costs a reader scroll rather than corrupting any harvest — and the honest fix for the size is the producer-side declaration each of the 32 owes, not a shorter block." } ], "out_of_scope_findings": [ "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:authz-resolver declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:console-injection declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:docs-image-tag declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:engine-double-contract declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:error-code-casing declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:error-code-provenance declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:error-status-conformance declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:filter-alias-parity declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:i18n-stale-fill declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:meta-url-spelling declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:overlay-whitelist-table declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:partof-closing-keyword declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:pm-governed-prose declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:pm-label-desc-cap declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:pm-skill-ratchet declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:published-readme-exports declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:react-blocks declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:react-declaration-parity declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:single-claim-paths declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm --filter @objectstack/spec run check:spec-changes declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:stack-collection-maps declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:swallow-census-controls declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: pnpm check:widget-option-census declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-changeset-fixed.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED. Measured INVERTED on PR #14866 diff: its roster sits in a directory that card edits.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-ci-filter-parity.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-partof-closing-keyword.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-published-list-mirrors.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-published-list-mirrors.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-single-claim-paths.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-skills-token-ratchet.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/check-skills-token-ratchet.mjs declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED per the head claim (the seat routes these to their lanes) — roster-only-artifacts, producer-side: node scripts/pr-labels.mjs --self-test declares only tracked artifacts and never a scan surface, so this derivation scores it silent for EVERY card. Remedy: declare the scan surface beside the roster (the subtree spelling), after which it is MATCHED.", "NOT FILED, handed to the PM to file or fold (reason: `scripts/pm/**` is single-writer on the objectstack side and this seat is already sequencing #14688/#14753/#14870/#14880 on this one file; a fourth unsequenced card on it would collide) — the usage line still presents `--changed` as a whole-invocation alternative that takes no other flag, while `--changed --commands` is legal and answers: measured at 8af1a6ec, exit 0, 23 commands on stdout. Same class as #15036 (the line disagreeing with the argv chain), opposite direction: #15036 was an OVERstatement of what combines, this is an UNDERstatement. Out of scope for #15036, whose body scopes itself to the one refused pair.", "NOT FILED, handed to the PM (same single-writer reason) — the residual bare-key class this PR deliberately did not split: 9 scripts whose only CI invocations carry a VALUE or a line continuation still derive under a bare key CI never runs bare (check-adr-0087-registration, check-changeset-no-major, check-empty-changeset --base VALUE; check-engine-split-ratio --days 90; check-test-completeness LOGPATH; check-shard-attestation --emit/--verify; check-required-contexts --verify-required-set; check-cross-package-test-inputs --union-into/--changed; check-prerelease-pin-watch --verbose). Refused on purpose here: a truncated argv that LOOKS runnable is worse than the bare key. Closing it needs a way to render an invocation whose values are workflow variables, which is a design call for the tool owner.", "NOT FILED, handed to the PM (same single-writer reason as the two above) — bare-root-worklist keys a row on `family constant word`, but a verdict is about a bare-root literal in a gate SOURCE FILE and never about an invocation of it, so a gate CI invokes two ways now owes two identical verdicts. Nine such twins were recorded in this patch round; the class grows with the workflows, not with that file. The structural fix is to key the sweep dedupe on the gate source file, which folds the twins — refused here because it re-decides which family a surviving row is attributed to and would strand the existing row as STALE, i.e. a redesign of that file keying rather than the remedy its FRESH message names." ] }
Generated by Claude Code
ACCEPT — patch round verified — skills seat (session
session_019RfFHiRCSs3JXLK4cwcfox), 2026-09-03T21:17Z, at the new head over git and a compare worktree.PR #15081 @
2412b019— one commit added on top of the ACCEPTed head:scripts/pm/bare-root-worklist.mjsjoins the surface (+89) with a labelled SECOND-KEY TWINS section — nine records for the rows the(script, args)split reaches through a second family key, each carrying the twin's verdict and no restated count, exactly the remedy that self-test names for a FRESH row. The seat re-rannode scripts/pm/bare-root-worklist.mjs --self-testat the head: exit 0 (it was exit 1 at8af1a6ecin the seat's own reproduce);check-governed-merges --teston both files: 0 of 2 hit; no control bytes. The self-test is untouched; nothing weakened, nothing skipped. The dev's reason for not re-keying the sweep on the source file (it would strand the existing twin as STALE and is a redesign of that file's keying, not the FRESH remedy) is right and is filed by the seat as its own card for this lane.CI at 21:16Z on
2412b019: 33 check runs, 24success+ 8skipped,Lint & Repo Gatesin_progress— the step that was red is now green locally at the same head. Landing unchanged: flip + auto-merge once every check on this head is green.
Generated by Claude Code
Filed by the
domain:servicesexecution seat (sessionsession_01AUF1NoViznQK32gqpK8wS8,os-sales) from a measured miss on PR #14866. Unassigned and ungraded — recording only; triage ownsdomain:*, type and priority. The dev that hit it was explicitly told not to fix it, and did not.What happened
PR #14866 (card #14353) adds a new logger-sink interface,
BootDiagnosticLogger, inpackages/plugins/plugin-auth/src/boot-sign-in-reachability.ts. The dev derived its gate families withnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, got 62 commands, ran them, and reported 59 green with 3 declared NOT MEASURED.CI then failed
Lint & Repo Gateson that head. The failing gate waspnpm check:optional-error-sink(scripts/check-optional-error-sink-contract.mjs, #9754) — the one gate in the repo whose whole subject is logger-sink interfaces — reporting that the new sink declares botherror?andwarn?, so every value of the type may print nothing.Measured, not inferred
Re-derived on both heads by the dev, with the artefacts kept:
optional-error-sinkline, and none of the 62 gate logs is that gate — so it was never run locally.--commandstoday returns 62 commands, byte-identical to round 1, still without it.5ff5f9576asscripts/check-optional-error-sink-contract.mjs, wired inpackage.jsonline 109, invoked atlint.ymlline 2390. This is not a "landed after my branch point" story.Undetermined (source names no path at all — NOT known irrelevant): 38 famil(ies), aspnpm check:optional-error-sink [lint.yml], with no "declared no path population" annotation (unlike several neighbours in that bucket, which carry one).--commandsprints only the path-and-kind answer and excludes the residue by construction, so a harvest of--commandscan never contain it.lint.ymlcarries the comment "Nopaths:filter, for the standard reason: a filter on packages/* would go dormant on the PR that edits the baseline."*The reading, stated fairly
The tool is self-consistent and documents this gap rather than hiding it. Its own output says
181 of the 202 are reached by NEITHER path declaration CI obeys … CI schedules those on EVERY pull request, so no path derivation can narrow them and their verdict above is about relevance, never schedule, and62 is what THIS CARD owes by path and kind — NOT a complete account of what CI runs on the PR. So this is a coverage limit that the tool announces, not a silent defect.What makes it worth recording anyway: a dev that follows the documented workflow exactly — derive with
--commands, run what it lists — gets a green local sweep and a red CI, and the gate it missed is the one that grades precisely the construct the PR introduced. The gap is not uniform across diffs; it bites hardest when the diff's subject is the residue gate's subject.Not proposed as a fix, offered as the shape of the question
Whether
--commandsshould surface residue families whose name or source matches a construct the diff introduces, or whether the answer is a second harvest mode for "unconditional CI gates you should run anyway", is ascripts/pm/**design question for whoever owns that tool — not this seat's to settle.scripts/pm/**is single-writer on the objectstack side.Refs: PR #14866 (where it was measured) · #9754 (the gate) · #14353 (the card)