Uh oh!
There was an error while loading. Please reload this page.
feat(spec): declare introspectSchema on IDataDriver and type IDataEngine.introspectDatasource — the engine-registration seam meets the compiler - #11834
Conversation
…ection (#11493) IDataDriver gains optional introspectSchema?(): Promise<IntrospectedSchema>; IDataEngine gains optional introspectDatasource?(datasource): Promise<IntrospectedSchema>. ObjectQL.introspectDatasource() tightens Promise<unknown> to the spec type and drops its as-any driver probe (compiled JS unchanged). service-datasource's plugin deletes its structural DataEngineLike re-declaration and types the 'data' service with the real contract; its dead getDatasourceDriver fallback probe is respelled to the declared getDriverByName member. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
check:engine-double-contract counts IDataEngine literals in this file against a shrink-only baseline; the pins read the member type off the contract instead, so the double census is untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
📓 Docs Drift CheckThis PR changes 3 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 127 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 58fe91b586da847f6cf0cb6aa6318a523d6d799d && git checkout 58fe91b586da847f6cf0cb6aa6318a523d6d799d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bb418686547de42c8feae103fa2e41dd419aa171 eab6d131ba4c791fc57c76c8ff4ae487300a3c41 && git checkout -B drift-repro bb418686547de42c8feae103fa2e41dd419aa171 && git merge --no-ff eab6d131ba4c791fc57c76c8ff4ae487300a3c41
node scripts/docs-audit/affected-docs.mjs --json bb418686547de42c8feae103fa2e41dd419aa171
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11493
What this does
Extends the #11123 ruling to the engine-registration seam, per the maintainer ruling on the card (comment 5394014425), quoted verbatim and untranslated:
(maintainer, 2026-08-24, live PM chat, on the aligned four-facet analysis — both steps approved as the extension of the #11123 ruling to the engine-registration seam.)
IDataDriver(spec contract) gains an OPTIONALintrospectSchema?()member returning the spec typeIntrospectedSchema(packages/spec/src/contracts/data-driver.ts), so drivers without introspection stay conformant. A driver implementing it with a wrong shape now fails compile at the offending field, on theregisterDriver()road that feat(service-datasource): external-datasource driver return contract fails compile-time on mis-shaped drivers (#11123 option C) #11492 could not reach.IDataEnginegains an OPTIONALintrospectDatasource?(datasource: string)member with the same spec return type (packages/spec/src/contracts/data-engine.ts, the same evidenced-binding posture as the [The service-lookupanyrule missesgetService<any>(...)— 80 sites erase the slot contract, 3 of them inside the rule's own scope #4251] registry pair beside it), andObjectQL.introspectDatasource()tightens its return from the untyped Promise to the spec type, dropping theas anydriver probe (packages/objectql/src/engine.ts).service-datasource/plugin.tsDELETES its structuralDataEngineLikere-declaration — part of the fix per the ruling, not follow-up. The plugin now types the'data'service with the realIDataEnginecontract. Forced by the deletion: the workaround's second member,getDatasourceDriver?, matched no engine in either repository, ever (measured 2026-08-24: zero references outside this file), so its fallback branch could never fire; the probe is respelled to the member the contract actually declares (getDriverByName?, [The service-lookupanyrule missesgetService<any>(...)— 80 sites erase the slot contract, 3 of them inside the rule's own scope #4251]), making the degradation reachable for the first time. This is the one deliberate runtime-visible edit in the PR (see "Runtime" below).The interface member on
IDataEngineis the mechanism step ② requires: without it, deletingDataEngineLikeleaves the plugin nothing typed to compile against — the ruling's own wording (IDataEngine.introspectDatasource()) names the member on the contract.In-tree driver audit (required by the ruling — readings)
introspectSchema@objectstack/driver-sql(SqlDriver)sql-driver.ts)IntrospectedSchema extends SpecIntrospectedSchema(derive-not-redeclare, post-#11124/#11270; extras: per-columnisUnique/maxLength, per-tableprimaryKeys/foreignKeys)tsc --noEmitgreen against the new contract@objectstack/driver-turso(TursoDriver extends SqlDriver)@objectstack/driver-sqlite-wasm(SqliteWasmDriver extends SqlDriver)@objectstack/driver-memory(InMemoryDriver)@objectstack/driver-mongodb(MongoDBDriver)@objectstack/objectqlconsumer types (util.ts)IntrospectedSchema extends SpecIntrospectedSchemaNo drift found ⇒ no driver touch-ups needed. Deliberately NOT widened: the Zod mirror
DriverInterfaceSchema(data/driver.zod.ts) — its coverage of optional TS members is already partial by existing practice (updateMany/syncSchemasBatch/explaindeclared;temporalFilterValue/registerExternalObject/getSchemaSyncStats/reclaimSpacenot), and the ruling names the TS contract; widening the mirror is a separate decision.Out-of-tree driver risk (the accepted face)
An out-of-tree driver with a same-named, differently-shaped
introspectSchemagets a loud compile error on upgrade — carried from the four-facet block as the accepted face of this change. The runtime is untouched: plain-JS drivers and stale builds are reached by no compiler, and the PR #11001 runtime shim keeps absorbing them exactly as before.Reverse verification (the required inversion, both legs recorded)
One fixture (a driver class whose
introspectSchemaanswers{ tables }with the retiredisPrimarycolumn spelling, plus an engine whoseintrospectDatasourceanswers{ tables: {} }), no suppression directives, compiled bytsc --noEmitinpackages/spec:e43b18fd9(pre-fix): exit 0, zero diagnostics. The mis-shapes compile GREEN — the gap the card describes, measured.TS2416: Property 'introspectSchema' in type 'MisShapenDriver' is not assignable to the same property in base type 'IDataDriver'. … Type '{ tables: … isPrimary … }' is missing the following properties from type 'IntrospectedSchema': dialect, introspectedAtTS2322 … The types returned by 'introspectDatasource(...)' are incompatible … Type '{ tables: {}; }' is missing the following properties from type 'IntrospectedSchema': dialect, introspectedAtThe fixture was never committed; the committed pins carry the same inversion as
@ts-expect-errordirectives (an unused directive is itself an error, so a revert redspnpm --filter @objectstack/spec typecheckin either direction).Pins
@ts-expect-errorpins indata-driver.test.ts(retiredisPrimaryspelling; bare{ tables }; a mis-shaped member offered on the driver value) anddata-engine.test.ts(non-spec engine answer) — the repo's existing type-test idiom (tsc-resolved, vitest-hosted, zero entries intest-typecheck-debt.json).undefined extendspins on both members; value-level, the file's pre-existing minimal literals already omit both members and compile.extendspins that the members' return type ISIntrospectedSchema(a revert to the untyped Promise resolves the pin type tonever).IDataEngineliteral, socheck:engine-double-contract's shrink-only double census is untouched.@objectstack/service-datasourcetsc --noEmitgreen with the workaround deleted.Runtime
Zero runtime change in spec and objectql, verified at the artifact: the spec edits are interface members (type-only imports + declarations — no JS is emitted for them), and the compiled
ObjectQL.introspectDatasourcebody inpackages/objectql/dist/index.jsafter the change is the identical three-step function (lookup → two named throws → delegate); theas anyand the return annotation never reached JS. The one deliberate JS change isservice-datasource/plugin.ts's fallback probe respelling (getDatasourceDriver→getDriverByName), declared above: the old spelling was dead against every engine that has ever existed in-tree, so no behaviour that ever executed changes; the branch becomes reachable, which is the point.H17-adjacent note:
service-datasource/sqlite-driver-fallback.tsandtsup.config.ts(hold #10062 trigger files) are untouched — the diff in this package issrc/plugin.tsonly.skills/** readings (required)
check:skill-refsregeneratedskills/objectstack-data/references/_index.md: the new type-onlyIntrospectedSchemaimport pullsdriver-sql.zod.ts+deploy-bundle.zod.tsinto the skill's reference closure (generated artifact; gate red without it).skills/**/SKILL.md): 10505 → 10505 lines (no SKILL.md touched).This path also makes the diff touch a governed surface (
skills/**, Prime Directive #14): this PR is human-merge only — it stays DRAFT, is never queued, and auto-merge is never armed. Clause-②: yes — public contract member addition; the PR stays draft, the contract-review chain runs before enqueue.Verification (all readings at head
eab6d131bunless noted)@objectstack/spec:pnpm typecheck→ exit 0 (tsc --noEmit+ scripts +check:test-typecheckOK, debt ledger unchanged at 55 files / 263 errors); fullvitest run --maxWorkers=2→ 420 files / 11228 tests passed (verify-lock verdictcommand-exit 0; suite ran at 0acd55c — the follow-up commit touches onlydata-engine.test.ts, whose file-scoped rerun at head is below).@objectstack/objectql:pnpm typecheck→ exit 0; fullvitest run --maxWorkers=2→ 231 files / 4106 tests passed (verify-lock verdictcommand-exit 0).@objectstack/service-datasource:pnpm typecheck→ exit 0;vitest run --maxWorkers=2→ 27 files / 585 tests passed (verify-lock verdictcommand-exit 0).@objectstack/driver-sql:pnpm typecheck→ exit 0 (audit leg).data-driver.test.ts+data-engine.test.ts→ 2 files / 35 tests passed.turbo run buildover./packages/*+./packages/*/*→ exit 0 (70/70 tasks);turbo run typecheck --concurrency=2→ exit 0, "Tasks: 129 successful, 129 total" (verify-lock verdictcommand-exit 0) — the return-type tightening surfaced no latent consumer.pnpm --filter @objectstack/spec check:generated→ after regenerating the one proved-stale artifact (gen:skill-refs), exit 0;check:api-surfaceandcheck:docsgreen without regeneration.node scripts/pm/dispatch-gates.mjs, no paths — change set derived by the script from the merge base; derivation line: "gate list derived from the tree of 'objectstack-ai/objectstack' at commit …"): all 32 path-matched famil(ies) + the 5 convention-triggered test-file famil(ies) +check:nul-bytesrun locally, every gate exit 0 at head (per-gate exits captured pipe-free;check:engine-double-contractprintsOK — 401 pinned, 133 in the DEBT ledger, 2 exempt;check:type-check-debt --re-measureandcheck:dev-prereqsrun after the full workspace build).CI is expected to re-run the full farm; per the dispatch contract this PR is reported at draft time with CI possibly still in progress.
Out-of-scope finding filed: #11833 (two more consumer-local structural
DataEngineLikedeclarations, observation-class).Generated by Claude Code