Repository navigation
fix(objectql,platform-objects,metadata-protocol): read sys_migration flag rows through findOne, ending the paging warning on every upgraded boot (#20648) - #20766
Conversation
…flag rows through findOne The platform's own sys_migration primary-key lookups went through find() with limit: 1, which the SQL driver reads as page one of a walk. The engine's readMigrationFlagVerified runs at boot before the table is registered with the driver, so every boot and every os migrate plan printed the driver's nondeterministic-paging warning. All five ledger reads now use findOne, the single-row route the driver exempts. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Move the flag-reader doubles to findOne (held to the engine predicate), pin the three engine readers against a recording driver, and pin end to end that a ledger read on a table another driver instance created prints no paging warning while an unsorted page on it still does. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
…col for the sys_migration findOne lookups Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
…gration-flag-point-lookup
…ates; record the new pinned findOne doubles Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
…own for the moved doubles Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
📓 Docs Drift CheckThis PR changes 3 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 26 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 4707e188297797f5fb4b9bc4cc0bed018c5ced8e && git checkout 4707e188297797f5fb4b9bc4cc0bed018c5ced8e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c9c182ed14b1ea1a28cbc56fe71a5a373aca0268 67d5021948ac82000adfcca2bbde972ffa126eef && git checkout -B drift-repro c9c182ed14b1ea1a28cbc56fe71a5a373aca0268 && git merge --no-ff 67d5021948ac82000adfcca2bbde972ffa126eef
node scripts/docs-audit/affected-docs.mjs --json c9c182ed14b1ea1a28cbc56fe71a5a373aca0268
|
…wing of two published engine interfaces platform-objects and metadata-protocol go to minor with a BREAKING paragraph; Clause-② reads no (narrowing); one ADR-0087 disposition marker, not-required (runtime-interface-only) naming both interfaces. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Contract reviewServed-tier: Inputs read: card #20648 (body; comments 5891790566 triage, 5902376781 claim, 5903382265 and 5903601935 Check-runs on the head as read for this record: 41 runs — 35 ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
…e-interface-only claim on a body with no migration prescription The FROM/TO table goes; the fix is one prose line. The marker says the body carries no migration prescription and the fix is compiler-carried. The public door is named resolveSeedTenancySeam. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Contract reviewServed-tier: Delta review after FAIL record 5903787430 on Check-runs on the head as read for this record: 34 runs — 30 ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…once at the engine and RLS seams (objectstack-ai#5930 step 2) (objectstack-ai#20794) Part of objectstack-ai#5930 — step 2 of ruling 5902355785 (the seam lowering in the engine / RLS seams). Steps 3 (the analytics-face seams and the F5 / F11 vocabulary) and 4 (the per-face deletions) remain, so the card stays open. Clause-②: yes ## What this does This implements ADR-0053 D-D1 as amended on 2026-09-30, items 1–7 and 9. The bare-day upper bound, the `$between` split and the NULL-polarity guards are applied once, by one shared `FilterCondition → FilterCondition` lowering. It runs at the engine and RLS seams, after the comparand doors and after filter-token resolution. Drivers receive the lowered filter. - **`@objectstack/spec/data`**: new pure module `filter-lowering.ts`, exporting `lowerFilterCondition(filter, options?)` and `FilterLoweringOptions`. It is exported from the `./data` subpath only, never the root entry (the ruling's D3). The rules: 1. `$between` becomes `$gte` min and `$lte` max. A range holding a `{ $field }` or a non-pair is left whole, for the face that refuses it. 2. A `$lte` on a bare `YYYY-MM-DD` becomes `$lt` `nextUtcCalendarDay(day)`, in the calendar-string domain, never a storage form (D-A1). `UNBOUNDED_ABOVE` turns a lone `$lte` into `{ $null: false }`, and a `$between` keeps its minimum. Instants and `Date`s are never widened. 3. NULL polarity, cell for cell from the four hand copies' tables. `$ne` of a value, `$nin` and `$notContains` get `{ $or: [{ f: { $null: true } }, { f: op }] }` (objectstack-ai#5298). Every leaf of a `$not` operand is made total (objectstack-ai#5146). - The module is copy-on-write, so a filter it does not change comes back as the same reference. It is idempotent, and it recognises its own guards and the same shapes written by an author. It never refuses. It carries the objectstack-ai#8220 provenance mark onto every node it replaces. `options.isDatetimeColumn` scopes rules 1–2 on a typed seam (item 7). - **`@objectstack/objectql` `engine.ts`**: one stage function, `resolveThenLowerWhere` (resolve, then lower), is the only way any filter position resolves. That covers `find`, `findOne` and `count` (`resolveWhereTokens`); `update` and `delete` (`withResolvedWhere`); and `aggregate`'s `where`, each `aggregations[i].filter` and `having`. `resolveWhereTokens` and `withResolvedWhere` now require the lowering options, so no verb can resolve without lowering. The judge (`judgeWhereAdmission`) runs the same stage. The type reader for `where` and `aggregations[i].filter` is the object's declared `type === 'datetime'`, the same test `SqlDriver` indexes `datetimeFields` by. For `having` it is the aggregated row's column types (`aggregatedRowColumnTypes`, where `max(datetime)` is `datetime`). - **`@objectstack/plugin-security`**: `judgeCompiledComparands` (the RLS compile seam, serving `using` and `check`) lowers every compiled policy filter right after the two faces. `RlsFieldGuard` gains an optional `datetime` set. `SecurityPlugin` fills it from the same declaration pass as the field-name set (`loadObjectFieldNames`) and hands it in at both compile sites. A guard without types reads no column as `datetime`. - **`scripts/adr-anchors/packages__spec__src__data__filter-lowering.ts.json`**: pins ADR-0053 to the module (Prime Directive objectstack-ai#13). - Changeset `.changeset/5930-shared-filter-lowering.md`: `@objectstack/spec` minor, `@objectstack/objectql` and `@objectstack/plugin-security` patch. It cites ADR-0053 D-D1 (amended). No face copy is deleted, no driver file is touched, and the analytics `where` / preview door, the read scope and the memory cube face are untouched (step 3). ## The stop line was reached: one evaluator's answers move, on rows with no value The acceptance is answer invariance. Measured, the answers of every driver face stay the same. The engine's own in-process evaluator for `aggregations[i].filter` and `having` (F8, `having-filter.ts`) moves, and only on rows or groups with no value. Before this change F8 was the only face that disagreed with the others on those rows. After it, all faces agree. A/B probe, not committed. Each face answers the filter as written and the lowered filter, on sqlite `SqlDriver` (F1), `InMemoryDriver` (F3), `matchesFilterCondition` (F7) and `matchesAggregationFilter` (F8): | Case set | Filters | Cells | Moved | Filters where the faces disagree, before → after | |---|---|---|---|---| | `FILTER_LOGIC_CASES` over `FILTER_LOGIC_ROWS` | 36 | 144 | 0 | 0 → 0 | | `TEMPORAL_CASES` (plus the resolved `tokenFilter`s) over `TEMPORAL_ROWS` | 32 | 128 | 0 | 0 → 0 | | the same plus a row with no value, each filter also under `$not`, plus `$between` / `$ne` / `$nin` / `$notContains` probes | 70 | 280 | **14, all F8** | **14 → 0** | All 14 moved cells are a `$between` on a `datetime` column with a row whose value is null. F8 kept that row in the range (7 cells) and dropped it under `$not` (7 cells). F1, F3 and F7 exclude it from the range and keep it under `$not`, which is the objectstack-ai#5146 / objectstack-ai#5298 reading. A second probe found the same class on a number column: `{ $not: { amount: { $lt: 5 } } }` dropped a null `amount` in F8, because JS compares `null < 5` as true. Everywhere else the null row is kept. That probe covered the per-aggregation filter and `having`, one cell each. The decision on whether to keep the two aggregate seams wired is in the report on objectstack-ai#5930, with the four-axis frame. This PR carries option A (keep them). Dropping them (option B) removes the two `aggregate` hunks and their two pin rows. ## Mechanism hypotheses — which held - **H1 held**: the doors run in `lowerWhereFilterArray` and tokens resolve after it on every verb. One helper (`resolveThenLowerWhere`) holds "resolve, then lower", and every verb has its own pin. - **H2 — measured: yes, a compiled policy CAN carry an unresolved date token.** `record.signed_on <= '{today}'` compiles to `{ signed_on: { $lte: '{today}' } }`, passes both faces, and reaches `using`'s drivers and `check`'s `matchesFilterCondition` verbatim. Nothing resolves a placeholder on either RLS clause. The RLS lowering therefore runs after the faces (item 3) and reads `'{today}'` as a non-day string it leaves as written, the same as every face does today. This is pinned. - **H3 — measured.** (a) The RLS seam could not read declared types: `RlsFieldGuard` carried names only, but the types are in the same declaration `loadObjectFieldNames` reads. (b) No in-repo or example policy compares any column against a bare day or a date token: 72 non-test `using`/`check` predicate lines, all `==`, `in`, `== null`, `!= null` or `1 == 1`. So no real policy's rows or admitted writes change under either reading. The seam takes the typed reading, because the type-blind one would move `using` answers on SQL for a non-`datetime` column (a text column holding day-prefixed strings, and `$lte '9999-12-31'` on text) in constructible policies. - **H4 held on F1/F3/F7; F8 is the stop-line item above.** Face suites are green after the change. Before: `main` at `085ca6bc1c` has `Test Core` (6/6), `Temporal Conformance (live PG + MySQL)` and `Dogfood Regression Gate` green. - **H5 held**: the output introduces only `$and`, `$or`, `$lt`, `$gte`, `$lte` and `$null`. The unit table pins that closure over `FILTER_LOGIC_CASES`, `TEMPORAL_CASES` and every row. F1, F2, F3, F6, F7 and F8 already compile those. - **H6 held**: the three polarity functions and the `$not` totaliser are the SQL copies' tables cell for cell. The copies stay. ## Evidence (all on head `9ca3698b67`) - New pins: - `packages/spec/src/data/filter-lowering.test.ts`: 46 tests. The rule table, the item-7 scope, idempotence over the table and both case sets, vocabulary closure, copy-on-write, provenance, and pass-through. - `packages/objectql/src/engine-shared-filter-lowering-seam.test.ts`: 11 tests, one per verb and position, plus `{today}` resolved-then-widened, the typed scope, the last supported day, copy-on-write and the judge. - `packages/plugins/plugin-security/src/rls-shared-lowering-seam.test.ts`: 14 tests. Both clauses through `RLSCompiler`, plus `SecurityPlugin.getReadFilter` and `computeWriteCheckFilter` fed the declared `datetime` set. - Existing shape pins updated to expect the lowered driver input. No asserted row count changed. The door suites for number comparands, text operators and filter arrays now compare against the lowering of the door's output. `rls-compiled-comparand-faces` gets the same treatment. `rls-empty-membership-polarity`'s `not in` shape is updated, and its admitted-row count stays 3. - Suites after the change: - spec: 578 files, 17065 passed. - objectql: 341 files, 6729 passed. - plugin-security: 148 files, 3216 passed. - driver-sql: 200 files passed and 11 skipped (live PG/MySQL cells). - driver-memory: 65 files, 1470 passed. - driver-turso: 80 files, 2195 passed. - driver-sqlite-wasm: 36 files, 675 passed. - driver-mongodb: 29 files passed; 5 skipped, because they need a real mongod. - formula: 42 files, 1240 passed. - `typecheck` is green for spec, objectql, plugin-security, the five drivers and formula. `check:driver-conformance` is OK (50 cells). - Ablations, each with the seam committed and then mutated on disk through `scripts/ablation-replace.mjs` and restored (blob equals HEAD, `git diff HEAD` empty): - engine `resolveThenLowerWhere` without the lowering: 8 of 11 seam pins red, across every verb and all three `aggregate` positions. - RLS `judgeCompiledComparands` without the lowering: 8 of 14 red. - `SecurityPlugin` without the `datetime` hand-off: the 2 plugin-level pins red. - The pins that stay green are the pass-through rows, which hold with or without the lowering. - Gates: `node scripts/pm/dispatch-gates.mjs --commands` derived 103 families; all 103 were run with exit 0, and `--ran` reports 0 NOT-MEASURED and 0 UNRUN. Three gates (`check:dual-build-cjs-loads`, `check:i18n`, `check:type-check-debt`) first refused with `PREREQUISITE NOT MET`. They were re-run green after `turbo run build --filter='./packages/*' --filter='./packages/*/*'`. `check:generated` shows 15 of 15 up to date after regenerating `api-surface/` and `export-origins/`. - Lint, as a narrowed run: - The population is the `eslint.config.mjs` block `files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']`. - `eslint --no-inline-config --format json` over the 13 changed source files reports 13 files, 0 errors and 0 warnings. - The config enables no type-aware linting (every `parserOptions` is `ecmaVersion` / `sourceType` only), so this diff cannot move a verdict on an untouched file. - NOT MEASURED locally: live PostgreSQL / MySQL (the CI job covers them), a real mongod, and Turso remote against a live server. - Changed lines: 1318 (+1289 / −29, 17 files). ## Acceptance notes - **Item 7, when declarations are absent at run time.** Both seams read NO column as `datetime` when the object's declaration is missing (a registry-less host, or a guard without types). They do not apply the rule type-blind. This keeps every driver's answer where it was (`SqlDriver` also widens nothing without a declaration). The step-4 deletion cards should re-read this: once a face's copy is gone, a declaration-less path gets no whole-day bound. - **Two sibling files outside the declared file surface.** `security-plugin.ts` changes in two places: the `datetime` set is read in the existing declaration pass, and it is handed in at the two compile sites. The ADR anchor is one new JSON file. Neither adds a seam. - **RLS `check` with a date token.** `os validate` refuses a `{placeholder}` in a read-scope `using` clause (`validate-rls-predicate-enforceability.ts` judges it through the engine). A `check` clause is not judged there, and nothing resolves the token at run time. A `check` of `record.signed_on <= '{today}'` therefore compares against the literal text, and every ISO value sorts below `{`. Public-door reach is not measured. Carrier: none. - **The analytics read scope.** It reads `using` through `getReadFilter`, so from this PR on it receives the RLS seam's lowered policy. For a policy with a bare-day `$lte` on a `datetime` column (none in-repo), it now keeps the whole day, which is objectstack-ai#20733's direction. objectstack-ai#20733 itself (the scope's own bound on a caller's filter) is step 3 and is not addressed here. - Serial: objectstack-ai#20766 landed before this PR and was merged in cleanly. objectstack-ai#20745 has not landed. It edits other regions of `engine.ts` and `filter.zod.ts` prose, and whichever lands second merges `main`. --- _Generated by [Claude Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20648
Clause-②: no (narrowing)
The platform reads its deployment ledger (
sys_migration) one row at a time, by primary key. Five readers wrote that read asfind(sys_migration, { where: { id }, limit: 1 }). The SQL driver cannot tell that apart from page one of a walk. On a table it has not registered, it warns that the walk is not deterministic. All five readers now usefindOne, the single-row route the driver already exempts. The driver's check and its wording are unchanged. An unsortedlimitread on a table the driver did not create still warns, and a test pins that control.Reproduction on the base, and where the warning came from
Construction: the 17.4 code path. A one-object probe app, installed from npm with every
@objectstack/*package pinned to 17.4.0 (npm lsshows 261 entries, all 17.4.0).objectstack devon 17.4.0 created a new SQLite file and booted it three times. Each run below then used a fresh copy of that file, booted by this branch's base01e78dceef(the same app, wrapped indefineStack, which main requires).Paged read of 'sys_migration'warningsobjectstack devon the 17.4 file01e78dceefos migrate planon the 17.4 file01e78dceef+ sys_migration [add_columns: columns_moved_at], as the card says)objectstack devon a new file (examples/app-crm), first boot01e78dceef01e78dceefSo the warning is not specific to upgraded databases. Every boot of main prints it once, because the read runs before the schema pass registers
sys_migrationwith the driver.The producer was not either of the two sites the card names. I added a temporary stack-trace line to driver-sql's built
dist/, behind an env var, logging every unsorted paged read of a table the driver has not registered. On boot and onos migrate plan, the only such read ofsys_migrationwasObjectQL.readMigrationFlagVerifiedinpackages/objectql/src/engine.ts, reached throughhaveFileColumnsMoved(). The dist was restored afterwards, byte-identical to the build:cmpagainst the saved copy matches. 17.4.0 ships the same driver check but has nohaveFileColumnsMoved, which is why 17.4.0 prints nothing. By the time the two named sites run in these compositions, the table is registered, so they order byidand never warn. They still have the defect's shape, so they move too. Landing site, per the dispatch's producer clause: the engine as well as the two named files.What changed
packages/objectql/src/engine.ts: the threesys_migrationreads (readMigrationFlagVerified,recordObservedDeviation,retractCreationAttestation) usethis.findOne(...).packages/platform-objects/src/system/migration-flag.ts:readDataMigrationFlagusesengine.findOne(...). The duck-typedMigrationFlagEnginedeclaresfindOnein place offind, sincefindis no longer called.packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts: the receipt's existence check usesledger.findOne(...).SeedTenancyLedgerdeclaresfindOnein place offind, andresolveSeedTenancyLedgerrequiresfindOne. The doc comment listing the ledger's readers now says they read throughfindOne.findalone changes behaviour: its read fails, which reads as "no flag row", and the gates stay closed. The changeset states the FROM and TO shapes.H1 checked.
ObjectQL.findOnecallsdriver.findOne, which callsfindRows(..., singleRowLookup = true)insql-driver.ts. The options survive the move.where: { id }is a predicate, sorequireFindOnePredicatepasses. The query'scontext: { isSystem: true }goes through the samemergeReadContext(query.context, options.context)as onfind. The platform-objects and metadata-protocol tests pin the exact call:findOne('sys_migration', { where: { id }, context: { isSystem: true } }).H3 checked.
os migrate planmakes one unregistered-table read ofsys_migration: the engine's gate read throughhaveFileColumnsMoved(). Neither named site is reached on that path. The pin is the engine test below.After: this branch at
2c59fcaf18, builtPaged read of 'sys_migration'warningsobjectstack devon a fresh copy of the 17.4.0 file (booted twice from a build of4ad574e769, once from a build of2c59fcaf18)os migrate planon a fresh copy of the 17.4.0 file (from builds of4ad574e769and2c59fcaf18)add_columns: columns_moved_atline is still listed, as expected)os migrate planon the file this branch's boot had already openedobjectstack devon a new file (examples/app-crm), first and second boot (build of4ad574e769)The flag read still answers correctly on the old schema. The plan logs
this deployment has verified the file-as-reference migration, read throughfindOnefrom asys_migrationtable that has nocolumns_moved_atcolumn.4ad574e769is the fix before the merge oforigin/main, and2c59fcaf18is the final head.The family sweep: census, and the stop line
The census is AST-based, over non-test
packages/**/*.ts: a call with an object-literal argument that haslimit: 1(a numeric literal) and awhere/filterobject literal with anidkey, as a shorthand, a scalar or{ $eq }. Blind spots: awhereorlimitheld in a variable, and atop: 1alias. Lit control: both named sites are hits at the base.01e78dceef: 55 hits in 16 packages. plugin-approvals 11, plugin-security 9, plugin-sharing 8, service-automation 7, runtime 4, objectql 3, plugin-email 3, mcp 2, and one each in metadata-protocol, platform-objects, plugin-auth, plugin-hono-server, service-datasource, service-queue, service-settings and service-storage.sys_migrationmove here. 50 hits remain in 13 packages at2c59fcaf18.That is far past the dispatch's ~15-site stop line, and most of the remainder is in other lanes' packages. Several are not one-line moves:
runtime/src/action-execution.tsunwraps.valueand scans the array, andplugin-hono-server's isfind.call(ql, ...). So they are not moved here, and the census goes to the seat. None of the 50 printed a warning in the measured boots. The driver warns only when such a read reaches a table before the table is registered.Tests
packages/runtime/src/sys-migration-point-lookup.integration.test.ts(new). A realSqlDriverover one SQLite file with two driver instances: the first createssys_migrationand writes a flag row. The second is one the engine knows about but that has not registered the table. The test assertspaginationTieBreaker('sys_migration')is null, as a stated precondition. It then runs the engine's gate read, platform-objects'readDataMigrationFlag, and the receipt-shapedfindOne: zero paging warnings. Control: an unsortedengine.find('sys_migration', { limit: 1 })afterwards gives exactly one warning. It runs last because the warning is once per object per driver.packages/objectql/src/sys-migration-single-row-route.test.ts(new). A recording driver double checks each engine reader on the production path that reaches it: the gate reads, the deviation marker after a lax-admitted write, and revocation of a contradicted creation attestation. It asserts thatsys_migrationreaches the driver only asfindOne. The probe is lit: a caller's own limit-boundfindis recorded as afind.findOneand are held toassertEngineFindOnePredicate. Files: platform-objectsmigration-flag.test.ts,migration-flag.column-move.test.ts,plugin.test.ts; metadata-protocolseed-tenancy-backfill.test.ts; service-storagefiles-to-references-migration.test.ts,lax-deviation-reclamation-gate.test.ts; objectqlengine.test.ts(the 平台形态的迁移门禁:带自检的数据迁移 + 部署级标记 —— file-as-reference 回收与 strict 翻转都依赖它 #3617 flag block),value-shape-scan-advisory.test.ts,adr0104-attestation-evidence.test.ts(the memoization spy).--project local: 339 files, 6708 tests (after the merge oforigin/main).seed-tenancy-autonumber-split.integration.test.ts, 15 tests.check:test-typecheckdebt is unchanged, and--listFilesconfirms both new test files are in their packages' test programs.Ablation, run from the committed fix.
scripts/ablation-replace.mjsrevertedreadMigrationFlagVerifiedtofindwithlimit: 1: anchor hit once, blob607f1216tob828359f. objectql was rebuilt, andablation-dist-preflightfound the marker in 2 built files. Result: the runtime pin is red (expected [ Array(1) ] to deeply equal []), and the objectql pin is red on 3 of 4 cases. The restore was proven (blob equals HEAD,git diff HEADempty). objectql was rebuilt again, the preflight--absentpassed (exit 0, tree clean), and both pins are green again.Gates
node scripts/pm/dispatch-gates.mjs --commandsat2c59fcaf18derives 77 commands, and all 77 ran on that head with exit 0.--ranreconciliation: "77 derived famil(ies) accounted for — 77 run, 0 NOT-MEASURED (a DERIVED zero — all 77 recorded an exit code and none of them is 3)".Three gates needed changes on the way:
check:engine-double-contract: the new pinnedfindOnedoubles were recorded with--write, which grew the ledger only.check:objectql-double-limit: ratcheted down for the doubles that no longer implement a limit-blindfind.check:query-options-erasure: the new tests type their options, so the count stays at its ceiling of 236.check:dual-build-cjs-loadsfirst answered PREREQUISITE NOT MET (unbuilt packages). It is green after a fullturbo run build.Acceptance notes
[sql-driver] DATABASE_ERROR — the backend refused a read on 'sys_migration' (SQLITE_ERROR) ... no such tableline once. It did so before this change and does so after it. It is the same early read, on a table that does not exist yet. The driver logs it one frame below the engine, whose own missing-table line is already demoted todebug. Not changed here. Reported to the seat.packages/objectql/src/engine-find-missing-table-log-level.test.tsstill listsreadMigrationFlagVerifiedamong thefindcallers it measured. That is a historical reading. The reader now goes throughfindOne.Generated by Claude Code