Skip to content

Commit 53fc81f

Browse files
claude[bot]claude
andauthored
tooling(pm): key the gate derivation on (script, args) and name the artifact-roster silences (#15081)
* wip: dispatch-gates derivation key (script, args) * tooling(pm): derive one gate entry per workflow invocation, not per script path `dispatch-gates` keyed a discovered family on the script PATH, so CI's two invocations of one script collapsed into a single entry — and the entry kept was the plain one, because the direct matcher captured the path and dropped the argument tail. Measured on PR #14958: `lint.yml` runs `node scripts/check-tenant-audit-census.mjs --self-test` beside the plain run, the red was carried entirely by the `--self-test` invocation, and the derived list named only the invocation that was already green. A dev following the list verbatim could not see the failure mode at all. The key is now (script, args). The argv half is admitted only when this tool can render the invocation runnably — a complete run of flag-shaped tokens; a tail carrying a value, or continued onto the next line, keeps the bare path key it has today rather than being truncated into a command that cannot run. Read from this tree's workflow text: 28 scripts in `lint.yml` are invoked more than once under different argv, 41 across all workflow files, and every one of them carried a `check-` basename and so collapsed. 204 discovered families become 242; three `check-` scripts CI never invokes plainly lose a bare key that named a command nobody runs. Also: a `--self-test` invocation is never CI-measured-only. The payload-access read that classification rests on is taken from the gate's work body, which a self-test run does not execute, so suppressing it from `--commands` would hide a command a dev can run. And the usage line printed on the derivation-failure path moves `--residue` inside the alternation, so it no longer advertises the `--tier --residue` pair the CLI has refused since #14753. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox * tooling(pm): name the artifact-roster silences in their own block, beside the derivation A family whose declared literals all name tracked FILES declares a roster — a baseline, an allowlist of the members it already has — and never a population. A list of the files that already exist can never contain one added tomorrow, so this derivation scores those families `silent` for every card in the tree, and no path a caller passes can move them. Two measured CI reds were carried by exactly that shape: `check:optional-error-sink` on PR #14866 and `check:error-code-provenance` on PR #14930, both invisible to a `--commands` harvest by construction, for every card. `artifactOnlyNote` already said all of this — per family, but only inside the silent listing, which is behind a flag no dispatch brief tells anyone to pass. The block says it where the default run shows it, and where `--commands` puts every other accounting: on stderr, so the stream a consumer executes still carries commands and nothing else. Measured before building it, over the SILENT bucket for the diff of PR #14866: 32 of 120 silent families declare only tracked artifacts, 5 of them with the roster sitting in a directory one of that card's paths is in. A minority, so the block enumerates rather than only counting, and marks the correlated subset. The block is never counted among the derived families and never merged into the runnable list. That is structural, not a filter: rosters are `silent`, and `commandsFor` reads only the matched, convention and always-runs rows. It deliberately does NOT call these gates repo-wide scanners. Whether a roster is a baseline sitting in a directory or a census taken of that directory is intent, and intent is not in the tree — the two live side by side here. The block states what is true of every member instead, and points at the producer-side remedy the residue already carries: declare the scan surface beside the roster. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox * tooling(pm): record the bare-root verdicts the second derivation key owes The (script, args) split gives a gate CI invokes both plainly and with `--self-test` two family keys, and `bare-root-worklist`'s rows are keyed on `family constant word` — so nine rows landed FRESH in one edit without any gate changing. Its self-test says so and names the remedy for a FRESH row: record a verdict. Recorded, one per new key, in a labelled section of their own. Each is the twin of a row already carrying a decision about the identical literal, in the identical file, under the identical constant, so the verdict and (where the verdict requires one) the spelling transfer whole. No count is restated: the map's own docblock bans carrying a sibling's numbers into a new row because two rows are normally two populations measured at two times, and here they are one population read through two keys — restating a figure would mint a reading this pass never took. The section also records the structural alternative and why it was not taken here: keying the sweep's dedupe on the gate SOURCE FILE rather than on the family would fold the twins, but it re-decides which family a surviving row is attributed to and would strand the existing row as STALE — a redesign of this file's keying, which is not what the FRESH remedy names. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e38da2b commit 53fc81f

2 files changed

Lines changed: 627 additions & 24 deletions

File tree

scripts/pm/bare-root-worklist.mjs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,95 @@ const TRIAGE = new Map([
847847
spelling: 'examples manifests',
848848
why: 'workspace manifests only — 4 of 241 (1.7%), re-measured 2026-08-26; 4 of 4 covered',
849849
}],
850+
851+
// ── SECOND-KEY TWINS: one literal, two family keys (#14880) ──────────────
852+
//
853+
// Nine rows landed FRESH in one edit, and NOT because a gate changed. The
854+
// dispatch-gates derivation key became (script, args) in #14880 — one entry
855+
// per workflow INVOCATION rather than per script path — so CI's
856+
// `--self-test` invocation of a gate it also runs plainly is now its own
857+
// family. `sweep()` keys a row on `family constant word`, so the identical
858+
// bare-root literal, in the identical file, under the identical constant, is
859+
// reached a second time and produces a second row.
860+
//
861+
// ⛔ These are therefore NOT new populations, and each `why` below states
862+
// that instead of a measurement. The docblock above forbids carrying a
863+
// sibling's numbers into a new row, and the reason it gives is that two rows
864+
// are two populations measured at two times; here the two rows are ONE
865+
// population read through two keys, so restating a number would mint a
866+
// reading this pass never took — the same defect the ban is written against,
867+
// arriving by the one route the ban's wording does not cover. The verdict is
868+
// the twin's verdict for the only honest reason there is: a different verdict
869+
// on the same literal would have this map assert two decisions about one
870+
// population.
871+
//
872+
// ⚠️ The class GROWS with the workflows, not with this file: any gate CI
873+
// starts invoking a second way acquires a twin row here on the next run. The
874+
// structural alternative — keying `sweep()`'s dedupe on the gate SOURCE FILE
875+
// rather than on the family, since the verdict is about a literal in a file
876+
// and never about an invocation — is real and is deliberately NOT taken here:
877+
// it re-decides which family a surviving row is attributed to, which would
878+
// strand the existing twin as STALE, and redesigning this file's keying is
879+
// not what the FRESH remedy names. Recorded for whoever owns that call.
880+
['scripts/check-adr-0087-registration.mjs --self-test PACKAGE_ROOTS packages', {
881+
verdict: 'SPELLABLE-UNDECLARED',
882+
spelling: 'packages manifests',
883+
why: 'second-key twin of the row keyed on this same script without the flag — same file, '
884+
+ 'same constant, same root, one population. It exists because CI invokes this gate both '
885+
+ 'plainly and with the self-test flag and the derivation now keys on the invocation. The '
886+
+ 'verdict and the spelling are that row decision, unchanged; ⛔ no count is restated here, '
887+
+ 'because this pass measured none and the twin numbers belong to the pass that took them',
888+
}],
889+
['scripts/check-adr-0087-registration.mjs --self-test PACKAGE_ROOTS apps', {
890+
verdict: 'SPELLABLE-UNDECLARED',
891+
spelling: 'apps manifests',
892+
why: 'second-key twin, apps half — same file, same constant, same root as the unflagged row. '
893+
+ 'Verdict and spelling carried as one decision about one population, counts deliberately '
894+
+ 'not restated',
895+
}],
896+
['scripts/check-adr-0087-registration.mjs --self-test PACKAGE_ROOTS examples', {
897+
verdict: 'SPELLABLE-UNDECLARED',
898+
spelling: 'examples manifests',
899+
why: 'second-key twin, examples half — same file, same constant, same root as the unflagged '
900+
+ 'row. Verdict and spelling carried as one decision about one population, counts '
901+
+ 'deliberately not restated',
902+
}],
903+
['scripts/check-declaration-mirrors.mjs --self-test SCRIPTS_DIR scripts', {
904+
verdict: 'REFUSE-UNSPELLABLE',
905+
why: 'second-key twin of the unflagged row for this script. The refusal is about the walk the '
906+
+ 'gate own source performs — an extension filter no subtree idiom describes — and a walk '
907+
+ 'is a property of the file, not of which invocation CI happens to schedule, so the '
908+
+ 'refusal transfers whole and its measurement stays where it was taken',
909+
}],
910+
['scripts/check-position-name-fold-loaders.mjs --self-test SCAN_ROOTS packages', {
911+
verdict: 'REFUSE-WIDE',
912+
why: 'second-key twin of the unflagged row for this script. The trade it refuses — a true '
913+
+ 'declaration naming this gate on every card under the root — is the same trade whichever '
914+
+ 'invocation CI schedules, so the verdict transfers and the numbers stay with the pass '
915+
+ 'that measured them',
916+
}],
917+
['scripts/check-position-name-fold-loaders.mjs --self-test SCAN_ROOTS examples', {
918+
verdict: 'REFUSE-WIDE',
919+
why: 'second-key twin, examples half — refused with its packages half for the reason the '
920+
+ 'unflagged row states, and for the same one population',
921+
}],
922+
['scripts/check-position-name-fold-loaders.mjs --self-test SCAN_ROOTS apps', {
923+
verdict: 'REFUSE-WIDE',
924+
why: 'second-key twin, apps half — same trade, same reason, same single population as the '
925+
+ 'unflagged row',
926+
}],
927+
['scripts/check-position-name-fold-loaders.mjs --self-test SCAN_ROOTS scripts', {
928+
verdict: 'REFUSE-WIDE',
929+
why: 'second-key twin, scripts half — the root where this gate own allowed readers live, '
930+
+ 'walked wholesale for the reason the unflagged row records, and one population with it',
931+
}],
932+
['scripts/check-skills-token-ratchet.mjs --self-test SKILLS_DIR skills', {
933+
verdict: 'SPELLABLE-UNDECLARED',
934+
spelling: 'published skill files',
935+
why: 'second-key twin of the unflagged row for this script. The recorded spelling is that '
936+
+ 'row spelling and is already pinned LIVE, PRECISE and COMPLETE below on its own terms, so '
937+
+ 'this row adds a key and no new claim; the deferral reason is the one recorded there',
938+
}],
850939
]);
851940

852941
/**

0 commit comments

Comments
 (0)