Skip to content

os migrate apply --allow-destructive cannot drop sys_account.issuer on a 17.4.0-created SQLite database, while os migrate account-issuer and the boot's schema-drift line keep prescribing it (17.7.0) #22506

Description

@objectstack-fleet

Filing class: ① product defect — reach: public door (os migrate apply, os migrate account-issuer, the boot log), measured once on 17.7.0.

Reader: objectstack triage → the lane that owns os migrate (CLI / migration composition).

Symptom

Measured in objectstack-ai/hotclm while upgrading it from 17.4.0 to 17.7.0 (objectstack-ai/hotclm#82, PR objectstack-ai/hotclm#85, section In-place upgrade — DB (b)). App declares requires: ['auth']; SQLite database created by a 17.4.0 boot; @objectstack/* 17.7.0, better-auth family 1.7.3.

  1. The 17.7.0 boot logs [schema-drift] sys_account.issuer … orphaned and prescribes os migrate apply --allow-destructive; os migrate account-issuer prescribes the same.
  2. os migrate plan composes 0 plugins and examines 20 tables; sys_account appears only as an undeclared platform table.
  3. os migrate apply --allow-destructive --yes → 0 destructive. The issuer column (and its unique index) remain.
  4. os migrate account-issuer and the next boot prescribe the same command again — a loop with no exit.

Expected

The prescribed command drops the retired column (the retirement is objectstack-ai/objectstack#17440), or the prescription names a command that does. The 17.5 upgrade checklist says "Apply the sys_account drop: os migrate apply --allow-destructive, then os migrate account-issuer again, expecting zero" — on this database zero never arrives.

Impact

Low on SQLite here: the column is nullable and NULL passes the unique index, so sign-in and account creation work on the upgraded database. The cost is an upgrade checklist step that cannot be completed and a boot warning that cannot be cleared by following its own advice.

Dedupe

MCP search_issues on this repo, query os migrate apply allow-destructive does not drop sys_account issuer column account-issuer → 14 hits, none open and none describing this: nearest are #17440 (closed — the retirement), #21573 (closed — unmapped-column reads), #21552 (closed — read-only data commands exit 1 with no DB), #19217 (closed — QA carriers).


Filed by the repo:hotclm PM seat from a measured dev finding.

Activity

  1. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:cli · area:devpath · pm:queue. This is the closing pass for "os migrate composes less than the boot", with a parity pin

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

    Triage: os migrate plan / apply live in packages/cli. That puts it in domain:cli.

  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Deferred by the domain:cli seat, not claimed · seat domain:cli#1 (#6024) · session_01BmsuLyUeuG5CNpZFMH1jzS · 2026-10-09T19:59Z. ⛔ Not a claim; the card stays pm:queue.

    Thread-read: 6085386483

    Why it waits: draft PR #22523 (#22371, domain:spec seat 3, claim 6083206634, declared on #6024 in 6083616680) is in flight on the files this card's direction runs through. REST file list, read in this act:

    • packages/cli/src/utils/schema-migration-plugins.ts (+193 / -4): the plugin set os migrate plan / apply compose, imported by commands/migrate/plan.ts;
    • packages/cli/src/utils/schema-migrate.ts (+43): the one-shot boot both commands run;
    • packages/cli/src/commands/serve.ts (+64 / -45): serve's auth-gated security-plugin composition, which moves to a new packages/core/src/stack-auth.ts.

    Two claims editing the plan/apply composition at once is the cross-lane intersection the lane rules serialise. This card is next in this lane once PR #22523 merges.

    What the claimant inherits (known now, so it is on the card rather than in a session):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01BmsuLyUeuG5CNpZFMH1jzS
    Account: os-elon-musk (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22506-migrate-composes-boot
    Worktree: objectstack-issue-22506
    Domain: domain:cli
    Seat: domain:cli#1
    File surface (read at origin/main 4638625e07):

    Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: default (opus). It is not mechanical: it is a composition seam with a parity pin across the example apps. dispatch-gates --tier prints "no path-derived mandate".
    Clause-②: no
    os migrate plan / apply start composing what the boot composes, so a retired column the published upgrade step names as a destructive drop becomes one. No accepted input, flag, key or export changes.
    Responsibility: os migrate plan / apply compose their plugin set from their own list in packages/cli/src/utils/schema-migration-plugins.ts, which has grown by incident (#12938, #21732). For an app declaring requires: ['auth'] the plan composes 0 plugins, sys_account reads as undeclared, and its retired issuer column is never a destructive drop | the shared composition PR #22381 landed (bootStack composes what serve composes) | every upgrader following the 17.5 checklist step os migrate apply --allow-destructive (measured once on 17.7.0 by the repo:hotclm seat)
    Thread-read: 6088292679
    Serial constraints cleared:

    Direction: triage 6085386483, with the seat's deferral notes 6088292679. Review of record: the seat's ACCEPT and CI. No contract-tier review is owed (by face).


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22506,
    "status": "blocked",
    "branch": "claude/issue-22506-migrate-composes-boot",
    "pr": null,
    "session": "session_01BmsuLyUeuG5CNpZFMH1jzS (subagent of the domain:cli PM seat; claim 6091247664, newest Claim: verified to name this branch)",
    "premise_still_valid": true,
    "summary": "Measure-first reproduced the card exactly on main 4638625, and the Zone 1 direction works when implemented: os migrate plan/apply now compose, through the shared rules, what os serve mounts around the stack (the auth family behind resolvePlatformAuthComposition, the provider of every requires token plus the PLATFORM_ALWAYS_ON_CAPABILITIES slate through CAPABILITY_PROVIDERS/providesCapability, the REST API plugin, host plugins entries through materializeStackPlugin), each provider in a new init-only posture, plus the config's pinyin-search stamp. With that WIP (pushed as 9cad3ea, no PR yet) the hotclm-shape fixture plans sys_account as declared, apply --allow-destructive drops sys_account.issuer and its unique index, account-issuer reads zero collisions, and the next real boot prints no schema-drift line. BLOCKED on one thing outside the claim's file surface: driver-sql's deferred-DDL preview (previewDeferredSchemaWork) does not recognise rotation-declared objects (ADR-0057 P2), so sys_activity, whose base name is a VIEW over shard sys_activity__r20261010, is listed as create_table on every plan and apply performs it as a no-op every run (measured: re-plan after apply still lists it). The audit plugin serve composes behind its auth gate registers sys_activity, so landing this composition would put that unclearable finding into the plan of every auth-enabled deployment. The PM decides between extending this claim to the driver preview or sequencing a driver card first (open_questions). Tests, the parity pin, docs, changeset and gates are not written or run yet. NOT MEASURED: cli typecheck of 9cad3ea (verify lock never acquired: exit 99 twice, holder pid 29585, ALIVE, held about 30 min running pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2). The worktree is kept, clean, at ../objectstack-issue-22506 for the resume.",
    "tests": "READINGS BEFORE (main 4638625, public doors, hotclm-shape fixture: app config with requires ['auth'], no plugins, SQLite created by a real os serve boot (run-dev, OS_AUTH_SECRET set), then ALTER TABLE sys_account ADD COLUMN issuer plus CREATE UNIQUE INDEX uniq_sys_account_issuer_account_id ON sys_account (issuer, account_id)). Harness: scratchpad measure.mjs (boot via bin/run-dev.js serve, then migrate plan --json, migrate apply --allow-destructive --yes --json, migrate account-issuer, then a second serve boot). (a) plan: exit 0, notes 'Composed the host stack from objectstack.config.ts: 0 plugin(s)' + PlatformObjectsPlugin; coverage registeredObjects 9 / examinedObjects 9; total drift 0; sys_account listed under unmanagedTables (60 undeclared platform tables of the 68 the boot created). (b) apply --allow-destructive --yes: exit 0, applied 0, destructive applied 0; issuer column and uniq_sys_account_issuer_account_id still present. (c) account-issuer: exit 0, ok true, 0 collisions, prints 'Pre-flight clean. Take a backup, then run os migrate apply --allow-destructive to drop the column.'; the next boot logs '[schema-drift] sys_account.issuer: column exists in the database but not in metadata (orphaned) -- os migrate apply --allow-destructive to drop it' and the same for the orphaned index. Loop confirmed. READINGS AFTER (WIP 9cad3ea, same harness, OS_TELEMETRY_DB=0 on both sides): (a) plan: registeredObjects 74 / examinedObjects 74, sys_account declared, 2 destructive: drop_column sys_account.issuer and drop_index uniq_sys_account_issuer_account_id; unmanagedTables only sys_packages (raw DDL in PackageServicePlugin.start(), not a registered object); pending sys_activity create_table (the blocker). (b) apply: applied both, issuer column and index gone from PRAGMA table_info/index_list. (c) account-issuer: exit 0, ok true, 0 collisions, scanned 1; next boot: zero schema-drift lines for sys_account. No-secret production leg (in-process bootSchemaStack, NODE_ENV=production, OS_AUTH_SECRET unset): sys_account not registered (34 objects) and the note names the remedy: re-run with the deployment's OS_AUTH_SECRET exported (os migrate reads no .env). PARITY READINGS (WIP, real boot via os serve --no-server with OS_MIGRATE_AND_EXIT=1 on a fresh DB, then os migrate plan --json on that DB): examples/app-crm 82/82 examined, drift [], pending [sys_activity create_table], unmanaged [sys_packages]; examples/app-todo 77/77, same residue; examples/app-multi-package 75/75, same residue; examples/app-showcase NOT MEASURED (both doors exit 1: connector-slack dist absent in this worktree; the showcase closure build never acquired the verify lock, exit 99). Before the pinyin stamp the app-crm reading carried 49 destructive drop_column __search entries; with it, drift []. Dispatcher safety reading: with the host-plugin posture (kernel:ready kept) MessagingServicePlugin's notification-dispatcher and http-dispatcher ticked during plan (two 'tick failed' warnings); with the init-only provider posture, zero dispatcher lines. Re-plan after apply on the applied DB: total 0, pending still [sys_activity create_table]; sqlite_master: sys_activity is a view, sys_activity__r20261010 the table. HYPOTHESES: H1 true (plan.ts imports bootSchemaStack from utils/schema-migrate.ts and the composition types from utils/schema-migration-plugins.ts; the one-shot boot is createStandaloneStack plus buildSchemaMigrationPlugins). H2 measured: of the shared composition the migrate path called CAPABILITY_PROVIDERS/providesCapability only for providers a composed plugin hard-depends on (through the Serve handles), bypassed materializeStackPlugin (string entries dropped silently, bundles composed raw), and called resolvePlatformAuthComposition only for security-catalog-overlays; stackSuppliesAuthPlugin only through the gate. The WIP calls all four for plan/apply. H3 not triggered: no packages/core change needed; the change needed outside the surface is in packages/drivers/driver-sql instead. H4 partly false: serve's composition is inline in the oclif run() and has no in-process compose entry, and bootStack's set is the verify harness's, not serve's (it always mounts Auth/Security/Settings/Analytics/Sharing and mounts the slate only as hard dependencies), so the faithful 'boot registers' reading is serve's own OS_MIGRATE_AND_EXIT=1 door against a fresh database, which the parity readings above use. NOT MEASURED: cli typecheck, cli unit layer, cli integration layer, pnpm lint, the 64 derived gates, check:type-check-debt and check:dual-build-cjs-loads: none run, because no tests, changeset or final diff exist yet and the lock was never acquired. Dogfood: git grep for migrate plan/apply, MigratePlan, MigrateApply, bootSchemaStack, buildSchemaMigrationPlugins over packages/qa/dogfood exits 1 (zero hits), so dogfood stays declared to CI.",
    "mcp_calls": "0",
    "api_writes": "1: POST /repos//issues/22506/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). Not REST: two git push of claude/issue-22506-migrate-composes-boot (the empty-branch probe at 4638625, then 9cad3ea). No pr_create, no label-write.",
    "open_questions": [
    {
    "question": "The composition cannot land until driver-sql's deferred-DDL preview stops listing a rotation-declared object (sys_activity, ADR-0057 P2: shard tables plus a read view under the base name) as create_table: previewDeferredSchemaWork asks knex hasTable(base), which is false for the view, while flushDeferredSchemaDdl re-enters syncSchema, which takes the rotation branch and creates nothing new. That file is outside claim 6091247664's surface. Which sequencing?",
    "options": [
    "A: extend this claim's surface to packages/drivers/driver-sql/src/sql-driver.ts (previewDeferredSchemaWork answers a rotation-declared object from the rotator's own facts: current shard and read view present means no pending work) with a driver pin, one PR, changesets for @objectstack/cli and @objectstack/driver-sql (patch).",
    "B: file the driver preview defect as its own card (sub-issue of #22506; it is reachable on main through any composition that registers sys_activity, e.g. a host config mounting AuditPlugin, though not measured there) and dispatch it first; this card resumes from 9cad3ea with Blocked-by on it.",
    "C: land the composition without the audit plugin until the driver fix, leaving sys_audit_log/sys_activity/sys_comment unexamined; not recommended, because it lands a composition that knowingly differs from serve's and the parity pin would have to carry a residue."
    ],
    "recommendation": "B when the driver lane can take it at once, otherwise A. Business need: measured, the published upgrade step cannot complete today, and either option ends it. Long-term: B keeps the lanes clean, and the fix is the driver's own #3978 obligation that the plan must not promise work apply cannot do. AI-error prevention: equal, since both pin the invariant where it lives. Startup focus: A costs one round instead of two, so prefer it if B would queue."
    },
    {
    "question": "Composition inputs that differ from the serving boot, left out of this card unless the PM folds them in: os migrate reads no project .env while os serve/start/dev load it through dotenv-flow (measured: plan targets .objectstack/data/objectstack.db while .env names another OS_DATABASE_URL, and an OS_AUTH_SECRET kept in .env leaves the auth family out of the plan); plan/apply take no --preset/--dev, so a deployment served with --preset minimal gets the slate examined; and the one-shot boot provisions no telemetry sibling datasource (dev boots and OS_TELEMETRY_DB), so lifecycle-classed objects are planned against the primary. Fold any into this card?",
    "options": [
    "A: keep them out; the no-secret note already names the command that works, and the other two are filed as findings below.",
    "B: fold the .env load into this card (a behaviour change: os migrate would start targeting the database a .env names, which needs a maintainer ruling).",
    "C: add --preset/--dev to plan/apply as security-catalog-overlays has them (a new accepted flag, so Clause-② becomes yes and the changeset at least minor)."
    ],
    "recommendation": "A. Each of B and C changes an accepted input or the target database of a migration command, which is a maintainer decision this card's Clause-② no does not cover. The telemetry sibling is a multi-driver plan change (deferral must arm the second driver, drift and apply run on one driver today) and belongs to its own card."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public door, measured on this branch: os migrate plan --json lists pending sys_activity create_table on every run and os migrate apply --yes reports 'Created/extended 1 table(s)' while sqlite_master shows sys_activity is a view over sys_activity__r20261010 and no table is added; reachable on main through any composition that registers sys_activity (not measured on main) · evidence: packages/drivers/driver-sql/src/sql-driver.ts previewDeferredSchemaWork asks hasTable(tableName), the deferral in syncSchema records the object before the ADR-0057 P2 rotation branch, flush re-enters syncSchema which calls ensureRotation · this is the blocker above, a sub-issue of #22506 · dedupe words: previewDeferredSchemaWork rotation shard, sys_activity create_table phantom, deferred DDL rotation view, os migrate plan never in sync",
    "class: a · reach: public door, measured on main: after a development boot (run-dev, telemetry sibling on by default) os migrate plan lists sys_metadata_audit create_table and os migrate apply --yes creates it in objectstack.db while the boot keeps it in objectstack.telemetry.db; with this branch's composition the same holds for sys_audit_log, sys_activity, sys_job_run, sys_notification, sys_notification_delivery and sys_http_delivery (OS_TELEMETRY_DB opt-in production deployments too) · evidence: utils/telemetry-datasource.ts provisionTelemetryDatasource is called by every serving boot and not by bootSchemaStack; resolveTelemetryDbPath is on for dev and for OS_TELEMETRY_DB · this branch widens it from 1 object to 7, so it needs its own card before or with this one · dedupe words: telemetry datasource os migrate, objectstack.telemetry.db migrate apply, lifecycle-classed objects primary database, provisionTelemetryDatasource one-shot",
    "class: a · reach: public door, measured on main: a fixture whose .env sets OS_DATABASE_URL=file:from-dotenv.db, os migrate plan --json reports database .objectstack/data/objectstack.db, while os serve loads that .env (serve.ts dotenvFlow.config) and opens from-dotenv.db · evidence: dotenv-flow is loaded only in commands/serve.ts, start.ts, dev.ts and doctor.ts; the migrate commands read process.env only (--database-url env: OS_DATABASE_URL) · dedupe words: os migrate dotenv, .env OS_DATABASE_URL migrate plan, migrate targets different database than serve, dotenv-flow migrate",
    "class: a · reach: public door, measured on this branch after the drop: os migrate account-issuer prints 'sys_account.issuer is safe to drop' and 'Pre-flight clean. Take a backup, then run os migrate apply --allow-destructive to drop the column.' on a database where the column is already gone; on main the same text prints whenever the pre-flight is clean · evidence: commands/migrate/account-issuer.ts success branch never asks whether the column still exists · carrier: this card's resume round (account-issuer.ts is in the claim's surface 'only if its prescription must change'; the loop ends without it, so it is optional there) · dedupe words: account-issuer already dropped, issuer safe to drop after drop, account-issuer post-check prescription",
    "carrier: this card's resume round · noted, not filed: the composition notes and the unmanaged-tables block report sys_packages as an undeclared platform table on every served deployment; it is created by raw DDL in PackageServicePlugin.start(), not a registered object, so the statement is true and needs no fix, but the parity pin must name it as outside the object set",
    "carrier: this card's resume round · noted, not filed: the kernel prints 'CORE: Core service missing, functionality may be degraded: auth' during plan/apply once the auth family is composed through createIdentityObjectsPlugin (no auth service by design); cosmetic stderr noise on a declaration boot"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Surface extension to claim 6091247664: driver-sql's deferred-DDL preview (the seat answers the dev's open questions: 1 → A, 2 → A)

    domain:cli seat (#6024) · session_01BmsuLyUeuG5CNpZFMH1jzS · 2026-10-10T01:00Z.

    Thread-read: 6091898600

    What stopped the dev (report 6091898600; the WIP is pushed as 9cad3eaef7, with no PR yet):

    • The direction works. plan / apply now compose through the shared rules, and on the hotclm shape sys_account reads declared, apply --allow-destructive drops issuer and its unique index, account-issuer reads zero, and the next boot prints no drift line.
    • But the composed audit plugin registers sys_activity. That object is rotation-declared (ADR-0057 P2: shard tables plus a read view under the base name). previewDeferredSchemaWork in packages/drivers/driver-sql/src/sql-driver.ts asks hasTable(base), which is false for a view, so every plan lists sys_activity create_table, and apply then creates nothing. Measured: a re-plan after apply still lists it.
    • Landing the composition as it stands would put an entry nobody can clear into the plan of every auth-enabled deployment.

    Question 1, answered A: this claim widens to the preview. The driver's preview must not promise work apply cannot do. It is this card's acceptance blocker, the dev has already diagnosed it, and one round beats two now that the maintainer has made dispatch serial (6091889844 on #6024).

    • Added surface: packages/drivers/driver-sql/src/sql-driver.ts, only previewDeferredSchemaWork (about :13497 at 4638625e07). A rotation-declared object answers from the rotator's own facts: the current shard and the read view present means no pending work. Plus a driver pin beside its existing preview tests, and .changeset/22506-*.md gains an @objectstack/driver-sql patch entry.
    • ⛔ Not the rotator, not syncSchema's rotation branch, not flushDeferredSchemaDdl. A need for any of them is a stop-and-report.
    • Cross-lane: packages/drivers/driver-* is domain:engine's. It is declared on [PM seat] domain:engine · seat 2 — ⏳ vacant #20966 in this act. No open PR touches packages/drivers/driver-sql/ (REST file lists of all 10 open PRs, read in this act).
    • Clause-②: no stands: a preview that stops listing work that does not exist changes no accepted input. No contract-tier review is owed by face (no packages/spec, no governed text).

    Question 2, answered A: keep .env loading, --preset/--dev and the telemetry sibling out of this card. The first two change a migration command's accepted input or its target database, which is the maintainer's call. The telemetry sibling is a multi-driver plan change. The seat files the measured findings at the ACCEPT. Note for the PR body: this card's composition widens the telemetry-sibling mismatch from one object to seven; say so, with the reading.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22506,
    "status": "done",
    "branch": "claude/issue-22506-migrate-composes-boot",
    "pr": "#22574",
    "session": "session_01BmsuLyUeuG5CNpZFMH1jzS (subagent of the domain:cli PM seat; claim 6091247664 with surface extension 6091922191; round 2, resumed from 9cad3ea)",
    "premise_still_valid": true,
    "summary": "os migrate plan/apply now compose, through the rules os serve reads, what serve mounts around the stack, each piece for its declarations only: the auth family behind resolvePlatformAuthComposition (plugin-auth's objects through createIdentityObjectsPlugin, plus security and audit), the provider of every requires token plus the always-on slate (CAPABILITY_PROVIDERS/providesCapability), and the REST API plugin; every plugins entry goes through materializeStackPlugin, and the config's pinyin stamp is applied. A new init-only provider posture (composeProviderForDeclarations) keeps dispatchers and schedulers out of a dry run. Per the surface extension, driver-sql's previewDeferredSchemaWork answers a rotation-declared object from the rotator's facts, so sys_activity is no longer a phantom create_table, and account-issuer's clean prescription got its one-line fix. Pinned by: the hotclm shape (plan names both drops, apply performs them, account-issuer reaches zero, re-plan in sync), a parity pin per app shape against a real os serve OS_MIGRATE_AND_EXIT=1 boot naming sys_packages as raw DDL, a driver pin red against base, and unit cases. Draft PR #22574 at 4782330, assigned os-elon-musk. Its body states that this composition widens the telemetry-sibling mismatch from 1 object to 7, with the reading.",
    "tests": "HEAD 4782330 (source unchanged since 143f14e; later commits touch two test files only). DRIVER PIN (pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-deferred-ddl.test.ts src/sql-driver-rotation.test.ts, under the verify lock, at 306a75d): 2 files, 23 passed. RED AGAINST BASE (sql-driver.ts at 4638625 written to the tree only, marker count 0, trap-restored): 2 failed ('already sharded ... previews no work', 'converges ...'), 16 passed (the never-sharded control among them); restore: git diff HEAD empty, hash 01edeb164c equals the HEAD blob. driver-sql dist rebuilt; the fix's SQL string is present once in dist/index.js and once in dist/index.mjs. CLI TYPECHECK (pnpm --filter @objectstack/cli typecheck, at 143f14e): exit 0, test-layer debt held. CLI UNIT LAYER (vitest run --project unit, at 143f14e): 276 files, 4085 passed; at 4782330, schema-migration-plugins.test.ts plus test/exit-signal.pin.test.ts: 2 files, 160 passed. CLI INTEGRATION, through the lock, as a declared narrowing: the integration-tier files that boot the schema stack or drive the migrate commands (filters src/commands/migrate/, src/utils/schema-migrat, unmanaged-tables, platform-migrations-arming, artifact-boot-migration, sqlite-occupancy, test/migrate-, test/dev-standalone-self-heal). At 143f14e: 34 files, 317 passed, 2 skipped, 2 failed, both in the new parity pin (host-config fixture had no data stack; the sweep is unreadable on an artifact project), fixed in 4782330, where the parity file passes 3/3. The one-shot family's no-write pin passed, so plan still moves no byte. The rest of the integration project is declared to CI; the filter test/json-stdout-purity matched no file, and test/exit-signal is unit-tier (ran there). HOTCLM-SHAPE PIN apply.account-issuer-retirement.integration.test.ts: 4/4 passed in the subset (plan names drop_column issuer plus drop_index uniq_sys_account_issuer_account_id; apply drops both, verified by PRAGMA; account-issuer ok, collisions [], scanned 1; re-plan changes [] and pending []). PARITY ABLATION (scripts/ablation-replace.mjs, composeServedPlatform: false in plan.ts, anchor 1 to 0, blob c355ffc8 to 9776b976): 3/3 red, boot tables 78/76/75 against examined-plus-raw 10/11/10; restored (diff HEAD empty, hash equal). READINGS BEFORE (main 4638625, public doors, hotclm shape over a serve-created DB plus the legacy column and index): plan examined 9 of 68 tables with sys_account undeclared; apply applied 0; account-issuer ok and re-prescribed apply; the next boot printed 2 schema-drift lines. AFTER: plan 74 examined, sys_account declared, 2 destructive drops; apply drops both; account-issuer 0 collisions; next boot no sys_account drift line; re-plan in sync (pinned). No-secret production leg: sys_account not registered, and the note names the remedy (export OS_AUTH_SECRET). EXAMPLE PARITY (scratch, real serve OS_MIGRATE_AND_EXIT=1 then plan --json): app-showcase at 4782330, drift [] pending [] unmanaged [sys_packages], 105/107 examined (2 federated), 107 boot tables = 105 + sys_packages + _objectstack_sequences (driver auto-number table); app-crm 82/82, app-todo 77/77, app-multi-package 75/75 with drift [] (measured before the driver fix, each then with the sys_activity phantom now pinned away). LINT, proven narrowing at 4782330: population is eslint.config.mjs's **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus the global NEVER_LINTED; the 10 changed source files with --no-inline-config --format json give 10 files, 0 errors, 0 warnings; the config never enables type-aware linting (no parserOptions.project, no typed rules), so untouched files' verdicts cannot move. GATES: dispatch-gates --commands derives 69 (the dispatch's 64 plus check:dispatcher-error-vocabulary, check:driver-conformance, check:error-status-conformance, check:object-def-param-keys, check:tenant-chokepoint); all 69 exit 0 at 4782330. check:i18n-coverage and check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: showcase's connector-slack, and 8 unrelated packages with no dist/, in this worktree) and re-ran green after building those under the lock. check:type-check-debt and check:dual-build-cjs-loads ran last. dispatch-gates --ran with the recorded exit codes: 69 derived, 69 run, 0 NOT-MEASURED (a derived zero). Dogfood is declared to CI: git grep for migrate plan/apply, MigratePlan, MigrateApply, bootSchemaStack, buildSchemaMigrationPlugins over packages/qa/dogfood exits 1 (zero hits). CI on #22574 at report time: 13 checks completed with no failure, 18 in_progress.",
    "mcp_calls": "0",
    "api_writes": "3 this round, all through scripts/pm and the fleet-write relay: POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft forced, relay run 38015853249, #22574, body read back 14572 bytes identical); POST /repos//issues/22574/assignees (label-write --assign os-elon-musk, relay run 38015899581, read back MATCHES); POST /repos//issues/22506/comments (this os-dev-report, round 2). Round 1 posted one os-dev-report (comment 6091898600). Not REST: git push of 306a75d, 143f14e, 87603d4 and 4782330. Worktree ../objectstack-issue-22506 removed after the PR (node_modules first, no --force; branch fully pushed).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door, measured on main: after a development boot (telemetry sibling on by default) os migrate plan lists sys_metadata_audit create_table and os migrate apply --yes creates it in objectstack.db while the boot keeps it in objectstack.telemetry.db; this branch's composition widens it to 7 objects (sys_activity, sys_audit_log, sys_http_delivery, sys_job_run, sys_metadata_audit, sys_notification, sys_notification_delivery) for dev and OS_TELEMETRY_DB deployments, stated in the PR body per the seat's note · evidence: utils/telemetry-datasource.ts provisionTelemetryDatasource is called by every serving boot and not by bootSchemaStack · the seat files it at ACCEPT (ruling 6091922191) · dedupe words: telemetry datasource os migrate, objectstack.telemetry.db migrate apply, lifecycle-classed objects primary database, provisionTelemetryDatasource one-shot",
    "class: a · reach: public door, measured on main: a fixture whose .env sets OS_DATABASE_URL=file:from-dotenv.db gets a plan --json reporting database .objectstack/data/objectstack.db, while os serve loads that .env (dotenv-flow) and opens from-dotenv.db; the same gap leaves an OS_AUTH_SECRET kept in .env out of the auth gate · evidence: dotenv-flow is loaded only in commands/serve.ts, start.ts, dev.ts and doctor.ts · the seat files it at ACCEPT · dedupe words: os migrate dotenv, .env OS_DATABASE_URL migrate plan, migrate targets different database than serve",
    "class: a · reach: public door, measured on this branch: os migrate plan --json on a compiled-artifact project with no config reports unmanagedTables status unreadable, reason 'this project has no host config, so the composed object set is the compiled artifact plus the platform floor rather than what os serve registers', while the same run composed 74 objects including the served platform; the reason this branch makes false is conservative in effect (no wrong finding, a sweep withheld) · evidence: utils/unmanaged-tables.ts collectUnmanagedTables gates on composition.hostConfigLoaded alone · outside this claim's surface · carrier: none (the seat decides; the fix is to gate on whether the composition mirrors the served boot) · dedupe words: unmanaged tables unreadable artifact project, declared by nothing artifact no host config, unmanagedTables reason platform floor",
    "carrier: PR #22574 Acceptance notes · noted, not filed: --preset/--dev are absent on plan/apply (a --preset minimal deployment gets the slate examined); the rotation preview does not diff the current shard's columns (the rotator column-syncs on flush); a plan across UTC midnight can list the new day's shard as create_table (real flush work); CORE: Core service missing ... auth stderr noise once identity objects compose without AuthPlugin; plugin-auth's formatter still prints 'sys_account.issuer is safe to drop.' after the drop; sys_packages (raw DDL) and _objectstack_sequences (driver auto-number) are non-object tables"
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Review of PR #22574 (head 47823309b): one change before the ACCEPT, so claim 6091247664 widens to os migrate unmapped-columns

    domain:cli seat (#6024) · session_01BmsuLyUeuG5CNpZFMH1jzS · 2026-10-10T02:39Z.

    Thread-read: 6092569265

    CI is green on 47823309b: 34 runs, 31 success and 3 roster skips. git merge-tree against main 25be87612d is clean. The rest of the review holds. One divergence stops the ACCEPT.

    os migrate unmapped-columns no longer reads the plan's object set

    • Its boot comment in packages/cli/src/commands/migrate/unmapped-columns.ts (about :286 at 47823309b) reads: "The os migrate plan boot, so the differ runs over the plan's object set".
    • content/docs/deployment/cli.mdx (:1078 at main) makes the same promise: "It reads exactly the columns os migrate plan reports as unmapped_column for that object's table: the same differ, the same boot."
    • This PR passes composeServedPlatform: true from plan and apply only. unmapped-columns still boots with composeHostStack alone.

    Failure path (read from the code, not run):

    1. Once this lands, os migrate plan declares the platform objects the deployment serves, such as sys_account.
    2. An undeclared column on one of their tables is now reported as unmapped_column.
    3. The documented read step is os migrate unmapped-columns --object sys_account. It looks the name up in stack.allObjects(), does not find it, and throws OBJECT_NOT_FOUND.
    4. Before this PR, both commands drew on the same narrower set, so their answers agreed.

    Surface extension

    • Boot flag: in packages/cli/src/commands/migrate/unmapped-columns.ts, its bootSchemaStack call passes composeServedPlatform: true, and the comment names it. Nothing else in the command changes.
    • Pin: a pin beside its existing tests. A platform object that plan declares on an auth-enabled fixture is resolved by unmapped-columns --object, not refused. Removing the flag turns the pin red.
    • Changeset: the @objectstack/cli line names the third command.
    • ⛔ Not the other bootSchemaStack callers. Their comments claim the plan's read-only posture ([finding] seven more dry-run / report-only CLI commands boot the app seed loader and rewrite seeded rows (the family of #21349) #21391), not its object set. value-shapes and files-to-references record deployment flags over their own registry, and widening that registry is a separate question. A need to change any of them is a stop-and-report.
    • No conflicting PR: no open PR touches unmapped-columns* (REST file lists of all 12 open PRs, read in this act).
    • Clause-②: no stands. The command's documented contract already is the plan's object set, and this keeps it true. No contract-tier review is owed by face: no packages/spec, no governed text.

    The dev resumes on the same branch and PR. The seat re-reads the head and CI, then posts the ACCEPT.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22506,
    "status": "done",
    "branch": "claude/issue-22506-migrate-composes-boot",
    "pr": "#22574",
    "session": "session_01BmsuLyUeuG5CNpZFMH1jzS (subagent of the domain:cli PM seat; claim 6091247664, widened in 6091922191 and 6092886177; review round, resumed from 4782330)",
    "premise_still_valid": true,
    "summary": "Step 1, read before changing anything: the seat's failure path holds. unmapped-columns.ts resolves --object only through stack.allObjects() (about :326); with composeHostStack alone nothing registers sys_account for an app that mounts no AuthPlugin, and no other route registers it. Measured with the pin's flag-removed leg: OBJECT_NOT_FOUND, exit 1. unmapped-columns.ts now passes composeServedPlatform: true from its bootSchemaStack call, with the comment naming it; nothing else in the command changes. The composeServedPlatform docs in schema-migrate.ts and schema-migration-plugins.ts, which said plan and apply only, name the third caller, and the changeset's @objectstack/cli line names it. Head 5bdfba6 is pushed to the same PR; CI there reads 34 completed, 0 failed. CONFLICT, stated not silently chosen: the order says to update the PR body, but the role file says the dev writes the PR body once, at pr_create, never PATCHes it, and names later changes for the seat to write; the role file governs on a conflict, so the body is unchanged (14559 chars). The full updated body is at /tmp/claude-0/-home-user/297ae329-0faf-5db9-a494-f807d046b9f2/scratchpad/issue-22506/pr-body-r3.md (16701 bytes, no angle brackets, one closing keyword on line 1). Its three changes: a 'commands/migrate/unmapped-columns.ts' paragraph before the account-issuer one; an 'unmapped-columns' bullet first under Pins (with the red/green reading below); and a 'Review round, head 5bdfba6' block opening Verification, with the round-1 block kept under it.",
    "tests": "HEAD 5bdfba6, under the verify lock unless noted. Fresh worktree: pnpm install, then the @objectstack/cli closure built (VERDICT command-exit 0; driver-sql dist carries the preview fix's SQL once). TYPECHECK (pnpm --filter @objectstack/cli typecheck): exit 0. UNIT (vitest run --project unit src/commands/migrate/unmapped-columns.test.ts src/utils/schema-migration-plugins.test.ts; the tier module classifies both as unit): 2 files, 67 passed. INTEGRATION (vitest run --project integration with unmapped-columns.integration.test.ts, plan.boot-parity.integration.test.ts and apply.account-issuer-retirement.integration.test.ts): 3 files, 17 passed. That includes the new case 'a platform object the plan declares (#22506)': the seed adds sys_account from today's definition plus the retired issuer column and one row; on an auth-enabled stack, --object sys_account --json exits 0 with records [{id: acc_os22506, values: {issuer: 'local:credential'}}], and its columns equal plan --json's unmapped_column set for sys_account (['issuer']). The existing 'writes nothing' case still passes with the door's widened boot. FLAG REMOVED (scripts/ablation-replace.mjs --delete of ' composeServedPlatform: true,' in unmapped-columns.ts; anchor 1 to 0, blob 6213fba8 to 810add42): 1 failed, 9 passed. The failure is exactly the new case, AssertionError {"error":"Object 'sys_account' not found","code":"OBJECT_NOT_FOUND"}, exit 1. Restored: blob back to HEAD, git diff HEAD empty, hash 6213fba8 equals HEAD's (verified by the tool and again by the script). LINT, the proven narrowing at 5bdfba6: population eslint.config.mjs's **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED; the 12 changed source files with --no-inline-config --format json give 12 files, 0 errors, 0 warnings; type-aware linting is never enabled, so untouched files' verdicts cannot move. GATES: dispatch-gates --commands at 5bdfba6 derives the same 69 families as round 2 (sorted diff empty). After building the showcase closure and the 8 packages those gates read, so none answered PREREQUISITE NOT MET: 67 exit 0, then check:dual-build-cjs-loads exit 0 (107 entry points across 66 packages load) and check:type-check-debt exit 0, last. dispatch-gates --ran with recorded exit codes: 69 derived, 69 run, 0 NOT-MEASURED (a derived zero). Not re-run this round, and unchanged since round 2: the full cli unit layer and the wider integration subset; this round's diff is unmapped-columns.ts, its integration test, docs comments and the changeset.",
    "mcp_calls": "0",
    "api_writes": "1: POST /repos//issues/22506/comments (this os-dev-report, round 3, through scripts/pm/post-stamped.mjs and the fleet-write relay). No PR body write (see summary). Not REST: git push of 5bdfba6. Earlier rounds: 6091898600 (round 1); #22574 pr_create, the assignee label-write, and 6092569265 (round 2).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: this card's ACCEPT or the seat · noted, not filed: schema-migrate.ts's composeHostStack doc still says it is 'Set by the two SCHEMA commands, os migrate plan and os migrate apply, and by nothing else', which was already false on main (unmapped-columns and security-catalog-overlays pass it); left as is, because this round's order limits the change to the composeServedPlatform statements"
    ]
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #22574 (head 5bdfba6bc), triage's direction 6085386483, with the claim widened by 6091922191 and 6092886177

    domain:cli seat (#6024) · session_01BmsuLyUeuG5CNpZFMH1jzS · 2026-10-10T03:20Z. Reviewed against GitHub and the dev's reports, not against the summary.

    Thread-read: 6093221889

    • PR shape: draft, base main. The first line is Fixes #22506, then a line-initial Clause-②: no with the claim's reason. 13 files, +1205 / -30, all inside the claim plus its two extensions:
      • plan.ts, apply.ts, schema-migrate.ts, schema-migration-plugins.ts;
      • the one-line account-issuer.ts prescription;
      • driver-sql's previewDeferredSchemaWork;
      • unmapped-columns.ts's boot flag (the review round);
      • their tests;
      • a changeset for @objectstack/cli and @objectstack/driver-sql.
        No packages/spec, no governed path, so no contract-tier review is owed. Assignee os-elon-musk.
    • The fix, read:
      • plan / apply pass composeServedPlatform, and bootSchemaStack composes what serve mounts around the stack, each piece for its declarations only:
        • the auth family behind resolvePlatformAuthComposition;
        • the provider of every requires token plus the always-on slate, through CAPABILITY_PROVIDERS / providesCapability;
        • the REST API plugin;
        • every plugins entry through materializeStackPlugin.
      • A new init-only posture (composeProviderForDeclarations) keeps dispatchers and schedulers out of a dry run.
      • Read and accepted: the auth family's three members (identity objects, Security, Audit) are named here as serve names them inline. The decision to compose them is the shared rule's, and the parity pin is the guard: a member serve adds and this omits reds there.
    • The review round, read (5bdfba6bc, asked in 6092886177): os migrate unmapped-columns passes composeServedPlatform: true too, so it resolves the platform objects the plan now reports on. It keeps its documented promise (content/docs/deployment/cli.mdx: "the same differ, the same boot"). Nothing else in the command changed; the option's docblocks and the changeset name the third caller.
      • The dev read the failure path first, and it held: with composeHostStack alone, --object sys_account answered OBJECT_NOT_FOUND, exit 1.
      • The pin (unmapped-columns.integration.test.ts, "a platform object the plan declares") seeds sys_account with the retired issuer column and one row. It reads that row on an auth-enabled stack, and its columns equal plan --json's unmapped_column set for the table. With the flag removed, exactly that case goes red (1 failed, 9 passed); restored, it is green.
    • The driver fix, read: previewDeferredSchemaWork answers a rotation-declared object from the rotator's facts: the current shard and the read view present means no pending work. Its sqlite_master query runs only where supportsRotation, which is isSqlite, so other dialects are untouched.
    • Direction met (the hotclm shape, public doors):
      • Before: plan examined 9 of 68 tables, sys_account undeclared; apply applied 0; account-issuer re-prescribed apply; the next boot printed 2 drift lines.
      • After: plan examines 74 and names both drops; apply drops the column and its index; account-issuer reads zero; the next boot prints no sys_account drift line; and a re-plan is in sync, with no phantom sys_activity.
      • No secret in production: sys_account is not composed, and the note names the remedy.
    • Pins and evidence (from the reports):
      • The hotclm-shape pin: 4 / 4.
      • The parity pin per app shape, against a real os serve OS_MIGRATE_AND_EXIT=1 boot: 3 / 3, red 3 / 3 when the served-platform composition is ablated. It names sys_packages as raw DDL outside the object set.
      • The driver pin: red against base (2 failed / 16 passed, the control green) and 23 / 23 green.
      • Example readings: app-showcase 105 of 107 examined (2 federated), app-crm 82 / 82, app-todo 77 / 77, app-multi-package 75 / 75, each with drift [].
      • Checks, round 2 (47823309b): cli typecheck exits 0, and the unit layer gives 4085 passed. Lint ran as the proven narrowing. All 69 derived gates exit 0, with --ran a derived zero.
      • Checks, the review round (5bdfba6bc, report 6093221889): typecheck exits 0. The two unit files give 67 passed, and the three integration files (unmapped-columns, the parity pin, the hotclm pin) give 17 passed. Lint on the 12 changed source files reads 0 / 0. dispatch-gates derives the same 69 families; all 69 exit 0, and --ran reads 0 NOT-MEASURED.
    • CI on 5bdfba6bc: 34 check runs: 31 success and 3 skipped, all three on the roster (Packed-tarball smoke (opt-in), Console Pin Gate, Build Docs), 0 failed. git merge-tree against current main 99801d831f is clean, and no file main gained since the base 4638625e07 is in this PR. check-governed-merges --pr 22574, run from main 99801d831f: 0 of 13 paths governed, 1235 changed lines, under the 3000 threshold.
    • Review of record: this ACCEPT plus CI.
    • Accepted deviations:
      • The PR body was rewritten by the seat, not the dev. The rework order asked the dev to update the body, but os-dev.md says the dev writes it once, at pr_create, and the seat writes later changes. The dev stated the conflict and followed the role file. The seat posted the dev's prepared text, with one stale sentence corrected ("which only plan and apply set"). The order's wording was the seat's error.
      • The integration layer was a declared narrowing: the 34 files that boot the schema stack or drive the migrate commands, 317 passed; the rest of the project is CI's.
      • The account-issuer.ts one-liner: its clean prescription now says to drop only while plan still lists the drop.
    • The telemetry-sibling widening is known and stated: this composition widens the existing mismatch from 1 object to 7 (the PR body has the reading). It creates empty tables in the primary database, and no data is lost. Filed as [finding] cli(migrate): os migrate plan / apply never provision the telemetry sibling datasource, so lifecycle-classed objects a dev or OS_TELEMETRY_DB boot keeps in objectstack.telemetry.db are planned and created in the primary database #22579.
    • Out of scope:
    • Next: ready and auto-merge into the queue. Then, per the maintainer's ruling 6091889844, dispatch goes serial: [finding] serve(config boot): a multi-package config's top-level metadata no package owns is served by nothing and warned about by nothing, while an artifact boot of the same project registers it under manifest.id and warns — the family's closing card #22521 next.

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22574 → 86f53a4b8d, a single-parent queue squash. os migrate composes what os serve mounts

    domain:cli seat (#6024) · session_01BmsuLyUeuG5CNpZFMH1jzS · 2026-10-10T03:42Z.


    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:clipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions