Repository navigation
tooling(pm): dispatch-gates — the hand-written tables move out as data, the self-test out as a module with fast/slow tiers, derivation byte-identical - #22531
Draft
objectstack-fleet[bot] wants to merge 6 commits into
Conversation
…not drive, before the split Condition 1 of the lift: measured with V8 block coverage over a full run of the battery (the engine's functions and branches, self-test excluded), then one pin per path no case reached. No behaviour changes in this commit. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JmWtcHfGbC4ncw4GFKWuRA
…, the self-test out as a module with two tiers The gate roster was never a table — families are derived from the workflows, the composite actions, package.json and the gate sources on every run — so what moves is what IS hand-written: the marker grammar vocabularies, the ledgers, the change-kind roster, the tier globs and the ladder words, into dispatch-gates.data.mjs with their docblocks and no logic (a predicate or a tier is named, and the engine resolves the name at load, refusing one it does not know). The battery moves into dispatch-gates.self-test.mjs, loaded only by the engine's --self-test branch, and splits into a FAST tier (every in-process case) and a SLOW tier (every CLI spawn, temporary repository and whole-tree sweep), the two pinned to partition the battery. The wrapper check-dispatch-gates.mjs runs the fast tier on a dev box, both under GITHUB_ACTIONS or --slow, and says which before it spawns; lint.yml and the package.json line are untouched, so no new family enters the derivation. The engine keeps roster loading, path matching and derivation; its declared inherited population (.github/workflows, .github/actions) is what every follower reads, so no derivation moved — proven by the replay in the PR. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JmWtcHfGbC4ncw4GFKWuRA
…dening only Both occurrences of the clause-② sentence now carry the lane rule's meaning: a card that widens a published accept set or the public surface owes the contract-review-tier review and is spec-lane work; a card that only narrows one stays in its lane and owes one such review before the queue. The old sentence gave a narrowing card two answers. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JmWtcHfGbC4ncw4GFKWuRA
…follows the moved table
The gate's known-instance row for scripts/pm/dispatch-gates.mjs expected
`marked`: the change-kind sentence carrying the ⛔ MAINTAINER-ONLY token
lived in the engine. That roster moved out as data, so the sweep's offer
grammar reaches nothing in the engine now and the gate's own positive
control reddened ("the gate changed shape — correct the entry"). The row
turns `excluded` with the move recorded in its reason; the data file
classifies excluded on its own and needs no row. The token still reaches
the author verbatim, from the data row.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmWtcHfGbC4ncw4GFKWuRA
…acks the second reading showed The three load-time binders become exported functions with the same behaviour (bindChangeKindRows, bindMandatoryTierRows, bindSuspectTierRows) so a row naming a predicate, tier or exception the engine does not define can be shown to refuse loudly; the battery gains those pins and the ones for the fallback fragments a caller reaches only by handing in nothing. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JmWtcHfGbC4ncw4GFKWuRA
…spatch-gates-roster-split
Contributor
Author
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Contributor
Author
维护者速读 — PR #22531(#22478 + #22167)— skills 席 1,2026-10-09T20:12Z
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #22478
Fixes #22167
Clause-②: no
What this PR is
W3 of the skills-lane diet program, under the maintainer's letter A on #22453 (「同意」, 2026-10-09T11:15Z, record 6079780639): the ruling-208 freeze on
scripts/pm/dispatch-gates.mjsis lifted for exactly this split and restores itself when this PR lands. The three conditions of the lift are the three sections below, in the ruling's order. The--tiernote remainder of #22167 (ruling C, record 6053791774) rides the same PR.1. Coverage first (condition 1)
Method.
NODE_V8_COVERAGEover one full run of the battery onorigin/main(e148ca98, the engine unchanged sincedee7692fbut for the landed #22451 pin), the parent process and every CLI child it spawns merged (60 processes), read per top-level declaration of the engine with the self-test excluded. Reading is the block granularity V8 reports, so "a path" below is a function or a branch inside one.Before. 245 engine declarations carry code; 245 of 245 were invoked by some case — no function of the engine was untested. Inside them, 253 of 2,259 blocks had a zero count (merged the way V8 means it: a block is listed only when its count differs from the enclosing range, so an unlisted block inherits the innermost listed count per process, and the maximum across the 60 processes is taken — a first reading that merged listed ranges alone said 338, and over-counted). Those 253 are the measure of condition 1; most are defensive refusals (
throwon a key the table does not hold), the arms of a renderer never reached by a fixture, and the classes of the run-record reconciliation no case had landed a family in.Pins added (commit (a), 59 cases, all in-process on fixtures, in the fast tier): the marker grammars —
markerFormKindandpopulationMarkerPatternrefusals,lineFormReason's two no-cut exits and its cut control,markerReasonCutRefusal's unknown-key throw and both the BLOCK and LINE repair texts,refuseCutMarkerReasonthrowing and passing,unparsedPopulationMarkers+unparsedPopulationMarkerRefusal,readPopulationDeclaration's unknown key; the path matcher's floor (hintCoverson a one-character hint, with a control); the tier renderer's two one-line-class exits (tierLines), the changed-line reading's three states (changedLineLines: NOT MEASURED / under / OVER against the gate's own threshold), the sister-repo tier run's three exits (sisterRepoTierRun: not a sister, no paths, answered), the claim line reader's two absences (readContainerModelLine); the argv splitter's valueless and joined forms (splitArgv); the repo assertion's three refusals, the governed-sister hint and the case-insensitive pass (repoAssertionVerdict); the absent-path verdict with no identity, with one, and settled by an assertion (absentPathVerdict); and the run record end to end —runRecordKillLabel's five answers,parseRunRecord's malformed tails, claims and CRLF, and every classrunReconciliationcan land a family in (a contradiction resolved toward exit 3, a kill with a stated reason, a kill with none, a kill with a reasonless claim, a reasoned claim, the three explained extras, a near miss, both malformed kinds, the silent class) withrunReconciliationLinesrendering each and the three evidence kinds (FLOOR, RUNNER'S CLAIM, DERIVED zero).After. On commit (c)'s head, the same method over a full run of the split battery (60 processes): 248 engine declarations with code, 248 of 248 invoked, 248 of 2,274 blocks at zero — the 59 pins closed their targets (
hintCovers,markerFormKind,populationMarkerPattern,readPopulationDeclaration,runRecordKillLabel,splitArgv, two ofrunReconciliationLines' renderings, one ofderivationProvenance's — the rest of the first list had already been reached by the CLI children once the merge was read correctly), and the split's three new load-time binders added four zero blocks of their own: the refusals of a data row naming a predicate, a tier or an exception the engine does not define, and the suspect row with no exception. Commit (e) makes those binders exported functions (same behaviour) and pins all four, plus the fallback fragments the second reading listed (cut.fileabsent, a lookalike with no comment opener,readContainerModelLine/parseRunRecordon nothing, a record entry with nomalformedKind, the repo assertion and absent-path verdict with no identity): 14 more cases. NOT MEASURED on this head (8f1979ee): the full-battery coverage rerun after commit (e) was killed with the session by the second 5-hour usage wall at 1,990 of 2,085 cases (its 60 child files landed, the parent's never did; the battery under V8 block coverage takes ~45 minutes on this box) and is not re-run on the merged head; the (c)-head reading above stands as the measured "after", and commit (e)'s 14 pins name their targets by function and branch — each passes in the battery, so each target ran in the parent process; the block counts are the only thing not re-read. What stays at zero by construction is listed next.What is not pinned, and why: the two closing-invariant throws (
familyReconciliation,runReconciliation: "the classes and the derivation came from different structures") are reachable only by corrupting the function's own locals;derive/machineReadableOutput/discoverFamiliesPassbranches that depend on a live tree shape (a workflow with apaths-ignore:, a package-local gate under a second base) are driven by the CLI children over this tree and stay at the block counts the tree gives them; the git-reading branches (runGit,changedPathsFromGiton a shallow checkout) are exercised in the slow tier's temporary repositories. None of these is a derivation path a card's--commands,--tieror--rananswer can reach without a different tree.2. The split — what moved, what stayed, why (condition 2)
The measured shape of the roster (mechanism assumption 1 — confirmed). The engine embeds no path → family table. Every run re-reads
.github/workflows/*.yml, follows eachuses: ./.github/actions/…into the action'sruns:steps, resolves everycheck:*throughpackage.json, and scans the gate scripts' own sources for the path literals they operate on (discoverFamiliesPass; the header's "Why derived, never listed"). So the roster was already data — the tree's — and nothing of it moves; inventing a file for it would be the second roster the dispatch order forbids. What IS hand-written in the engine, measured by parsing it (acorn, 363 top-level declarations, 89 of them pure literals): the marker grammar vocabularies, four ledgers, the one hand-written gate-family list (CHANGE_KIND_GATES, the convention-triggered gates keyed by change KIND), the tier globs and the ladder words. Those move.Why moving them moves no derivation (measured before touching anything). Three families read the engine's hints —
check:pm-dispatch-gatesthrough the wrapper's RUN edge,check:declared-population-liveandcheck:watch-hint-literalthrough an IMPORT edge — and all three inherit not the 33 literals the engine spells but its DECLARED inherited population:.github/workflowsand.github/actions(theinherited-populationmarker on the engine, whichdeclaredInheritedPopulationlets only narrow, never invent). Those two literals are spelled by the discovery code andCOMPOSITE_ACTION_DIR, which stay. Neither new file is a watch surface: no family resolves to it and no gate imports it (import edges are one level deep and never expand a run target's own imports). The replay below is the proof; this paragraph is why it was expected.Line budget (
wc -l).dispatch-gates.mjs30,050 onorigin/main(e148ca98) → 30,194 after the pins (commit (a), +144) → 15,911 after the split (commit (b)) → 15,915 after the note (commit (c)) → 15,927 on this head (commit (e), the three binders as named functions). New beside it:dispatch-gates.data.mjs1,258 (data only, docblocks included),dispatch-gates.self-test.mjs13,538 (the battery, two tiers, 59 + 14 pins).check-dispatch-gates.mjs502 → 567 (+72 / −7: the tier decision, its header section and three self-test cases).check-ratchet-remedy-authority.mjs+2 / −2 (commit (d), one ledger row). Commits, each green on its own: (a)44d264edpins · (b)3cbaf471split · (c)2a5b52c4note · (d)c45ee314the sibling ledger row · (e)8f1979eebinder functions + 14 pins · thenorigin/mainee8751d4merged as10274911(12 commits, none touching these files; #22451's pin keeps its landed 3000 / 3001 meaning, it had come in withe148ca98). Changed lines on this head: 29,479 (+15,109 / −14,370).check:*families).github/workflows/*.yml, the composite actions theyuses:,package.jsonand the gate scripts' own sourcesdiscoverFamiliesPass)POPULATION_MARKER_KEYS,REASON_TAIL_MARKER_KEYS,PATH_LIST_MARKER_KEYS,MARKER_COMMENT_FORMS,MARKER_KEY_FORMSdispatch-gates.data.mjs(verbatim, docblocks included)MARKER_REASON_GRAMMARS,MARKER_LOOKALIKES,POPULATION_DECLARATION_FIELDS) stay in the engine because they reference engine functionsROOT_WALK_RESIDUE_LEDGER,COMPOUND_ANCHOR_LEDGER,GOVERNED_READ_FLOOR,ESCAPABLE_LITERAL_LEDGER(rows)ESCAPABLE_LITERAL_LEDGER_ROWS, the engine binds the Set)CHANGE_KIND_GATES— the one hand-written gate-family list (convention-triggered gates, keyed by the KIND of change)CHANGE_KIND_ROWS, each row naming its predicate by KEY (test-file,i18n-bundle-package,metadata-form-module,gate-script,root-ts-program,error-code-literal,http-status-emit); the engine resolves the key at load and refuses an unknown oneMANDATORY_TIER_GLOBS,SUSPECT_TIER_GLOBSisTestPathFUNCTION*_GLOB_ROWS, the tier by NAME (CONTRACT_REVIEW_TIER) and the exception by KEY (test-path); the engine resolves both at load and refuses an unknown nameCONTRACT_REVIEW_TIER), as the one-value-site pin requiresTIER_FLOOR,TIER_DEFAULT,RETIRED_TIER_WORDSDERIVATION_SURFACE,COMPOSITE_ACTION_DIR,MODULE_SPECIFIER_EXTENSIONS,GOVERNED_SURFACE_PREFIXES/FILES,REPO_ROOT_WALK_SPELLINGS,HARVEST_SNIPPET, every regex constantfunction selfTest)dispatch-gates.self-test.mjs, loaded only by--self-test3. Byte-identical derivation (condition 2) — the replay and its empty diff
Corpus. Every literal argv the battery hands the tool's CLI (
runCli/runCliHypotheticalindispatch-gates.self-test.mjs, the battery's own bindings resolved: the seam card, the test card, the guard card, the ran card, the absent path, the governed sister repo's slug, the changeset probe and the value-bearing probe), the lane's three change sets of 2026-10-09 (PR #22457's fleet-write modules and three references; PR #22458's four.claude/**texts andlabel-write.mjs; PR #22475's automation SKILL.md and its eval), and this card's own surface (scripts/pm/dispatch-gates.mjs+package.json) — each in--commands,--tier, the human rendering,--residue,--jsonand--commands --residue; plus, for eight cards, the four--ranrecords the battery builds by construction (complete, short by one, every exit coded 0, one exit 3) in all four renderings and one unreadable record. 69 corpus invocations + 8--rancards per engine; stdout, stderr and exit code recorded for each.Same tree, two engines. The base engine (
origin/maine148ca98's file — the merged main with the landed #22451 pin, the engine unchanged sincedee7692fbut for that one self-test line — as an untracked copy beside the head engine so both resolve the same ROOT) and the head engine, replayed in one checkout — so the "swept over N tracked file(s)" count and every other tree fact are identical by construction and only the engine can differ.Result:
diff -r base head→ empty (exit 0, 0 lines), 852 files per side; the base leg took 1,217 s and the head leg 1,222 s at parallel 2 on the contended box. A second head leg with the COMMITTED (b) engine (3cbaf471, which carries four moreexportkeywords than the engine the first head leg ran, for the pins' private helpers) over the same base leg:diff -r base head-b→ empty (exit 0, 0 lines), 852 files per side. Two of the 852 entries are--changedruns that derive the change set from the working tree itself (--changed --commandsand the no-path--tier), so their output names the tree's own diff; those two were re-taken with both engines on one tree state (the untracked base copy present for both) before the final diff — the first pass had differed on exactly that count, which is the tree, not the engine. The replay runner and its corpus enumeration are below, verbatim minus angle-bracket placeholders; the corpus is read from the committed test module, so the proof reproduces from this PR alone.Commit (e) (the binders as named functions): the (c) engine as an untracked copy beside the (e) engine, over the lane's three change sets, this card's surface and a
*.zod.tssuspect in--tier, the human rendering,--jsonand--commands(60 files per side):diff -r→ empty (exit 0, 0 lines).Merged head (
10274911, baseee8751d4— 12 later commits onmain, none touching the engine): the same replay, same tree, base engine vs the head engine: this base is the pre-split engine with the OLD clause-② sentence and this head carries commit (c), sodiff -r base head→ 28 of 852 files differ, all of them.out(no.err, no.exit), 62 changed lines, and every one of the 62 is the clause-② sentence or its SUSPECT parenthesis (checked mechanically: 62 of 62 carry the old "accept/reject" or the new "WIDENS" wording; the--commandsand--ranoutputs are untouched). That is ruling C's rewording and nothing else — modulo the one sentence the ruling ordered changed, the derivation on the merged head is byte-identical toorigin/main's.The replay runner (
replay.mjs, kept out of the tree on purpose — a committed golden replay would be a new ratchet; see Acceptance notes):The same-tree driver (
replay-both.sh TREE BASE_SHA OUT_DIR):4. Two tiers — the battery's seconds before and after, per tier (condition 3)
Before (origin/main
dee7692f, the oneselfTest()with no tiers).pnpm check:pm-dispatch-gateson this box: 2,011 cases, 1,093.7 s (the wrapper's own wall-clock line; load average ≈3.4 on 4 cores with one other battery running — the same contended-box shape as the 1,052–1,178 s readings on the card). Per-tier attribution of that battery, from a second run of the same base engine with every printed line timestamped (1,051.3 s total, concurrent with the run above): the thirteen sections that spawn the tool's CLI, build temporary git repositories or sweep the tree — the sections the split names SLOW — took ≈719 s; everything else ≈332 s.After. On commit (c)'s head (
2a5b52c4), same box, same contention shape: both tiers (pnpm check:pm-dispatch-gates --slow): 2,071 cases pass, 1,108.7 s — the battery's own tally: fast 1,811 cases in 274.3 s · slow 260 cases in 834.0 s. Fast tier alone (--fast, what a dev delivery runs; read on commit (b)'s head): 1,811 cases pass in 283.8 s (284 s wall; the six deferred sections named in the log). The 2,071 = the 2,011 cases onorigin/main+ 59 coverage pins + 1 tier-partition pin; no case dropped (the partition pin readsfast + slow = cases.lengthwith nothing deferred). The fast tier is 1/4 of the battery's wall clock; the slow tier's 834 s is the CLI children, the temporary repositories and the whole-tree sweeps, exactly the before-run's attribution.Final head (
8f1979ee, commits (d) and (e) on top), both tiers: 2,085 cases pass, 1062.0 s on this box (the coverage rerun of the same battery started concurrently in its last minutes) — fast 1,825 cases in 271.3 s · slow 260 cases in 790.4 s;EXIT=0.Merged head (
10274911), both tiers: 2,085 cases pass, 1062.6 s on this box (the gate union and the replay's base leg ran concurrently) — fast 1,825 cases in 285.9 s · slow 260 cases in 776.1 s;EXIT=0.What each tier is. FAST: every in-process case — fixture derivations, path matching, the marker grammars, the renderings, the one memoised discovery of this tree (and the handful of sections that run an extra discovery pass to pin memoisation — up to ~45 s each on the contended box, derivation cases by the ruling's definition). SLOW: the six
slow(label, …)groups indispatch-gates.self-test.mjs— the governed-sister-repo verdict on the real CLI; the change set derived from git (temporary repositories with real history); base drift; the real CLI end to end (the noise floor, the sister-repo verdict, the three absent-path shapes, the entry guard); end to end on the real tree with the CI-measured family; and the run record built from--commandswith the value-bearing bucket and the NOT MEASURED renderings. ⛔ No case dropped: a run of both tiers prints the battery's verdict line exactly as before, and the tail pins thatfast + slow = cases.lengthwith nothing deferred (full) or every deferred section named (fast).Who runs which.
pnpm check:pm-dispatch-gatesunchanged inpackage.json; the wrappercheck-dispatch-gates.mjsdecides: a dev box with no flag →--self-test --fast;--slowon argv, orGITHUB_ACTIONS=true→--self-test(both tiers).lint.yml's step runs the gate with no argument, so CI keeps the slow tier without a workflow edit and without a second family for the derivation to name; the tier chosen and its reason print before the spawn, and the wrapper's own--self-testdrives all three cases against children that echo the argv they received. The read's failure direction is the safe one: an unset variable buys a shorter run that says which tier it was, never a quiet one.5. The
--tiernote (ruling C on #22167)Ruling C on #22167 (record 6053791774): Clause-② asks only what
execution-duties.mdasks — does the card WIDEN the accept set or the public surface — and a card that only NARROWS a published accept set isClause-②: no, stays in its lane, and owes one contract-review-tier review before the queue (the sentence PR #22223 added there). The engine's--tiernote said "a card that changes contract accept/reject behaviour or widens the public surface" in two places — theCONTRACT_REVIEW_TIERdocblock and theclause2linetierLinesprints on every run — which gave a narrowing card two answers. Commit (c) rewords both (and the SUSPECT line's parenthesis, the same sentence in its third spelling) to the lane rule's meaning in the engine's own words: a card that WIDENS a published accept set or the public surface owes the contract-review-tier review and is spec-lane work whichever seat found it; a card that only NARROWS one isClause-②: no, stays in its lane, and owes one such review before the queue. On this headgrep -c "changes contract accept/reject" scripts/pm/dispatch-gates.mjs→ 0. The only derivation output that moves between commits (b) and (c) is that sentence: with the (b) engine (3cbaf471) as an untracked copy beside the (c) engine, over the lane's three change sets, this card's surface and a*.zod.tssuspect, in--tier, the human rendering,--jsonand--commands(60 files per side):diff -r→ 10 files differ, every one a rendering that prints the note, and every differing line is the clause-② sentence (10 pairs) or the SUSPECT parenthesis (2 pairs); the five--commandsoutputs are identical.6. Nothing else in the file changed under the lift
Under the lift the engine file changed in exactly these ways, each a consequence of the split and nothing else: (1) the data import and the re-export block under the existing imports; (2)
exportadded to 25 bindings the moved battery drives (constants, flags and eight functions, four of them the private grammar helpers the coverage pins name — no behaviour), and 10 import specifiers only the battery used pruned; (3) each moved table replaced by a one-line pointer, and the four rebound tables (CHANGE_KIND_GATES,MANDATORY_TIER_GLOBS,SUSPECT_TIER_GLOBS,ESCAPABLE_LITERAL_LEDGER) by their load-time bindings, which refuse an unknown predicate / tier / exception name; (4) the--self-testbranch loads the battery module (without a top-level await — the battery imports the engine, so an await here deadlocks at exit 13 with nothing printed); (5) one refusal text whose pointer follows the movedMARKER_COMMENT_FORMStable to the data file; (6) the header's one usage line for--self-test [--fast]. Commit (c) is the two--tiernote sentences and nothing more.package.json'scheck:pm-dispatch-gatesline andlint.ymlare untouched, so no new family enters the derivation (the family list this card derives is byte-identical, see §3). After landing the freeze is back as ruling 208 states it.One file off the declared surface, in its own commit (d):
scripts/check-ratchet-remedy-authority.mjs's known-instance ledger expected the engine file to bemarked— the change-kind sentence carrying the⛔ MAINTAINER-ONLYtoken lived there. With that roster moved out as data, the gate's own positive control reddened on this head ("the gate changed shape — correct the entry"), so the row turnsexcludedwith the move recorded in its reason (+2 / −2); the data file classifiesexcludedon its own and needs no row, and the token still reaches the author verbatim from the data row. Recorded here and in the report as a deviation from "and nothing else": a mechanical consequence of the move, in a separate commit the seat can drop.Gates on this head
Derivation on this head (
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths — the change set read from git off the merge base; derive exit 0): 30 commands, each run with its exit captured before any pipe and recorded asCOMMAND :: exit N, then reconciled with--ran(exit 0).node scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/check-declaration-mirrors.mjsnode scripts/check-declaration-mirrors.mjs --self-testnode scripts/check-scripts-symbol-anchors.mjsnode scripts/check-scripts-symbol-anchors.mjs --self-testnode scripts/check-self-test-wired.mjsnode scripts/check-self-test-wired.mjs --self-testnode scripts/check-self-test-workflow-commands.mjsnode scripts/check-self-test-workflow-commands.mjs --self-testnode scripts/check-whole-set-label-write.mjsnode scripts/check-whole-set-label-write.mjs --self-testnode scripts/pm/bare-root-worklist.mjs --self-testpnpm check:agent-test-spellingpnpm check:bash32-floorpnpm check:cli-command-idspnpm check:cross-package-test-inputspnpm check:declared-population-livepnpm check:driver-memory-censuspnpm check:entry-guardpnpm check:gitlink-declaredpnpm check:nul-bytespnpm check:parse-guardpnpm check:pm-dispatch-gatespnpm check:pnpm-filter-targetspnpm check:ratchet-remedy-authoritypnpm check:refd-timer-probepnpm check:watch-hint-literalNon-zero exits: none.
--ranreconciliation tail:Narrowed lint, measured not skipped:
pnpm exec eslint --no-inline-config --format jsonover the four touched files → 4 files, 0 errors, 0 warnings (eslint files: 4 errors: 0 warnings: 0). Population: the config's one object matching**/*.mjs(no type-aware linting anywhere —parserOptionscarriesecmaVersionandsourceTypeonly, noproject), with exactly two rules on forscripts/pm/*.mjsper--print-config:no-restricted-importsandcomment-swallow/no-code-inside-block-comment, both per-file; so no untouched file's verdict can move with this diff, and the four-file run is the whole measurable effect. Repo-widepnpm lintis CI's run.The engine's own no-path
--tierreading on this head:Changed lines — 29479 (+15109 / -14370; generated files INCLUDED) vs the human-merge threshold 3000: ⛔ OVER — this PR lands as a Tier H surface does: an authorized APPROVED review (GOVERNED_APPROVERS, on ANY commit) and then the owning seat, or a HUMAN MERGE (maintainer rulings 2026-09-18 and 2026-09-27; no exemption for generated files, regen artefacts, docs builds or reverts). The governed terminal: no seat flips it ready, enqueues it, or arms auto-merge before that — ACCEPT on the card,needs-user-decisionon the PR, the final 维护者速读, review requested from GOVERNED_APPROVERS. The landing pre-checkcheck-governed-merges.mjs --pr Nreads the PR's own number.Acceptance notes
selfTest()was lines 16715–29714 of 30,050. The two tiers are as the assumption describes; CI runs both through the unchangedpnpm check:pm-dispatch-gatesline (the wrapper readsGITHUB_ACTIONS), solint.ymlis untouched and no second family enters the derivation.--ranrecords the battery builds by construction — stdout, stderr and exit code all compared. Comparing across two checkouts instead would have differed on the "swept over N tracked file(s)" count alone (two new tracked files), which is not the engine's doing.package.json:79— "and nothing else" — and a committed golden replay would be a new ratchet that reds on every unrelated workflow edit, which the ruling forbids (「⛔ 无原话不新增」). The replay script is carried in this PR body (scrubbed of angle-bracket placeholders) and its corpus is derived from the committed test module, so it is reproducible by anyone; it is not a one-off in a temp dir, and it is not a gate.GITHUB_ACTIONS). The wrapper's own header argues against env-controlled child substitution; this read runs in the opposite direction (it only ever adds the slow tier), is announced before the spawn, and is pinned in both directions by its self-test. The alternative — a secondpackage.jsonscript wired intolint.yml— would have edited a workflow off the file surface and added a family to every derivation touching this file.--self-testbranch could not keep a top-levelawait— the battery imports the engine, so the engine awaiting the battery deadlocks (Node exit 13, nothing printed). Loaded withimport(...).then(...)instead; the handshake constant and the exit-1 sentence are unchanged. Recorded here so the next reader does not "simplify" it back.function selfTestTier(in the wrapper was flagged by the engine's compound-anchor census (COMPOUND_ANCHOR_LEDGER) — the anchor fires on NAMES; the helper is namedtierOfThisRunfor that reason, and the ledger is untouched.additions + deletions(29,421 on this head: +15,053 / −14,368 againste148ca98;HUMAN_MERGE_LINE_THRESHOLDis 3,000 since governance: lower the human-merge line threshold from 5,000 to 3,000 changed lines (HUMAN_MERGE_LINE_THRESHOLD), per the maintainer's 2026-10-09 ruling #22451 landed). Git cannot see it as a rename — the engine stays the engine. So although the surface is off the governed register, this PR lands the way Multi-agent discipline §7(c) says a diff over the line threshold lands: an authorized APPROVED review and then the owning seat, or a human merge — not the queue on green alone. The card's "非受管面,席位入队落地" expectation does not hold for this diff; the engine's own no-path--tierreading on this head says it in its words: "⛔ OVER — this PR lands as a Tier H surface does: an authorized APPROVED review (GOVERNED_APPROVERS, on ANY commit) and then the owning seat, or a HUMAN MERGE … no seat flips it ready, enqueues it, or arms auto-merge before that". The seat routes it so; the maintainer's letter A authorised exactly this split.scripts/check-ratchet-remedy-authority.mjs— one ledger row (+2 / −2) whose expectation named the engine as the home of a token the moved roster carried; the gate's own positive control reddened on this head and its text prescribes correcting the entry. Nothing else off the surface was touched; the union's other 29 families were green before and after.维护者速读(草稿)
改了什么:
scripts/pm/dispatch-gates.mjs(派发时给每张卡推导本地门禁和模型档位的引擎)拆成三个文件:引擎本体只留「读表、匹配路径、推导」;手写的表(标记语法词表、各类台账、按改动类型触发的门禁清单、模型档位 glob、档位阶梯词)搬到旁边的dispatch-gates.data.mjs,纯数据、无逻辑;自检电池搬到dispatch-gates.self-test.mjs,并分成快档(进程内用例)和慢档(真实起子进程、建临时 git 仓、全树扫描的用例)。另按 #22167 裁决 C 把--tier输出里关于条款②的那句话改成「只问放宽」的同一含义(两处)。为什么改:#22453 维护者批了 A:解冻这一次拆分。拆分前引擎 30,049 行、每次交付都要跑约 17–19 分钟的电池;拆分后开发交付默认只跑快档,CI 仍跑全部;新增一条按类型触发的门禁从此是一行数据(仍只凭维护者原话新增)。
风险与代价(含回滚):推导输出逐字节未变(PR 里有回放脚本与空 diff);旧用例一条不少(快档+慢档=全量,有 pin);被动风险是 CI 的档位判断依赖
GITHUB_ACTIONS环境变量,失败方向是「少跑但大声说」,不会静默。回滚 = revert 本 PR(三个提交各自独立可 revert)。席位意见:(留空)
你要做的:这张 PR 的文件面不在受管清单上,但改动行数是 29,421(搬出 1.3 万行自测被
additions + deletions计了两次),远超人合阈值 3,000——引擎自己的无路径--tier读数已判 ⛔ OVER:「this PR lands as a Tier H surface does: an authorized APPROVED review (GOVERNED_APPROVERS, on ANY commit) and then the owning seat, or a HUMAN MERGE」。所以不是「席位入队落地」:需要维护者给一次 APPROVED 评审(之后席位落地),或由维护者人合;席位不翻 ready、不入队、不挂 auto-merge。落地后冻结自动恢复。Generated by Claude Code
Closing-target claims: #22478 — claim 6080824452 · #22167 — claim 6088380960 (ruling C's remainder rides this branch, per the maintainer's letter A on #22453).