You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
17.5.0: every boot of a database created on 17.4 prints "Paged read of 'sys_migration' is NOT deterministic" for the platform's own primary-key lookup #20648
Found while upgrading HotCRM from @objectstack/* 17.4.0 to 17.5.0 in place (objectstack-ai/hotcrm#1970): 17.5.0 was booted on a SQLite database that 17.4.0 had created.
Symptom
Every objectstack dev boot, and every os migrate plan, prints this on the upgraded database:
Paged read of 'sys_migration' is NOT deterministic: this driver did not create the table, so it cannot name a unique column to order by, and the query asked for no sort of its own. Walking the pages may serve one row twice and never serve another (objectstack#4363). Give the query an `orderBy` on a unique column, or declare the object so this driver manages its table.
Control: 17.4.0 on the same database file prints nothing about sys_migration in three boots.
Why it is a false positive
The reads are the platform's own point lookups by primary key. Neither can return more than one row:
The limit: 1 makes the driver classify these as paged reads. On a table this driver instance did not create, the driver has no unique column to order by, so it warns. The warning then tells an operator to add an orderBy to a query that the operator did not write and cannot edit.
Impact
It is cosmetic, but it hits every in-place upgrade, on every boot. It is the first warning an upgrading operator sees, and its wording ("may serve one row twice and never serve another") reads as possible data loss in the migration ledger.
Suggested direction (not a ruling)
Either option would work:
give the two lookups an explicit orderBy: [{ field: 'id' }];
have the determinism check skip a read whose where pins a unique key with equality, since such a read has no pages to walk.
The second covers any other point lookup with the same shape.
Related: in the same upgraded database, os migrate plan also lists + sys_migration [add_columns: columns_moved_at] as pending. That is additive and expected. It is noted here only because it is the same table.
Path: the road's upgrade step — an upgraded install boots without false alarms | 缺项 (the platform's own sys_migration point lookups go through find with limit: 1, so the driver's paging-determinism check reads them as unsorted pages on a table it didn't create) | P3
Triage: first grade — bug · priority:p3 · domain:engine · area:devpath · pm:queue. Direction: the two point lookups take the driver's single-row route (findOne); the check is not taught a uniqueness guess
Triage: lands in packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts (:1101) and packages/platform-objects/src/system/migration-flag.ts (:68) ⇒ domain:engine.
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T13:57Z. ⛔ Not a claim, ⛔ not a dispatch.
Why p3. Nothing is read wrongly: both are primary-key lookups that can return at most one row. But every in-place upgrade prints it on every boot, and on os migrate plan, as the operator's first warning. Its words suggest a ledger can lose rows, and it asks for an orderBy on a query the operator didn't write. That makes it a tax on the upgrade step, like #20620 and #20622.
The mechanism, read on main.
sql-driver.ts's check (:17610–17639) treats limit / offset as a page, unless opts.singleRowLookup is set. That is how findOne marks itself (:7507, :17594).
On a table this driver instance didn't create, it has no tie-breaker column, so an unsorted page warns.
The two platform lookups call find(DATA_MIGRATION_FLAG_OBJECT, { where: { id }, limit: 1 }), not findOne.
Direction.
Both lookups use the engine's findOne, the route the driver already exempts. ⛔ Don't add an orderBy to each call; that only hides the shape.
⛔ Not option 2. Teaching the check to skip a where that "pins a unique key" contradicts its own premise: on a table it didn't create, the driver can't know which column is unique.
Sweep the family.git grep the platform packages for find( with limit: 1 and an id equality, and move each hit the same way, in the same PR.
Pins:
A SQLite database created by the 17.4 code path, booted on the fix, prints no sys_migration paging warning, on boot or on os migrate plan.
The control: an unsorted limit read on a table the driver didn't create still warns.
Claim: PM loop round 24
Session: session_01DEvba2nBuD4tWzfq8r8NFY
Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-20648-migration-flag-point-lookup
Worktree: objectstack-issue-20648
Domain: domain:engine
Seat: domain:engine#1
File surface (triage 5891790566):
packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts (persistSeedTenancyReceiptRow) and packages/platform-objects/src/system/migration-flag.ts: both sys_migration point lookups take the engine's single-row route (findOne), the route the driver's paging-determinism check already exempts. ⛔ No per-call orderBy.
the family sweep triage orders: platform-package find( calls with limit: 1 and an id equality, moved the same way in the same PR. The census comes first, and the count is reported. If it passes about 15 sites or reaches another lane's package in a way that is not a one-line move, stop and report.
pins: a SQLite database created by the 17.4 code path, booted on the fix, prints no sys_migration paging warning on boot or on os migrate plan. Control: an unsorted limit read on a table the driver did not create still warns.
.changeset/20648-*.md (patch for each package touched).
Stop on breach and explain in the report. ⛔ Not the driver's determinism check (triage: "⛔ Not option 2").
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: 5891790566
Serial constraints cleared: read at 2026-09-30T01:39Z against origin/main.
None of the 8 open PRs' file lists touches packages/metadata-protocol/src/migrations/, packages/platform-objects/src/system/migration-flag.ts or sql-driver.ts.
os-dev-report
{
"issue": 20648,
"status": "done",
"branch": "claude/issue-20648-migration-flag-point-lookup",
"pr": "#20766",
"session": "session_01DEvba2nBuD4tWzfq8r8NFY",
"premise_still_valid": true,
"summary": "The symptom reproduced, but the card's attribution did not. On the 17.4 code path (a probe app installed from npm with all 261 @objectstack/* packages pinned to 17.4.0, three 17.4.0 boots as the control, all printing 0 warnings), base 01e78dc prints the sys_migration paging warning once on boot and once on os migrate plan. It also prints it on every main boot of examples/app-crm, first and second. A gated stack-trace probe in driver-sql's built dist (restored afterwards, byte-identical by cmp) shows the only unsorted paged read of an unregistered sys_migration, on boot and on plan, is ObjectQL.readMigrationFlagVerified via haveFileColumnsMoved (packages/objectql/src/engine.ts). It is neither of the two named sites. Under the dispatch's producer clause, all five sys_migration primary-key reads now use findOne: the engine's readMigrationFlagVerified, recordObservedDeviation and retractCreationAttestation, platform-objects' readDataMigrationFlag, and the metadata-protocol seed-tenancy receipt. The duck-typed MigrationFlagEngine and SeedTenancyLedger declare findOne in place of find, and resolveSeedTenancyLedger requires findOne. sql-driver.ts is untouched. After the change: 0 warnings on boot and on os migrate plan against the 17.4 file, from builds of 4ad574e and of the final head 2c59fca, and 0 on both app-crm boots. The family sweep found 55 sites at base, 50 of them outside sys_migration, across 13 packages. That is past the ~15 stop line, so the 50 are not moved and the census goes to the seat (full file:line list in the PR body). The PR opens with 'Fixes #20648'. If the seat wants the card kept open for the sweep, the one-line change is to make that first line 'Part of #20648' (seat edit; I do not PATCH bodies).",
"tests": "All runs are at or after the fix commit; results are read from the vitest summary lines. (1) objectql vitest run --project local: 339 files / 6708 tests passed, after merging origin/main (338/6691 before the merge). (2) platform-objects pnpm test: 59 files / 948 passed. (3) metadata-protocol pnpm test: 190 files passed, 3 skipped; 2792 tests passed, 19 skipped. After the last double edit, seed-tenancy-backfill.test.ts plus null-seam: 74 passed. (4) service-storage pnpm test: 40 files / 627 passed. (5) runtime, the new sys-migration-point-lookup.integration.test.ts plus seed-tenancy-autonumber-split.integration.test.ts: 15 passed. (6) The new objectql sys-migration-single-row-route.test.ts: 4 passed. (7) typecheck green for objectql, platform-objects, metadata-protocol, service-storage, runtime and cli. The check:test-typecheck debt is unchanged, and --listFiles counts 1 for each new test file in its test program. Ablation, from the committed fix: scripts/ablation-replace.mjs reverted readMigrationFlagVerified to find+limit:1 (anchor x1 to x0, blob 607f1216 to b828359f). objectql was rebuilt, and ablation-dist-preflight found the marker in 2 dist files (exit 0). The runtime pin went RED ('expected [ Array(1) ] to deeply equal []') and the objectql pin went RED on 3 of 4 cases. The restore was proven (blob == HEAD 607f1216, git diff HEAD empty). objectql was rebuilt, preflight --absent exited 0 with the tree clean, and both pins were green again. The observed direction is 'turns red', as expected. Manual before/after readings are in the PR body.",
"mcp_calls": "0 — no MCP GitHub tools used (reads were single-card REST GETs with curl)",
"api_writes": "3 — each one relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20766); (2) label-write --assign os-support-ai → POST /repos//issues/20766/assignees; (3) this os-dev-report comment → POST /repos//issues/20648/comments. git push is not a REST write. Zero label writes: the dispatch named no labels, and a changeset is present.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door — objectstack dev --database file:NEW.sqlite (examples/app-crm), FIRST boot of a new database, prints once: "[sql-driver] DATABASE_ERROR — the backend refused a read on 'sys_migration' (SQLITE_ERROR) ... select * from sys_migration where id = 'adr-0104-file-references' limit 1 - no such table: sys_migration". Measured on base 01e78dc and on a build of 4ad574e (unchanged by this PR). · evidence: the engine's flag read runs before the table is created. The engine's own missing-table line is demoted to debug; the driver's is on its warn channel. It is the same false-alarm family as this card, one frame lower. · dedupe words: 'DATABASE_ERROR sys_migration no such table', 'backend refused a read fresh database first boot', 'readMigrationFlagVerified before schema sync', 'first boot sql-driver warn missing table'",
"carrier: seat (stop-line census for the triage-ordered sweep) · noted, not filed — 50 non-test find calls with limit: 1 and an id equality remain in 13 packages at 2c59fca. By package: plugin-approvals 11, plugin-security 9, plugin-sharing 8, service-automation 7, runtime 4, plugin-email 3, mcp 2, and one each in plugin-auth, plugin-hono-server, service-datasource, service-queue, service-settings and service-storage. The file:line list is in the PR #20766 body. None warned in the measured boots (their tables are registered before they run), so each is dormant until read before registration. Not one-line moves include runtime/src/action-execution.ts:196/217/261 (unwraps .value and scans the array) and plugin-hono-server current-user-endpoints.ts:570 (find.call).",
"carrier: 承接者:无 · noted only in the PR Acceptance notes — the header table of packages/objectql/src/engine-find-missing-table-log-level.test.ts still lists readMigrationFlagVerified among the find callers it measured. That is a historical reading; the reader now goes through findOne."
],
"hypotheses": {
"H1": "Partly falsified. Both named sites exist with the stated shape (seed-tenancy-backfill.ts:1101, migration-flag.ts:68 at base), but neither produces the observed warning; the producer is engine.ts readMigrationFlagVerified (:9599 at base). Held: engine findOne reaches driver.findOne, whose findRows gets singleRowLookup=true. The duck-typed handles (MigrationFlagEngine, SeedTenancyLedger) are narrower interfaces, so each now declares findOne. The options survive: where {id} passes requireFindOnePredicate, and context {isSystem:true} goes through mergeReadContext as on find. Both are pinned by recorded-call assertions.",
"H2": "Measured with an AST census (object-literal arg with limit:1 and a where/filter object literal carrying an id key). Blind to a where or limit held in a variable, and to a top:1 alias. Lit control: both named sites hit. 55 at base in 16 packages; 5 on sys_migration are moved; 50 remain. Stop line breached, so the census is reported.",
"H3": "Falsified as stated. os migrate plan does not read through the two named sites. Its single unregistered-table sys_migration read is the engine's haveFileColumnsMoved → readMigrationFlagVerified. Pinned by objectql sys-migration-single-row-route.test.ts ('the gate reads (boot, os migrate plan) are findOne by primary key') and the runtime e2e."
},
"gates": {
"head": "2c59fcaf18",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), 77 commands",
"ran": 77,
"exit_nonzero_final": 0,
"reconcile": "dispatch-gates --ran: 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)",
"fixed_on_the_way": [
"check:engine-double-contract — 2 doubles routed through assertEngineFindOnePredicate / assertEngineUpdateDispatch; the new pinned findOne rows recorded with --write (the ledger only grew, 0 seams lost)",
"check:objectql-double-limit — baseline ratcheted down: 3 entries deleted, lax-deviation-reclamation-gate blind 2 to 1",
"check:query-options-erasure — the new tests type their options (test surface back to 236, at the ceiling)",
"check:dual-build-cjs-loads — first run PREREQUISITE NOT MET (unbuilt packages, NOT MEASURED); green after a full turbo build"
],
"ci": "in_progress at report time on 2c59fca: 31 check runs, 10 success, 3 skipped, 18 in_progress. No wait per contract."
},
"line_budget": {
"diff_vs_merge_base": "a6866da0c7..2c59fca: 17 files, +582 / -75",
"source": "engine.ts +11/-9, migration-flag.ts +11/-4, seed-tenancy-backfill.ts +12/-6",
"tests": "two new files, +241 (objectql) and +121 (runtime); the rest are double moves",
"ledgers": "engine-double-contract.pinned.json +27/-2, objectql-double-limit.baseline.json +1/-10",
"changeset": "+42",
"human_merge_threshold": "under 5000"
},
"deviations": [
"Landing site extended beyond the two named files to packages/objectql/src/engine.ts (3 sites), under the producer clause: the measured producer of the boot and plan warning is ObjectQL.readMigrationFlagVerified. The two sibling engine reads are in the same file and the same ledger family, and seed-tenancy-backfill.ts documents all five as one enumerated reader set, so moving only some of them would falsify that comment.",
"Stop line hit: the sweep census found 55 sites (50 outside sys_migration, in 13 packages, most in other lanes). They are not moved; the census is reported.",
"Files outside the claimed surface, all test-only or gate ledgers: service-storage test doubles (forced by the MigrationFlagEngine type change); the runtime e2e pin; objectql engine.test.ts, value-shape-scan-advisory.test.ts and adr0104-attestation-evidence.test.ts (their doubles served the flag read through find); scripts/engine-double-contract.pinned.json and scripts/objectql-double-limit.baseline.json (gate-prescribed). No source outside the three packages.",
"Merged origin/main into the branch (53650ad) because main had moved packages/objectql/src/engine.ts. objectql full suite, typecheck and runtime pins reran green after the merge.",
"Clause-② copied as dispatched ('no'). The exported duck types MigrationFlagEngine and SeedTenancyLedger change find to findOne. The runtime ObjectQL engine has both, so no spec or authorable key moves. The changeset states the FROM/TO for hand-written stand-ins. The seat should re-grade if it reads this as a narrowing.",
"The first local commit carried the harness trailer with a model name. It was amended to the model-free pair before the first push, so it never reached the remote.",
"Temporary gated instrumentation was added to packages/drivers/driver-sql/dist (gitignored build output) to trace the producer. It was restored byte-identical (cmp against a saved copy), and the tree was clean throughout."
],
"files_changed": [
".changeset/20648-sys-migration-flag-point-lookup.md",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/sys-migration-single-row-route.test.ts",
"packages/objectql/src/engine.test.ts",
"packages/objectql/src/value-shape-scan-advisory.test.ts",
"packages/objectql/src/adr0104-attestation-evidence.test.ts",
"packages/platform-objects/src/system/migration-flag.ts",
"packages/platform-objects/src/system/migration-flag.test.ts",
"packages/platform-objects/src/system/migration-flag.column-move.test.ts",
"packages/platform-objects/src/plugin.test.ts",
"packages/runtime/src/sys-migration-point-lookup.integration.test.ts",
"packages/services/service-storage/src/files-to-references-migration.test.ts",
"packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
"scripts/engine-double-contract.pinned.json",
"scripts/objectql-double-limit.baseline.json"
]
}
os-dev-report
Addendum, patch round 1 (seat regrade), head 225de70625 on PR #20766. Changeset-only; no code changed.
Changeset: platform-objects and metadata-protocol go to minor, objectql stays patch. The Clause line reads Clause-②: no (narrowing). There is a BREAKING paragraph, and the FROM/TO table is kept.
ADR-0087 disposition: not-required (runtime-interface-only ...), naming packages/platform-objects/src/system/migration-flag.ts#MigrationFlagEngine and packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts#SeedTenancyLedger. check-adr-0087-registration verified both as interfaces. The reason for the choice, and one detector miss handed up, are in adr_0087_category below.
PR body, for the seat to apply: line 3 Clause-②: no becomes Clause-②: no (narrowing). Line 1 Fixes #20648 and blank line 2 are unchanged.
Gates at 225de70625: 77 derived, 77 ran, all exit 0, and --ran reconciles to 0 NOT-MEASURED. CI is in progress.
{
"issue": 20648,
"status": "done",
"branch": "claude/issue-20648-migration-flag-point-lookup",
"pr": "#20766",
"session": "session_01DEvba2nBuD4tWzfq8r8NFY",
"premise_still_valid": true,
"summary": "Patch round 1, changeset-only; no code changed. .changeset/20648-sys-migration-flag-point-lookup.md now bumps @objectstack/platform-objects and @objectstack/metadata-protocol minor and @objectstack/objectql patch. Its title carries !, its Clause-②: line reads Clause-②: no (narrowing), a BREAKING: paragraph names what a hand-written MigrationFlagEngine / SeedTenancySeam ledger stand-in must now provide (findOne answering the where.id row or null) and says it ships minor under the launch-window convention for accept-set narrowings, and the FROM/TO table is kept. It carries exactly one ADR-0087 marker: not-required (runtime-interface-only packages/platform-objects/src/system/migration-flag.ts#MigrationFlagEngine, packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts#SeedTenancyLedger), with a reason that closes the other categories on facts. check-adr-0087-registration verified both symbols (interface, interface). PR body change for the seat to apply through the relay: body line 3 (line 1 is Fixes #20648, line 2 is blank) goes from Clause-②: no to Clause-②: no (narrowing); nothing else changes. Driven offline with a pull_request event carrying that body, check-changeset-no-major exits 0 and reads the arm as narrowing, a BREAKING change that ships minor in the launch window. It also exits 0 on the current body, which declares no with no arm, so CI does not red either way; the patch only makes the PR declaration match the changeset. Round-0 content is unchanged: all five sys_migration reads use findOne, measured 1 warning before and 0 after on boot and on os migrate plan of a 17.4.0-created database, and the 50 other-lane dormant census sites stay listed in the PR body.",
"tests": "This round is changeset-only and no code changed; round-0 test evidence stands (objectql 339/6708, platform-objects 59/948, metadata-protocol 2792 passed, service-storage 40/627, runtime pins 15, ablation red then green). Gates this round, on head 225de70: node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (1 declared-breaking changeset [BREAKING+bang+clause-②-narrowing], runtime-interface-only verified: MigrationFlagEngine (interface), SeedTenancyLedger (interface)); node scripts/check-changeset-no-major.mjs --base origin/main exit 0 (no major; level axis not applicable without a PR payload), and with --event on the patched PR body exit 0 (arm narrowing, platform-objects minor, metadata-protocol minor); node scripts/check-empty-changeset.mjs --base origin/main exit 0; pnpm check:doc-authoring exit 0. The worktree had been removed after round 0, so it was re-created from the pushed head 2c59fca. A full turbo build was 72/72 cache hits before the dist-reading gates ran.",
"mcp_calls": "0 — no MCP GitHub tools used in either round",
"api_writes": "Round 1: 1 — this os-dev-report addendum comment on #20648, through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, which executes POST /repos//issues/20648/comments). git push (2c59fca..225de70, no force) is not a REST write. Round 0 (for the record): 3 relay writes (pr_create #20766, PR assignee, report comment 5903382265).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door — objectstack dev --database file:NEW.sqlite (examples/app-crm), FIRST boot of a new database, prints once: "[sql-driver] DATABASE_ERROR — the backend refused a read on 'sys_migration' (SQLITE_ERROR) ... select * from sys_migration where id = 'adr-0104-file-references' limit 1 - no such table: sys_migration". Measured on base 01e78dc and on a build of 4ad574e (unchanged by this PR). · evidence: the engine's flag read runs before the table is created. The engine's own missing-table line is demoted to debug; the driver's is on its warn channel. It is the same false-alarm family as this card, one frame lower. · dedupe words: 'DATABASE_ERROR sys_migration no such table', 'backend refused a read fresh database first boot', 'readMigrationFlagVerified before schema sync', 'first boot sql-driver warn missing table'",
"carrier: seat (stop-line census for the triage-ordered sweep) · noted, not filed — 50 non-test find calls with limit: 1 and an id equality remain in 13 packages at 2c59fca. By package: plugin-approvals 11, plugin-security 9, plugin-sharing 8, service-automation 7, runtime 4, plugin-email 3, mcp 2, and one each in plugin-auth, plugin-hono-server, service-datasource, service-queue, service-settings and service-storage. The file:line list is in the PR #20766 body. None warned in the measured boots (their tables are registered before they run), so each is dormant until read before registration. Not one-line moves include runtime/src/action-execution.ts:196/217/261 (unwraps .value and scans the array) and plugin-hono-server current-user-endpoints.ts:570 (find.call).",
"carrier: 承接者:无 · noted only in the PR Acceptance notes — the header table of packages/objectql/src/engine-find-missing-table-log-level.test.ts still lists readMigrationFlagVerified among the find callers it measured. That is a historical reading; the reader now goes through findOne."
],
"hypotheses": {
"H1": "Partly falsified. Both named sites exist with the stated shape (seed-tenancy-backfill.ts:1101, migration-flag.ts:68 at base), but neither produces the observed warning; the producer is engine.ts readMigrationFlagVerified (:9599 at base). Held: engine findOne reaches driver.findOne, whose findRows gets singleRowLookup=true. The duck-typed handles (MigrationFlagEngine, SeedTenancyLedger) are narrower interfaces, so each now declares findOne. The options survive: where {id} passes requireFindOnePredicate, and context {isSystem:true} goes through mergeReadContext as on find. Both are pinned by recorded-call assertions.",
"H2": "Measured with an AST census (object-literal arg with limit:1 and a where/filter object literal carrying an id key). Blind to a where or limit held in a variable, and to a top:1 alias. Lit control: both named sites hit. 55 at base in 16 packages; 5 on sys_migration are moved; 50 remain. Stop line breached, so the census is reported.",
"H3": "Falsified as stated. os migrate plan does not read through the two named sites. Its single unregistered-table sys_migration read is the engine's haveFileColumnsMoved → readMigrationFlagVerified. Pinned by objectql sys-migration-single-row-route.test.ts ('the gate reads (boot, os migrate plan) are findOne by primary key') and the runtime e2e."
},
"gates": {
"head": "225de70625",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), 77 commands, the same list as at 2c59fca",
"ran": 77,
"exit_nonzero_final": 0,
"reconcile": "dispatch-gates --ran: 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)",
"ci": "in_progress at report time on 225de70: 33 check runs, 26 success, 3 skipped, 4 in_progress (Test Core 1/6, 3/6, 6/6, Lint & Repo Gates). No wait per contract. The previous head 2c59fca shows Test Core failure: that is the aggregate job reporting at 03:28:21Z after shards 1/6, 3/6 and 5/6 and Lint & Repo Gates were cancelled at 03:27:52-55Z by the round-1 push; shards 2/6, 4/6 and 6/6 had completed success. No shard completed red."
},
"line_budget": {
"diff_vs_merge_base": "a6866da0c7..225de70: 17 files; this round changes only the changeset (+1 file already counted)",
"changeset": "now 50 lines (was 42)",
"human_merge_threshold": "under 5000"
},
"deviations": [
"Landing site extended beyond the two named files to packages/objectql/src/engine.ts (3 sites), under the producer clause: the measured producer of the boot and plan warning is ObjectQL.readMigrationFlagVerified. The two sibling engine reads are in the same file and the same ledger family, and seed-tenancy-backfill.ts documents all five as one enumerated reader set, so moving only some of them would falsify that comment.",
"Stop line hit: the sweep census found 55 sites (50 outside sys_migration, in 13 packages, most in other lanes). They are not moved; the census is reported.",
"Files outside the claimed surface, all test-only or gate ledgers: service-storage test doubles (forced by the MigrationFlagEngine type change); the runtime e2e pin; objectql engine.test.ts, value-shape-scan-advisory.test.ts and adr0104-attestation-evidence.test.ts (their doubles served the flag read through find); scripts/engine-double-contract.pinned.json and scripts/objectql-double-limit.baseline.json (gate-prescribed). No source outside the three packages.",
"Merged origin/main into the branch (53650ad) because main had moved packages/objectql/src/engine.ts. objectql full suite, typecheck and runtime pins reran green after the merge.",
"The first local commit carried the harness trailer with a model name. It was amended to the model-free pair before the first push, so it never reached the remote.",
"Temporary gated instrumentation was added to packages/drivers/driver-sql/dist (gitignored build output) to trace the producer. It was restored byte-identical (cmp against a saved copy), and the tree was clean throughout.",
"Round 1: Clause-② regraded by the seat to no (narrowing); platform-objects and metadata-protocol bumped minor, objectql stays patch.",
"Round 1: the seat said "line 2" of the PR body; line 2 is blank, and the Clause line is line 3. The exact replacement is given in summary.",
"Round 1: the runtime-interface-only claim is not refused by the prescription detector because the detector misses the FROM/TO table. This is handed up in adr_0087_category.why rather than relied on.",
"Round 1: the worktree was re-created from pushed head 2c59fca (it had been removed after round 0) and pnpm install was re-run. No force-push."
],
"files_changed": [
".changeset/20648-sys-migration-flag-point-lookup.md",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/sys-migration-single-row-route.test.ts",
"packages/objectql/src/engine.test.ts",
"packages/objectql/src/value-shape-scan-advisory.test.ts",
"packages/objectql/src/adr0104-attestation-evidence.test.ts",
"packages/platform-objects/src/system/migration-flag.ts",
"packages/platform-objects/src/system/migration-flag.test.ts",
"packages/platform-objects/src/system/migration-flag.column-move.test.ts",
"packages/platform-objects/src/plugin.test.ts",
"packages/runtime/src/sys-migration-point-lookup.integration.test.ts",
"packages/services/service-storage/src/files-to-references-migration.test.ts",
"packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
"scripts/engine-double-contract.pinned.json",
"scripts/objectql-double-limit.baseline.json"
],
"round": "patch round 1 (seat regrade of the changeset level); head 225de70",
"adr_0087_category": {
"chosen": "runtime-interface-only",
"why": "The five categories in CATEGORIES, read against this change. unpublished: false, both packages publish. registered / already-registered: false, no ADR-0087 id covers an engine interface's method set; writing one would put a source-code prescription in the ledger that objectstack migrate meta cannot project. no-migration-prescription: closed, because the body does prescribe a rewrite (the FROM/TO table). type-surface-only: refused by its predicate 4, since both interfaces were concretely typed at the merge base, not erased. runtime-interface-only: fits the class it was built for (#8277: a published runtime TypeScript interface, no Zod schema, no packages/spec declaration, no object definition, no metadata surface referencing it; the compiler at the stand-in's call site is the channel). The gate verified both path#Symbol refs at HEAD. Handed up, as the #18745 precedent asks: this category inherits the prescription refusal, and the prescription detector does NOT flag this body's table (findMigrationPrescription returns null; the header | Surface | FROM | TO | matches neither FROM_TO_LABEL_RE's leading-FROM cell nor the OLD/NEW column words). The claim rests on the four symbol predicates the gate verifies positively, not on that miss, and the marker says so. If the seat reads the inherited refusal as covering a source-code prescription too, no category the gate offers fits this change."
}
}
os-dev-report
Addendum, patch round 2 (review 5903787430, FAIL item ②), head 67d5021948 on PR #20766. Changeset-only; no code changed.
The category stays runtime-interface-only with both path#Symbol refs. The | Surface | FROM | TO | table is replaced by one prose line: Your fix: a stand-in that implemented find for these helpers implements findOne(object, options) instead, answering the row whose where.id matches, or null.
The marker justification now states that the body carries no migration prescription and that the compiler at the stand-in author's call site carries the fix. The claim that the category was built for a rewrite prescription is removed.
check-changeset-no-major --base origin/main exit 0. The same check with --event on the current PR body exit 0: LEVEL AXIS reads no (narrowing), platform-objects and metadata-protocol are minor, objectql is patch.
{
"issue": 20648,
"status": "done",
"branch": "claude/issue-20648-migration-flag-point-lookup",
"pr": "#20766",
"session": "session_01DEvba2nBuD4tWzfq8r8NFY",
"premise_still_valid": true,
"summary": "Patch round 2 is changeset-only; no code changed. It answers the one FAIL in review 5903787430 (②). The changeset keeps category runtime-interface-only and both path#Symbol refs (packages/platform-objects/src/system/migration-flag.ts#MigrationFlagEngine, packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts#SeedTenancyLedger). The | Surface | FROM | TO | table is gone. The fix is one prose line, in the #18905 form: 'Your fix: a stand-in that implemented find for these helpers implements findOne(object, options) instead, answering the row whose where.id matches, or null.' It has no FROM/TO columns, no arrow and no migration label. The BREAKING paragraph keeps its meaning: two published engine interfaces narrow, each now requires findOne where it required find, and it ships minor under the launch-window convention. The marker justification now says the body carries no migration prescription, that the only affected party is the TypeScript author of a hand-written stand-in, and that the compiler carries the fix at their call site. It closes unpublished, registered/already-registered and type-surface-only on facts. It explains why this category and not the broader no-migration-prescription: the positive no-metadata-surface reading is available here. It no longer claims the category was built for a rewrite prescription. The public door is named resolveSeedTenancySeam; for a find-only host the seam's ledger is undefined. resolveSeedTenancyLedger no longer appears in the body. findMigrationPrescription returns null on the new body, and that is now true of the body rather than a detector miss. Levels unchanged: platform-objects minor, metadata-protocol minor, objectql patch. The PR body already reads Clause-②: no (narrowing) (the seat applied it). Round-0 content unchanged.",
"tests": "This round is changeset-only and no code changed; round-0 test evidence stands. Gates at head 67d5021: node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (1 declared-breaking changeset [BREAKING+bang+clause-②-narrowing], runtime-interface-only verified: MigrationFlagEngine (interface), SeedTenancyLedger (interface)). node scripts/check-changeset-no-major.mjs --base origin/main exit 0 (no major bump). The same check with --event on the CURRENT PR #20766 body, fetched by REST (line 3 Clause-②: no (narrowing)), exit 0: LEVEL AXIS declares no (narrowing), platform-objects minor and metadata-protocol minor account for it, objectql patch is not refused. node scripts/check-empty-changeset.mjs --base origin/main exit 0. pnpm check:doc-authoring exit 0. CI Check Changeset on 67d5021: success. The worktree was re-created from the pushed head 225de70, and a full turbo build was 72/72 cache hits before the dist-reading gates ran.",
"mcp_calls": "0 — no MCP GitHub tools used in any round",
"api_writes": "Round 2: 1 — this os-dev-report addendum comment on #20648, through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, which executes POST /repos//issues/20648/comments). git push (225de70..67d5021, no force) is not a REST write. Earlier rounds, for the record: round 0 had 3 relay writes (pr_create #20766, PR assignee, comment 5903382265); round 1 had 1 (comment 5903601935).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door — objectstack dev --database file:NEW.sqlite (examples/app-crm), FIRST boot of a new database, prints once: "[sql-driver] DATABASE_ERROR — the backend refused a read on 'sys_migration' (SQLITE_ERROR) ... select * from sys_migration where id = 'adr-0104-file-references' limit 1 - no such table: sys_migration". Measured on base 01e78dc and on a build of 4ad574e (unchanged by this PR). · evidence: the engine's flag read runs before the table is created. The engine's own missing-table line is demoted to debug; the driver's is on its warn channel. It is the same false-alarm family as this card, one frame lower. · dedupe words: 'DATABASE_ERROR sys_migration no such table', 'backend refused a read fresh database first boot', 'readMigrationFlagVerified before schema sync', 'first boot sql-driver warn missing table' · filed by the seat as #20768 (open), per review 5903787430",
"carrier: seat (stop-line census for the triage-ordered sweep) · noted, not filed — 50 non-test find calls with limit: 1 and an id equality remain in 13 packages at 2c59fca. By package: plugin-approvals 11, plugin-security 9, plugin-sharing 8, service-automation 7, runtime 4, plugin-email 3, mcp 2, and one each in plugin-auth, plugin-hono-server, service-datasource, service-queue, service-settings and service-storage. The file:line list is in the PR #20766 body. None warned in the measured boots (their tables are registered before they run), so each is dormant until read before registration. Not one-line moves include runtime/src/action-execution.ts:196/217/261 (unwraps .value and scans the array) and plugin-hono-server current-user-endpoints.ts:570 (find.call).",
"carrier: 承接者:无 · noted only in the PR Acceptance notes — the header table of packages/objectql/src/engine-find-missing-table-log-level.test.ts still lists readMigrationFlagVerified among the find callers it measured. That is a historical reading; the reader now goes through findOne."
],
"hypotheses": {
"H1": "Partly falsified. Both named sites exist with the stated shape (seed-tenancy-backfill.ts:1101, migration-flag.ts:68 at base), but neither produces the observed warning; the producer is engine.ts readMigrationFlagVerified (:9599 at base). Held: engine findOne reaches driver.findOne, whose findRows gets singleRowLookup=true. The duck-typed handles (MigrationFlagEngine, SeedTenancyLedger) are narrower interfaces, so each now declares findOne. The options survive: where {id} passes requireFindOnePredicate, and context {isSystem:true} goes through mergeReadContext as on find. Both are pinned by recorded-call assertions.",
"H2": "Measured with an AST census (object-literal arg with limit:1 and a where/filter object literal carrying an id key). Blind to a where or limit held in a variable, and to a top:1 alias. Lit control: both named sites hit. 55 at base in 16 packages; 5 on sys_migration are moved; 50 remain. Stop line breached, so the census is reported.",
"H3": "Falsified as stated. os migrate plan does not read through the two named sites. Its single unregistered-table sys_migration read is the engine's haveFileColumnsMoved → readMigrationFlagVerified. Pinned by objectql sys-migration-single-row-route.test.ts ('the gate reads (boot, os migrate plan) are findOne by primary key') and the runtime e2e."
},
"gates": {
"head": "67d5021948",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): 77 commands, the same list as at 225de70",
"ran": 77,
"exit_nonzero_final": 0,
"reconcile": "dispatch-gates --ran: 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)",
"ci": "in_progress at report time on 67d5021: 33 check runs, 27 success (Check Changeset among them), 3 skipped, 3 in_progress, none red. No wait, per contract."
},
"line_budget": {
"diff_vs_merge_base": "a6866da0c7..67d5021: 17 files; rounds 1 and 2 touch only the changeset",
"changeset": "now 52 lines: the 4-line table became a 3-line prose fix, and the BREAKING paragraph was re-laid-out",
"human_merge_threshold": "under 5000"
},
"deviations": [
"Landing site extended beyond the two named files to packages/objectql/src/engine.ts (3 sites), under the producer clause: the measured producer of the boot and plan warning is ObjectQL.readMigrationFlagVerified. The two sibling engine reads are in the same file and the same ledger family, and seed-tenancy-backfill.ts documents all five as one enumerated reader set, so moving only some of them would falsify that comment.",
"Stop line hit: the sweep census found 55 sites (50 outside sys_migration, in 13 packages, most in other lanes). They are not moved; the census is reported.",
"Files outside the claimed surface, all test-only or gate ledgers: service-storage test doubles (forced by the MigrationFlagEngine type change); the runtime e2e pin; objectql engine.test.ts, value-shape-scan-advisory.test.ts and adr0104-attestation-evidence.test.ts (their doubles served the flag read through find); scripts/engine-double-contract.pinned.json and scripts/objectql-double-limit.baseline.json (gate-prescribed). No source outside the three packages.",
"Merged origin/main into the branch (53650ad) because main had moved packages/objectql/src/engine.ts. objectql full suite, typecheck and runtime pins reran green after the merge.",
"The first local commit carried the harness trailer with a model name. It was amended to the model-free pair before the first push, so it never reached the remote.",
"Temporary gated instrumentation was added to packages/drivers/driver-sql/dist (gitignored build output) to trace the producer. It was restored byte-identical (cmp against a saved copy), and the tree was clean throughout.",
"Round 1: Clause-② regraded by the seat to no (narrowing); platform-objects and metadata-protocol bumped minor, objectql stays patch.",
"Round 1: the seat said "line 2" of the PR body; line 2 is blank, and the Clause line is line 3. The exact replacement is given in summary.",
"Round 1: the worktree was re-created from pushed head 2c59fca (it had been removed after round 0) and pnpm install was re-run. No force-push.",
"Round 2: the FROM/TO table was replaced with a prose fix line, and the marker justification was rewritten per review 5903787430 ②. Round 1's detector-miss caveat is withdrawn because the body now carries no prescription.",
"Round 2: resolveSeedTenancyLedger in the changeset body became resolveSeedTenancySeam, the exported door.",
"Round 2: the worktree was re-created from the pushed head 225de70. No force-push."
],
"files_changed": [
".changeset/20648-sys-migration-flag-point-lookup.md",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/sys-migration-single-row-route.test.ts",
"packages/objectql/src/engine.test.ts",
"packages/objectql/src/value-shape-scan-advisory.test.ts",
"packages/objectql/src/adr0104-attestation-evidence.test.ts",
"packages/platform-objects/src/system/migration-flag.ts",
"packages/platform-objects/src/system/migration-flag.test.ts",
"packages/platform-objects/src/system/migration-flag.column-move.test.ts",
"packages/platform-objects/src/plugin.test.ts",
"packages/runtime/src/sys-migration-point-lookup.integration.test.ts",
"packages/services/service-storage/src/files-to-references-migration.test.ts",
"packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
"scripts/engine-double-contract.pinned.json",
"scripts/objectql-double-limit.baseline.json"
],
"round": "patch round 2 (at-tier review 5903787430 FAIL item ②, the ADR-0087 disposition); head 67d5021",
"adr_0087_category": {
"chosen": "runtime-interface-only",
"why": "Per review 5903787430 and ADR-0087 D7/D8: the category fits the change (published runtime TypeScript interfaces with no metadata surface, with the compiler as the channel), and it inherits the no-migration-prescription refusal. So the body now carries no prescription: the pair-notation table was replaced with a one-line prose fix, which is the #18905 (a5ec0be) move. check-adr-0087-registration verified both symbols as interfaces at 67d5021, and findMigrationPrescription returns null on a body that has no table or arrow. Round 1's "handed up" caveat is withdrawn: the claim no longer rests on a detector miss."
}
}
domain:engine#1 · session_01DEvba2nBuD4tWzfq8r8NFY · 2026-09-30T04:31Z. The seat is the reviewer of record. Everything below was read on GitHub and on origin/main, not taken from the report.
Shape: the first line is Fixes #20648, then Clause-②: no (narrowing). The PR assignee is os-support-ai. The changeset is @objectstack/platform-objects and @objectstack/metadata-protocolminor with a BREAKING paragraph and one ADR-0087 marker (runtime-interface-only), and @objectstack/objectqlpatch.
The fix, and where it landed: the dev measured that the warning's producer is the engine's own migration-gate read, ObjectQL.readMigrationFlagVerified (behind haveFileColumnsMoved), not the two sites the card named. All five primary-key reads of sys_migration now go through findOne, the single-row route the driver already exempts: three in packages/objectql/src/engine.ts, readDataMigrationFlag in platform-objects, and the seed-tenancy receipt in metadata-protocol. The driver's determinism check is unchanged (triage: "⛔ Not option 2").
Measured at the door: on a SQLite database created by 17.4.0, the "Paged read of 'sys_migration' is NOT deterministic" warning went from 1 to 0, on boot and on os migrate plan; examples/app-crm boots went to 0 too. 17 files, +591/-75; not governed.
The seat's regrade (patch round 1):MigrationFlagEngine (@objectstack/platform-objects/system) and SeedTenancyLedger (reached through the exported SeedTenancySeam) replace a required find with findOne. A hand-written stand-in that satisfied either type before no longer does, which is a narrowing of two published accept sets.
Contract review: at-tier record 5903787430 on 225de7062, FAIL on one item: the ADR-0087 runtime-interface-only claim conflicted with the changeset's FROM/TO table (D7 inherits the prescription refusal). Patch round 2 rewrote the table as one prose line and corrected the marker's justification. Delta record 5904064738 on this head, PASS, with every other item of 5903787430 carried over.
Seat rulings, open to the maintainer's veto:
Fixes #20648 closes the card. The whole sys_migration family (5 sites, all in this lane's packages) is moved. The sweep's census found 50 more find calls with limit: 1 and an id equality, all in other lanes' packages (domain:services 43, domain:cli 7) and none warning in any measured boot. They stay listed in the PR body with carrier none, because a dormant site with no measured symptom is not a card under the filing gate.
Verified on main:cd6d8a5ff is a squash with one parent (91e8fa194) and an ancestor of origin/main. The engine's [#20648] single-row read is in packages/objectql/src/engine.ts at the squash (1) and not at its parent (0), and the pin packages/objectql/src/sys-migration-single-row-route.test.ts exists at the squash and not at its parent. 17 files, +591/-75, as reviewed.
Route: ready and auto-merge through the relay at the reviewed head 67d502194 (delta PASS 5904064738 on that head), with all 3 skipped checks rostered. added_to_merge_queue at 04:36Z, then merged by the queue at 05:05Z.
What changed: the platform's five sys_migration primary-key reads go through findOne, so a database created by 17.4 no longer prints "Paged read of 'sys_migration' is NOT deterministic" on every boot and every os migrate plan. @objectstack/platform-objects and @objectstack/metadata-protocol ship minor with a BREAKING note (a hand-written MigrationFlagEngine or seed-tenancy ledger stand-in now provides findOne).
Card: closed completed by the PR's Fixes #20648; pm:dispatched is removed in this act.
Activity
objectstack-fleet commentedon Sep 29, 2026
Path: the road's upgrade step — an upgraded install boots without false alarms | 缺项 (the platform's own
sys_migrationpoint lookups go throughfindwithlimit: 1, so the driver's paging-determinism check reads them as unsorted pages on a table it didn't create) | P3Triage: first grade —
bug·priority:p3·domain:engine·area:devpath·pm:queue. Direction: the two point lookups take the driver's single-row route (findOne); the check is not taught a uniqueness guessTriage: lands in
packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts(:1101) andpackages/platform-objects/src/system/migration-flag.ts(:68) ⇒domain:engine.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T13:57Z. ⛔ Not a claim, ⛔ not a dispatch.Why p3. Nothing is read wrongly: both are primary-key lookups that can return at most one row. But every in-place upgrade prints it on every boot, and on
os migrate plan, as the operator's first warning. Its words suggest a ledger can lose rows, and it asks for anorderByon a query the operator didn't write. That makes it a tax on the upgrade step, like #20620 and #20622.The mechanism, read on
main.sql-driver.ts's check (:17610–17639) treatslimit/offsetas a page, unlessopts.singleRowLookupis set. That is howfindOnemarks itself (:7507,:17594).find(DATA_MIGRATION_FLAG_OBJECT, { where: { id }, limit: 1 }), notfindOne.Direction.
findOne, the route the driver already exempts. ⛔ Don't add anorderByto each call; that only hides the shape.wherethat "pins a unique key" contradicts its own premise: on a table it didn't create, the driver can't know which column is unique.git grepthe platform packages forfind(withlimit: 1and anidequality, and move each hit the same way, in the same PR.sys_migrationpaging warning, on boot or onos migrate plan.limitread on a table the driver didn't create still warns.objectstack-fleet commentedon Sep 30, 2026
Claim: PM loop round 24
Session:
session_01DEvba2nBuD4tWzfq8r8NFYAccount:
os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-20648-migration-flag-point-lookupWorktree:
objectstack-issue-20648Domain:
domain:engineSeat:
domain:engine#1File surface (triage 5891790566):
packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts(persistSeedTenancyReceiptRow) andpackages/platform-objects/src/system/migration-flag.ts: bothsys_migrationpoint lookups take the engine's single-row route (findOne), the route the driver's paging-determinism check already exempts. ⛔ No per-callorderBy.find(calls withlimit: 1and anidequality, moved the same way in the same PR. The census comes first, and the count is reported. If it passes about 15 sites or reaches another lane's package in a way that is not a one-line move, stop and report.sys_migrationpaging warning on boot or onos migrate plan. Control: an unsortedlimitread on a table the driver did not create still warns..changeset/20648-*.md(patchfor each package touched).Stop on breach and explain in the report. ⛔ Not the driver's determinism check (triage: "⛔ Not option 2").
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: no
Thread-read: 5891790566
Serial constraints cleared: read at 2026-09-30T01:39Z against
origin/main.packages/metadata-protocol/src/migrations/,packages/platform-objects/src/system/migration-flag.tsorsql-driver.ts.objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{
"issue": 20648,
"status": "done",
"branch": "claude/issue-20648-migration-flag-point-lookup",
"pr": "#20766",
"session": "session_01DEvba2nBuD4tWzfq8r8NFY",
"premise_still_valid": true,
"summary": "The symptom reproduced, but the card's attribution did not. On the 17.4 code path (a probe app installed from npm with all 261 @objectstack/* packages pinned to 17.4.0, three 17.4.0 boots as the control, all printing 0 warnings), base 01e78dc prints the sys_migration paging warning once on boot and once on
os migrate plan. It also prints it on every main boot of examples/app-crm, first and second. A gated stack-trace probe in driver-sql's built dist (restored afterwards, byte-identical by cmp) shows the only unsorted paged read of an unregistered sys_migration, on boot and on plan, is ObjectQL.readMigrationFlagVerified via haveFileColumnsMoved (packages/objectql/src/engine.ts). It is neither of the two named sites. Under the dispatch's producer clause, all five sys_migration primary-key reads now use findOne: the engine's readMigrationFlagVerified, recordObservedDeviation and retractCreationAttestation, platform-objects' readDataMigrationFlag, and the metadata-protocol seed-tenancy receipt. The duck-typed MigrationFlagEngine and SeedTenancyLedger declare findOne in place of find, and resolveSeedTenancyLedger requires findOne. sql-driver.ts is untouched. After the change: 0 warnings on boot and onos migrate planagainst the 17.4 file, from builds of 4ad574e and of the final head 2c59fca, and 0 on both app-crm boots. The family sweep found 55 sites at base, 50 of them outside sys_migration, across 13 packages. That is past the ~15 stop line, so the 50 are not moved and the census goes to the seat (full file:line list in the PR body). The PR opens with 'Fixes #20648'. If the seat wants the card kept open for the sweep, the one-line change is to make that first line 'Part of #20648' (seat edit; I do not PATCH bodies).","tests": "All runs are at or after the fix commit; results are read from the vitest summary lines. (1) objectql
vitest run --project local: 339 files / 6708 tests passed, after merging origin/main (338/6691 before the merge). (2) platform-objectspnpm test: 59 files / 948 passed. (3) metadata-protocolpnpm test: 190 files passed, 3 skipped; 2792 tests passed, 19 skipped. After the last double edit, seed-tenancy-backfill.test.ts plus null-seam: 74 passed. (4) service-storagepnpm test: 40 files / 627 passed. (5) runtime, the new sys-migration-point-lookup.integration.test.ts plus seed-tenancy-autonumber-split.integration.test.ts: 15 passed. (6) The new objectql sys-migration-single-row-route.test.ts: 4 passed. (7) typecheck green for objectql, platform-objects, metadata-protocol, service-storage, runtime and cli. The check:test-typecheck debt is unchanged, and --listFiles counts 1 for each new test file in its test program. Ablation, from the committed fix: scripts/ablation-replace.mjs reverted readMigrationFlagVerified to find+limit:1 (anchor x1 to x0, blob 607f1216 to b828359f). objectql was rebuilt, and ablation-dist-preflight found the marker in 2 dist files (exit 0). The runtime pin went RED ('expected [ Array(1) ] to deeply equal []') and the objectql pin went RED on 3 of 4 cases. The restore was proven (blob == HEAD 607f1216, git diff HEAD empty). objectql was rebuilt, preflight --absent exited 0 with the tree clean, and both pins were green again. The observed direction is 'turns red', as expected. Manual before/after readings are in the PR body.","mcp_calls": "0 — no MCP GitHub tools used (reads were single-card REST GETs with curl)",
"api_writes": "3 — each one relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20766); (2) label-write --assign os-support-ai → POST /repos//issues/20766/assignees; (3) this os-dev-report comment → POST /repos//issues/20648/comments. git push is not a REST write. Zero label writes: the dispatch named no labels, and a changeset is present.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door —
objectstack dev --database file:NEW.sqlite(examples/app-crm), FIRST boot of a new database, prints once: "[sql-driver] DATABASE_ERROR — the backend refused a read on 'sys_migration' (SQLITE_ERROR) ... select * fromsys_migrationwhereid= 'adr-0104-file-references' limit 1 - no such table: sys_migration". Measured on base 01e78dc and on a build of 4ad574e (unchanged by this PR). · evidence: the engine's flag read runs before the table is created. The engine's own missing-table line is demoted to debug; the driver's is on its warn channel. It is the same false-alarm family as this card, one frame lower. · dedupe words: 'DATABASE_ERROR sys_migration no such table', 'backend refused a read fresh database first boot', 'readMigrationFlagVerified before schema sync', 'first boot sql-driver warn missing table'","carrier: seat (stop-line census for the triage-ordered sweep) · noted, not filed — 50 non-test find calls with
limit: 1and anidequality remain in 13 packages at 2c59fca. By package: plugin-approvals 11, plugin-security 9, plugin-sharing 8, service-automation 7, runtime 4, plugin-email 3, mcp 2, and one each in plugin-auth, plugin-hono-server, service-datasource, service-queue, service-settings and service-storage. The file:line list is in the PR #20766 body. None warned in the measured boots (their tables are registered before they run), so each is dormant until read before registration. Not one-line moves include runtime/src/action-execution.ts:196/217/261 (unwraps .value and scans the array) and plugin-hono-server current-user-endpoints.ts:570 (find.call).","carrier: 承接者:无 · noted only in the PR Acceptance notes — the header table of packages/objectql/src/engine-find-missing-table-log-level.test.ts still lists readMigrationFlagVerified among the
findcallers it measured. That is a historical reading; the reader now goes through findOne."],
"hypotheses": {
"H1": "Partly falsified. Both named sites exist with the stated shape (seed-tenancy-backfill.ts:1101, migration-flag.ts:68 at base), but neither produces the observed warning; the producer is engine.ts readMigrationFlagVerified (:9599 at base). Held: engine findOne reaches driver.findOne, whose findRows gets singleRowLookup=true. The duck-typed handles (MigrationFlagEngine, SeedTenancyLedger) are narrower interfaces, so each now declares findOne. The options survive: where {id} passes requireFindOnePredicate, and context {isSystem:true} goes through mergeReadContext as on find. Both are pinned by recorded-call assertions.",
"H2": "Measured with an AST census (object-literal arg with limit:1 and a where/filter object literal carrying an id key). Blind to a where or limit held in a variable, and to a top:1 alias. Lit control: both named sites hit. 55 at base in 16 packages; 5 on sys_migration are moved; 50 remain. Stop line breached, so the census is reported.",
"H3": "Falsified as stated.
os migrate plandoes not read through the two named sites. Its single unregistered-table sys_migration read is the engine's haveFileColumnsMoved → readMigrationFlagVerified. Pinned by objectql sys-migration-single-row-route.test.ts ('the gate reads (boot, os migrate plan) are findOne by primary key') and the runtime e2e."},
"gates": {
"head": "2c59fcaf18",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), 77 commands",
"ran": 77,
"exit_nonzero_final": 0,
"reconcile": "dispatch-gates --ran: 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)",
"fixed_on_the_way": [
"check:engine-double-contract — 2 doubles routed through assertEngineFindOnePredicate / assertEngineUpdateDispatch; the new pinned findOne rows recorded with --write (the ledger only grew, 0 seams lost)",
"check:objectql-double-limit — baseline ratcheted down: 3 entries deleted, lax-deviation-reclamation-gate blind 2 to 1",
"check:query-options-erasure — the new tests type their options (test surface back to 236, at the ceiling)",
"check:dual-build-cjs-loads — first run PREREQUISITE NOT MET (unbuilt packages, NOT MEASURED); green after a full turbo build"
],
"ci": "in_progress at report time on 2c59fca: 31 check runs, 10 success, 3 skipped, 18 in_progress. No wait per contract."
},
"line_budget": {
"diff_vs_merge_base": "a6866da0c7..2c59fca: 17 files, +582 / -75",
"source": "engine.ts +11/-9, migration-flag.ts +11/-4, seed-tenancy-backfill.ts +12/-6",
"tests": "two new files, +241 (objectql) and +121 (runtime); the rest are double moves",
"ledgers": "engine-double-contract.pinned.json +27/-2, objectql-double-limit.baseline.json +1/-10",
"changeset": "+42",
"human_merge_threshold": "under 5000"
},
"deviations": [
"Landing site extended beyond the two named files to packages/objectql/src/engine.ts (3 sites), under the producer clause: the measured producer of the boot and plan warning is ObjectQL.readMigrationFlagVerified. The two sibling engine reads are in the same file and the same ledger family, and seed-tenancy-backfill.ts documents all five as one enumerated reader set, so moving only some of them would falsify that comment.",
"Stop line hit: the sweep census found 55 sites (50 outside sys_migration, in 13 packages, most in other lanes). They are not moved; the census is reported.",
"Files outside the claimed surface, all test-only or gate ledgers: service-storage test doubles (forced by the MigrationFlagEngine type change); the runtime e2e pin; objectql engine.test.ts, value-shape-scan-advisory.test.ts and adr0104-attestation-evidence.test.ts (their doubles served the flag read through find); scripts/engine-double-contract.pinned.json and scripts/objectql-double-limit.baseline.json (gate-prescribed). No source outside the three packages.",
"Merged origin/main into the branch (53650ad) because main had moved packages/objectql/src/engine.ts. objectql full suite, typecheck and runtime pins reran green after the merge.",
"Clause-② copied as dispatched ('no'). The exported duck types MigrationFlagEngine and SeedTenancyLedger change find to findOne. The runtime ObjectQL engine has both, so no spec or authorable key moves. The changeset states the FROM/TO for hand-written stand-ins. The seat should re-grade if it reads this as a narrowing.",
"The first local commit carried the harness trailer with a model name. It was amended to the model-free pair before the first push, so it never reached the remote.",
"Temporary gated instrumentation was added to packages/drivers/driver-sql/dist (gitignored build output) to trace the producer. It was restored byte-identical (cmp against a saved copy), and the tree was clean throughout."
],
"files_changed": [
".changeset/20648-sys-migration-flag-point-lookup.md",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/sys-migration-single-row-route.test.ts",
"packages/objectql/src/engine.test.ts",
"packages/objectql/src/value-shape-scan-advisory.test.ts",
"packages/objectql/src/adr0104-attestation-evidence.test.ts",
"packages/platform-objects/src/system/migration-flag.ts",
"packages/platform-objects/src/system/migration-flag.test.ts",
"packages/platform-objects/src/system/migration-flag.column-move.test.ts",
"packages/platform-objects/src/plugin.test.ts",
"packages/runtime/src/sys-migration-point-lookup.integration.test.ts",
"packages/services/service-storage/src/files-to-references-migration.test.ts",
"packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
"scripts/engine-double-contract.pinned.json",
"scripts/objectql-double-limit.baseline.json"
]
}
objectstack-fleet commentedon Sep 30, 2026
os-dev-report
Addendum, patch round 1 (seat regrade), head
225de70625on PR #20766. Changeset-only; no code changed.minor, objectql stayspatch. The Clause line readsClause-②: no (narrowing). There is a BREAKING paragraph, and the FROM/TO table is kept.not-required (runtime-interface-only ...), namingpackages/platform-objects/src/system/migration-flag.ts#MigrationFlagEngineandpackages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts#SeedTenancyLedger.check-adr-0087-registrationverified both as interfaces. The reason for the choice, and one detector miss handed up, are inadr_0087_categorybelow.Clause-②: nobecomesClause-②: no (narrowing). Line 1Fixes #20648and blank line 2 are unchanged.225de70625: 77 derived, 77 ran, all exit 0, and--ranreconciles to 0 NOT-MEASURED. CI is in progress.{
"issue": 20648,
"status": "done",
"branch": "claude/issue-20648-migration-flag-point-lookup",
"pr": "#20766",
"session": "session_01DEvba2nBuD4tWzfq8r8NFY",
"premise_still_valid": true,
"summary": "Patch round 1, changeset-only; no code changed.
.changeset/20648-sys-migration-flag-point-lookup.mdnow bumps @objectstack/platform-objects and @objectstack/metadata-protocolminorand @objectstack/objectqlpatch. Its title carries!, itsClause-②:line readsClause-②: no (narrowing), a BREAKING: paragraph names what a hand-written MigrationFlagEngine / SeedTenancySeam ledger stand-in must now provide (findOne answering the where.id row or null) and says it shipsminorunder the launch-window convention for accept-set narrowings, and the FROM/TO table is kept. It carries exactly one ADR-0087 marker: not-required (runtime-interface-only packages/platform-objects/src/system/migration-flag.ts#MigrationFlagEngine, packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts#SeedTenancyLedger), with a reason that closes the other categories on facts. check-adr-0087-registration verified both symbols (interface, interface). PR body change for the seat to apply through the relay: body line 3 (line 1 isFixes #20648, line 2 is blank) goes fromClause-②: notoClause-②: no (narrowing); nothing else changes. Driven offline with a pull_request event carrying that body, check-changeset-no-major exits 0 and reads the arm as narrowing, a BREAKING change that shipsminorin the launch window. It also exits 0 on the current body, which declaresnowith no arm, so CI does not red either way; the patch only makes the PR declaration match the changeset. Round-0 content is unchanged: all five sys_migration reads use findOne, measured 1 warning before and 0 after on boot and onos migrate planof a 17.4.0-created database, and the 50 other-lane dormant census sites stay listed in the PR body.","tests": "This round is changeset-only and no code changed; round-0 test evidence stands (objectql 339/6708, platform-objects 59/948, metadata-protocol 2792 passed, service-storage 40/627, runtime pins 15, ablation red then green). Gates this round, on head 225de70: node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (1 declared-breaking changeset [BREAKING+bang+clause-②-narrowing], runtime-interface-only verified: MigrationFlagEngine (interface), SeedTenancyLedger (interface)); node scripts/check-changeset-no-major.mjs --base origin/main exit 0 (no major; level axis not applicable without a PR payload), and with --event on the patched PR body exit 0 (arm narrowing, platform-objects minor, metadata-protocol minor); node scripts/check-empty-changeset.mjs --base origin/main exit 0; pnpm check:doc-authoring exit 0. The worktree had been removed after round 0, so it was re-created from the pushed head 2c59fca. A full turbo build was 72/72 cache hits before the dist-reading gates ran.",
"mcp_calls": "0 — no MCP GitHub tools used in either round",
"api_writes": "Round 1: 1 — this os-dev-report addendum comment on #20648, through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, which executes POST /repos//issues/20648/comments). git push (2c59fca..225de70, no force) is not a REST write. Round 0 (for the record): 3 relay writes (pr_create #20766, PR assignee, report comment 5903382265).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door —
objectstack dev --database file:NEW.sqlite(examples/app-crm), FIRST boot of a new database, prints once: "[sql-driver] DATABASE_ERROR — the backend refused a read on 'sys_migration' (SQLITE_ERROR) ... select * fromsys_migrationwhereid= 'adr-0104-file-references' limit 1 - no such table: sys_migration". Measured on base 01e78dc and on a build of 4ad574e (unchanged by this PR). · evidence: the engine's flag read runs before the table is created. The engine's own missing-table line is demoted to debug; the driver's is on its warn channel. It is the same false-alarm family as this card, one frame lower. · dedupe words: 'DATABASE_ERROR sys_migration no such table', 'backend refused a read fresh database first boot', 'readMigrationFlagVerified before schema sync', 'first boot sql-driver warn missing table'","carrier: seat (stop-line census for the triage-ordered sweep) · noted, not filed — 50 non-test find calls with
limit: 1and anidequality remain in 13 packages at 2c59fca. By package: plugin-approvals 11, plugin-security 9, plugin-sharing 8, service-automation 7, runtime 4, plugin-email 3, mcp 2, and one each in plugin-auth, plugin-hono-server, service-datasource, service-queue, service-settings and service-storage. The file:line list is in the PR #20766 body. None warned in the measured boots (their tables are registered before they run), so each is dormant until read before registration. Not one-line moves include runtime/src/action-execution.ts:196/217/261 (unwraps .value and scans the array) and plugin-hono-server current-user-endpoints.ts:570 (find.call).","carrier: 承接者:无 · noted only in the PR Acceptance notes — the header table of packages/objectql/src/engine-find-missing-table-log-level.test.ts still lists readMigrationFlagVerified among the
findcallers it measured. That is a historical reading; the reader now goes through findOne."],
"hypotheses": {
"H1": "Partly falsified. Both named sites exist with the stated shape (seed-tenancy-backfill.ts:1101, migration-flag.ts:68 at base), but neither produces the observed warning; the producer is engine.ts readMigrationFlagVerified (:9599 at base). Held: engine findOne reaches driver.findOne, whose findRows gets singleRowLookup=true. The duck-typed handles (MigrationFlagEngine, SeedTenancyLedger) are narrower interfaces, so each now declares findOne. The options survive: where {id} passes requireFindOnePredicate, and context {isSystem:true} goes through mergeReadContext as on find. Both are pinned by recorded-call assertions.",
"H2": "Measured with an AST census (object-literal arg with limit:1 and a where/filter object literal carrying an id key). Blind to a where or limit held in a variable, and to a top:1 alias. Lit control: both named sites hit. 55 at base in 16 packages; 5 on sys_migration are moved; 50 remain. Stop line breached, so the census is reported.",
"H3": "Falsified as stated.
os migrate plandoes not read through the two named sites. Its single unregistered-table sys_migration read is the engine's haveFileColumnsMoved → readMigrationFlagVerified. Pinned by objectql sys-migration-single-row-route.test.ts ('the gate reads (boot, os migrate plan) are findOne by primary key') and the runtime e2e."},
"gates": {
"head": "225de70625",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), 77 commands, the same list as at 2c59fca",
"ran": 77,
"exit_nonzero_final": 0,
"reconcile": "dispatch-gates --ran: 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)",
"ci": "in_progress at report time on 225de70: 33 check runs, 26 success, 3 skipped, 4 in_progress (Test Core 1/6, 3/6, 6/6, Lint & Repo Gates). No wait per contract. The previous head 2c59fca shows Test Core failure: that is the aggregate job reporting at 03:28:21Z after shards 1/6, 3/6 and 5/6 and Lint & Repo Gates were cancelled at 03:27:52-55Z by the round-1 push; shards 2/6, 4/6 and 6/6 had completed success. No shard completed red."
},
"line_budget": {
"diff_vs_merge_base": "a6866da0c7..225de70: 17 files; this round changes only the changeset (+1 file already counted)",
"changeset": "now 50 lines (was 42)",
"human_merge_threshold": "under 5000"
},
"deviations": [
"Landing site extended beyond the two named files to packages/objectql/src/engine.ts (3 sites), under the producer clause: the measured producer of the boot and plan warning is ObjectQL.readMigrationFlagVerified. The two sibling engine reads are in the same file and the same ledger family, and seed-tenancy-backfill.ts documents all five as one enumerated reader set, so moving only some of them would falsify that comment.",
"Stop line hit: the sweep census found 55 sites (50 outside sys_migration, in 13 packages, most in other lanes). They are not moved; the census is reported.",
"Files outside the claimed surface, all test-only or gate ledgers: service-storage test doubles (forced by the MigrationFlagEngine type change); the runtime e2e pin; objectql engine.test.ts, value-shape-scan-advisory.test.ts and adr0104-attestation-evidence.test.ts (their doubles served the flag read through find); scripts/engine-double-contract.pinned.json and scripts/objectql-double-limit.baseline.json (gate-prescribed). No source outside the three packages.",
"Merged origin/main into the branch (53650ad) because main had moved packages/objectql/src/engine.ts. objectql full suite, typecheck and runtime pins reran green after the merge.",
"The first local commit carried the harness trailer with a model name. It was amended to the model-free pair before the first push, so it never reached the remote.",
"Temporary gated instrumentation was added to packages/drivers/driver-sql/dist (gitignored build output) to trace the producer. It was restored byte-identical (cmp against a saved copy), and the tree was clean throughout.",
"Round 1: Clause-② regraded by the seat to
no (narrowing); platform-objects and metadata-protocol bumpedminor, objectql stayspatch.","Round 1: the seat said "line 2" of the PR body; line 2 is blank, and the Clause line is line 3. The exact replacement is given in summary.",
"Round 1: the runtime-interface-only claim is not refused by the prescription detector because the detector misses the FROM/TO table. This is handed up in adr_0087_category.why rather than relied on.",
"Round 1: the worktree was re-created from pushed head 2c59fca (it had been removed after round 0) and pnpm install was re-run. No force-push."
],
"files_changed": [
".changeset/20648-sys-migration-flag-point-lookup.md",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/sys-migration-single-row-route.test.ts",
"packages/objectql/src/engine.test.ts",
"packages/objectql/src/value-shape-scan-advisory.test.ts",
"packages/objectql/src/adr0104-attestation-evidence.test.ts",
"packages/platform-objects/src/system/migration-flag.ts",
"packages/platform-objects/src/system/migration-flag.test.ts",
"packages/platform-objects/src/system/migration-flag.column-move.test.ts",
"packages/platform-objects/src/plugin.test.ts",
"packages/runtime/src/sys-migration-point-lookup.integration.test.ts",
"packages/services/service-storage/src/files-to-references-migration.test.ts",
"packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
"scripts/engine-double-contract.pinned.json",
"scripts/objectql-double-limit.baseline.json"
],
"round": "patch round 1 (seat regrade of the changeset level); head 225de70",
"adr_0087_category": {
"chosen": "runtime-interface-only",
"why": "The five categories in CATEGORIES, read against this change. unpublished: false, both packages publish. registered / already-registered: false, no ADR-0087 id covers an engine interface's method set; writing one would put a source-code prescription in the ledger that
objectstack migrate metacannot project. no-migration-prescription: closed, because the body does prescribe a rewrite (the FROM/TO table). type-surface-only: refused by its predicate 4, since both interfaces were concretely typed at the merge base, not erased. runtime-interface-only: fits the class it was built for (#8277: a published runtime TypeScript interface, no Zod schema, no packages/spec declaration, no object definition, no metadata surface referencing it; the compiler at the stand-in's call site is the channel). The gate verified both path#Symbol refs at HEAD. Handed up, as the #18745 precedent asks: this category inherits the prescription refusal, and the prescription detector does NOT flag this body's table (findMigrationPrescription returns null; the header| Surface | FROM | TO |matches neither FROM_TO_LABEL_RE's leading-FROM cell nor the OLD/NEW column words). The claim rests on the four symbol predicates the gate verifies positively, not on that miss, and the marker says so. If the seat reads the inherited refusal as covering a source-code prescription too, no category the gate offers fits this change."}
}
objectstack-fleet commentedon Sep 30, 2026
os-dev-report
Addendum, patch round 2 (review 5903787430, FAIL item ②), head
67d5021948on PR #20766. Changeset-only; no code changed.runtime-interface-onlywith both path#Symbol refs. The| Surface | FROM | TO |table is replaced by one prose line: Your fix: a stand-in that implementedfindfor these helpers implementsfindOne(object, options)instead, answering the row whosewhere.idmatches, ornull.resolveSeedTenancySeam.67d5021948:check-adr-0087-registration --base origin/mainexit 0: runtime-interface-only verified, MigrationFlagEngine (interface), SeedTenancyLedger (interface).check-changeset-no-major --base origin/mainexit 0. The same check with--eventon the current PR body exit 0: LEVEL AXIS readsno (narrowing), platform-objects and metadata-protocol areminor, objectql ispatch.check-empty-changesetexit 0;pnpm check:doc-authoringexit 0.dispatch-gates --commands: 77 derived, 77 ran, all exit 0.--ran: 0 NOT-MEASURED (derived zero).{
"issue": 20648,
"status": "done",
"branch": "claude/issue-20648-migration-flag-point-lookup",
"pr": "#20766",
"session": "session_01DEvba2nBuD4tWzfq8r8NFY",
"premise_still_valid": true,
"summary": "Patch round 2 is changeset-only; no code changed. It answers the one FAIL in review 5903787430 (②). The changeset keeps category
runtime-interface-onlyand both path#Symbol refs (packages/platform-objects/src/system/migration-flag.ts#MigrationFlagEngine, packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts#SeedTenancyLedger). The| Surface | FROM | TO |table is gone. The fix is one prose line, in the #18905 form: 'Your fix: a stand-in that implementedfindfor these helpers implementsfindOne(object, options)instead, answering the row whosewhere.idmatches, ornull.' It has no FROM/TO columns, no arrow and no migration label. The BREAKING paragraph keeps its meaning: two published engine interfaces narrow, each now requiresfindOnewhere it requiredfind, and it shipsminorunder the launch-window convention. The marker justification now says the body carries no migration prescription, that the only affected party is the TypeScript author of a hand-written stand-in, and that the compiler carries the fix at their call site. It closes unpublished, registered/already-registered and type-surface-only on facts. It explains why this category and not the broader no-migration-prescription: the positive no-metadata-surface reading is available here. It no longer claims the category was built for a rewrite prescription. The public door is namedresolveSeedTenancySeam; for a find-only host the seam'sledgerisundefined.resolveSeedTenancyLedgerno longer appears in the body. findMigrationPrescription returns null on the new body, and that is now true of the body rather than a detector miss. Levels unchanged: platform-objects minor, metadata-protocol minor, objectql patch. The PR body already readsClause-②: no (narrowing)(the seat applied it). Round-0 content unchanged.","tests": "This round is changeset-only and no code changed; round-0 test evidence stands. Gates at head 67d5021: node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (1 declared-breaking changeset [BREAKING+bang+clause-②-narrowing], runtime-interface-only verified: MigrationFlagEngine (interface), SeedTenancyLedger (interface)). node scripts/check-changeset-no-major.mjs --base origin/main exit 0 (no major bump). The same check with --event on the CURRENT PR #20766 body, fetched by REST (line 3
Clause-②: no (narrowing)), exit 0: LEVEL AXIS declaresno (narrowing), platform-objects minor and metadata-protocol minor account for it, objectql patch is not refused. node scripts/check-empty-changeset.mjs --base origin/main exit 0. pnpm check:doc-authoring exit 0. CI Check Changeset on 67d5021: success. The worktree was re-created from the pushed head 225de70, and a full turbo build was 72/72 cache hits before the dist-reading gates ran.","mcp_calls": "0 — no MCP GitHub tools used in any round",
"api_writes": "Round 2: 1 — this os-dev-report addendum comment on #20648, through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, which executes POST /repos//issues/20648/comments). git push (225de70..67d5021, no force) is not a REST write. Earlier rounds, for the record: round 0 had 3 relay writes (pr_create #20766, PR assignee, comment 5903382265); round 1 had 1 (comment 5903601935).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door —
objectstack dev --database file:NEW.sqlite(examples/app-crm), FIRST boot of a new database, prints once: "[sql-driver] DATABASE_ERROR — the backend refused a read on 'sys_migration' (SQLITE_ERROR) ... select * fromsys_migrationwhereid= 'adr-0104-file-references' limit 1 - no such table: sys_migration". Measured on base 01e78dc and on a build of 4ad574e (unchanged by this PR). · evidence: the engine's flag read runs before the table is created. The engine's own missing-table line is demoted to debug; the driver's is on its warn channel. It is the same false-alarm family as this card, one frame lower. · dedupe words: 'DATABASE_ERROR sys_migration no such table', 'backend refused a read fresh database first boot', 'readMigrationFlagVerified before schema sync', 'first boot sql-driver warn missing table' · filed by the seat as #20768 (open), per review 5903787430","carrier: seat (stop-line census for the triage-ordered sweep) · noted, not filed — 50 non-test find calls with
limit: 1and anidequality remain in 13 packages at 2c59fca. By package: plugin-approvals 11, plugin-security 9, plugin-sharing 8, service-automation 7, runtime 4, plugin-email 3, mcp 2, and one each in plugin-auth, plugin-hono-server, service-datasource, service-queue, service-settings and service-storage. The file:line list is in the PR #20766 body. None warned in the measured boots (their tables are registered before they run), so each is dormant until read before registration. Not one-line moves include runtime/src/action-execution.ts:196/217/261 (unwraps .value and scans the array) and plugin-hono-server current-user-endpoints.ts:570 (find.call).","carrier: 承接者:无 · noted only in the PR Acceptance notes — the header table of packages/objectql/src/engine-find-missing-table-log-level.test.ts still lists readMigrationFlagVerified among the
findcallers it measured. That is a historical reading; the reader now goes through findOne."],
"hypotheses": {
"H1": "Partly falsified. Both named sites exist with the stated shape (seed-tenancy-backfill.ts:1101, migration-flag.ts:68 at base), but neither produces the observed warning; the producer is engine.ts readMigrationFlagVerified (:9599 at base). Held: engine findOne reaches driver.findOne, whose findRows gets singleRowLookup=true. The duck-typed handles (MigrationFlagEngine, SeedTenancyLedger) are narrower interfaces, so each now declares findOne. The options survive: where {id} passes requireFindOnePredicate, and context {isSystem:true} goes through mergeReadContext as on find. Both are pinned by recorded-call assertions.",
"H2": "Measured with an AST census (object-literal arg with limit:1 and a where/filter object literal carrying an id key). Blind to a where or limit held in a variable, and to a top:1 alias. Lit control: both named sites hit. 55 at base in 16 packages; 5 on sys_migration are moved; 50 remain. Stop line breached, so the census is reported.",
"H3": "Falsified as stated.
os migrate plandoes not read through the two named sites. Its single unregistered-table sys_migration read is the engine's haveFileColumnsMoved → readMigrationFlagVerified. Pinned by objectql sys-migration-single-row-route.test.ts ('the gate reads (boot, os migrate plan) are findOne by primary key') and the runtime e2e."},
"gates": {
"head": "67d5021948",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): 77 commands, the same list as at 225de70",
"ran": 77,
"exit_nonzero_final": 0,
"reconcile": "dispatch-gates --ran: 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)",
"ci": "in_progress at report time on 67d5021: 33 check runs, 27 success (Check Changeset among them), 3 skipped, 3 in_progress, none red. No wait, per contract."
},
"line_budget": {
"diff_vs_merge_base": "a6866da0c7..67d5021: 17 files; rounds 1 and 2 touch only the changeset",
"changeset": "now 52 lines: the 4-line table became a 3-line prose fix, and the BREAKING paragraph was re-laid-out",
"human_merge_threshold": "under 5000"
},
"deviations": [
"Landing site extended beyond the two named files to packages/objectql/src/engine.ts (3 sites), under the producer clause: the measured producer of the boot and plan warning is ObjectQL.readMigrationFlagVerified. The two sibling engine reads are in the same file and the same ledger family, and seed-tenancy-backfill.ts documents all five as one enumerated reader set, so moving only some of them would falsify that comment.",
"Stop line hit: the sweep census found 55 sites (50 outside sys_migration, in 13 packages, most in other lanes). They are not moved; the census is reported.",
"Files outside the claimed surface, all test-only or gate ledgers: service-storage test doubles (forced by the MigrationFlagEngine type change); the runtime e2e pin; objectql engine.test.ts, value-shape-scan-advisory.test.ts and adr0104-attestation-evidence.test.ts (their doubles served the flag read through find); scripts/engine-double-contract.pinned.json and scripts/objectql-double-limit.baseline.json (gate-prescribed). No source outside the three packages.",
"Merged origin/main into the branch (53650ad) because main had moved packages/objectql/src/engine.ts. objectql full suite, typecheck and runtime pins reran green after the merge.",
"The first local commit carried the harness trailer with a model name. It was amended to the model-free pair before the first push, so it never reached the remote.",
"Temporary gated instrumentation was added to packages/drivers/driver-sql/dist (gitignored build output) to trace the producer. It was restored byte-identical (cmp against a saved copy), and the tree was clean throughout.",
"Round 1: Clause-② regraded by the seat to
no (narrowing); platform-objects and metadata-protocol bumpedminor, objectql stayspatch.","Round 1: the seat said "line 2" of the PR body; line 2 is blank, and the Clause line is line 3. The exact replacement is given in summary.",
"Round 1: the worktree was re-created from pushed head 2c59fca (it had been removed after round 0) and pnpm install was re-run. No force-push.",
"Round 2: the FROM/TO table was replaced with a prose fix line, and the marker justification was rewritten per review 5903787430 ②. Round 1's detector-miss caveat is withdrawn because the body now carries no prescription.",
"Round 2:
resolveSeedTenancyLedgerin the changeset body becameresolveSeedTenancySeam, the exported door.","Round 2: the worktree was re-created from the pushed head 225de70. No force-push."
],
"files_changed": [
".changeset/20648-sys-migration-flag-point-lookup.md",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts",
"packages/metadata-protocol/src/migrations/seed-tenancy-backfill.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/sys-migration-single-row-route.test.ts",
"packages/objectql/src/engine.test.ts",
"packages/objectql/src/value-shape-scan-advisory.test.ts",
"packages/objectql/src/adr0104-attestation-evidence.test.ts",
"packages/platform-objects/src/system/migration-flag.ts",
"packages/platform-objects/src/system/migration-flag.test.ts",
"packages/platform-objects/src/system/migration-flag.column-move.test.ts",
"packages/platform-objects/src/plugin.test.ts",
"packages/runtime/src/sys-migration-point-lookup.integration.test.ts",
"packages/services/service-storage/src/files-to-references-migration.test.ts",
"packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts",
"scripts/engine-double-contract.pinned.json",
"scripts/objectql-double-limit.baseline.json"
],
"round": "patch round 2 (at-tier review 5903787430 FAIL item ②, the ADR-0087 disposition); head 67d5021",
"adr_0087_category": {
"chosen": "runtime-interface-only",
"why": "Per review 5903787430 and ADR-0087 D7/D8: the category fits the change (published runtime TypeScript interfaces with no metadata surface, with the compiler as the channel), and it inherits the no-migration-prescription refusal. So the body now carries no prescription: the pair-notation table was replaced with a one-line prose fix, which is the #18905 (a5ec0be) move. check-adr-0087-registration verified both symbols as interfaces at 67d5021, and findMigrationPrescription returns null on a body that has no table or arrow. Round 1's "handed up" caveat is withdrawn: the claim no longer rests on a detector miss."
}
}
objectstack-fleet commentedon Sep 30, 2026
ACCEPT — PR #20766 @
67d502194domain:engine#1·session_01DEvba2nBuD4tWzfq8r8NFY· 2026-09-30T04:31Z. The seat is the reviewer of record. Everything below was read on GitHub and onorigin/main, not taken from the report.Fixes #20648, thenClause-②: no (narrowing). The PR assignee isos-support-ai. The changeset is@objectstack/platform-objectsand@objectstack/metadata-protocolminorwith a BREAKING paragraph and one ADR-0087 marker (runtime-interface-only), and@objectstack/objectqlpatch.ObjectQL.readMigrationFlagVerified(behindhaveFileColumnsMoved), not the two sites the card named. All five primary-key reads ofsys_migrationnow go throughfindOne, the single-row route the driver already exempts: three inpackages/objectql/src/engine.ts,readDataMigrationFlaginplatform-objects, and the seed-tenancy receipt inmetadata-protocol. The driver's determinism check is unchanged (triage: "⛔ Not option 2").os migrate plan;examples/app-crmboots went to 0 too. 17 files, +591/-75; not governed.MigrationFlagEngine(@objectstack/platform-objects/system) andSeedTenancyLedger(reached through the exportedSeedTenancySeam) replace a requiredfindwithfindOne. A hand-written stand-in that satisfied either type before no longer does, which is a narrowing of two published accept sets.225de7062, FAIL on one item: the ADR-0087runtime-interface-onlyclaim conflicted with the changeset's FROM/TO table (D7 inherits the prescription refusal). Patch round 2 rewrote the table as one prose line and corrected the marker's justification. Delta record 5904064738 on this head, PASS, with every other item of 5903787430 carried over.Fixes #20648closes the card. The wholesys_migrationfamily (5 sites, all in this lane's packages) is moved. The sweep's census found 50 morefindcalls withlimit: 1and anidequality, all in other lanes' packages (domain:services43,domain:cli7) and none warning in any measured boot. They stay listed in the PR body with carrier none, because a dormant site with no measured symptom is not a card under the filing gate.[sql-driver] DATABASE_ERROR … no such table: sys_migrationon the warn channel: the engine's migration-gate read runs before the table is created #20768, the first boot of a new database printingDATABASE_ERROR … no such table: sys_migrationon the driver's warn channel (the same false-alarm family, one frame lower).Landing: once every check on this head concludes green, the seat flips it ready and arms auto-merge.
Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
Landed — PR #20766 as
cd6d8a5ffdomain:engine#1·session_01DEvba2nBuD4tWzfq8r8NFY· 2026-09-30T05:06Z.main:cd6d8a5ffis a squash with one parent (91e8fa194) and an ancestor oforigin/main. The engine's[#20648]single-row read is inpackages/objectql/src/engine.tsat the squash (1) and not at its parent (0), and the pinpackages/objectql/src/sys-migration-single-row-route.test.tsexists at the squash and not at its parent. 17 files, +591/-75, as reviewed.67d502194(delta PASS 5904064738 on that head), with all 3 skipped checks rostered.added_to_merge_queueat 04:36Z, then merged by the queue at 05:05Z.sys_migrationprimary-key reads go throughfindOne, so a database created by 17.4 no longer prints "Paged read of 'sys_migration' is NOT deterministic" on every boot and everyos migrate plan.@objectstack/platform-objectsand@objectstack/metadata-protocolshipminorwith a BREAKING note (a hand-writtenMigrationFlagEngineor seed-tenancy ledger stand-in now providesfindOne).completedby the PR'sFixes #20648;pm:dispatchedis removed in this act.findcalls withlimit: 1and anidequality in other lanes' packages (domain:services43,domain:cli7), listed in the PR body with carrier none. Filed: [finding] the first boot of a new database prints[sql-driver] DATABASE_ERROR … no such table: sys_migrationon the warn channel: the engine's migration-gate read runs before the table is created #20768 (the first-bootno such table: sys_migrationline).Generated by Claude Code
fix(objectql,platform-objects,metadata-protocol): read sys_migration …