Repository navigation
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
Activity
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actions定级
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 nodateleg and nojsonleg) ⇒domain:engine; rationale: cells a remote-Turso boot wrote before PR #19863 stay wrong at rest — a storedtruereads back as1, a string'42'as the number42, and an equality filter on adatemisses 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/maindabf8d795e)- PR fix(driver-turso): remote syncSchemasBatch registers read coercion and runs the canonical backfill #19863 已落地(
1f89ba0d70),driver-turso remotesyncSchemasBatch— the engine boot door — skips read-coercion registration, managed-object recording and the canonical backfill: a remote boolean can read back as 1, JSON as a string #19844 已由其车道关闭 ⇒ 卡面写的「等 fix(driver-turso): remote syncSchemasBatch registers read coercion and runs the canonical backfill #19863 落地之后」已满足,⛔ 不再挂阻塞。 turso-driver.ts:1741completeRemoteSchemaSync→:1746只调backfillRemoteCanonicalTemporalQuietly;:1781的backfillRemoteCanonicalTemporal只遍历datetimeFields(:1793)和timeFields(:1797)。没有date分支,也没有json分支,与卡面一致。
判定
- 业务后果:用远程 Turso 的客户,修复前写进去的布尔型 JSON 值读出来变成
1,文本'42'变成数字42;按日期等值筛选会漏掉那些存成完整时间戳的行。不报错,只是数据不对。 p1:北极星「优先级」第 1 条(数据完整性),静默错误,覆盖所有经修复前开机入口写过这两类值的远程 Turso 部署。PR fix(driver-turso): remote syncSchemasBatch registers read coercion and runs the canonical backfill #19863 的 changeset 只是向升级者披露了这两类残留,没有纠正它们。- 修法方向(⛔ 不是裁定,卡面已写):在同一个同步后时点,加一个远程版的 JSON 重新编码,再加一个
date归一化分支;带上 [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 那次 JSON 回填的安全闸门(已经是合法 JSON 且形状对的单元格跳过)。本地一侧要不要也加date分支,由接手人测量后决定。 ⚠️ 串行:driver-turso remotedetectManagedDrift()diffs against the dummy:memory:Knex connection remote mode is given — drift is always empty, so the artifact boot migration gate reads a remote Turso database as never drifted #19845(远程detectManagedDrift)也改这个文件。认领前读一遍在飞 PR 的文件清单,有碰turso-driver.ts的就排在其后,⛔ 不并入。
Generated by Claude Code
- PR fix(driver-turso): remote syncSchemasBatch registers read coercion and runs the canonical backfill #19863 已落地(
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2and removed
on Sep 23, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsClaim: 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 fromcompleteRemoteSchemaSync, tests), one.changeset/*.md;packages/drivers/driver-sql/src/sql-driver.tsONLY 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
1may have been the booleantrueor the number1, 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
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsACCEPT — round 21 · PR #19904 ·
Fixesdomain:engine#1,session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T17:58Z. Every reading below was taken against GitHub andorigin/mainin this act, ⛔ not from the dev's report.Review
check reading head 257ac60607(patch round 1, fast-forward from4afdb8c170)PR shape draft → main; first body lineFixes #19868(switched fromRefsby the seat under the #12380 ruling, below); no other closing keyword;Clause-②: noat column 0, agreeing with thepatchchangesetscope 4 files, all under packages/drivers/driver-turso/src/plus one changeset: the newremote-codec-residue-backfill.ts, its wiring inturso-driver.ts(+91 / −6), a 14-case test filedecision 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 ( datestored as a full timestamp, scalarjsonstored unencoded) and discloses the ambiguous rest in its changesetclause ② no,patchconfirmed: no codec, DDL, read or public-surface change; precedent #5770 / #19844 for an at-rest convergence at bootgoverned 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, 5skipped, 0 failed; all seven required contextssuccessOut-of-scope findings — dispositions:
- The LOCAL [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 json backfill
json_quotes a correctly stored value nested past SQLite's JSON depth limit (reproduced by the dev and the reviewer) → filed driver-sql: the local #12380 json backfill json_quotes JSON nested past SQLite depth limit, so a correctly stored deep array becomes a string on the next syncSchema #19912. - Remote boots scan each managed table with
date/jsoncolumns once per process (Turso bills rows read); disclosed → Acceptance notes. - The libsql double binds a JS number as REAL while the Turso wire sends a boolean as INTEGER; both shapes pinned → Acceptance notes.
- The remote schema doors never call
resolveFileColumnsMoved, so an engine-supplied ADR-0104 moved-columns resolver is never asked in remote mode. No defect was reproduced → Acceptance notes. It rides driver-turso remote planMediaColumnMove answers empty, so os migrate files-to-references reports nothing to move on a remote Turso database whose media columns are present #19894's dispatch as context (the same remote ADR-0104 media-column family). - The NUL exception docblock could say "immediately after the day"; wider readings are still safe → Acceptance notes.
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
- The LOCAL [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 json backfill
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsPR #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_groupongh-readonly-queue/main/pr-19904-8490127962834a125239ae85d9e6a88c0386896c, the PR alone onmain8490127962):- 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-anchorissue exists forsrc/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.
- PR fix(driver-turso): converge remote date/json cells the pre-fix batch door stored unconverted #19904 changes four paths, all under
packages/drivers/driver-turso/plus its changeset. The failing file imports@objectstack/objectql,@objectstack/driver-sqland@objectstack/metadata-protocoland runs a realSqlDriveron on-diskbetter-sqlite3. It does not importdriver-turso, and nothing it runs is in this diff. - The same base passed:
main8490127962has everyTest Coreshardsuccess,Test Core (2/6)included, and the test file is unchanged since spec: inline view arms of the runtime write door require the object binding #8089. - The failure mode is a 5000 ms timeout in an eight-case file that builds a fresh on-disk database per case: load-sensitive, the class the triage comment reads as "负载/时序".
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 stayspm:dispatcheduntil PR #19904 lands. A second ejection on the same signature stops re-queuing for good and is diagnosed afresh. #19868 stayspm:dispatchedmeanwhile. The two same-file cards queued behind it (#19893p1, #19894p2) stay undispatched until this lands.
Generated by Claude Code
- job
Filing gate: ① a defect with a named landing site, the remote post-sync backfill of
TursoDriverinpackages/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:engineexecution 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:dategiven aDateor 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.jsonvalue (a string, number or boolean) was stored without its JSON encoding:truebinds as1into theTEXTcolumn, and the string'42'stays'42'. After the fix it reads back as whatever the text parses to: a storedtruereads back as1, and the string'42'as the number42. Objects and arrays were unaffected (the transport'sserializeValuestringifies them exactly asformatInputwould).The one backfill the fix runs,
backfillRemoteCanonicalTemporal, probes onlydatetimeFields/timeFieldscolumns. It has nodateleg and nojsonleg. The local face'sSqlDriver.backfillCanonicalJsonEncodingis a Knex path the remote face never calls, and nodatebackfill exists on either face.Reach
Every remote Turso deployment (a
libsql://,https://,http://,wss://orws://URL withoutsyncUrl, ormode: 'remote') that wrote adatefield from aDateor a full timestamp, or a scalar into ajsonfield, 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.syncSchemasBatchalone (the pre-fix arm), then boot aTursoDriverwith the fixedsyncSchemasBatchover the samelibsqlSQLite double, then read and filter. Measured against the fix code (turso-driver.tsblobb33d3987b0bc, identical at PR #19863 headsd8940d7ee4andde31187a96): 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
datenormalization 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 adateleg belongs on the local face too is the taker's measurement.Filing-gate answers
packages/drivers/driver-tursoafter triage routes it (the domain table:domain:engine); it lands after PR fix(driver-turso): remote syncSchemasBatch registers read coercion and runs the canonical backfill #19863 (same file).closedincluded:turso remote backfill json encoding scalar date full timestamp not converged backfillCanonicalJsonEncoding remote counterpart→ 5 hits: driver-turso remotesyncSchemasBatch— the engine boot door — skips read-coercion registration, managed-object recording and the canonical backfill: a remote boolean can read back as 1, JSON as a string #19844 (the parent; its door fix, not the residue), driver-turso remote mode ignores deferred DDL —os migrate planagainst a remote Turso datasource performs the DDL and the canonical backfill it was meant to preview, and prints no pending work #19823 (closed, deferred DDL), drivers(turso): remote 模式补 canonical backfill 通道(@libsql/client,分批、可恢复、完成标记)—— cloud#1005 移交,维护者已裁方案 1 #5770 (closed, the temporal backfill channel this would extend), [finding] driver-turso puts one declaredField.jsonin two different physical column types —jsonon the local transport,TEXTon the remote one — and nothing records that as intended #12586 (closed, json column type on the two transports), drivers(turso): RemoteTransport 条件层的$-算子键被当列名编译成静默空集 —— SqlDriver 已按 #5348 拒收,remote 是唯一剩余面(cloud#1077 移交) #5769 (closed,$-operator keys). None is this defect.Dedupe words:
turso remote date full timestamp not converged·remote scalar json unencoded backfill·backfillCanonicalJsonEncoding remote counterpart·pre-fix batch door residue cellsGenerated by Claude Code