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
{{ message }}
Repository navigation
Commit 869fa76
Browse filesBrowse the repository at this point in the historyBrowse files
fix(service-datasource): a plugin-driver datasource connected or reconnected through the connection service keeps the engine's floor (#27715)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiVy7F2atKWpo5rp3YPrqddh)_
**Fable review owed.** This PR was finished on Opus while the Fable
quota was out (maintainer order: finish on Opus, then wait for the
contract review). It touches `packages/spec/src/**`, so it does not land
until an independent full Fable review and a same-shape
`CONTRACT_REVIEW_TIER` record PASS on its current head.
Card #27666 (epic #27261), from the post-landing contract review of PR
#27403 (6502877381, ③).
## What changes
- `EngineDatasourceDef` (`@objectstack/spec/contracts`) gains an
optional `driver`, the datasource's driver id.
- `DatasourceConnectionService.connect` registers the def with
`record.driver`. `reconnect`'s keep-old-pool restore registers the old
record's driver id (`previous.driver`). Before this, a plugin-driver
datasource built through the connection service was never held behind
the #27277 floor, and a reconnect dropped the floor because
`unregisterDriver` deletes the stored def.
- The engine comment on `registerDatasourceDef` now says the stored id
is a keep, not a source.
- Riders from the same review (④, comment-only): the
`EXTERNAL_ROW_CAP_EXCEEDED` ledger row says why it is deliberately not
the standard `MAX_RECORDS_EXCEEDED`, and the "ADR-0015 Tier A" citations
in objectql name the epic ruling instead.
## Evidence (local, at `df7e3c66`)
- `packages/spec` `src/contracts/data-engine.test.ts`: 36/36.
- `packages/objectql` `src/external-driver-floor.test.ts`: 54/54,
including the unregister-then-connect sequences.
- `packages/services/service-datasource`: connection-service and
update-rebuild suites, 114/114.
- `tsc --noEmit` clean for spec, objectql and service-datasource.
- spec `check:api-surface`, `check:export-origins`, `check:docs`,
`check:generated` and `check:error-code-provenance` all exit 0.
Implementation: the #27666 card thread's commit (picked onto this branch
after the thread stalled on the quota) plus the epic PM's changeset
declaration.
Clause-②: yes (narrowing)
Metadata-protocol: none
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(service-datasource): a plugin-driver datasource connected or reconnected through the connection service keeps the engine's correctness floor (#27666)
7
+
8
+
Clause-②: yes (narrowing) — `EngineDatasourceDef` (`@objectstack/spec/contracts`) gains an optional `driver` key, and a plugin-driver datasource connected through the datasource-connection service is now held behind the #27277 floor, so its accepted reads and writes narrow as that floor's changeset describes
9
+
Metadata-protocol: none
10
+
11
+
<!-- adr-0087: not-required (no-migration-prescription) No key of any authored schema is added, removed, renamed or re-shaped; the new key is on an engine-facing contract that the connection service fills itself. A datasource that now gets the floor needs no metadata change, so there is nothing for a migration to rewrite. -->
12
+
13
+
**BREAKING** for a host whose datasource-driver factory builds plugin drivers: those datasources are now floored (a read matching more than 1000 rows answers 422 `EXTERNAL_ROW_CAP_EXCEEDED`, predicate bulk writes and `aggregate` are refused by name), as manifest-declared ones already were.
14
+
15
+
The engine holds a plugin driver on a non-managed datasource behind a correctness floor (row cap, the query re-applied in memory, remote write refusals mapped) when the datasource definition it stores names a driver id that is not built in. Only the engine's own manifest install path carried that id. The datasource-connection service registered the definition without it, and `EngineDatasourceDef` had no such key, so a datasource created at runtime through datasource management was never floored, and one edited and reconnected lost the floor (the eviction drops the stored definition, and the connect that follows had no id to keep). Nothing said so: a driver that ignored `where` could answer rows outside the caller's sharing or row-level scope.
16
+
17
+
-`EngineDatasourceDef` (`@objectstack/spec/contracts`) gains an optional `driver`, the datasource's driver id. An engine never reads an absent key as "built in": a caller that registers only the write-gate facts keeps the id stored before it.
18
+
-`DatasourceConnectionService.connect` registers the definition with `record.driver`. `reconnect`'s keep-old-pool restore registers the OLD record's driver id (`previous.driver`; `previous` now carries it), not the new record's, because the kept pool was built from the old record.
19
+
20
+
A built-in driver (memory, sqlite, sqlite-wasm, postgres, mysql, mongodb, turso) and a managed datasource are unchanged: the floor engages only for a driver id a server plugin published on a datasource whose `schemaMode` is not `managed`. A host that registers no driver factory for plugin ids (the default) sees no change today.
21
+
22
+
What to do: nothing. A host whose factory builds plugin drivers gets the floor on those datasources from the connection service's next connect.
it('control: after the eviction a re-registration that names no driver has nothing to keep, and the where-ignoring driver answers every row',async()=>{
557
+
// The defect's mechanism, pinned so the positive cases above are known to
558
+
// depend on the driver id reaching `registerDatasourceDef`: the engine's
559
+
// "keep the stored id" only keeps what the eviction left, which is nothing.
0 commit comments