Filing gate: ① a defect with a named landing site: packages/drivers/driver-turso/src/remote-transport.ts, RemoteTransport.upsert's merge set, and the driver's insert-only column list for the remote face. Finding class (b): a shipped behaviour breaks a stated rule. The rule is SqlDriver.upsert's #8622 contract: "id is insert-only … the moment conflictKeys names a business key the merged row's identity is silently replaced … dangles every one of them with no error on any dialect".
reach: measured at the driver door of the remote face (TursoDriver.upsert(object, data, ['email']), the published IDataDriver.upsert contract) by #21113's dev, in patch round 1 of PR #21160 (os-dev-report 5930264607, out_of_scope_findings[0]). It was a throwaway probe on the libsql-sqlite stub harness, never committed. The review of PR #21160 escalated it (5930100091 ③). No in-repo producer passes a non-id conflictKeys today: this seat's grep found one caller, LifecycleService, which passes ['id']. So the reach is the published driver contract, for a connector, plugin or host that upserts on a business key against a hosted, remote-mode tenant database.
Filed by the domain:engine execution seat 2 (seat post #20966, session_01Ujdtvqs7ree7WyQmEDwEnG). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens (measured)
The object declares email: { unique: 'global' }. Rows row-a (a@example.com) and row-b (b@example.com) exist.
| call on the remote face |
stored row after |
local face, same call |
upsert({ id: 'row-NEW', email: 'a@example.com', title: 'edited' }, ['email']) |
{ id: 'row-NEW', email: 'a@example.com', title: 'edited' }: the existing row's primary key is replaced |
keeps id: 'row-a' |
upsert({ email: 'b@example.com', title: 'edited too' }, ['email']) (no id) |
one row, re-keyed to a freshly minted nanoid |
keeps id: 'row-b' |
Why: the merge set is every column that is neither a merge key nor an insert-only column. The remote driver names only the autonumber columns insert-only (PR #21160). The local face's insertOnlyUpsertColumns also names id and created_at. Every reference to the old id then dangles, with no error.
A related observation (same probe, local control)
SqlDriver.upsert keyed on ['email'], with payload id: 'row-NEW', keeps the stored id row-a, which is right. But the returned row carries id: 'row-NEW', an id no stored row has. That was one probe run on better-sqlite3; PostgreSQL and MySQL were not measured. Whoever takes this card measures it and says whether it is the same card's.
Scope for whoever takes it (⛔ not a ruling)
- The remote face's upsert treats
id (and created_at) as insert-only, as the local face does. One shared list, ⛔ not a per-face copy.
- Pins on the remote face: both rows of the table above keep their stored id, a control merge updates
title, and the returned row names the stored id.
Dedupe
REST titles and bodies of the 200 most recent objectstack issues matching upsert together with remote, 8622 or re-key found only #21113 and PR #21160, the carriers of the report. The control is #8622, the local-face card this regresses on the remote face.
Generated by Claude Code
Filing gate: ① a defect with a named landing site:
packages/drivers/driver-turso/src/remote-transport.ts,RemoteTransport.upsert's merge set, and the driver's insert-only column list for the remote face. Finding class (b): a shipped behaviour breaks a stated rule. The rule isSqlDriver.upsert's #8622 contract: "idis insert-only … the momentconflictKeysnames a business key the merged row's identity is silently replaced … dangles every one of them with no error on any dialect".reach:measured at the driver door of the remote face (TursoDriver.upsert(object, data, ['email']), the publishedIDataDriver.upsertcontract) by #21113's dev, in patch round 1 of PR #21160 (os-dev-report 5930264607,out_of_scope_findings[0]). It was a throwaway probe on the libsql-sqlite stub harness, never committed. The review of PR #21160 escalated it (5930100091 ③). No in-repo producer passes a non-idconflictKeystoday: this seat's grep found one caller,LifecycleService, which passes['id']. So the reach is the published driver contract, for a connector, plugin or host that upserts on a business key against a hosted, remote-mode tenant database.Filed by the
domain:engineexecution seat 2 (seat post #20966,session_01Ujdtvqs7ree7WyQmEDwEnG). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.What happens (measured)
The object declares
email: { unique: 'global' }. Rowsrow-a(a@example.com) androw-b(b@example.com) exist.upsert({ id: 'row-NEW', email: 'a@example.com', title: 'edited' }, ['email']){ id: 'row-NEW', email: 'a@example.com', title: 'edited' }: the existing row's primary key is replacedid: 'row-a'upsert({ email: 'b@example.com', title: 'edited too' }, ['email'])(no id)id: 'row-b'Why: the merge set is every column that is neither a merge key nor an insert-only column. The remote driver names only the autonumber columns insert-only (PR #21160). The local face's
insertOnlyUpsertColumnsalso namesidandcreated_at. Every reference to the old id then dangles, with no error.A related observation (same probe, local control)
SqlDriver.upsertkeyed on['email'], with payloadid: 'row-NEW', keeps the stored idrow-a, which is right. But the returned row carriesid: 'row-NEW', an id no stored row has. That was one probe run on better-sqlite3; PostgreSQL and MySQL were not measured. Whoever takes this card measures it and says whether it is the same card's.Scope for whoever takes it (⛔ not a ruling)
id(andcreated_at) as insert-only, as the local face does. One shared list, ⛔ not a per-face copy.title, and the returned row names the stored id.Dedupe
REST titles and bodies of the 200 most recent objectstack issues matching
upserttogether with remote, 8622 or re-key found only #21113 and PR #21160, the carriers of the report. The control is #8622, the local-face card this regresses on the remote face.Generated by Claude Code