Repository navigation
fix(driver-sql): honour a string field's declared maxLength in emitted DDL - #11564
Conversation
…d DDL `createColumn` mapped the string family (string/email/url/phone/password) with a bare `table.string(name)`, so every column took knex's default of 255 and the field's `maxLength` was never read. Measured through the driver's own `initObjects` on live MySQL 8.0.46 and Postgres 16: a `maxLength: 400` email, a `maxLength: 1024` url and a `maxLength: 20` phone all landed as `varchar(255)`, and a 300-character write the declaration plainly permits was refused by both (`ER_DATA_TOO_LONG`; `22001 value too long for type character varying(255)`). The bound now binds in both directions. Narrowing is not a destructive migration: `createColumn`'s four call sites are all CREATE TABLE / ALTER ADD COLUMN, so the column it sizes is always empty. Narrowing an existing populated column stays where it was — the `narrow_varchar` drift op, behind `os migrate apply --allow-destructive`. Past the varchar ceiling (measured: MySQL refuses 16384 with ERROR 1074) the column becomes TEXT rather than being clamped, since clamping would reinstate the defect. lookup/user, autonumber and the catch-all keep 255 — none of them stores the declared value. schema-drift.ts is in the diff because the emitter and the differ must agree on which declarations count: it now applies the same positive-integer predicate (`maxLength: 0` planned `varchar(0)` at severity error/destructive), and no longer reads a MySQL TEXT column as a varchar 65535 wide (measured: MySQL reports character_maximum_length 65535 for TEXT, Postgres NULL — so every bounded unkeyed text column reported a permanent destructive narrow_varchar on MySQL only). Part of #11431 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfyXxZ2WPjcjhuXpiQQc3y
The Type Conversion Matrix, the `email` mapping bullet and the `phone` storage sentence all asserted VARCHAR(255) for the string family, which this branch's diff falsifies. Corrected in place, with a footnote naming the rule in both directions, the TEXT fallback above 16383, and — deliberately — the neighbouring rows that do NOT follow it (select/radio, lookup/master_detail/tree, autonumber). Also drops an untyped query-options bag the new test had introduced; the read-back now asserts `char_length` from the server, which is the fact being pinned anyway. Part of #11431 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfyXxZ2WPjcjhuXpiQQc3y
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 24d66eda76aab4f201b39456cc16d45e1df66b9f && git checkout 24d66eda76aab4f201b39456cc16d45e1df66b9f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ba8420b58d36077fe79ca8ce0f201bb31f8be2bc bdf9b6da06e89cdd2c50d0bd3f7c44257177b35c && git checkout -B drift-repro ba8420b58d36077fe79ca8ce0f201bb31f8be2bc && git merge --no-ff bdf9b6da06e89cdd2c50d0bd3f7c44257177b35c
node scripts/docs-audit/affected-docs.mjs --json ba8420b58d36077fe79ca8ce0f201bb31f8be2bc
|
…ui#11466, objectstack-ai#11574, objectstack-ai#11578, objectstack-ai#11581 and objectstack-ai#11583) (objectstack-ai#21710) Fixes objectstack-ai#21696 Clause-②: no This moves the bundled Console's objectui pin from `ab1879721595` (objectstack-ai#21625) to objectui `main` as of the write time, `2e818d0b51ecdf8fdd9fcbf4b916bcd7fe9a9cf6`, which is past `973fc20f3`. The Console now carries objectui#11574 (the phantom "(empty)" groups fix), objectui#11578, objectui#11581, objectui#11576, objectui#11583 and objectui#11466 (V1 declared node slot). It also carries objectui's own 17.7.0 package release. ⛔ This is not a release act. Version PR objectstack-ai#21352 is untouched, and no tag, publish or Release was made. Whether 17.7.0 ships with this pin is the maintainer's call. ## Range `ab1879721595..2e818d0b51ec` has 22 non-merge commits. `git merge-base --is-ancestor` exits 0 against objectui `origin/main` for `973fc20f3`, and `2e818d0b51ec` was objectui `main` when it was read (`git ls-remote` just before this PR). From the changesets objectui declared over the range: - 17 releasing changesets, 1 release-nothing, and 6 commits that carry no changeset. - 12 of the 18 changesets were already consumed by objectui's release `b493919c7`. Each was read from the commit that added it. - 2 breaking entries, both annotated by the author and both from one commit. The range was listed by measurement, not copied from the card. Exactly one commit subject in the range carries `!`: `git log --format='%h %s' ab187972..2e818d0b5 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:'` gives `83e3f8377 feat(types,react,core,plugin-dashboard)!: … (objectui#11466)`. | objectui landing | commit | note | |---|---|---| | objectui#11466: node slot and `SchemaRenderer`'s `schema` take `DeclaredNode`; the dashboard producers name declared nodes; an envelope's object-metric retires | `83e3f8377` | **declared breaking**, both breaking entries | | objectui#11574: a grouping-field switch paints only the server's groups | `2f54dca53` | smoke below | | objectui#11581 / objectstack-ai#11578 / objectstack-ai#11576: Create View doors share one builder; a refused save is said; chart views bind an ADR-0021 dataset | `b65aa5e65`, `f1c937966`, `d0097af2e` | | | objectui#11583: a refused metadata write on the view, report and draft surfaces is said | `e8c0b9614` | past the card's `973fc20f3` | | objectui#11598: dashboard slot-entry and chart-producer reads compile without `BaseSchema`'s index signature | `2e818d0b5` (the pin itself) | | | objectui#11572 / objectstack-ai#10380: interface and object page relay stored view config | `09036173c`, `068564691` | | | objectui#11588: grid summary footer reads currency/precision from the object field only | `d768c3178` | | | objectui#11216: `object-kanban` declares `grouping` as the spec's `GroupingConfig` | `a7557a7d4` | | | objectui#11573, objectstack-ai#11564: `AnyComponentSchema` / flex child typing | `bdc9049ed`, `b10c68e58` | | | objectui release 17.7.0 | `b493919c7` | removes 2726 consumed `.changeset/*.md` files | ## ADR-0087 disposition of the declared-breaking entries Both entries are `not-required (no-migration-prescription)`. The disposition is in `.changeset/console-2e818d0b51ec.md`, which replaces the script's `adr-0087: TODO` placeholder. | objectui PR | commit | entry | disposition | |---|---|---|---| | objectui#11466 (PR objectui#11512) | `83e3f8377` | A node slot and `SchemaRenderer`'s `schema` prop take `DeclaredNode` | not-required (no-migration-prescription). This is objectui's own TypeScript face (`@object-ui/types` / `@object-ui/react`). No ObjectStack schema declares or references it. | | objectui#11466 (PR objectui#11512) | `83e3f8377` | An `object-metric` node in a dashboard widget's legacy `component` envelope draws the retired-format prompt | not-required (no-migration-prescription). An ObjectStack author cannot write that envelope: `ui/dashboard.zod.ts` refuses a widget's `component` key by name as objectui-internal, and no example authors one. The showcase's `object-metric` page tiles still draw their numbers (smoke below). | `check-adr-0087-registration --base origin/main` gives "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition". `check-changeset-no-major --base origin/main` gives "This diff introduces no `major` bump". ## What changed here - **`.objectui-sha` and `.changeset/console-2e818d0b51ec.md`** were written by `scripts/bump-objectui.sh 2e818d0b51ecdf8fdd9fcbf4b916bcd7fe9a9cf6 --no-commit` with `OBJECTUI_ROOT` set to a read-only clone of objectui. The level was auto-set to `minor`. No `../objectui` sibling exists in this container. - **`scripts/sdui-manifest.record.json`** was re-recorded by `node scripts/gen-sdui-manifest-node.mjs` over the tree that `pnpm objectui:build` built at the pin. **`sdui.manifest.json` is byte-identical**: 107 components, sha256 `0ead67c1111d…` at both pins, so no component input moved. The record moves its pin, its `modulesRoot` and objectui's workspace version (17.6.0 to 17.7.0). - **`packages/sdui-parser/objectui-lockstep.json`** was re-recorded with `pnpm gen:sdui-lockstep` against `OBJECTUI_ROOT=.cache/objectui-2e818d0b51ec`. It records 214 grammar lines (blob `0131f27cf86d`), 25 codes and containment predicate `76c18fb95d1f`. All are unchanged, so no port is owed. objectui's `packages/sdui-parser/src` is byte-identical across the range; only its CHANGELOG and package.json moved. - **The 54 asserting pin citations in `packages/spec/src`** were re-measured at the new pin, not restamped: - Every anchor in each asserting record was resolved against objectui's tree at the old pin and mapped through `git diff -U0 ab187972 2e818d0b5`. - Four cited files changed: - `plugin-dashboard/src/index.tsx` (+13/-1, objectui#11466). Lines were added above the `object-metric` registration, so its start moves `244` to `256` and the icon input moves `269` to `281`. Both lines are byte-identical, still `{ name: 'icon', type: 'string' }`. - `packages/types/src/objectql.ts`. objectui#11216's `grouping` member landed below the cited `limit` member, so `:4720` did not move. - `content/docs/plugins/plugin-kanban.mdx`. A `grouping` row landed below the `limit` row, and the page still teaches `limit: 250`. - `packages/react/src/SchemaRenderer.tsx` (+21/-20, objectui#11466). Only the type import, the `SchemaRendererProps` docblock and the `schema` type changed. The `properties.*` hoist, its strip list and the `createElement` spread did not, so `object-timeline`'s prop channel still carries `data`. - Every other cited file is byte-identical across the hop. - Each record gains a dated 2026-10-04 hop sentence and keeps its `ab1879721` history. - **The six migration entries' corpus counts** were re-taken with `git grep -o -F`. That method first reproduced every `ab1879721` number: 10267 files, `objectstack` 16377, `@objectstack/spec` 6665, `timeout` 1348, `useState` 2476, `TTL` 180, `tenant` 1238, `Span` 491. - The new numbers are 7579 files, 17227, 7134, 1351, 2477, 182, 1317 and 505. The file count drops because objectui's 17.7.0 release removed 2726 consumed changesets. - Every zero is still zero: 98 tokens were checked, the export lists of the three cited spec files plus every named key. The only non-zero tokens are the expected `Span` / `SpanSchema`. The 14 new `Span` hits are `Spanish` prose. - `packages/spec/src/migrations/registry.ts` was regenerated with `gen:migration-registry`. - **`.changeset/objectui-pin-citations-2e818d0b51ec.md`** is a `@objectstack/spec` patch, because the `FormField.span` describe and six migration descriptions name the pin. `content/docs/references/ui/view.mdx` was regenerated by `check:generated --fix`. - **`main` was merged** (`7e0066af7a`) with `scripts/pm/os-regen-merge.sh` in merge commit `a0a35cb30f`, with no conflict hunk. Afterwards: - `check:generated` reports all 15 artifacts current. - main's three new semantic entries (`ui-object-form-fields-names-typed`, `ui-object-gantt-markers-typed`, `ui-object-timeline-mapping-typed`) are present in the regenerated registry. - The pin-citation gate still counts 54 asserting citations. main added historical ones only (108 to 118). No example, test or gate needed adapting, and no code changed outside generated records, citations and changesets. ## Browser smoke: `examples/app-showcase` with the Console built at `2e818d0b51ec` The Console was built by `pnpm objectui:build`. The build log reports: "Console dist matches the objectui pin (objectui@2e818d0b51ec)", "Single-zod canary: exactly one zod version literal {major:4,minor:6,patch:5}" and "Console bundle carries THIS tree's @objectstack/spec, and only it". The server ran `pnpm dev -- --fresh --ui --no-watch -p 41853` (with `OS_PORT=41853`) on its own ephemeral DB with the seeded admin. Headless Chromium (`/opt/pw-browsers/chromium`) drove it, with console messages, page errors and every non-2xx response captured. Only the PIDs this run started were stopped. **Dashboards.** objectui#11466 touches the dashboard producers. There are no page errors and no "retired" prompt on any of the four dashboards. | dashboard | metric tiles (value read in the DOM) | charts drawn | console errors | |---|---|---|---| | Delivery Operations `showcase_ops_dashboard` | Active Projects 2, At-Risk (Red) 1, Awaiting Review 2, Total Budget 1,090,000 | 4 | 404 `/api/v1/usage/storage`, 404 `/favicon.ico` | | Revenue Pulse `showcase_revenue_pulse` | Invoices 7, Invoiced Subtotal 1,920, Accounts Signed 3, Accounts (all time) 14, Paid Rate 42.9% | 4 | 404 `/api/v1/usage/storage` | | Chart Gallery `showcase_chart_gallery` | Total Tasks 10, Avg Progress 47.0, Total Spent 616,000 | 13 | 404 `/api/v1/usage/storage` | | System Overview `system_overview` (setup) | Total Users 3, Active Sessions 3, Login Events 3, Config Changes 0 | 2 | 404 `/api/v1/usage/storage` | The Delivery Operations tiles match the REST data on the same server: - `showcase_project?status=active` returns 2 rows, `?health=red` returns 1, and the budget sum is 1,090,000. - `showcase_task?status=in_review` returns 2 rows. - `showcase_task` has 10 rows, matching the Chart Gallery's Total Tasks. - `showcase_account` has 14 rows, matching Revenue Pulse's all-time count. The `object-metric` page tiles that this repo authors also draw: - My Work shows Open Tasks 8, In Review 2 and At-Risk Projects 1. - Command Center (大屏) shows 2 / 8 / 2 / 1 / 14 / 1.1M. Neither page has a page error. **Grouping-field switch (objectui#11574).** The drive was Tasks list, then Group, then Add group field (it selects Title). The select then switched to Priority, Status and Priority. The painted groups are counted from the DOM, and the server answers are each `POST /api/v1/data/showcase_task/query` with `groupBy`, all 200: | step | painted groups | server rows | "(empty)" groups | stuck "Loading grid…" | |---|---|---|---|---| | Title | 10 (one per task) | 10 | 0 | 0 | | Priority | 4: High 3, Low 1, Medium 4, Urgent 2 | 4 | 0 | 0 | | Status | 5: Backlog 2, Done 2, In Progress 2, In Review 2, To Do 2 | 5 | 0 | 0 | | Priority | 4: High 3, Low 1, Medium 4, Urgent 2 | 4 | 0 | 0 | The same sequence painted 13, 8 and 8 groups (9, 3 and 4 phantom) at `ab1879721595`, as objectstack-ai#21625's acceptance notes recorded. **No phantom "(empty)" groups appear at this pin.** **Console errors across the run.** The only ones are the browser's "Failed to load resource: 404" lines for `/api/v1/usage/storage` and `/favicon.ico`, and the run has 0 page errors. The `usage/storage` 404 was already recorded in objectstack-ai#21625's smoke. ## objectstack-ai#21671 lockstep reading - objectui's `packages/sdui-parser/src/validate.ts` at `2e818d0b5` is byte-identical to `ab1879721595`. - `checkType` (`:450-466`) still grades `type-mismatch` with `severity: arms.includes('enum') ? 'error' : 'warning'`. `checkMemberTypes` (`:407-419`) grades `member-type-mismatch` the same way. - This repo grades both `error` since PR objectstack-ai#21678 (`packages/sdui-parser/src/validate.ts:446`, `:506`). **The gap still stands at this pin.** - This repo's "Known lead" note (`packages/sdui-parser/src/validate.ts:336-340`) names objectui's copy "at the `.objectui-sha` pin" without a sha, so it is still true and needs no update. The bump route regenerates nothing there, and `validate.ts` is not edited. - `check:sdui-lockstep` does not compare severity, so this is a reading, not a gate result. ## Gates and tests (head `a0a35cb30f`) - `node scripts/pm/dispatch-gates.mjs --commands` derived 127 commands from the 21-path diff. That is the same set before and after the merge. - At `378d424915` (pre-merge) and again at `a0a35cb30f`, all 127 exited 0. `--ran` reports "127 derived, 127 run, 0 NOT-MEASURED, 0 UNRUN", with every exit code recorded. - Also exit 0: - `check:objectui-pin-citations --verify-anchors` with objectui at the pin: 54 asserting citations match `2e818d0b5`, and 7 content assertions are verified. - `check:objectui-bump` (20 assertions across 5 cases), `check:sdui-lockstep`, `check-sdui-manifest`, `check:console-sha`, `check:console-injection`, `check:migration-registry`. - `@objectstack/spec check:generated`: all 15 artifacts current after the merge. - `pnpm --filter @objectstack/spec test`: 612 files, 18185 passed, 1 todo. - `pnpm --filter @objectstack/spec typecheck`: exit 0. - `pnpm --filter @objectstack/sdui-parser test`: 225 passed. - `eslint --no-inline-config` on the 15 changed TS files: 0 errors and 0 warnings. The lint population is `**/*.{ts,…}` (`eslint.config.mjs:971`). The config enables no type-aware linting (`:328`), so this diff cannot move a judgment on an untouched file. Repo-wide `pnpm lint` is left to CI. ## Acceptance notes - `ui/component.zod.ts`'s `object-grid` `columns` record (the⚠️ paragraph on `plugin-grid/src/useColumnSummary.ts:558-568`) says, about `ab1879721595`, that the footer summary reads `currency` / `defaultCurrency` / `precision` / `scale` off an authored column. objectui#11588 (`d768c3178`, inside this pin) retires that read. The record is in the historical spelling and stays true of the pin it names, so it is left as is. - Two anchors in records that cite no sha are outside the pin-citation gate's population and were not re-measured: - `ObjectView.tsx:882`, in `ui/view.zod.ts`'s [objectstack-ai#5074] block and two view tests. - `plugin-dashboard/src/index.tsx:204`, in the `ObjectMetricPropsSchema icon liveness` test. - Both already read an unrelated line at `ab1879721595`, and they shift to `:936` and `:214` here. - Writes: no PR assignee or label was written. This dispatch's write budget names neither. --- _Generated by [Claude Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #11431
createColumnmapped the string family —string/email/url/phone/password— with a baretable.string(name), so every column took knex's default width of 255 and the field's ownmaxLengthwas never read.Part of, notFixes: the card enumerateslookup/user/auto_numberalongside the five, and this PR deliberately does not change those three. The reasons are measured and below; the remaining question ("shouldmaxLengthmean anything on a lookup or an autonumber?") is a spec decision, not a driver one.Measured, before and after — live MySQL 8.0.46 and Postgres 16
Through the driver's own
initObjects, readinginformation_schemarather than the DDL emitted:email({ maxLength: 400 })varchar(255)varchar(400)url({ maxLength: 1024 })varchar(255)varchar(1024)phone({ maxLength: 20 })varchar(255)varchar(20)email()— no boundvarchar(255)varchar(255)url({ maxLength: 100000 })varchar(255)textlookup/user/autonumber/secret/selectvarchar(255)varchar(255)The write the card reports, a 300-character value into the
maxLength: 1024column:Drift against the table the driver had just created, with no rows in it — 10 findings before, 5 after, and the 5 that remain are exactly the families this PR leaves alone:
Narrowing — the fence, and why I read it differently than the dispatch did
The dispatch order fenced this to widening only, and asked to be corrected with a measurement rather than obeyed. Here is the measurement.
createColumnhas exactly four call sites, and every one is aCREATE TABLEor anALTER TABLE ADD COLUMNfor a column that does not exist yet (sql-driver.ts8054, 8063, 8508, 8525). The column it sizes is always empty.rebuildSqliteTablePatched— the one path that re-materialises a populated table — does not call it. So honouringmaxLength: 20at create time cannot truncate anything: there is nothing in the column.Narrowing an existing populated
varchar(255)is a different road, and this PR does not touch it: it is still thenarrow_varchardrift op, categorydestructive, behindos migrate apply --allow-destructive. The differ's narrowing arm is unchanged in behaviour.The trigger the order named — "would this newly make an existing deployment emit a truncating ALTER" — is therefore not met, in the strong sense:
narrow_varcharfor amaxLength: 20field on avarchar(255)column is emitted onmaintoday, identically, before this branch exists. The measurement is in the before-table above. What the change removes is the false half of that: a table the driver itself had just created reporting a destructive narrowing against its own empty columns.If the fence was meant to hold regardless of that reasoning, the narrowing half is one condition in
declaredVarcharLengthand can be dropped — but it would leavemaxLength: 20inert and the self-drift in place.Upper bound
Measured, not read off a doc page:
One ceiling for every dialect, and it is the lowest of the three (16383) — the alternative is one declaration with three physical shapes. Above it the column becomes
TEXTrather than being clamped, because a clamp would reinstate the exact defect being fixed: a column narrower than the declaration, refusing writes the declaration permits.TEXTrefuses nothing the author declared, and the bound is still enforced at the write seam by the record validator'smax_lengthcheck — with a field-named ADR-0112 envelope instead of a rawER_DATA_TOO_LONG.varchar(1024)creates, 16 ×varchar(1024)isERROR 1118 Row size too large; 64 ×varchar(255)(today's shape) creates and 65 does not. Postgres has no such limit (40 ×varchar(4096)creates cleanly — it TOASTs). No object in this repo comes near it — the widest string-family bound declared anywhere here is 2000, and no object carries more than six such fields — but an authored app could, andcreateColumnis per-column so no decision inside it can see the budget. Filed separately rather than solved here, since wiring a diagnostic means wrapping the sharedcreateTablecall that sibling cards are on.The three families left at 255, each for a measured reason
lookup/user— the column holds the referenced row's id, not the declared value. MySQL is content with a mismatched FK (varchar(20)child →varchar(255)parentidcreates cleanly, and Postgres accepts it too), so the type system gives no warning — but a platform id is 26 characters, soINSERT … VALUES ('01JQ8XKZ9M4N7P2R5T6V8W0Y3B')intovarchar(20)isERROR 1406 Data too long. Honouring the bound here would make the column structurally incapable of holding any id. (My first hypothesis — that the FK itself would be refused — was wrong; measuring it is what found the real and worse failure.)autonumber— the value is runtime-issued.maxLengthhas no write-time counterpart on this type (the record validator's textual branch does not cover it, and runtime-owned types never reach it), so binding the DDL to it creates a refusal with nothing declaring it.secretpersists an opaquesys_secretref rather than the credential (ADR-0100);select/radio/code/treestore option machine names or ids. None of them stores the string the bound describes.Why
schema-drift.tsis in the diffTwo corrections, both required for this change to be coherent rather than optional cleanups — the emitter and the differ disagreeing about which declarations count is the defect class this card exists to close, and both of these would have re-opened it one case to the left.
maxLengthis no longer read as a bound.maxLength: 0took the narrowing arm (0 > 255is false) and plannedvarchar(0);maxLength: 12.5plannedvarchar(12.5)— both at severityerror, categorydestructive, i.e. as work--allow-destructiveshould go do. The differ now applies the same positive-integer predicate the emitter does.TEXTcolumn is no longer diffed as avarchar65535 wide. Measured on the same two columns: MySQL reportscharacter_maximum_length = 65535forTEXT, Postgres reportsNULL— so this was MySQL-only and invisible on Postgres. Every bounded unkeyed text field, the shapecreateColumndeliberately leaves asTEXT, diffed as "declared 4000, column allows 65535" and produced anarrow_varcharat severityerror/ category destructive against a freshly created empty table. Measured on live MySQL againstsys_email's real field shapes:Reverse verification
The pins were run against pre-fix source, rebuilt, with the mutation confirmed on disk before the run (an editing tool's exit code is not evidence) and a
trap … EXIT INT TERMrestoring both files:The 4 pins that stay green pre-fix are the ones asserting unchanged behaviour — malformed bound → 255, the excluded families → 255, and the negative half on both dialects. That negative half is the one that would catch a fix that widened everything to silence the symptom:
varchar(20)must really still refuse 300 characters, and it does.Verification
All on
bdf9b6da06, the final commit of this branch.pnpm --filter @objectstack/driver-sql testwithOS_TEST_MYSQL_URLandOS_TEST_POSTGRES_URLset — 126 files, 2521 tests passed, 10 of them new.pnpm --filter @objectstack/driver-sql typecheck— clean.node scripts/pm/dispatch-gates.mjs(not from the dispatch list, which predated the docs edit) — all pass.check:query-options-erasurered first at "test surface grew 240 -> 241"; the new test's untyped options bag was replaced with achar_lengthread from the server, which is the fact being pinned anyway.bdf9b6da06, working tree clean.Docs
content/docs/protocol/objectql/types.mdxasserted the old shape in three places this diff falsifies — theemailmapping bullet, thephonestorage sentence, and the Type Conversion Matrix row. Corrected, plus a footnote stating the rule in both directions and the TEXT fallback, and naming the neighbouring rows that deliberately do not follow it.Fences
⛔ Line 12728's keyed-text branch (
col = keyable === null ? table.text(name) : table.string(name, keyable)) is not in this diff — #11374 is unruled and may reshape it.declaredVarcharLengthdeliberately mirrorskeyableTextLengthwithout sharing code with it: the two answer different questions, and a shared helper would couple a settled decision to an unsettled one.toDateOnly, the aggregate lowering region, andintrospectForeignKeysare untouched.Generated by Claude Code
Generated by Claude Code