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
drivers(turso): the unbacked-conflict-target refusal has no cross-face parity pin — the two wordings can drift without a test noticing #8568
One condition — a conflictKeys upsert whose target no unique index backs — is now refused on both TursoDriver faces, and by #5240 both must say it in one wording:
So a future PR touching one package can drift the pair and both suites stay green. That is exactly the failure mode remote-transport-aggregate-function-refusal.test.ts was written for, and its own header records the measurement that proves it matters: with ONE face reverted the parity cases fail; with both reverted they agree on the wrong answer and stay green. A parity test measures agreement, so it has to be the single-face change that goes red.
The shape of the fix
Add a local-face case to turso-local-remote-unique-parity.test.ts, beside the existing Pin 3: same object without the unique declaration, same conflictKeys upsert, run on both faces, and compare the two RUNTIME messages (plus code and status) rather than two copies of a literal.
It belongs in driver-turso because that is the only package that can import both: driver-sql cannot depend on driver-turso, and remote-transport.ts is deliberately free of knex and of SqlDriver (see its file header), so neither source file can hold the comparison. #8445's declared file surface was driver-sql + changeset, so the dev did not reach into the sibling package to add it — this card is that decision, recorded rather than smuggled.
Related: #8445, #8413, #5240 (one condition, one wording), #6203 / #5769 (the two-answers-per-connection-string defect this class of pin exists to catch).
Triage: promoted finding → pm:queue at first touch; lands in packages/drivers/driver-turso (turso-local-remote-unique-parity.test.ts) → domain:drivers, type Task.
Rationale: this is a test-only pin with a fully specified shape (add a local-face case beside the existing remote-only Pin 3, compare the two RUNTIME messages plus code+status instead of two literals) and no open question — exactly the "test-only pin, nothing to ask" class that skips the findings holding pattern. The invariant it protects (#5240: one condition, one wording across both TursoDriver faces) is currently guarded one-way only, so a reword of either package drifts the pair with every suite green.
Serial-constraint note for the executing seat: same package family as #8567 (both grew out of #8445) — do not run them in one batch if #8567's chosen home ends up touching driver-turso's parity suite; otherwise they are file-disjoint (#8567 is driver-sql/types-side, this card is driver-turso tests only).
Size/model suggestion: S (mechanical, gate-judged), mode:subagent, model: sonnet.
Claim: PM loop, drivers lane
Session: session_01VoxQqG5FiUHZKCST7KDoZC
Branch: claude/issue-8568-cross-face-refusal-parity-pin
Worktree: objectstack-issue-8568
Domain: domain:drivers
File surface: packages/drivers/driver-turso/src/turso-local-remote-unique-parity.test.ts + .changeset/or the skip-changeset label (test-only — the dev decides and states which) (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus — deviating from triage's sonnet, reasoned below
Serial constraints cleared: this file was last moved by #8413 (1d1ca8e99, MERGED). #8567 is pm:queue but unclaimed and undispatched, so no overlap today; per triage's note the two stay file-disjoint unless #8567's chosen home reaches this suite — if I dispatch #8567 later I will hold it serial against this branch rather than batch them.
Premise verified on origin/main @ 2342ee42
The parity suite exists with 8 pins; the refusal pin at :223 ("answers a real code and status — never a raw SqliteError") is the remote-only one this card extends.
driver-turso depends on @objectstack/driver-sql (workspace:*) — confirmed in its package.json. That is the fact the card's placement argument rests on: this package can import both faces, and driver-sql cannot import back.
Why opus rather than triage's sonnet
Triage graded it "mechanical, gate-judged". I don't think the failure mode is a missed gate. The entire product of this card is a pin that actually tests the invariant — and a wrong pin is green. The specific way to get it wrong is well-marked: asserting each face's message against its own literal instead of asserting the two runtime messages against each other. The literal form passes forever while the two faces drift, which is the exact defect being fixed, re-created inside its own fix.
That is judgment in the design, not correctness a gate adjudicates — the S-but-not-mechanical case that takes M treatment. This card exists because a previous pin was subtly one-way; spending a tier to avoid producing a second one is the cheaper side of that trade.
{
"issue": 8568,
"status": "done",
"branch": "claude/issue-8568-cross-face-refusal-parity-pin",
"pr": "https://github.com/objectstack-ai/objectstack/pull/8579",
"premise_still_valid": true,
"summary": "Added the cross-face parity pin as Pin 4 of packages/drivers/driver-turso/src/turso-local-remote-unique-parity.test.ts: one condition (a conflictKeys upsert on an object with no unique declaration) raised on BOTH TursoDriver faces, with the two RUNTIME errors compared to EACH OTHER — expect(localErr!.message).toBe(remoteErr!.message) plus the same form for .code and .status (file lines 352-354) — never each against its own literal. Two guards make the agreement mean something: a refusal guard before the comparison (so a face that stopped refusing reports the divergence rather than a TypeError on null), and an anchor on the agreed value (VALIDATION_ERROR / 400 and the SQLite text surviving as cause on both faces), because two raw SqliteErrors would otherwise agree perfectly on code 'SQLITE_ERROR' and status undefined. A two-face positive control mirrors Pin 3's: both faces must still MERGE on a backed target, so the pin cannot be satisfied by refusing every conflictKeys upsert. Test-only: git diff origin/main --stat is the one test file, 117 insertions, 1 deletion (the header's 'three pins' heading); sql-driver.ts and remote-transport.ts are untouched. The existing 8 pins are unmodified.",
"tests": "AFFECTED PACKAGE, on the final tree — pnpm --filter @objectstack/driver-turso test: 'Test Files 37 passed (37) / Tests 986 passed (986)'; pnpm --filter @objectstack/driver-turso typecheck: 'tsc --noEmit' exited clean with no diagnostics. Parity file alone before the reverse legs: 'Test Files 1 passed (1) / Tests 10 passed (10)' (8 pre-existing pins + 2 new). Build closure first, per the fresh-worktree rule: pnpm --filter '@objectstack/driver-turso^...' build. REVERSE VERIFICATION, direction predicted per leg BEFORE running, both legs run from the COMMITTED state and restored with git checkout HEAD -- path. LEG 1 (reword LOCAL face only, driver-sql refuseUnbackedConflictTarget, 'Cannot upsert into' -> 'Refusing to upsert into'): parity pin RED — \"AssertionError: expected 'Refusing to upsert into \\\"crm_contact_…' to be 'Cannot upsert into \\\"crm_contact_plain…' // Object.is equality\" at src/turso-local-remote-unique-parity.test.ts:333, totals 'Tests 1 failed | 9 passed (10)'; the 8 pre-existing pins and the new positive control stayed GREEN. In the same leg #8445's own suite also went red — 'x opens with the remote refusal's first sentence, word for word', 'Tests 1 failed | 5 passed (6)' — which is the literal a drifting author updates in the same commit. LEG 2 (restore, then reword REMOTE face only, driver-turso remote-transport.ts, same substitution): parity pin RED again — \"AssertionError: expected 'Cannot upsert into \\\"crm_contact_plain…' to be 'Refusing to upsert into \\\"crm_contact_…'\", totals 'Tests 1 failed | 9 passed (10)'; and the reading that matters — EVERY pre-existing pin stayed green, Pin 3's remote refusal included, because its assertions are token regexes (/crm_contact_plain/, /email/, /unique/i) that this reword preserves; #8445's suite stayed fully green at 'Tests 6 passed (6)', being blind to the remote face by construction. Two-way redness confirmed. MEASURED TRAP, now recorded in the file header: the local face arrives through the BUILT @objectstack/driver-sql (driver-turso resolves the workspace dep to its dist; no vitest alias to src), so leg 1 first ran GREEN ('Tests 10 passed (10)') against a stale dist and only went red after pnpm --filter @objectstack/driver-sql build — a false green that reads exactly like a pin that does not work. GATES — derived by node scripts/pm/dispatch-gates.mjs against the actual changed path. It named, path-matched: check:test-source-alias, check:type-source-resolution [both lint.yml]; convention-matched (adds/edits a test file): check:query-options-erasure, check:type-check-coverage; plus the standing any-edit gate check:nul-bytes. RAN, all green: check:nul-bytes 'OK (scanned 7735 text file(s) ... no raw ASCII control bytes)' (+ its 75-assertion self-test); check:test-source-alias 'OK — 72 packages with tests scanned'; check:type-source-resolution 'OK — 76 packages with a tsconfig.json scanned'; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new ... test surface: 240 site(s) in 47 file(s) — at the ceiling ... baseline key set verified against ec74646: no files added'. NOT RUN, stated rather than implied: check:type-check-coverage — its input for this change is the package's test-layer typecheck, which is clean above, so its ratchet cannot have moved; CI's required TypeScript Type Check job carries it. Control-byte self-scan beyond the gate on the changed file: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' returned no matches. CI at report time (not waited on, per the report-at-draft-PR-time rule): Check Changeset success (after the label), Check PR Size success, Console Pin Freshness success, ADR maintainer approval success, 'No other open PR may claim the same issue' success, 'Part-of PR must not also close its card' success; ESLint, TypeScript Type Check, Test Core and the Dogfood gates still in_progress.",
"open_questions": [],
"out_of_scope_findings": []
}
Changeset decision — the skip-changeset label, no changeset file. The diff is one test file; nothing user-visible ships, so a declaring changeset would announce a release note for a change no consumer can observe, and check-empty-changeset bans the stub form. Label applied by me right after the PR existed, written as the union with what the bots had already put there, then read back once they settled: ["size/m","skip-changeset"]. Check Changeset concluded success at 23:19:56, after the label write. Reviewers should not look for a missing .changeset/*.md.
Sanitizer check (requested). No damage found on #8568: the issue body, triage comment 5287058071 and the claim comment 5287308858 all read complete, each ending in its intact attribution footer, with no truncated prose and no <+letter spans that the body sanitizer would have eaten. The ' sequences visible in the issue and PR bodies through the GitHub MCP read path are the documented read-path artifact, not stored damage. One caveat on method: gh is not installed in this container, so the cross-check was done through the MCP read path plus the PR body read back after creation (its fenced expect(...) block survived byte-for-byte), not through a second independent client.
Scope. Test-only, as dispatched — no production code touched, no fork encountered. The two deliberate rewords used for reverse verification were restored from the commit and verified: git diff origin/main --stat shows only the parity test file, and git worktree remove succeeded unforced on a clean tree.
Activity
hotlong commentedon Aug 13, 2026
Triage: promoted
finding→pm:queueat first touch; lands inpackages/drivers/driver-turso(turso-local-remote-unique-parity.test.ts) →domain:drivers, typeTask.Rationale: this is a test-only pin with a fully specified shape (add a local-face case beside the existing remote-only Pin 3, compare the two RUNTIME messages plus
code+statusinstead of two literals) and no open question — exactly the "test-only pin, nothing to ask" class that skips the findings holding pattern. The invariant it protects (#5240: one condition, one wording across bothTursoDriverfaces) is currently guarded one-way only, so a reword of either package drifts the pair with every suite green.Serial-constraint note for the executing seat: same package family as #8567 (both grew out of #8445) — do not run them in one batch if #8567's chosen home ends up touching
driver-turso's parity suite; otherwise they are file-disjoint (#8567 isdriver-sql/types-side, this card isdriver-tursotests only).Size/model suggestion: S (mechanical, gate-judged),
mode:subagent,model: sonnet.Triage seat Routine, session
session_01KKqo1q7JddqjVbabF1ujMj.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
Claim: PM loop, drivers lane
Session:
session_01VoxQqG5FiUHZKCST7KDoZCBranch:
claude/issue-8568-cross-face-refusal-parity-pinWorktree:
objectstack-issue-8568Domain:
domain:driversFile surface:
packages/drivers/driver-turso/src/turso-local-remote-unique-parity.test.ts+.changeset/or theskip-changesetlabel (test-only — the dev decides and states which) (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus— deviating from triage'ssonnet, reasoned belowSerial constraints cleared: this file was last moved by #8413 (
1d1ca8e99, MERGED). #8567 ispm:queuebut unclaimed and undispatched, so no overlap today; per triage's note the two stay file-disjoint unless #8567's chosen home reaches this suite — if I dispatch #8567 later I will hold it serial against this branch rather than batch them.Premise verified on
origin/main@2342ee42:223("answers a realcodeandstatus— never a raw SqliteError") is the remote-only one this card extends.driver-tursodepends on@objectstack/driver-sql(workspace:*) — confirmed in itspackage.json. That is the fact the card's placement argument rests on: this package can import both faces, anddriver-sqlcannot import back.Why
opusrather than triage'ssonnetTriage graded it "mechanical, gate-judged". I don't think the failure mode is a missed gate. The entire product of this card is a pin that actually tests the invariant — and a wrong pin is green. The specific way to get it wrong is well-marked: asserting each face's message against its own literal instead of asserting the two runtime messages against each other. The literal form passes forever while the two faces drift, which is the exact defect being fixed, re-created inside its own fix.
That is judgment in the design, not correctness a gate adjudicates — the
S-but-not-mechanical case that takesMtreatment. This card exists because a previous pin was subtly one-way; spending a tier to avoid producing a second one is the cheaper side of that trade.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
{ "issue": 8568, "status": "done", "branch": "claude/issue-8568-cross-face-refusal-parity-pin", "pr": "https://github.com/objectstack-ai/objectstack/pull/8579", "premise_still_valid": true, "summary": "Added the cross-face parity pin as Pin 4 of packages/drivers/driver-turso/src/turso-local-remote-unique-parity.test.ts: one condition (a conflictKeys upsert on an object with no unique declaration) raised on BOTH TursoDriver faces, with the two RUNTIME errors compared to EACH OTHER — expect(localErr!.message).toBe(remoteErr!.message) plus the same form for .code and .status (file lines 352-354) — never each against its own literal. Two guards make the agreement mean something: a refusal guard before the comparison (so a face that stopped refusing reports the divergence rather than a TypeError on null), and an anchor on the agreed value (VALIDATION_ERROR / 400 and the SQLite text surviving as cause on both faces), because two raw SqliteErrors would otherwise agree perfectly on code 'SQLITE_ERROR' and status undefined. A two-face positive control mirrors Pin 3's: both faces must still MERGE on a backed target, so the pin cannot be satisfied by refusing every conflictKeys upsert. Test-only: git diff origin/main --stat is the one test file, 117 insertions, 1 deletion (the header's 'three pins' heading); sql-driver.ts and remote-transport.ts are untouched. The existing 8 pins are unmodified.", "tests": "AFFECTED PACKAGE, on the final tree — pnpm --filter @objectstack/driver-turso test: 'Test Files 37 passed (37) / Tests 986 passed (986)'; pnpm --filter @objectstack/driver-turso typecheck: 'tsc --noEmit' exited clean with no diagnostics. Parity file alone before the reverse legs: 'Test Files 1 passed (1) / Tests 10 passed (10)' (8 pre-existing pins + 2 new). Build closure first, per the fresh-worktree rule: pnpm --filter '@objectstack/driver-turso^...' build. REVERSE VERIFICATION, direction predicted per leg BEFORE running, both legs run from the COMMITTED state and restored with git checkout HEAD -- path. LEG 1 (reword LOCAL face only, driver-sql refuseUnbackedConflictTarget, 'Cannot upsert into' -> 'Refusing to upsert into'): parity pin RED — \"AssertionError: expected 'Refusing to upsert into \\\"crm_contact_…' to be 'Cannot upsert into \\\"crm_contact_plain…' // Object.is equality\" at src/turso-local-remote-unique-parity.test.ts:333, totals 'Tests 1 failed | 9 passed (10)'; the 8 pre-existing pins and the new positive control stayed GREEN. In the same leg #8445's own suite also went red — 'x opens with the remote refusal's first sentence, word for word', 'Tests 1 failed | 5 passed (6)' — which is the literal a drifting author updates in the same commit. LEG 2 (restore, then reword REMOTE face only, driver-turso remote-transport.ts, same substitution): parity pin RED again — \"AssertionError: expected 'Cannot upsert into \\\"crm_contact_plain…' to be 'Refusing to upsert into \\\"crm_contact_…'\", totals 'Tests 1 failed | 9 passed (10)'; and the reading that matters — EVERY pre-existing pin stayed green, Pin 3's remote refusal included, because its assertions are token regexes (/crm_contact_plain/, /email/, /unique/i) that this reword preserves; #8445's suite stayed fully green at 'Tests 6 passed (6)', being blind to the remote face by construction. Two-way redness confirmed. MEASURED TRAP, now recorded in the file header: the local face arrives through the BUILT @objectstack/driver-sql (driver-turso resolves the workspace dep to its dist; no vitest alias to src), so leg 1 first ran GREEN ('Tests 10 passed (10)') against a stale dist and only went red after pnpm --filter @objectstack/driver-sql build — a false green that reads exactly like a pin that does not work. GATES — derived by node scripts/pm/dispatch-gates.mjs against the actual changed path. It named, path-matched: check:test-source-alias, check:type-source-resolution [both lint.yml]; convention-matched (adds/edits a test file): check:query-options-erasure, check:type-check-coverage; plus the standing any-edit gate check:nul-bytes. RAN, all green: check:nul-bytes 'OK (scanned 7735 text file(s) ... no raw ASCII control bytes)' (+ its 75-assertion self-test); check:test-source-alias 'OK — 72 packages with tests scanned'; check:type-source-resolution 'OK — 76 packages with a tsconfig.json scanned'; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new ... test surface: 240 site(s) in 47 file(s) — at the ceiling ... baseline key set verified against ec74646: no files added'. NOT RUN, stated rather than implied: check:type-check-coverage — its input for this change is the package's test-layer typecheck, which is clean above, so its ratchet cannot have moved; CI's required TypeScript Type Check job carries it. Control-byte self-scan beyond the gate on the changed file: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' returned no matches. CI at report time (not waited on, per the report-at-draft-PR-time rule): Check Changeset success (after the label), Check PR Size success, Console Pin Freshness success, ADR maintainer approval success, 'No other open PR may claim the same issue' success, 'Part-of PR must not also close its card' success; ESLint, TypeScript Type Check, Test Core and the Dogfood gates still in_progress.", "open_questions": [], "out_of_scope_findings": [] }Changeset decision — the
skip-changesetlabel, no changeset file. The diff is one test file; nothing user-visible ships, so a declaring changeset would announce a release note for a change no consumer can observe, andcheck-empty-changesetbans the stub form. Label applied by me right after the PR existed, written as the union with what the bots had already put there, then read back once they settled:["size/m","skip-changeset"].Check Changesetconcludedsuccessat 23:19:56, after the label write. Reviewers should not look for a missing.changeset/*.md.Sanitizer check (requested). No damage found on #8568: the issue body, triage comment
5287058071and the claim comment5287308858all read complete, each ending in its intact attribution footer, with no truncated prose and no<+letter spans that the body sanitizer would have eaten. The'sequences visible in the issue and PR bodies through the GitHub MCP read path are the documented read-path artifact, not stored damage. One caveat on method:ghis not installed in this container, so the cross-check was done through the MCP read path plus the PR body read back after creation (its fencedexpect(...)block survived byte-for-byte), not through a second independent client.Scope. Test-only, as dispatched — no production code touched, no fork encountered. The two deliberate rewords used for reverse verification were restored from the commit and verified:
git diff origin/main --statshows only the parity test file, andgit worktree removesucceeded unforced on a clean tree.Generated by Claude Code
test(driver-turso): pin the unbacked-conflict-target refusal across b…
fix(drivers): recognise the unbacked conflict target on Postgres too (o…