Repository navigation
fix(gate): correct why check-published-files lets packages/spec ship its zod sources — a read route, not an import route - #19468
Conversation
`check-published-files` allowed `src/**/*.zod.ts` in the spec package's `files[]` on the ground that "downstream code imports them directly, so these sources are product rather than build input". Measured against a real packed tarball, the mechanism in that sentence is false: the exports map declares no `./src/*` subpath and no wildcard, so Node (ESM and CJS), esbuild and tsc all refuse the deep specifier, and a literal path into node_modules cannot load a `.ts` file there either. 191 of the 203 shipped modules also import one of 44 src/ modules the glob does not ship, so a route that reached them would not load them. The conclusion of that sentence is nevertheless true, for a different mechanism: the published skills catalog points agents at these files by path, to READ in a consumer's node_modules, and each skill's reference index is generated from this very set -- 170 pointers across 9 published index files, all inside the glob. Correct the reason to name the read route, and record why neither opening the exports map nor dropping the entry is the repair the false reason invites. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
The first measurement resolved relative specifiers without mapping the NodeNext `.js` spelling back to `.ts`, so five `.js` specifiers were counted as unshipped targets when the files are shipped (`tenant.zod.ts` and `date-macros.zod.ts` among them). Re-measured with that mapping and zero unresolved specifiers left: 190 of 203, over 42 modules, not 191 over 44. `src/shared/lazy-schema.ts` at 182 importers is unchanged. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
…blished-files-zod-glob
Contract reviewServed-tier: Isolated review at the contract-review tier for the ① Derived judgmentsShape of the diff, re-measured. Accept/reject behaviour of the gate: unchanged, by construction. The reason string is consumed at exactly one site, REGISTERED ( Claim 1, the experiment — re-run where an install is not required, and it reproduces. From the head's own
Judgment: the card's first half (the route is closed) and second half (most files could not load if it were open) both hold on the head. The dev's table is internally coherent with the manifest and with Node's documented behaviour; the parts that need a built Claim 2, the pivot — verified, it holds. Both quoted prose sites exist: Claim 3, Claim 4, node10. Reproduced: Claim 5, the two refused routes. Opening the Claim 6, hold #8133. The PR names it and quotes its Governed register. The one changed path matches none of the six ② Semver level
③ Boundary flags
NOT MEASURED here, named so nothing below reads as a pass: esbuild 0.28.1 probe (no esbuild on this host); the load-side lit controls (490 exports on Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19009
Clause-②: yes
The experiment the card said nobody had run
The gate
scripts/check-published-files.mjslets@objectstack/specshipsrc/**/*.zod.tsinfiles[], and states why, verbatim: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.tsfrom a real consumer — had not been run. It is run here, first, before any edit.npm packofpackages/specat this branch's base, installed into a scratch consumer, then every spelling of the deep import:@objectstack/spec/src/data/object.zod.tsERR_PACKAGE_PATH_NOT_EXPORTED.jsextensionERR_PACKAGE_PATH_NOT_EXPORTEDrequire.resolve(...)ERR_PACKAGE_PATH_NOT_EXPORTEDThe path "./src/data/object.zod.ts" is not exported by packagetsc,moduleResolution: bundlerTS2307tsc,moduleResolution: nodenextTS2307node_modulesERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPINGLit controls, same install, same run:
@objectstack/spec/dataresolves with 490 exports, the root entry with 137,require.resolveof./package.jsonreturns 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 ignoresexportsmaps. It opens the shipped source and then fails — 120 errors, the firstCannot find module '../shared/lazy-schema'— and in that same mode the package's own declared subpath@objectstack/spec/datadoes 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.tsmodules carry a relative import onto one of 42src/modules the glob does not ship, andsrc/shared/lazy-schema.tsalone 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.mdstates the mechanism itself: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:Measured with the tarball in hand: 170 pointers across 9 published index files, 0 of them outside the glob.
check:skill-refsis green, reporting 9 generated files in sync withpackages/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:
exportsmap 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 chore: version packages #10).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:retriagecondition 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
#8133is open andpm:on-hold, carryingRestart-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
exportsmap —packages/spec/package.jsonis not in this diff at all. What a reader of that hold needs from here:exportsmap was read in this work and is unchanged: 19 subpaths, none of them a./src/*and no wildcard;srcsubpath. It was rejected on the measurement above, so the hold keeps its standing and its blast radius is untouched;#8133remains 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:
node scripts/check-published-files.mjsexit 0, and--self-testexit 0.check:published-filesand its self-test among them.check:pm-dispatch-gatesandcheck:watch-hint-literalare 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 ownbuild-time scriptentry warns about.--ranreconciliation: 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN.Lint, narrowed with the narrowing proven rather than assumed:
eslint --no-inline-config --format jsonover the one changed file gives 1 file, 0 errors, 0 warnings, exit 0. The population is read from eslint's own config viaisPathIgnoredover the 9,096 tracked files: 6,946 files in the lint population. The config never enables type-aware linting — noparserOptions.project, noprojectService, 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/clinot built), which is the harness working as designed; re-run afterpnpm --filter '@objectstack/cli...' build.@objectstack/spec— 506 files, 14,797 tests, all passed.pnpm --filter @objectstack/spec check:skill-refsexit 0;check:skill-docsexit 0.Changeset
skip-changeset. Nothing in this diff publishes: the only changed file isscripts/check-published-files.mjs, the root manifest is private, and this gate's own FORBIDDEN rule barsscripts/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-②: yesbecause one live branch, opening theexportsmap, expands the public surface. That branch was measured and rejected, so the landed diff expands nothing and publishes nothing. Theyesis kept as declared.Acceptance notes
Observations from this work, deliberately not filed and not fixed here:
*.zod.tsbecause thefilesallowlist 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 bareREADME.mdentry, which npm matches at any depth, sopackage/src/migrations/entries/README.mdrides into the tarball — the single non-.zod.tsfile undersrc/in it. Observation only: one small file, and the entry is canonical..jsspelling 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