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
[finding] MongoDBDriver.update() and RemoteTransport.update() fabricate a record for a missing id — a third posture that becomes a semantic violation once IDataDriver.update() declares null (#13878 ruling item 5) #14428
Filed unassigned by the #13878 dev seat (session session_0112hMx9hjJ9BgB28X97DS68), as the director's ruling on #13878 (comment 5494524403) item 5 directs — verbatim, untranslated:
Recording + measurement only — no severity asserted, no fix chosen; the posture is the maintainer's decision. Generic types are written in SQUARE brackets throughout (the body sanitizer eats the angle-bracket spelling).
updateOne({ id }), then findOne({ id }); when nothing comes back it returns withoutUndefinedOwnKeys({ id: String(id), ...updateData }) — a row assembled from the caller's payload plus the updated_at it stamped, for an id that names no document
UPDATE ... WHERE "id" = ?, then SELECT * ... WHERE "id" = ?; when no row comes back it returns { id, ...data } — the caller's payload with the id stapled on
Both are declared Promise[Record[string, unknown]] and both honour that declaration by inventing data. After #13878 the contract's not-found arm is null (the shape findOne carries, what InMemoryDriver / SqlDriver / TursoDriver (local) / SqliteWasmDriver return), so "a row for an id that does not exist" is no longer a way of satisfying the declaration — it is a value the declaration now distinguishes from, and these two drivers answer "updated" where every other driver answers "not found".
Who reads their update() results — the measurement the ruling asked for
Reader
Path
What a fabricated row does there
Engine by-id dispatch
packages/objectql/src/engine.ts:11017 — result = await driver.update(object, id, data, options)
Returned to the engine's caller as the updated record. The only guard downstream is typeof result === 'object' && result && 'id' in result around an id read — a fabricated row passes it, so a REST / SDK / MCP update on a missing id answers 200 with a record that does not exist, on these two drivers only
Passes the fabricated row through as the driver's own result; the local branch (super.update, SqlDriver) returns null for the same miss — one driver, two postures, chosen by isRemote
RemoteTransport.bulkUpdate()
packages/drivers/driver-turso/src/remote-transport.ts:1625-1632 — if (updated) results.push(updated)
The cross-driver skip convention SqlDriver.bulkUpdate follows is dead code on this transport: updated is never falsy, so a batch over N missing ids answers N fabricated rows
In-package bulkUpdate on Mongo
none — mongodb-driver.ts has no this.update( call site
No claim about the upsert doors of either driver (an upsert never answers "not found" by definition).
Dedup
MCP search_issues, one targeted query (MongoDBDriver update fabricates synthesizes a record for a missing id RemoteTransport returns invented row instead of null not found) → 5 results, none on this posture: #13878 (the parent), #12586 (turso json column types), #11151 (mongo boolean aggregates), #6944 (turso remote autonumber), #5088 (updateMany hooks on a nonexistent id). Control query for #13878's own title → #13878 ranked first ⇒ the channel answers, the zero on this posture is a reading.
Related
#13878 (the ruling, item 5) · #13854 (SqlDriver.bulkUpdate, the live dependent of the null skip — the convention RemoteTransport.bulkUpdate copies but can never take)
Triage — the block is discharged and the premise is now present tense
#13878 closed as completed at 2026-09-02T08:28:30Z via merged PR #14434 ("declare the not-found arm on IDataDriver.update() and un-mask driver-memory's published update/upsert types"). The Blocked-by: #13878 line was therefore exhausted and has been removed from this body — verified by re-reading the card after the write.
That discharge is what makes this card urgent rather than anticipatory. The contract's | null arm is on mainnow, so the sentence "these two drivers answer updated where every other driver answers not found" is a description of today, not of a state after some future landing. Everything the measurement table predicts is live.
The measurement the ruling ordered — 「实施者测完『谁读它们的 update() 结果』后另立卡」 — is done and it is thorough (five readers enumerated, the dead if (updated) in RemoteTransport.bulkUpdate found, and the absence of any test pinning the miss posture established). So this card is ready for the ruling half: 「行为变更,量了再裁」 — the measuring is finished, the ruling is the maintainer's.
③ 防 AI 犯错 —— 最响的一棱,而且坏在方向上:它在该说「没找到」的地方说了「成功」。捏造的那行还带着调用方自己传进去的字段和一个刚刚戳上的 updated_at,看起来完全合理,没有任何地方不对劲。调用方(人或 agent)据此认定写入落地了 —— 不重试、不告警、不回滚。批量更糟:RemoteTransport.bulkUpdate 里那句跨驱动的 if (updated) 跳过约定在这个传输上是死代码,N 个不存在的 id 换回 N 行假数据。
Filed unassigned by the #13878 dev seat (session
session_0112hMx9hjJ9BgB28X97DS68), as the director's ruling on #13878 (comment 5494524403) item 5 directs — verbatim, untranslated:Recording + measurement only — no severity asserted, no fix chosen; the posture is the maintainer's decision. Generic types are written in SQUARE brackets throughout (the body sanitizer eats the angle-bracket spelling).
The two sites (measured at
79b6a22a5)MongoDBDriver.update()packages/drivers/driver-mongodb/src/mongodb-driver.ts:403-424updateOne({ id }), thenfindOne({ id }); when nothing comes back it returnswithoutUndefinedOwnKeys({ id: String(id), ...updateData })— a row assembled from the caller's payload plus theupdated_atit stamped, for an id that names no documentRemoteTransport.update()packages/drivers/driver-turso/src/remote-transport.ts:1517-1534UPDATE ... WHERE "id" = ?, thenSELECT * ... WHERE "id" = ?; when no row comes back it returns{ id, ...data }— the caller's payload with the id stapled onBoth are declared
Promise[Record[string, unknown]]and both honour that declaration by inventing data. After #13878 the contract's not-found arm isnull(the shapefindOnecarries, whatInMemoryDriver/SqlDriver/TursoDriver(local) /SqliteWasmDriverreturn), so "a row for an id that does not exist" is no longer a way of satisfying the declaration — it is a value the declaration now distinguishes from, and these two drivers answer "updated" where every other driver answers "not found".Who reads their
update()results — the measurement the ruling asked forpackages/objectql/src/engine.ts:11017—result = await driver.update(object, id, data, options)typeof result === 'object' && result && 'id' in resultaround an id read — a fabricated row passes it, so a REST / SDK / MCPupdateon a missing id answers 200 with a record that does not exist, on these two drivers onlyTursoDriver.update()remote branchpackages/drivers/driver-turso/src/turso-driver.ts:778—this.formatRemoteRow(object, await this.remoteTransport!.update(...))super.update,SqlDriver) returnsnullfor the same miss — one driver, two postures, chosen byisRemoteRemoteTransport.bulkUpdate()packages/drivers/driver-turso/src/remote-transport.ts:1625-1632—if (updated) results.push(updated)SqlDriver.bulkUpdatefollows is dead code on this transport:updatedis never falsy, so a batch over N missing ids answers N fabricated rowsbulkUpdateon Mongomongodb-driver.tshas nothis.update(call sitemongodb-driver.test.ts:157,171·turso-driver.test.ts:138,731·turso-remote-autonumber-refusal.test.ts:369update()results over rows that EXIST; no test pins the miss posture on either driver, so changing it breaks nothing landedWhat this does NOT claim
InMemoryDriver.update()returnsnullfor a missing id, whichIDataDriver.update()'s declared return type forbids — hidden for the life of the code by an inferredany#13878 are (a) returnnullon a miss — the contract's declared arm and what the other four implementations do — or (b) throw. Both are behaviour changes visible to every reader above; (a) would want a per-driver pin of the same shape as driver-memory'sshould return null on update of missing record in default mode.InMemoryDriver.update()returnsnullfor a missing id, whichIDataDriver.update()'s declared return type forbids — hidden for the life of the code by an inferredany#13878's PR:packages/drivers/driver-mongodb/**andpackages/drivers/driver-turso/**were fenced to measure-only by the dispatch.upsertdoors of either driver (an upsert never answers "not found" by definition).Dedup
MCP
search_issues, one targeted query (MongoDBDriver update fabricates synthesizes a record for a missing id RemoteTransport returns invented row instead of null not found) → 5 results, none on this posture: #13878 (the parent), #12586 (tursojsoncolumn types), #11151 (mongo boolean aggregates), #6944 (turso remote autonumber), #5088 (updateManyhooks on a nonexistent id). Control query for #13878's own title → #13878 ranked first ⇒ the channel answers, the zero on this posture is a reading.Related
#13878 (the ruling, item 5) · #13854 (
SqlDriver.bulkUpdate, the live dependent of thenullskip — the conventionRemoteTransport.bulkUpdatecopies but can never take)Triage — the block is discharged and the premise is now present tense
#13878 closed as completed at 2026-09-02T08:28:30Z via merged PR #14434 ("declare the not-found arm on
IDataDriver.update()and un-mask driver-memory's published update/upsert types"). TheBlocked-by: #13878line was therefore exhausted and has been removed from this body — verified by re-reading the card after the write.That discharge is what makes this card urgent rather than anticipatory. The contract's
| nullarm is onmainnow, so the sentence "these two drivers answer updated where every other driver answers not found" is a description of today, not of a state after some future landing. Everything the measurement table predicts is live.The measurement the ruling ordered — 「实施者测完『谁读它们的
update()结果』后另立卡」 — is done and it is thorough (five readers enumerated, the deadif (updated)inRemoteTransport.bulkUpdatefound, and the absence of any test pinning the miss posture established). So this card is ready for the ruling half: 「行为变更,量了再裁」 — the measuring is finished, the ruling is the maintainer's.<!-- os-decision-facets -->
null」,六个实现里四个照做,两个捏造一行数据交差。而那两个之所以捏造,靠的是「声明不允许 null,所以必须返回点什么」这个理由 —— 那个理由已经被 [finding]InMemoryDriver.update()returnsnullfor a missing id, whichIDataDriver.update()'s declared return type forbids — hidden for the life of the code by an inferredany#13878 修掉了,姿态却还留着。长远终态只有一个:一个契约一个答案。①指向姿态 (a) 返回null,因为那是契约自己声明的那一支,也是另外四个实现的做法;(b) 抛错是第三种姿态,等于在两个答案之上再加一个。InMemoryDriver.update()returnsnullfor a missing id, whichIDataDriver.update()'s declared return type forbids — hidden for the life of the code by an inferredany#13878 已合并关闭,契约的null支已在main上。此刻一个客户对着已删除的记录发 update,在 Mongo 或 Turso 远程上拿回的是 200 加一条并不存在的记录。不是「将来会怎样」。updated_at,看起来完全合理,没有任何地方不对劲。调用方(人或 agent)据此认定写入落地了 —— 不重试、不告警、不回滚。批量更糟:RemoteTransport.bulkUpdate里那句跨驱动的if (updated)跳过约定在这个传输上是死代码,N 个不存在的 id 换回 N 行假数据。推荐:A = 姿态 (a),miss 返回
null。 四棱同向。每个驱动配一条与driver-memory的should return null on update of missing record in default mode同形的 pin —— 卡面已经量到当前没有任何测试钉住 miss 姿态,所以这是净增覆盖,不是改基线,风险面比一般行为变更小得多。回退:B = 姿态 (b) 抛错。 若维护者认为「更新一个不存在的记录」应当是响亮失败而非安静的
null,走这条 —— 但必须同批裁定另外四个实现是否一起改,⛔ 否则就是把两种答案变成三种,而那正是本卡要消灭的东西。置信缺口(本分析看不见什么): 没有量 Mongo 与 Turso 远程的真实部署面 —— 有多少客户在这两个驱动上跑写路径,决定 (a) 的迁移面是零还是非零。卡面量到的是仓内无测试钉住,仓外未知,而那正是「净增覆盖」这句话唯一可能不成立的地方。
Generated by Claude Code