Skip to content

test(spec,drivers): add the VALUE_ROUNDTRIP conformance case-set — what you wrote is what you read back, per driver per dialect - #12585

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-12393-value-roundtrip-caseset
Aug 26, 2026
Merged

test(spec,drivers): add the VALUE_ROUNDTRIP conformance case-set — what you wrote is what you read back, per driver per dialect#12585
os-warren merged 3 commits into
mainfrom
claude/issue-12393-value-roundtrip-caseset

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes #12393

Route D of #12380, ruled in by the maintainer on 2026-08-26 alongside route A and
sequenced behind it. Route A landed as 4045b954d3, so the reason for the hold has
expired.

What this adds

VALUE_ROUNDTRIP_CASES — a tenth case-set in the driver-conformance census, asking every
driver on every dialect the one question none of the other nine asks: what you wrote is
what you read back
.

The census was green at 9 of 9 dialect-scored cells after #12136 promoted MATRIXED, and
all nine of its case-sets are about which rows come back — filters, temporal form,
pagination, aggregation. None is about what is in them. That green was therefore not
weak evidence about a round-trip defect; it was no evidence at all, and it would have
stayed green forever with the defect in place. Hence the drip of one-instance cards:
#12380, #11535, #11782, #10995.

Enrolled through the census's existing machinery rather than as a bespoke suite —
that reuse is the whole argument for this route. CLASSIFIED obliges the fixture to be
classified, CONSUMED obliges every driver to run it, and MATRIXED obliges
driver-sql's cell to be answered on every dialect it speaks rather than on SQLite
alone, which is precisely the coverage shape that let #12380 survive.

check-driver-conformance: OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt.
check-driver-conformance: dialect axis — 8 conformance suite(s) across 1 dialect-capable
  driver(s): 7 run the matrix, 1 declare named cell(s), 0 in the DIALECT ledger.
  10 of 10 dialect-scored cell(s) have a matrix-routed suite.

The size, re-measured rather than inherited

The card refused to size itself and asked for a measurement. Measured:

cases in the table 41, over json / multiple: true / string / number / boolean
new census cells 5 (one per driver), taking 45 → 50
backends actually executed 7 — driver-sql × 3 dialects, driver-turso × 2 transports, driver-memory, driver-sqlite-wasm, driver-mongodb (server-free)
driver-side round-trip divergences found 0
ledger rows needed 0LEDGER and DIALECT_LEDGER both stay empty
divergences found anywhere 1, and it was in a test double, not a driver (below)

Every value in the table is one some driver was measured to change (#12380's boundary
set, #11535's array, #11782's boolean) or a control that stayed faithful in the same
measurement. Nothing was invented to round out a table.

The three things the card said would decide acceptance

1. #5499 — the freeze is already fully lifted, so no freeze-scoping was needed

The brief said to read the ruling before scoping. Reading it changed the answer. #5499 is
still open as a historical anchor, but both halves were lifted by the maintainer on
2026-08-11, recorded on the card itself:

5499 解冻 mongodb 相关开发,继续全速推进

driver-memory 也解冻,继续清剩下两行 DEBT

The second comment states it outright: "this card's freeze is fully dissolved … it no
longer gates anything."
So driver-memory and driver-mongodb were enrolled on the same
terms as everyone else, and both answered the case-set green on arrival. Nothing was
quietly fixed in a frozen driver, and nothing needed to be
— no driver source changed at
all. The freeze did not have to become an excuse, because there is no freeze.

⚠️ Flagging the stale premise rather than silently acting on it: the card body, the
dispatch and #12380's landing comment all still describe the freeze as live.

2. A gate that ships red vs. one that ships honest — it ships green, and non-vacuously

Zero red cells, zero ledger rows, and no new escape hatch — so the #12136 sharp edge
(a DIALECT row deliberately does not clear MATRIXED) is not re-opened, because nothing
here asks a ledger for anything. That was reachable only because route D was sequenced
behind route A
; landing it first would have shipped red on SQLite by construction.

⛔ Green on arrival is worth nothing unless the column can go red, so it was ablated
see below.

One value class is scoped out by name rather than ledgered: a declared field written
as an explicit undefined, which the JS-backed drivers return as an own key holding
undefined. That is #9276 — open, on hold, tracked — not something this table discovered.
Including it would have shipped a case-set red on driver-memory by construction. A ledger
row would have been the wrong instrument: the case-set simply does not ask that question
yet, and the case-set file says so, by name, with the condition under which it should.

3. Clause ② — no, confirmed against the actual diff

 .changeset/value-roundtrip-conformance-case-set.md |  47 ++++
 .../src/memory-value-roundtrip-conformance.test.ts | 102 ++++++++
 .../mongodb-value-roundtrip-translation.test.ts    | 123 +++++++++
 .../sql-driver-value-roundtrip-conformance.test.ts | 130 +++++++++
 ...sqlite-wasm-value-roundtrip-conformance.test.ts | 105 ++++++++
 .../driver-turso/src/libsql-sqlite-stub.testkit.ts |  43 ++-
 .../src/turso-value-roundtrip-conformance.test.ts  | 148 +++++++++++
 packages/spec/export-origins/data.json             |   7 +
 packages/spec/src/data/index.ts                    |   7 +
 .../spec/src/data/value-roundtrip-conformance.ts   | 291 +++++++++++++++++++++
 scripts/check-driver-conformance.mjs               |  34 +++
 11 files changed, 1034 insertions(+), 3 deletions(-)

Six test files, one conformance fixture, one CASE_SETS row, one changeset, one
regenerated artifact, and one test-double fix. No Zod schema, no runtime, no driver
source, no API, no config.
The two packages/spec/src files are the fixture and its
export * from line — the package's data export gains seven names that only conformance
suites consume, and nothing an existing consumer resolves changes shape. No accept-set is
narrowed or widened anywhere in the diff.

Ablation — the column can go red, and only where it should

Direction and exact failure counts were written down before the mutation was applied;
the mutation was proven on disk by anchored grep -cF counts before any result was read;
restore ran under trap … EXIT INT TERM and was verified with an empty git diff.

Mutation: restore the pre-#12380 SQLite branch in SqlDriver.formatInput — on SQLite
only, store a non-object Field.json value as-is instead of JSON.stringify-ing it.

suite predicted measured
driver-sql value-roundtrip (sqlite cell) 16 failed 16 failed / 29 passed ✅ exact
the nine existing case-sets (driver-sql) 0 failed 252 passed, 0 failed the card's thesis
driver-sqlite-wasm value-roundtrip 16 failed 16 failed / 28 passed ✅ exact
driver-turso value-roundtrip 16 failed 31 failed / 57 passed ❌ mispredicted — see below
driver-memory value-roundtrip 0 failed 44 passed ✅ control fires
driver-mongodb value-roundtrip 0 failed 45 passed ✅ control fires
sql-driver-12380-json-roundtrip (instance pin) red 18 failed ✅ mutation landed

The new column goes red on three drivers while all nine existing columns stay green.
That is the argument the card was filed on, measured rather than asserted.

Mutation confirmed on disk before any result was read: fix marker
ONE encoding, every dialect (#12380) 1 → 0, mutant marker ABLATION-12393-MUTANT 0 → 2,
git diff --numstat = 4 2. Restore verified: markers back to 1 / 0, git status empty.

Two things the run corrected, reported rather than tidied away

(a) A false green that read exactly like "the case-set does not bite there." The first
pass mutated driver-sql's source only. driver-sqlite-wasm and driver-turso import
SqlDriver from @objectstack/driver-sql — the package exports, i.e. dist/ — so their
first readings (44 passed, 88 passed) were measured against the unmutated artifact
and are void. ablation-dist-preflight then refused the first rebuilt attempt too, because
the marker was only a comment and tsup strips comments from executable output — it
survived in the sourcemap alone, and the tool correctly called that not a reading. Only
with an executable marker did the preflight pass (✓ marker present in 2 built files) and
the real numbers appear. Both legs rebuilt; the restore leg rebuilt as well and proved the
marker absent from all 6 built files, so no later measurement runs against a poisoned
dist.

driver-sql's own suites were not exposed to this, and that is measured rather than
assumed: all eight resolve SqlDriver through relative source (./sql-driver.js,
./index.js, ../src/index.js) and zero driver-sql test files import
@objectstack/driver-sql — with a positive control, since the same grep found
from './sql-driver.js' in four of them.

(b) A mechanism claim of mine was wrong. I predicted turso's remote transport would
stay green because it "does not reach formatInput at all". It went red on 15 of 44. The
source says why: TursoDriver.toRemoteWriteForms is formatInput, deliberately, "so
the two transports cannot disagree about what a written value becomes."
I had reasoned
from RemoteTransport.serializeValue without reading its caller.

The two transports diverge by exactly one case under the mutant (s_0123), and the reason
is informative: local emits a column declared json (NUMERIC affinity destroys the bare
string '0123' before storage), while RemoteTransport.mapFieldTypeToSQL emits TEXT,
where no affinity conversion applies and JSON.parse('0123') throws so the keep-as-string
fallback holds. Both are green under the fixed code, so this is not a defect today — but a
declared-column-type asymmetry between two faces of one driver is the #11535 class, and it
is reported below rather than left in a scratch file.

The one non-test change, and its bounding scan

driver-turso's makeLibsqlSqliteStub did not model @libsql/client's client-side
boolean → 1/0 conversion, so a declared boolean written through the REMOTE transport
could not be bound at all — better-sqlite3 rejected it outright. Surfaced by this
case-set, since it is the first thing in the package to store a declared boolean through
that transport.

Which side was wrong was settled by reading the dependency, not the comment.
RemoteTransport.serializeValue says "booleans are kept as-is (libsql handles them)"
and @libsql/client's sqlite3 backend does exactly that, converting a bound boolean to
1/0 under the default intMode (lib-esm/sqlite3.js, the typeof value === "boolean"
arm). So the transport is correct and unchanged; the stub was the half that diverged.
The asymmetry that hid it: the LOCAL path never reaches the question, because
SqlDriver.formatInput carries its own bind safety net for these value classes.

Bounding scan before touching a testkit 24 files share: zero tests anywhere in the repo
pin the stub's boolean-bind throw (grep for the error text finds 10 hits, none in
driver-turso — all are prose in driver-sql / spec / service-analytics about
better-sqlite3's real behaviour). Since every such bind throws today, no existing green
test can depend on the old behaviour. Positive control: 20 turso test files reference the
stub, so the grep reaches them. driver-turso's full suite: 40 files / 1094 tests, all
passing.
Deliberately not modelled, and said so in the code: the client's Date and
ArrayBuffer conversions, because serializeValue converts a Date to ISO before the
client sees one, and an unreachable conversion would be a claim nothing checks.

Changeset grade — patch, defended

@objectstack/spec: patch. The published surface gains one conformance fixture whose
seven exports only conformance suites consume; no schema, no runtime, no behaviour, and
nothing an existing consumer resolves changes shape. Not minor, because a minor
signals a capability consumers can adopt and there is none. Not none, because the export
surface genuinely grows and packages/spec/export-origins/data.json records it. The
driver packages get no changeset: their diffs are test files plus one test-only testkit.

Verification

Gate union derived, not recalled, at the final head 6d96e0a73f:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack → 36 path-derived
families, re-derived after the last commit and byte-identical to the earlier derivation.
All 36 run, plus the 7 convention-triggered families the derivation names (test-file kind
and gate-script kind) and check:type-check-debt --re-measure. Every exit code captured
before any pipe
into a per-command results file — the lock's own VERDICT line reports
the batch's exit and is not a verdict on any one command.

All green. Verdict lines quoted from the gates themselves, not from $?:

  • check-driver-conformance: OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt. +
    10 of 10 dialect-scored cell(s) have a matrix-routed suite.
  • check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 245.3s, 1843 raw tsc error(s) total, none above its recorded number.
  • check-nul-bytes: OK (scanned 6932 text file(s) … no raw ASCII control bytes).
  • where-matcher conformance holds: 303 matcher(s) discovered, 303 answer the combinator battery correctly or refuse it loudly
  • OK: 20 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
  • check-engine-double-contract: 398 (file, verb) row(s) held by the RETAINED ledger

⚠️ One gate REFUSED before it was measured, reported as NOT MEASURED rather than as a
pass:
check-dev-prereqs.mjs exited 1 with "The workspace is not built — 1 unmet
precondition, not a list of problems"
(34 of 67 packages missing a dist/ entry point).
The workspace closure was then built (turbo run build, 70/70 successful) and it was
re-run: ✓ 67 package build artifacts present. check:type-check-debt has the same
precondition and was run only after that build.

Suites (all foreground, serialized through scripts/pm/os-verify-lock.sh):

package result
@objectstack/spec 432 files / 11464 passed
@objectstack/driver-sql 138 files / 2158 passed, 8 files + 129 tests skipped (the unprovisioned pg/mysql cells)
@objectstack/driver-turso 40 files / 1094 passed
@objectstack/driver-memory 28 files / 823 passed
@objectstack/driver-sqlite-wasm 26 files / 439 passed
@objectstack/driver-mongodb 18 files / 432 passed, 5 files + 143 skipped (the opt-in real-mongod suites, #5517)

Typecheck: all six touched packages, each script name echoed in the output so a
zero-match --filter cannot read as a pass.

⚠️ A real failure this caught, and how: the first @objectstack/spec run came back
1 failed | 11463 passed — the package's own compiler-free freshness guard, because
packages/spec/export-origins/data.json is generated and my seven new exports made it
stale. Regenerated with the command the failure text names; the diff is exactly those
seven lines and nothing else. Worth noting which instrument caught it: the derivation
lists check:export-origins among the 9 families "where the layout moved under a gate
that still spells the old path"
— so the path derivation could never have named it, and
only running the package's full suite did.

⚠️ Not measurable in this container, stated rather than implied: OS_TEST_POSTGRES_URL
and OS_TEST_MYSQL_URL are unset, so the pg and mysql cells skip rather than prove
green. CI's Temporal Conformance (live PG + MySQL) job runs the whole driver-sql suite
with both servers and OS_EXPECT_LIVE_DIALECT_MATRIX=1, which turns those skips into named
reds — and it picks this file up automatically, since it runs the package rather than a
path list. The asymmetry half of the ablation therefore rests here on (a) the mutation
being this.isSqlite-guarded by construction and (b) driver-memory / driver-mongodb
measuring green under it, which is a real cross-driver control this container can run.
The live PG/MySQL half was measured on #12380's own ablation.

Out-of-scope findings — handed up, not filed

⛔ Neither is filed as a card, and the reason is a channel fact rather than a judgement:
direct REST to the GitHub API returns 403 from this dev seat (the #12123 gap #12380
documented), and MCP issue search is off-limits for dedup reads under the dispatch
contract. Filing without a dedup search would be filing blind, so both are handed up with
the local-grep half of the evidence done. Same call the #12172 dev made.

  1. [drivers] a driver returning a declared field as an own key holding undefined is a storage-contract defect — Option C from #8489 #9276's restart condition fired nine days before it was written. That card sits
    pm:on-hold with Restart-when: … OR driver-memory / driver-mongodb come out of the #5499 investment freeze. The line was added 2026-08-20; the freeze was fully lifted
    2026-08-11. So it is held by a condition that was already satisfied at the moment of
    writing, and no mechanism will ever wake it — which is the exact failure that card's own
    note says the restart line was added to prevent.

  2. driver-turso declares a Field.json column as json locally and TEXT remotely.
    SqlDriver emits json (confirmed from the catalog by the [finding] driver-sql's SQLite json codec is not round-trip faithful for a string whose content is valid JSON — '123' reads back as a number, and Postgres does not agree #12380 suite);
    RemoteTransport.mapFieldTypeToSQL returns TEXT for both json and multiple: true.
    Benign today — both transports round-trip faithfully, which this PR's suite measures —
    but the two faces of one driver put the same declared field in different physical column
    types, and the ablation showed they consequently fail differently under the same
    defect. That is the driver-sql: single→multi-value field change never migrates the existing column type (varchar/text kept), arrays silently stored as stringified literals — data corruption on Postgres #11535 class. Local grep finds the two mapFieldTypeToSQL lines and
    one comment in remote-read-coercion.test.ts (// json stored as TEXT); nothing states
    the asymmetry as a known, intended fact.


Generated by Claude Code

os-warren and others added 2 commits August 26, 2026 11:05
)

The driver-conformance census was green at 9 of 9 dialect-scored cells and none
of its nine case-sets was about value storage — every one asks which rows come
back, none asks what is in them. VALUE_ROUNDTRIP closes that as a class: 41
cases over json / multiple / string / number / boolean, asserting type as well
as value, plus the injectivity pairs a per-value check cannot see.

Enrolled through the census's own machinery, so CLASSIFIED obliges the fixture
to be classified, CONSUMED obliges every driver to run it, and MATRIXED obliges
driver-sql's cell to be answered on every dialect it speaks. 50 covered cells,
10 of 10 dialect-scored cells matrix-routed, 0 DEBT, 0 exempt.

Also fixes a test-double fidelity gap the case-set surfaced: driver-turso's
libsql stub did not model @libsql/client's client-side boolean -> 1/0
conversion, so a declared boolean written through the remote transport could
not be bound at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o
#12393)

The generated export-origins artifact records every runtime export of each
entry point, and `./data` gained seven. Caught by the package's own
compiler-free freshness guard (scripts/export-origins.test.ts), regenerated
with the command that guard's failure text names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

60 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 3 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json, packages/spec/src/data/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json, packages/spec/src/data/index.ts) — pages documenting those are invisible to this run
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 129 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f93df4dbe314d5133f4a8c395b255ba0e2aeeaffpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 8b8a0a9ec0302255d7a1f413705098b985aab1bb — the merge of head c1d0ba950317028bfc94fd56b7ff911521eb46e0 into base f93df4dbe314d5133f4a8c395b255ba0e2aeeaff, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8b8a0a9ec0302255d7a1f413705098b985aab1bb && git checkout 8b8a0a9ec0302255d7a1f413705098b985aab1bb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f93df4dbe314d5133f4a8c395b255ba0e2aeeaff c1d0ba950317028bfc94fd56b7ff911521eb46e0 && git checkout -B drift-repro f93df4dbe314d5133f4a8c395b255ba0e2aeeaff && git merge --no-ff c1d0ba950317028bfc94fd56b7ff911521eb46e0

node scripts/docs-audit/affected-docs.mjs --json f93df4dbe314d5133f4a8c395b255ba0e2aeeaff

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 26, 2026
@os-warren
os-warren marked this pull request as ready for review August 26, 2026 12:26
@os-warren
os-warren enabled auto-merge August 26, 2026 12:27

Copy link
Copy Markdown
Collaborator Author

PM review — accepted, flipped ready, auto-merge armed. And one correction that is mine, not the PR's.

⛔ My dispatch brief carried a stale premise. The dev checked it; I had not.

I told this dispatch, in bold: "#5499 froze investment in driver-memory and driver-mongodb (maintainer ruling 2026-08-05, cross-card anchor). Read that ruling first and scope against it." I took that from the card body and did not open the anchor.

Verified now, on #5499's own comments:

ruling date
「5499 解冻 mongodb 相关开发,继续全速推进」 2026-08-11 04:34Z
「driver-memory 也解冻,继续清剩下两行 DEBT」 — and explicitly "this card's freeze is fully dissolved … it no longer gates anything" 2026-08-11 11:27Z

The freeze has been fully lifted for fifteen days. The dev read the anchor, found the rulings, enrolled both drivers on the same terms as everyone else, and flagged the stale premise instead of quietly working around it — which is why this cost nothing except the reading. Had they scoped the case-set to "drivers not under the freeze" as my brief invited, the column would have shipped with two backends silently excluded and a false reason recorded in the file.

This is the same discipline I have been enforcing on every dispatch this shift — re-verify the premise on current state, never inherit it — applied to a card's standing constraints rather than its citations, which is where I stopped looking. The card body, my brief, and #12380's landing comment all still describe the freeze as live; #5499 is the authority and it says otherwise.

The case-set

Size re-measured, not inherited: 41 cases, 5 new cells (45 → 50), 7 backends executed, 0 driver-side divergences. Zero ledger rows, both LEDGER and DIALECT_LEDGER stay empty, and #12136's narrowing (a DIALECT row deliberately does not clear MATRIXED) is not re-opened. The census now reads 10 of 10 dialect-scored cells matrix-routed.

Green on arrival proves nothing, and the PR knows it. The ablation is the card's thesis measured rather than asserted: restoring the pre-#12380 SQLite json branch turns the new column red on three drivers while all nine existing columns stay green at 252 passed. That green row is the whole argument for the card existing — the nine case-sets cover filters, temporal, pagination and aggregation, and a round-trip defect walks straight past every one of them.

Two self-corrections, both reported rather than tidied away

  • A false-green ablation leg, declared void. The first pass mutated source only, while driver-sqlite-wasm and driver-turso resolve SqlDriver through package exportsdist. Their "44 passed / 88 passed" measured the unmutated artifact and are reported as void, not as evidence. Then ablation-dist-preflight refused the rebuilt attempt too — because the marker was only a comment, and tsup strips comments from executable output (it survived in the sourcemap alone). Only an executable marker passed. That is a preflight tool doing exactly what it exists for, and a dev letting it.
  • A mechanism prediction of theirs that the run disproved. They predicted turso's remote transport would stay green because it "does not reach formatInput at all"; it went red on 15 of 44. The source says why: TursoDriver.toRemoteWriteForms is formatInput, deliberately, "so the two transports cannot disagree about what a written value becomes." They had reasoned from RemoteTransport.serializeValue without reading its caller, and they say so.

The one non-test change is not what it looks like

A driver-turso libsql test double did not model @libsql/client's client-side boolean → 1/0 conversion. Verified against the dependency's own source rather than against the transport's comment — which is the right direction, since a comment is the thing under suspicion. The transport is correct and unchanged; the double was lying.

Follow-ups I am taking, so they do not evaporate

  1. [drivers] a driver returning a declared field as an own key holding undefined is a storage-contract defect — Option C from #8489 #9276 un-held — its restart condition ("OR driver-memory / driver-mongodb come out of the [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 freeze") was written 2026-08-20, nine days after the freeze lifted. It was held by an event already in the past, so nothing would ever have woken it. Moved to pm:queue with the reasoning recorded there.
  2. The turso column-type asymmetryField.json is declared json on the local transport and TEXT on the remote one. Benign today (both round-trip faithfully, which this suite now measures), but the ablation showed they fail differently under one defect, which is the driver-sql: single→multi-value field change never migrates the existing column type (varchar/text kept), arrays silently stored as stringified literals — data corruption on Postgres #11535 class. Filing it, since the dev's REST channel is 403 ([finding] os-dev subagent seats have no direct GitHub REST channel — GITHUB_TOKEN is a placeholder, curl gets the session-gate 403, gh is absent — while dispatch protocol text assumes REST list endpoints are reachable #12123) and filing blind was correctly refused.
  3. check:export-origins sits in the derivation's "families where the layout moved under a gate that still spells the old path" bucket — and it is the gate that would have named the stale generated artifact this PR had to regenerate. The path derivation could not have named it; only running the package's full suite caught it. Recording for the tooling lane.

⚠️ And one line from the report that should not be skimmed past, because it is the #12288 / #12365 family live again: "a backgrounded wrapper reported exit code 0 for a run whose verify-lock VERDICT line said command-exit 1 — the lock's line was correct and the suite had failed." Per-command exits were read throughout. A wrapper's zero is not a verdict.

CI is the remaining gate — including Temporal Conformance (live PG + MySQL), which picks this file up automatically because it runs the package rather than a path list, and which turns the two locally-skipped pg/mysql cells into named reds if they fail.


Generated by Claude Code

…RIP exports

CI's `Type Check · consumer gates` was red on `check:api-surface`, which named
the seven additions and the exact remedy. Regenerated with the repo's tooling;
the diff is those seven lines and nothing else, and `check:api-surface` now
reports the surface unchanged.

The generator refuses without a built package and says so rather than emitting
a wrong snapshot — the build was run first, and `OS_SKIP_DTS=1` was NOT used,
since that flag skips exactly the declarations this reads (AGENTS.md 9).

Why it reached CI: `check:api-surface` is one of the nine families in #12514
whose declared path population matches nothing in the tree, so the local
dispatch-gates derivation could not name it for this change set. The sibling
`export-origins` snapshot WAS regenerated, because that one was caught by the
package's own suite.

Copy link
Copy Markdown
Collaborator Author

CI was red on check:api-surface — fixed, head now c1d0ba9503

Type Check · consumer gates failed, and the gate named both the cause and the remedy:

  ./data
    + VALUE_ROUNDTRIP_CASES (const)          + ValueRoundTripCase (interface)
    + VALUE_ROUNDTRIP_COLLISION_PAIRS (const) + ValueRoundTripColumn (type)
    + VALUE_ROUNDTRIP_FIELDS (const)          + valueRoundTripDivergence (function)
    + VALUE_ROUNDTRIP_ROWS (const)

@objectstack/spec public API changed: 0 breaking (removed/narrowed), 7 added.
If intentional, run `pnpm --filter @objectstack/spec gen:api-surface` and commit the updated snapshots.

The export-origins snapshot was regenerated in this PR; the api-surface one was not. Regenerated with the repo's own tooling — the diff is one file, those seven lines, nothing else — and check:api-surface now reports "public API surface + factory signatures unchanged ✓".

⚠️ The generator refused on the first attempt rather than emitting a wrong snapshot: "Build first, then re-run … (Do NOT use OS_SKIP_DTS=1 for this one — AGENTS.md §9 names it as the flag that emits JS and skips exactly the declarations this reads.)" Built the package first; the flag was not used. That refusal measured nothing and is recorded as such, not as a red.

Everything else on the previous head was green, including Temporal Conformance (live PG + MySQL) — the job the report correctly declared as CI-owed for the unprovisioned pg/mysql cells.

Why this reached CI instead of the local run — it is #12514, exactly

check:api-surface is one of the nine families in #12514: gates whose declared path population matches nothing in the tree, so dispatch-gates cannot name them for any change set. The report handed that observation up as a standing repo fact — and it was the family that then caught this PR.

That is the second concrete cost today. The first was this same PR's export-origins staleness, which only the package's full suite caught, and which the report already noted the path derivation "could never have named". So on one change set, two generated-snapshot gates were unreachable from the derived union: one was rescued by a full-suite run, the other by CI.

Cross-posting to #12514 — it now has a measured consequence rather than only a mechanism.

No other change: the seven exports, the case-set and every pin are untouched. Auto-merge stays armed.


Generated by Claude Code

@os-warren
os-warren added this pull request to the merge queue Aug 26, 2026
Merged via the queue into main with commit 6bd3231 Aug 26, 2026
34 checks passed
@os-warren
os-warren deleted the claude/issue-12393-value-roundtrip-caseset branch August 26, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/xl tests tooling

Projects

None yet

2 participants