You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(datasource,runtime): teardown through the one datasource path, honoring adopted pools (#3993); ADR-0062 status flip (#3992) (#3999)
DatasourceConnectionService owns the disconnect half symmetrically now:
disconnect(name, { asDefault }) resolves the default under its natural
name, disconnectAll() closes exactly the 'connected' pools this service
opened, and the new DatasourceDriverHandle.ownership discriminator
('factory' | 'host') keeps kernel teardown away from adopted instances —
createPrebuiltDriverFactory stamps 'host', so a cloud LRU eviction can
never pull a shared pool from under its other consumers. Wired at the
kernel's real teardown phase (destroy(); the Plugin contract has no
stop()) via DefaultDatasourcePlugin and DatasourceAdminServicePlugin.
ADR-0062: header and D1 notes now record the completed cross-repo
convergence (cloud#915) with the escape-hatch classification and the
parity guard's shifted role; D5 gains the teardown amendment.
Closes#3992. Closes#3993.
Claude-Session: https://claude.ai/code/session_01GK5nd3gtehcAhrF6Zzp2on
Co-authored-by: Claude <noreply@anthropic.com>
**Status**: Accepted (2026-06-22) — D2–D8 implemented (`service-datasource` connection service + opt-in-safe gate + fail-closed connect policy; native-SQL declines external per D6; D7 lint in `validate-expressions.ts`). **D1 implemented for every open-core boot path** (#3826: the standalone/artifact `default` and the CLI serve fallback are declared definitions connected through the one service — see the status notes under D1); the cloud repo's own stack composition is the last remaining second site, and the parity guard stays until it converges.
3
+
**Status**: Accepted (2026-06-22) — D1–D8 implemented. **D1 fully implemented across both repos** (#3826, closed): every open-core boot path (#3968) and every cloud composition (cloud#915 — control-plane preset, objectos `artifact-kernel-factory`, `DefaultEnvironmentKernelFactory`) connects its `default` through the one `DatasourceConnectionService` path; the remaining `DriverPlugin` uses are documented named-auxiliary/escape-hatch cases, and the degraded-boot parity guard stays to pin them (see the status notes under D1). D5's teardown half landed with #3993: kernel teardown disconnects through the same service, honouring adopted (host-owned) instances — see the amendment under D5.
4
4
5
5
**Supersedes the runtime portions of**: ADR-0015 §18 addendum (kept as the historical record). ADR-0015 remains the canonical spec/binding decision; this ADR is the canonical *runtime* decision.
6
6
@@ -68,7 +68,7 @@ Introduce a single service that, given a datasource definition, builds a driver
68
68
>
69
69
> **Config-load fallback converged too (#3826, third pass).**`createStorageDriver` is gone: the CLI's serve fallback (a host `objectstack.config.ts` with objects but no driver plugin) now emits a definition via `resolveStorageDefinition` and hands it to the same `DefaultDatasourcePlugin`. `mysql` joined the shared factory for it; the dev loosen-only self-heal (#2186) rides as `config.autoMigrate` and the CLI's wasm persistence mode as `config.persist` — host-composition passthroughs the factory honours, never part of the app-facing datasource spec. `turso`/libSQL keeps its loud typed failure at *resolution* (nothing is constructed to fail later). The `telemetry` sibling datasource deliberately stays a pre-built `DriverPlugin` (the documented escape hatch for named auxiliary drivers): it is best-effort, dev-oriented, and its own `resolveSqliteDriver` step-down check replaces the old primary-resolution coupling.
70
70
>
71
-
> **Remaining second site: the cloud repo's own compositions** — the real primary-driver sites are `environment-kernel-factory.ts` (each environment's per-tenant driver) and the control-plane preset fed by `cloud-stack.ts`'s `buildControlDriver`, which also owns the `turso` driver. Until they converge, `packages/runtime/src/degraded-boot-parity.test.ts` remains load-bearing: it pins both connect paths to the same operator-visible contract (fail-fast by default, identical `OS_ALLOW_DRIVER_CONNECT_FAILURE` parsing, `DEGRADED BOOT` on stderr). #3741 → #3758 was exactly the miss it exists to catch.
71
+
> **Convergence completed — the cloud compositions landed (cloud#915), no second site remains.** The cloud repo's three primary-driver sites — the control-plane preset (fed by `cloud-stack.ts`'s `buildControlDriver`, whose instance doubles as every environment kernel's proxy base), `artifact-kernel-factory.ts` (the objectos per-tenant hot path), and both `DefaultEnvironmentKernelFactory` paths — now boot through `DefaultDatasourcePlugin` with their pre-built instances adopted via `createPrebuiltDriverFactory`. Every remaining `DriverPlugin` use is a **documented named-auxiliary / escape-hatch case**, not a default path: the framework's `telemetry` sibling, the cloud proxy `cloud` datasource (`registerAsDefault: false`), the objectos host routing shell, the artifact factory's `'cloud'` alias metadata registration, and test injection. `packages/runtime/src/degraded-boot-parity.test.ts` **stays load-bearing after the convergence**, with its role shifted from "the unconverged second implementation must not drift" to "the escape hatches must not drift": as long as `ObjectQLEngine.init()` can throw a connect verdict at all (the boot re-verification, pre-built `DriverPlugin` drivers), that verdict must match the service's. #3741 → #3758 was exactly the miss it exists to catch.
72
72
>
73
73
> **The convergence seam (#3826, fourth pass).** Two properties of the cloud composition made "just declare it" impossible: its driver kinds live outside open-core (`turso`), and its instances are *pooled beyond one kernel* (the control-plane driver doubles as the proxy base of every environment kernel; per-environment drivers are cached across kernel rebuilds — reconstruction per boot would multiply pools). So `DefaultDatasourcePlugin` now accepts an **injected `IDatasourceDriverFactory`** (defaulting to the shared open-core factory), and `createPrebuiltDriverFactory` wraps an already-built instance as a factory — the "adopt an existing driver" entry point this ADR's first pass found missing, landed *as a factory* so the connect orchestration (policy-free init connect, `bootCritical` verdict, shared escape hatch, start() replay into retained state) stays this one implementation. Construction and pooling remain host concerns; only the verdict converges. The `@objectstack/verify` dogfood harness also boots through the declared default now (not the `DriverPlugin` escape hatch), making the §Risk mitigation — "behind the dogfood gate" — actually true for the converged path.
> **A `DatasourceConnectPolicy` denial is not a connect failure** and stays metadata-only, unchanged. A multi-tenant host that blocks egress for a tenant's plan is making a deliberate decision about a datasource it knows it is refusing; fail-fasting there would turn a policy verdict into a boot outage for every tenant on the shared runtime. The fail-fast set covers *failures* — unreachable, unauthenticated, unsupported — not *refusals*.
110
110
111
+
> **Amendment (#3993) — teardown is one implementation too, and it distinguishes owned from adopted.** After the D1 connect convergence, "owns connect/disconnect" was half-true: nothing disconnected the `default` (or a declared datasource's pool) on graceful shutdown — `DriverPlugin` never had teardown, `ObjectQLPlugin`'s teardown never touched drivers, and the kernel's actual teardown phase is **`destroy()`** (the Plugin contract has no `stop()`; stray `stop` methods were never called). Now `DatasourceConnectionService` owns the disconnect half symmetrically: `disconnect(name, { asDefault })` resolves the default under its **natural name** (the same #3826 rule that makes `drivers.get('default')` impossible), and `disconnectAll()` — wired from `DefaultDatasourcePlugin.destroy()` and `DatasourceAdminServicePlugin.destroy()` — closes **exactly the pools this service opened** (`'connected'` states; `already-registered` drivers belong to whoever registered them). The factory handle carries the discriminator: **`ownership: 'host'`** (set by `createPrebuiltDriverFactory`) marks an ADOPTED instance whose pool outlives the kernel — the cloud constraint: the control-plane driver doubles as every environment kernel's proxy base, per-environment drivers are registry-cached across kernel rebuilds, and an LRU eviction (`kernel.shutdown()`) closing a shared pool would pull it from under every other consumer. For those, teardown clears the retained verdict and leaves the pool to its host. A welcome side effect: a file-backed `sqlite-wasm` default with `persist: 'on-disconnect'` now actually flushes on graceful shutdown.
112
+
111
113
### D6 — Native-analytics SQL honors the remote table/columns
112
114
113
115
The analytics native-SQL strategy compiles its own `FROM "<table>"` / column references outside the driver (ADR-0015 §18 noted this). It must resolve an external object's physical table (`remoteName`/`remoteSchema`) and columns (`columnMap`) the same way `SqlDriver` now does — reusing the driver's resolution (e.g. an exposed `physicalTableFor(object)` / `physicalColumnFor(object, field)`), not a second copy. Until then, analytics over external objects stays disabled rather than silently querying the wrong table.
0 commit comments