Uh oh!
There was an error while loading. Please reload this page.
refactor(service-datasource): derive the admin plugin's engine view from the IDataEngine contract - #12011
Conversation
…rom IDataEngine Replace the consumer-local structural `DataEngineLike` in `datasource-admin-plugin.ts` with a type derived from the declared `IDataEngine` contract — the #4251 B3 sweep pattern, extending #11493's ruling one file over. A private structural re-declaration meets no compiler on the producer side, so engine-surface drift lands silently in the consumer. The five members this seam actually uses (`findOne`/`find`/`insert`/`update`/ `delete`) all have compatible declared equivalents on `IDataEngine`, verified member-by-member by substitution before the swap. `registerDriver?`, `registerDatasourceDef?` and `getDriverByName?` are dropped rather than derived: all three had ZERO call sites in this file and the type is file-private — the same never-matched-probe shape #11493 deleted. Hot pool (de)registration is really driven through `ConnectionEngineLike`, which declares them and is the type the connection service is handed. Optionality is preserved exactly (`Partial<…>`): this is a deliberate graceful-degradation seam and making any member required would change what a degraded boot does. Type-only change — no runtime behaviour changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4
📓 Docs Drift CheckThis PR changes 1 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 — 0 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 dc41fe3e0ac53b43042120f5fb2d7f46c14e53ad && git checkout dc41fe3e0ac53b43042120f5fb2d7f46c14e53ad
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2cc71222459e91964e883419611a820c28302429 de81acf547a4b7122453f94d9e58df9da051d05f && git checkout -B drift-repro 2cc71222459e91964e883419611a820c28302429 && git merge --no-ff de81acf547a4b7122453f94d9e58df9da051d05f
node scripts/docs-audit/affected-docs.mjs --json 2cc71222459e91964e883419611a820c28302429
|
Uh oh!
There was an error while loading. Please reload this page.
Part of #11833
Replaces the consumer-local structural
DataEngineLikeinservice-datasource/src/datasource-admin-plugin.tswith the declaredIDataEnginecontract members — the #4251 B3 sweep pattern, extending #11493's ruling one file over.This lands one of the card's two sites. The
service-analyticshalf is deliberately NOT in this PR: measuring it turned up two forks and two signature mismatches that cannot be resolved without widening a contract, which the card rules is not a consumer-side call. That is why this saysPart ofand not a closing keyword — #11833 stays open for the analytics half. Detail below.The card's real deliverable: the compatibility measurement
#11833 marked itself "a pattern observation, not a measured defect" and flagged the unverified half: whether the loosened local signatures are still assignable-compatible with the contract members they shadow. Nothing compiled them, so I compiled them — a throwaway probe asserting the real
IDataEngine/IObjectQLEngineagainst each local declaration, whole-object and member-by-member, under this repo'sstrict: true.datasource-admin-plugin.ts— the site this PR changesfindOne?IDataEngine.findOnefind?IDataEngine.findinsert?IDataEngine.insertupdate?IDataEngine.updatedelete?IDataEngine.deleteregisterDriver?IObjectQLEngine.registerDriverregisterDatasourceDef?getDriverByName?IDataEngine.getDriverByName?Two things the card did not expect:
1. Three members were dead. The card states "no dead member was found here." Three were:
registerDriver,registerDatasourceDefandgetDriverByNamehave zero call sites indatasource-admin-plugin.ts, and the type is file-private. That is the same never-matched-probe shape #11493 deleted. They are dropped rather than derived — hot pool (de)registration really runs throughConnectionEngineLike, which declares them and is the type the connection service is handed, so dropping leaves one declaration instead of two.2.
registerDriver?was actively wrong about the engine. It declared(driver: unknown, …); the contract is(driver: IDataDriver, …). UnderstrictFunctionTypesthe real engine is therefore not assignable to the local view:The local type promised the engine accepts any value as a driver. It does not.
service-analytics/plugin.ts— measured, deliberately NOT changedThe card describes this type as
aggregate+execute. It actually declares five members, and only one is cleanly replaceable:execute?IDataEngine.execute?aggregateIDataEngine.aggregategetObject?IObjectQLEngine.getObjectunknownresolveEffectiveDatasource?ObjectQL:6530)getDriverForObject?ObjectQL:12361)aggregate— the local type declaresaggregations[].function: string; the contract declares the six-value enum'count'|'sum'|'avg'|'min'|'max'|'count_distinct'. The widening is not vacuous: the authorableAggregationMetricType(packages/spec/src/data/analytics.zod.ts) has nine values — those six plusnumber/string/booleancustom-SQL expressions. So the auto-bridge can and does carry amethodthe engine contract does not admit. Substituting the contract member here would surface that as a build error, which is the correct signal — but resolving it is a design decision about how custom-SQL measures route, not something to settle with a cast.getObject?— the contract member returnsunknown. The local type claims a structured{ fields?, external? }and ~10 call sites read?.fields?.[…]off it. Substituting would replace real typing with casts at every one — a net loss, and contract-first says fix the contract's return type, not the consumer.resolveEffectiveDatasource?/getDriverForObject?— real on theObjectQLclass, declared by no contract. Per the card these are forks: reported, not widened from the consumer side.Which members stayed optional, and why
Partial<Pick<IDataEngine, …>>— every member stays optional, exactly as the hand-written type had it. This is a deliberate graceful-degradation seam: a lightweight kernel can register a'data'service with no durable CRUD, in which case all five are absent and datasource persistence degrades to in-memory (pre-existing behavior). Theengine?.insert/engine?.findruntime probes are the other half of that same contract. Making any member required would change what a degraded boot does, which the card puts out of scope.Partial<>here is load-bearing, not shorthand.Verification
Commit
de81acf547. Type-only change — the emitted JS is unchanged (a type alias erases).pnpm --filter @objectstack/service-datasource typecheck→EXIT=0pnpm --filter @objectstack/service-datasource test→Test Files 27 passed (27)·Tests 585 passed (585), including the degraded-path cases (does not block boot when nothing is persisted (dev: in-memory store), the sys_metadata persist/restore-across-restart pair, and the durable-row delete)pnpm --filter @objectstack/service-datasource build→DTS ⚡️ Build successpnpm lint(full repo,eslint . --no-inline-config) →VERDICT command-exit 0node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, all green, each quoting its own verdict:check:published-files,check:slot-lookup("ratchet holds: 107 unswept site(s) … none new"),check:test-source-alias,check:type-source-resolution,check:nul-bytes,check:ratchet-remedy-authority,check:pm-skill-ratchet,check:query-options-erasure("ratchet holds: 67 unswept non-test site(s) … none new"),check-plugin-teardown-shape,check-affected-docs,check-drift-commentAblation
Predicted in writing before running. A runtime ablation of a type-only change is vacuous by construction, so the real ablation is at the compiler, and it has a control leg:
registerDatasourceDefback into thePick<>list fails with TS2344 "does not satisfy the constraintkeyof IDataEngine".ABLATED_EXIT=2,error TS2344: Type '"find" | "registerDatasourceDef" | …' does not satisfy the constraint 'keyof IDataEngine'. (First attempt was a malformed 3-argPickand failed as TS2314 arity instead; re-run with correct union syntax gave the predicted diagnostic.)origin/main:CONTROL_EXIT=0, green. A member no contract declares typechecked clean. That is the drift The service-lookupanyrule missesgetService<any>(...)— 80 sites erase the slot contract, 3 of them inside the rule's own scope #4251 exists to prevent, demonstrated live, and it is what this PR closes at this seam.Mutation was confirmed on disk each leg by grepping the injected marker and the displaced anchor (never a bare
git diff --stat), each leg carried atrap … EXIT INT TERMrestore, and after every legdisk == index == HEADwas verified withgit diff --quiet/git diff --cached --quietplus an emptygit status --porcelain.No changeset
Type-only, no runtime behaviour change, no public surface moves — the replaced type is file-private and the members dropped had no call sites. Labelled
skip-changeset.Out-of-scope finding
Filed as #12010 (unassigned): a third consumer-local structural engine type,
ConnectionEngineLike— exported, and the onedatasource-admin-plugin.tsactually casts to — carries the sameregisterDriver?mismatch plus three members no contract declares (registerDatasourceDef,markDatasourceUnavailable,clearDatasourceUnavailable). Not touched here: closing it requires widening a contract, which is a maintainer call.Generated by Claude Code