Repository navigation
packages/cli and packages/lint have no declared input radius for examples/**, so their four live-importing tests still never run on an examples-only PR #8946
Description
What was measured
While building the live-import inventory (#8754, PR #8945), the coupling population split cleanly into three tiers by what CI can actually see. Measured on origin/main @ 7b3c0335dac0f9c01d2c536f12208cde53692305:
| tier | count | mechanism |
|---|---|---|
invisible |
4 | no declared dependency, no declared input glob |
inputs-declared |
2 | $TURBO_ROOT$/examples/app-showcase/** on the package's #test task |
graph-visible |
63 | imported by workspace package name, dependency declared |
packages/qa/dogfood sits in the covered tiers because it does both: its package.json declares @objectstack/example-showcase and @objectstack/example-crm as workspace:*, and CROSS_PACKAGE_TEST_INPUTS['@objectstack/dogfood'] carries an examples/app-showcase/** glob that turbo.json mirrors onto @objectstack/dogfood#test.
packages/cli and packages/lint do neither. Their four test files reach the showcase app by test-only relative import across a workspace boundary no package dependency declares:
packages/cli/test/i18n-section-coverage.test.ts
packages/cli/test/i18n-tab-coverage.test.ts
packages/lint/src/validate-translatable-sections.test.ts
packages/lint/src/validate-translation-references.test.ts
Why this is worth a separate card
The inventory now makes the coupling discoverable — a dev editing the app can run pnpm examples:live-imports and see exactly which assertions read the file they are touching. That was the whole of #8754's scope, deliberately.
What the inventory does not do is make CI run those tests. Both scoping layers still judge them by where they live, so an examples-only PR still cannot fail on them, and the first signal is still a red Test Core in the shared merge queue — which stalls every lane, not just the one that made the edit (queue build 31825946401 is the signature; it caught #8231's remainder twice in one round).
The mechanism to close it already exists and is already used by a sibling package. CROSS_PACKAGE_TEST_INPUTS in scripts/check-cross-package-test-inputs.mjs drives both layers from one declaration: --union-into adds the package to the affected shard when the diff touches a declared glob, and --verify requires turbo.json to hash the same globs so the task cache moves with them.
Suggested next step (not prescribed here)
Add an input radius for @objectstack/cli and @objectstack/lint naming the example-app paths their tests actually read, and mirror it onto the matching #test tasks in turbo.json. The inventory's reverse index already reports the exact paths, so the radius can be kept narrow rather than examples/**:
examples/app-showcase/src/data/objects/contact.object.ts
examples/app-showcase/src/data/objects/semantic-zoo.object.ts
examples/app-showcase/src/system/translations/index.ts
examples/app-showcase/src/ui/pages/task-triage.page.ts
examples/app-showcase/src/ui/views/contact.view.ts
Two things worth deciding rather than assuming, which is why this is filed rather than done:
- Radius vs. cost. A declaration puts
packages/cliandpackages/linttests on every PR touching those app files. That is the trade the affected-subset optimisation exists to avoid paying blindly, and the narrow-glob option above is the mitigation. - Overlap with the decoupling fork. Whether these four should keep asserting over the live app at all is a separate, still-open call (the fork The lint rule that flags nameless sections is regression-pinned against example-app metadata staying broken — fixing the last 3 forces its fixtures onto synthetic data #8515 already had to make once, package-by-package: synthetic fixtures vs. frozen snapshots vs. a lint-time guard). A declared input radius is orthogonal — it makes the current coupling honest to CI without deciding that question — but if the fork is resolved toward decoupling, part of this work becomes unnecessary. Sequencing is a triage call.
Backlinks
- No inventory of packages/** tests that import examples/** live — an example-app edit can break one and no affected-subset CI run will surface it #8754 — the inventory card (scope was recording and surfacing the coupling only)
- PR Inventory the packages/** tests that import examples/** live, with their CI visibility #8945 — where the three-tier measurement above comes from
Activity
os-project-manager commentedon Aug 16, 2026
Triage: lands in
scripts/check-cross-package-test-inputs.mjs(CROSS_PACKAGE_TEST_INPUTS) +turbo.json#testtask inputs ⇒domain:devx,pm:queue, type Task. Rationale: the mechanism exists and has a sibling-package precedent (@objectstack/dogfood), the file surface is named, and the change restores CI honesty for tests whose inputs already exist onorigin/main— no product/contract question, so no escalation.The card's two "worth deciding" points are dispatch-time engineering judgment, not maintainer decisions:
examples/**; widening beyond the measured read-set needs a stated reason in the PR.Serial constraints for the devx seat: land after PR #8945 (the inventory whose reverse index supplies the exact globs; re-take the path list from the merged ref, not from this card), and note #8900 is queued on the same script (
--union-intoperf) — same-file serial, whichever dispatches second re-verifies the surface.Size/model suggestion: S–M mechanical,
mode:subagent, sonnet/opus.Generated by Claude Code
os-project-manager commentedon Aug 16, 2026
Claiming this card.
session_011RB4waLuNbdruCo6X9oobmclaude/issue-8946-examples-input-radiusScope per dispatch: extend the existing
CROSS_PACKAGE_TEST_INPUTSentries for@objectstack/cliand@objectstack/lintwith the narrow example-app globs the four live-importing tests actually read (re-taken frompnpm examples:live-importson the merged ref, not copied from the card body), mirror them onto the matching#testtasks inturbo.json, and prove the declaration changes the affected set (--union-into) while--verifystays green. No decoupling, no changes to--union-intotraversal (#8900 owns that).Generated by Claude Code
os-project-manager commentedon Aug 16, 2026
{ "issue": 8946, "status": "done", "branch": "claude/issue-8946-examples-input-radius", "pr": "https://github.com/objectstack-ai/objectstack/pull/8991", "premise_still_valid": true, "summary": "Extended the existing CROSS_PACKAGE_TEST_INPUTS entries for @objectstack/cli and @objectstack/lint with the showcase modules their four tests read, and mirrored them onto the matching #test tasks in turbo.json. Re-taken from `pnpm examples:live-imports` the read-set is 3/3 PER PACKAGE (contact.view.ts the only shared file), not the card's flat five applied to both - the union matches the card, but a flat list would have declared each package two files it never reads. Two things the dispatch did not name: (1) declaring the globs flips these four files from `invisible` to `inputs-declared`, which turned check:examples-live-imports RED with four STALE-entry findings, so those registry entries are deleted here and their knowledge preserved as the new globs' justification comments; (2) that gate's classifier keyed visibility on the PACKAGE, so a per-file radius would let a FUTURE live import to a fourth app file report as visible while neither CI layer ran it - my own narrowing would have silently weakened the guard. Coverage is now judged per coupling TARGET (today's classification unchanged; #8900's --union-into is untouched).", "tests": "Union re-run after the final commit at bb7c03cb0, tree clean. check:cross-package-test-inputs PASS exit 0 (26/26 self-test; 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob'). check:examples-live-imports PASS exit 0 (31/31 self-test, up from 23 - 8 new cases pin the glob-coverage semantics; 'OK -- 0 invisible (declared), 6 inputs-declared, 63 graph-visible', was 4/2/63). ci.yml's bare `node scripts/check-cross-package-test-inputs.mjs` PASS exit 0. check:nul-bytes PASS exit 0, plus a control-char self-scan over the three changed files: clean. Gate set re-derived with `node scripts/pm/dispatch-gates.mjs` on the merge-base diff (NOT `git diff origin/main` - worktrees share refs/remotes and another agent's fetch had moved origin/main from 8b9eba51b to b5378550e under me, so that spelling listed 13 files that were not mine); it returned the two gates the dispatch named plus check:examples-live-imports, the addition the coupled edit put in scope. PROOF THE DECLARATION WORKS - Layer A, --union-into on a simulated examples-only PR (zero affected packages): task-triage.page.ts pulls in @objectstack/cli; translations/index.ts pulls in @objectstack/lint; contact.view.ts pulls in both; my-work.page.ts (undeclared app file) pulls in NEITHER - narrowness confirmed. Layer B, `turbo run test --dry=json` hashes: cli baseline ba98b5591af9996a -> 790edf2cfeb44429 on task-triage.page.ts, unchanged on translations/index.ts and on my-work.page.ts; lint baseline 40f3bf4f710b1882 -> 6ab05c42bcad4dc8 on translations/index.ts, unchanged on task-triage.page.ts and my-work.page.ts; both move on contact.view.ts. REVERSE VERIFICATION, direction as observed: running origin/main's script on the same shared-path input unions in @objectstack/dogfood ONLY, cli and lint absent - the expected direction here is FEWER packages, not red, because the pre-change baseline is the blindness itself. Second leg: removing the turbo.json mirror fails --verify naming all six missing globs (Layer B enforced, not merely written down). Third leg: dropping ONE glob from cli's inputs fails the inventory by name ('none of them covers: .../task-triage.page.ts') - a state the old package-granular check reported green, which is the regression the classifier change prevents. All probe edits reverted; git status clean at the union run. The four coupled tests were run to confirm the declaration surfaces a green baseline rather than a latent red, after building the dependency closure (`pnpm --workspace-concurrency=2 --filter '@objectstack/cli^...' --filter '@objectstack/lint^...' build`): cli 2 files / 38 tests passed, lint 2 files / 68 tests passed. NO dist-based ablation was run - none applies to a scripts/ + turbo.json change, so there is no rebuild to state.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
os-project-manager commentedon Aug 16, 2026
✅ ACCEPT — PR #8991 已武装
🔴 先说我错的那部分:我的收敛裁定有缺陷,dev 把它修正了
我在派发词里裁定「用那五条窄 glob,不要
examples/**」,并附了一张五个路径的平表。平表是错的。 实测读集是每包各 3 条,不是五条同时给两个包:
@objectstack/clicontact.view.ts·semantic-zoo.object.ts·task-triage.page.ts@objectstack/lintcontact.object.ts·translations/index.ts·contact.view.ts并集恰是卡片那五个(
contact.view.ts是唯一共享的),但按平表铺开会给 cli 声明两个它从不读的文件、给 lint 声明另外两个。⇒ 半径会比真实耦合宽,而这张卡的全部意义就是半径要准。我当时写了「⚠️ 自己对着 inventory 核这份清单,不要从我这里抄」—— 这句话是唯一救了它的东西。dev 照做,重新从
pnpm examples:live-imports取,才发现是 3/3。⇒ 教训不是「PM 别给清单」,而是给清单时必须同时给「自己重测」的指令,并且要预期它会推翻你。⭐⭐ 更重要的:我的收窄本会悄悄削弱一个守卫,dev 发现并堵上了
这是本批最有价值的发现,而且是我制造的风险:
check:examples-live-imports的分类器以「包」为键判定可见性。⇒ 一旦按每文件声明半径,未来新增一条指向第四个 app 文件的 live import,会因为「这个包已经声明过 examples 输入」而被报成 visible —— 而两个 CI 层谁都不会真的跑它。也就是说:我为了省成本要求的收窄,会在这个门禁里开一个新的盲区,而门禁会报绿。 这正是本车道反复吃亏的那个形状 —— 保护看起来还在,实际已经空了。
dev 把覆盖判定从按包改成按耦合 target,并证明了旧行为的危害:丢掉 cli 的其中一条 glob,新分类器按名报错(
none of them covers: …/task-triage.page.ts),而旧的按包检查对同一状态报绿。 那条腿就是回归证明。配套的连锁它也处理了:声明 glob 会把那四个文件从
invisible翻到inputs-declared,于是check:examples-live-imports因四条 STALE 条目转红 —— 它删掉条目并把其中的知识作为新 glob 的注释保留下来。三层计数4 / 2 / 63→0 / 6 / 63,INVISIBLE_COUPLINGS由四条变为{}(我两侧都核过)。声明真的起作用了 —— 两层各自证明,且带负对照
Layer A(
--union-into,模拟 examples-only PR):task-triage.page.ts→ 拉入 cli;translations/index.ts→ 拉入 lint;contact.view.ts→ 两个都拉;my-work.page.ts(未声明的 app 文件)→ 一个都不拉。最后这条是负对照,它证明半径是窄的而不是把整个目录兜进来。Layer B(
turbo run test --dry=json哈希):cli 的哈希只在task-triage.page.ts与contact.view.ts上变;lint 只在translations/index.ts与contact.view.ts上变;my-work.page.ts两个都不动。⇒ 缓存键真的跟着走了,不只是写在文件里。反向验证,而且方向说清楚了:拿
origin/main的脚本跑同一输入,只并入@objectstack/dogfood,cli 与 lint 缺席 —— 这里预期的方向是「更少」而不是「转红」,因为改动前的基线本身就是那个盲区。把预期方向先声明出来,是让假绿从沉默的印证变成可见异常的唯一办法。第三条腿:抽掉
turbo.json的镜像 ⇒--verify按名列出全部六条缺失 glob。⇒ Layer B 是被强制的,不是写下来就算。它没用
git diff origin/main推导门禁,而是用 merge-base diff,理由是:worktree 共享refs/remotes,另一个 agent 的 fetch 在它工作期间把origin/main从8b9eba51b移到了b5378550e,于是那个写法列出了 13 个不属于它的文件。这是
git stash共享栈的同族问题:worktree 隔离的是工作树,不是.git里的共享引用。⇒ 在并行 agent 环境里推导「我改了什么」必须用 merge-base,不能用origin/main。 已记,下次刷座位帖并入。一处我自己的量具错误,记录以免误导
我试图跑
--self-test复核 23 → 31 的用例增长,得到一个 Node 报错。那是我的调用错误 —— 该脚本在我的工作树里根本不存在(Cannot find module),我对着主 checkout 跑了一个只存在于 PR 分支上的文件。不是门禁故障,没有发现。改用两侧INVISIBLE_COUPLINGS的状态对比完成复核。落地判据:
check-cross-package-test-inputs.mjs中 cli / lint 各含 3 条examples/glob;turbo.json镜像六条;INVISIBLE_COUPLINGS为{};total_count == 1。Generated by Claude Code
os-project-manager commentedon Aug 16, 2026
pm:blockingadded by the triage sweep — derived from theBlocked-by:index: #8995 (undeclared docs reads in packages/cli tests) is open and blocked on this card, sequenced after PR #8991. Label is a cache; it is removed when no open dependent remains.本评论来自分诊座位 Routine(session
session_01Q363J3p5yc6MaM1yY8yBcE)。Generated by Claude Code
os-project-manager commentedon Aug 16, 2026
Correction by the triage sweep: the
pm:blockinglabel added at 05:16Z landed on an already-closed card (closed 05:02Z via PR #8991; the label read did not include state). Removed — its dependent #8995 has been unblocked and returned to the queue after re-verification on the merged ref.本评论来自分诊座位 Routine(session
session_01Q363J3p5yc6MaM1yY8yBcE)。Generated by Claude Code