fix(#1392): hash-ledger migrations + install-time schema verification (closes #1392, closes #1402) (#1403)
ci/woodpecker/push/publish Pipeline failed

Co-authored-by: ops-deploy-01 <[email protected]>
This commit was merged in pull request #1403.
This commit is contained in:
2026-08-24 23:11:50 +00:00
committed by orch-01
parent d790572e2e
commit 4d24ae8618
9 changed files with 805 additions and 74 deletions
@@ -12,6 +12,7 @@ import {
import {
createDb,
createPgliteDb,
getMigrationStatus,
runPgliteMigrations,
type Db,
type DbHandle,
@@ -74,6 +75,11 @@ export class DatabaseModule implements OnApplicationShutdown, OnModuleInit {
// the same DATABASE_URL, so a single call covers both the gateway DB and
// the storage tables. We deliberately do NOT call runMigrations() here to
// avoid opening a second short-lived connection and doubling startup cost.
//
// #1392: we DO verify afterwards (getMigrationStatus opens one short-lived
// connection) and refuse to start on an incomplete schema. A gateway that
// boots "healthy" on an empty or partial database is precisely the failure
// that shipped in the T63 batch: silent at startup, catastrophic later.
async onModuleInit(): Promise<void> {
if (this.config.tier === 'local') {
this.logger.log('Applying PGlite schema migrations...');
@@ -81,6 +87,24 @@ export class DatabaseModule implements OnApplicationShutdown, OnModuleInit {
}
this.logger.log(`Initializing storage adapter (${this.storageAdapter.name})...`);
await this.storageAdapter.migrate();
if (this.config.storage.type === 'postgres') {
const status = await getMigrationStatus(this.config.storage.url);
if (!status.complete) {
this.logger.error(
`Database schema incomplete: ${status.appliedCount.toString()}/${status.expectedCount.toString()} migrations applied ` +
`(last expected: ${status.expectedLastTag}). ` +
'Refusing to start on a partial schema — see issues #1392/#1402. ' +
"Remediation: re-run 'mosaic gateway install' (it now verifies), or apply migrations manually.",
);
throw new Error(
`Database schema incomplete: ${status.appliedCount.toString()}/${status.expectedCount.toString()} migrations applied`,
);
}
this.logger.log(
`Database schema verified: ${status.appliedCount.toString()}/${status.expectedCount.toString()} migrations applied.`,
);
}
}
async onApplicationShutdown(): Promise<void> {