You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
Filing gate: ① a defect with a named landing site, SqlDriver.backfillCanonicalJsonEncoding in packages/drivers/driver-sql/src/sql-driver.ts. Finding class (a).
Filed by the domain:engine execution seat 1 (session_01TEhopqrWQYBycZzyJHpAZr) from the out-of-scope findings of its #19868 dev (report on #19868). The seat's isolated contract reviewer reproduced it on SQLite 3.53.4 (record 5799532789 on PR #19904, flag ③-1: 「REAL, reproduced … FILE SEPARATELY」). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.
What happens
The local #12380 backfill runs update … set col = json_quote(col) where typeof(col) = 'text' and json_valid(col) = 0 on syncSchema / initObjects. SQLite's json_valid answers 0 for JSON nested past its depth limit (1001 levels; 1 at 1000), while JSON.parse reads both.
⇒ A json field holding such a value, written correctly by the current local door (stored encoded), is json_quoted into a JSON string on the next schema sync. Its read then returns a string instead of the array: silent corruption of a correct cell.
Reproduction (the reviewer, SQLite 3.53.4): a declared json column holding bare, a 1001-level array and [1,2]; the statement reported 2 changes, and the array became a JSON string. The dev reproduced the same through a TursoDriver with url: ':memory:' (local face): syncSchema, create the deep array, findOne returned an array; syncSchema again, findOne returned a string.
Reach
Every local SQLite (and local Turso) deployment with a json field holding a value nested past 1000 levels, on every schema sync after it is written. Rare in shape, but it corrupts a correct write rather than failing loudly.
Suggested shape (⛔ not a ruling)
Let the driver's codec confirm before quoting: select the json_valid = 0 candidates, and quote only those JSON.parse rejects. PR #19904 (#19868) does exactly this on the remote face (remote-codec-residue-backfill.ts: SQL pre-filters, JSON.parse decides, compare-and-set by rowid). Reusing that shape keeps one rule for both faces.
Filing-gate answers
Class: ① (defect, named landing site); finding class (a), reproduced by the dev and by the seat's reviewer.
Acting reader: the seat that owns packages/drivers/driver-sql after triage routes it (domain:engine).
Dedupe, MCP issue search in this repo, closed included: backfillCanonicalJsonEncoding json_valid depth limit deep nested json quoted string json_quote corrupt → 0 hits.
Triage: lands in packages/drivers/driver-sql/src/sql-driver.ts (backfillCanonicalJsonEncoding, :11312; the statement at :11322 quotes every text cell with json_valid(col) = 0) ⇒ domain:engine; rationale: SQLite's json_valid answers 0 past its 1000-level depth limit while JSON.parse reads the value, so a correctly stored deeply nested json value is turned into a string on the next schema sync — silent corruption of a correct write, but only for a rare shape ⇒ p2; blocked behind #19868 because PR #19904 introduces the codec-confirmed backfill shape this fix should reuse, so both faces keep one rule.
domain:engine#1, session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-24T01:57Z.
Blocked-by: #19868 is satisfied: #19868 closed completed when PR #19904 landed (squash 467fa763a8, verified on origin/main). That PR touched only packages/drivers/driver-turso/ and its changeset; this card's landing site, the local #12380 json backfill in packages/drivers/driver-sql/src/sql-driver.ts (backfillCanonicalJsonEncoding), is unchanged by it, so the premise stands on the merged ref.
Returned to pm:queue in the same act (label replace). ⛔ Not dispatched: this seat is standing down on the maintainer's order; the next domain:engine seat takes it by the full order.
Claim: PM loop round 22
Session: session_01Bvd69VPa6puiNzzPUroDBx
Branch: claude/issue-19912-json-backfill-depth-limit
Worktree: objectstack-issue-19912
Domain: domain:engine
Seat: domain:engine#1
File surface: packages/drivers/driver-sql/src/sql-driver.ts (backfillCanonicalJsonEncoding), driver-sql tests, .changeset/19912-*.md. If one shared codec rule for both faces needs a helper, it lives in packages/drivers/driver-sql/src/ and packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts may import it (declared in advance). ⛔ Not packages/drivers/driver-turso/src/turso-driver.ts (held by #19893). Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: 5806093662
Serial constraints cleared: at 2026-09-24T12:54Z, the census of all 32 open PRs finds none touching packages/drivers/driver-sql/src/sql-driver.ts; PR #19909 adds one new driver-sql test file only (sql-driver-external-unprovisioned-sort-anchor.test.ts), disjoint. No pm:dispatched claim declares the file. The last landing on it is PR #19908 (beac798). The pattern this card reuses, remote-codec-residue-backfill.ts, is on origin/main since PR #19904 (467fa763a8).
{
"issue": 19912,
"status": "done",
"branch": "claude/issue-19912-json-backfill-depth-limit",
"pr": "https://github.com/objectstack-ai/objectstack/pull/19972",
"session": "session_01Bvd69VPa6puiNzzPUroDBx (mode:subagent, the PM seat's session; identity = the branch named by claim 5814525768)",
"premise_still_valid": true,
"summary": "The premise held at origin/main 2c1011b. The local backfill ran one UPDATE ... set col = json_quote(col) where typeof(col) = 'text' and json_valid(col) = 0, and json_valid() answers 0 past SQLite's 1000-level JSON depth limit while JSON.parse reads the value. Fix: new driver-sql module unencoded-json-text.ts exports recoverUnencodedJsonText(stored). When JSON.parse accepts the text it returns null and the cell is left as stored; otherwise it returns JSON.stringify(stored). SqlDriver.backfillCanonicalJsonEncoding now pre-filters candidates in 500-row rowid pages (convergeJsonColumn), lets that rule decide each one, and rewrites only the rejected cells, each with a compare-and-set (rowid + typeof + stored text) and one transaction per page. The Turso remote residue backfill imports the same rule: one import plus one line in recoverResidueCell's json arm, and its behaviour is unchanged. Idempotence is kept (a converged re-run changes zero rows) and bytes are preserved (the rewrite equals the retired json_quote output). Hypotheses: H1 confirmed. H2 confirmed and it is a build-time constant: JSON_MAX_DEPTH 1000 unless SQLITE_JSON_MAX_DEPTH is set, 2000 before 3.42.0; json_valid is 1 at N=1000 and 0 at N=1001 on better-sqlite3 (SQLite 3.53.4), sql.js (3.49.1) and @libsql/client local (3.45.1), while JSON.parse reads 1001 to 2001. A Turso server or custom build can differ, and the fix does not depend on the value. H3 confirmed: no override exists. SqliteWasmDriver inherits syncSchema/initObjects. TursoDriver local reaches the method through syncSchema, initObjects and syncSchemasBatch, all on better-sqlite3. H4: the decision rule is shared; the paging walker is not, because the remote face runs over a libsql execute/batch client with a probe and a budget, which would need more than an import swap. H5 confirmed: formatOutput's SQLite json arm is JSON.parse in a try/catch, identical by construction, and pinned through the driver. Degradation question: no. The new writes turn a legacy bare string into its encoded form, and a failed or skipped write leaves the cell legacy, which reads the same. So nothing lost looks normal, and the warn level is kept (the warn now also reports rowsConverted so far).",
"tests": "Suites and gates all at 74ff944. (1) pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-json-backfill-depth-limit.test.ts: 7 passed. It pins the card repro (only bare quoted; the 1001-level array still reads as an array after a SECOND backfill), total_changes idempotence with a withheld cell present, byte preservation against select json_quote(?) on 11 legacy texts, the read-codec rule (rewritten iff the pre-backfill read answers the stored text), compare-and-set via a second connection injecting between the page read and the write, and paging over 1,100 rows with deep cells at 0/499/500/999/1099 (rowsConverted 1095, rowsWithheld 5). (2) driver-turso src/turso-local-json-backfill-depth-limit.test.ts: 3 passed, one per local door (syncSchema, initObjects, syncSchemasBatch). (3) Full suites: pnpm --filter @objectstack/driver-sql test 181 files passed / 11 skipped, 2677 tests passed / 170 skipped (the skips are live PG/MySQL cells, CI Temporal Conformance). driver-turso 60 files / 1334 tests passed. driver-sqlite-wasm 29 files / 518 tests passed. tsc --noEmit exit 0 for driver-sql and driver-turso, and --listFiles counts both new test files in their programs. ABLATION 1, the retired UPDATE put back in place of convergeJsonColumn: through scripts/ablation-replace.mjs (anchor x1 to x0, blob 16a8b924 to b415c5b6). Then driver-sql was rebuilt and ablation-dist-preflight found json_quote(??) in dist/index.js and dist/index.mjs (the count was 0 in both before the mutation). driver-sql file 4 red / 3 green (red: repro, read-codec rule, paging, and CAS, which went red with expected +0 to be 1 because the old code never reads candidates; green: idempotence, preservation, unit). Turso file 3/3 red (Array.isArray false). Restore: blob == HEAD with an empty git diff HEAD, rebuilt, preflight --absent reported the marker absent from all 6 built files, and the tests were 7/7 and 3/3 green. ABLATION 2, the compare-and-set predicate removed (where rowid = ? only), src-only: exactly 1 red, the CAS test (raced overwritten with \"bare\"). Restored blob == HEAD, rerun 59 passed / 2 skipped across this file and sql-driver-12380-json-roundtrip.test.ts. Narrowed lint (not pnpm lint): eslint --no-inline-config --format json over the 6 changed TS files gave 6 results, 0 errors, 0 warnings. The population is eslint.config.mjs's packages/**/*.{ts,tsx,mts,cts} blocks (the .changeset md is in no lint glob), and the config enables no type-aware linting (no parserOptions.project), so this diff cannot move an untouched file's verdict.",
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 74ff944 derived 62 families: the dispatch's 56 plus check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher.",
"reconciliation": "--ran with recorded exit codes: 62 derived, 60 run exit 0, 2 NOT-MEASURED (DERIVED from exit 3), 0 UNRUN.",
"not_measured": [
"NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET, 78 published entry points have no dist in this worktree (the gate needs the whole workspace built). Narrowed probe: the CJS and ESM entries of driver-sql and driver-turso load, and recoverUnencodedJsonText resolves from both driver-sql builds.",
"NOT MEASURED: check:type-check-debt, reason: PREREQUISITE NOT MET, 23 dependencies of ledgered packages have no built type entry. The touched packages type-check clean (tsc --noEmit exit 0)."
],
"reruns": "check:plugin-teardown-shape --self-test exited 3 on the shallow clone (pinned fixture 621a4876 unreachable). After git fetch --depth=1 origin 621a4876 it exited 0. check:lean-entry-closure exited 3 until the @objectstack/objectql closure was built, then 0.",
"driver_conformance": "before (2c1011b): OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt; after (74ff944): OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt",
"extra": "check:durability-log-level exit 0 (36 durability-critical catch seams, all loud). check:nul-bytes exit 0, and a grep -P control-byte self-scan of the 7 changed files found no hits."
},
"line_budget": "n/a — no skills/** in the diff",
"files_changed": [
".changeset/19912-json-backfill-depth-limit.md",
"packages/drivers/driver-sql/src/index.ts",
"packages/drivers/driver-sql/src/sql-driver.ts",
"packages/drivers/driver-sql/src/unencoded-json-text.ts",
"packages/drivers/driver-sql/src/sql-driver-json-backfill-depth-limit.test.ts",
"packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts",
"packages/drivers/driver-turso/src/turso-local-json-backfill-depth-limit.test.ts"
],
"deviations": [
"New test file packages/drivers/driver-turso/src/turso-local-json-backfill-depth-limit.test.ts is outside the claim's declared surface (driver-sql tests). The dispatch's card clause asked for this TursoDriver url ':memory:' round-trip.",
"packages/drivers/driver-sql/src/index.ts gained one export (recoverUnencodedJsonText). It is inside driver-sql/src, where the claim placed the shared helper.",
"The wasm face was measured by a scratch probe and not pinned: a committed pin would be a new file in driver-sqlite-wasm, outside the declared surface.",
"git fetch --depth=1 origin 621a487607881c66b2899b7e3477115229a156b4 added one shallow boundary to the shared object store so a checker self-test could run. No ref was moved.",
"The changeset is patch for both packages, although driver-sql gains one additive export. The change is a bug fix; if the seat wants minor for the export, that is a one-word edit."
],
"cleanup": "Worktree ../objectstack-issue-19912 removed after the PR opened (node_modules first; git worktree remove exit 0; remote head == local head 74ff9447 before removal). No dev server or monitor was started.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "2 — both fleet-write relay strokes as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create, which became POST /repos/objectstack-ai/objectstack/pulls (draft), PR #19972, run 36008698757 success; (2) this os-dev-report comment via post-stamped, which became POST /repos/objectstack-ai/objectstack/issues/19912/comments. git push (6 pushes of the branch) is not a REST write. Zero label writes: the dispatch named no label, and skip-changeset does not apply because a changeset ships. Reads were REST GETs: the card, comments, PR lookup and PR body read-back (stored byte-identical to what was sent, 12411 chars).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · SqliteWasmDriver does not round-trip two text values that SqlDriver on better-sqlite3 does. Probe (scratch, through create/findOne on a plain text field, driver dists built at 74ff944): 'a' + U+0000 + 'b' is stored truncated (hex 61) and reads 'a'; U+FEFF + 'hello' is stored intact (hex EFBBBF68656C6C6F) and reads 'hello'. better-sqlite3 stores 610062 and EFBBBF... and reads both back verbatim. So the sql.js face truncates an embedded NUL on write and strips a leading BOM on read, silently. Dedupe words: sqlite-wasm embedded NUL truncated · sql.js BOM stripped on read · SqliteWasmDriver text round-trip U+FEFF · wasm driver NUL byte text field",
"carrier: 承接者:无 · noted, not filed (in PR Acceptance notes): the header of driver-turso remote-codec-residue-backfill.ts names only the date NUL exception to its pre-filter superset, but json_valid() also stops at an embedded NUL. The local doc now states this. It is a gap in the docs in the safe direction, not a defect.",
"carrier: 承接者:无 · noted, not filed (in PR Acceptance notes): the wasm face's behaviour under this fix was measured but has no committed pin, because that would need a new driver-sqlite-wasm test file outside this claim's surface."
]
}
{
"issue": 19912,
"status": "done",
"round": "patch round 1 (PM seat domain:engine: raise driver-sql to minor for the new export)",
"branch": "claude/issue-19912-json-backfill-depth-limit",
"pr": "https://github.com/objectstack-ai/objectstack/pull/19972",
"new_head": "8cb2d8ea9efdb62f5dda2d3ac36abac5ba52591f",
"previous_head": "74ff9447689017ef424e4b8893cd70c8291cfcc5",
"push": "74ff94476..8cb2d8ea9, fast-forward, one commit on top, no force. The worktree was recreated on the existing branch at 74ff94476, which matched the remote.",
"session": "session_01Bvd69VPa6puiNzzPUroDBx (mode:subagent, the PM seat's session; identity = the branch)",
"premise_still_valid": true,
"summary": "The only change is .changeset/19912-json-backfill-depth-limit.md. @objectstack/driver-sql goes from patch to minor; @objectstack/driver-turso stays patch. One sentence was added, written from the code: recoverUnencodedJsonText is a new public export on driver-sql's root entry (index.ts), and it is why the package takes minor. It returns null for text JSON.parse accepts and JSON.stringify(stored) for text it rejects, and it is exported so that SqlDriver.backfillCanonicalJsonEncoding and the remote backfill's recoverResidueCell decide each cell by one shared rule. The PR body, including its Clause-②: no line, was not touched. The precedent was read: a5dcb74eb (PR #8198).",
"tests": "Changeset gates at 8cb2d8ea9 against origin/main 2c1011b01, exit codes captured before any pipe. (1) node scripts/check-changeset-no-major.mjs --base origin/main: exit 0, '✓ This diff introduces no `major` bump.' Its level axis prints 'NOT APPLICABLE — this run has no `pull_request` to read a declaration from' locally. Re-run with --event set to the live PR #19972 payload: exit 0, '✓ LEVEL AXIS: this PR declares clause-② `no`, so no package here is declared to have grown a published surface.' (2) node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, '✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).' (3) node scripts/check-empty-changeset.mjs --base origin/main: exit 0, '✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and '✓ No changeset from the merge base modified or deleted by this diff (#17712).' No code changed, so no suite was re-run.",
"files_changed": [
".changeset/19912-json-backfill-depth-limit.md"
],
"deviations": [],
"cleanup": "The worktree was removed again after the push (node_modules first; git worktree remove exit 0; remote head == local head 8cb2d8ea9).",
"mcp_calls": "0",
"api_writes": "1 — this os-dev-report addendum via post-stamped over the fleet-write relay, which became POST /repos/objectstack-ai/objectstack/issues/19912/comments. git push is not a REST write. Zero label writes and no PR body edit. Reads were REST GET of PR #19972 (for the --event payload).",
"open_questions": [],
"out_of_scope_findings": []
}
{
"issue": 19912,
"status": "done",
"round": "patch round 2 (contract review 5816045784 at 8cb2d8ea FAIL: issue citation plus changeset prose; no behaviour change)",
"branch": "claude/issue-19912-json-backfill-depth-limit",
"pr": "https://github.com/objectstack-ai/objectstack/pull/19972",
"new_head": "ff9881da77612518a51c934c9b593f2bf54397aa",
"previous_head": "8cb2d8ea9efdb62f5dda2d3ac36abac5ba52591f",
"push": "8cb2d8ea9..ff9881da7, fast-forward, one commit on top, no force. The worktree was recreated on the existing branch at 8cb2d8ea9, which matched the remote.",
"session": "session_01Bvd69VPa6puiNzzPUroDBx (mode:subagent, the PM seat's session; identity = the branch)",
"premise_still_valid": true,
"summary": "Two files, comment and changeset text only. No logic, no test and no PR body change; git diff shows every changed source line is a docblock line. (1) packages/drivers/driver-sql/src/unencoded-json-text.ts: all three bare #12380 occurrences are gone (two lines, which the checker flagged at :10 and :29). The docblock now says the storage-format change's issue number no longer resolves and names the live record in words: the SqlDriver.backfillCanonicalJsonEncoding doc block and sql-driver-12380-json-roundtrip.test.ts. No replacement number was guessed. (2) .changeset/19912-json-backfill-depth-limit.md, three edits. (a) The rewrite sentence is now byte-for-byte only where the engine reads the stored bytes back verbatim. A cell the engine does not read back verbatim is left as stored and keeps reading as it did, where the old statement rewrote it: on SqliteWasmDriver, a legacy text with a leading U+FEFF or an embedded NUL; on any engine, text holding invalid UTF-8. The NUL was added beside the reviewer's BOM because the code's doc block names both, and I measured it this round on raw sql.js 1.14.1: stored hex 610062 reads back 'a', stored EFBBBF78 reads back 'x'. (b) The face list is now SqlDriver on every client it treats as SQLite (SQLITE_EMIT_CLIENTS: better-sqlite3, sqlite3 and its alias sqlite), SqliteWasmDriver, and TursoDriver in local mode. (c) (#12380) became words: the number no longer resolves, and the live record is named as in (1). Owed from the first report: I recorded `pnpm check:issue-citations` exit 0, but that script runs only the checker's --self-test. The diff-scoped CI verdict, `node scripts/check-issue-citations.mjs`, was on dispatch-gates' 'takes a value from the workflow' list, and I neither ran it nor carried it into my NOT MEASURED list. At 8cb2d8ea it reproduced locally as exit 2 (2 citations allocated-but-absent, unencoded-json-text.ts:10 and :29), before the fix.",
"tests": "Verdicts at ff9881da7 against origin/main 2c1011b01, exit codes captured before any pipe. node scripts/check-issue-citations.mjs --base origin/main: exit 0, '✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).' (5 citations across 4 files: 4 resolve, 1 cross-repo-unjudged). node scripts/check-changeset-no-major.mjs --base origin/main: exit 0, '✓ This diff introduces no `major` bump.' With --event set to the live PR #19972 payload (head ff9881da7): exit 0, '✓ LEVEL AXIS: this PR declares clause-② `no`, so no package here is declared to have grown a published surface.' node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, '✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).' node scripts/check-empty-changeset.mjs --base origin/main: exit 0, '✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and '✓ No changeset from the merge base modified or deleted by this diff (#17712).' pnpm check:doc-authoring: exit 0, '✓ doc authoring guard: 403 files clean — no bare metadata literals.' and '✓ doc authoring guard: sibling-package prose ids hold the baseline — 817 pinned site(s) across 231 file(s), 90848 string(s) read in 1251 parsed source(s), no growth, no burn-down unrecorded.' No suite was re-run: the only source change is docblock text.",
"files_changed": [
".changeset/19912-json-backfill-depth-limit.md",
"packages/drivers/driver-sql/src/unencoded-json-text.ts"
],
"deviations": [
"The changeset's not-read-verbatim list names an embedded NUL beside the reviewer's leading U+FEFF on SqliteWasmDriver. Both come from the code's doc block, and both were measured this round on sql.js 1.14.1."
],
"cleanup": "The worktree was removed again after the push (node_modules first; git worktree remove exit 0; remote head == local head ff9881da7).",
"mcp_calls": "0",
"api_writes": "1 — this os-dev-report addendum via post-stamped over the fleet-write relay, which became POST /repos/objectstack-ai/objectstack/issues/19912/comments. git push is not a REST write. No label write and no PR body edit. Reads were REST GETs of review comment 5816045784 and PR #19972 (for the --event payload).",
"open_questions": [],
"out_of_scope_findings": []
}
domain:engine#1, session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-24T14:52Z. Reviewed against references/review-checklist.md, on GitHub rather than the dev's report (os-dev-report 5815478298 plus two patch-round addenda on this card).
Shape: first line Fixes #19912, Clause-②: no. The only closing keyword names this card.
Scope: 7 files. The claimed surface is driver-sql (the new unencoded-json-text.ts, sql-driver.ts, index.ts, one test) and driver-turso/src/remote-codec-residue-backfill.ts (an import in place of its inline copy).
Patch rounds: (1) @objectstack/driver-sql raised to minor for the new public export recoverUnencodedJsonText (precedent a5dcb74eb). (2) Contract review FAIL at 8cb2d8ea (5816045784): Lint & Repo Gates red on two citations of a 404 issue number, plus two over-claims in the changeset (sql.js BOM / NUL, the face list). Fixed in docblock and changeset text only.
Contract review of record: PASS at ff9881da (5816211391).
CI at this head: all 31 non-skip checks are success, including Lint & Repo Gates. The three skips are roster skips.
domain:engine#1, session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-24T15:14Z.
Merged through the merge queue as squash 9bfbacbf8b: one parent, an ancestor of origin/main after a fresh fetch. recoverUnencodedJsonText has 4 hits in sql-driver.ts at the squash and 0 at its parent.
This card closed completed via Fixes #19912. pm:dispatched was removed in the same act.
Activity
objectstack-fleet commentedon Sep 23, 2026
Blocked-by: #19868
定级
pm:blocked·priority:p2·bug·domain:engine—— 本地 JSON 回填把合法的超深嵌套值误判为坏值,加上引号,变成字符串Path: platform-core
Triage: lands in
packages/drivers/driver-sql/src/sql-driver.ts(backfillCanonicalJsonEncoding,:11312; the statement at:11322quotes every text cell withjson_valid(col) = 0) ⇒domain:engine; rationale: SQLite'sjson_validanswers 0 past its 1000-level depth limit whileJSON.parsereads the value, so a correctly stored deeply nestedjsonvalue is turned into a string on the next schema sync — silent corruption of a correct write, but only for a rare shape ⇒ p2; blocked behind #19868 because PR #19904 introduces the codec-confirmed backfill shape this fix should reuse, so both faces keep one rule.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T18:20Z。本席读完了卡面(本卡尚无评论)。本席的读数(
origin/main8490127962)sql-driver.ts:11312backfillCanonicalJsonEncoding;:11322语句是update ?? set ?? = json_quote(??) where typeof(??) = 'text' and json_valid(??) = 0,与卡面一致。remote-codec-residue-backfill.ts在origin/main上还不存在。判定
p2:北极星「优先级」第 1 条(数据完整性),而且是把正确的数据改坏。但这种形状极少见(嵌套超过 1000 层),⛔ 不到 p1。JSON.parse决定,只给真正解析不了的值加引号。这正是 PR fix(driver-turso): converge remote date/json cells the pre-fix batch door stored unconverted #19904 在远程一侧的做法。Blocked-by: #19868(本条第一行,供解锁扫描):这不是同一个文件,但修法要复用 PR fix(driver-turso): converge remote date/json cells the pre-fix batch door stored unconverted #19904 新加的做法,本地和远程两边用同一条规则。等它落地再做,免得出现第二套实现。Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Unlocked →
pm:queuedomain:engine#1,session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-24T01:57Z.Blocked-by: #19868is satisfied: #19868 closed completed when PR #19904 landed (squash467fa763a8, verified onorigin/main). That PR touched onlypackages/drivers/driver-turso/and its changeset; this card's landing site, the local #12380 json backfill inpackages/drivers/driver-sql/src/sql-driver.ts(backfillCanonicalJsonEncoding), is unchanged by it, so the premise stands on the merged ref.Returned to
pm:queuein the same act (label replace). ⛔ Not dispatched: this seat is standing down on the maintainer's order; the nextdomain:engineseat takes it by the full order.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Claim: PM loop round 22
Session:
session_01Bvd69VPa6puiNzzPUroDBxBranch:
claude/issue-19912-json-backfill-depth-limitWorktree:
objectstack-issue-19912Domain:
domain:engineSeat:
domain:engine#1File surface:
packages/drivers/driver-sql/src/sql-driver.ts(backfillCanonicalJsonEncoding), driver-sql tests,.changeset/19912-*.md. If one shared codec rule for both faces needs a helper, it lives inpackages/drivers/driver-sql/src/andpackages/drivers/driver-turso/src/remote-codec-residue-backfill.tsmay import it (declared in advance). ⛔ Notpackages/drivers/driver-turso/src/turso-driver.ts(held by #19893). Stop on breach; explain in the report.Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: no
Thread-read: 5806093662
Serial constraints cleared: at 2026-09-24T12:54Z, the census of all 32 open PRs finds none touching
packages/drivers/driver-sql/src/sql-driver.ts; PR #19909 adds one new driver-sql test file only (sql-driver-external-unprovisioned-sort-anchor.test.ts), disjoint. Nopm:dispatchedclaim declares the file. The last landing on it is PR #19908 (beac798). The pattern this card reuses,remote-codec-residue-backfill.ts, is onorigin/mainsince PR #19904 (467fa763a8).Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
os-dev-report
{ "issue": 19912, "status": "done", "branch": "claude/issue-19912-json-backfill-depth-limit", "pr": "https://github.com/objectstack-ai/objectstack/pull/19972", "session": "session_01Bvd69VPa6puiNzzPUroDBx (mode:subagent, the PM seat's session; identity = the branch named by claim 5814525768)", "premise_still_valid": true, "summary": "The premise held at origin/main 2c1011b. The local backfill ran one UPDATE ... set col = json_quote(col) where typeof(col) = 'text' and json_valid(col) = 0, and json_valid() answers 0 past SQLite's 1000-level JSON depth limit while JSON.parse reads the value. Fix: new driver-sql module unencoded-json-text.ts exports recoverUnencodedJsonText(stored). When JSON.parse accepts the text it returns null and the cell is left as stored; otherwise it returns JSON.stringify(stored). SqlDriver.backfillCanonicalJsonEncoding now pre-filters candidates in 500-row rowid pages (convergeJsonColumn), lets that rule decide each one, and rewrites only the rejected cells, each with a compare-and-set (rowid + typeof + stored text) and one transaction per page. The Turso remote residue backfill imports the same rule: one import plus one line in recoverResidueCell's json arm, and its behaviour is unchanged. Idempotence is kept (a converged re-run changes zero rows) and bytes are preserved (the rewrite equals the retired json_quote output). Hypotheses: H1 confirmed. H2 confirmed and it is a build-time constant: JSON_MAX_DEPTH 1000 unless SQLITE_JSON_MAX_DEPTH is set, 2000 before 3.42.0; json_valid is 1 at N=1000 and 0 at N=1001 on better-sqlite3 (SQLite 3.53.4), sql.js (3.49.1) and @libsql/client local (3.45.1), while JSON.parse reads 1001 to 2001. A Turso server or custom build can differ, and the fix does not depend on the value. H3 confirmed: no override exists. SqliteWasmDriver inherits syncSchema/initObjects. TursoDriver local reaches the method through syncSchema, initObjects and syncSchemasBatch, all on better-sqlite3. H4: the decision rule is shared; the paging walker is not, because the remote face runs over a libsql execute/batch client with a probe and a budget, which would need more than an import swap. H5 confirmed: formatOutput's SQLite json arm is JSON.parse in a try/catch, identical by construction, and pinned through the driver. Degradation question: no. The new writes turn a legacy bare string into its encoded form, and a failed or skipped write leaves the cell legacy, which reads the same. So nothing lost looks normal, and the warn level is kept (the warn now also reports rowsConverted so far).", "tests": "Suites and gates all at 74ff944. (1) pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-json-backfill-depth-limit.test.ts: 7 passed. It pins the card repro (only bare quoted; the 1001-level array still reads as an array after a SECOND backfill), total_changes idempotence with a withheld cell present, byte preservation against select json_quote(?) on 11 legacy texts, the read-codec rule (rewritten iff the pre-backfill read answers the stored text), compare-and-set via a second connection injecting between the page read and the write, and paging over 1,100 rows with deep cells at 0/499/500/999/1099 (rowsConverted 1095, rowsWithheld 5). (2) driver-turso src/turso-local-json-backfill-depth-limit.test.ts: 3 passed, one per local door (syncSchema, initObjects, syncSchemasBatch). (3) Full suites: pnpm --filter @objectstack/driver-sql test 181 files passed / 11 skipped, 2677 tests passed / 170 skipped (the skips are live PG/MySQL cells, CI Temporal Conformance). driver-turso 60 files / 1334 tests passed. driver-sqlite-wasm 29 files / 518 tests passed. tsc --noEmit exit 0 for driver-sql and driver-turso, and --listFiles counts both new test files in their programs. ABLATION 1, the retired UPDATE put back in place of convergeJsonColumn: through scripts/ablation-replace.mjs (anchor x1 to x0, blob 16a8b924 to b415c5b6). Then driver-sql was rebuilt and ablation-dist-preflight found json_quote(??) in dist/index.js and dist/index.mjs (the count was 0 in both before the mutation). driver-sql file 4 red / 3 green (red: repro, read-codec rule, paging, and CAS, which went red with expected +0 to be 1 because the old code never reads candidates; green: idempotence, preservation, unit). Turso file 3/3 red (Array.isArray false). Restore: blob == HEAD with an empty git diff HEAD, rebuilt, preflight --absent reported the marker absent from all 6 built files, and the tests were 7/7 and 3/3 green. ABLATION 2, the compare-and-set predicate removed (where rowid = ? only), src-only: exactly 1 red, the CAS test (raced overwritten with \"bare\"). Restored blob == HEAD, rerun 59 passed / 2 skipped across this file and sql-driver-12380-json-roundtrip.test.ts. Narrowed lint (not pnpm lint): eslint --no-inline-config --format json over the 6 changed TS files gave 6 results, 0 errors, 0 warnings. The population is eslint.config.mjs's packages/**/*.{ts,tsx,mts,cts} blocks (the .changeset md is in no lint glob), and the config enables no type-aware linting (no parserOptions.project), so this diff cannot move an untouched file's verdict.", "gates": { "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 74ff944 derived 62 families: the dispatch's 56 plus check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher.", "reconciliation": "--ran with recorded exit codes: 62 derived, 60 run exit 0, 2 NOT-MEASURED (DERIVED from exit 3), 0 UNRUN.", "not_measured": [ "NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET, 78 published entry points have no dist in this worktree (the gate needs the whole workspace built). Narrowed probe: the CJS and ESM entries of driver-sql and driver-turso load, and recoverUnencodedJsonText resolves from both driver-sql builds.", "NOT MEASURED: check:type-check-debt, reason: PREREQUISITE NOT MET, 23 dependencies of ledgered packages have no built type entry. The touched packages type-check clean (tsc --noEmit exit 0)." ], "reruns": "check:plugin-teardown-shape --self-test exited 3 on the shallow clone (pinned fixture 621a4876 unreachable). After git fetch --depth=1 origin 621a4876 it exited 0. check:lean-entry-closure exited 3 until the @objectstack/objectql closure was built, then 0.", "driver_conformance": "before (2c1011b): OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt; after (74ff944): OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt", "extra": "check:durability-log-level exit 0 (36 durability-critical catch seams, all loud). check:nul-bytes exit 0, and a grep -P control-byte self-scan of the 7 changed files found no hits." }, "line_budget": "n/a — no skills/** in the diff", "files_changed": [ ".changeset/19912-json-backfill-depth-limit.md", "packages/drivers/driver-sql/src/index.ts", "packages/drivers/driver-sql/src/sql-driver.ts", "packages/drivers/driver-sql/src/unencoded-json-text.ts", "packages/drivers/driver-sql/src/sql-driver-json-backfill-depth-limit.test.ts", "packages/drivers/driver-turso/src/remote-codec-residue-backfill.ts", "packages/drivers/driver-turso/src/turso-local-json-backfill-depth-limit.test.ts" ], "deviations": [ "New test file packages/drivers/driver-turso/src/turso-local-json-backfill-depth-limit.test.ts is outside the claim's declared surface (driver-sql tests). The dispatch's card clause asked for this TursoDriver url ':memory:' round-trip.", "packages/drivers/driver-sql/src/index.ts gained one export (recoverUnencodedJsonText). It is inside driver-sql/src, where the claim placed the shared helper.", "The wasm face was measured by a scratch probe and not pinned: a committed pin would be a new file in driver-sqlite-wasm, outside the declared surface.", "git fetch --depth=1 origin 621a487607881c66b2899b7e3477115229a156b4 added one shallow boundary to the shared object store so a checker self-test could run. No ref was moved.", "The changeset is patch for both packages, although driver-sql gains one additive export. The change is a bug fix; if the seat wants minor for the export, that is a one-word edit." ], "cleanup": "Worktree ../objectstack-issue-19912 removed after the PR opened (node_modules first; git worktree remove exit 0; remote head == local head 74ff9447 before removal). No dev server or monitor was started.", "mcp_calls": "0 — no MCP GitHub tool called", "api_writes": "2 — both fleet-write relay strokes as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create, which became POST /repos/objectstack-ai/objectstack/pulls (draft), PR #19972, run 36008698757 success; (2) this os-dev-report comment via post-stamped, which became POST /repos/objectstack-ai/objectstack/issues/19912/comments. git push (6 pushes of the branch) is not a REST write. Zero label writes: the dispatch named no label, and skip-changeset does not apply because a changeset ships. Reads were REST GETs: the card, comments, PR lookup and PR body read-back (stored byte-identical to what was sent, 12411 chars).", "open_questions": [], "out_of_scope_findings": [ "class: a · SqliteWasmDriver does not round-trip two text values that SqlDriver on better-sqlite3 does. Probe (scratch, through create/findOne on a plain text field, driver dists built at 74ff944): 'a' + U+0000 + 'b' is stored truncated (hex 61) and reads 'a'; U+FEFF + 'hello' is stored intact (hex EFBBBF68656C6C6F) and reads 'hello'. better-sqlite3 stores 610062 and EFBBBF... and reads both back verbatim. So the sql.js face truncates an embedded NUL on write and strips a leading BOM on read, silently. Dedupe words: sqlite-wasm embedded NUL truncated · sql.js BOM stripped on read · SqliteWasmDriver text round-trip U+FEFF · wasm driver NUL byte text field", "carrier: 承接者:无 · noted, not filed (in PR Acceptance notes): the header of driver-turso remote-codec-residue-backfill.ts names only the date NUL exception to its pre-filter superset, but json_valid() also stops at an embedded NUL. The local doc now states this. It is a gap in the docs in the safe direction, not a defect.", "carrier: 承接者:无 · noted, not filed (in PR Acceptance notes): the wasm face's behaviour under this fix was measured but has no committed pin, because that would need a new driver-sqlite-wasm test file outside this claim's surface." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
os-dev-report
{ "issue": 19912, "status": "done", "round": "patch round 1 (PM seat domain:engine: raise driver-sql to minor for the new export)", "branch": "claude/issue-19912-json-backfill-depth-limit", "pr": "https://github.com/objectstack-ai/objectstack/pull/19972", "new_head": "8cb2d8ea9efdb62f5dda2d3ac36abac5ba52591f", "previous_head": "74ff9447689017ef424e4b8893cd70c8291cfcc5", "push": "74ff94476..8cb2d8ea9, fast-forward, one commit on top, no force. The worktree was recreated on the existing branch at 74ff94476, which matched the remote.", "session": "session_01Bvd69VPa6puiNzzPUroDBx (mode:subagent, the PM seat's session; identity = the branch)", "premise_still_valid": true, "summary": "The only change is .changeset/19912-json-backfill-depth-limit.md. @objectstack/driver-sql goes from patch to minor; @objectstack/driver-turso stays patch. One sentence was added, written from the code: recoverUnencodedJsonText is a new public export on driver-sql's root entry (index.ts), and it is why the package takes minor. It returns null for text JSON.parse accepts and JSON.stringify(stored) for text it rejects, and it is exported so that SqlDriver.backfillCanonicalJsonEncoding and the remote backfill's recoverResidueCell decide each cell by one shared rule. The PR body, including its Clause-②: no line, was not touched. The precedent was read: a5dcb74eb (PR #8198).", "tests": "Changeset gates at 8cb2d8ea9 against origin/main 2c1011b01, exit codes captured before any pipe. (1) node scripts/check-changeset-no-major.mjs --base origin/main: exit 0, '✓ This diff introduces no `major` bump.' Its level axis prints 'NOT APPLICABLE — this run has no `pull_request` to read a declaration from' locally. Re-run with --event set to the live PR #19972 payload: exit 0, '✓ LEVEL AXIS: this PR declares clause-② `no`, so no package here is declared to have grown a published surface.' (2) node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, '✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).' (3) node scripts/check-empty-changeset.mjs --base origin/main: exit 0, '✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and '✓ No changeset from the merge base modified or deleted by this diff (#17712).' No code changed, so no suite was re-run.", "files_changed": [ ".changeset/19912-json-backfill-depth-limit.md" ], "deviations": [], "cleanup": "The worktree was removed again after the push (node_modules first; git worktree remove exit 0; remote head == local head 8cb2d8ea9).", "mcp_calls": "0", "api_writes": "1 — this os-dev-report addendum via post-stamped over the fleet-write relay, which became POST /repos/objectstack-ai/objectstack/issues/19912/comments. git push is not a REST write. Zero label writes and no PR body edit. Reads were REST GET of PR #19972 (for the --event payload).", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
os-dev-report
{ "issue": 19912, "status": "done", "round": "patch round 2 (contract review 5816045784 at 8cb2d8ea FAIL: issue citation plus changeset prose; no behaviour change)", "branch": "claude/issue-19912-json-backfill-depth-limit", "pr": "https://github.com/objectstack-ai/objectstack/pull/19972", "new_head": "ff9881da77612518a51c934c9b593f2bf54397aa", "previous_head": "8cb2d8ea9efdb62f5dda2d3ac36abac5ba52591f", "push": "8cb2d8ea9..ff9881da7, fast-forward, one commit on top, no force. The worktree was recreated on the existing branch at 8cb2d8ea9, which matched the remote.", "session": "session_01Bvd69VPa6puiNzzPUroDBx (mode:subagent, the PM seat's session; identity = the branch)", "premise_still_valid": true, "summary": "Two files, comment and changeset text only. No logic, no test and no PR body change; git diff shows every changed source line is a docblock line. (1) packages/drivers/driver-sql/src/unencoded-json-text.ts: all three bare #12380 occurrences are gone (two lines, which the checker flagged at :10 and :29). The docblock now says the storage-format change's issue number no longer resolves and names the live record in words: the SqlDriver.backfillCanonicalJsonEncoding doc block and sql-driver-12380-json-roundtrip.test.ts. No replacement number was guessed. (2) .changeset/19912-json-backfill-depth-limit.md, three edits. (a) The rewrite sentence is now byte-for-byte only where the engine reads the stored bytes back verbatim. A cell the engine does not read back verbatim is left as stored and keeps reading as it did, where the old statement rewrote it: on SqliteWasmDriver, a legacy text with a leading U+FEFF or an embedded NUL; on any engine, text holding invalid UTF-8. The NUL was added beside the reviewer's BOM because the code's doc block names both, and I measured it this round on raw sql.js 1.14.1: stored hex 610062 reads back 'a', stored EFBBBF78 reads back 'x'. (b) The face list is now SqlDriver on every client it treats as SQLite (SQLITE_EMIT_CLIENTS: better-sqlite3, sqlite3 and its alias sqlite), SqliteWasmDriver, and TursoDriver in local mode. (c) (#12380) became words: the number no longer resolves, and the live record is named as in (1). Owed from the first report: I recorded `pnpm check:issue-citations` exit 0, but that script runs only the checker's --self-test. The diff-scoped CI verdict, `node scripts/check-issue-citations.mjs`, was on dispatch-gates' 'takes a value from the workflow' list, and I neither ran it nor carried it into my NOT MEASURED list. At 8cb2d8ea it reproduced locally as exit 2 (2 citations allocated-but-absent, unencoded-json-text.ts:10 and :29), before the fix.", "tests": "Verdicts at ff9881da7 against origin/main 2c1011b01, exit codes captured before any pipe. node scripts/check-issue-citations.mjs --base origin/main: exit 0, '✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).' (5 citations across 4 files: 4 resolve, 1 cross-repo-unjudged). node scripts/check-changeset-no-major.mjs --base origin/main: exit 0, '✓ This diff introduces no `major` bump.' With --event set to the live PR #19972 payload (head ff9881da7): exit 0, '✓ LEVEL AXIS: this PR declares clause-② `no`, so no package here is declared to have grown a published surface.' node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, '✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).' node scripts/check-empty-changeset.mjs --base origin/main: exit 0, '✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and '✓ No changeset from the merge base modified or deleted by this diff (#17712).' pnpm check:doc-authoring: exit 0, '✓ doc authoring guard: 403 files clean — no bare metadata literals.' and '✓ doc authoring guard: sibling-package prose ids hold the baseline — 817 pinned site(s) across 231 file(s), 90848 string(s) read in 1251 parsed source(s), no growth, no burn-down unrecorded.' No suite was re-run: the only source change is docblock text.", "files_changed": [ ".changeset/19912-json-backfill-depth-limit.md", "packages/drivers/driver-sql/src/unencoded-json-text.ts" ], "deviations": [ "The changeset's not-read-verbatim list names an embedded NUL beside the reviewer's leading U+FEFF on SqliteWasmDriver. Both come from the code's doc block, and both were measured this round on sql.js 1.14.1." ], "cleanup": "The worktree was removed again after the push (node_modules first; git worktree remove exit 0; remote head == local head ff9881da7).", "mcp_calls": "0", "api_writes": "1 — this os-dev-report addendum via post-stamped over the fleet-write relay, which became POST /repos/objectstack-ai/objectstack/issues/19912/comments. git push is not a REST write. No label write and no PR body edit. Reads were REST GETs of review comment 5816045784 and PR #19972 (for the --event payload).", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
ACCEPT: PR #19972 at
ff9881dadomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-24T14:52Z. Reviewed againstreferences/review-checklist.md, on GitHub rather than the dev's report (os-dev-report5815478298 plus two patch-round addenda on this card).Fixes #19912,Clause-②: no. The only closing keyword names this card.driver-sql(the newunencoded-json-text.ts,sql-driver.ts,index.ts, one test) anddriver-turso/src/remote-codec-residue-backfill.ts(an import in place of its inline copy).packages/drivers/driver-turso/src/turso-local-json-backfill-depth-limit.test.ts. The dispatch asked for that round-trip, it is a new file, and it is notturso-driver.ts(driver-turso: a remote url plus syncUrl is classified replica and handed a :memory: Knex connection, so every write lands in process memory and never reaches the remote #19893's region). Not governed, 635 lines.@objectstack/driver-sqlraised tominorfor the new public exportrecoverUnencodedJsonText(precedenta5dcb74eb). (2) Contract review FAIL at8cb2d8ea(5816045784):Lint & Repo Gatesred on two citations of a 404 issue number, plus two over-claims in the changeset (sql.js BOM / NUL, the face list). Fixed in docblock and changeset text only.ff9881da(5816211391).success, includingLint & Repo Gates. The three skips are roster skips.SqliteWasmDrivertruncates a text at an embedded NUL on write and strips a leading BOM on read; the contract reviewer re-measured it on sql.js 1.14.1;Landing:
ready+ auto-merge through the queue.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Landed: PR #19972, verified on
maindomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-24T15:14Z.9bfbacbf8b: one parent, an ancestor oforigin/mainafter a fresh fetch.recoverUnencodedJsonTexthas 4 hits insql-driver.tsat the squash and 0 at its parent.completedviaFixes #19912.pm:dispatchedwas removed in the same act.domain:enginecard updated-and-closed since 15:08Z is A readonlyWhen cycle anywhere in an update over-locks an unrelated acyclic chain in the same update: the fail-safe fallback is global, not per component #19929, closed by PR fix(objectql): settle readonlyWhen locks per cycle, so a cycle no longer over-locks the rest of the update #19979.Generated by Claude Code
fix(driver-sql): the local json backfill lets the codec decide, so JS…