Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-core): make the CJS entry point loadable, and gate the class - #12984
Conversation
…ire entry point loads tsup emits `import.meta` verbatim into `dist/index.cjs` at this build target, and `import.meta` outside an ES module is a parse-time error, so the package's whole CJS condition was unloadable and the guarding try/catch never ran. `shims: true` rewrites it to a real `__filename`-derived URL, matching the measured precedent already carried by packages/metadata-protocol and packages/runtime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ust load check:dual-build-cjs-loads parses every emitted CommonJS file and require()s every published require entry point in the workspace (105 entries across 67 packages), plus a declared cross-format behaviour probe pinning that metadata-core's resolveInstalledSpecVersion() answers the same installed spec version through both conditions. Runs as a step in Build Core, where a real dist/ exists; exits 3 PREREQUISITE NOT MET rather than degrading to a silent green when it does not. A SyntaxError in our own emitted bytes is never ledgerable — the shrink-only baseline can only declare a load-time failure whose cause is a dependency (today: two vitest-backed ./testing subpaths). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DECLARED-NARROWER: the gate declares packages/**/package.json (1.0% of the tree) and packages/**/tsup.config.ts (0.3%) beside SCAN_ROOT. The wider packages/**/src/** spelling — best recall, since #12971 arrived through one source line — is measured at 62.2% of the tracked tree and REFUSED on the same trade this map records for the 39% rows, and because the gate does not read those files at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The emitted ESM code bytes are identical with and without the shim; the whole difference is the shared chunk's content-hashed filename and its sourceMappingURL line. Measured by rebuilding both ways and hashing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f7a816c7e75413c43d857b4ccfdf36ea1748f3bc && git checkout f7a816c7e75413c43d857b4ccfdf36ea1748f3bc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2b4178aa53ca62089f43e2cfae0b7838cf340dd1 1ca6e3a27679814336f133bec83ced3d740484ba && git checkout -B drift-repro 2b4178aa53ca62089f43e2cfae0b7838cf340dd1 && git merge --no-ff 1ca6e3a27679814336f133bec83ced3d740484ba
node scripts/docs-audit/affected-docs.mjs --json 2b4178aa53ca62089f43e2cfae0b7838cf340dd1 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12971
Part of objectstack-ai/cloud#1653
What was broken
@objectstack/metadata-coredeclares"type": "module"with a dualexportsmap, sorequire('@objectstack/metadata-core')is a published, supported entry point. Since #12843 it was unloadable.resolveInstalledSpecVersion()anchors its@objectstack/speclookup withcreateRequire(import.meta.url)— correct for the ESM output — and tsup emits that identifier verbatim intodist/index.cjsat this build target.import.metaoutside an ES module is a parse-time error, so the module never begins executing: neither thetypeof require === 'function'fast path above the line nor thetry/catcharound it ever runs, and the failure is total rather than partial.The filed positive control, reproduced on this worktree at the base commit
3404bd171, from a clean build:The fix — one line, and it is the line this repo already measured twice
shims: trueinpackages/metadata-core/tsup.config.ts. tsup then rewritesimport.meta.urlin the CJS output to a real__filename-derived URL (itsassets/cjs_shims.js), so both formats anchor on this module's own file and resolve the same@objectstack/spec/package.json.That is not a guess between the issue's two suggested shapes — it is the shape already carried, for this exact measured reason, by
packages/metadata-protocol/tsup.config.ts(#11235) andpackages/runtime/tsup.config.ts(#10993). metadata-protocol's comment describes this failure mode word for word, on its own package. The third package to hit it had no such comment to read, which is the argument for the gate below rather than a fourth comment.The source comment that documented the wrong posture ("its transformed
import.meta.urlisundefinedthere —createRequirethen throws and the catch below answersnull") is corrected in the same commit: the shim is what makes the line legal in CJS at all, and thecatchcannot cover a missing shim.Both conditions, after:
The ESM half is untouched — measured, not assumed. Rebuilding with and without the shim and hashing every emitted ESM file: the code bytes are identical in all of them; the whole difference is the shared chunk's content-hashed filename (
chunk-DDDKWTSW.jsbecomeschunk-46MG4YHS.js) and the onesourceMappingURLline naming it — a 6-line diff, noexportstarget moved.The class gate — measured first, then built
The sweep. Over all 105 published
requireentry points across 67 publishable packages,import.metain an emitted CommonJS output was exactly one file: this one. So the class is fixed here, in full, and nothing else is deferred for that shape.The sweep did surface a different, pre-existing class worth a card of its own: two
requireconditions that can never load —@objectstack/metadata-core#./testingand@objectstack/service-cluster#./testing. Both re-export vitest, and vitest refuses by design to be loaded from CommonJS. The bytes parse; the load fails inside vitest's own entry. Independent of this issue (the same condition is declared atb489d3c725e8, before theimport.metaline landed), and the repair is a published-exportschange. Enumerated below for the PM rather than ridden in here.pnpm check:dual-build-cjs-loads(scripts/check-dual-build-cjs-loads.mjs) — three invariants per publishedrequireentry point:.cjs, plus.jswhen the manifest is not"type": "module") parses as CommonJS. Checked over the whole emitted set, not just the entry, because code splitting puts the offending line in a shared chunk as easily as inindex.cjs.require(entry)in a fresh child process completes.metadata-core'sresolveInstalledSpecVersion()to answer the installed spec version through both conditions. Loading is the weaker claim — a shim resolving the wrong anchor would load fine and quietly answernull, which is a legal value here (it closes the ADR-0087 forward-conversion window), so that degradation is silent.Green on this tree:
Design points worth review:
scripts/dual-build-cjs-loads.baseline.jsonis shrink-only and hand-edited, and it can only declare a load-time failure whose cause is a dependency. An entry whose emitted bytes do not parse is refused whatever the ledger says, and the finding says the ledger was ignored. A load failure is a fact about a dependency; a parse failure is always a fact about what we emitted. Pinned in--self-testin both directions, as is the stale-exemption direction (an entry that starts loading must be deleted).Build Core, beside "Verify capability packages ship a runtime entry" — same genre, same phase, required context, and a realdist/exists there. With nodist/it exits 3 (PREREQUISITE NOT MET) namingpnpm build; it never degrades to a silent green.Test Corehas none. turbo'stesttask declaresdependsOn: ["^build"](dependencies only, never the package's own dist) and excludesdist/**from its inputs — a suite reading its owndist/would be unbuilt in CI and un-invalidated by a rebuild. The package's own unit suite still covers the function; what needs built bytes is the cross-format claim.--self-test: 37 cases, on a real fixture tree with real emitted bytes and real spawns — including a poisoned shared chunk the entry never imports (which a require-only smoke misses), and a package that parses and throws at load (which a parse-only check misses).packages/**/package.json(74 files, 1.0% of the tree) andpackages/**/tsup.config.ts(20, 0.3%) as its dispatch-gates watch hints.packages/**/src/**has the best recall — feat(metadata): versioned ADR-0087 forward conversion at the artifact-ingestion door #12843 arrived through exactly one source line — but measures 4482 files, 62.2% of the tracked tree, wider than the 39% rowsscripts/pm/bare-root-worklist.mjsalready records as REFUSE-WIDE on this trade, and the gate does not read those files at all. The row recorded there isDECLARED-NARROWER, carrying that measurement. The recall is not lost: the gate is a required-context step on every PR, so the omission costs one CI round trip, not a missed defect.Ablation — disk-proven, both legs, rebuilt
From the committed state, with a
traprestore and absolute paths.Mutation leg — delete the
shims: trueline: anchor occurrencesbefore=1 after=0; on-disk hash8a9c7153…against the HEAD blobef53c3e7…; rebuilt;import.metaoccurrences indist/index.cjs= 1, so the mutation demonstrably reacheddist/. Then:23 findings from one line is the in-repo blast radius: 21 downstream packages resolve metadata-core through the
requirecondition and inherit the parse failure.Restore leg —
git checkout HEAD -- <path>(never a baregit checkout -- <path>), proven by an emptygit diff HEADon the path and a restored hashef53c3e7…equal to the HEAD blob, then rebuilt and re-measured:import.metaoccurrences indist/index.cjs= 0, gateEXIT=0.Consumer acceptance — the two suites cloud#1712 named, both directions
Run on a dedicated cloud worktree detached at cloud
d441d923(the pin commit on cloud#1713's branch,.objectstack-sha=fc8a33935ce9), built withpnpm build --force, with all 174 framework symlinks repointed by prefix between the two checkouts. The repointing was proven per run, not assumed:require.resolveprinted the resolved file, itsimport.metaoccurrence count, and whetherrequire()succeeded.fc8a33935(unfixed)apps/objectos-eetest/signup-membership-policy.e2e.test.tsTests 3 failed | 2 passed (5), EXIT=1Test Files 1 passed (1)/Tests 5 passed (5), EXIT=0apps/objectos-eetest:hotcrm-acceptanceTest Files 2 failed (2)/Tests 1 passed | 12 skipped (13), EXIT=1Test Files 2 passed (2)/Tests 13 passed (13), EXIT=0Both unfixed columns carry the filed cause lines verbatim —
cause: Cannot use 'import.meta' outside a moduleandFATAL: tenancy posture 'isolated' was requested but @objectstack/organizations could not be loaded— and reproduce the counts in the issue exactly. Zeroimport.metaoccurrences appear in either fixed-column log.Declared: the HotCRM artifact is not a committed fixture, and a first run was PREREQUISITE NOT MET for that reason rather than a measurement. It was supplied from the pinned SHA build (
.hotcrm-sha1e40d16cebb8, artifact sha256e1a5951c32f7…) rather than re-cloned, and both columns above ran against that same byte-identical artifact.What the cloud pin bump should target
cloud#1713 holds on its own branch per the Option A adjudication. After this PR lands, re-run
scripts/bump-objectstack.shon that same branch to any objectstackmainSHA at or after this PR's merge commit — the only constraint the card carries is "at or pastfc8a33935", and every such SHA now also carries this fix. No earlier SHA works:54e2d3692(which introduced the break) is an ancestor offc8a33935, so no commit both satisfies cloud#1712 and excludes the defect.Verification
Union re-run after the final commit, at
1ca6e3a27(git statusclean).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths; the script derives its own change set from the merge base). All 44 derived families plus the two convention-triggered ones (bare-root-worklist --self-test,check:pm-dispatch-gates), the ADR-0112 lead (check:dispatcher-error-vocabulary) andcheck:nul-byteswere run, each exit code captured before any pipe. Green at their own verdict lines, with two exceptions, both read as NOT MEASURED rather than as passes:check:bash32-floor— EXIT=1, and environmental, not this diff: this host'sbashis 3.2.57 (macOS), so the self-test's simulated-3.2 harness cannot remove builtins that were never there, and 8 of 153 cases fail on "the probe is shell this host can parse". Proven pre-existing by running the identical, unmodified script on the shared checkout atfc8a33935(a commit that predates this branch): same EXIT=1, the same 8 cases. This diff touches no shell file and not that script. CI's ubuntu runner ships bash 5.check:pm-half-states/scripts/pm/check-half-states.mjs— EXIT=3,PREREQUISITE NOT METby its own words ("Nothing was swept … it is no reading at all"): the container token is not a GitHub credential. CI owns it.node scripts/check-dual-build-cjs-loads.mjs --self-test→✓ 36 cases passat the time of the gate commit,37after the watch-hint narrowing; the gate itself → the verdict line quoted above.scripts/pm/bare-root-worklist.mjs --self-test(its own suite, owed because this diff edits it) →OK self-test: 47 live row(s), 40 unreachable as spelled, 40 recorded verdict(s) — none stale, none missing, none contradicted.pnpm check:pm-dispatch-gates→✓ dispatch-gates self-test: 834 cases pass.No dedicated vitest suite names either script (git greped; the self-tests are the suites).pnpm --filter @objectstack/metadata-core test→Test Files 14 passed (14)/Tests 234 passed (234), EXIT=0.pnpm --filter @objectstack/metadata-core typecheck(tsc --noEmitplus the test program) → EXIT=0, and the edited source really is in that program:--listFilesnamesartifact-forward-conversion.ts.pnpm lint(eslint . --no-inline-config) → EXIT=0. No narrowing to declare.Declared narrowing — verification ran UNLOCKED.
scripts/pm/os-verify-lock.shcould not take the shared verify lock on this host: no usableflock. The shared verify lock is declared Linux-only (flockis util-linux, and a stock macOS does not ship it), so the commands were run directly, without the lock — a declared narrowing, not a silent one. No serialization guarantee held for these runs, nor for any sibling agent in this container while they ran.Out of scope, for the PM to file
A
requirecondition that declares an entry point vitest itself refuses to serve:@objectstack/metadata-core#./testingand@objectstack/service-cluster#./testingboth re-export vitest, whose CJS entry throwsVitest cannot be imported in a CommonJS module using require(). Pre-existing (present atb489d3c725e8), a different class from this one, and the repair is a published-exportschange — a test-harness subpath advertising arequirecondition it cannot honour. Both are declared with their reasons inscripts/dual-build-cjs-loads.baseline.json, so the new gate reports them rather than passing over them, and the ledger is shrink-only: whichever card fixes them must delete the entries in the same PR or the gate reds.Generated by Claude Code