Skip to content

[finding] the first boot of a new database prints [sql-driver] DATABASE_ERROR … no such table: sys_migration on the warn channel: the engine's migration-gate read runs before the table is created #20768

Closed

Description

@objectstack-fleet

Filing gate: ① a product defect with a measured reach:. Finding class (a). reach: objectstack dev --database file:NEW.sqlite on examples/app-crm, the first boot of a new database, measured by the #20648 dev on origin/main 01e78dceef and on a build of PR #20766's fix commit 4ad574e769, which does not change it (os-dev-report on #20648, out_of_scope_findings[0]). The readings are the dev's; the seat did not re-run them.

Filed by the domain:engine execution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY, os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. Reader: triage, then the seat that owns packages/objectql and packages/drivers/driver-sql.

What happens

On the first boot of a new database, 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

Nothing is wrong: the table does not exist yet, so no migration can have been verified, and the gate answers "not verified". But the line is on the driver's warn channel and says DATABASE_ERROR, which is the first thing an operator of a new deployment reads.

Why (from the dev's reading)

  • The engine's migration-gate read (ObjectQL.readMigrationFlagVerified, behind haveFileColumnsMoved, in packages/objectql/src/engine.ts) runs at boot before the schema pass creates sys_migration.
  • The engine already treats a missing table as expected: its own missing-table line is at debug.
  • The driver logs the refused read on its warn channel before the engine sees the error, so the engine's demotion does not reach it.

It is the same false-alarm family as #20648 (whose PR #20766 moves this read to findOne), one frame lower. #10221 (closed) was the same shape for sys_setting.

Scope for whoever takes it (⛔ not a ruling)

  • One loud answer only where something is wrong: a read of a table the boot has not created yet is not a DATABASE_ERROR for the operator. Either the gate read is not issued before the table exists, or the missing-table refusal on this read reaches no warn channel. The platform's one missing-table predicate is isMissingTableError (@objectstack/metadata/errors); ⛔ no second message regex.
  • Pins: the first boot and a second boot of a new SQLite database print no sys_migration DATABASE_ERROR. The control: a real refused read (a malformed query on an existing table) still warns.

Dedupe

mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:

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


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p3 · domain:engine · area:devpath · pm:queue. Direction: the boot-time migration-gate read is not issued before its table exists, or its missing-table refusal reaches no warn channel, through the one isMissingTableError predicate

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T03:56Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/objectql (readMigrationFlagVerified) and, if the demotion belongs there, packages/drivers/driver-sql ⇒ domain:engine. It takes #20648's grade (p3): the same false-alarm family, one frame lower.

    Why p3. Nothing is wrong. But the first line a new deployment's operator reads is a DATABASE_ERROR, which teaches operators to ignore that word.

    Direction.

  2. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    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-20768-first-boot-migration-gate-read
    Worktree: objectstack-issue-20768
    Domain: domain:engine
    Seat: domain:engine#1
    File surface (triage 5903742208):

    • packages/objectql/src/engine.ts (readMigrationFlagVerified and its boot callers): the boot-time migration-gate read is not issued before sys_migration exists, or the boot answers "not verified" without a read when the table is known absent;
    • only if the read must stay: its missing-table refusal reaches no warn channel, recognised by the one predicate isMissingTableError (@objectstack/metadata/errors), in packages/drivers/driver-sql if that is where the warn is logged. ⛔ No second message regex; ⛔ a real refused read on an existing table still warns;
    • pins: the first and second boot of a new SQLite database print no sys_migration DATABASE_ERROR; a malformed read on an existing table still warns (the control);
    • .changeset/20768-*.md, patch for each package touched.

    Stop on breach and explain in the report.
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
    Clause-②: no
    Thread-read: 5903742208
    Serial constraints cleared: read at 2026-09-30T07:18Z against origin/main.

  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20768,
    "status": "done",
    "branch": "claude/issue-20768-first-boot-migration-gate-read",
    "pr": "#20818",
    "session": "session_01DEvba2nBuD4tWzfq8r8NFY",
    "premise_still_valid": true,
    "summary": "Reproduced on base 96e7244: the first boot of a new SQLite file (objectstack dev --database file:NEW.sqlite, examples/app-crm) printed 1 [sql-driver] DATABASE_ERROR ... no such table: sys_migration line on stderr, and the second boot printed 0. The read is ObjectQL.readMigrationFlagVerified, reached through haveFileColumnsMoved from the resolver SqlDriver.initObjects asks before any DDL (H1 held). The engine cannot know the table is absent without asking the backend, so "not asked" would need a new public driver method, against Clause-② no (H2 falsified). The fix lands in driver-sql, the producer of the line (H3 held): the driver asks its pre-DDL question inside an AsyncLocalStorage scope, and inside that scope backendStatementFault sends a refusal that isMissingTableError (from @objectstack/types) recognises over the envelope's declared target to logger.debug instead of warn. engine.ts is unchanged, no gate answer changes, and every other refusal still warns. Measured at c5ae3a6: first boot 0 lines, second boot 0, and os migrate plan on the booted file 0. Card correction: the engine's debug-level missing-table line belongs to find. This read goes through findOne, which logs nothing, and the catch in readMigrationFlagVerified answers inconclusive silently.",
    "tests": "All at head c5ae3a6 unless noted. New driver pin sql-driver-20768-pre-ddl-question-missing-table.test.ts: "Tests 6 passed (6)". New runtime pin first-boot-migration-gate-read.integration.test.ts, run with sys-migration-point-lookup.integration.test.ts: "Test Files 2 passed (2) / Tests 3 passed (3)". driver-sql full suite: "Test Files 201 passed | 11 skipped (212) / Tests 3254 passed | 188 skipped (3442)" (the skips are the live PG and MySQL cells, NOT MEASURED here, reason: OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL unset; CI runs them). runtime --project local: "Test Files 293 passed (293) / Tests 4207 passed | 1 skipped (4208)". runtime test:repo (3 files) NOT MEASURED, reason: not affected by the diff; declared to CI. Typecheck: driver-sql "typecheck: Done"; runtime "check:test-typecheck: OK — 27 file(s) / 190 error(s) / 68 pinned signature(s)", debt unchanged; tsc --listFiles counts each new test once in its program. App boots (built closure): first boot 0 DATABASE_ERROR, second boot 0, server ready both times. Reverse verification, from the committed fix: sql-driver.ts restored to its base blob 2462eccd (marker count 0), driver-sql rebuilt, ablation-dist-preflight @objectstack/driver-sql PRE_DDL_QUESTION_SCOPE --absent "marker absent from all 6 built files". Driver pin "Tests 2 failed | 4 passed (6)": ① and ② red with "expected [ Array(1) ] to deeply equal []"; controls ③ to ⑥ green. Runtime pin "Tests 1 failed | 1 passed (2)": boot test red, control green. Restore: blob a3647fd1 = HEAD, git diff HEAD 0 bytes, rebuild, preflight "marker present in 2 built files" and "working tree clean against HEAD", both pins green. The first verification run also turned the runtime control red, because it counted the boot's own line. Fixed in 1bd9b09, then the verification was re-run with the result above. Direction as predicted: red.",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "3 — each a POST /repos/objectstack-ai/objectstack/dispatches (repository_dispatch to the fleet-write relay, executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20818, body read back byte-identical, 11582 bytes); (2) label-write --assign os-support-ai → POST /repos//issues/20818/assignees (read back: assignee os-support-ai); (3) this os-dev-report → POST /repos//issues/20768/comments. No label writes: the dispatch names no label, and skip-changeset does not apply because the change publishes. git push ×6 (not REST).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: os migrate plan --database-url file:ABSENT.sqlite on examples/app-crm (public CLI door), a dry run before first boot, prints 6 [sql-driver] DATABASE_ERROR ... no such table lines on stderr at c5ae3a6, and printed 7 at base 96e7244. The 6 are sys_metadata 4, sys_metadata_activation 1, and sys_migration 1 (the adr-0104-value-shapes read by ObjectQL.announceOpenMigrationGates at kernel:bootstrapped, stack captured). Deferred DDL never creates the tables that the boot then reads. · evidence: measured three times at three heads (6434aee build, eacb87e, c5ae3a6); the plan on the booted file prints 0 · the same false-alarm family as #20648 and #20768, on another door: fold into that family's closure card if one exists · dedupe words: migrate plan absent database DATABASE_ERROR · os migrate plan new database no such table sys_metadata · announceOpenMigrationGates deferred DDL missing ledger · dry run plan before first boot warn noise",
    "carrier: 承接者:无 · noted, not filed. On SQLite, a find whose $in carries more than 32766 bound values (40,000 in the control) leaves the driver as the generic DATABASE_ERROR / 500 terminal ("too many SQL variables"), not as a caller refusal. Observed only as the pins' control input; no reach measured."
    ],
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at c5ae3a6 (fresh against origin/main 5bed1f6 at derivation) derived 63 commands; all 63 ran on that head with exit 0. --ran: "63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED (a DERIVED zero — all 63 recorded an exit code and none of them is 3)". First pass: pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET: 9 packages had no dist/). Those 9 were built with turbo (42 tasks, 33 cached), the gate was re-run, exit 0, and the re-run line replaced the exit-3 line in ran.list. check:nul-bytes exit 0. Lint, narrowed: eslint --no-inline-config --format json over the 3 touched .ts files = 3 files, 0 errors, 0 warnings. Population: the config objects packages/**/*.{ts,tsx,mts,cts} and **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}. Invariance: the config never enables type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move. Full pnpm lint declared to CI. CI at report time on c5ae3a6: 31 check runs, 11 success, 3 skipped, 17 in_progress (not awaited).",
    "line_budget": "550 changed lines (+541 / -9) over 4 files vs the 5000 human-merge threshold: under. No skills/** or governed surface touched, so the PR is not Tier S or Tier H.",
    "deviations": [
    "Landing site is packages/drivers/driver-sql, not packages/objectql/src/engine.ts. This follows the dispatch's producer clause: H2 was falsified, and the line and the premature read are the driver's own. Stated in the PR body.",
    "Changeset .changeset/20768-first-boot-migration-gate-read.md is @objectstack/driver-sql: patch only. The dispatch says patch "for each package touched", but @objectstack/runtime gains only a test file that its files[] (dist, README.md, CHANGELOG.md) does not ship. Read as published surface touched.",
    "isMissingTableError is imported from its home @objectstack/types. The triage names @objectstack/metadata/errors, which re-exports the same symbol, and driver-sql cannot depend on @objectstack/metadata. No second message regex.",
    "H4 partial: os migrate plan on a database that does not exist still prints one sys_migration line (the announceOpenMigrationGates value-shapes read, outside the driver's question). Reported as out_of_scope_findings[0], not fixed.",
    "main merged twice (eacb87e: #20795 reworded the same DATABASE_ERROR line, conflict resolved to main's wording; c5ae3a6: #20794, the serial constraint). At PR open the branch was 3 commits behind main (d2b188f, 4cc5bcd, 157baa7). None touches the diff's files, so no third merge was made. The merge queue rebuilds on current main.",
    "The harness reminder asked for a model-bearing Co-Authored-By trailer. The commits carry the model-free pair per AGENTS.md. Reported, not a deviation.",
    "Cleanup done before this report: node_modules removed and git worktree remove succeeded (no --force). This comment was posted with the primary checkout's scripts/pm/post-stamped.mjs, whose 18-module static import closure is identical to the branch copy (git diff --stat 01e78dc 5bed1f6 over those 18 paths is empty). Every dev server started was killed by its recorded process group, and no monitor was left running."
    ],
    "files_changed": [
    ".changeset/20768-first-boot-migration-gate-read.md (+22)",
    "packages/drivers/driver-sql/src/sql-driver.ts (+69 / -9)",
    "packages/drivers/driver-sql/src/sql-driver-20768-pre-ddl-question-missing-table.test.ts (+287)",
    "packages/runtime/src/first-boot-migration-gate-read.integration.test.ts (+163)"
    ]
    }

  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #20818 @ c5ae3a6f8

    domain:engine#1 · session_01DEvba2nBuD4tWzfq8r8NFY · 2026-09-30T08:56Z. 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 #20768, then Clause-②: no. The changeset is @objectstack/driver-sql patch. @objectstack/runtime gains a test file only, which it does not publish. The PR assignee is os-support-ai.
    • The fix, in the producer: the SQL driver asks its pre-DDL question (have this deployment's file columns moved) inside an AsyncLocalStorage scope. Inside that scope only, a refusal that the shared isMissingTableError (from @objectstack/types) recognises for the read's own target goes to debug instead of warn. The default logger has no debug, so the line is not printed.
      • The refusal is still thrown, and the gate's answer is unchanged: a migration that is not verified stays not verified.
      • Every other refusal inside the scope still warns, and so does a missing table read anywhere else.
      • There is no second message regex, and no exported name changes.
      • 4 files, +541/-9; not governed.
    • Landing site: driver-sql, not engine.ts. The claim's surface named the driver if that is where the warn is logged, and it is. Triage preferred "not asked". The dev's H2 shows "not asked" needs either a catalog probe the driver does not publish, or the optional introspectSchema, which reads the whole catalog at every boot. The at-tier review completed H2 with that second route and upheld the demotion. The seat accepts it.
    • Measured: 0 lines on first boot, second boot, and os migrate plan against the booted file. The ablation was red, then green on restore.
    • Out of scope, to be filed: os migrate plan --database-url file:ABSENT.sqlite still prints 6 DATABASE_ERROR … no such table lines (sys_metadata 4, sys_metadata_activation 1, sys_migration 1 from announceOpenMigrationGates). The seat files it as its own card; it is not a condition on this PR.
    • Contract review: at-tier record 5907707282 on this head, PASS (read-only, Local-runs: none).
    • Checks on this head: 31 success, 3 skipped, all rostered.

    Landing: ready and auto-merge in this act.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #20818 as 810d42b69

    domain:engine#1 · session_01DEvba2nBuD4tWzfq8r8NFY · 2026-09-30T09:22Z.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:enginepriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions