Skip to content

fix(driver-sql): an autonumber prefix carrying _, % or \ seeds its counter from the stored MAX on SQLite (#21163) - #21206

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21163-autonumber-like-escape
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21163-autonumber-like-escape

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #21163

Clause-②: no

What changed

SqlDriver.scanMaxNumericTail (packages/drivers/driver-sql/src/sql-driver.ts) is the one read of the highest counter already stored under an autonumber prefix. Its two callers are the cold bootstrap in getNextSequenceValue and the #5495 re-seed resyncSequenceToDataMax. It escapes the rendered prefix with escapeLikePrefix (a backslash before \, % and _). It then compiled where(field, 'like', pattern) through Knex, which declares no ESCAPE clause on any dialect.

The scan now reads:

.whereRaw('?? like ? escape ?', [field, `${this.escapeLikePrefix(prefix)}%`, LIKE_ESCAPE_CHARACTER])

LIKE_ESCAPE_CHARACTER is the driver's existing bound escape character, the one the filter compiler's LIKE ... ESCAPE ? already binds. It is bound and never written as a literal, because MySQL applies C escape syntax inside string literals. There is one escape helper (escapeLikePrefix, unchanged) and one declared escape character, the same on every dialect. The method's docblock records why. One file of production code changed. The Turso remote face's own statement (turso-driver.ts, which already declares ESCAPE with a backslash) is untouched.

Why

SQLite's LIKE has no escape character unless one is declared. Postgres and MySQL use a backslash by default. So on the SQLite faces, a prefix carrying _, % or \ matched no stored row. Measured at cb45469e with this PR's pins and the pre-fix builder restored:

format stored before the create cold create issued create after rows 2..30 land above a warm counter
SO_{0000} SO_0007 SO_0001 refused: UNIQUE constraint failed (on SO_0005, retries spent)
SO%{0000} SO%0007 SO%0001 refused, same
SO\{0000} SO\0007 SO\0001 refused, same
{region}-{0000}, region north_east north_east-0007 north_east-0001 not pinned
SO-{0000} (control) SO-0007 SO-0008 SO-0031

The {region} row shows that the trigger does not need an unusual format. The prefix is rendered, so a _ can come from a field value such as a snake_case machine name.

Pins

  • packages/drivers/driver-sql/src/sql-driver-21163-autonumber-prefix-like-escape.test.ts: one suite per dialect cell through declareDialectCell. SQLite always runs. Live Postgres and MySQL run where provisioned (Temporal Conformance (live PG + MySQL) runs this whole package with OS_EXPECT_LIVE_DIALECT_MATRIX=1) and are declared un-run otherwise. Each cell covers _, % and \ prefixes plus a plain control, each cold and on the Autonumber counter neither syncs to MAX(existing) per tenant nor re-checks on collision — warm-DB creates 409 in bursts, each failure burning a number (25 retries observed) #5495 re-seed, plus the {field}-borne _.
  • packages/drivers/driver-turso/src/turso-autonumber-prefix-like-escape.test.ts: the LOCAL face (url: ':memory:') and the REMOTE face (over makeLibsqlSqliteStub, a real SQLite behind the libsql client), each with _, % and a plain control, cold and on re-seed. The REMOTE legs passed before this fix because that statement already declared its escape. They hold both faces to one answer from the one helper.

The assertion is the issued autonumber as stored, read back through the driver's findOne by an id the test chose. On MySQL create returns the insert id rather than the row, so its return value is not a reading every cell can make. Never a SQL string.

Reverse verification (fix committed first, at 9b3f7d0a)

Mutation: node scripts/ablation-replace.mjs replaced the new whereRaw(...) with the pre-fix where(field, 'like', ...) (anchor 1 to 0, blob cb63656d to 5f8edc7a). Restore: blob equal to HEAD, git diff HEAD empty, whole-tree git status --porcelain clean.

  • driver-sql pin over src: 7 failed / 2 passed. All three escaped prefixes failed cold and on re-seed, and so did the {field} case (expected 'SO_0001' to be 'SO_0008', UNIQUE constraint failed). The two controls passed.
  • driver-sql rebuilt, and node scripts/ablation-dist-preflight.mjs @objectstack/driver-sql '?? like ? escape ?' --absent confirmed the fix was absent from all 6 built files. Then the turso pin: 4 failed / 8 passed. LOCAL _ and % failed cold and on re-seed. LOCAL control and all 6 REMOTE legs passed.
  • Restore leg: rebuilt, and preflight 'where(field, "like"' --absent confirmed the mutation was gone from dist (tree clean). Pins: driver-sql 9 passed / 2 skipped (live cells unprovisioned), turso 12 passed. The pristine dist reading (default mode) found ?? like ? escape ? present in dist/index.js and dist/index.mjs.

Live Postgres (local PG 16, initdb + pg_ctl on a scratch port, torn down after)

  • With the fix: 18 passed / 1 skipped (9 sqlite + 9 live postgres; mysql un-run).
  • With the pre-fix builder restored: 7 failed, all in the sqlite cell. 0 failed in live postgres.

So declaring the escape does not change behavior on Postgres, which already used a backslash by default. MySQL: NOT MEASURED locally because this container has no MySQL server. The same file's live mysql cell runs in Temporal Conformance (live PG + MySQL).

Verification (at HEAD 7b7fcafb)

  • Patch round 1 (5b83098b, seat edit): the live MySQL cell failed on create return values (insert id 0 on MySQL). The pin now reads the stored row. The whole driver-sql suite, CI-shaped against a local live PostgreSQL 16 and MySQL 8.0.46: 219 files, 5108 passed / 1 skipped. Reverse verification on the reworked pin: 7 failed, all in the SQLite cell, 0 in live PostgreSQL and live MySQL.

  • pnpm --filter @objectstack/driver-sql test: exit 0, 208 files passed / 11 skipped, 3433 tests passed / 192 skipped.

  • pnpm --filter @objectstack/driver-turso test: exit 0, 84 files passed, 2243 tests passed / 33 skipped.

  • typecheck on both packages: exit 0. tsc --listFilesOnly includes both new test files in each package's program.

  • pnpm check:driver-conformance: identical before the first edit (at cb45469e) and after the last commit: 50 covered cells, 0 in the DEBT ledger, 0 exempt. Dialect axis: 8 suites, 0 in the DIALECT ledger.

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) derived 63 commands. 61 exited 0. check:type-check-debt hit a 280 s per-command cap on the first run and exited 0 on a re-run (re-measure OK in 211 s, self-test OK). Two exited 3 (PREREQUISITE NOT MET, NOT MEASURED): check:dual-build-cjs-loads needs a whole-repo build, and check:lean-entry-closure needs objectql's dist, which is outside this diff's closure. For the first one, both changed packages' CJS builds were loaded directly with require and loaded OK. --ran reconciliation: 63 derived, 61 run, 2 NOT-MEASURED, 0 UNRUN.

  • Lint was narrowed to the 3 changed TS files: eslint --no-inline-config --format json gave 3 files, 0 errors, 0 warnings. Population: eslint.config.mjs lints **/*.{ts,...} outside its never-linted build dirs, and no file reported as ignored. Invariance: the config enables no type-aware linting (no parserOptions.project), so this diff cannot move the verdict on any untouched file. Repo-wide pnpm lint is CI's.

Acceptance notes

  • escapeLikeComparand (the filter compiler's module function) and escapeLikePrefix (this method) escape the identical set: a backslash before each of \, % and _. They were not merged. Triage named escapeLikePrefix as this path's one helper, and nothing here needed a merge.
  • The Turso remote face writes its escape as an SQL literal. That is correct, because it only ever talks to SQLite. Its new REMOTE legs are the guard that keeps the two faces' escape characters equal.
  • SqliteWasmDriver extends SqlDriver and inherits the scan. A one-off probe after the fix (not committed) issued SO_0008 over a stored SO_0007 on that face. That face has no permanent pin here, and its pre-fix answer was not measured.
  • Counters that an earlier release already seeded too low are not rewritten by this change. The next collision on such a counter now re-seeds it from the stored MAX, as on any other prefix.
  • Patch round 2 (f71f31b2, seat edit): the branch merged origin/main at 097ef802, a clean merge with the same 4 files in the net diff. It carries the sibling fix(driver-turso)!: the remote filter compiler refuses the JSON-column family and answers $contains by membership (#21178) #21208 (862f12c0b, driver-turso), and it re-ran the Type Check · source gates lane that had timed out at 5b83098b. Every check is green on this head.

Generated by Claude Code

claude added 2 commits October 1, 2026 15:53
…scapes for

scanMaxNumericTail escapes the rendered prefix's backslash, percent and
underscore with a backslash, then compiled `where(col, 'like', ...)`
through Knex, which declares no ESCAPE clause. SQLite has no LIKE escape
character unless one is declared, so on every SQLite face a prefix
carrying one of those characters scanned an empty partition: the cold
bootstrap seeded from 0 and the collision re-seed could not move the
counter. The scan now binds the driver's one LIKE_ESCAPE_CHARACTER on
every dialect, as the filter compiler already does.

Pins: driver-sql over the dialect cells (sqlite; live pg/mysql where
provisioned) and the driver-turso local and remote faces.

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

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql, touching 2 documentable anchor(s).

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/queries.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/permissions/tenant-audit-census.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/lifecycle.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/query-syntax.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/types.mdx (via SqlDriver (symbol, a top-level class))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-5.mdx (via SqlDriver (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 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 — 11 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 097ef802700e109aa98a89c2991041516476b6f9 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8a2e0e19f71eac31cdb820e77df8704ac699d91c — the merge of head f71f31b29b93ec50a2f3dd02fb18c3e65135c94f into base 097ef802700e109aa98a89c2991041516476b6f9, 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 8a2e0e19f71eac31cdb820e77df8704ac699d91c && git checkout 8a2e0e19f71eac31cdb820e77df8704ac699d91c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 097ef802700e109aa98a89c2991041516476b6f9 f71f31b29b93ec50a2f3dd02fb18c3e65135c94f && git checkout -B drift-repro 097ef802700e109aa98a89c2991041516476b6f9 && git merge --no-ff f71f31b29b93ec50a2f3dd02fb18c3e65135c94f

node scripts/docs-audit/affected-docs.mjs --json 097ef802700e109aa98a89c2991041516476b6f9

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 097ef802700e109aa98a89c2991041516476b6f9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI red on this PR's own pins: Temporal Conformance (live PG + MySQL) failed at head 7b7fcafbd (check run 110472592352), read at 2026-10-01T16:41Z. domain:engine#1 · session_017xfMoEjKUuSh2xYB8sCozp.

  • Signature (the job's annotations): sql-driver-21163-autonumber-prefix-like-escape.test.ts lines 130, 138 and 153 assert created.so_no and receive undefined, for every case: SO_, SO%, SO\, the {field} case north_east-0008, and the plain-prefix control SO-.
  • Why it is this PR's: the control fails the same way, so this is not the escape fix being wrong. It is the new test file's harness on a live-database cell CI runs and the dev could not run locally (the report declared MySQL as NOT MEASURED; PostgreSQL 16 was green locally). The PostgreSQL service log in the same job shows the expected re-seed collisions on uniq_os21163_like_escape_so_no, so the cell that reads undefined still needs naming from the job log.
  • Next: a patch round on this branch by its original dev, at the seat's next free subagent slot (batch 3 is full right now). This PR stays draft; nothing is armed.

Generated by Claude Code

… on every dialect cell

The live mysql cell failed every case, controls included: knex does not
support `.returning()` on MySQL, so `SqlDriver.create` hands back the
insert id (measured `0` on a live MySQL 8.0.46) instead of the row, and
the pin asserted `create`'s return value. The row itself was stored with
the right number. The pin now creates with an id it chose and reads the
autonumber back through the driver's `findOne`, a reading every cell can
make.

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

TypeScript Type Check red at head 5b83098b9: a lane time-out, not a type error. Read at 2026-10-01T17:15Z. domain:engine#1 · session_017xfMoEjKUuSh2xYB8sCozp.

  • What failed: the aggregator reports type-check lane typecheck-source-gates concluded cancelled. That lane (check run 110484970506) was cancelled at its timeout-minutes: 10 (lint.yml), mid-step in a census, with no error line. The other three lanes are green on this head.
  • Why it is not this head's code:
    • The same lane passed on the previous head 7b7fcafbd in about 7 minutes, and on main it runs in 3 to 4.
    • Between the two heads the diff changes one driver-sql test file, which no source gate judges.
  • What this seat can do: it has no job re-run op (only relay writes). The lane re-runs on the next commit: the patch round's next push, or a main merge through update-branch if no push comes. The patch-round verdict that matters, Temporal Conformance (live PG + MySQL), is still running on this head. This PR stays draft.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f71f31b29b93ec50a2f3dd02fb18c3e65135c94f
Local-runs: none

Inputs: card #21163 (body and all five comments, rulings and the three os-dev-reports 5935918486, 5936913914, 5937340497 included), PR #21206 (body, file list, net diff against main), and the check-runs on the head. The head is a merge of 5b83098b and origin/main 097ef802; the net diff is 4 files, +344 / -3, and git diff --stat 097ef802 f71f31b2 read locally agrees. At the head sql-driver.ts holds exactly one ?? like ? escape ? and zero copies of the pre-fix where(field, 'like'. Everything below was read at origin/main with git show / git grep; nothing was built, run or re-run.

① Derived judgments

1. One scan, one helper, one bound escape — right. scanMaxNumericTail is the only prefix LIKE read of a counter's partition in driver-sql at origin/main (callers: the cold bootstrap in getNextSequenceValue at :7703 and resyncSequenceToDataMax at :7922; driver-sqlite-wasm overrides none of it). It now compiles whereRaw('?? like ? escape ?', [field, escapeLikePrefix(prefix) + '%', LIKE_ESCAPE_CHARACTER]). LIKE_ESCAPE_CHARACTER is the file's existing module constant (:3322, one backslash), already bound by the filter compiler at :3602 and :3800 in the same ?? … ? ESCAPE ? shape, so no import and no new constant; escapeLikePrefix is unchanged (prefix.replace(/([\\%_])/g, '\\$1'), the same set escapeLikeComparand escapes). No per-dialect branch, no second copy. The remote driver-turso statement (scanRemoteMaxCounter, turso-driver.ts :2529) is outside the diff and consistent with it: the same escapeLikePrefix anchor under a literal ESCAPE '\', sent only to a SQLite engine, the same character the bound one spells. scanMaxNumericTail's and escapeLikePrefix's signatures are unchanged (protected), so the subclass-visible surface moves on behaviour only; the escapeLikePrefix docblock now states the declaration every statement using the anchor owes.

2. The answer changes only on the SQLite faces — right. SQLite's LIKE has no escape character unless one is declared, so before the fix the helper's backslashes were literal pattern characters there and a prefix carrying _, % or \ scanned an empty partition; PostgreSQL and MySQL already read a backslash as the default escape, so binding that same character declares what they did. Measured by the dev with the pre-fix builder restored under the reworked pin (report 5936913914): 7 failed, all in the sqlite cell, 0 in live PostgreSQL 16 and 0 in live MySQL 8.0.46; with the fix, 27 of 27 across the three cells. Binding rather than writing a literal is the right spelling for a statement that runs on three dialects: MySQL applies C escape syntax inside string literals (the reason the filter compiler gives at :3598), and a bound ? has one spelling everywhere. Judged right; the backslash-prefix case passing on MySQL too shows the bound character equals the default one there.

3. The pins prove what they claim — right.

  • driver-sql pin: _, % and \ prefixes plus the plain SO- control, each COLD (a bypass row …0007, then the first create must issue …0008) and on the Autonumber counter neither syncs to MAX(existing) per tenant nor re-checks on collision — warm-DB creates 409 in bursts, each failure burning a number (25 retries observed) #5495 RE-SEED (warm …0001 on an empty table, bypass rows 2..30, then the create must issue …0031); plus {region}-{0000} with north_east, the _ arriving from data through {field} rendering. declareDialectCell runs the sqlite cell always and each live cell where provisioned, declaring it un-run otherwise. The reset drops the data table and deletes the object's rows from _objectstack_sequences by its object column (:7338), so every test starts cold; on a persistent live database the warm …0001 expectation would fail if it did not, and both live cells passed. The MySQL cell's reading is right: issue() creates with an id the test chose and reads so_no back through driver.findOne, the same reading on every cell, because create answers formatOutput(object, result[0]) from .insert().returning('*') (:7187) and on MySQL Knex ignores returning and hands back the insert id, not the row. Reverse verification on sqlite: 7 failed (every escaped case cold and re-seed, and the {field} case) / 2 passed (the controls) with the pre-fix builder; 9 passed / 2 skipped with the fix.
  • driver-turso pin: the LOCAL face (url: ':memory:') and the REMOTE face (makeLibsqlSqliteStub, a real SQLite behind the libsql client, so the LIKE is evaluated, not string-asserted), each with _, % and the control, cold and re-seed. It asserts create's return value, which IS the row on both SQLite faces. Ablation: LOCAL _ and % red cold and re-seed (4), the LOCAL control and all 6 REMOTE legs green, exactly as predicted; the REMOTE legs now hold the two faces' escape characters equal. The turso pin omits the \ case; the driver-sql pin carries it on the same inherited scan, and the card's scope named SO_ and the control, so this is not a gap.

4. Gate verdicts on the head (the check-runs API, read 2026-10-01T18:06:21Z; an earlier reading at 2026-10-01T18:00:45Z had Lint & Repo Gates and Test Core shards 1, 5 and 6 still in progress, and one at 2026-10-01T18:05:38Z only shard 5): the seven required contexts all read success — Lint & Repo Gates, TypeScript Type Check, Test Core (all six shards and the aggregate), Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Temporal Conformance is the one that runs this pin's live PostgreSQL and MySQL cells (ci.yml runs the whole driver-sql package under the workflow's live-matrix setting). Every other check reads success except three skipped optional or path-filtered jobs (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)). The two reds in this PR's history (the live MySQL cell at 7b7fcafb, the cancelled type-check lane at 5b83098b) were diagnosed in PR comments 5936029864 and 5936638100 and are not on this head. Record rendered 2026-10-01T18:08Z.

② Semver level

  • .changeset/21163-autonumber-like-escape.md: '@objectstack/driver-sql': patch, body carrying Clause-②: no. Right. The only published change is a behavioural fix inside driver-sql's dist (files: dist, README.md, CHANGELOG.md): no export, signature, authorable key or accepted-input set moves, so no widening or narrowing arm applies and patch is the floor and the ceiling. driver-turso gains a test file only (its files is dist-only; no source changed), so it owes no entry — and it moves with the fixed group anyway. No skip-changeset label (the PR's labels are the bots' documentation, size/m, tests, tooling); Check Changeset reads success.
  • The PR body's Clause-②: no stands at line start (line 3), equal to the dispatch's and the changeset's. Right.
  • The changeset prose is what an upgrading reader needs: names every SQLite face (better-sqlite3, Turso local and embedded replica, the WebAssembly driver by inheritance), the two scans, the data-borne {field} case, that PostgreSQL and MySQL do not change, and that counters already seeded too low are re-seeded on their next collision rather than rewritten. No migration is owed (nothing removed or renamed). No model identifier anywhere in the diff.

③ Boundary flags

  • open_questions: empty in all three reports. Nothing to answer.
  • Dev deviations, each answered. Round 1: (1) ablation leg logs first landed in / and were moved; the mutation and restore evidence is the tools' own stdout. (2) A temporary wasm probe test was written and deleted in one command, never committed, tree clean. (3) PG data dir under /tmp, removed. (4) origin/main not merged — superseded by round 2's merge. (5) Model-free trailer pair per AGENTS.md rather than the harness reminder's spelling — verified at the head (Claude-Session URL plus Co-authored-by: Claude); AGENTS.md is the instruction of record. Round 2: (1) a throwaway MySQL 8.0.46 from extracted debs, stopped by its recorded PID and removed; (2) PG again, removed; (3) dispatch-gates derivation flagged STALE against origin/main — the verdict is CI on the merge ref, green above; (4) another agent's mariadbd left untouched, the correct act. Round 3 (the merge): clean, no os-regen deferral, same 4 files. None bears on the diff.
  • out_of_scope_findings[0] (report 5936913914), the candidate class (b) finding: SqlDriver.create on MySQL returns Knex's insert id (0 measured) rather than the inserted record IDataDriver.create declares, and createWithAutonumberResync in the engine returns that value; reach at a public door NOT measured. It does not bear on this PR's correctness: scanMaxNumericTail never reads create's return value, the fix's MySQL behaviour was measured on the stored row, and the pin reads through findOne. The mechanism reading at origin/main supports the candidate (.returning('*') then result[0] at :7187–7188, and Knex ignores returning on mysql), so it is plausible beyond this PR. Filing is left to the seat: the public-door measurement (a create on a MySQL datasource through the data route) is what decides whether it meets the filing bar; it is not filed here and not buried.
  • Acceptance notes, answered: (a) escapeLikeComparand and escapeLikePrefix escape the identical set and were not merged — pre-existing, outside this card, the right place for it; (b) the remote face's literal ESCAPE '\' — SQLite-only engine, consistent, now guarded by the REMOTE legs; (c) SqliteWasmDriver inherits the scan (no override at origin/main), has no permanent pin on this card and its pre-fix answer was not measured — acceptable, the changeset names the face and the inherited scan is the one pinned; (d) counters seeded too low are not rewritten — that is the forward-only re-seed's contract, stated in the changeset.
  • PR body line for the seat to edit (the dev writes the body once; the seat already applied the two round-1 edits): the last acceptance note, "The branch has not merged main. The one commit that landed since cb45469e (0d421041 …)", is now false — the head IS a merge of origin/main 097ef802 (merge commit f71f31b2, clean, same 4 files). One replacement line naming the merge is enough. A body matter, not a diff matter; it does not move the verdict.
  • Governance and landing shape: none of the 4 paths is a governed surface; head repo equals base repo; 347 changed lines, under the 5,000 human-merge line; Governed Surface Queue Guard success. The ordinary queue path applies once the seat readies the draft. Escalations: none.

Implemented-by: claude/issue-21163-autonumber-like-escape
Reviewed-by: session_017xfMoEjKUuSh2xYB8sCozp

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 1, 2026 18:12
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 1, 2026 18:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit c6b6889 Oct 1, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21163-autonumber-like-escape branch October 1, 2026 18:39
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/m tests tooling

Projects

None yet

2 participants