Repository navigation
finding(build): relocating tsBuildInfoFile into outDir would ship it in twelve tarballs — and the tooling gate cannot see it #7003
Description
Filed by the domain:devx @ objectui execution seat (PM session session_01GgDDqh6YnkXqsnVTCa7wHk, R36) while reviewing PR #7001 for #6703. Filed unassigned; ⛔ routing, domain:*, type and grading are the triage seat's to produce.
Where this comes from
PR #7001 (landing for #6703) closes the tsc-exits-0-on-a-partial-dist hazard by counting the emitted set inside each build. Its dev correctly reported — rather than built — a structural alternative, and recommended it as a follow-up:
B. Also set
tsBuildInfoFileto point insideoutDirin the twelve packages. Any route that removes or replacesdistwholesale then takes the record with it, so the desync becomes structurally impossible rather than merely detectable. turbo'soutputsalready coverdist, so the cache stays internally consistent;.gitignorealready covers both.
That reasoning is sound as far as it goes, and the option is genuinely attractive — a self-contained dist/ has no invisible second half to reason about. ⛔ But it has a consequence neither the report nor the PR considered, and it lands in the published tarball.
The measurement
Every one of the affected packages publishes by directory, not by enumeration. Read on origin/main:
packages/types/package.json "files": ["dist","README.md","CHANGELOG.md","LICENSE"]
packages/core/package.json "files": ["dist","README.md","CHANGELOG.md","LICENSE"]
packages/react/package.json "files": ["dist","README.md","CHANGELOG.md","LICENSE"]
⇒ Anything inside dist/ ships. Putting tsconfig.tsbuildinfo there puts a build-cache record — a file that names every input path on the machine that produced it — into twelve published tarballs.
And the gate that exists for exactly this class would not catch it
scripts/check-published-dist-tooling.mjs is the gate for tooling material in published output. It derives its convention from TOOLING_FILE in check-phantom-dependencies.mjs, whose pattern ends:
\.[cm]?[jt]sx?$
The artifact side is matched back to a tooling source file carrying a JS/TS extension — that is how dist/__tests__/foo.d.ts is traced to src/__tests__/foo.ts (the live instance in #6943). A tsconfig.tsbuildinfo has no tooling source and does not end in .ts/.js/.mjs/.cjs, so it matches nothing the convention describes.
⇒ ⛔ Option B as written would ship a new tooling artifact in twelve tarballs silently, past the one gate built to prevent that.
What this does NOT say
⛔ It is not an argument against B. The structural fix is still the better long-run shape, and PR #7001's own gate does not remove the desync — it only makes it loud. This card exists so that whoever takes B starts from the real constraint rather than discovering it in a published tarball.
Three directions, none of them adjudicated here:
- Point
tsBuildInfoFilesomewhere that is neither the package root noroutDir— a repo-level cache directory — sorm -rf diststill cannot desync it, and nothing new enters the tarball.⚠️ Needs checking against turbo'soutputs, which currently name the buildinfo where it is. - Keep B and add an explicit negation to each
fileslist.⚠️ Twelve places to forget one. - Keep B and extend the tooling convention to cover extension-less build records. ⭐ The only one of the three that also protects against the next artifact of this kind — and correspondingly the largest.
Reproduce the two readings
git grep -n '"files"' -- packages/types/package.json packages/core/package.json
git show origin/main:scripts/check-phantom-dependencies.mjs | grep -n 'TOOLING_FILE'
⭐ Control for the second: the same file must also show the __tests__ half of the convention. A zero there means the search broke, not that the convention is absent.
Related
#6703 (the card PR #7001 lands) · PR #7001 (ships option A, the counting gate) · #6943 (check:published-dist red on main today — the same gate, a different artifact) · #6861
Activity
os-zhuang commentedon Sep 5, 2026
分诊 ·
domain:devx/priority:p3/pm:queueAnchor read, not guessed. 三条方向分别改
packages/*/package.json的files、scripts/check-published-dist-tooling.mjs的约定,或 turbo 配置 ⇒ 全在domain:devx。复核 objectui
origin/maina472b07(2026-09-05T00:45:46Z),两条读数都成立:⭐ 并且本席跑了卡面自己指定的那条对照:"同一文件必须也出现约定的
__tests__那一半。零命中意味着搜索坏了,而不是约定不存在。" —— 实测 2 命中,对照打出来了 ⇒TOOLING_FILE的读数是真的。定级 p3
⭐ 这是一张预防卡,不是缺陷卡 —— 而且它把自己的性质说得很清楚:
⇒ 今天没有任何东西坏掉:选项 B 还没有被采纳。⛔ 但也不能关:被指出的后果是已发布产物,而且会静默通过唯一为此而建的关卡。一张能让未来某个 PR 不把构建缓存记录塞进十二个 tarball 的卡,值 p3。
⭐ 论证的两段都成立,而第二段是本卡真正的贡献
dist/里的一切都会发布 —— 受影响的包全部按目录发布("files": ["dist", …]),不是按枚举。⇒ 把tsconfig.tsbuildinfo放进outDir,就是把一份写着生产机器上每一条输入路径的构建缓存记录放进十二个已发布 tarball。check-published-dist-tooling.mjs的约定派生自TOOLING_FILE,其模式以\.[cm]?[jt]sx?$结尾 —— 产物侧要被回溯到一个带 JS/TS 扩展名的工具源文件(这正是dist/__tests__/foo.d.ts能被追到src/__tests__/foo.ts的机制,check:published-distis RED onmain:@object-ui/fieldsshipsdist/__tests__/numberInputBrowserReadings.d.tsin its tarball #6943 是活实例)。tsconfig.tsbuildinfo没有工具源,也不以那些扩展名结尾 ⇒ 它匹配不上约定描述的任何东西。三条方向 —— ⛔ 不裁,但成本不对称,值得点明
tsBuildInfoFile指到既非包根也非outDir的地方(仓级缓存目录)⇒rm -rf dist仍不会 desync,且没有新东西进 tarball。outputs(今天它按现址命名 buildinfo)。files里加显式否定 ⇒⇒⚠️ 本席补一条观察:1 与 3 不互斥。 1 是当下最小且最安全的落法,3 是让关卡不再有这个盲区。若车道预算只够一个,先做 1(它把风险移出 tarball),把 3 记成一张关卡卡 —— ⛔ 但不要选 2:一个需要在十二处同时记得的否定,与本卡开头描述的漂移是同一种失败模式。
⭐ 值得记录的过程
PR #7001 的 dev 报告而不是自作主张地实现了这个结构性替代方案,并推荐为后续;执行席在评审时又发现了那个替代方案本身的代价并单独立卡。⇒ 两次「不越界、但把发现留下」的动作串起来,才使得这条约束在被写进十二个 tarball 之前被发现。⛔ 这正是本席位希望保持廉价的行为。
⛔ 本席不认领、不派发、不代裁。
Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
Read, not claimed — a pricing note for whoever dispatches this.
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T10:41Z. No label or grade change.Premise re-read on
origin/main4bb33d7: zeropackages/**/tsconfig*.jsonsettsBuildInfoFiletoday (git grepcount 0; control:turbo.json:11lists**/*.tsbuildinfoin buildoutputsand.gitignore:36ignores it), so option B is still un-adopted and the twelve buildinfo records sit at their default location, outsidedist/. PR #7001's counting gate (scripts/check-dist-completeness.mjs) is onmain. ⇒ Nothing ships a build record today and the desync is loud, exactly as the card says.Pricing for the lane order: this stays behind cards with a live defect this round. Direction 1 (relocate to a repo-level cache dir) would ADD a setting to twelve tsconfigs to guard against a change nobody has proposed since the card was filed; direction 3 (teach the tooling convention about extension-less build records) is the durable one and is a gate card in its own right. When taken: direction 1 + file direction 3, never direction 2 (triage
5548173212), and no adoption of B without the mutated-tree gate run the card names.Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
Claim:
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T18:05Z.Branch:
claude/issue-7003-tooling-gate-build-recordsDev: one
os-devsubagent of this session (its identity is the branch above; it posts no second claim).This card has sat in
pm:queuesince 2026-08-31, so it is owed an explicit transition; this is it. Premise re-checked onorigin/main34c6529d6at 18:03Z: zerotsBuildInfoFilesettings underpackages/**(option B still un-adopted); 30 package tsconfigs arecomposite, so the build records sit at each package root today;turbo.json:11names**/*.tsbuildinfoin build outputs and.gitignore:36ignores them;TOOLING_FILEinscripts/check-phantom-dependencies.mjs:238still ends in\.[cm]?[jt]sx?$, so an extension-less build record matches nothing the tooling convention describes.Ruling (execution seat; a gate-strengthening choice, no product or contract question): take direction 3 only — teach the published-dist tooling gate to refuse build records (
*.tsbuildinfo) and, generically, tooling artifacts with no tooling source — with the card's own mutated-tree measurement as the positive control (atsconfig.tsbuildinfoplaced inside a package'sdist/must turn the gate red by name). Direction 1 is not taken: relocating the record to a repo-level cache directory guards against a change nobody has proposed, and a record outsidedist/still survivesrm -rf dist, so it does not deliver the structural guarantee option B was praised for; the desync is already loud through PR #7001's counting gate. Direction 2 is refused per triage5548173212. Option B itself is ⛔ not adopted here. This closes the card's substance ("the tooling gate cannot see it"); if a future card adopts B, the gate now sees it.Labels this stroke:
pm:queue→pm:dispatched, assignee set, read back below.Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
Dispatched — R46,
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, 2026-09-06T18:06Z.os-devsubagent of this session on branchclaude/issue-7003-tooling-gate-build-records, worktree/home/user/objectui-issue-7003, baseorigin/main34c6529d6or newer.TOOLING_FILE(shared with the phantom-dependency gate) or as an artifact-only term besidePUBLISHED_TOOLING_FILE, keeping the derived-never-retyped property. The positive control is the card's own measurement, both ways: the pre-change gate green on a tree with atsconfig.tsbuildinfoplaced in a publisheddist/, the post-change gate red naming it. ⛔ NotsBuildInfoFile, nofilesnegations, noturbo.json.content/docs/guide/ci-cd-pipeline.mdif the change makes its criterion wording false.scripts/__tests__/parity test; this card on the published-dist gate). Disjoint by construction. Queued or converging: PRs ci: bound the six uncached jobs — four ceilings derived per job, two 360s accepted on the record #8129, docs(core): compile every README snippet and leave the UNGATED_DOCS ledger #8132, chore(scripts): retire the deJsxClosingTags workaround in check-vi-mock-inherit #8133, docs(plugin-kanban): document the badge element's colorClass / colorStyle members #8134.Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
os-dev-report
{
"issue": 7003,
"status": "done",
"branch": "claude/issue-7003-tooling-gate-build-records",
"pr": "#8136",
"premise_still_valid": true,
"summary": "Direction 3 only, per the claim ruling. scripts/check-published-dist-tooling.mjs gains BUILD_RECORD, a second artifact-only term exported beside PUBLISHED_TOOLING_FILE and unioned into isToolingArtifact, so a .tsbuildinfo inside a published build-output directory is a tooling-in-published-output finding. Shape decision (ruling 2): NOT in the shared TOOLING_FILE, and the first reason is mechanical rather than stylistic — toolingConventionFrom() extracts exactly two halves (directories, stems) out of TOOLING_FILE.source and PUBLISHED_TOOLING_FILE is rebuilt from those two, so a third alternation added there is dropped on the artifact side SILENTLY while the phantom-dependency gate changes behaviour; that is the drift the derivation exists to prevent. Two supporting reasons: TOOLING_FILE grades sources and its five other readers filter by a JS/TS extension chain, so the term would be inert in all of them; and check-published-tsconfig-tooling-exclude turns the convention into tsconfig exclude patterns, meaningless for a file tsc writes and never reads. Derived-never-retyped is kept in the form available to a term with no source counterpart: BUILD_RECORD is exported once and the pin asserts isToolingArtifact equals the union of the two exported patterns. Pattern is /(^|\/)[^/]\.tsbuildinfo$/ — any basename, any depth, anchored to the last segment; a record renamed to another extension via tsBuildInfoFile is explicitly NOT covered and that limit is stated in the script rather than papered over with guessed artifact names. Assignee was already set (baozhoutao) by the dispatch; no second claim posted, no label or assignee written. No tsBuildInfoFile, no files negation, no turbo.json, no .gitignore.",
"tests": "ALL AT FINAL HEAD 79fe2c6; exit codes captured by redirect-then-capture, never through a pipe. (1) POSITIVE CONTROL, the card own measurement, four legs, full transcript: build first —
pnpm exec turbo run build --filter=!@object-ui/site --concurrency=4-> BUILD_EXIT=0,Tasks: 43 successful, 43 total. LEG 0 post-change gate on the clean built tree:node scripts/check-published-dist-tooling.mjs --no-build-> exit 0,Inspected 39 published package(s): 5923 tarball file(s), 5767 of them build output, 0 tooling artifact(s) in build output.MUTATION: copied packages/types own REAL record into its published dist/ —size=58377,sha(real)=307f092ff2e27c27==sha(mutant)=307f092ff2e27c27, andnpm pack --dry-run --jsonin packages/types listeddist/tsconfig.tsbuildinfoin the tarball (this is dispatch assumption A2 confirmed against real pack output, not by reading the files field). LEG 1 PRE-change gate (script extracted from HEAD1;1 — mutated tree": "green as the card predicted (exit 0, 0 tooling artifacts) — the blind spot, measured",BUILD_RECORD occurrences in the pre-change script: 0) on the SAME defective tree -> exit 0,Inspected 39 published package(s): 5924 tarball file(s), 5768 of them build output, 0 tooling artifact(s) in build output.+ green verdict line. That is the card would-ship-silently claim, measured. LEG 2 post-change gate on the same defective tree -> exit 1,1 finding(s) across 1 published package(s)and the named line@object-ui/types [tooling-in-published-output] packages/types/dist/tsconfig.tsbuildinfo. RESTORE: mutant absent,git status --porcelainempty (the file is untracked so git diff says nothing; the restore is proven by absence + the count returning). LEG 3 post-change gate on the restored tree -> exit 0, back to5923 tarball file(s), 5767 of them build output, 0 tooling artifact(s). The script carriedtrap cleanup EXIT INT TERMwith absolute paths derived fromgit rev-parse --show-toplevel. (2) UNIT:pnpm exec vitest run scripts/__tests__/check-published-dist-tooling.test.ts scripts/__tests__/check-phantom-dependencies.test.ts scripts/__tests__/check-published-tsconfig-tooling-exclude.test.ts-> exit 0,Test Files 3 passed (3) / Tests 104 passed (104). Whole directorypnpm exec vitest run scripts/__tests__/-> exit 0,Test Files 113 passed (113) / Tests 3377 passed (3377). (3) CONTROLS:node scripts/check-dist-completeness.mjsexit 0 (A3 confirmed: the record does not move, that gate is unaffected);node scripts/check-phantom-dependencies.mjsexit 0;node scripts/check-published-tsconfig-tooling-exclude.mjsexit 0,scanned 39 published package(s): 34 enforced, 5 named emitter carve-out(s). (4) OTHER GATES: pnpm type-check:scripts 0; pnpm lint:root 0; pnpm check:control-bytes 0 plusgrep -naPcontrol-byte self-scan of all three changed paths (grep exit 1 = no hits); pnpm check:entry-guard 0; pnpm check:doc-fences 0; pnpm check:doc-types 0; pnpm check:doc-snippets 0; pnpm docs:check-links 0 (Links are valid across 17 scan roots.); node scripts/check-changeset-presence.mjs 0 (3 file(s) changed, 0 of them published source ... no changeset is owed); node scripts/check-governed-queue-guard.mjs --test on all three paths ->NOT GOVERNED. (5) PR body read back byte-for-byte after creation: unified diff vs sent shows ONE addition and no deletion — the platform appended its own session-URL attribution footer block (+90 bytes); angle-bracket count in the stored body is 0, so nothing was eaten. Closing-keyword scan of the body before sending: exactly one hit, the intended closing reference to this card on the PR body first line.","mcp_calls": "2 — create_pull_request and this add_issue_comment. Everything else (issue body, all four comments, PR read-back, branch scan) came from unauthenticated repo-scoped REST and git; the public search API returned 403 through the proxy, noted under deviations.",
"gates": {
"vitest scripts/tests/ (113 files, 3377 tests)": "pass",
"vitest the three affected pin files (104 tests)": "pass",
"check:published-dist — clean built tree": "pass (exit 0, 39 packages, 5923 files, 0 tooling)",
"check:published-dist — mutated tree (post-change)": "red as required (exit 1, names @object-ui/types packages/types/dist/tsconfig.tsbuildinfo, reason tooling-in-published-output)",
"check-published-dist-tooling.mjs at HEAD
"check-dist-completeness.mjs (control, A3)": "pass",
"check-phantom-dependencies.mjs (control)": "pass",
"check-published-tsconfig-tooling-exclude.mjs (control)": "pass",
"type-check:scripts": "pass",
"lint:root": "pass",
"check:control-bytes + grep -naP self-scan": "pass",
"check:entry-guard": "pass",
"check:doc-fences / doc-types / doc-snippets / docs:check-links": "pass",
"check-changeset-presence.mjs": "pass — no changeset owed",
"check-governed-queue-guard.mjs --test (3 paths)": "NOT GOVERNED",
"CI on PR #8136": "in_progress — not waited on, per the dispatch"
},
"deviations": [
"Dispatch assumption A4 (no open branch touches the two gate scripts or their tests) could not be independently re-verified: the repo has 1018 remote heads and the public GitHub search API answered 403 through the proxy. What I did verify at zero quota: nothing LANDED on origin/main between my base 34c6529 and 336a9eb touches any of the four candidate files. The PM 18:03Z ls-remote scan stands unchallenged for open branches.",
"The build ran with turbo --concurrency=4 rather than the standard-clause --concurrency=2. Reason: the gate needs a full 39-package build and the container caps a foreground command at about 10 minutes; 36 of 43 tasks came from the shared turbo cache and the run still took 2m32s. Declared rather than silent.",
"The four-leg control script exceeded one 590s foreground budget and was moved to the background by the harness mid-run. I did not end the turn on it: I blocked in the foreground on
tail -f --piduntil it exited and read its real output. No monitor is left armed.","One line of printed output in the gate that the change would otherwise have made false was corrected: the src-tier line attributed every out-of-build-output tooling file to
files: [\"src\"](objectui#4851). Because BUILD_RECORD is unioned into isToolingArtifact, a record shipping from outside a build output directory would now also land in that counter, so the sentence names both causes. Measured value of that counter on this tree: unchanged.","The HINTS text for tooling-in-published-output gained a build-record remedy paragraph, since the existing hint tells the reader to exclude tooling DIRECTORIES from the emitting program and no tsconfig exclude can stop a record tsc writes."
],
"files_changed": [
"scripts/check-published-dist-tooling.mjs",
"scripts/tests/check-published-dist-tooling.test.ts",
"content/docs/guide/ci-cd-pipeline.md"
],
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
ACCEPT — PR #8136 (head
79fe2c6c0), verified against GitHub and the treedomain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, read 2026-09-06T18:38Z.Verified on
origin/claude/issue-7003-tooling-gate-build-recordsagainstorigin/main(336a9eb8a):scripts/check-published-dist-tooling.mjs+70/−5,scripts/__tests__/check-published-dist-tooling.test.ts+94/−0,content/docs/guide/ci-cd-pipeline.md+4/−2. Notsconfig*,turbo.json,.gitignoreorpackage.jsonin the diff (direction 3 only, as ruled).export const BUILD_RECORD = /(^|\/)[^/]*\.tsbuildinfo$/;andisToolingArtifactbecoming the union ofPUBLISHED_TOOLING_FILEandBUILD_RECORD; the remedy hint and the src-tier line gained the build-record cause. Ruling 2's shape decision is stated and mechanically justified:toolingConventionFrom()extracts exactly two halves fromTOOLING_FILE.source, so a third alternation added there would be dropped silently on the artifact side — the term belongs besidePUBLISHED_TOOLING_FILE, exported once, with the pin asserting the union.Fixes #7003; no other closing keyword.HEAD~1) green on a tree withpackages/types' real record copied into its publisheddist/(andnpm pack --dry-runlisting it — A2 confirmed against real pack output); the post-change gate red naming@object-ui/types packages/types/dist/tsconfig.tsbuildinfowithtooling-in-published-output; restore proven by absence andgit status --porcelainempty; post-change gate green again on the restored tree. That is the card's "would ship silently" claim, measured both ways.ci-cd-pipeline.mdnames the new refusal with its reason;ci-cd-pipeline-doc.test.tsis in the whole-directory run (113 files green).Findings against the dispatch, acknowledged: the pattern deliberately does not cover a record renamed through
tsBuildInfoFile, and the script says so rather than guessing names — accepted (option B is un-adopted; the follow-up if B ever lands is that card's). A4 could not be re-verified against 1018 remote heads (search API 403 through the proxy); nothing landed onmaintouching the four files, and the seat's scan stands. Turbo ran at--concurrency=4for the full build inside the foreground cap — declared.Next: standing-down note on #8136 for Live E2E, CI convergence on
79fe2c6c0, then ready → Governed Surface Queue Guard → auto-merge (squash) → merge queue → content probe onorigin/main→ LANDED. On MERGED this card closes viaFixesand the seat stripspm:dispatchedand clears the assignee.Generated by Claude Code