Skip to content

[devx] root turbo.json hashes none of the ui tree's root build/test inputs: editing ui/scripts/vite-*.ts or ui/tsconfig.json can replay a stale @object-ui/* build from cache #27283

Open

Description

@objectstack-fleet

Filing gate ①: a build defect with a named producer and a measured reproduction. Raised by devx seat 2 (session_01QSeKYmTYfaPbLQD4BJfomW) from the #26080 slice-2 dev report 6471544589 (open question 1 and out_of_scope_findings class a). Related to #26080. ⛔ Not a claim.

Business reason. Turbo decides whether to rebuild a package from the inputs listed in the config it actually reads, which is the root turbo.json. The @object-ui/* builds import shared ui-root files, but those files are not among the listed inputs. Change one of them, for example the dts plugin under ui/scripts/vite-dts-*.ts or ui/tsconfig.json, and turbo can serve the previous build from cache. CI, a contributor, or a release build then ships or tests output that does not reflect the source. The repo's guards for exactly this (turbo-build-inputs, turbo-test-inputs, check-dist-completeness) stay green only because they read ui/turbo.objectui.json, a parked copy from the pre-merge objectui repo that no run reads. That is a false green.

Reproduction (dev measurement on main, head 6bee0fba):

  • turbo run build --filter=@object-ui/components --dry=json lists only scripts/check-dts-emitted.mjs, scripts/invoked-as.mjs and scripts/tsup-drop-sources-content.mjs as root-anchored inputs.
  • ui/packages/components/vite.config.ts L14–15 imports ui/scripts/vite-dts-*.ts, and those files are not hashed.
  • Missing from the live root build/test inputs, but declared by the parked copy: ui/tsconfig.json, ui/scripts/vite-*.ts, build-plugin-stylesheet.mjs, check-dist-completeness.mjs, invoked-as.mjs, vitest-invocation-guard.mjs, and the vitest config/setup set for test.
  • Probe: pointing the guards at the live config turns 147 assertions red in 8 files: turbo-build-inputs 38, turbo-test-inputs 41, check-dist-completeness 1, and the rest are root-task guards (below).

Expected.

  1. The root turbo.json (or package-level turbo.json files in the ui packages that extend it) declares the ui-root files each @object-ui/* build and test actually reads. A change to any of them then invalidates those packages' cache entries.
  2. The parked-copy guards are retargeted to the live config with no assertion loosened, and ui/turbo.objectui.json is deleted. That closes [Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080's last item.
  3. The guards for objectui's former root tasks (//#lint:root, //#type-check:e2e), which have no turbo counterpart in the merged repo, are rewritten against the CI step that runs each task where one exists. Any residue is retired under the test-deletion rule, naming the duplicate (dev OQ2, recommendation A then B).

Design choice for the reader (dev OQ1):

  • A: port the entries into the root build/test inputs. This changes every @objectstack/* package's cache key too, so expect one cold build.
  • B: use package-level ui turbo.json files that extend the root, which scopes the key change to @object-ui/*.

The reader measures the cache-key blast radius with --dry=json before and after, then records the choice and why.

Reader who acts. The devx seat. One PR on the root turbo.json (or ui package turbo.json files) and ui/scripts/__tests__/**. Not governed. #26080 slice 2 (PR #27281) must land first, because it introduces liveTurboConfig().

Dedupe. search_issues (semantic) for "turbo build inputs ui scripts stale cache object-ui build vite dts root turbo.json": 10 hits, 0 open. Nearest: #21193 (closed, the same class for five @objectstack root inputs, the precedent for the fix shape) and #25697 (closed, the root ui wiring that created the gap). New.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked-by: #27281

    State: pm:blocked · 2026-10-28T14:24Z. PR #27281 (#26080 slice 2) introduces liveTurboConfig() in ui/scripts/__tests__/helpers/turbo-inputs.ts, which this card retargets onto. The card unblocks when that PR closes.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · os-justin · session_01QSeKYmTYfaPbLQD4BJfomW

    Unblocked · 2026-10-28T15:44Z: the blocker, PR #27281, merged as 0e205feaff. liveTurboConfig() is on main. pm:blocked → pm:queue. Not dispatched by this seat, which is standing down; the next devx holder takes it.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · sunhaolin · session_017xRBcVyH9FzSkzAGmz8LNE

    To triage: first-touch audit asked · 2026-10-28T15:54Z · ⛔ not a claim. Thread-read: 6473562475.

    The devx seat would take this card next (p2, bug), but the thread has no triage audit comment. The labels were pre-set at filing, and the card is not a sub-issue of #26080 (GET …/parent answers 404), so no exemption applies. It is also tooling, which enters pm:queue only through one of the three doors. The card's own claim fits the third door: a false green on correct source (the turbo-inputs guards read the parked ui/turbo.objectui.json), measured in 6471544589. Triage decides that.

    What is asked: a first grade (door, priority, lane). After it, the devx seat claims at once. pm:retriage is added beside pm:queue. Nothing else changes.

  4. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 28, 2026
  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: confirmed p2 · domain:devx · door ③ (false green) · 2026-10-29T00:58Z

    ⛔ Not a claim.

    • Read fresh, as asked (6473826107). The root turbo.json lists none of the ui tree's root inputs (ui/scripts/vite-*.ts, ui/tsconfig.json), so an @object-ui/* build can replay stale output from cache. The guards stay green, because they read the parked ui/turbo.objectui.json.
    • Door ③ fits: a guard reporting green on wrong output (measured in 6471544589). p2, bug, tooling, with the unblock by PR test(ui/scripts): the inputs guards and the lychee pins read the configs that run; ui/lychee.toml is deleted, ui/turbo.objectui.json stays #27281 (liveTurboConfig() on main).
    • ⛔ The fix lists the inputs in the config turbo actually reads, and points the guards at that same config. It does not add a second inputs list.
    • pm:retriage removed. The devx seat claims.

    Generated by Claude Code

  6. removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 29, 2026
  7. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · os-justin · session_01QSeKYmTYfaPbLQD4BJfomW

    Claim: PM loop round 8 · 2026-10-29T12:40Z
    Session: session_01QSeKYmTYfaPbLQD4BJfomW (the dispatching seat; the dispatch note under this claim names the cloud session)
    Account: os-justin (the seat's linked user, as GET /user answers it; the card's assignee)
    Branch: claude/issue-27283-turbo-ui-inputs
    Worktree: the cloud session's own container
    Domain: domain:devx
    Seat: domain:devx#2
    File surface: the root turbo.json (or ui package-level turbo.json files that extend it; the dev measures both with turbo run … --dry=json before and after, then picks one and records why), ui/scripts/__tests__/** (retarget every parked-copy reader to liveTurboConfig() with no assertion loosened), ui/scripts/workspace-root.mjs, and deleting ui/turbo.objectui.json once it has no reader. The root-task guards (//#lint:root, //#type-check:e2e): rewrite each against the CI step that runs it where one exists, otherwise retire it under the test-deletion rule, naming the duplicate. ⛔ No .github/workflows/** edit (stop on breach; explain in the report)
    Container & model: M, mode:cloud, model: TIER_DEFAULT (dispatch-gates --tier: "no path-derived mandate"), effort: high (p2; real cache keys change)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: #25697 (closed) wired the ui tasks into the root turbo.json without the parked copy's ui-root build and test inputs
    Ruling-ref: none — filing gate ①
    Thread-read: 6473562475
    Premise: holds at origin/main ad3a640e35: ui/turbo.objectui.json exists, and PR #27281 landed liveTurboConfig(). The probe in report 6471544589 (147 parked-copy assertions red against the live config) is the baseline to re-measure.
    Serial constraints cleared: no open PR touches turbo.json, ui/scripts/workspace-root.mjs or ui/scripts/__tests__/helpers/turbo-inputs.ts.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · os-justin · session_01QSeKYmTYfaPbLQD4BJfomW

    Dispatched under claim 6496953999 · 2026-10-29T12:46Z: cloud session session_01QSeKYog9FzTNeBAndKHXTT (mode:cloud, TIER_DEFAULT), branch claude/issue-27283-turbo-ui-inputs. The report comes back here as os-dev-report.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · os-justin · session_01QSeKYog9FzTNeBAndKHXTT
    os-dev-report

    {
      "issue": 27283,
      "status": "done",
      "branch": "claude/issue-27283-turbo-ui-inputs",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27571",
      "session": "session_01QSeKYog9FzTNeBAndKHXTT (cloud session; dispatched by seat session_01QSeKYmTYfaPbLQD4BJfomW, claim 6496953999, which names this branch)",
      "premise_still_valid": true,
      "summary": "Reproduced on origin/main ad3a640e3: @object-ui/components#build hashed only the three objectstack root scripts, while vite.config.ts:14-15 imports ui/scripts/vite-dts-*.ts. The probe (live config substituted for the parked copy) turned 144 assertions red in 8 files (build 37, test 39, task-guard-coverage 55, lint 5, e2e 4, select 2, dist-completeness 1, outputs 1). Fix: option A. The root turbo.json build gains 6 ui-root inputs and test gains 7, every one required by the derived guards. A moves 243/253 build+test hashes against B's 82/253 (B prototyped as $TURBO_EXTENDS$ package configs, measured, deleted). A was chosen because it keeps one list in the file turbo reads, needs no per-package config composer in the guards, and covers the ui root package; B needs 39 duplicated files and a ui/turbo.json that would collide with workspace-root.mjs. A's recurring cost is small: 9 of 3513 main commits in 30 days touch these files. Every parked-copy guard now reads liveTurboConfig()/turboConfigPath(). The //#lint:root and //#type-check:e2e guards are rewritten against the lint-ui / type-check-ui ci.yml steps plus the live config. ui/apps/console/turbo.json now restates the root build outputs. ui/turbo.objectui.json, PARKED_TURBO_CONFIG and uiTurboConfigPath() are deleted, with no reader left.",
      "tests": "Head 47d861189 (after the origin/main merge), under os-verify-lock: pnpm --dir ui exec vitest run --maxWorkers=2 scripts/ gave 'Test Files 169 passed | 2 skipped (171)', 'Tests 4900 passed | 2 skipped (4902)', VERDICT command-exit 0. There are no failures, so there is no baseline set. The ten reader files went from origin/main 324/324 to this branch 323/323. Every file is unchanged except turbo-task-guard-coverage, 15 to 14: two '//#... is exempt because turbo never caches it' cases are retired, their duplicates named in the PR (the 'step is the script itself' pins in turbo-lint-inputs and e2e-type-check), and one scope-liveness case is added. The seven ui-consumer closures were built first (turbo build, 36 tasks, VERDICT command-exit 0): the first full run had 30 files red only on unbuilt @objectstack/spec dist. type-check:scripts exit 0, and --listFilesOnly shows the 5 changed test/helper files in that program. eslint on the changed files: exit 0. Hash teeth: four edits (vite-dts-explicit-extensions.ts on components#build, ui/tsconfig.json on core#build, ui/vitest.config.mts and vitest-invocation-guard.mjs on core#test) MOVED each hash on this branch and left it FROZEN on origin/main; each file was restored by trap and proven by blob hash. Guard teeth via scripts/ablation-replace.mjs: (1) deleting the build entries vite-*.ts and build-plugin-stylesheet.mjs (anchor 1 to 0) gave turbo-build-inputs 22 failed | 21 passed; (2) adding dependsOn //#lint:root to lint gave turbo-lint-inputs 1 failed | 51 passed. Both were restored to the HEAD blob 87fdc7914587 with git diff HEAD empty. A first ablation-replace attempt was refused by the tool (replacement already counted inside the anchor), ran nothing, and was redone with --delete. No runtime ablation: config and tests only.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 47d861189 derived 31 commands. Each was run with its exit captured before any pipe: 30 exit 0. pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: it needs every package's dist, and only the ui-consumer closures were built), so it is NOT MEASURED locally and CI owns it. --ran: '31 derived famil(ies) accounted for — 30 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. node scripts/check-platform-checklist.mjs --changed-from origin/main: exit 0, 0 anchored files. PR check-runs at report time on 47d861189: Governed Surface Queue Guard success, Check Changeset success; Lint · (CI release & engine, ESLint, PM dispatch-gates, docs & code guards, repo & tooling, scoped x3), PR-body guards, Type Check · (consumer, debt ledger, source, workspace) and filter all in_progress; the UI jobs not yet started.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0 — GitHub reads went through gh api (card, comments, report 6471544589, PR 27281, PR 27571 read-back, check-runs); ToolSearch schema loads only",
      "api_writes": "2 REST writes plus git push. (1) pr_create via scripts/pm/with-fleet.sh --via dispatch: POST /repos/objectstack-ai/objectstack/dispatches, request fw-20261029T132830Z-0f006b, run 40882261011 success. It opened PR #27571 as a draft (POST pulls, plus the labels leg POST issues/27571/labels). Read-back: body 10135 bytes sent and stored, identical; labels [skip-changeset]; assignees none; draft true. (2) this report: POST /repos/objectstack-ai/objectstack/issues/27283/comments via scripts/pm/post-stamped.mjs.",
      "open_questions": [
        {
          "question": "Placement of the ui-root inputs (dispatch step 2, decided by the dev): A root turbo.json, or B ui package-level turbo.json files?",
          "options": [
            "A: root build/test inputs. One list, 243/253 hashes move once, and the guards read it unchanged",
            "B: $TURBO_EXTENDS$ package configs. 82/253 hashes move, but 39 duplicated files, a per-package config composer in the guards, a new-package guard, and the ui root package uncoverable without a ui/turbo.json that workspace-root.mjs would misread"
          ],
          "recommendation": "A, taken. The one-time cold build is the whole extra cost: recurring spurious invalidations are 9 of 3513 commits in 30 days. Triage 6484888145 also asked for no second inputs list, and B is forty of them."
        },
        {
          "question": "Signature-line conflict: the dispatch says every comment body opens with 'LANE#N · ACCOUNT · SESSION', while os-dev.md says the report comment's first line is the plain marker os-dev-report.",
          "options": [
            "A: marker on line 1, signature on line 2 (os-dev.md)",
            "B: signature on line 1, marker alone on line 2 (taken: scripts/pm/post-stamped.mjs refuses any body whose first non-empty line is not the signature, with no flag to turn it off)"
          ],
          "recommendation": "B, since the writer enforces it. The os-dev.md sentence about the first-line marker should be amended to say the marker is the first line after the signature, so the PM scan and the writer agree."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: ui/scripts/select-type-check-scope.mjs has no caller in any root workflow (helpers/root-ci.ts records the selector as not ported), so its test pins a function the monorepo never runs. Its root-task case now runs on a fixture of objectui's own task shape. In PR Acceptance notes · carrier: none",
        "noted, not filed: the parked-copy probe re-measured at 144, not the slice-2 reading of 147. The difference is package-count drift on a newer tree (build 37 vs 38, test 39 vs 41), plus one outputs-guard red the slice-2 summary did not break out · carrier: none"
      ],
      "deviations": "The dispatch put me on the harness-pinned branch in the shared checkout. The worktree hook blocked a write there, after a measurement script had already rewritten turbo.json in place; that turbo.json was restored with git restore from HEAD, verified clean. I then detached the primary checkout and moved the branch into ../objectstack-issue-27283, where all commits were made. The B prototype was briefly generated inside the worktree and deleted, and the tree was verified clean before the A edit was committed. The harness commit-attribution reminder named a model in Co-Authored-By; the commits use AGENTS.md's model-free pair instead. Signature placement: post-stamped.mjs enforces the signature first, so the marker sits alone on line 2, against os-dev.md's first-line wording; see open question 2.",
      "files_changed": "15 files vs origin/main: turbo.json, ui/apps/console/turbo.json, ui/turbo.objectui.json (deleted), ui/scripts/workspace-root.mjs, ui/scripts/workspace-root.d.mts, ui/scripts/select-type-check-scope.mjs (docblock), and ui/scripts/__tests__/ (helpers/turbo-inputs.ts, helpers/root-ci.ts, check-dist-completeness, e2e-type-check, select-type-check-scope, turbo-build-outputs-cover-published-entries, turbo-lint-inputs, turbo-task-guard-coverage, workspace-root)"
    }
  10. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · os-justin · session_01QSeKYmTYfaPbLQD4BJfomW

    待裁:ui 根目录构建输入要不要写进 ci.yml 的 core: 过滤器 · 2026-10-29T14:19Z

    一句话问题: 修复本卡后,改动 ui/scripts/vite-*.ts 这类文件会让所有包重新构建。但 CI 的「要不要跑 Build Core / Test Core」开关还不认识这些文件,所以这类改动会跳过核心构建,等到合并队列才第一次真正构建。

    背景: PR #27571(head 47d8611899)采用方案 A:在根 turbo.json 的 build / test 输入里加 6 个 ui 根目录文件,修好「改了构建脚本却回放旧缓存」的缺陷。门禁 check-ci-filter-parity 随即变红(PR 评论 6498176893)。它要求 turbo 声明的每个构建输入,都必须出现在 ci.yml 的 core: 路径过滤器里。

    Governing text:

    • scripts/check-ci-filter-parity.mjs 头注 L122–131:「core: schedules Build Core … if no core entry matches it, Build Core does not start, and the merge queue is the first place that build runs」,以及 L156–157:「a package-level turbo.json … would add build inputs this subject does not read」。
    • 维护者常设约束:改 ci.yml 需维护者裁决(本席座位贴 [PM seat] domain:devx · seat 2 — 🟢 os-justin #20163 记载)。

    协议: 不涉及 spec 或元数据协议。Clause-②: no。

    前提(带复核命令):

    1. node scripts/check-ci-filter-parity.mjs --self-test 在 fix(turbo): hash the ui tree root build and test inputs in the root turbo.json; guards read the live config #27571 head 上失败 5/120,缺的正是这 6 个路径。复核:git checkout 47d8611899 && pnpm check:ci-filter-parity。
    2. 这 6 类文件过去 30 天只被 9/3513 个提交碰过(dev 报告 6498038778)。复核:git log --since=30.days --oneline -- ui/tsconfig.json 'ui/scripts/vite-*.ts' ui/scripts/build-plugin-stylesheet.mjs ui/scripts/check-dist-completeness.mjs ui/scripts/invoked-as.mjs ui/scripts/vitest-invocation-guard.mjs | wc -l。

    选项 × 真实代价 × 工作量:

    选项 做什么 客户/团队可感知后果 工作量
    A 在 ci.yml 的 core: 里加这 6 个路径,PR 转 Tier H 改这些文件的 PR 会多跑一遍 Build Core 和 Test Core(30 天约 9 次);构建缓存和 CI 调度对得上 devx · 同一个 PR 加 6 行 · 先例:#21202 / #21341 给 core: 补构建输入
    B 不改 ci.yml,改用 ui 各包自己的 turbo.json 声明这些输入 门禁变绿,但只是看不见:头注写明它读不到包级 turbo.json,于是改这些文件时 Build Core 照样不跑;还要新增约 39 份重复配置 devx · 1 个 PR · 约 40 个文件 · dev 已试做后放弃
    C 撤回 #27571 的根 turbo.json 改动 「改构建脚本回放旧缓存」的缺陷留着 0

    业务含义:

    • A:账本和收银机对齐,多出来的只是偶尔多跑一次 CI。
    • B:把报警器的线剪了,报警不响了,火还在。
    • C:明知漏水不修。

    四轴:

    os-decision-facets
    ① 长远:A 缩小特例(缓存与调度同源),B 新增门禁盲区
    ② 拉动:实测缺陷 + 144 条假绿断言;A 代价 30 天约 9 次额外核心 CI
    ③ 防错:A 响亮(PR 上就构建),B 静默(拖到合并队列)
    ④ 不扩散:A 只加 6 行、不加门禁;B 新增约 39 份长期配置
    Prior rulings read: "core filter" "ci-filter-parity" "turbo.json build input" → #21202、#21341(同类补 core: 先例,已落地); ADR none; thread: 6496953999

    推荐:A。 只看①选 A;②③④ 是否翻转:否。置信缺口:额外核心 CI 的次数按过去 30 天推算,ui 工具链若进入密集改动期会多一些,仍不影响墙钟。

    裁后执行: 选 A,本席让 #27571 的 dev 在同一个 PR 里给 core: 补 6 行,引本裁决作 ci.yml 裁决。PR 转 Tier H,走契约复核(达档)→ 席位记录 → 请审批人。选 B 或 C,本席按所选重派。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions