From d80e9f0b10da9374d30c9b5003ef2ea5af17926e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 18:55:41 +0000 Subject: [PATCH] docs(gates): measure the changeset LEVEL axis's src/** over-read, and pin the proxy as a decision The LEVEL axis reads "did this PR grow the package's published surface?" from where the changed file lives. That proxy over-reads, and the gap was filed without a rate. Measured over the axis's whole life -- born c3b63f7add (2026-09-06), 449 commits landed, 233 of them introducing a changeset entry and moving a package the axis calls grown: * the over-read is common: 50 of 358 (package, PR) pairs, 14.0% * its cost is zero: not one false refusal, with the declaration forced to `yes` on every PR, and `refusable` is computed before the declaration is read, so the same zero covers the NOT MEASURED lane * the instance the card was filed on no longer refuses either -- it reads `discharged` at exit 0 under the rule that landed after it So neither remedy is bought, and no verdict changes here. The cheaper remedy that was left open -- exempt a file no entry point re-exports -- is separately recorded as measured UNSOUND: it holds only for a bundled build, and packages/cli emits its whole program, so a module nothing imports still ships. An instrument that skipped that distinction reported twelve false refusals for that package which reading the build mode collapsed to zero. Three self-test rows pin the proxy as a decision, with a nonsense control, so that exempting unreachable files costs a deliberate deletion and a fresh rate. Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude --- scripts/check-changeset-no-major.mjs | 105 +++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/scripts/check-changeset-no-major.mjs b/scripts/check-changeset-no-major.mjs index 4ac280bfe7..09a7d6f9dc 100644 --- a/scripts/check-changeset-no-major.mjs +++ b/scripts/check-changeset-no-major.mjs @@ -918,6 +918,63 @@ export function render(result) { // merged PR, the thing it exists to find. ⛔ That count is a reading for the // maintainer, never an argument for a tolerance: there is no allowlist and no // grandfathering here, and the six are cleared by declaring, not by softening. +// +// ## THE LEVEL: `src/**` as a proxy for a public face — measured, left alone (#16985) +// +// Everything above decides WHICH packages this axis can see. This section is +// about what it then asserts about one: that a moved `packages/**/src/**` byte +// means the package's published surface grew. Those two facts do come apart — +// a module under `src/**` that no entry point reaches ships nothing at all — +// and #16985 filed the gap, correctly refusing to prescribe a repair before +// somebody produced a RATE. Its one instance (PR #16694, whose only `rest` +// change was `packages/rest/src/rest-route-ledger.ts`, a review record nothing +// imports) had an exit, and one instance is not a rate. +// +// MEASURED over the axis's WHOLE life, so the population is the thing itself +// rather than a sample of it: the axis was born at `c3b63f7add` (#16264, +// 2026-09-06) and 449 commits landed to `3c5f3c5991`, 233 of them introducing +// a changeset entry AND moving a package this axis calls «grown». Method as in +// the DEPTH note above — drive this file's own `scan`, `packagesTouched` and +// `judgeLevel` at each merge commit against its parent — with one reading added +// per package: does this diff ship anything? That question is answered from the +// build each package actually runs, never from the path (see +// `PUBLISHED_SOURCE_ROOT` for why the distinction is the whole ballgame). +// +// * THE OVER-READ IS COMMON. 50 of 358 (package, PR) pairs — 14.0%, spread +// across 37 of the 233 PRs — name a package the diff ships NOTHING for. 35 +// of the 50 moved only `src/**/*.test.ts`, which a bundled package never +// emits; 12 moved only modules no entry point reaches; 3 moved both. +// * ITS COST IS ZERO, and that is the number that sizes the repair. 47 of the +// 50 never reach the offender list at all — the package is graded `minor`+, +// or is not graded. THREE do. Two of those sit beside a co-offender that +// genuinely ships (PR #17020, PR #17095), so the refusal is earned and the +// over-read costs a line in the message, not a verdict. The third is #16694 +// itself, which on TODAY's rule reads `discharged` at exit 0: the +// `@objectstack/client: minor` in its own changeset discharges the PR under +// #16361, which landed after it. ⇒ Not one false refusal exists in the +// axis's entire history, including the instance the card was filed on. +// * THE ZERO IS DECLARATION-INDEPENDENT, which is what makes it a bound and +// not a coincidence of who declared what. It is measured with the +// declaration forced to `yes` on EVERY PR — the maximally exposed +// assumption, stronger than anything that actually happened — and +// `refusable` is computed before the declaration is consulted, so the same +// zero covers the `not-measured-material` lane too. +// +// ⇒ Triage sized the repair in advance: an exemption list if the false red is +// rare, an exported-surface reading if it is common. It is neither — it is +// ABSENT — so neither instrument is bought, and the proxy stands. #16361 +// already took the half of this that was real by deleting the per-line claim +// from the rendering, which is why the residual is two noisy lines rather than +// a reader mistaking the pair for the finding. +// +// ⛔ This is a reading for the maintainer, never a verdict that the proxy is +// SOUND. It says the repair is unbought AT TODAY'S RATE. The shape that would +// buy it is named and still unobserved: a PR genuinely `Clause-②: yes` for +// package A that also moves a package-internal line under package B's `src/**` +// — there the author has no exit at all, because a carrier alone forces `yes` +// (see `declarationFromPullRequest`) and only the review seat can clear it. Two +// of the 128 refusable PRs already have the mixed offender shape; none has the +// declaration to go with it. ⇒ Re-measure before repairing, not instead of it. /** * The compiled-source root whose movement makes a package's PUBLISHED surface @@ -931,6 +988,26 @@ export function render(result) { * this axis outright, and print a tick while doing it: the exact shape #16692 * and #16713 both document. ⇒ The packed set is the reason for a SECOND leg * below, never a replacement for this one. + * + * ⛔ Nor is the repair "exempt a file no entry point transitively re-exports", + * the cheaper alternative #16985 left open — MEASURED UNSOUND, and unsound by + * the same asymmetry that condemns the packed-set predicate. It holds only for + * a BUNDLED package: `tsup` builds from `src/index.ts`, so a module nothing + * reaches contributes no emitted byte. `packages/cli` does not bundle. It runs + * `tsc -p tsconfig.build.json`, which emits the whole `include` program, so a + * `src/**` file nothing imports STILL ships as its own `dist/*.js` — which is + * why that tsconfig carries an explicit `exclude` line for a review-record + * module, and says in prose that its bundled siblings needed none. It is the + * only whole-program public package here, 1 of 69, and one is enough: an + * entry-graph exemption applied uniformly would turn this axis OFF for exactly + * the package where it must not be, printing a GREEN — the expensive + * direction, and the same failure #16692 and #16713 each landed to undo. + * + * ⚠️ That is measured rather than reasoned. While sizing #16985 an instrument + * that skipped the build mode reported TWELVE false refusals for + * `@objectstack/cli`; reading the build mode collapsed them to zero. An + * exemption written from the same blind spot would have shipped that error as + * a rule. */ const PUBLISHED_SOURCE_ROOT = 'src'; @@ -3198,6 +3275,34 @@ function selfTest() { 'nonsense control on the superset run: a manifest under every ancestor must NOT make an ordinary file owned — otherwise the four rows above would hold for any input at all', ); + // ── #16985: `src/**` is owned WITHOUT asking what re-exports it ──────── + // + // These two rows are a DECISION, not an oversight, and they are here so + // that undoing it costs a deliberate deletion. The over-read they pin is + // real and was measured rather than denied — 14.0% of (package, PR) pairs + // over the axis's whole life name a package the diff ships nothing for — + // and it is KEPT because that over-read has never once changed a verdict + // (zero false refusals, declaration-independent) and because the cheaper + // repair is unsound for a whole-program build. Both readings, with their + // method and their controls, are in the header; the unsoundness is at + // `PUBLISHED_SOURCE_ROOT`. ⇒ An edit that exempts unreachable files must + // delete these rows and bring a rate that buys the change. + const noManifest = () => null; + assert( + JSON.stringify(publishedSourceOwners('packages/rest/src/rest-route-ledger.ts', noManifest)) === '["packages/rest"]', + '#16985: a module no entry point re-exports is owned all the same — the proxy is deliberate, and an entry-graph exemption is a false GREEN for a whole-program `tsc` package', + ); + assert( + JSON.stringify(publishedSourceOwners('packages/rest/src/rest-route-ledger.test.ts', noManifest)) === '["packages/rest"]', + '#16985: a test file under `src/**` is owned too — 35 of the 50 measured over-reads are this shape, kept for the same reason and refused an exemption on the same evidence', + ); + // Nonsense control on THIS pair, so the two rows above cannot be read as + // "any path at all is owned": one segment over, nothing is. + assert( + JSON.stringify(publishedSourceOwners('packages/rest/rest-route-ledger.ts', noManifest)) === '[]', + '#16985 control: the same file name outside `src/**` is owned by nobody — the two rows above are about `src/**`, not about the name', + ); + // ── End to end, on real temp git repositories ───────────────────────── const manifestJson = (bin) => JSON.stringify({ name: CLI, version: '0.0.0', files: ['dist', 'README.md'], bin }); const levelOf = ({ dir, base }) => {