Repository navigation
fix(service-package, cli): os migrate plan / apply cover sys_packages, and the boot honours OS_SKIP_SCHEMA_SYNC for it - #23060
Conversation
… the boot honours OS_SKIP_SCHEMA_SYNC PackageServicePlugin.init() declares sys_packages as the package.storage service (presence probe + the boot's own ensurePackageTable); bootSchemaStack lists it as create_table when absent and creates it on flush, so os migrate plan / apply cover it. start() then skips its DDL when the engine resolved skipSchemaSync, logging one info line. Claude-Session: https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce Co-authored-by: Claude <noreply@anthropic.com>
…ipSchemaSync boot skip Claude-Session: https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce Co-authored-by: Claude <noreply@anthropic.com>
…hrough the real funnel Claude-Session: https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce Co-authored-by: Claude <noreply@anthropic.com>
…SKIP_SCHEMA_SYNC for it Claude-Session: https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce Co-authored-by: Claude <noreply@anthropic.com>
…s-packages-managed-then-flag
📓 Docs Drift CheckThis PR changes 2 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 635bee273e9823de0c99e5795e15059ed2e07a9d && git checkout 635bee273e9823de0c99e5795e15059ed2e07a9d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e49aaec733d5cebd82a6b2e5521cdaae998b5b8d 9c4be9e42d488c81e68f6c36dec268229e0fdaa7 && git checkout -B drift-repro e49aaec733d5cebd82a6b2e5521cdaae998b5b8d && git merge --no-ff 9c4be9e42d488c81e68f6c36dec268229e0fdaa7
node scripts/docs-audit/affected-docs.mjs --json e49aaec733d5cebd82a6b2e5521cdaae998b5b8d
|
Fixes #23022
Clause-②: no (the seat cannot read it without a build: a newly exported object definition or name flips it; the dev measures on the built declaration closure and stops on a flip)
What this does, in the card's order
sys_packages.PackageServicePlugin.init()now declares the table as a kernel service,package.storage: a read-only catalog probe (SQLitesqlite_master, PostgreSQLinformation_schema.tablesatcurrent_schema(), MySQLinformation_schema.tablesatDATABASE()) and the boot's ownensurePackageTable.bootSchemaStack(the funnelos migrate planandos migrate applyshare) lists the table ascreate_tablewhen it is absent, andflushSchemaDdlcreates it with those same statements. A migrate boot runs a provider'sinit()and never itsstart(), which is why the declaration lives ininit().start()reads the valueObjectQLPluginresolved and published on the engine (skipSchemaSync, the seam plugin-audit: provisionSystemTables creates tables under OS_SKIP_SCHEMA_SYNC=1, while objectql's boot says it registered every schema WITHOUT DDL #22984 added for plugin-audit and service-messaging; no second environment read). When it is set,start()sends no DDL and no probe, logs oneinfoline namingsys_packages, the flag andos migrate apply, and still registers thepackageservice. Unset, the boot is unchanged.Where the table's presence cannot be read (a default driver that names no SQL dialect, or a seam that returns no result set), the run says so in its composition notes and neither lists nor creates the table: "could not tell" is never read as "absent".
Why not a registered object (the triage's preferred shape): measured, A3 holds
A declared
sys_packages, throughSqlDriver.initObjectson SQLite (better-sqlite3) at this branch's base:`id` varchar(255),created_at/updated_attypeddatetimewith astrftimedefault, every other column nullable, andprimary key (id)alone. A second version of one package id is refused:SQLITE_CONSTRAINT_PRIMARYKEY ... UNIQUE constraint failed: sys_packages.detectManagedDrift()reports 0 entries.So fresh and existing deployments would get different tables, and the fresh one breaks multi-version publish. That is the A3 stop shape, so the managed-path entry keeps the raw DDL instead.
package-table.tsalready recorded the same result on PostgreSQL 16.14 and MySQL 8.0.46 (from the #21243 work). The create statements are unchanged, soapplycreates exactly what the boot creates. On SQLite,sqlite_masterholds byte-identicalCREATE TABLEandCREATE INDEXtext for both (integration reading below). PostgreSQL and MySQL were not reachable from this container: the new presence probes on those dialects are pinned by statement, not run live.Measured through the public doors (
os servewithOS_MIGRATE_AND_EXIT=1,os migrate plan --json,os migrate apply --yes --json, SQLite)9f4b652e36f0d23fdesys_packages=truesys_packages=truesys_packages=true(DDL behind the flag)sys_packages=false, oneNOT provisioned at bootinfo lineos migrate plan --json, table absentpending: []pending: ["create_table:sys_packages"]os migrate apply --yes --json, flag setmessage=in_sync, still absentcreated: ["create_table:sys_packages"], presentVERDICT shape apply == boot: trueA4:
Clause-②: noholds, measured on the built declarationsBASE sources of the three changed source files were built in this worktree (
service-packagetsup,clitsc), then HEAD's were. The comparison covers 40 declaration files: service-packagedist/index.d.tsandindex.d.cts, the CLI's entry closure (index/console/hook-body, 37 files) anddist/utils/schema-migrate.d.ts. They were compared after the TypeScript printer stripped comments:VERDICT IDENTICAL: 40 identical, 0 differ. Positive control: injecting one member intodeclare class PackageServicePluginand one export intoschema-migrate.d.tsreadsDIFFERfor both. The restore was proven by blob hashes and an emptygit status --porcelain, and two HEAD builds read identical. New names are module-private, andpackage.storageis a service name, not a declared one.Tests
@objectstack/service-package: newmanaged-path.test.ts(18 cases). On a realnode:sqlitedatabase, the declaration creates the(id, version)table and its index, and the probe is a read. Each dialect is asked its own catalog. "Could not tell" isundefined, and no SQL is sent. With the flag set,start()sends no DDL, the table stays absent, there is one info line, and the service is registered. The CONTROL rows are unset,falseand the string'true'.package-table.test.ts's context now captures only thepackageservice, becauseinit()registers one more. Suite: 7 files / 113 passed;typecheckexit 0 (itstsc --listFilesincludes the new test).@objectstack/cli: newsrc/utils/schema-migrate.package-table.integration.test.ts, through the real funnel withOS_SKIP_SCHEMA_SYNC=1set. It covers three steps. First, a plan on a never-started project listscreate_table sys_packageswith its seven columns and creates no file. Second, apply has nothing for the table before the flush; the flush creates it asPRIMARY KEY (id, version)withTEXTcolumns andidx_packages_latest (id, created_at). Third, a re-plan lists nothing for it.9c4be9e42(after mergingmain):--project unit: 283 files / 4270 passed.--project integration, the 10 files on this path: the new one,unmanaged-tables,schema-migrate.host-composition/readonly-probe/deferred-ddl/one-shot-family/ plain,plan.boot-parity,plan.telemetry-siblingandapply.account-issuer-retirement. 10 files / 119 passed.typecheck, includingcheck:test-typecheck: exit 0.Ablations: each negative pin was put back and watched go red, then restored
Mutations went through
scripts/ablation-replace.mjs, with an EXIT/INT/TERM trap that restores fromHEAD. Each restore was proven by blob hash and an emptygit diff HEAD.if (false && bootSchemaSyncSkipped(objectql)). Result:managed-path.test.ts2 failed | 16 passed,expected [ …(2) ] to deeply equal [](the two DDL statements were sent) andexpected true to be false(the table exists).ensure()while planning. Result: 1 failed,expected [] to deeply equal [ { table: 'sys_packages', …(2) } ].PACKAGE_STORAGE_SERVICErenamed to'package.storage.ablated'in service-package, which was then rebuilt.ablation-dist-preflightreported "marker present in 2 built files". Result: the CLI test failed 1,expected [] to deeply equal [ { table: 'sys_packages', …(2) } ]. Restore leg: rebuilt, preflight--absentreported "marker absent from all 6 built files", and the test passed 1.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat9c4be9e42derived 68 families over 7 changed paths, with merge basedf7a13792. All 68 ran, and all exited 0.check:dual-build-cjs-loadsandcheck:i18n-coveragefirst answeredPREREQUISITE NOT MET(exit 3: packages outside the CLI closure were not built). They were re-run green on the same HEAD after a full turbo build.--ranreports:68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED.Lint was narrowed and the narrowing proven.
eslint --no-inline-config --format jsonover the 6 changed.tsfiles reports 6 files, 0 errors, 0 warnings, ESLint exit 0. The config lints all 6 files (no ignore warning) and enables no type-aware linting (noparserOptions.project), so no untouched file's verdict can move. The changeset.mdis outside eslint's population.Acceptance notes (not filed)
package.storage. LikeskipSchemaSync, it is an undeclared surface, chosen so that no name is added to either package's declarations. It is held together by the CLI integration test booting the real plugin, and the producer ablation shows that test failing when the name moves. A declared accessor would be the contract-first form and widen the published surface. Carrier: none.skipSchemaSync: true. service-package does not depend on@objectstack/objectql, and the unaliased-test-import ledger is shrink-only. The real producer was exercised once, throughos serve(table above). Its publication is pinned by plugin-audit's and service-messaging's real-producer seam tests. Carrier: none.os migrate apply, hydration'slist()meets the missing table. The SQL driver logs its ownDATABASE_ERROR — no such table: sys_packagesline, and hydration logs atdebug. That is loud enough to find, and the changeset says to runos migrate applyfirst. Carrier: none.idx_packages_latest. A table that exists without its index (reachable only by hand) is not re-planned. Carrier: none.os migrate plan's unmanaged-table sweep still listssys_packageswhen the table exists. Its wording, "declared by no object in this plan", stays literally true: the table is declared by a service, not by an object. The boot-parity pins still expect it. Carrier: none.os migrate planon a fresh project now lists one morecreate_table. That is intended: it is whatapplywill create.mainat9c4be9e42before opening. The incoming commits touchpackages/spec, the security package-verb walls and CI, and none of this diff. durability log level: schema-sync DDL refusals in four paths log below error, and check:durability-log-level cannot see them (no syncObjectSchema entry; sync.call reads as callee "call") #22985's region ofschema-migration-plugins.tsis untouched: this change lives inschema-migrate.ts.Generated by Claude Code