Uh oh!
There was an error while loading. Please reload this page.
feat(devx,datasource,automation): gate undeclared workspace imports in published src (#10062) - #12942
Conversation
…dger them instead (#10062)
…declared-dep-imports
📓 Docs Drift CheckThis PR changes 4 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 18 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 b7893355b4896ca68d822580724886d26b563c25 && git checkout b7893355b4896ca68d822580724886d26b563c25
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f4e741bd194f72c880a99d7af2e6a4faae09d63a c078354fcb3bd4399f35d33be9b2b263b2ad95d8 && git checkout -B drift-repro f4e741bd194f72c880a99d7af2e6a4faae09d63a && git merge --no-ff c078354fcb3bd4399f35d33be9b2b263b2ad95d8
node scripts/docs-audit/affected-docs.mjs --json f4e741bd194f72c880a99d7af2e6a4faae09d63a
|
…declared-dep-imports
…new field in the census ledger (#10062)
os-elon
commented
Aug 28, 2026
Repair pushed: What was red
The declaration itself is correct and stays: the The fixOne row in The substantive half is the Post-fix outputThe ruled count did not move: Ran past where CI stopped
No sibling census is hiding behind this one. Worth knowing: this census gate is one of the two families Generated by Claude Code |
os-elon
commented
Aug 28, 2026
ACCEPT — armed ( Entry qualification: every check, not the required subset. 33 check runs at The repairOne row in The substantive half was the Both my Zone 2 assumptions were falsified, and the way they were falsified is the point"A second census is hiding behind this one" — false, and proven rather than assumed. "The The lesson this card bought, recorded on the seatI derived this card's gate families from the diff and still did not anticipate a gate keyed on which field declares a dependency. ⇒ When a remedy changes a manifest, enumerate the ledgers keyed on that manifest, not just the gates triggered by its path.
Also green on this head: repo-wide
Generated by Claude Code |
Fixes#10062
A package's published
src/**was free to import any workspace package, declared or not, and nothing in the tree checked it.check:type-source-resolutionandcheck:test-source-aliasanswer which copy of a dependency's types resolves, never whether the dependency is declared, andeslint.config.mjscarries noimport/no-extraneous-dependenciesrule.What the sweep actually measures now
Re-measured on
aef1b7e6, with the instrument's own defects fixed first. The card was filed at 1 member and released at 3; the honest count is 4 packages / 15 occurrences, and the release comment's list came from a scan that could not see dynamic imports.Two fabrications the naive scan produces, both fixed before any conclusion was drawn:
rest-server.ts,storage-driver.tsandturso-driver-factory.tseach carry prose that NAMES the package it is explaining. An unmasked scan reds on the authors who documented the situation.packages/cli/src/commands/create.tsbuilds a scaffolded README inside a template literal, and that README contains a fenced code block readingimport ... from '@objectstack/plugin-PLACEHOLDER'. The comment mask leaves it standing — it is a string, not prose. A match now counts only when itsimport/from/requirekeyword sits at a byte the scanner flags as code.The gate
scripts/check-undeclared-dep-imports.mjs, wired intoLint & Repo Gatesand aspnpm check:undeclared-dep-imports.node_modules— a reviewer can run it in place rather than read it.dependencies+peerDependencies+optionalDependencies. A peer is a declaration to the consumer; reading peers as undeclared would red on@objectstack/cli, which did the right thing.@objectstack/*specifiers extracted. A dead matcher produces an empty finding set, and the empty set is what success looks like.--self-test, 41 cases, running first in CI: every import form firing, and the four shapes that must stay silent (declared / peer / optional / self, tests and fixtures, comments, tsconfig-excluded payload). The production run over a fixed tree is green by construction and cannot tell a working matcher from a dead one.packages/create-objectstack/src/templatesis judged mechanically, not skipped by name: it is payload copied verbatim into a scaffolded project, and the package's owntsconfig.jsonexcludes it. The evidence lives in the tree, so it cannot rot into a hardcoded list.Per-member remediation, decided one at a time
Declaring makes a coupling real and installable; routing it away removes it. The two are not interchangeable, so nothing here is applied uniformly.
Fixed — the coupling was real and unguarded.
@objectstack/service-datasourcenow declares@objectstack/driver-sqland@objectstack/driver-memoryasdependencies. Both are loaded through an unguardedawait import(...)on the postgres, mysql, sqlite and memory arms, so a consumer reaching one of those paths metERR_MODULE_NOT_FOUNDinstead of a diagnosis. The type-onlySqliteAbsentFileModeimport rides along.Fixed — the coupling was avoidable.
isCodeArtifactBodyand its private helperisTenantAuthoredmoved from@objectstack/objectql's registry into@objectstack/metadata-core, by the same criterion as the write-verb dispatch predicates and the audit governance table already sitting there: a second layer needs the answer, and the reverse import would either close a cycle or make the consumer depend on the whole data engine for one predicate.objectqlre-exportsisCodeArtifactBodyfrom its original path, so its public API is unchanged, andservice-automationimports it from a package it already declared.Recorded, with mechanical evidence — the tree already carries the decision not to declare, and its reason. Seven ledger rows, shrink-only, each red the moment its evidence stops holding.
optional-runtime-proberows demand the occurrence stay a dynamicimport(); atype-onlyrow demands every occurrence stayimport type. That second kind is this card's original mitigation, turned from prose into a check — it was prose that letflow-precedence.tsbecome a value import with nothing going red anywhere.The measurement that closes the card, re-taken rather than quoted
Ablation on
flow-precedence.ts, mutation confirmed on disk by blob hash before each build and the restore proved by an emptygit diff HEADplus an--absentdist preflight.isCodeArtifactBodyindist/index.js@objectstack/objectqlisTenantAuthored— objectql's PRIVATE helper — inlined@objectstack/metadata-corecheck:undeclared-dep-importsThe release comment's claim holds and is sharper than recorded: all four textual
@objectstack/objectqloccurrences in the defective bundle are inlined JSDoc carried in from objectql's ownengine.tsandregistry.ts, and there is no runtime import at all. The count is 10 rather than 9 — the build has moved — and the inlining reaches objectql'sSchemaRegistryinternals, not just the one predicate. After the fix the bundle carries a realimport { isCodeArtifactBody } from "@objectstack/metadata-core"and no copy of anyone else's code.The
type-onlyrow's evidence was measured too, and the first phrasing of it was wrong and corrected:packages/rest/dist/index.d.tsandindex.d.ctscarry zero module references to@objectstack/metadata-protocol— the single textual occurrence is inside a TSDoc comment — and everyfromspecifier in rest's published types names a package rest declares.One thing measured that is worth a reviewer's eye
Declaring
@objectstack/driver-tursoas an optional peer ofservice-datasourcewas tried and reverted. It makes pnpm link the package in the workspace, which took out a live assertion:default-datasource-driver-factory.test.tsreaches the missing-package arm with no stub because the package does not resolve from here, and it fails with a notice saying exactly that. The three guarded driver arms are therefore left undeclared and ledgered. Declaring them as optional peers is still a defensible hygiene improvement; it needs that test rewritten to stub the import, which is its own change.Verification
Union run on
2f51f9cc0(this head, after mergingmain).check:undeclared-dep-imports— self-test 41 cases PASS; production78 workspace packages, 2058 non-test src files, 1805 @objectstack/* specifiers, 7 ledger rows, all evidence intacttypecheckgreen for all five packagespnpm lint— the full repo-wide scan, no narrowing: 5362 files, 0 errors, 0 warningscheck:self-test-wired,check:step-collectors,check:watch-hint-literal,bare-root-worklist --self-test,check:pm-dispatch-gates,check:dispatcher-error-vocabulary,check:published-files,check:override-consistency,check:type-check-coverageandcheck:type-check-debt --re-measure(green locally with the full closure built; the check:type-check-debt --re-measure OOMs only on CI — the gate's harness does not pin the heap ceiling, so a local run is not CI-shaped #12856 OOM did not reproduce here)scripts/pm/check-half-states.mjs(exit 3,PREREQUISITE NOT MET— no GitHub credential) and, before the clone was deepened,check-engine-split-ratio(shallow history; green aftergit fetch --shallow-since)The gate declares
ROOT_DIR_WATCH_HINTS, so a change underpackages/**names it in a dispatch brief instead of leaving it in the invisible-population set.Follow-up left on the table
scripts/cross-package-test-inputs.mjswas not touched — it is held by #12834 this round. The gate needs no declaration there: it is not a test, and its--self-testreads only temp fixture trees it creates itself. #12834 stays open.Generated by Claude Code