Skip to content

Commit 137781f

Browse files
os-billclaude
andauthored
fix(devx): hold the cross-package gate's bare-specifier boundary to its own premise (#18667)
Fixes #18236 Clause-②: no `File surface:` — one path, `scripts/check-cross-package-test-inputs.mjs`. No `turbo.json`, no `.changeset/*.md`, no `packages/spec/src/**`, no governed surface. Why each of those stayed shut is measured below, not asserted. ## The card's premise, re-taken on `origin/main` — and one half of it is falsified The card's mechanism is: `packages/cli/test/format-zod-union.test.ts` consumes `ListViewSchema.sort` through the **bare** specifier `@objectstack/spec`, that specifier is excluded by the gate's import-side boundary, therefore the coupling is invisible to **both** the affected-subset filter and the turbo cache. The first two links hold — the import is bare (`:37`) and the gate does skip it. The third does not, and it is the one the fix would have rested on. Measured on `62d830e54e`, turbo 2.10.10, with a firing control beside each reading: | reading | result | |---|---| | `turbo ls --affected` for a diff touching ONLY `packages/spec/src/ui/view.zod.ts` | **76 of 81** packages, `@objectstack/cli` among them | | the same document after the real `--union-into` | 76, unchanged (`No cross-package scan declares inputs touched by this diff.`) | | `@objectstack/cli#test` turbo hash, view.zod.ts pristine | `09d6dde83abb1ddf` | | the same hash, view.zod.ts mutated (marker count 0 to 1 on disk) | `2d8ebbbb4202c0e5` — **moves** | | CONTROL: the same hash, an unrelated package mutated instead (`packages/qa/dogfood/test/action-params-contract.dogfood.test.ts`, marker count 0 to 1) | `09d6dde83abb1ddf` — unmoved, so the instrument is specific | | **PM 补的 Layer A 亮控**(复核时第一手加测):同一条 `turbo ls --affected` 钉 `TURBO_SCM_BASE=HEAD` 后,**干净树读 0 packages**;只改 `view.zod.ts` 读 76,含 `@objectstack/cli` | ⚠️ **不钉 base 时干净树也读 76** —— 上面第一行那个 76 单独看不区分「改了」和「没改」;钉了 base 之后结论不变 | ⇒ **the PROBE the card asks for already passes, before any change.** Layer A reaches `@objectstack/cli` through the dependency graph, and Layer B moves because `@objectstack/cli#test` dependsOn its own `build`, which dependsOn `^build`, which folds `@objectstack/spec#build`'s hash in. `packages/cli/package.json` has declared `"@objectstack/spec": "workspace:*"` throughout, including at `1e20f816e^` (checked on that tree, not recalled). ## Why that answers all three of the seat's questions at once, with numbers Measured over all 3714 test sources under `packages/`, `apps/` and `examples/`: | | | |---|--:| | test files importing a workspace sibling by BARE specifier | 2010 | | bare workspace specifier occurrences | 3692 | | distinct (importer, imported) package pairs | 349 | | distinct importing packages / imported packages | 66 / 55 | | **pairs NOT backed by an edge on the importer's manifest** | **0** | 1. **How should bare specifiers be admitted?** They should not. A bare specifier resolves only because the importing package declares the target — that is what pnpm's strict linking means — and that declaration is an edge on the graph both CI layers already walk. All 349 pairs have one. 2. **Can turbo express it?** It does not have to. The coupling is already in turbo's own dependency graph, so `turbo.json` needs no new `inputs` entry and this diff touches none. 3. **Does the fix put every package's suite on every sibling?** The card's route 2 would: the natural glob is `packages/TARGET/src/**`, so 349 declarations across 66 packages pointing at 55 siblings' whole source trees — the explosion the boundary was written to prevent — for **zero** coverage, since the graph carries every pair. Refused on that measurement. The affected subset after this PR is byte-for-byte the subset before it: **0 declarations added, 0 packages moved.** ## What IS wrong, and what this PR changes The boundary asserts a conclusion ("a bare specifier is an installed dependency, never a repo source input") while nothing checks its premise. That premise fails the moment a bare specifier names a workspace package the importer does **not** declare, and such an import still resolves whenever something else supplies it — a vitest `resolve.alias`, a hoisted `node_modules`, a tsconfig path. Then there is no manifest edge, no graph edge, no glob and **no flag**: #7802 by a sixth spelling, arriving through the one door this file had declared safe on reasoning rather than on a check. So the exclusion stays and becomes conditional, with the condition enforced every run: > A bare specifier is excluded from the radius roster BECAUSE it is an installed dependency. Every > bare specifier naming a workspace package must therefore be declared as one by the package > importing it. `bareWorkspaceImportCensus()` is its own walk, not a second output of `findEscapingPackages()`: that walk opens with a `FS_READ`-or-ascending-literal pre-filter which a file importing only bare specifiers never passes — precisely the file this question is about. The verdict line now prints the live census, so the limb's steady-state silence is a measurement rather than an absence: ``` OK: 29 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3 on ACCEPTED_WALK_RADII; 2010 test file(s) import a workspace sibling by bare specifier over 349 package pair(s), every one declared. ``` Those two numbers are produced by the gate and agree with the independent scan above, which was written first and separately. ## Red before green — named gate, shown red, on the incident file itself The gate that can go red here is `pnpm check:cross-package-test-inputs`, and the ablation deletes the one manifest edge the premise rests on. Committed first, then mutated, then restored. | leg | on-disk proof | `check:cross-package-test-inputs` | |---|---|---| | baseline | — | **exit 0**, the OK line above | | mutated: `"@objectstack/spec": "workspace:*"` deleted from `packages/cli/package.json` | deleted-text count **1 to 0**; `git diff HEAD --name-only` 0 to 1 line; the mutated manifest still parses as JSON | **exit 1**, 92 findings | | restored | `git hash-object` equals the `HEAD` blob (`5d84eb3ea…`); `git diff HEAD --name-only` back to **0** lines | — | The decisive finding under mutation names the incident file and the incident import: ``` - @objectstack/cli imports the workspace package `@objectstack/spec` from a test and does not declare it. packages/cli/test/format-zod-union.test.ts @objectstack/spec ``` No rebuild leg is owed and none is claimed: this gate is a `scripts/*.mjs` source scan invoked directly by node, with no `dist/` anywhere in its resolution path, so the mutation reached the code under test the moment it reached disk. Direction declared before the run and observed: **turns red** — the ordinary direction. ## Controls - ⭐ **LIT CONTROL.** The incident file's two relative imports (`:38` `../src/utils/format`, `:39` `./helpers/serve-process.js`) were already visible to the roster half and still are: this diff touches no code on that path, and `check:cross-package-test-inputs` reports the same 29 escaping packages, the same 13 walked roots and the same 3 accepted radii before and after. - ⭐ **DARK CONTROL, two halves.** `vitest`, `zod` and `node:fs` must read 0 — pinned by new self-test cases one per shape, beside the three pre-existing `does NOT flag` cases that the #10452 boundary already carried. And the affected subset must not balloon: it does not move by a single package, because this limb adds no declaration and no glob (0 of 349 pairs are findings). - ⭐ **PROBE.** Satisfied before and after, by the graph rather than by this gate — the measurement at the top of this body is the evidence, taken with its own firing control. ## Gates | gate | verdict | |---|---| | 30 commands derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` | see the run table in the report; all exit 0 | | `pnpm check:cross-package-test-inputs` | exit 0 | | `node scripts/check-cross-package-test-inputs.mjs --self-test` | **214** cases (was 191), exit 0, verdict line reached | | `node scripts/pm/check-widening-tells.mjs --declaration no --diff …` | **NOT MEASURED** — exit 0 while judging nothing: `scripts/**` is covered by no declared surface, so no tell could have fired whatever the diff contains | | `node scripts/check-published-list-mirrors.mjs` | exit 0 — `RECOGNISED_PATH_SPELLINGS` is untouched; only `RECOGNISED_IMPORT_SPELLINGS`, which no mirror row declares, gains four comment lines | | control characters | `grep -naP` over the changed file: 0 hits, beside `pnpm check:nul-bytes` | `skip-changeset`: nothing here publishes. `scripts/` sits at the repo root, outside every package directory, so no package's `files[]` can ship it; the only packages that name this script do so in turbo `inputs`, which is hashing, not shipping. ## Acceptance notes - **The incident's actual cause is still open, and it is not this gate.** CI evidence from #17914's own run (34746808828, `Test Core`): the timing table reports `cache-replayed packages, excluded as non-measurements: 1 (@objectstack/cli)` and marks every `@objectstack/cli` row `replayed log` — so `@objectstack/cli#test` was a turbo cache REPLAY on the PR that reddened `main`, and the suite never ran. That contradicts the hash measurement above, which shows the task hash moving for exactly that diff. One of the two readings is about a mechanism nobody has named yet. Reported to the dispatching seat as a finding to file; it is the link that actually let #17914 through, and it survives this PR untouched. - The `queue-flake anchor` naming convention (#18032) is noted, not filed here: it is a triage narrative question, and it already carries its own "This issue is a NAME, not a diagnosis" line. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent f6c2eb7 commit 137781f

1 file changed

Lines changed: 355 additions & 4 deletions

File tree

0 commit comments

Comments
 (0)