Blocked-by: #13878
Recorded by the domain:engine execution seat (session session_0112hMx9hjJ9BgB28X97DS68) from the #13878 dev report (comment 5505634328, "reported, not filed") — the dev left the routing to the seat because the file belonged to #13854 at dispatch time; #13854 has since closed (PR #14170 merged), so the coupling is gone and the observation gets its own card. Generic type arguments are written in SQUARE brackets (the body sanitizer eats the angle-bracket spelling).
The observation
packages/drivers/driver-sql/src/sql-driver.ts — SqlDriver.update() (around :6820 on origin/main at ad54eb342; locate by symbol) is declared with an explicitPromise[any] and returns this.formatOutput(object, updated) || null on a missing id. So the published .d.ts of @objectstack/driver-sql says any where the behaviour is "record or null", and every consumer dereferences the result with zero compiler complaint — the same mask class #13878 measured on InMemoryDriver (there it was an INFERRED any through the store's type parameter; here it is written out).
Inherited unchanged by TursoDriver (extends SqlDriver, local branch super.update) and SqliteWasmDriver (extends SqlDriver).
Why it matters now
PR #14434 (#13878, ruling A) declares the not-found arm on IDataDriver.update() — Promise[Record[string, unknown] | null] — and un-masks driver-memory's doors so a caller must narrow before reading. SqlDriver's explicit any still satisfies the widened contract structurally, so tsc says nothing, and the family is honest on one driver and masked on three.
Suggested shape (not a decision)
Replace the explicit Promise[any] on SqlDriver.update() with the contract's type Promise[Record[string, unknown] | null] (and check upsert() / the sibling doors in the same file for the same spelling); add the type-level pin driver-memory now carries (memory-update-declared-null.test.ts is the template: IsAny = false, Equals the contract shape) inside driver-sql's tsc program; then typecheck the prefix consumer closure — a reader that dereferenced the any result without narrowing will surface as a real error, which is the point. Changeset @objectstack/driver-sql: minor (published .d.ts narrows from any, the same convention PR #14434 used), plus whatever the turso/sqlite-wasm packages need if their emitted .d.ts re-declares the door.
Dedup
search_issues (semantic) for the mask on SqlDriver.update returned only #13878 (the driver-memory sibling, this finding's source) and #14428 (Mongo / RemoteTransport fabrication posture); a control query for driver-sql bulkUpdate no transaction returned #13854 — the channel fires. #14435 covers driver-memory's remaining any doors (find / findOne / create), not driver-sql.
Related: #13878 (ruling A, PR #14434) · #14428 · #14435 · #13854 (closed; PR #14170 — the transaction fix on bulkUpdate, whose if (updated) guard is the live consumer of the null arm).
Filed unassigned for triage.
Generated by Claude Code
Blocked-by: #13878
Recorded by the
domain:engineexecution seat (sessionsession_0112hMx9hjJ9BgB28X97DS68) from the #13878 dev report (comment 5505634328, "reported, not filed") — the dev left the routing to the seat because the file belonged to #13854 at dispatch time; #13854 has since closed (PR #14170 merged), so the coupling is gone and the observation gets its own card. Generic type arguments are written in SQUARE brackets (the body sanitizer eats the angle-bracket spelling).The observation
packages/drivers/driver-sql/src/sql-driver.ts—SqlDriver.update()(around:6820onorigin/mainatad54eb342; locate by symbol) is declared with an explicitPromise[any]and returnsthis.formatOutput(object, updated) || nullon a missing id. So the published.d.tsof@objectstack/driver-sqlsaysanywhere the behaviour is "record ornull", and every consumer dereferences the result with zero compiler complaint — the same mask class #13878 measured onInMemoryDriver(there it was an INFERREDanythrough the store's type parameter; here it is written out).Inherited unchanged by
TursoDriver(extends SqlDriver, local branchsuper.update) andSqliteWasmDriver(extends SqlDriver).Why it matters now
PR #14434 (#13878, ruling A) declares the not-found arm on
IDataDriver.update()—Promise[Record[string, unknown] | null]— and un-masks driver-memory's doors so a caller must narrow before reading.SqlDriver's explicitanystill satisfies the widened contract structurally, sotscsays nothing, and the family is honest on one driver and masked on three.Suggested shape (not a decision)
Replace the explicit
Promise[any]onSqlDriver.update()with the contract's typePromise[Record[string, unknown] | null](and checkupsert()/ the sibling doors in the same file for the same spelling); add the type-level pin driver-memory now carries (memory-update-declared-null.test.tsis the template:IsAny= false,Equalsthe contract shape) inside driver-sql's tsc program; then typecheck the prefix consumer closure — a reader that dereferenced theanyresult without narrowing will surface as a real error, which is the point. Changeset@objectstack/driver-sql: minor(published.d.tsnarrows fromany, the same convention PR #14434 used), plus whatever the turso/sqlite-wasm packages need if their emitted.d.tsre-declares the door.Dedup
search_issues(semantic) for the mask onSqlDriver.updatereturned only #13878 (the driver-memory sibling, this finding's source) and #14428 (Mongo / RemoteTransport fabrication posture); a control query fordriver-sql bulkUpdate no transactionreturned #13854 — the channel fires. #14435 covers driver-memory's remaininganydoors (find/findOne/create), not driver-sql.Related: #13878 (ruling A, PR #14434) · #14428 · #14435 · #13854 (closed; PR #14170 — the transaction fix on
bulkUpdate, whoseif (updated)guard is the live consumer of thenullarm).Filed unassigned for triage.
Generated by Claude Code