Repository navigation
Commit 5e7d83c
fix(gate): correct why check-published-files lets packages/spec ship its zod sources — a read route, not an import route (#19468)
Fixes #19009
Clause-②: yes
## The experiment the card said nobody had run
The gate `scripts/check-published-files.mjs` lets `@objectstack/spec`
ship `src/**/*.zod.ts` in `files[]`, and states why, verbatim:
> The Zod schemas are themselves the contract (Prime Directive #1);
downstream code imports them directly, so these sources are product
rather than build input.
The card measured both halves of that sentence statically and said
plainly that the decisive experiment — pack the tarball, install it,
import a shipped `.zod.ts` from a real consumer — had not been run. It
is run here, first, before any edit.
`npm pack` of `packages/spec` at this branch's base, installed into a
scratch consumer, then every spelling of the deep import:
| probe | result |
|---|---|
| ESM `@objectstack/spec/src/data/object.zod.ts` |
`ERR_PACKAGE_PATH_NOT_EXPORTED` |
| ESM, no extension, and `.js` extension |
`ERR_PACKAGE_PATH_NOT_EXPORTED` |
| CJS `require.resolve(...)` | `ERR_PACKAGE_PATH_NOT_EXPORTED` |
| esbuild bundle of the deep specifier | `The path
"./src/data/object.zod.ts" is not exported by package` |
| `tsc`, `moduleResolution: bundler` | `TS2307` |
| `tsc`, `moduleResolution: nodenext` | `TS2307` |
| literal relative path into `node_modules` |
`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` |
Lit controls, same install, same run: `@objectstack/spec/data` resolves
with 490 exports, the root entry with 137, `require.resolve` of
`./package.json` returns a path, and esbuild bundles the declared
subpath to 1.4 MB. The zeros above are readings, not a dead harness.
One resolver mode does reach the files, and it measures both halves of
the card at once: `tsc --moduleResolution node10`, which ignores
`exports` maps. It opens the shipped source and then fails — 120 errors,
the first `Cannot find module '../shared/lazy-schema'` — and in that
same mode the package's own declared subpath `@objectstack/spec/data`
does not resolve either. It is not a mode in which this package is
consumable at all.
So the card's first half holds: no consumer can import these files. The
stated reason's mechanism is false.
## What the files are actually for
Re-measured on this branch: 190 of the 203 shipped `*.zod.ts` modules
carry a relative import onto one of 42 `src/` modules the glob does not
ship, and `src/shared/lazy-schema.ts` alone is named by 182. The card's
second half holds too.
But both halves only bite if the point is to IMPORT them, and it is not.
The published skills catalog points agents at these files BY PATH, to
read inside a consumer's `node_modules`. `skills/README.md` states the
mechanism itself:
> a `references/_index.md` that points into the authoritative Zod
sources in `node_modules/@objectstack/spec/src/...` (the published
`@objectstack/spec` package ships these `.zod.ts` sources, so the
pointers resolve in consumer apps too)
Those index files are generated from this very glob by
`packages/spec/scripts/build-skill-references.ts`, whose own guard
already states the dependency from the other end:
> `resolveAll()` keeps only `*.zod.ts` from the closure, because the
published package's `files` allowlist ships those sources and nothing
else — a pointer to any other src file 404s in a consumer's
`node_modules`.
Measured with the tarball in hand: **170 pointers across 9 published
index files, 0 of them outside the glob.** `check:skill-refs` is green,
reporting 9 generated files in sync with `packages/spec`.
That is also why the 190-of-203 count is a property of the glob rather
than a defect in it: a reader never resolves those imports.
## The route taken, and the two that were not
**Correct the reason.** The entry stays; the sentence justifying it now
names the read route instead of an import route that has never been
open. The long-form rationale sits in a comment above the entry so the
next reader does not re-derive the two wrong repairs:
- **Do not open the `exports` map** to make the old sentence true. It
would advertise a route broken for 190 of 203 files — a machine-readable
surface that lies (Route and surface ownership rule 4; Prime Directive
#10).
- **Do not drop the entry** as unreachable payload. It is reachable
product, by read rather than by resolve, and dropping it makes every
generated pointer in the shipped catalog 404 at once. Triage's boundary
applies and is respected here: that would be removing a published
capability, and the measurement above is what shows the capability is
real rather than absent.
**No downstream resolution result changes.** This diff edits a reason
string and a comment in one repo-root gate script. Zero published bytes
move — the tarball is byte-identical before and after — so triage's
`pm:retriage` condition is not met.
## Cost of this glob, measured here
The card pointed at #16045's 12,661,943-byte reading as the same
question asked of a different `files[]` entry, explicitly not as this
entry's cost. Measured for THIS glob, from the packed tarball:
**5,557,289 bytes uncompressed across 203 files, 3.8% of the unpacked
tree** (tarball 28,299,508 bytes compressed, 147,205,318 uncompressed).
Recorded as a reading; it vetoes nothing.
## Hold #8133 — restart trigger
`#8133` is open and `pm:on-hold`, carrying `Restart-when: any PR touches
the packages/spec build/publish pipeline (tsup config, or the exports
map in packages/spec/package.json)`.
**This PR does not trip that trigger.** It touches neither the tsup
config nor the `exports` map — `packages/spec/package.json` is not in
this diff at all. What a reader of that hold needs from here:
- the `exports` map was read in this work and is unchanged: 19 subpaths,
none of them a `./src/*` and no wildcard;
- one branch considered and rejected here WOULD have tripped it —
opening the map to admit the `src` subpath. It was rejected on the
measurement above, so the hold keeps its standing and its blast radius
is untouched;
- `#8133` remains open and is not addressed here.
Stated by hand rather than left to the patrol: the half-state patrol's
H17 trigger-file index does not list `#8133`, because that index carries
only tracked file paths and this hold's trigger is written as prose. The
index says of itself that it under-reports and never invents, so its
silence on this file is a reach limit and not a clear.
## Gates
Derived from the actual changed path with `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, every exit code captured before any pipe,
reconciled with `--ran`.
Readings are labelled by which side of the edit they came from, because
this PR edits the gate it also runs:
- **pre-edit**: `node scripts/check-published-files.mjs` exit 0, and
`--self-test` exit 0.
- **post-edit**: all 31 derived families exit 0 at the merged head,
`check:published-files` and its self-test among them.
- `check:pm-dispatch-gates` and `check:watch-hint-literal` are both in
that set and green. That is the guard that matters most here: the new
prose lives in comments and in one long string, so it declares no watch
hint and fabricates no lead for this gate — the failure mode its own
`build-time script` entry warns about.
- `--ran` reconciliation: 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN.
Lint, narrowed with the narrowing proven rather than assumed: `eslint
--no-inline-config --format json` over the one changed file gives 1
file, 0 errors, 0 warnings, exit 0. The population is read from eslint's
own config via `isPathIgnored` over the 9,096 tracked files: 6,946 files
in the lint population. The config never enables type-aware linting — no
`parserOptions.project`, no `projectService`, the only textual hit being
the config's own comment saying so — so a one-file diff cannot move any
untouched file's verdict. The repo-wide sweep stays CI's run.
Consumer-package tests:
- `@objectstack/downstream-contract` — 3 files, 31 tests, all passed.
This is the harness that resolves named out-of-repo consumer specifiers
out of a packed tarball, so it is the on-point consumer reading for a
publish-surface card. Its first run refused with a stated prerequisite
(`@objectstack/cli` not built), which is the harness working as
designed; re-run after `pnpm --filter '@objectstack/cli...' build`.
- `@objectstack/spec` — 506 files, 14,797 tests, all passed.
- `pnpm --filter @objectstack/spec check:skill-refs` exit 0;
`check:skill-docs` exit 0.
## Changeset
`skip-changeset`. Nothing in this diff publishes: the only changed file
is `scripts/check-published-files.mjs`, the root manifest is private,
and this gate's own FORBIDDEN rule bars `scripts/` from every package
tarball. Verified mechanically against the packed spec tarball — 0 hits
for the changed file, with a positive control
(`package/src/data/object.zod.ts`, 1 hit) proving the query was alive.
For review: the claim declared `Clause-②: yes` because one live branch,
opening the `exports` map, expands the public surface. That branch was
measured and rejected, so the landed diff expands nothing and publishes
nothing. The `yes` is kept as declared.
## Acceptance notes
Observations from this work, deliberately not filed and not fixed here:
- The glob-to-catalog dependency is one-directional in prose. The
generator's guard says it filters to `*.zod.ts` because the `files`
allowlist ships those and nothing else; after this PR the gate says the
catalog is why the allowlist has them. Neither names the other by path,
and nothing fails mechanically if one side moves. No PR and no person is
carrying this today — successor: none. `packages/spec/scripts/**` is
also held by an open sibling PR from this seat, so the back-pointer half
was out of reach here regardless.
- `files[]` carries a bare `README.md` entry, which npm matches at any
depth, so `package/src/migrations/entries/README.md` rides into the
tarball — the single non-`.zod.ts` file under `src/` in it. Observation
only: one small file, and the entry is canonical.
- The card's static readings drifted between filing and this branch —
188 of 202 over 35 modules there, 190 of 203 over 42 here. Direction
unchanged; recorded so the counts in the gate comment are not read as
contradicting the card.
- My own first pass at the import measurement resolved relative
specifiers without mapping the NodeNext `.js` spelling back to `.ts`,
which counted five shipped files as unshipped targets. Corrected in a
second commit on this branch before the numbers were relied on; the
corrected run leaves 0 unresolved specifiers.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 841ed38 commit 5e7d83c
1 file changed
Lines changed: 43 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
242 | 242 | | |
243 | 243 | | |
244 | 244 | | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
245 | 287 | | |
246 | | - | |
| 288 | + | |
247 | 289 | | |
248 | 290 | | |
249 | 291 | | |
| |||
0 commit comments