tooling(pm): admit a same-PR cross-file line-budget move, and take the first one - #14860
Open
os-litant wants to merge 5 commits into
Open
tooling(pm): admit a same-PR cross-file line-budget move, and take the first one#14860os-litant wants to merge 5 commits into
os-litant wants to merge 5 commits into
Conversation
The per-file line ratchet is shrink-only, which prices a FACT MOVING TO ITS SINGLE-SOURCE FILE as though it were growth: the destination sits at headroom 0 like every other entry, so consolidating a misplaced per-repo reading needed a per-instance maintainer ruling even though the corpus shrank on net. Adds the declared cross-file move as the one raise an author may take alone, under the three ruled conditions: the destination's raise is at most the net decrease of the sources it names, the map-wide total does not increase, and the declaration cites the ruling that authorises it. The declaration is in-tree data carrying each participant's pre-move ceiling, so the arithmetic is computable from the working tree alone — no baseline ref a shallow CI checkout can fail to provide, and no answer that evaporates once the move has landed. Verdict lines and exit codes are unchanged; the map is empty in this commit. Part of #14685 Part of #13597 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…y gate The #8435 convention asks who may take a registry-growing remedy on a shrink-only ratchet, and the answer has always been "a maintainer, per instance". One class of act is now authorised generally: a cross-file move in the per-file line ratchet, while the destination's raise is covered by the named sources' net decrease, the map-wide total does not increase, and the raised entry cites the ruling. The exception is written in comment space, because author-facing text describing a registry-growing act would make this gate an instance of the convention it enforces. Two self-test assertions hold it there: one that the phrase never reaches an author-facing message, one that the amended ratchet is still outside this non-recursive walk — so the exception stays honestly documentary, and a wider walk reds here instead of admitting a remedy class in silence. No new verdict class: a fifth class with a population of zero is enforcement nobody has. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
A per-repo gate reading written into one lane charter is read by that lane and
missed by the other six. Their single source is references/platform-readings.md,
so the three the Phase-1 tables measured move there, under the cross-file-move
rule this branch adds:
lanes/cli.md -4 the objectstack required-check set, `in_progress` is
not a pass, advisory red into main is shared damage
lanes/services.md -2 the two aggregate-reading gate boundaries
rest-channel.md -5 the merge_group count-is-not-a-mechanism tombstone,
moved byte-identically
platform-readings.md +10 against a net source decrease of 11, so the corpus
falls by one line: the per-job-conclusion rule cli.md carried is already stated
in the destination's queue section, so the moved bullet points at it rather than
paying for a second copy. Ceilings move with the text (314→324, 35→31, 30→28,
93→88) and the move is declared, so the gate re-derives the arithmetic on every
run instead of trusting the comment beside it.
No pointer is left at any source: nothing at the three sites becomes unreadable
without the moved sentence, and a pointer per move is how a corpus grows while
its ledger says it shrank.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…tchet-cross-file-move
This was referenced Sep 3, 2026
os-zhuang
approved these changes
Sep 3, 2026
os-zhuang
marked this pull request as ready for review
September 3, 2026 08:55
os-zhuang
enabled auto-merge
September 3, 2026 08:55
This was referenced Sep 3, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Sep 3, 2026
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.
Part of #14685
Part of #13597
Item 5 of decision batch 3, ruled A. Ruling comment 5520452691 on the card, verbatim:
Two halves land together because neither is useful alone: the rule, and the first move taken under it.
1. The rule —
scripts/pm/check-skill-line-ratchet.mjsThe defect is structural, not a hardship claim: a fact can be in the wrong file. A per-repo gate reading written into one lane charter is read by that lane and missed by the other six; its single source is
references/platform-readings.md. Consolidating it there reduces the corpus — one copy, one reader path — yet under a shrink-only per-file ceiling it was impossible without a per-instance maintainer ruling, because the destination sits at headroom 0 like every other entry. The ratchet was pricing a move as though it were growth, and the file's own remedy sentence (move narrative out rather than raise the roof) was the very act it could not price.A declared cross-file move is now the one raise an author may take alone. Three conditions, all re-derived from the tree on every run:
The mechanical form, and why this one
The flight brief offered two shapes. I took (i), in-tree declaration data, not (ii) a
--baseline origin/maindiff, and the map entry carries the human comment the ruling asks for plus a machine-readableCROSS_FILE_MOVESdeclaration rather than a comment the script has to regex out of its own source.Why:
--baseline origin/mainonly sees a raise while the PR is open; after merge, HEAD andorigin/mainagree and the check goes silent — so the rule would be enforced exactly once, by a run nobody re-reads.origin/mainis not a stable input. It is a local snapshot a sibling worktree's fetch moves under the run, and a shallow CI checkout need not carry it at all. A gate whose verdict depends on it must either red on a missing ref (breaking shallow checkouts) or skip — and a gate that cannot find its input must fail, not skip.ruling,was,sources) are read, not scraped; the prose comment beside the ceiling stays what it has always been, prose for a reviewer.What the gate can see, and what stays a reviewer's job
Stated in the header rather than implied. The script reads the working tree and its own map; it never sees the PR diff, so it cannot know a raise happened at all, still less that a decrease landed in the same PR. What it does is hold a declared move to its own arithmetic: each participant records the ceiling it carried before the move, so the raise, the decreases and the map-wide total are computable from the tree alone — deterministic, hermetic, available in a shallow checkout.
What stays with the reviewer is completeness: that each recorded
wasis the value onorigin/main, and that no other entry moved. Both are onegit diffaway in this PR and neither is inferable from a tree. That is the bar this map has always used — every ceiling in it is an author's number, read at review against the diff — so the declaration weakens nothing; it is the first raise path in the file a machine can check at all.Condition (b) is belt and braces, and the header says so: over declarations whose participants are disjoint (which the total check requires) it follows from (a) summed over the moves. It is written out because it is the ruling's own wording and because it is the condition that would still be right if (a) were ever loosened.
Lifecycle, deliberately asymmetric
A declaration keeps working after its PR lands.
wasreads as PAID DOWN and passes. A move declaration must never red a shrink.Self-test
--self-testgoes 111 to 136 cases (+25). The four the brief names, plus the boundary and lifecycle cases: a legal move passes; a raise whose sources did not shrink is RED; a raise exceeding the net decrease is RED even by one line, while a raise exactly equal to it is legal; a declaration citing no ruling is RED however sound its arithmetic; a declaration netting positive fails the map-wide total; one source may not pay for two destinations; a destination lowered back reads as paid down; a source grown back re-opens the move; an unknown participant is RED, not a skip; a file may not pay its own raise.Verdict lines and exit codes are unchanged. Two lines are added to the run's output: the per-move verdict, and the map-wide total (printed even at zero declarations, so "the move audit ran" is visible rather than assumed).
2. The exception —
scripts/check-ratchet-remedy-authority.mjsThe #8435 convention asks who may take a registry-growing remedy on a shrink-only ratchet, and the answer has always been "a maintainer, per instance". The ruling authorises one class of act generally instead, and the gate's self-description now records it.
Three calls worth reading rather than counting:
excluded— the shape its assertion (17) exists to catch. A new self-test assertion pins the exception phrase as present in source and absent from every author-facing message. This reverse-verified itself on the first attempt: the assertion reddened because the phrase was spelled as a plain literal in the test, which put it into author-facing text. It is assembled from parts now, the way the file's existingADDhoist is, and for the same reason.readdirofscripts/, and the ratchet the ruling amends lives one directory down inscripts/pm/. It has never been in this sweep, so no verdict of this gate moves — before and after are the same OK line, byte for byte. A second new assertion pins that, so widening the walk reds here and makes the class a decision someone takes rather than one that lands in silence.marked/refused/unmarked/excludedstay as they are. A fifth class for "authorised by standing ruling" would have a population of zero in this corpus, and an unreachable class is a claim of enforcement nobody has — the same defect that gate's control corpus exists to prevent, one level up.3. The first move — three per-repo readings consolidate
Site by site. Every source span was measured at this branch's base
f3ae441fa2, and the destination was grepped for the fact first, exactly as the flight brief asks.in_progressis not a pass and advisory red intomainis shared damagereferences/lanes/cli.md:17-20(4 lines)platform-readings.md队列成员资格 (门禁放行判据 = 承载门禁族 job 的 conclusion …). The six names,in_progressand the advisory clause: nocheck:type-check-debtcan red while a package typecheck is green;check:i18nexiting 1 on PREREQUISITE NOT MET is not driftreferences/lanes/services.md:21-22(2 lines)references/rest-channel.md:79-83(5 lines)No pointer sentence is left at any source. Nothing at the three sites becomes unreadable without the moved sentence:
cli.mdalready routes queue readings toplatform-readings.mdin the bullet above the deleted one;services.mdkeeps two independent standing commitments;rest-channel.mdkeeps four queue-routing bullets and its own header already refuses to hold a second copy of whatplatform-readings.mdowns. A pointer per move is how a corpus grows while its ledger says it shrank.Per-file arithmetic
references/lanes/cli.mdreferences/lanes/services.mdreferences/rest-channel.mdreferences/platform-readings.mdCondition (a): the destination rises +10 against a net source decrease of 11. Condition (b): the whole ceiling map goes 5112 to 5111, down one line — measured by summing
CEILINGSat base and at head, not asserted.Reported rather than smoothed: lines fall by one while bytes rise by 36 and tokens by 9. The ruled condition is stated in lines and the ratchet is a line ratchet, so the condition holds — but the byte direction is the honest reading and it is here rather than buried. The 36 bytes are the navigational clause the new bullet carries in place of the rule it declines to restate (pointing at the destination's own queue section instead of copying it); the three facts themselves shrank. Token counts use
countTokensfromcheck-skills-token-ratchet.mjs, the catalog's own instrument, not an estimate.Ceiling map diff
Each carries its own comment citing the ruling and naming the other participants, in the register the map already uses.
MAX_TABLE_ROW_BYTESis untouched: all four files pin at 0 and the moved text carries no table row.The declaration itself:
4. Reverse verification
Three mutations against the committed implementation, each with its landing proved on disk (a grep count of the removed text and of the injected text, never an editor's exit code), each restored with
git checkout HEAD -- {absolute path}and the restore proved by agit hash-objectmatch against the HEAD blob plus an emptygit diff HEAD. The script carries atrapon EXIT/INT/TERM with absolute paths.lanes/cli.md31 to 35)+10against a net decrease of7, and the total reds at+3324to326)+12against115. Gates
Union derived at this head with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands. Derivation line:Exit codes were captured by redirect before any pipe, and each verdict below is the gate's own line.
node scripts/check-ci-filter-parity.mjsOK: all 133 declared cross-package glob(s) (93 unique) are covered …node scripts/check-cross-package-test-inputs.mjsOK: 25 package(s) read outside themselves, all declared …node scripts/check-self-test-wired.mjs✓ every one of the 164 script(s) CI runs that ship a --self-test has that self-test run by CInode scripts/check-shard-attestation.mjs✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s)node scripts/check-test-completeness.mjsPREREQUISITE NOT MET— see below. NOT MEASURED, not a rednode scripts/check-whole-set-label-write.mjs✓ 0 violations — 250 file(s) over 3 root(s) …node scripts/pm/bare-root-worklist.mjs --self-testOK self-test: 57 live row(s), 49 unreachable as spelled, 49 recorded verdict(s) — none stale …pnpm --filter @objectstack/lint run check:doc-formula-expressions✓ 9 @example(s) judged clean across 1120 packages/spec/src files— first run exited 3 (PREREQUISITE NOT MET), prerequisite supplied, re-run to a real verdictpnpm check:agent-test-spelling✓ 0 violations — 430 file(s) · 5705 bare -- token(s) …pnpm check:bash32-floor✓ 26 tracked shell file(s) … name no bash 4+ constructpnpm check:cli-command-ids✓ 317 command-id literal(s) across 113 file(s) … all resolvepnpm check:cross-package-test-inputsOK: 25 package(s) read outside themselves, all declaredpnpm check:doc-authoring✓ 14546 customer-facing string(s) across 710 spec sources cleanpnpm check:entry-guard✓ 199 scripts/ file(s) — every entry guard goes through invoked-as.mjspnpm check:parse-guard✓ 198 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjspnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1241 cases passpnpm check:pm-governed-merges✓ check-governed-merges --self-test: 243 assertions …pnpm check:pm-skill-id-lint✓ 23 file(s) clean (pattern /#[0-9]{3,}/g)— the moved text carries no issue idpnpm check:pm-skill-ratchet✓ cross-file move into … platform-readings.md: +10 (314→324) against a net source decrease of 11 …; authorised by #14685 item 5 (comment 5520452691)and✓ declared cross-file moves: 1, total ceilings down 1 line; self-test136 cases passpnpm check:pnpm-filter-targets✓ 142/181 --filter occurrence(s) across 32 file(s) resolvepnpm check:ratchet-remedy-authorityOK 184 scripts swept; 12 mark the expanding remedy ⛔ MAINTAINER-ONLY, 6 turn it down outright, 166 hand out no ratchet-expanding remedy. Control corpus: 27 …— byte-identical to the same line at the base commitpnpm check:skill-frame-sync✓ 4 copies of the decision frame are structurally isomorphic across 3 files(untouched by this PR)pnpm check:watch-hint-literal✓ 47 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the treepnpm check:role-word(named by the flight brief)check-role-word: OK, no new occurrences of the reserved wordnode scripts/check-skills-token-ratchet.mjs(the other binding ratchet)✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratchetedNOT MEASURED, and why
node scripts/check-test-completeness.mjs— NOT MEASURED (exit 3). It grades a savedturbo run testlog and does not run tests; the derived family names it with no argument, which is the branch that has no log to grade. CI tees the log and passes the path on every invocation, so CI behaviour is unchanged. The prerequisite could only be supplied by running the whole test farm, which this diff (two gate scripts and four instruction-surface markdown files, no package source) does not touch. Recorded as the gate's own text asks — NOT MEASURED, not a pass and not a red.dispatch-gates --commandssays so itself: workflows with no path filter are outside the derived list, and one further family matched by path is CI-measured only (it reads the workflow event payload, so no local run can produce a verdict). Nine more families apply only once a changeset exists, and this PR deliberately carries none (skip-changeset: these paths publish nothing from any package). CI is the reading for all of those.pnpm lint(the repo-wide eslint scan) was NOT run, and this is a stated narrowing rather than a silent one. It is not in the derived union for these paths; the diff adds no JavaScript that eslint governs beyond twoscripts/**files whose own gate families all ran green above; and the repo-wide scan is a run CI owns unconditionally. No evidence of scope is claimed for it here.6. Judgement calls
lanes/cli.mdandlanes/services.mdfacts land in one destination bullet. They are the same rule (an aggregate is not a verdict) with different instruments, and the destination register bundles related instruments into one bullet routinely. Splitting them would have cost a second lead-in for no reader gain.7. Governance
Governed surfaces (
.claude/**,scripts/pm/**). Draft only — not flipped ready, not enqueued, no auto-merge, no reviewers requested, no approval. Labelsskip-changeset(these paths publish nothing from any package) andneeds:contract-review. The seat clears the review label at PASS.Generated by Claude Code
Generated by Claude Code