…hangeset gate
check-changeset-presence.mjs counted a changed file as a package's source only
when it sat under <pkg>/src/. apps/console/index.html does not: it is the
console's HTML entry, compiled into the published dist/, and it carries two
inline classic scripts that run in every user's browser during parse. A pull
request editing only that file changed shipped console behaviour while the gate
reported "No source of a released package changed in this range".
The population is now the package's published, executable source:
(a) under <pkg>/src/ — unchanged, and still all-of-it; or
(b) <pkg>/index.html, the bundler's HTML entry; or
(c) published verbatim by the package's own package.json `files` list,
minus documentation and licences, which ship in the tarball but are not
behaviour. Clause (a) answers first, so nothing guarded before is guarded less
now. On this tree the widening adds exactly three files —
apps/console/index.html, apps/console/plugin.ts, packages/runner/index.html —
and nothing else in any package directory.
Non-vacuity is executable: the positive leg goes red naming the file, the same
change with a changeset goes green, and four non-shipped neighbours in the same
package directory (public/, a preview page, the README, the vitest config) stay
green so the widening did not become a blanket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
Fixes #5733
Verification union run at
50365b547(the final commit).Where the population test actually is
The card cites
scripts/check-changeset-presence.mjs:416. That line number is stale — the file has been edited since. Located by code (git grep -n 'path.startsWith'), the population test was at line 467 ona1c41c516, insideclassifyChangedPaths:The premise, re-verified on current
origin/mainAppended one HTML comment to
apps/console/index.htmlon a cleana1c41c516worktree and ran the gate unchanged:Exit 0. The premise holds: a shipped, executable file outside a package's
src/changes with no changeset required. That file is compiled into the publisheddist/and carries two inline classic scripts that run in every user's browser during parse.The judgment: what the population should be
Picked by published AND executable — both words load-bearing, because the population has a wrong answer in each direction (too narrow ships undeclared executable code; too wide makes the gate noise people silence with empty changesets). The rule is stated in the file's header in checkable prose and implemented as
isPublishedSource, same clause order.A changed file counts when any of these holds:
PKG/src/— unchanged, and still all-of-it;PKG/index.html, the bundler's HTML entry, compiled into the publisheddist/;package.jsonfileslist publishes it verbatim (apps/console's rootplugin.ts, which is that package'smain).…minus documentation and licences (
*.md,LICENSE*), which ship in the tarball but are not behaviour. Clause (a) answers first and unconditionally, so the subtraction can never remove somethingsrc/already covered — nothing guarded before is guarded less now.On this tree the widening adds exactly three files and no fourth:
apps/console/index.html,apps/console/plugin.ts,packages/runner/index.html. Measured by runningclassifyChangedPathsover every tracked file in the repo: 3330 guarded, 3 of them outsidesrc/.Boundaries stated in the header rather than left as silences —
PKG/public/(published verbatim by Vite, but static, not code),PKG/*-preview.htmlandPKG/demo/(dev-server pages Vite never builds), andpackage.jsonitself (clause (c) reads it, does not match it). Clause (b) tests the name, not the build script's spelling: deriving it from a script would fail silently the day the spelling changes, which is this card's own defect class.No convention was invented that the repo has not settled —
filesis npm's own published-surface declaration andindex.htmlis Vite's documented default build entry; both packages with a rootindex.htmlbuild it (apps/console:tsc && vite build && pnpm build:plugin;packages/runner:vite build).Non-vacuity — executable, all three legs
Run against throwaway fixture repositories in
scripts/__tests__/check-changeset-presence.test.ts(new section 2b), plus the real tree.apps/console/index.htmlchanges, no changeset ⇒ red naming that file.declares 1 changeset).public/manifest.json,preview-gallery.html,README.md,vitest.config.ts) ⇒ green,4 file(s) changed, 0 of them published source. The widening did not become a blanket.Plus a clause table over
isPublishedSource, a clause-(c) leg onplugin.ts, a leg proving afiles-publishedREADME.mdstill does not count, and a real-repo leg pinning the three-files-and-no-fourth answer.These tests can fail
Ablated
isPublishedSourceback to the pre-fixreturn relative.startsWith('src/')(mutation confirmed on disk:ABLATION-5733marker present ×1, removed textrelative === HTML_ENTRYabsent ×0) and re-ran from the repo root:The two new tests that stayed green under ablation are both stays-green assertions (negative control 2, and the published-README leg), which is the correct direction for them; the clause table covers those branches and does go red. The script was restored by an
EXIT INT TERMtrap and verified byte-identical (diff -qclean).Reverse-verification against
apps/console/index.htmlDirection predicted before running: red, naming the file. Observed on the real repo at
a1c41c516+ this fix:Exit 1. Mutation proven on disk before each read (
grep -c 'objectui-5733-PROBE'= 1, byte count 9460 → 9505). Restored withgit checkout HEAD -- apps/console/index.html(not the bare form); restoration proven by emptygit diff HEADfor the path, zero probe hits, and byte count back to 9460.Same legs also run on the real tree for
apps/console/plugin.ts(red naming it) and for five non-shipped files at once (green,0 of them published source).Gate verdicts — each quoted from its own output
All at
50365b547, run from the repo root:scripts/__tests__/check-changeset-presence.test.tsTest Files 1 passed (1)/Tests 38 passed (38)scripts/__tests__/suiteTest Files 64 passed (64)/Tests 1770 passed (1770)check-changeset-presence.mjs(this PR's own diff)✅ No source of a released package changed in this range, so no changeset is owed.check-changeset-fixed.mjs✅ All workspace packages are in the changeset fixed group.check-changeset-no-major.mjs✅ No changeset declares a major bump.pnpm check:control-bytes✅ check-control-bytes: OK (scanned 5016 tracked text file(s); skipped 85 binary).node scripts/check-lint-coverage.mjs✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).node scripts/check-type-check-coverage.mjs✅ type-check coverage: 45/46 via type-check, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.pnpm type-check:scriptseslint --no-inline-configon both changed filesOn the eslint reading: this is not a narrowing. Root
pnpm lintisturbo run lint, a per-package fan-out;scripts/is not a package, sopnpm lintdoes not reach either changed file. The targeted run uses the rooteslint.config.jswith--no-inline-configand reports2files from--format json— the complete lint population for this diff.node-esm-load-gate.ymlis a nightly/dispatch workflow that does a real build and is not a per-PR gate for this diff; it is red in this unbuilt worktree with@object-ui/console: no-build-output — ./plugin.js does not exist after the build, which names a file this diff does not touch (the diff is two files underscripts/plus a changeset).Changeset
The gate says none is owed for this diff (
scripts/is no package's published source), so this is not the infinite-regress case. Added an empty-frontmatter declaration anyway, which is objectui's way of saying "ships nothing" — the gate's own documented first-class pass.The one boundary a reviewer may want to move
PKG/public/is genuinely published — Vite copies it verbatim intodist/, soapps/console/public/manifest.jsonreaches every user. It is excluded here because it is not executable, and demanding a declaration for a favicon swap is the friction that gets a gate answered with empty changesets nobody reads. That reasoning is written into the header as a considered exclusion rather than left silent, so moving the line is a one-clause edit if the maintainer reads it the other way.Out of scope, untouched
check-changeset-fixed,check-changeset-no-major, #5775'slane-job mirror ofreadChangesetState, and the release workflow. No gate was weakened: the only behavioural change to an existing verdict is the summary line's wording, and clause (a) preserves the prior guarded set exactly.Generated by Claude Code