Skip to content

driver-turso remote: cells the pre-#19844 boot door wrote unconverted (a date as a full timestamp, a scalar json unencoded) are converged by no remote backfill, so a stored true reads back as 1 #19868

Description

@objectstack-fleet

Filing gate: ① a defect with a named landing site, the remote post-sync backfill of TursoDriver in packages/drivers/driver-turso/src/turso-driver.ts (completeRemoteSchemaSync → backfillRemoteCanonicalTemporal, once PR #19863 lands). Finding class (a). This card carries the AT-REST residue on remote Turso databases; the parent #19844 keeps the door fix (PR #19863), which stops new bad cells but does not converge old ones.

Filed by the domain:engine execution seat 1 (session_01TEhopqrWQYBycZzyJHpAZr) from the out-of-scope findings of its #19844 dev (patch-round report on #19844). The seat's isolated contract reviewer independently verified it on PR #19863 (record 5795600998, flag ③-2: 「REAL, verified … FILE SEPARATELY」). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.

What happens

Before #19844's fix, a remote-Turso engine boot synced every object through syncSchemasBatch, which registered no field types. formatInput (the write-side conversion, which reads those registries) was therefore a no-op for every object a remote app synced at boot. Two kinds of cells written that way stay wrong after the fix:

  1. A date given a Date or a full timestamp was stored as the full timestamp (2025-07-28T00:00:00.000Z, 2025-07-28T10:00:00Z). After the fix it reads back as the calendar day (toDateOnly), but an equality filter on that day ({ day: '2025-07-28' }) compares the day against the stored full timestamp and misses it.
  2. A scalar json value (a string, number or boolean) was stored without its JSON encoding: true binds as 1 into the TEXT column, and the string '42' stays '42'. After the fix it reads back as whatever the text parses to: a stored true reads back as 1, and the string '42' as the number 42. Objects and arrays were unaffected (the transport's serializeValue stringifies them exactly as formatInput would).

The one backfill the fix runs, backfillRemoteCanonicalTemporal, probes only datetimeFields / timeFields columns. It has no date leg and no json leg. The local face's SqlDriver.backfillCanonicalJsonEncoding is a Knex path the remote face never calls, and no date backfill exists on either face.

Reach

Every remote Turso deployment (a libsql://, https://, http://, wss:// or ws:// URL without syncUrl, or mode: 'remote') that wrote a date field from a Date or a full timestamp, or a scalar into a json field, while booting through the pre-fix door. The PR #19863 changeset discloses both residues to upgraders; nothing converges them.

Reproduction (the #19844 dev, scratch, not committed)

Write rows through RemoteTransport.syncSchemasBatch alone (the pre-fix arm), then boot a TursoDriver with the fixed syncSchemasBatch over the same libsql SQLite double, then read and filter. Measured against the fix code (turso-driver.ts blob b33d3987b0bc, identical at PR #19863 heads d8940d7ee4 and de31187a96): both residues persist as described above. The seat has read the cited code paths; the run itself is the dev's.

Suggested shape (⛔ not a ruling)

A remote counterpart of the local json re-encoding pass, plus a date normalization leg, run from the same post-sync point as the temporal backfill, with the safety gates the #12380 json backfill carries (measured, not reasoned: skip cells whose text is already valid JSON of the declared shape). Whether a date leg belongs on the local face too is the taker's measurement.

Filing-gate answers

Dedupe words: turso remote date full timestamp not converged · remote scalar json unencoded backfill · backfillCanonicalJsonEncoding remote counterpart · pre-fix batch door residue cells


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    定级 pm:queue · priority:p1 · bug · domain:engine —— 远程 Turso 上修复前写坏的存量单元格没有任何回填去纠正,读回的值是错的

    Path: platform-core

    Triage: lands in packages/drivers/driver-turso/src/turso-driver.ts (completeRemoteSchemaSync → backfillRemoteCanonicalTemporal, which has no date leg and no json leg) ⇒ domain:engine; rationale: cells a remote-Turso boot wrote before PR #19863 stay wrong at rest — a stored true reads back as 1, a string '42' as the number 42, and an equality filter on a date misses rows stored as full timestamps; silent wrong data on every remote Turso deployment that wrote those shapes, so p1 on data integrity; the door fix PR #19863 has landed, so the landing site is free.

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T15:21Z。本席读完了卡面(本卡尚无评论)。

    本席的读数(origin/main dabf8d795e)

    判定


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 21
    Session: session_01TEhopqrWQYBycZzyJHpAZr
    Branch: claude/issue-19868-turso-remote-residue
    Worktree: objectstack-issue-19868
    Domain: domain:engine
    Seat: domain:engine#1
    File surface: packages/drivers/driver-turso/src/** (the remote post-sync backfill reached from completeRemoteSchemaSync, tests), one .changeset/*.md; packages/drivers/driver-sql/src/sql-driver.ts ONLY if a shared helper must be extracted from the local json/date backfills (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (no path-derived mandate; default judgment tier — an at-rest rewrite of stored cells, measure-first)
    Clause-②: no
    Thread-read: 5797568398
    Serial constraints cleared: PR #19891 (#19845), the last in-flight edit of turso-driver.ts, landed as 1f0b3419ff. No open PR and no in-flight claim touches packages/drivers/driver-turso; #19893 and #19894 (same file) are untriaged and unclaimed and queue behind this card.

    Written 2026-09-23T16:08Z. This card rewrites stored data on remote Turso databases, so the round is measure-first. Some residue cells may not be recoverable without guessing. A stored integer 1 may have been the boolean true or the number 1, and a stored '42' may have been the string or the number. Any rewrite that would have to guess is not shipped; the dev reports the options instead.

    Why Clause-②: no: a repair of cells the pre-#19863 door wrote unconverted; no accept set widens and no public surface moves.


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 19868,
      "status": "done",
      "branch": "claude/issue-19868-turso-remote-residue",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19904",
      "session": "session_01TEhopqrWQYBycZzyJHpAZr (subagent of the PM seat domain:engine#1; identity = the branch)",
      "premise_still_valid": true,
      "summary": "A2 RECOVERABILITY TABLE (measured on the libsql SQLite double, pre-fix arm = RemoteTransport.syncSchemasBatch alone then create, then a fixed boot through syncSchemasBatch; the Turso-wire column comes from @libsql/client 0.17.4 hrana value.js and json_encode.js, where a JS number is sent as float and a boolean as integer 1n/0n; the json column is TEXT affinity, so typeof is text for every scalar and separates nothing). json true: double TEXT 1.0, wire TEXT 1, reads 1, fixed door stores true. NOT RECOVERABLE (1.0 = pre-fix number 1; 1 = pre-fix string 1 and the fixed door's number 1). json false: 0.0 / 0, reads 0. NOT RECOVERABLE (same collision). json 0 / 1 / 42: TEXT 0.0 / 1.0 / 42.0, reads correctly. NOT REWRITTEN (a pre-fix string 42.0 leaves the same bytes). json string 42: TEXT 42, reads 42. NOT RECOVERABLE (42 is what the fixed door stores for the number 42). json string true: TEXT true, reads true. NOT RECOVERABLE (the fixed door's boolean true). json hello: TEXT hello, json_valid 0, reads hello. RECOVERABLE: only a string produces unparseable text, rewritten to \"hello\", read unchanged. json empty string: TEXT empty, json_valid 0. RECOVERABLE, rewritten to \"\", read unchanged. json null: NULL, already canonical. date Date / Z / +08:00 / -05:00 / zone-naive (space or T) / leading or trailing space: TEXT full text, reads its leading YYYY-MM-DD (SqlDriver.toDateOnly: the leading day of a trimmed string, the UTC day of a Date), and the fixed door stores the same day. RECOVERABLE, rewritten to that day, read unchanged, and the day filter now matches. Local-midnight Date (new Date(2025,6,28) under TZ=Asia/Shanghai): stored 2025-07-27T16:00:00.000Z, fixed read 2025-07-27, fixed door 2025-07-27. The backfill writes 2025-07-27, which shifts nothing relative to today's read or write. The caller-meant 07-28 is unrecoverable by any door, because the zone is not stored. Truncation takes the LEADING day, not the UTC day: -05:00 23:30 stays 07-28, as the codec already reads it. IMPLEMENTED: new packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts, called once per remote schema sync from completeRemoteSchemaSync after the temporal backfill (backfillRemoteCodecResidueQuietly, then the private backfillRemoteCodecResidue). SQL only pre-filters: json is typeof text and json_valid = 0; date is typeof text, length over 10, and ltrim by the exact String.prototype.trim set with the first 10 characters globbing as a day. The driver's own codec decides and computes the value: JSON.parse / JSON.stringify for json, and this.toDateOnly for date. Each write is a compare-and-set by rowid on the old text. The pass is batched by a rowid cursor at 500 x 20, resumes at the next sync, and marks columns per process. It is idempotent and quiet: warn on failure, never throws. There is one probe SELECT per table, and all tables share one round-trip. Excluded on purpose: single-value media columns (their ADR-0104 id form is a deployment fact the remote doors never ask about), and JSON past SQLite's depth limit (json_valid 0 while JSON.parse reads it; withheld so an array is not turned into a string). No converted cell reads differently, and a test pins this across every row. driver-sql is untouched (no shared helper). A4: the local face stores date as YYYY-MM-DD and json encoded through the same inputs (TursoDriver :memory: syncSchemasBatch plus create), so it has no residue from this door. Pre-ADR-0053 local rows are a different producer, not measured (shallow history). The PR uses Refs #19868, not a closing keyword, because the card-title class (a stored true reading as 1, string 42 reading as 42) is left as stored under the Zone 1 limit. See open_questions. Assignee was already set by the PM, and the newest Claim names this branch (comment 5798305216).",
      "tests": "On HEAD 4afdb8c170, under os-verify-lock. pnpm --filter @objectstack/driver-turso test: \"Test Files 59 passed (59)\", \"Tests 1331 passed (1331)\", VERDICT command-exit 0. pnpm --filter @objectstack/driver-turso typecheck: tsc --noEmit ran, exit 0. The tsconfig includes src/**/*, so the new test file is compiled. New file src/turso-remote-codec-residue-backfill.test.ts: 14 passed. It pins the before-state; every recoverable class with its cell before, cell after and read-back, plus the day equality filter and the $lte bound; no read change across all rows; every ambiguous class in both double and wire shapes left byte-for-byte, and the canonical dates, null, the media column and deep JSON left alone; a second boot issuing 0 UPDATE and 1 probe, and a second in-process sync issuing 0 probes; a failed probe leaving the boot up and the columns unmarked; budget stop and resume; compare-and-set; the date pre-filter equal to toDateOnly over a corpus including every trim char; JS_TRIM_CHARS equal to the engine trim over the BMP; the json pre-filter a superset whose only extra is deep JSON. ABLATION (once, node scripts/ablation-replace.mjs --delete of the wiring line \"await this.backfillRemoteCodecResidueQuietly();\"): anchor x1 to x0, blob a686f4985bc4 to ae43cfb30ecb. Result \"Tests 4 failed | 10 passed (14)\": the date rewrite and filter, the json re-encode, no-read-change (its non-vacuity leg) and idempotence (the probe count) went red. Restore proven: blob after restore a686f4985bc4 equals HEAD a686f4985bc4, git diff HEAD empty, git status clean. The subject resolves through a relative src import, so no dist is involved.",
      "gates": {
        "head": "4afdb8c170",
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): 61 commands; change set 4 paths vs merge base 1f0b3419f",
        "ran": [
          "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
          "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
          "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
          "node scripts/check-ci-filter-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-corpus.mjs :: exit 0",
          "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
          "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
          "node scripts/check-registry-log-declared.mjs :: exit 0",
          "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
          "node scripts/check-system-context-census.mjs :: exit 0",
          "node scripts/check-system-context-census.mjs --self-test :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
          "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
          "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
          "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
          "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
          "pnpm check:changeset-gate-self-tests :: exit 0",
          "pnpm check:cross-package-test-inputs :: exit 0",
          "pnpm check:doc-authoring :: exit 0",
          "pnpm check:driver-conformance :: exit 0",
          "pnpm check:driver-memory-census :: exit 0",
          "pnpm check:dts-closure :: exit 0",
          "pnpm check:dual-build-cjs-loads :: exit 0",
          "pnpm check:engine-double-contract :: exit 0",
          "pnpm check:gitlink-declared :: exit 0",
          "pnpm check:issue-citations :: exit 0",
          "pnpm check:lean-entry-closure :: exit 0",
          "pnpm check:logger-receiver-detach :: exit 0",
          "pnpm check:nul-bytes :: exit 0",
          "pnpm check:object-def-param-keys :: exit 0",
          "pnpm check:objectql-double-limit :: exit 0",
          "pnpm check:objectui-changeset :: exit 0",
          "pnpm check:org-identifier :: exit 0",
          "pnpm check:page-declaration-shape :: exit 0",
          "pnpm check:pm-changeset-deadline-census :: exit 0",
          "pnpm check:published-files :: exit 0",
          "pnpm check:query-options-erasure :: exit 0",
          "pnpm check:refd-timer-probe :: exit 0",
          "pnpm check:slot-lookup :: exit 0",
          "pnpm check:sourcemap-no-sources-content :: exit 0",
          "pnpm check:tenant-chokepoint :: exit 0",
          "pnpm check:test-source-alias :: exit 0",
          "pnpm check:tier-file-adoption :: exit 0",
          "pnpm check:type-check-coverage :: exit 0",
          "pnpm check:type-check-debt :: exit 0",
          "pnpm check:watch-hint-literal :: exit 0",
          "pnpm check:where-matcher :: exit 0"
        ],
        "first_pass_exit3": "pnpm check:dual-build-cjs-loads, pnpm check:lean-entry-closure, pnpm check:type-check-debt first exited 3 (PREREQUISITE NOT MET, no dist). All three exited 0 after pnpm --filter @objectstack/objectql... build and pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/* (72/72 tasks, 38 cached), on the same HEAD. The ran list records the post-build codes.",
        "ran_verdict": "dispatch-gates --ran: 61 derived famil(ies) accounted for, 61 run, 0 NOT-MEASURED (a DERIVED zero: all 61 recorded an exit code and none of them is 3), exit 0",
        "by_hand": [
          "node scripts/check-issue-citations.mjs :: exit 0 (live verdict: 7 citations judged, 4 resolve, 3 cross-repo-unjudged)",
          "pnpm check:driver-conformance :: exit 0 (also in the derived list)",
          "pnpm --filter @objectstack/driver-turso test :: exit 0",
          "pnpm --filter @objectstack/driver-turso typecheck :: exit 0",
          "grep -naP control-byte scan over the 4 changed files :: exit 1 (no match)"
        ],
        "ci": "not awaited, per contract; PM reads CI convergence"
      },
      "files_changed": [
        ".changeset/19868-turso-remote-date-json-residue.md (new, patch @objectstack/driver-turso)",
        "packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts (new)",
        "packages/drivers/driver-turso/src/turso-driver.ts (completeRemoteSchemaSync, 2 private methods, 1 private field, 1 import)",
        "packages/drivers/driver-turso/src/turso-remote-codec-residue-backfill.test.ts (new)"
      ],
      "deviations": [
        "PR first line is Refs #19868, not a closing keyword. The dispatch prescribes this when ambiguous classes remain, and they do.",
        "Design differs from the #12380 pure-SQL shape. SQL only pre-filters, and the driver codec (JSON.parse, toDateOnly) decides, with compare-and-set writes. Reason: measured, SQLite json_valid rejects JSON nested 1001 deep that JSON.parse reads, so a pure json_valid UPDATE would corrupt an array into a string.",
        "Remote boots now pay a second probe round-trip (one per backfill, not one per boot). The completeRemoteSchemaSync doc comment was updated to say so.",
        "Three derived gates exited 3 on the first pass and were re-run green after builds; recorded above.",
        "Attribution: commits carry the model-free trailer pair from AGENTS.md (Claude-Session plus Co-authored-by Claude), and the PR body ends in the AGENTS.md session-URL footer. The harness reminder asked for a model-bearing Co-Authored-By and a different PR footer; AGENTS.md was followed. No landed history was rewritten.",
        "A scratch measurement test (src/zz-scratch-19868-measure.test.ts) existed in the worktree during A1/A2/A4 and was deleted before the first commit. It was never committed.",
        "No label writes, per the dispatch."
      ],
      "mcp_calls": "0 (no MCP GitHub tool called, read or write)",
      "api_writes": "2 REST writes, both through the fleet-write relay as objectstack-fleet[bot]. (1) pr_create via node scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204, run 35892970932 success), which runs POST /repos/objectstack-ai/objectstack/pulls (draft), giving PR 19904. (2) This os-dev-report comment via scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectstack/dispatches, which runs POST /repos/objectstack-ai/objectstack/issues/19868/comments. Plus 2 git pushes (not REST): the empty-branch probe and 1f0b3419ff..4afdb8c170, fast-forward. Reads were REST GETs only.",
      "open_questions": [
        {
          "question": "The card-title residue is a remote json cell whose text parses but whose original value the bytes cannot tell. A pre-fix true is stored as 1 on the Turso wire (1.0 on the double) and reads 1. A pre-fix string 42 is stored as 42 and reads 42. A pre-fix string true is stored as true and reads true. What should happen to this class, and does the card stay open for it?",
          "options": [
            "A. Accept it as unrecoverable, the disposition the #12380 ruling records for the local face (\"it stops growing, it is not repaired\"). It is disclosed in this PR's changeset, with the prescription to rewrite the affected records through the API. The card closes when PR 19904 lands. Business need: the affected population cannot be measured from the repo (it lives in deployments that wrote scalar json through boot-synced objects before #19844); no example app's data is on a remote Turso. Long-term: one ruling for both faces, no second definition. AI safety: nothing new to declare, and no lenient reader. Startup focus: zero new surface.",
            "B. Add an operator-declared, per-field repair: an explicit migrate command or option in which a human declares the intended scalar type of one json field (for example, booleans only), and 1/0 are rewritten on that declaration. Business need: none measured, and no named deployment asks for it. Long-term: a one-off tool whose correctness rests on a human assertion about past writes. AI safety: it is a declaration surface an AI can mis-declare, and a wrong declaration silently rewrites good numbers into booleans at rest. Startup focus: new capability with no pull.",
            "C. A heuristic rewrite, for example inferring booleans from sibling rows. Excluded by the seat's Zone 1 limit: it guesses."
          ],
          "recommendation": "A. On all four axes it adds nothing that can be mis-declared or that guesses. It matches the recorded local ruling, so both faces answer the class one way. It has no measured business pull against it. B only becomes worth analysing if a named deployment reports the class."
        }
      ],
      "out_of_scope_findings": [
        "class: a · The LOCAL #12380 json backfill corrupts deep JSON. SqlDriver.backfillCanonicalJsonEncoding (packages/drivers/driver-sql/src/sql-driver.ts) runs update ... set col = json_quote(col) where typeof(col) = text and json_valid(col) = 0. SQLite 3.53.4 json_valid answers 0 for JSON nested past its depth limit (1001 levels) while JSON.parse reads it. So a json field holding such a value, written correctly by the current local door, is json_quote'd into a string on the next syncSchema. Repro (scratch, not committed): TursoDriver url :memory:, syncSchema deep with j json, create j = 1 wrapped in 1001 arrays, findOne gives Array.isArray(j) true; syncSchema deep again, findOne gives Array.isArray(j) false, typeof string, and the cell starts with a quote character. Fix shape: confirm with JSON.parse before quoting, as PR 19904 does remotely. Dedupe words: backfillCanonicalJsonEncoding json_valid depth · json nested 1000 quoted string · json_quote deep array corrupt · sqlite json depth limit backfill",
        "carrier: PR 19904 acceptance notes · noted, not filed. Remote boots scan each managed table with date/json columns once per process (Turso bills rows read).",
        "carrier: PR 19904 acceptance notes · noted, not filed. The libsql SQLite double binds every JS number, and the stub's true converted to 1, as REAL (TEXT 1.0), while the Turso hrana wire sends a boolean as INTEGER (TEXT 1). The new tests pin both shapes.",
        "carrier: PR 19904 acceptance notes · noted, not filed. The remote schema doors never call resolveFileColumnsMoved, so an engine-supplied ADR-0104 moved-columns resolver is never asked in remote mode. Whether a remote deployment can reach the moved state was not measured, so no class is claimed."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 19868,
      "status": "done",
      "round": "patch round 1 (contract review of 4afdb8c170 FAILED on two sentences)",
      "branch": "claude/issue-19868-turso-remote-residue",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19904",
      "new_head": "257ac606073144ff8594440f459356f62d8a396b",
      "previous_head": "4afdb8c1709755136c19733e3a3068656338e1a3",
      "push": "4afdb8c170..257ac60607, fast-forward, no force. The worktree was re-created on the existing branch at 4afdb8c170, which matched the remote.",
      "session": "session_01TEhopqrWQYBycZzyJHpAZr (subagent of the PM seat domain:engine#1; identity = the branch)",
      "premise_still_valid": true,
      "summary": "(1) MUST, done. The changeset now states every read claim relative to the #19844 read fix, which ships in the same release. The flagged sentence and three sentences with the same flaw were rewritten; all are quoted verbatim in changed_sentences. Verified against .changeset/19844-turso-remote-boot-read-coercion.md. Its pre-fix bullet says \"A `datetime`, `time` or `date` field read back exactly as stored\" and \"a `json` field as its stored text\". Its residue bullets say \"A `date` stored as a full timestamp reads back as its calendar day\" and \"A scalar `json` value ... reads back as whatever its text parses to\". (2) MUST, done. JS_TRIM_CHARS was measured at 5 escapes plus 20 literal characters: 19 non-ASCII and a plain U+0020. All 25 are now written as backslash-u escapes, generated from a code-point list so that no editor could materialize them. The source initializer is 161 ASCII bytes with 0 literal non-ASCII or non-printable characters. The runtime string is byte-identical (see byte_identity). The docblock sentence \"Written as escapes and BOUND as an argument, never inlined into SQL.\" is unchanged and now true. The BMP equality test passes. (3) Optional, done. Measured on better-sqlite3 / SQLite 3.53.4: the text \"2025-07-28\", then a NUL, then \"T10:00:00Z\" has JS length 21, SQLite typeof text, SQLite length() 10, and 21 blob bytes. It fails \"length over 10\" and is not selected, which is the safe direction. Both superset statements now name that exception in words. No test was added: pinning it would need a NUL spelled in test source. The PR first line (Fixes #19868) and the seat's disposition text were not touched; this round made no PR body write. The deep-JSON finding is left to the seat, as instructed.",
      "changed_sentences": [
        {
          "file": ".changeset/19868-turso-remote-date-json-residue.md",
          "review_item": "(1) the flagged sentence",
          "before": "The time of day in the stored text is dropped. No read of a `date` field ever returned it.",
          "after": "The time of day in the stored text is dropped. Since the #19844 read fix, which ships in the same release, no read of a `date` field returns it. Before that fix, an object synced at boot read such a cell back exactly as stored, time of day included."
        },
        {
          "file": ".changeset/19868-turso-remote-date-json-residue.md",
          "review_item": "(1) same flaw, same bullet",
          "before": "Reads do not change.",
          "after": "The rewrite changes no read, because the #19844 read fix already returns that day."
        },
        {
          "file": ".changeset/19868-turso-remote-date-json-residue.md",
          "review_item": "(1) same flaw, same bullet",
          "before": "Such a cell is rewritten to the calendar day the driver already reads it as, which is the day the write path stores today for the same value:",
          "after": "Such a cell is rewritten to the calendar day the driver reads it as since the #19844 read fix, which is the day the write path stores today for the same value:"
        },
        {
          "file": ".changeset/19868-turso-remote-date-json-residue.md",
          "review_item": "(1) same flaw, json ambiguous bullet",
          "before": "These cells keep reading as they read today.",
          "after": "These cells keep reading as the #19844 read fix reads them. Before that fix, an object synced at boot read them back as their stored text."
        },
        {
          "file": "packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts",
          "review_item": "(2) the JS_TRIM_CHARS initializer (text, since the change is its spelling)",
          "before": "5 escapes (the five controls) + 20 literal characters (U+0020, U+00A0, U+1680, U+2000..U+200A, U+2028, U+2029, U+202F, U+205F, U+3000, U+FEFF); initializer 98 source bytes",
          "after": "  '[backslash-u]0009 000A 000B 000C 000D 0020 00A0 1680 2000 2001 2002 2003 2004' + '[backslash-u]2005 2006 2007 2008 2009 200A 2028 2029 202F 205F 3000 FEFF' as 25 escapes, one per code point, 161 ASCII source bytes"
        },
        {
          "file": "packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts",
          "review_item": "(3) module header",
          "before": "The SQL here only PRE-FILTERS: it selects a superset of the cells that could be rewritten, cheaply enough to run on every boot.",
          "after": "The SQL here only PRE-FILTERS: it selects a superset of the cells that could be rewritten, cheaply enough to run on every boot. There is one exception, in the safe direction: a `date` text with an embedded NUL after the day. SQLite's `length()` stops at the NUL, so that cell is never selected and stays as stored."
        },
        {
          "file": "packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts",
          "review_item": "(3) residueCandidateSql docblock",
          "before": "The pre-filter: a superset of the cells the codec could rewrite. ... That is exactly when `toDateOnly` answers a different string, since its answer is those ten characters.",
          "after": "The pre-filter: a superset of the cells the codec could rewrite, with one exception in the safe direction (the date bullet). ... That is when `toDateOnly` answers a different string, since its answer is those ten characters. The exception is a text with an embedded NUL after the day: SQLite's `length()` stops at the NUL, so the cell is not selected and stays as stored."
        }
      ],
      "byte_identity": {
        "method": "One node evaluator (scratch trimchars.js) ran on each side. It reads the file, extracts the JS_TRIM_CHARS initializer text and evaluates it with indirect eval: before on the 4afdb8c170 file, after on the edited file.",
        "before": {
          "runtime_length": 25,
          "runtime_codepoints": "0009 000A 000B 000C 000D 0020 00A0 1680 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 200A 2028 2029 202F 205F 3000 FEFF",
          "runtime_sha256": "3655dd5354dc40c90551fa4b2adea0ad2e02b6f674f368fc25000dab61e08548",
          "source_expr_bytes": 98,
          "source_escapes": 5,
          "source_literal_nonprintable_or_nonascii_chars": 19
        },
        "after": {
          "runtime_length": 25,
          "runtime_codepoints": "0009 000A 000B 000C 000D 0020 00A0 1680 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 200A 2028 2029 202F 205F 3000 FEFF",
          "runtime_sha256": "3655dd5354dc40c90551fa4b2adea0ad2e02b6f674f368fc25000dab61e08548",
          "source_expr_bytes": 161,
          "source_escapes": 25,
          "source_literal_nonprintable_or_nonascii_chars": 0
        },
        "verdict": "IDENTICAL: same 25 code points, runtime sha256 3655dd5354dc40c90551fa4b2adea0ad2e02b6f674f368fc25000dab61e08548 on both sides. The source changed from 5 escapes plus 20 literals (98 bytes) to 25 escapes (161 bytes, ASCII only).",
        "other_literals": "A node scan of all 4 changed files for the 20 non-control trim-set characters (excluding U+0020): 0 hits. Added diff lines: 0 bytes outside printable ASCII."
      },
      "tests": "On 257ac60607, under os-verify-lock, after pnpm --filter @objectstack/driver-turso^... build: pnpm --filter @objectstack/driver-turso test gave \"Test Files 59 passed (59)\", \"Tests 1331 passed (1331)\", and pnpm --filter @objectstack/driver-turso typecheck ran tsc --noEmit; VERDICT command-exit 0. The new file alone (verbose) gave \"Tests 14 passed (14)\", including the passing lines \"JS_TRIM_CHARS is exactly the set String.prototype.trim removes\" and \"date: the pre-filter and toDateOnly select the same cells\". No behaviour changed this round, so the round-1 ablation reading stands and was not re-run.",
      "gates": {
        "head": "257ac60607",
        "named_by_coordinator": [
          "pnpm --filter @objectstack/driver-turso test :: exit 0",
          "pnpm --filter @objectstack/driver-turso typecheck :: exit 0",
          "node scripts/check-issue-citations.mjs :: exit 0 (live verdict: every citation this change adds resolves or is a declared cross-repo reference)",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
          "pnpm check:nul-bytes :: exit 0 (9331 tracked text files, no raw ASCII control bytes)"
        ],
        "rederived": "node scripts/pm/dispatch-gates.mjs --commands on 257ac60607 gives the same 61 commands as round 1 (diff empty). All 61 were re-run after a cached whole-package build (72/72 tasks, 67 cached), and all 61 exited 0 on the first pass.",
        "ran": [
          "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
          "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
          "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
          "node scripts/check-ci-filter-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-corpus.mjs :: exit 0",
          "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
          "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs :: exit 0",
          "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
          "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
          "node scripts/check-registry-log-declared.mjs :: exit 0",
          "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
          "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
          "node scripts/check-system-context-census.mjs :: exit 0",
          "node scripts/check-system-context-census.mjs --self-test :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
          "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
          "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
          "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
          "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
          "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
          "pnpm check:changeset-gate-self-tests :: exit 0",
          "pnpm check:cross-package-test-inputs :: exit 0",
          "pnpm check:doc-authoring :: exit 0",
          "pnpm check:driver-conformance :: exit 0",
          "pnpm check:driver-memory-census :: exit 0",
          "pnpm check:dts-closure :: exit 0",
          "pnpm check:dual-build-cjs-loads :: exit 0",
          "pnpm check:engine-double-contract :: exit 0",
          "pnpm check:gitlink-declared :: exit 0",
          "pnpm check:issue-citations :: exit 0",
          "pnpm check:lean-entry-closure :: exit 0",
          "pnpm check:logger-receiver-detach :: exit 0",
          "pnpm check:nul-bytes :: exit 0",
          "pnpm check:object-def-param-keys :: exit 0",
          "pnpm check:objectql-double-limit :: exit 0",
          "pnpm check:objectui-changeset :: exit 0",
          "pnpm check:org-identifier :: exit 0",
          "pnpm check:page-declaration-shape :: exit 0",
          "pnpm check:pm-changeset-deadline-census :: exit 0",
          "pnpm check:published-files :: exit 0",
          "pnpm check:query-options-erasure :: exit 0",
          "pnpm check:refd-timer-probe :: exit 0",
          "pnpm check:slot-lookup :: exit 0",
          "pnpm check:sourcemap-no-sources-content :: exit 0",
          "pnpm check:tenant-chokepoint :: exit 0",
          "pnpm check:test-source-alias :: exit 0",
          "pnpm check:tier-file-adoption :: exit 0",
          "pnpm check:type-check-coverage :: exit 0",
          "pnpm check:type-check-debt :: exit 0",
          "pnpm check:watch-hint-literal :: exit 0",
          "pnpm check:where-matcher :: exit 0"
        ],
        "ran_verdict": "dispatch-gates --ran: 61 derived famil(ies) accounted for, 61 run, 0 NOT-MEASURED (a DERIVED zero: all 61 recorded an exit code and none of them is 3), exit 0",
        "ci": "not awaited, per contract"
      },
      "files_changed": [
        ".changeset/19868-turso-remote-date-json-residue.md (4 sentences)",
        "packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts (JS_TRIM_CHARS spelling; 2 docblocks)"
      ],
      "deviations": [
        "Item (1) asked for one sentence. Three more sentences in the same changeset had the same flaw: they were true only relative to the #19844 read fix. They were corrected in the same edit and are quoted verbatim above.",
        "The coordinator named five checks. The full 61-command union was also re-derived and re-run on the final head.",
        "Byte identity was shown by two runs of one node evaluator script (before/after) rather than a single node -e, because the before leg needed the pre-edit file. The readings are identical.",
        "The PR body (seat-owned) still quotes round-1 readings on 4afdb8c170: \"Package runs on `4afdb8c170`\" and \"dispatch-gates.mjs --commands on `4afdb8c170` derived 61 commands ... Three first exited 3\". Suggested refresh for the seat: on 257ac60607 the package counts are the same (59 files, 1331 tests), and all 61 gates exit 0 on the first pass. The body contains neither of the two sentences the review flagged; this round made no body write.",
        "Commit trailers are the model-free pair from AGENTS.md, as in round 1."
      ],
      "mcp_calls": "0",
      "api_writes": "1 REST write: this os-dev-report comment via scripts/pm/post-stamped.mjs (relay: POST /repos/objectstack-ai/objectstack/dispatches, which runs POST /repos/objectstack-ai/objectstack/issues/19868/comments). Plus 1 git push (4afdb8c170..257ac60607, fast-forward; not REST). Reads were REST GETs only: the PR body check.",
      "cleanup": "Worktree ../objectstack-issue-19868 removed again after the push (node_modules first); git worktree remove exit 0.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — round 21 · PR #19904 · Fixes

    domain:engine#1, session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T17:58Z. Every reading below was taken against GitHub and origin/main in this act, ⛔ not from the dev's report.

    Review

    check reading
    head 257ac60607 (patch round 1, fast-forward from 4afdb8c170)
    PR shape draft → main; first body line Fixes #19868 (switched from Refs by the seat under the #12380 ruling, below); no other closing keyword; Clause-②: no at column 0, agreeing with the patch changeset
    scope 4 files, all under packages/drivers/driver-turso/src/ plus one changeset: the new remote-codec-residue-backfill.ts, its wiring in turso-driver.ts (+91 / −6), a 14-case test file
    decision option A. The maintainer's ruling of 2026-08-26 on #12380 settled this class for the local face ("The class stops growing; it is not retroactively repaired."), for the reason "No migration can know which was written"; a new branch of a ruled class reuses the ruling with its reason. The PR converges every exactly recoverable class (date stored as a full timestamp, scalar json stored unencoded) and discloses the ambiguous rest in its changeset
    clause ② no, patch confirmed: no codec, DDL, read or public-surface change; precedent #5770 / #19844 for an at-rest convergence at boot
    governed NOT governed (check-governed-merges --pr 19904, 0 of 4 paths; +992 / −6)
    contract review FAIL on 4afdb8c170 (5799532789: two false sentences, no logic defect) → patch round → PASS on this head (5800102426)
    CI latest per name on this head: 29 success, 5 skipped, 0 failed; all seven required contexts success

    Out-of-scope findings — dispositions:

    Landing: ready → queue on this head once CI is fully green. #19893 and #19894 (same file, turso-driver.ts) dispatch after this lands.


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    ContributorAuthor

    PR #19904 ejected from the merge queue — new signature, not re-queued

    domain:engine#1, session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T18:19Z.

    Signature (queue build 35899547571, merge_group on gh-readonly-queue/main/pr-19904-8490127962834a125239ae85d9e6a88c0386896c, the PR alone on main 8490127962):

    • job Test Core (2/6), step "Run this shard's tests";
    • @objectstack/runtime · src/package-uninstall-org-scope.integration.test.ts › #7705 — org-scoped uninstall must not orphan env-wide sys_metadata rows › does NOT sweep up another organization’s rows;
    • first error line: Error: Test timed out in 5000ms. (a timeout, not an assertion).

    Ledger reading: no queue-flake-anchor issue exists for src/package-uninstall-org-scope.integration.test.ts, open or closed. The triage comment (5800378376) shows this PR as the only hit in its 24h window, and that window's comment ledger was not read to its end. So this is a new signature, and under the seat's landing rules a new signature is ⛔ not re-queued.

    Initial judgment: not PR #19904's regression.

    The same record is on PR #19904 (5800424463).

    What unblocks it: a human upgrade of the ledger (this file's timeout recorded as known flaky), or an instruction to re-queue once. In either case the seat re-arms auto-merge on the unchanged head 257ac60607; this card stays pm:dispatched until PR #19904 lands. A second ejection on the same signature stops re-queuing for good and is diagnosed afresh. #19868 stays pm:dispatched meanwhile. The two same-file cards queued behind it (#19893 p1, #19894 p2) stay undispatched until this lands.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions