Skip to content

fix(devx): refuse published build records in the tooling gate - #8136

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-7003-tooling-gate-build-records
Sep 6, 2026
Merged

fix(devx): refuse published build records in the tooling gate#8136
baozhoutao merged 1 commit into
mainfrom
claude/issue-7003-tooling-gate-build-records

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #7003

Direction 3 of the card, and only that: the gate's convention learns to refuse build records. No tsBuildInfoFile setting anywhere (option B is still un-adopted, and this PR does not adopt it), no files negation, no turbo.json and no .gitignore change.

The shape decision (card ruling 2): a second, artifact-only term

The build-record rule lands beside PUBLISHED_TOOLING_FILE as its own exported BUILD_RECORD, not as a third alternation inside the shared TOOLING_FILE. Three reasons, the first mechanical:

  1. It would not arrive. toolingConventionFrom() extracts exactly TWO halves out of TOOLING_FILE.source — the directory alternation and the stem alternation — and PUBLISHED_TOOLING_FILE is 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.
  2. TOOLING_FILE grades 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 by SOURCE_FILE (a JS/TS extension chain), so a tsconfig.tsbuildinfo under src/ 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.
  3. check-published-tsconfig-tooling-exclude.mjs turns that convention into tsconfig exclude patterns. Excluding a .tsbuildinfo from a program is meaningless: tsc writes 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_RECORD is exported once, and the pin asserts isToolingArtifact is exactly the union of the two exported patterns — so a third spelling cannot appear in the gate without appearing in the test.

The pattern

export const BUILD_RECORD = /(^|\/)[^/]*\.tsbuildinfo$/;
export const isToolingArtifact = (path) => PUBLISHED_TOOLING_FILE.test(path) || BUILD_RECORD.test(path);

Recognised: any basename, at any depth, ending .tsbuildinfotsconfig.tsbuildinfo, tsconfig.build.tsbuildinfo, a bare .tsbuildinfo, and the same names nested (dist/chunks/tsconfig.build.tsbuildinfo), in every BUILD_OUTPUT_DIRS entry. Anchored to the last path segment, so a directory that happens to be named a.tsbuildinfo does 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 build outputs name a recursive glob over *.tsbuildinfo and .gitignore ignores 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 whose tsBuildInfoFile points 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 commit 79fe2c6c0. Full transcript is in the report comment on the card.

leg tree gate exit reading
0 clean, built post-change 0 39 packages, 5923 tarball files, 0 tooling artifacts
1 defective pre-change (HEAD~1, 0 occurrences of BUILD_RECORD) 0 5924 files, 0 tooling artifacts — the card's "would ship silently" claim, measured
2 defective post-change 1 @object-ui/types [tooling-in-published-output] packages/types/dist/tsconfig.tsbuildinfo
3 restored post-change 0 back to 5923 files, 0 tooling artifacts

The mutation is that package's real record (58377 bytes) copied into its published dist/, sha256 compared against the original; npm pack --dry-run --json in packages/types then listed dist/tsconfig.tsbuildinfo in the tarball, which confirms the dispatch's assumption A2 against real pack output rather than by reading files. Restore proven by the mutant's absence and an empty git status --porcelain (the file is untracked, so git diff says 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:

  • a build record is invisible to BOTH TOOLING_FILE and PUBLISHED_TOOLING_FILE and visible to isToolingArtifact — the blind spot in one assertion, driven off BUILD_OUTPUT_DIRS so the directory list is not retyped;
  • isToolingArtifact equals 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;
  • names that merely contain the word are clean (dist/tsbuildinfo.js, dist/tsconfig.tsbuildinfo.js, dist/a.tsbuildinfo/index.js);
  • auditPackedFiles names the package AND the file for dist/tsconfig.tsbuildinfo and a nested dist/chunks/tsconfig.build.tsbuildinfo;
  • a record at the package root — today's real location for all 30 composite packages, and the state option B would change — is NOT a finding.

MIN_PACKAGES and 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-bytes plus a grep -naP control-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 --test on 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

`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

Copy link
Copy Markdown
Contributor Author

Standing down on Live E2E (informational) for this PR, read 2026-09-06T18:38Z.

Every other check on 79fe2c6c0 is judged on its own; this PR flips to ready once the required set converges, then goes to the merge queue by squash.


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 6, 2026 18:50
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Armed — squash auto-merge enabled and the PR is in the merge queue, domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46.

  • Head 79fe2c6c0: 28 non-informational checks completed green at 18:50Z (the ci-wait reading); Live E2E (informational) red per the standing-down note above.
  • ready_for_review at 18:50:18Z; the post-flip Governed Surface Queue Guard run started after that timestamp and completed success at 18:50:34Z.
  • added_to_merge_queue at 18:50:42Z on the REST timeline (read 18:50:48Z). The queue is otherwise empty.

On MERGED: content probe on re-fetched origin/main (export const BUILD_RECORD in scripts/check-published-dist-tooling.mjs and isToolingArtifact as the union; the *.tsbuildinfo clause in the ci-cd-pipeline.md row; control: PUBLISHED_TOOLING_FILE's definition unchanged), then the LANDED note here and the closing stroke on #7003.


Generated by Claude Code

Merged via the queue into main with commit dd883d1 Sep 6, 2026
30 of 31 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-7003-tooling-gate-build-records branch September 6, 2026 19:06

Copy link
Copy Markdown
Contributor Author

LANDED — merged at 2026-09-06T19:06:24Z as dd883d169 (squash, via the merge queue), domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46.

Content probe on re-fetched origin/main (dd883d169, read 19:06:57Z):

  • export const BUILD_RECORD = /(^|\/)[^/]*\.tsbuildinfo$/; at scripts/check-published-dist-tooling.mjs:241 and isToolingArtifact as the union at line 249; the *.tsbuildinfo clause is in the ci-cd-pipeline.md row (1 hit).
  • Control: PUBLISHED_TOOLING_FILE's definition is unchanged against 336a9eb8a.

#7003 closed by the Fixes at merge time; the seat strips pm:dispatched and clears the assignee there in the same stroke.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(build): relocating tsBuildInfoFile into outDir would ship it in twelve tarballs — and the tooling gate cannot see it

2 participants