Skip to content

fix(driver-sql): refusals, drift reports and log lines state each decision in words instead of a tracker number (stage 1) - #20795

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20513-stage1-driver-sql
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20513-stage1-driver-sql

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20513
Clause-②: no

Stage 1 of 5 of this lane (driver-sql), under the maintainer's A / A ruling on the card. The card stays open for stages 2-5, so this PR carries no closing keyword. Text only: no error code, field name, HTTP status, export or control flow moves. Every changed line in the four source files is a string-literal line.

What this does

The SQL driver's refusals, drift reports and log lines sent the reader to a tracker number for the reason behind them. Each rewritten string now says that reason in words (form D, as the migration-entry rewrite applied it). Where the sentence already stated what was decided, only the citation goes. Where it did not, the decision is added in words:

Where Cited The sentence now says
sql-driver.ts reference_to DDL refusal 3896, 11567 Raw registerObject skips Zod; the FOREIGN KEY DDL the key used to gate is retired, because it could never fire for a spec-conformant lookup.
sql-driver.ts NULL-safe UNIQUE messages (drift plan, restore, hash-shadow, direct, bare-composite fallback) 5030 SQL UNIQUE is NULL-distinct, so the old index never constrained rows without an organization.
sql-driver.ts foreign column in the hash-shadow slot 11627, 13015 The column is not the driver's to replace: it may hold data.
sql-driver.ts SQLite canonicalisation info lines 3912, 3994, 12380 Datetime and time are now one text form, so filters and sorts compare values rather than mixed encodings; json cells are now JSON-encoded strings, the one encoding a json column holds on every dialect.
sql-driver.ts MySQL widening info lines 3942, 3994 TIMESTAMP ends at 2038-01-19, keeps no milliseconds and converts through the session timezone; a zero-precision TIME rounds the canonical milliseconds to the second.
cross-field-conformance-cases.ts case notes 5146, 7597, 1051 (cloud), 7398, 5041 Each note states the rule it guards: $not is NULL-safe because each leaf is totalised; the = spelling of a field reference lowers to $eq; only a declared column reaches a SQL identifier position; scalar operators on a JSON column compare the whole serialized text; $field comparands compile, and the positions that cannot compile are refused.
everything else (filter refusals, withheld-detail wording, TEXT-key and row-size explanations, other hash-shadow lines, drift entries, pagination warning, testkit error) 4363, 4560, 5158, 5240, 5347, 5348, 5369, 5907 (uncompilable), 6050, 6212, 7398, 7929, 8197, 8220, 8790, 8931, 9350, 11067, 11374, 11535, 11541, 11565, 11627, 12121, 12999, 13015, 13056, 3696 The sentence already said what was decided; only the citation goes.

The withheld-detail wording now reads "withheld from the message; the full diagnostic is in the server log". That is exactly the Turso remote transport's existing copy, which remote-transport-compile-refusal-seam.test.ts derives from this driver's sentence by stripping the citation, so the pin still holds.

Three strings kept for the driver-turso stage

Three aggregate refusals keep their citation: sql-driver.ts about 1674 (undeclared function, 5907), 1778 (per-aggregation filter, 10413) and 1812 (count_distinct without field, 6409). Their twins in driver-turso's remote-transport.ts (about 948, 1071 and 1014) carry the same citation. Tests compare the two faces byte for byte:

  • remote-transport-aggregate-function-refusal.test.ts about 348, 408, 510 and 576;
  • remote-transport-undeclared-aggregate-keys.test.ts about 201.

So one face cannot drop the number alone. Measured with a one-off ablation on the committed head: deleting only the 5907 citation here, rebuilding driver-sql (the marker present in dist/, 2 files) and running those two driver-turso files gave 8 failed, 25 passed. After the restore (blob equal to HEAD, git diff HEAD empty, the marker absent from dist/, working tree clean) they gave 33/33. No test file was left behind. The order forbids moving another package's ledger rows here, so these three rows (#5907: 1, #10413: 1, #6409: 1) are handed to the driver-turso stage, where both twins change together.

Pins re-pinned: 18 assertion lines in 6 test files

Each asserted a number; each now asserts the words that carry the decision:

  • #5030 (12 lines): sql-driver-unique-violation-predicate.test.ts 199, 208, 218, 232, 289; sql-driver-14902-plain-unique-duplicate-preflight.test.ts 222, 301, 308; sql-driver-15479-shadow-plain-unique-duplicates.test.ts 172, 199, 222; sql-driver-unique-tenancy.test.ts 694. They now assert NULL-distinct. The negative pins (289, 222 in 14902, 172 and 199 in 15479) keep their meaning: the plain-unique messages do not borrow the NULL-safe arm's story.
  • #11374 (2) and #12999 (3): sql-driver-12999-stale-text-remedy.test.ts 193, 243 and 196, 210, 224. They now assert the declare-maxLength sentence, and the absence of the stale-column sentence ("re-declaring maxLength changes nothing here").
  • #11535 (1): schema-drift.base-type-mismatch.test.ts 114. It now asserts "receives one opaque id instead of a list".

Ledger burn-down

scripts/doc-authoring-prose-id.baseline.json was regenerated with node scripts/check-doc-authoring.mjs --census-ledger. It was generated into a scratch file first, so the growth refusal ran against the checked-in baseline, and then copied into place. Only driver-sql rows moved:

File Occurrences before after (file, id) pairs before after
driver-sql/src/sql-driver.ts 62 3 32 3
driver-sql/src/cross-field-conformance-cases.ts 6 0 (row leaves) 5 0
driver-sql/src/schema-drift.ts 4 0 (row leaves) 4 0
driver-sql/src/live-dialect-matrix.testkit.ts 1 0 (row leaves) 1 0
driver-sql total 73 3 42 3

Whole ledger: files 227 to 224, pairs 611 to 572, occurrences 931 to 861. The gate's printed "pinned site(s)" (id-bearing string literals) go 794 to 732. A census of every other package's sites before and after is identical.

The two counts on the card, reconciled. The census counted 52 messages, and the ledger holds 73 occurrences. The census's 52 are sql-driver.ts 48 plus schema-drift.ts 4. It folds a + chain into one message, so its 48 messages are the gate's 54 string literals. Those 52 messages carry 66 occurrences, the ledger's 62 + 4. The census listing prints ids deduplicated per message, which gives 65. The one gap is the hash-shadow warning, whose two ternary branches each cite 13015. The remaining 7 occurrences are the 7 src/-shipped test strings (the six case notes and the testkit error), which sit outside the census's 52-message population.

Verification (head 733b0e6815)

  • Build. turbo run build over the driver-turso and driver-sqlite-wasm closures (which include driver-sql): 9/9. Then every package under packages/: 71/71. Both runs exited 0 under the verify lock.
  • Built output. The new sentences are in both dist/index.js and dist/index.mjs. The three held citations are the only runtime citations left in them; the other hits are docblocks, which the bundle keeps.
  • Tests.
    • @objectstack/driver-sql: 200 files passed and 11 skipped (the live PG / MySQL cells, no server here); 3,248 tests passed and 188 skipped.
    • The six re-pinned files, run on their own: 76 passed, 4 skipped. sql-driver-15479-shadow-plain-unique-duplicates.test.ts is MySQL-only.
    • @objectstack/driver-turso (the byte-parity consumer, reading the rebuilt dist/): 80 files, 2,195 tests passed, 33 skipped.
    • @objectstack/driver-sqlite-wasm (extends SqlDriver): 36 files, 675 tests passed.
  • Typecheck. @objectstack/driver-sql typecheck exits 0; --listFiles shows it reads all 211 of the package's test files.
  • Derived gates. node scripts/pm/dispatch-gates.mjs --commands (no paths; 12 paths against merge base 085ca6bc1) names 71 commands, and all 71 exit 0. check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt first answered PREREQUISITE NOT MET (exit 3) on the partial build. After the full build they exit 0, and check:dts-closure was re-run there too (71 packages swept). --ran reconciliation: "71 derived, 71 run, 0 NOT-MEASURED, 0 UNRUN". check:doc-authoring: "sibling-package prose ids hold the baseline — 732 pinned site(s) across 224 file(s) ... no growth, no burn-down unrecorded".
  • Lint, narrowed. eslint --no-inline-config --format json over the 10 touched TypeScript files reports 10 files, 0 errors and 0 warnings. The population is the touched files, and the count is read from eslint's JSON. The narrowing is safe because eslint.config.mjs enables no type-aware linting: the resolved parserOptions are ecmaVersion and sourceType only, with no project or projectService. So this diff cannot move an untouched file's verdict. The repo-wide pnpm lint is CI's.
  • NOT MEASURED here (CI's): the live PG / MySQL cells of the driver-sql suite, including the three re-pins in the MySQL-only 15479 file. The required Temporal Conformance (live PG + MySQL) job runs the whole driver-sql suite against both servers. Also CI's: the Test Core shards, the Dogfood jobs and the workspace type-check lanes.

Acceptance notes

  • Carrier for the three held strings: the driver-turso stage (stage 4 of this lane). It should rewrite each pair together, the driver-sql string and its remote-transport.ts twin, and that PR's ledger diff then moves one driver-sql row as well. That is the same serial ledger, one stage later.
  • Two twins now differ by the citation only. The declared-but-uncompiled refusal (5907, about 1749 here and 987 there) and the date-bucket refusal (6212, about 1867 here and 1108 there). Their contract is first-sentence parity, which holds; the remote copies drop the number in the driver-turso stage.
  • Cited cards read. Of the 41 distinct cited ids, 35 read as closed and completed. Five answer 404: 11067, 11374, 12380, 12999 and 13015. They were read through their landing commits and today's docblocks. The cloud-repository card behind the $field identifier note is not readable from this session; that note is rewritten from its own sentence. Each rewritten sentence describes today's code: $field comparands are compiled (the enforce-or-remove question was answered "enforce"), the = lowering fix landed, and the FK DDL is retired.
  • Comments untouched. Code comments and docblocks citing the same numbers are not runtime strings and are not changed here.

Generated by Claude Code

…ision in words instead of a tracker number

Text only: no error code, field name, status or behaviour changes. The
prose-id ledger is recomputed with --census-ledger; only driver-sql rows
leave. Three aggregate refusals keep their citation because a driver-turso
test compares them byte for byte with the remote transport's copies.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

38 anchor(s) derived from 1 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
  • 1 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 — 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 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3d2d460b2a807d73f191b1daa6619f016a1c8814 — the merge of head 733b0e6815e750e41fdcb3b23efa15cbb799323c into base 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb, 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 3d2d460b2a807d73f191b1daa6619f016a1c8814 && git checkout 3d2d460b2a807d73f191b1daa6619f016a1c8814
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb 733b0e6815e750e41fdcb3b23efa15cbb799323c && git checkout -B drift-repro 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb && git merge --no-ff 733b0e6815e750e41fdcb3b23efa15cbb799323c

node scripts/docs-audit/affected-docs.mjs --json 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb

⚠️ 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: 733b0e6815e750e41fdcb3b23efa15cbb799323c
Local-runs: none

Read-only, at tier, adversarial to the dispatch. Inputs: card #20513 (body and all 13 comments — the census 5900801368, the ruling 5902360492 「20513 A」 A / A, the lane checklist 5902678544, the services pointer 5903999785, the stage-1 claim 5904785587, the stage-1 os-dev-report 5905390342), PR #20795 (body, 12-file list, the net diff origin/main...733b0e6815 at merge base 085ca6bc1), PR #20738 at 5a23096ca as the form-D precedent, the head's check-runs, the cited cards, and the head's own source through git show. Nothing built, run or re-run.

Check-runs on 733b0e6815 (read for this record, 2026-09-30T07:15Z): 34 check-runs, all completed: 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke — path-filtered). The seven required contexts all conclude success: Lint & Repo Gates (carries check:doc-authoring), TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Also success: Check Changeset, Check PR Size, Part-of PR must not also close its card, the claim and single-writer guards. Nothing in_progress.

① Derived judgments

Scope against the ruling and the claim — right. The ruling orders stages per package until the ledger is empty, all three categories, form D, --census-ledger in the same PR; the claim scopes stage 1 to packages/drivers/driver-sql/src/**, the pins, the ledger and one patch changeset, and forbids wire code / field / status changes, comment edits and other packages' ledger rows. The 12-file list is exactly that surface: 4 source files, 6 test files, the ledger, the changeset. No governed path.

Text only — right, checked line by line. Every removed and added line in sql-driver.ts (117 lines), schema-drift.ts (10), cross-field-conformance-cases.ts (12) and live-dialect-matrix.testkit.ts (2) is inside a string literal (a template or quoted + chain, or a note: value). No err.code, err.status, StandardErrorCode member, exported symbol, field name, branch or call moves; the d.message = nullSafeKey ? … : … ternary and every logger.* / logDurabilityFailure call keep their shape. Comments and docblocks are untouched, as the claim requires. The six test-file changes are the 18 pin lines only.

Accept-set and public surface — nothing moves — right. No schema, filter grammar, DDL, route or export changes; a caller sees different prose in the same envelopes with the same code and status. driver-turso's byte-parity tests are the only cross-package readers of these strings, and Test Core is green on them.

Ledger diff — right, exact. Only four driver-sql entries move: cross-field-conformance-cases.ts (6 occurrences, 5 pairs), schema-drift.ts (4 / 4) and live-dialect-matrix.testkit.ts (1 / 1) leave; sql-driver.ts goes from 32 pairs / 62 occurrences to 3 / 3 (#10413, #5907, #6409, one each). 73 to 3. I reconciled the removed occurrences against the source diff id by id: 59 removed from sql-driver.ts (#7929 ×7, #11627 ×7, #8197 ×5, #5030 ×5, #13015 ×3, #13056 ×3, #5907 2 to 1, and the rest one or two each) plus 6 + 4 + 1 in the other three files — every removed occurrence is a citation deleted in the diff, and no other package's row moves. A git grep of the head over the four files finds exactly three non-comment tracker-id hits, the held ones at sql-driver.ts 1674, 1778 and 1812. check:doc-authoring inside the green Lint & Repo Gates run is the mechanical confirmation (the dev's local read: 732 pinned sites across 224 files, no burn-down unrecorded).

Form D, string by string — right. I judged each sentence against its cited card — or, for the five that answer 404 (11067, 11374, 12380, 12999, 13015), against the landing commit and today's docblock: 479fba50df, route A of #11374 3954fb7df5, 4045b954d3, ebcc34e893, cd1348802e / 431d2fb42a — and against the head's code. All 35 readable objectstack cards are closed and completed; no cited decision has been reversed. Where words were added:

Pins — right, 18 lines in 6 files. Each number pin moved to the phrase that carries the decision: NULL-distinct ×12 (7 of them negative pins, whose meaning holds because the plain-unique branches at about 13541-13549 and 14790-14840 never say it), the declare-maxLength sentence and the absence of "re-declaring maxLength changes nothing here" ×5, "receives one opaque id instead of a list" ×1. Each asserted phrase is present in the head's string. The MySQL-only 15479 file's three pins are exercised by Temporal Conformance, which runs the whole driver-sql suite against live PG and MySQL (ci.yml about 1266 onward) — green.

Two wording notes, not FAIL items (carrier: a later stage of this lane, or whoever next edits these lines): (a) the datetime and time "every value is now one … text form" info line can print beside the warn that follows it naming withheld bare-numeric rows; "every converted value" would be exact. (b) at about 14020 the failed-restore arm reads "Rows without an organization are not constrained, because SQL UNIQUE is NULL-distinct", while the reason in that arm is that no index exists at all; the statement is not false and the remedy it gives is right.

② Semver level

.changeset/20513-driver-sql-runtime-strings-state-the-decision.md: @objectstack/driver-sql: patch — right. The diff publishes changed prose in one released package and nothing else; no key, export, envelope code, status or default moves, so this is a fix-class patch, never skip-changeset. The changeset body is CHANGELOG-fit: it names the message families that changed, states "text only", and says why three aggregate refusals keep their citation for now. No model identifier in the changeset, the commit trailers or the PR body (session-URL footer on the body; the commit carries the model-free trailer pair).

Clause-②: no — right. Nothing an author can write is widened or narrowed; the PR body carries Clause-②: no at line start and the changeset repeats it. Check Changeset is green.

③ Boundary flags

Dev flags (report 5905390342), each answered:

  1. Three aggregate refusals held (#5907 at 1674, #10413 at 1778, #6409 at 1812), handed to the driver-turso stage — accepted. remote-transport-aggregate-function-refusal.test.ts about 348 and 510 assert remote.message equal to local.message for the undeclared-function class, about 576 asserts the per-aggregation-filter refusal equal after a backend-name substitution, and remote-transport-undeclared-aggregate-keys.test.ts about 201 asserts the same byte equality; the field-less count_distinct refusal is compared by the block at about 394. The twins in remote-transport.ts carry the same citations, and the claim forbids touching another package's ledger rows, so one face cannot move alone. The stage-4 carrier is named in the dev's out_of_scope_findings and the PR's acceptance notes; that stage's ledger diff will move these three driver-sql rows as well. Held, not lost.
  2. Two twins now differ by the citation only (5907 uncompiled at 1749 / remote about 987; 6212 date bucket at 1867 / remote about 1108) — accepted. The remote copies still end in (#5907) / (#6212); the pinned contract is first-sentence parity (remote-transport-groupby-node.test.ts about 462-463; the aggregate test's class-2 comparison is gone since count_distinct compiles on both faces), and it holds. The remote numbers go with the driver-turso stage.
  3. 62 literals in one PR, under the ~80 stop line — accepted. The ruling's "author-visible text first, log lines last" orders stages that must be split; a stage that lands whole has nothing left to order.
  4. Withheld-detail sentences left at deletion only — accepted, and correct. remote-transport-compile-refusal-seam.test.ts about 489-494 asserts the remote withheld message equals '[RemoteTransport] ' plus the local one, and remote-transport.ts already reads "withheld from the message; the full diagnostic is in the server log" (about 837, 3686, 3718, 3751, 3903). Adding words on the local face would have broken that pin; the deletion makes the two faces identical.
  5. 18 re-pins — verified in ①.
  6. Live PG / MySQL NOT MEASURED locally — CI's, and green (Temporal Conformance success).

open_questions: none in the stage-1 report; none raised here. The PR body's numbers reconcile: 41 distinct cited ids = 35 closed objectstack cards + 5 that answer 404 + cloud#1051; census 52 messages against ledger 73 occurrences reconciles through the +-chain folding and the 7 src-shipped test strings, as the body states. The card's lane children (#20749, #20751, #20753, Blocked-by: #20513) are unaffected by this stage.

Implemented-by: claude/issue-20513-stage1-driver-sql
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 07:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit df67985 Sep 30, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20513-stage1-driver-sql branch September 30, 2026 07:53
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

Development

Successfully merging this pull request may close these issues.

2 participants