Skip to content

fix(driver-sqlite-wasm)!: a file wasm SQLite cannot read is refused at connect, naming the file, instead of being renamed and replaced with an empty database - #25200

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-25g3fn
Oct 22, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-25g3fn

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

Fixes #25019

Clause-②: no (narrowing)
Metadata-protocol: none

Narrowing face: a connect that resolved over a file wasm SQLite cannot read now rejects, naming the file. WasmSqliteConnection.open() no longer renames such a file to FILE.corrupt-TIMESTAMP and starts an empty database in its place; resolveSqliteDriver re-throws the refusal at the wasm rung instead of trying the in-memory driver. Nothing exported changes shape. It owes a CONTRACT_REVIEW_TIER review before the queue (the claim's line).

Member 3 of the SQLite file-failure family (the wasm quarantine), the card's last member, under the domain:engine seat 2's ruling 6311031589 (route A, inherited from #24960's route A). Members 1 and 2 landed in PR #25080 (d6a5fa3ba, Part of #25019). Dev session session_015pbbff7SHh7EEEWiasBdDe (a project card thread of seat session session_01FaVyM6YvQLg9CsjPVCYc5K), claim 6335885549; the cross-lane line in service-datasource is declared on #21118 (6335909713).

What changed

  • packages/drivers/driver-sqlite-wasm/src/wasm-connection.ts
    • open(): a file whose bytes sql.js cannot open or read (new SQL.Database(bytes) or PRAGMA quick_check(1) throws, or quick_check does not answer ok) is refused with unreadableDatabaseFileRefusal: an Error carrying code (SQLITE_NOTADB when SQLite says "file is not a database", SQLITE_CORRUPT for every other read failure), filename, cause (sql.js's own throw), and a message that says how to move the file aside. sql.js throws a bare Error with SQLite's message and no result code, so the producer maps SQLite's own errstr for SQLITE_NOTADB; nothing downstream reads message text.
    • quarantineCorruptFile retires (it was private). The refused file is left byte-for-byte as found and nothing is written beside it.
    • quarantineOrphanedWal now runs only after the image is accepted, so a stale write-ahead log beside a refused file stays where it is too.
  • packages/services/service-datasource/src/sqlite-driver-fallback.ts (the one cross-lane file): the wasm rung's catch re-throws when isSqliteFileOpenFailure(err) holds, exactly as the native rung does. The predicate needed no new code: the wasm refusal reaches it as SqlDriver's own SqliteDatabaseFileError with SQLITE_NOTADB / SQLITE_CORRUPT (below). Every other wasm failure, sql.js itself not loading first among them, still steps down to the in-memory driver. Module note and predicate doc updated.
  • packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-durability.test.ts: the self-heal case is now a refusal pin (ruling item 3).
  • Pins: packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-25019-unreadable-file-refused.test.ts (relative src import) and packages/services/service-datasource/src/__tests__/sqlite-driver-fallback-wasm-unreadable-file.test.ts (resolves @objectstack/driver-sqlite-wasm through dist/).
  • Changeset .changeset/25019-wasm-unreadable-file-refused.md: @objectstack/driver-sqlite-wasm and @objectstack/service-datasource patch, Clause-②: no (narrowing), Metadata-protocol: none, BREAKING banner (FROM, TO, Unchanged, fix) and ADR-0087 disposition not-required (no-migration-prescription).

One vocabulary with the native driver, measured

SqliteWasmDriver is a SqlDriver, and wasm SQLite's connection is opened by knex's pool on the first statement SqlDriver.connect() runs (PRAGMA auto_vacuum). The pool propagates the create error (propagateCreateError: true), so applyAutoVacuumIncremental sees the refusal's code and answers it through the same private SQLITE_FILE_REFUSALS table the native driver uses, wrapping it in SqliteDatabaseFileError. So a wasm refusal carries the native driver's class, code and words ("is not a SQLite database, and was left as it is" / "is damaged (SQLite cannot read its header or its schema), and was left as it is"), with no second phrase table and no edit to driver-sql. The prescription is wasm's own cause text, since the rename it replaces was wasm's: the native refusal carries none (the dispatch's assumption that the native family has a move-aside prescription to import did not hold; measured by grep, zero hits in driver-sql/src).

The line an operator sees (public door below):

datasource 'archive': connect failed — SQLite database file './data/archive.db' (resolved: '…/door/data/archive.db') is not a SQLite database, and was left as it is: wasm SQLite cannot read it (file is not a database). To start from an empty database, move the file aside yourself (mv './data/archive.db' './data/archive.db.unreadable') and start again; to keep its data, restore a copy that opens.

Premise (base f7d740376)

quarantineCorruptFile was at wasm-connection.ts, called from open() on a read failure, renaming to FILE.corrupt- plus Date.now(); the wasm rung of resolveSqliteDriver was a bare catch stepping down to memory. Both doors were measured by the dev report 6310958104 (open question 1): (a) a declared sqlite datasource whose native addon cannot load, over a plain-text or header-truncated file, renamed on a development boot; (b) a datasource declared driver: 'sqlite-wasm' over a plain-text file, renamed and available in development and production. sql.js 1.14 answers, measured: plain text and magic-header junk throw "file is not a database" at quick_check; a real database truncated to 50 bytes or to half throws "database disk image is malformed"; a real database and an empty file answer ok.

Tests

  • New pins: driver-sqlite-wasm 11/11 across the two files; service-datasource 10/10: (a) the native-addon-cannot-load step-down (a real ERR_DLOPEN_FAILED at the fix(cli,service-datasource): under bin/run-dev.js (NODE_ENV=development) an unopenable SQLite datasource steps down to an in-memory engine, so a dev-mode os migrate audit-mail-bodies reports an unreachable archive as examined and clean #24950 suite's seam) over a plain-text and a header-truncated file rejects with the driver's refusal, no wasm or memory warning, file bytes unchanged, directory holds the file alone; the same through DatasourceConnectionService in development reads failed, no driver registered; (b) driver: 'sqlite-wasm' over both files in development and production reads failed with the code's words naming the file, not available, nothing moved; (c) controls: a real database opens and answers (wasm, and sqlite-wasm declared, both modes available), an absent file is created, an empty file opens, a native addon that cannot load over an absent file still steps down to wasm.
  • Whole packages (pre-merge head 73dc5b1a, under the verify lock): driver-sqlite-wasm "Test Files 39 passed (39) · Tests 703 passed (703)"; service-datasource "Test Files 55 passed (55) · Tests 955 passed (955)"; typecheck for both, exit 0.
  • Ablation (committed fix at f8ffdfc0, scripts/ablation-replace.mjs): the refusal's throw replaced by the old quarantine (rename to .corrupt-, empty database, plus a marker), blob 07f7cb3e0fa3 to 15743567f704; rebuilt; ablation-dist-preflight marker present in 2 built files. Predicted and observed: driver-sqlite-wasm "6 failed | 5 passed" (the 5 refusal pins and the durability refusal case red; 3 controls and 2 durability cases green); service-datasource "7 failed | 3 passed" (all of (a) and (b) red, the 3 controls green). Restore: blob back to 07f7cb3e0fa3 == HEAD, git diff HEAD empty, rebuilt, preflight --absent in 0 of 6 built files, tree clean; both pins green again (11/11, 10/10).

Public door, measured

bin/run-dev.js migrate apply --yes (tsx, built closure), a project declaring archive with driver: 'sqlite-wasm', config.filename: './data/archive.db', one object bound, over a 25-byte plain-text file:

  • exit 1; the line above, from ✗ Plugin plugin.app.door_25019 failed to start; md5 bcbdbe7ff2cba060ed588d3fca48b30c before and after; data/ holds archive.db alone.
  • Following the prescription (mv './data/archive.db' './data/archive.db.unreadable') and running the same command again: exit 0, datasource 'archive': connected (driver=sqlite-wasm, schemaMode=managed), the moved file's md5 unchanged.

Gates and battery

  • Derived set: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at e39b18e5 derived 68; each run once with its exit captured to a file before any pipe. 65 exit 0 on the first pass. Three did not:
    • node scripts/check-plugin-teardown-shape.mjs --self-test exit 3: this shallow clone lacked its pinned fixture commit; fetched it, re-run exit 0 (48 cases).
    • pnpm check:dispatcher-error-vocabulary exit 1: its objlit pattern matched the refusal's code TYPE (code: 'SQLITE_NOTADB' | 'SQLITE_CORRUPT' in a type literal). The gate's own self-test declares a type annotation is not a stamp and passes a named type, so the type was named (UnreadableDatabaseFileCode, cda55e654); re-run exit 0 ("56 unregistered code-stamping site(s), all classified"). Disclosed below.
    • pnpm check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET (eight packages outside this closure have no dist/): NOT MEASURED locally, CI's Lint & Repo Gates runs it.
  • --ran at cda55e654: "67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED".
  • check:driver-conformance before (f7d740376) and after: "55 covered cell(s), 0 in the DEBT ledger, 0 exempt" both; dialect axis unchanged.
  • check-platform-checklist not owed (no exported symbol renamed or removed; quarantineCorruptFile was private). test:repo and the CLI integration layer not run locally (no such file touched); CI owns them.
  • Full dogfood battery at e39b18e5 (pnpm install --frozen-lockfile, the @objectstack/dogfood^... closure build, pnpm --filter @objectstack/dogfood test, no shard, no path): lock VERDICT command-exit 1, "Test Files 1 failed | 286 passed | 3 skipped (290) · Tests 2395 passed | 21 skipped (2416)". The one failure is a suite-level beforeAll timeout (10 s, bootShowcase()) in test/showcase-single-platform-insert-default-organization.dogfood.test.ts, before any of its 3 tests. Re-run alone it times out the same way, and the CONTROL (the same file with wasm-connection.ts restored to base f7d740376 and rebuilt, quarantineCorruptFile present in dist/) times out identically, so it is not this change: the showcase boots a fresh wasm database (no file to refuse) and runs out of hook time on this shared box. Tree restored and rebuilt after the control (dist/index.mjs md5 back to 11790b18…). CI's full battery is the reading of record.
  • The only commit after the battery is cda55e654 (a type alias): rebuilt dist/index.mjs and dist/index.js are byte-identical before and after it (md5 11790b18…, f3177051…).

Acceptance notes

  • Level patch, not the ruling's minor. The ruling's item 4 names minor; the level now follows the metadata-protocol kind alone (AGENTS.md Post-Task Checklist step 3; check-changeset-no-major.mjs, maintainer ruling A on changeset levels: minor only when the metadata protocol changes (added / narrowed / semantic), patch otherwise; release notes grouped by what the reader must do (maintainer ruling A, 2026-10-21) #25105), and nothing a metadata author writes changes here, so the kind is none, which the gate grades patch and refuses at minor. BREAKING-ness is carried by the Clause-② arm, the banner and the ADR-0087 disposition, as 24837-managed-datasource-sync-refused.md does. The seat may re-grade.
  • The ruling's named cost: a development server whose own wasm image is damaged no longer starts itself clean; the refusal's prescription says how to move the file aside, and the public door above shows that step working.
  • knex logs Acquire connection error: with the refusal's stack at warn once per acquire attempt (its own logger, knex/lib/client.js). The native refusal never fails an acquire, so it does not print this; every other dialect's acquire failure (a refused PG connection) does. Noted, not filed.
  • Named only, per family note 6305864303: the development telemetry sibling (packages/cli/src/utils/telemetry-datasource.ts) still catches every throw, so a damaged telemetry sibling is now neither renamed nor reported. Pre-existing best-effort shape, out of the claim.
  • A SQLite datasource whose connect() is refused keeps its better-sqlite3 handle open for the life of the process: the connection service never disconnects a rejected handle, and SqlDriver.connect() keeps its pool #25084 (the refused-connect handle) is not addressed here; a wasm refusal holds no connection (the open failed before the pool kept one).
  • check:dispatcher-error-vocabulary: the refusal stamps SQLite's own result-code names (SQLITE_NOTADB / SQLITE_CORRUPT) through a ternary assignment, a position outside that gate's declared population, as driver-sql carries the same names (copied from better-sqlite3's error, and as SQLITE_FILE_REFUSALS keys). They are foreign vocabulary, not ADR-0112 codes. A foreign-vocabulary row in packages/runtime/src/dispatcher-error-vocabulary.ts would make that explicit; it is outside this claim's file surface, so it is left to the seat.
  • Every read failure other than SQLite's "file is not a database" is classified SQLITE_CORRUPT: the bytes were read off disk and sql.js could not read them as a database, which is the file's verdict. An operating-system failure reading the file (EACCES) is not this path and is unchanged.

Generated by Claude Code

…instead of renaming it

WasmSqliteConnection.open() throws SQLITE_NOTADB / SQLITE_CORRUPT naming the
file, with a prescription to move it aside; quarantineCorruptFile retires.
resolveSqliteDriver re-throws a wasm file refusal instead of stepping down to
the in-memory rung.

Claude-Session: https://claude.ai/code/session_015pbbff7SHh7EEEWiasBdDe
Co-authored-by: Claude <noreply@anthropic.com>
…iod the connect line adds

Claude-Session: https://claude.ai/code/session_015pbbff7SHh7EEEWiasBdDe
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

9 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

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

Which tree this was computed on

This run read content/docs from 76f84229cf98a12f14114524e2f15aaa1c08eeeb — the merge of head cda55e65473673ae0db2ebae9b3aac7143384404 into base 2919ddb875cd0daa38ef452d741bcba04cf366af, which is what actions/checkout gives a pull_request run. Not the PR head.

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

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 76f84229cf98a12f14114524e2f15aaa1c08eeeb && git checkout 76f84229cf98a12f14114524e2f15aaa1c08eeeb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2919ddb875cd0daa38ef452d741bcba04cf366af cda55e65473673ae0db2ebae9b3aac7143384404 && git checkout -B drift-repro 2919ddb875cd0daa38ef452d741bcba04cf366af && git merge --no-ff cda55e65473673ae0db2ebae9b3aac7143384404

node scripts/docs-audit/affected-docs.mjs --json 2919ddb875cd0daa38ef452d741bcba04cf366af

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: cda55e65473673ae0db2ebae9b3aac7143384404
Local-runs: none
Scope: packages/drivers/driver-sqlite-wasm/src/wasm-connection.ts, packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-durability.test.ts, packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-25019-unreadable-file-refused.test.ts, packages/services/service-datasource/src/sqlite-driver-fallback.ts, packages/services/service-datasource/src/tests/sqlite-driver-fallback-wasm-unreadable-file.test.ts + packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver.ts (isSqlite, toKnexConfig, connect, disconnect), packages/drivers/driver-sqlite-wasm/src/knex-wasm-dialect.ts (acquireRawConnection, destroyRawConnection), packages/drivers/driver-sqlite-wasm/src/index.ts, packages/drivers/driver-sql/src/sql-driver.ts (connect, applyAutoVacuumIncremental, SQLITE_FILE_REFUSALS, releaseRefusedSqliteConnection, isSqlite, clientSpelling, sqliteFilename), packages/drivers/driver-sql/src/sqlite-database-file.ts (SqliteDatabaseFileError), scripts/check-changeset-no-major.mjs (the LEVEL axis), scripts/check-adr-0087-registration.mjs (CATEGORIES), scripts/check-dispatcher-error-vocabulary.mjs (SHAPES), packages/runtime/src/dispatcher-error-vocabulary.ts, .changeset/24837-managed-datasource-sync-refused.md; the changeset .changeset/25019-wasm-unreadable-file-refused.md is judged under ②.

Face: Clause-②: no (narrowing), line-initial on the PR body and in the changeset; no published schema and no governed path in the committed file list (Governed Surface Queue Guard success). Inputs: card #25019's body and all nine comments (the binding ruling 6311031589, route A, member 3; the dev report 6339572437), PR #25200's body, file list and net diff against merge-base 14e7239e7 (6 files, +527/-63), and the head's check-runs. Nothing was built, run or re-run.

① Derived judgments

Ruling 6311031589, item by item:

  1. WasmSqliteConnection.open() refuses a file sql.js cannot read, in every mode, naming the file, with SQLITE_NOTADB / SQLITE_CORRUPT; nothing renamed, no empty database in its place; quarantineCorruptFile retires — delivered. The catch around new SQL.Database(bytes) plus assertReadable now closes the half-opened handle and throws unreadableDatabaseFileRefusal(this.filename, err): an Error carrying code, filename and cause. open() reads no mode, so the refusal is unconditional. quarantineCorruptFile is deleted; it was private, and index.ts is untouched, so nothing exported is renamed or replaced. The orphaned-WAL parking moved below the accept, so a refused file keeps its sidecar as well (pinned). Right.
  2. resolveSqliteDriver re-throws a wasm file refusal instead of stepping down to the in-memory rung — delivered, with the two lines the native rung already uses: closeQuietly(wasmDriver) then if (isSqliteFileOpenFailure(err)) throw err. The predicate is unchanged (E*, SQLITE_CANTOPEN, SQLITE_NOTADB, SQLITE_CORRUPT; no message text). closeQuietly writes nothing: the connection whose open() threw never entered the pool, so destroyRawConnection → close() → flush() never runs on it, and SqlDriver.connect()'s own catch had already run releaseRefusedSqliteConnection (destroy and re-initialise, never throws). Every other wasm failure still reaches the in-memory rung, the throw being guarded. Right.
  3. The durability self-heal case becomes a refusal pin — delivered: it asserts class, code, filename, the prescription, readdirSync(dir) equal to ['corrupt.db'], the bytes equal to the junk written, and no "Quarantined" warning. Right.
  4. Clause-②: no (narrowing), BREAKING — delivered on the PR body and in the changeset, with the BREAKING banner. The minor half is judged in ②.
  5. No option, no knob, no leaving it, no header sniff in the consumer — delivered: SqliteWasmDriverConfig and sqlite-wasm-driver.ts are untouched (no option), sqlite-driver-fallback.ts reads only err.code (no bytes), and open() keeps no quarantine branch. Right.

The one-vocabulary claim ("the native driver's class, code and words, with no driver-sql edit") holds in the cited code: SqliteWasmDriver overrides isSqlite to true (its class-valued client would otherwise spell as ''), so SqlDriver.connect() runs applyAutoVacuumIncremental; its PRAGMA auto_vacuum is the first acquire, Client_WasmSqlite.acquireRawConnection awaits conn.open(), knex's pool propagates the create error, and the catch maps code through SQLITE_FILE_REFUSALS into new SqliteDatabaseFileError(filename, words, e) — filename from sqliteFilename() (the declared connection.filename), the wasm refusal as cause, its message the tail after the colon. That is the operator line the PR body quotes, and both pins assert name === 'SqliteDatabaseFileError' through connect(). Right.

The code mapping: sql.js throws a bare Error carrying SQLite's errstr and no result code, so the producer maps the one text 'file is not a database' to SQLITE_NOTADB and every other read failure (a malformed image, a quick_check that does not answer ok) to SQLITE_CORRUPT; the only downstream reader is code. Should a later sql.js re-word that errstr, the code degrades to SQLITE_CORRUPT, which the predicate and the table still refuse — a less specific word, never a resolved connect. Right, and in the right place: producer side, one site.

Unchanged behaviour, read in the code and pinned:

  • an absent file: the ENOENT branch above the hunk is untouched, new SQL.Database(); pinned by "an absent file is created" and by the step-down control (native addon cannot load, absent file, still wasm with NATIVE_SQLITE_WASM_FALLBACK_WARNING);
  • a real database: bytes are read before the hunk in both versions; sql.js opens the in-memory image; quick_check answers ok; the WAL sidecar is parked exactly as before, after the accept instead of before it (the loaded image never read the sidecar); this.db = candidate. Pinned at the wasm layer and through DatasourceConnectionService in development and production;
  • an empty file: a zero-length Uint8Array is truthy, sql.js opens it as an empty database; pinned;
  • a refused file prints neither step-down line: the wasm warning is emitted only when wasmOk; pinned.

Public-surface changes the diff implies: WasmSqliteConnection.open() and SqliteWasmDriver.connect() reject where they resolved (the narrowing); resolveSqliteDriver rejects at the wasm rung for the predicate's codes where it resolved to memory (its second half); DatasourceConnectionService reads failed where it read available. No export is added, removed or re-typed: UnreadableDatabaseFileCode and UnreadableDatabaseFileRefusal are module-private. The refusal prose carries no tracker number. Right.

Rejection pins assert code, filename, the message, the directory listing and the file bytes at both layers; a control stands for every "Unchanged" line of the changeset. The ablation (quarantine restored, 6 + 7 red, controls green) is the dev's own reading and was not re-run here.

② Semver level

Both packages are published (no private field in either manifest), both carry patch, and Metadata-protocol: none sits beside Clause-②: no (narrowing) on the PR body and in the changeset. patch is right.

③ Boundary flags

Dev report 6339572437, open_questions:

  1. Changeset level. Answered in ②: patch with Metadata-protocol: none is right under the maintainer's ruling A; the seat ruling's minor is superseded on the level only. Not escalated.
  2. A foreign-vocabulary row for SQLITE_NOTADB / SQLITE_CORRUPT in packages/runtime/src/dispatcher-error-vocabulary.ts. Unneeded for this PR, and wrong as a row. The gate's SHAPES derive the assign position only as assign (.code = 'X', a quoted literal right after the =) and assignconst (.code = CONST); the ternary refusal.code = reason === SQLITE_NOTADB_ERRSTR ? 'SQLITE_NOTADB' : 'SQLITE_CORRUPT' matches neither, so no site is derived — exactly as SqliteDatabaseFileError's own ternary (this.code = typeof code === 'string' ? code : undefined) and the SQLITE_FILE_REFUSALS map keys derive none in driver-sql. The gate reconciles rows against derived sites in both directions ("a table row whose site is gone fails"), so a row for a site the scan does not see would red the gate rather than green it. Lint · docs & code guards, the lane that runs pnpm check:dispatcher-error-vocabulary, is success on this head. Whether the gate should learn a ternary at the assign position is a card on the gate's own population (its header already records that position as unclosed), neither this PR's nor a ledger row's; the wasm names share the status the native copies have had since PR fix(driver-sql): a SQLite file that is not a database fails connect() at once, naming it, and a dev boot no longer renames and replaces it #25006. Not required for correctness; no separate card owed by this card.

Dev flags in the PR's acceptance notes:

  • knex's Acquire connection error: warn with the refusal's stack on each acquire attempt: knex's own logger on a failed pool create, the same for every dialect's refused connection; not a defect of this repo. Noted correctly, nothing to file.
  • The development telemetry sibling's catch-all (packages/cli/src/utils/telemetry-datasource.ts): named per family note 6305864303 and excluded by the claim. Right.
  • A SQLite datasource whose connect() is refused keeps its better-sqlite3 handle open for the life of the process: the connection service never disconnects a rejected handle, and SqlDriver.connect() keeps its pool #25084: a wasm refusal holds no pooled connection; releaseRefusedSqliteConnection still runs on the catch and never throws. Right, nothing to add.
  • Every non-NOTADB read failure classified SQLITE_CORRUPT: judged right in ①.
  • "An operating-system failure reading the file (EACCES) is not this path and is unchanged": true. By code reading only, NOT MEASURED: wasm open() re-throws a non-ENOENT readFile error raw, applyAutoVacuumIncremental has no refusal row for E* codes, so the wasm driver's connect() logs Failed to set PRAGMA auto_vacuum=INCREMENTAL and resolves, and an explicit sqlite-wasm datasource over a file the process may not read would still read available; on the step-down the native rung refuses such a file before wasm sees it. Pre-existing and outside the ruling's five items; handed to the seat to card or dismiss, not a hold on this PR.

Dev deviations, each read:

  • Branch claude/project-thread-25g3fn rather than claude/issue-25019-*: the claim 6335885549 names that branch, and "The card this PR closes must claim this branch" is success. Fine.
  • main moved seven commits past the merge at e39b18e5 and was not merged in: the after-PR merge rule (AGENTS.md §10) says exactly that; the one driver-sql commit named is a different region of sql-driver.ts; the queue rebuilds on current main. Fine.
  • Dogfood battery at e39b18e5, one commit behind this head: the only later commit, cda55e654, names a type alias, and the dev reports the rebuilt dist/index.mjs and dist/index.js byte-identical by md5. Accepted as pinned by equivalence. Its one red, a 10 s beforeAll timeout in showcase-single-platform-insert-default-organization.dogfood.test.ts, reproduced on a control with wasm-connection.ts at base, so it is the box and not the change; Dogfood Regression Gate on this head is success; CI's full battery on main is the reading of record. The lane seat re-reads this at ACCEPT.
  • check:dual-build-cjs-loads NOT MEASURED locally: Lint · repo & tooling gates is success on this head.
  • No review request written, the attribution line linking the card thread, model-free trailers: outside this review's face.

Check-runs on this head as read 2026-10-22T11:34Z: 27 success, 7 skipped, 6 in_progress — the six Test Core (n/6) shards. Lint & Repo Gates, TypeScript Type Check, Build Core, Governed Surface Queue Guard, Check Changeset and Dogfood Regression Gate all success; every run's head_sha is this head. An in_progress shard is a reading, not a pass: the queue's own condition (this record PASS and every check green) is the seat's to re-read before arming; this verdict covers ①②③ only.

Implemented-by: claude/project-thread-25g3fn
Reviewed-by: session_01FaVyM6YvQLg9CsjPVCYc5K

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:engine seat 2 (seat post #20966) · os-bill · session_01FaVyM6YvQLg9CsjPVCYc5K · 2026-10-22T11:39Z · head cda55e65473673ae0db2ebae9b3aac7143384404 · card #25019 member 3, seat ruling 6311031589 (route A).

  1. Form: draft, Fixes #25019, assignee os-bill, 6 files (+527 / −63). check-governed-merges --pr 25200: 0 of 6 paths governed, 590 changed lines ≤ 3000 ⇒ ordinary queue landing, no human-merge tier.
  2. Premise measured: the dev report 6339572437 measured both doors the ruling names (the native-addon-cannot-load step-down and an explicit sqlite-wasm datasource, development and production), with controls and an ablation; the contract review read the same in code.
  3. Gates: contract review at CONTRACT_REVIEW_TIER PASS on this head, comment 6339798606 (Clause-②: no (narrowing) face). Check-runs on this head at this write: 29 success, 7 skipped, 4 in progress (Test Core (2/6), (3/6), (4/6), (5/6)). Landing waits for every check green on this head; it is re-read at the flip.
  4. Accepted deviations:
  5. Out of scope, disposed:
  6. Landing: once every check on this head is green, the seat flips ready and arms auto-merge through the relay; the card closes on merge by Fixes #25019, and the landing comment records the queue branch and origin/main readings.

Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants