Skip to content

test(driver-sql,cli): read the dialect axis the migration-width pin cannot - #16989

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-16394-width-pin-live-dialect
Sep 9, 2026
Merged

test(driver-sql,cli): read the dialect axis the migration-width pin cannot#16989
os-project-manager merged 1 commit into
mainfrom
claude/issue-16394-width-pin-live-dialect

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #16394

The #16091 migration-width pin runs its oracle — SqlDriver.initObjects, read
back with PRAGMA table_info — on an in-memory better-sqlite3 database, while
every width it asserts is a PostgreSQL claim. The step between the two is a
premise nothing was holding: the driver's character-column width does not
depend on the dialect
.

L7b, re-driven here — ⛔ not inherited

The card recorded L7/L7b at head fd79a125d1f and both the filing seat and
triage said they had not independently re-driven them. Re-run in this worktree
at a72b6865f8, with the mutation rebuilt into dist/ and proved present by
scripts/ablation-dist-preflight.mjs before any colour was believed:

leg tree mutation live in dist/ pin result
baseline clean 28 passed (28)
L7b clean if (this.isPostgres) return null; as the first line of keyableTextLength 28 passed (28) — the defect, reproduced
L7b with this PR same 2 failed / 28 passed (30) — the fix bites

Both mutate legs: blob 0e9b25dd…03df8c12…, marker present in 2 built
files. Both restore legs: blob back to 0e9b25dd…, whole-tree
git status --porcelain empty, and --absent reporting the marker gone from
all 6 built files. ⛔ No mutation is in the delivered diff.

⚠️ Two readings that differ from the card, stated rather than smoothed over:

  • The pin has 28 tests at this head, not the 81 the card's run reports. The
    premise it measured is intact; the population differs, so the counts are not
    comparable and only the colour is.
  • The repository as a whole is not blind to this exact gate. Driven against
    a live PostgreSQL 16.13, sql-driver-11794-richtext-text-family.test.ts
    reddens under it today (1 failed / 14 passed / 2 skipped). The blind
    instrument is the pin, which is what the card claims — but the gap is
    narrower than "nothing sees it", and the next section is the measurement that
    says why the new coverage is still not redundant.

Route 2, and where each half had to live

⛔ Not route 1. A no-dialect-token source assertion reddens because a token
appeared, which a rename evades, and it returns to the layer #16298 spent four
rounds leaving.

packages/drivers/driver-sql/src/sql-driver-16394-character-width-dialect-parity.test.ts
one object covering all three arms of createColumn's character switch, driven
through the real chain on SQLite and on each live length-enforcing dialect, with
the columns read back from each server's own catalog through columnInfo().
The assertion is equality between the two catalogs, not a transcribed width
table: a driver change that moves every dialect together stays green here and is
caught by #16091; a change that moves ONE dialect is what this file exists to
see. A literal control on the SQLite baseline keeps parity from being satisfiable
by a chain that answers text everywhere, and a write probe at the bound and one
past it makes the reported width a statement about the column a deployment gets
(22001 at 65 characters, accepted at 64, on live PG 16.13).

⚠️ It is in driver-sql and not in the pin because that is the only place it
can run.
Temporal Conformance (live PG + MySQL) is the sole job in this
repository that provisions a live server, and it runs
pnpm --filter @objectstack/driver-sql test and nothing else — measured by
grepping every workflow for the URL env var, which appears in exactly one job.
A live cell written into packages/cli would be provisioned by no job and would
report itself un-run forever.

The pin itself gains a server-free dialect leg (§F2b): the two width bodies
and the emitter's dispatch mirror are called on a DriverOracle configured for
each dialect the emitter branches on — isPostgres reads the config, so
nothing is dialled — and must return the same value. It reddens because the
width moved, not because a token appeared. Its control asserts the three
instances really report three different dialects, so the sweep cannot degenerate
into comparing one driver against itself.

The precedent actually used, and why not the one the dispatch named

The dispatch pointed at schema-drift.12732-varchar-emitter-parity.test.ts as
"already driven across live dialects". Read at source, it is not: it calls
dialectCell('sqlite').config() once to build an unconnected instance for
varcharColumnChars, and drives no live cell at all. Its own comment says why —
"Dialect is irrelevant to that method (confirmed by reading it: no
this.dialectName branch)"
— which is the same unenforced claim this card is
about, one file over, and which the new pin now backs with a measurement.

The shape copied instead is sql-driver-string-maxlength-varchar.test.ts and
sql-driver-11794-richtext-text-family.test.ts: declareDialectCell over
[PG_CELL, MYSQL_CELL], initObjects, columnInfo(), LIVE_CELL_TIMEOUT_MS
applied at the one seam every consumer already goes through. Same testkit, same
per-file schema isolation, no third shape invented.

The non-redundancy measurement

A second gate shape, if (this.isPostgres) return 64; — a dialect gate that
returns a constant rather than null — driven the same way (blob
0e9b25dd…139afad6…, marker present in 2 built files, restore proved):

file result under the constant-64 gate
sql-driver-11794-richtext-text-family.test.ts passed — its keyed columns declare exactly 64
sql-driver-string-maxlength-varchar.test.ts passed
the new parity pin failedkeyed_text_100 / keyed_markdown_255 moved on PG only

1 failed / 17 passed / 3 skipped. That is why the corpus carries three distinct
keyed widths: the existing live coverage transcribes one, and a gate that
happens to agree with the transcription is invisible to it.

Local verification

  • pnpm --filter @objectstack/cli exec vitest run --project integration src/commands/generate-string-family-width.pin.test.ts30 passed (30). The pin is integration tier by vitest-tiers.ts's predicate (it value-imports @objectstack/driver-sql); the unit tier is untouched by this diff.
  • pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-16394-character-width-dialect-parity.test.ts1 passed / 2 skipped without servers (both live cells REPORTED, never silently omitted), 3 passed / 1 skipped against live PostgreSQL 16.13 at Asia/Shanghai (the MySQL cell is the remaining named skip — no MySQL server and no container runtime here; it is declared to the live CI job).
  • Full suites, both affected packages, at dccac1f683: pnpm --filter @objectstack/cli test227 files / 2946 tests, all passed (both tiers); pnpm --filter @objectstack/driver-sql test against the same live PostgreSQL → 169 files passed / 3 skipped, 3054 tests passed / 76 skipped (the skips are the unprovisioned MySQL cells, each REPORTED by declareUnprovisionedCell).
  • pnpm exec eslint . --no-inline-config --format json at dccac1f6836390 files, 0 errors, 0 warnings, exit 0. The whole population, so no narrowing is claimed.
  • pnpm --filter @objectstack/driver-sql --filter @objectstack/cli run typecheck → green. Both edited files are really in a tsc program: --listFiles puts the new file in driver-sql's, and the pin in packages/cli/tsconfig.json's.
  • The 53 gate commands scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives from this diff: all 53 run, all green, reconciled with --ran53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN, re-derived after a fresh git fetch origin main. Two of them first returned PREREQUISITE NOT MET (exit 3, check:dual-build-cjs-loads and check:i18n-coverage) because packages outside this diff had no dist/; both were re-run to exit 0 after a full pnpm build rather than recorded as failures.
  • main moved to b834b48e7a while this ran; none of its 15 paths is in packages/cli or packages/drivers/driver-sql, so the merge-base derivation is unchanged and no scoped re-run is owed.
  • content/ delta: 0 paths, with a live positive control on both halves — the same three-dot command prints this PR's 2 non-content paths, and the -- content/ pathspec really matches on a commit that touched that tree.

No changeset — measured, not assumed

Both packages publish, so this is a measurement with a positive control rather
than a judgement. Built, then npm pack --dry-run --json enumerated what each
package actually ships — 505 entries for @objectstack/cli (including the
bin/ target, which npm packs regardless of files[]) and 10 for
@objectstack/driver-sql
— and every packed path was searched:

symbol in cli's packed files in driver-sql's packed files
varcharCharsFor 0 0
DIALECT_ORACLES 0 0
os16394_widths 0 0
keyed_markdown_255 0 0
keyableTextLength (control) 1 5
generateMigrationSql (control) 2 0

Zero test files appear among the 515 packed entries. The controls fire, so the
zeros are readings. ⇒ skip-changeset.

Contract review

Clause-②: no
Re-derived from the delivered diff, not copied from the dispatch. The diff is two
*.test.ts files. It relaxes no accepted set, widens no schema or published
surface, adds no closed-set member or registry entry, withdraws no capability and
migrates no stored data shape; the one new public method is on a test-local
subclass and is absent from every packed byte of both packages (table above).
node scripts/pm/check-widening-tells.mjs --declaration no --diff DELIVERED_DIFF_FILE
exit 0, 2 changed files read, no widening tell on any declared surface.

packages/drivers/driver-sql/src/sql-driver.ts is byte-identical to main.
Route 2 drives the driver; it does not modify it.

验收备注

Observations from this area, ⛔ not filed and not in scope for this PR:

  • schema-drift.12732-varchar-emitter-parity.test.ts carries a comment asserting
    that varcharColumnChars has no dialect branch, "confirmed by reading it". It
    is an unenforced claim of exactly this card's class. The new parity pin now
    measures the property that comment relies on, so the claim is backed rather
    than merely stated — no edit to that file is warranted for it. Successor: this
    PR.
  • The card's 81 passed (81) figure is not reproducible at this head against
    this file, which carries 28 tests. Recorded above so a later reader does not
    chase the discrepancy as a defect; nothing about the premise depends on it.

Generated by Claude Code

…annot

The #16091 width pin runs its oracle — `SqlDriver.initObjects`, read back with
`PRAGMA table_info` — on an in-memory better-sqlite3 database, while every
width it asserts is a PostgreSQL claim. The step between the two is a premise
nothing was holding: that the driver's character-column width does not depend
on the dialect. Re-measured here: a Postgres gate as the first line of
`keyableTextLength`, rebuilt into `dist` and proved present by
`ablation-dist-preflight`, changes the column a real PostgreSQL deployment gets
and leaves the whole pin green (28/28).

Two halves now hold the premise.

`sql-driver-16394-character-width-dialect-parity.test.ts` drives one object
covering all three arms of `createColumn`'s character switch through the real
chain on SQLite and on each live length-enforcing dialect, and asserts the
columns read back from the servers' own catalogs are EQUAL rather than
transcribing a width table. It lives in driver-sql because the live PG + MySQL
job runs that package's suite and nothing else. A literal control on the SQLite
baseline keeps parity from being satisfiable by a chain that answers `text`
everywhere, and a write probe at the bound and one past it makes the reported
width a statement about the column a deployment gets.

The pin itself gains a server-free dialect leg: the two width bodies and the
emitter's dispatch mirror are called on a driver configured for each dialect —
`isPostgres` reads the config, so nothing is dialled — and must return the same
value. It reddens on the mutation above rather than on the appearance of a
token, so a rename cannot evade it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 b834b48e7a6ca1786be5731a1a46945216007c92packageMentionDocs.

Copy link
Copy Markdown
Collaborator Author

PM review — accepted, arming

CI. 37 raw check-run rows, all terminal, 0 red. Four names duplicated across two workflow batches (Packed-tarball smoke (opt-in), Check Changeset, Check PR Size, Auto Label), so the latest-per-name collapse is 37 → 33.

The row that matters here is Temporal Conformance (live PG + MySQL) — success. It is the only job in the repository that provisions a live server, so it is the only one that can execute the new parity file. Unlike two sibling PRs tonight where CI's green did not cover the change, here it does, and the placement argument is what makes that true.

Clause-② — exit 0, both carriers agree, no widening tell.

Red line verified by blob hash, not by reading the diff. packages/drivers/driver-sql/src/sql-driver.ts is 0e9b25dd2e8f87bb420deb8becd0d95d4ae755a6 on both main and this head — byte-identical. ⭐ And that is the same blob the PR cites as its ablation baseline, which independently cross-confirms the mutation legs were driven against the real file rather than a stale copy.

⭐ The hard condition was met exactly

L7b was re-driven here, not inherited — the condition triage and I both attached to this card, because the filing seat measured it once and triage explicitly recorded that it had not re-driven it either.

leg tree pin
baseline clean 28 passed
L7b clean 28 passed — the defect, reproduced
L7b with this PR 2 failed / 28 passed — the fix bites

And the mutation was proved live in dist/ by ablation-dist-preflight.mjs before any colour was believed — the step that separates "the gate did not fire" from "the gate was never installed". Restores proved by blob equality, empty git status --porcelain, and --absent across all 6 built files.

⭐⭐ The non-redundancy measurement is the best work in this batch

The PR reports something that cuts against its own card: the repository is not wholly blind to L7b — sql-driver-11794-richtext-text-family.test.ts reddens under it today on live PG 16.13. A seat optimising for an easy accept would have left that out.

Then it answered the objection that reading invites, with a second mutation: a gate returning a constant 64 instead of null.

file under if (this.isPostgres) return 64
sql-driver-11794-richtext-text-family.test.ts ✅ passed
sql-driver-string-maxlength-varchar.test.ts ✅ passed
the new parity pin failed

Because the existing live coverage transcribes one width, and a gate that happens to agree with the transcription is invisible to it — which is why the new corpus carries three distinct keyed widths. That is anticipating a reviewer's objection and settling it with an experiment rather than an argument.

⭐ The placement is measured, not asserted

The live cell lives in driver-sql because Temporal Conformance (live PG + MySQL) runs pnpm --filter @objectstack/driver-sql test and nothing else — established by grepping every workflow for the server URL env var and finding it in exactly one job. A live cell written into packages/cli would be provisioned by no job and would report itself un-run forever. That is the difference between a test that exists and a test that runs.

⛔ A correction that is mine, not this PR's

My claim comment (5592958980) declared route 2 on the evidence that schema-drift.12732-varchar-emitter-parity.test.ts was "already driven across live dialects". That is false, and I verified the correction at source before writing this: the file builds one unconnected SQLite instance (new SqlDriver(dialectCell('sqlite').config())) and drives no live cell — no declareDialectCell, no PG_CELL/MYSQL_CELL, no initObjects, no columnInfo. Positive control: the file that does drive live cells matches those markers 9 times; this one matches none.

I grepped for dialectCell and keyableTextLength, found both in one file, and concluded it drove live dialects — an anchor treated as a conclusion, by me, in the one document a delivering seat is entitled to take as given. Owned in full on the card (5593546893).

⭐ Sharper still: that file's comment asserts the dialect is irrelevant "confirmed by reading it: no this.dialectName branch" — an unenforced claim of exactly this card's class, one file over. I cited it as evidence the axis was covered; it was evidence the axis was being asserted. Reading it at source instead of inheriting my reading, and reporting the discrepancy rather than routing around it silently, is the right handling.

⚠️ One dispatch requirement I completed myself

The PR measures the content/ delta (0 paths, with a live control on both halves — better-controlled than most tonight), but the dispatch also asked for a token sweep, which is a different question. Done here: keyableTextLength, varcharColumnChars, PRAGMA table_info, character_maximum_length, generate-string-family-width, live-dialect0 each; better-sqlite3 → 4 hits, all in data-modeling/drivers.mdx about installing and configuring the driver (connection strings, Knex client names, install commands), none asserting a character width. Controls fire (varchar 1, PostgreSQL 28). Nothing falsified — a two-test-file diff carries no rule a page could restate. Recorded as mine, not the delivering seat's.

Both card discrepancies were reported rather than smoothed: the pin carries 28 tests at this head, not the card's 81 (populations not comparable — only the colour is), and the "nothing sees it" framing is wider than the measurement. Both belong in the record.

Marking ready for review, then arming auto-merge, in that order.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 9, 2026 00:18
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit fe2b755 Sep 9, 2026
39 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-16394-width-pin-live-dialect branch September 9, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants