Repository navigation
fix(plugin-audit, service-messaging): boot-time system-table provisioning honours OS_SKIP_SCHEMA_SYNC - #23021
Conversation
…ning honours skipSchemaSync ObjectQLPlugin publishes the skipSchemaSync value it resolved (the option, else OS_SKIP_SCHEMA_SYNC=1) on the engine instance as an undeclared property. AuditPlugin and MessagingServicePlugin read it in provisionSystemTables and, when it is set, issue no DDL and log one info line naming the objects they did not provision and the os migrate apply remedy. With the flag unset the provisioning is unchanged. syncObjectSchema itself stays ungated: a runtime publish still gets its table. Claude-Session: https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce Co-authored-by: Claude <noreply@anthropic.com>
…ovisioning skip Per plugin: with the flag published on the engine, no DDL and no probe is issued, the table stays absent, and one info line names the skip; the CONTROL provisions exactly as before. A seam table boots the real ObjectQLPlugin as producer: option true and the env fallback skip, unset provisions, and an explicit skipSchemaSync false beats OS_SKIP_SCHEMA_SYNC=1. Also turns two docblock links to a module-private function into plain code spans. Claude-Session: https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 24 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 783471949ce4dbf657e65ab5da2806516d23e119 && git checkout 783471949ce4dbf657e65ab5da2806516d23e119
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9432bac077ecbfa703c09409f3fed416edd41d99 de65696a4938d59e0afe092c62d1347c6ae87114 && git checkout -B drift-repro 9432bac077ecbfa703c09409f3fed416edd41d99 && git merge --no-ff de65696a4938d59e0afe092c62d1347c6ae87114
node scripts/docs-audit/affected-docs.mjs --json 9432bac077ecbfa703c09409f3fed416edd41d99
|
Fixes #22984
Clause-②: no (the seat cannot read it without a build: any new name on a package entry's declared surface, an accessor included, flips it; the dev measures on the built declaration closure and stops on a flip)
What this changes
OS_SKIP_SCHEMA_SYNC=1, orObjectQLPlugin'sskipSchemaSyncoption, declares that a deployment's DDL is managed out-of-band. The engine's boot honoured it.AuditPlugin.provisionSystemTablesandMessagingServicePlugin.provisionSystemTablesdid not, so a flag deployment got its system tables re-created atkernel:readywhile the boot log said no DDL ran. The direction is the triage grade (comment 6121183235): the fix is in the provisioning plugins, andsyncObjectSchemais not gated.packages/objectql/src/plugin.ts(the producer).init()publishes theskipSchemaSyncvalue the plugin resolved (the option, elseOS_SKIP_SCHEMA_SYNC === '1') on the engine instance it registers. It is the undeclared own propertyskipSchemaSync, keyed by the module constantSKIP_SCHEMA_SYNC_ENGINE_KEY, which is not exported. The option's TSDoc gains one paragraph saying the value also governs the plugins' boot provisioning.packages/plugins/plugin-audit/src/audit-plugin.ts.provisionSystemTableshoists its four objects intoobjects, then checksbootSchemaSyncSkipped(engine)first. That is a module-private probe,engine.skipSchemaSync === true. When it is set, the method logs oneinfoline (provisionSkippedLine) and returns before any DDL or probe. The rest of the method is unchanged. One docblock sentence became false with this change and is corrected: "OS_SKIP_SCHEMA_SYNCis NOT named: this method issues its DDL whether or not that flag is set". A new "Why the flag skips this provisioning" section is added.packages/services/service-messaging/src/messaging-service-plugin.ts. Same shape. Theobjectsarray now sits above thesyncObjectSchemaprobe. Next comes thebootSchemaSyncSkipped(engine)early return with its oneinfoline, then the unchangedconst sync = engine.syncObjectSchema; if (typeof sync !== 'function') return;and the per-object loop..changeset/22984-boot-provision-honours-skip-sync.md:patchfor all three packages.#22985 (queued, same messaging function) remains open. The shape it will meet is the one in the messaging bullet above.
Measurements (the dispatch's assumptions)
A1, confirmed on
origin/main8e4a2302a. NeitherprovisionSystemTablesread the flag: the flag's only mention in either file was the audit docblock sentence above.ObjectQLPluginheld the resolved value inprivate skipSchemaSyncand exposed no accessor.A2, confirmed for all twelve tables, so the skip is built in both plugins. Measured through
ositself (packages/cli/bin/run-dev.js, BASE tree8e4a2302a).os servewithOS_MIGRATE_AND_EXIT=1provisions a fixture app withrequires: ['auth']. Every physical table backing the twelve objects is dropped. Thenos migrate plan --jsonandos migrate apply --yesrun.migrate planlistscreate_tablemigrate applycreatesOS_TELEMETRY_DB=0)Created/extended 12 table(s))sys_audit_log,sys_activity,sys_notification,sys_notification_deliveryandsys_http_deliveryland inboot.telemetry.db)OS_SKIP_SCHEMA_SYNC=1set in the plan and apply environmentThe tables:
sys_audit_log,sys_activity(physicallysys_activity__r20261012),sys_comment,sys_comment_reaction,sys_notification,sys_inbox_message,sys_notification_receipt,sys_notification_delivery,sys_notification_preference,sys_notification_subscription,sys_notification_templateandsys_http_delivery.A3: the seam adds no entry-reachable name, so
Clause-②: noholds. The seam is the optional probe of the concrete engine: an undeclared instance property, with no public method, exported type or kernel-service contract. It was measured on the built declarations. The BASEdistdeclaration files (.d.ts,.d.mts,.d.cts) of all three packages were snapshotted before editing and rebuilt after. Each pair was compared raw, and again after the TypeScript printer withremoveComments: true.index.d.tsandindex.d.mts, and the private method's docblock in plugin-audit and service-messaging. That raw difference also proves each.d.tswas rebuilt from this source.util-Ch_WDmDU.d.ts, which holdsdeclare class ObjectQL, reads as DIFFER at that line once onereadonly skipSchemaSync: boolean;member is injected. A first control attempt anchored onindex.d.tsmatched nothing (0 anchor hits) and was discarded as void.A4: the skip line is
info. Under the flag nothing is degraded. The operator declared DDL out-of-band, and nothing claims persistence it does not have, so the AGENTS.md "Degradation log levels" question answers No. If a table is in fact missing, the writers report it on first use (audit-writers.tslogs the missing-table write aterror). The line names every skipped object, the flag andos migrate apply.A5, held. Under the flag
provisionSystemTablesmakes no engine call in either plugin. The audit unit pin asserts the recording engine saw zero calls: nosyncObjectSchema,getDriverForObject,getDefaultDriverNameorfind.Integration-level reading of the triage pin
os servewithOS_MIGRATE_AND_EXIT=1,OS_TELEMETRY_DB=0,OS_LOG_LEVEL=info, and the A2 fixture:sys_comment_reaction(audit) andsys_inbox_message(messaging).OS_SKIP_SCHEMA_SYNC=1.BASE
8e4a2302ashows the defect in both plugins. The card left the messaging half unmeasured.This branch:
Tests
New pins in each plugin's test file (
audit-plugin.test.ts,messaging-service-plugin.test.ts):infoskip line names every object, the flag andos migrate apply. Nothing about provisioning is logged atwarnorerror.false, every object is provisioned exactly as before.ObjectQLPluginas producer (registerProtocol: false, the engine'ssyncObjectSchemaspied). Optiontrueskips. Option unset withOS_SKIP_SCHEMA_SYNC=1skips. Both unset provisions. OptionfalsewithOS_SKIP_SCHEMA_SYNC=1provisions; this is the row that discriminates, because a plugin that read the environment itself would skip there.Ablations. Each used
scripts/ablation-replace.mjswith an EXIT/INT/TERM trap, and each restore was proven by blob hash equal to HEAD and an emptygit diff HEAD.if (false && bootSchemaSyncSkipped(engine))): 3 red, 18 green. Red were the flag-set pin (expected [ 'getDefaultDriverName', …(8) ] to deeply equal []) and the two skip rows of the seam table (expected "syncObjectSchema" to not be called at all, but actually been called 4 times).… called 8 times).void SKIP_SCHEMA_SYNC_ENGINE_KEY;and objectql rebuilt (14/14).ablation-dist-preflight.mjs --absentproved the publish gone from all 14 built files. Result: audit 2 red, 19 green; messaging 2 red, 13 green. In each suite the red rows were the two skip rows of the seam table. The restore leg rebuilt, the preflight found the publish back indist/index.jsanddist/index.mjs, and the tree was clean.Suites at
8afbc3b90:@objectstack/plugin-audit: 47 files, 761 passed. Typecheck exit 0; the test layer goes throughtsconfig.test.json, andaudit-plugin.test.tsis in that program.@objectstack/service-messaging: 48 files, 540 passed. Typecheck exit 0;--listFilesincludesmessaging-service-plugin.test.ts.@objectstack/objectql(vitest run --project local): 406 files, 7936 passed. Typecheck exit 0.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackderived 68 commands over the 6 changed paths (merge base8e4a2302a). All 68 ran at8afbc3b90and every one exited 0. The--ranverdict reads 68 run and 0 NOT-MEASURED, derived from the recorded exit codes. Three gates needed a prerequisite first and were re-run on the same HEAD:check-engine-split-ratio --days 90refused on the shallow clone; history was deepened to 2026-07-07.check:dual-build-cjs-loadsandcheck:i18nneeded built output.Lint, narrowed and proven. eslint
--no-inline-config --format jsonover the 5 changed.tsfiles reported 5 files, 0 errors and 0 warnings. eslint's own config covers all 5 (no ignore notice) and excludes the changeset (File ignored because no matching configuration was supplied.). The repo's config enables no type-aware linting, so this diff cannot move a verdict on an untouched file. The repo-widepnpm lintrun is CI's.Acceptance notes
PackageServicePlugin.start()createssys_packageswith a rawCREATE TABLE IF NOT EXISTSunder the flag too: measured dropped, then re-created by a flag boot.os migrate applydoes not create it:plandoes not list it, and after apply it stays absent. Honouring the flag there would leave a flag deployment withoutsys_packages. That is the decision A2 reserves for the maintainer.Skipping schema sync (OS_SKIP_SCHEMA_SYNC=1) …info line twice per flag boot. This is cosmetic and not filed.origin/mainbefore opening. Six commits (63 files) landed since8e4a2302a, none of them in this diff or inserve/migrate. CI runs the merge ref.Generated by Claude Code