fix(devx): refuse published build records in the tooling gate - #8136
Conversation
`check-published-dist-tooling.mjs` derives its convention from `TOOLING_FILE`, whose artifact side matches a tooling MARKER — a `__tests__/`-class directory or a `test|spec|bench|stories` stem — because every artifact it grades was emitted from a source file somebody wrote. An incremental build record has no such source: it is a compiler by-product, so `PUBLISHED_TOOLING_FILE` matches nothing about `dist/tsconfig.tsbuildinfo` and the one gate built for tooling material in published output would approve a tarball carrying one. Every affected package publishes by directory (`files: ["dist", …]`), and the record names every input path on the machine that produced it. Adds `BUILD_RECORD`, a second artifact-only term beside `PUBLISHED_TOOLING_FILE`, and unions it into `isToolingArtifact`. It is NOT a third alternation in the shared `TOOLING_FILE`: `toolingConventionFrom` extracts exactly two halves out of that regex, so a term added there is dropped here silently; the convention's five other readers walk source trees filtered by `SOURCE_FILE` and would never honour it; and the sibling tsconfig gate turns it into `exclude` patterns, which is meaningless for a file `tsc` writes rather than reads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
|
Standing down on
Every other check on Generated by Claude Code |
|
Armed — squash auto-merge enabled and the PR is in the merge queue,
On MERGED: content probe on re-fetched Generated by Claude Code |
|
LANDED — merged at 2026-09-06T19:06:24Z as Content probe on re-fetched
#7003 closed by the Generated by Claude Code |
Fixes #7003
Direction 3 of the card, and only that: the gate's convention learns to refuse build records. No
tsBuildInfoFilesetting anywhere (option B is still un-adopted, and this PR does not adopt it), nofilesnegation, noturbo.jsonand no.gitignorechange.The shape decision (card ruling 2): a second, artifact-only term
The build-record rule lands beside
PUBLISHED_TOOLING_FILEas its own exportedBUILD_RECORD, not as a third alternation inside the sharedTOOLING_FILE. Three reasons, the first mechanical:toolingConventionFrom()extracts exactly TWO halves out ofTOOLING_FILE.source— the directory alternation and the stem alternation — andPUBLISHED_TOOLING_FILEis rebuilt from those two. A third alternation added over there is dropped here silently: the phantom-dependency gate would change behaviour and this one would not. That is precisely the drift the derivation exists to prevent, so the shared regex is the one place the term must not go.TOOLING_FILEgrades SOURCE files, and a build record is not one. Its five other readers (check-phantom-dependencies,check-doc-example-shared-reader,check-handler-key-read-sites,check-node-esm-load,check-unreferenced-sources) walk source trees filtered bySOURCE_FILE(a JS/TS extension chain), so atsconfig.tsbuildinfoundersrc/would never even be listed. The term would be a declaration none of them honours — declared and unenforced, which is the shape this repo refuses elsewhere.check-published-tsconfig-tooling-exclude.mjsturns that convention into tsconfigexcludepatterns. Excluding a.tsbuildinfofrom a program is meaningless:tscwrites the record, it never reads one as an input.The "derived, never retyped" property is kept in the form that applies to a term with no source counterpart:
BUILD_RECORDis exported once, and the pin assertsisToolingArtifactis exactly the union of the two exported patterns — so a third spelling cannot appear in the gate without appearing in the test.The pattern
Recognised: any basename, at any depth, ending
.tsbuildinfo—tsconfig.tsbuildinfo,tsconfig.build.tsbuildinfo, a bare.tsbuildinfo, and the same names nested (dist/chunks/tsconfig.build.tsbuildinfo), in everyBUILD_OUTPUT_DIRSentry. Anchored to the last path segment, so a directory that happens to be nameda.tsbuildinfodoes not drag its contents in.Why that suffix and not a list of guessed artifacts: it IS this repository's spelling for the thing.
turbo.json's buildoutputsname a recursive glob over*.tsbuildinfoand.gitignoreignores the same one, so a record renamed away from that suffix is already uncached and untracked. Not recognised, and said out loud rather than papered over: a record whosetsBuildInfoFilepoints at an arbitrary name with another extension. Nothing readable from the tarball tells that file apart from an emitted one, and a guessed list would red a clean package.The positive control — the card's own measurement, run
Built the workspace the way the gate does (
turbo run build --filter=!@object-ui/site, 43 tasks successful), then four legs on commit79fe2c6c0. Full transcript is in the report comment on the card.HEAD~1, 0 occurrences ofBUILD_RECORD)@object-ui/types [tooling-in-published-output] packages/types/dist/tsconfig.tsbuildinfoThe mutation is that package's real record (58377 bytes) copied into its published
dist/, sha256 compared against the original;npm pack --dry-run --jsoninpackages/typesthen listeddist/tsconfig.tsbuildinfoin the tarball, which confirms the dispatch's assumption A2 against real pack output rather than by readingfiles. Restore proven by the mutant's absence and an emptygit status --porcelain(the file is untracked, sogit diffsays nothing about it), and by the file count returning to 5923.Leg 1 is the load-bearing one: the gate built for exactly this class was green on the defective tree, with the offending record in the pack list.
The pin (
scripts/__tests__/check-published-dist-tooling.test.ts)New property 8 in the file's header, and five cases in the existing shape:
TOOLING_FILEandPUBLISHED_TOOLING_FILEand visible toisToolingArtifact— the blind spot in one assertion, driven offBUILD_OUTPUT_DIRSso the directory list is not retyped;isToolingArtifactequals the union of the two exported patterns over records, roots, emitted JS, declarations and tooling directories — the derived-never-retyped property for the new term;dist/tsbuildinfo.js,dist/tsconfig.tsbuildinfo.js,dist/a.tsbuildinfo/index.js);auditPackedFilesnames the package AND the file fordist/tsconfig.tsbuildinfoand a nesteddist/chunks/tsconfig.build.tsbuildinfo;MIN_PACKAGESand the wiring cases are untouched and green.Docs
One sentence in
content/docs/guide/ci-cd-pipeline.md: the Published Dist Gate section enumerates what the gate refuses, and that enumeration became false. The build record is added to it with the reason. The command cells pinned by objectui#3653 are untouched.Gates
Green at
79fe2c6c0:vitest run scripts/__tests__/(113 files, 3377 tests) ·pnpm check:published-dist(the four legs above) ·node scripts/check-dist-completeness.mjs(control, objectui#7001 — unaffected, the record does not move) ·node scripts/check-phantom-dependencies.mjs(control) ·node scripts/check-published-tsconfig-tooling-exclude.mjs(control, 34 enforced packages) ·pnpm type-check:scripts·pnpm lint:root·pnpm check:control-bytesplus agrep -naPcontrol-byte self-scan of all three changed paths ·pnpm check:entry-guard·pnpm check:doc-fences/check:doc-types/check:doc-snippets/docs:check-links·node scripts/check-changeset-presence.mjs(no changeset owed: 3 files changed, none published source of a released package, none a manifest whose published contract moved) ·node scripts/check-governed-queue-guard.mjs --teston all three paths (NOT GOVERNED).Live E2E (informational)is red on every branch today for an upstream reason (objectui#7990) and is not this PR's.Session for this work, as prose so it survives a body edit:
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code