From 57df39f3abb70266fc34bccb891beea72bcccc02 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 12 Aug 2026 20:04:33 -0700 Subject: [PATCH] refactor(state): simplify schema migration validation --- src/state/openclaw-state-db-maintenance.ts | 21 +- src/state/openclaw-state-db-schema-repair.ts | 7 +- src/state/openclaw-state-db.test.ts | 275 ++++++------------- src/state/openclaw-state-db.ts | 17 +- 4 files changed, 109 insertions(+), 211 deletions(-) diff --git a/src/state/openclaw-state-db-maintenance.ts b/src/state/openclaw-state-db-maintenance.ts index beb10365e395..765e2f518fb3 100644 --- a/src/state/openclaw-state-db-maintenance.ts +++ b/src/state/openclaw-state-db-maintenance.ts @@ -42,6 +42,11 @@ const STATE_V5_ADDITIVE_TABLES = [ "worker_transcript_commits", ...STATE_V6_ADDITIVE_TABLES, ] as const; +const STATE_MIGRATION_ALLOWED_MISSING_TABLES = { + 5: STATE_V5_ADDITIVE_TABLES, + 6: STATE_V6_ADDITIVE_TABLES, +} as const satisfies Record; +type OpenClawStateMigrationVersion = keyof typeof STATE_MIGRATION_ALLOWED_MISSING_TABLES; /** Open shared SQLite database handle plus WAL maintenance lifecycle. */ @@ -133,7 +138,7 @@ export function assertOpenClawStateDatabaseForMaintenance( function assertOpenClawStateDatabaseVersionForMigration( database: DatabaseSync, - options: { pathname: string; version: number; allowedMissingTables: readonly string[] }, + options: { pathname: string; version: OpenClawStateMigrationVersion }, ): void { const userVersion = readSqliteUserVersion(database); if (userVersion !== options.version) { @@ -153,7 +158,7 @@ function assertOpenClawStateDatabaseVersionForMigration( ); } assertSqliteSchemaTablesPresent(database, options.pathname, OPENCLAW_STATE_SCHEMA_SQL, { - allowedMissingTables: options.allowedMissingTables, + allowedMissingTables: STATE_MIGRATION_ALLOWED_MISSING_TABLES[options.version], }); } @@ -162,11 +167,7 @@ export function assertOpenClawStateDatabaseV5ForMigration( database: DatabaseSync, options: { pathname: string }, ): void { - assertOpenClawStateDatabaseVersionForMigration(database, { - ...options, - version: 5, - allowedMissingTables: STATE_V5_ADDITIVE_TABLES, - }); + assertOpenClawStateDatabaseVersionForMigration(database, { ...options, version: 5 }); } /** Require every stable v6 table before the v7 retirement migration can run. */ @@ -174,11 +175,7 @@ export function assertOpenClawStateDatabaseV6ForMigration( database: DatabaseSync, options: { pathname: string }, ): void { - assertOpenClawStateDatabaseVersionForMigration(database, { - ...options, - version: 6, - allowedMissingTables: STATE_V6_ADDITIVE_TABLES, - }); + assertOpenClawStateDatabaseVersionForMigration(database, { ...options, version: 6 }); } export function resolveDatabasePath(options: OpenClawStateDatabaseOptions = {}): string { diff --git a/src/state/openclaw-state-db-schema-repair.ts b/src/state/openclaw-state-db-schema-repair.ts index 50cd744d4ebe..301d968540fa 100644 --- a/src/state/openclaw-state-db-schema-repair.ts +++ b/src/state/openclaw-state-db-schema-repair.ts @@ -186,8 +186,7 @@ const ADDITIVE_RETIRED_COMMITMENTS_SCHEMA_COMPATIBILITY: SqliteSchemaCompatibili function hasSupportedRetiredCommitmentsSchema( db: DatabaseSync, schemaSql: string, - expectedIndexNames: readonly string[], - compatibility: SqliteSchemaCompatibility = {}, + compatibility: SqliteSchemaCompatibility, ): boolean { if (collectSqliteSchemaIssues(db, schemaSql, compatibility).length > 0) { return false; @@ -202,7 +201,7 @@ function hasSupportedRetiredCommitmentsSchema( ORDER BY type, name`, ) .all() as Array<{ name: string; type: string }>; - const expectedIndexes = new Set(expectedIndexNames); + const expectedIndexes = new Set(RETIRED_COMMITMENTS_INDEX_NAMES); return attachedObjects.every( (object) => object.type === "index" && expectedIndexes.has(object.name), ); @@ -228,13 +227,11 @@ function hasRecognizedRetiredCommitmentsSchema(db: DatabaseSync): boolean { hasSupportedRetiredCommitmentsSchema( db, RETIRED_COMMITMENTS_SCHEMA_SQL, - RETIRED_COMMITMENTS_INDEX_NAMES, RETIRED_COMMITMENTS_SCHEMA_COMPATIBILITY, ) || hasSupportedRetiredCommitmentsSchema( db, ADDITIVE_RETIRED_COMMITMENTS_SCHEMA_SQL, - RETIRED_COMMITMENTS_INDEX_NAMES, ADDITIVE_RETIRED_COMMITMENTS_SCHEMA_COMPATIBILITY, ) ); diff --git a/src/state/openclaw-state-db.test.ts b/src/state/openclaw-state-db.test.ts index b3ef401050d4..4e69002b11d4 100644 --- a/src/state/openclaw-state-db.test.ts +++ b/src/state/openclaw-state-db.test.ts @@ -187,17 +187,10 @@ function replaceManagedImageRecordsWithLegacyTable( const LEGACY_SESSION_WATCH_SCHEMA_VERSION = 3; const LEGACY_AMBIENT_WATCH_PREFIX = "ambient-group-watch:"; -function markStateDatabaseAsV5(database: DatabaseSync): void { +function markStateDatabaseVersion(database: DatabaseSync, version: 5 | 6): void { database.exec(` - PRAGMA user_version = 5; - UPDATE schema_meta SET schema_version = 5 WHERE meta_key = 'primary'; - `); -} - -function markStateDatabaseAsV6(database: DatabaseSync): void { - database.exec(` - PRAGMA user_version = 6; - UPDATE schema_meta SET schema_version = 6 WHERE meta_key = 'primary'; + PRAGMA user_version = ${version}; + UPDATE schema_meta SET schema_version = ${version} WHERE meta_key = 'primary'; `); } @@ -267,7 +260,7 @@ function seedV6CommitmentSchema(database: DatabaseSync): void { scope, lease_key, owner, expires_at, heartbeat_at, payload_json, created_at, updated_at ) VALUES ('test', 'preserved-lease', 'migration-test', 100, 50, '{}', 1, 2); `); - markStateDatabaseAsV6(database); + markStateDatabaseVersion(database, 6); } function seedAdditiveV6CommitmentSchema(database: DatabaseSync): void { @@ -315,7 +308,7 @@ function seedAdditiveV6CommitmentSchema(database: DatabaseSync): void { CREATE INDEX idx_commitments_agent_sent ON commitments(agent_id, status, sent_at_ms, session_key); `); - markStateDatabaseAsV6(database); + markStateDatabaseVersion(database, 6); } function seedPartiallyAdditiveV6CommitmentSchema(database: DatabaseSync): void { @@ -339,15 +332,28 @@ function seedPartiallyAdditiveV6CommitmentSchema(database: DatabaseSync): void { ON commitments(agent_id, session_key, status, due_earliest_ms, due_latest_ms); CREATE INDEX idx_commitments_status_due ON commitments(status, due_earliest_ms, due_latest_ms); - INSERT INTO commitments ( - id, agent_id, session_key, channel, account_id, kind, status, dedupe_key, - due_earliest_ms, due_latest_ms, updated_at_ms, last_attempt_at_ms, record_json - ) VALUES ( - 'partial-commitment', 'main', 'agent:main:main', 'telegram', 'default', - 'followup', 'pending', 'partial-dedupe', 10, 20, 1, 1, '{}' - ); `); - markStateDatabaseAsV6(database); + markStateDatabaseVersion(database, 6); +} + +function seedEarlyCommitmentSchema(database: DatabaseSync): void { + database.exec(` + CREATE TABLE commitments ( + id TEXT NOT NULL PRIMARY KEY, + agent_id TEXT NOT NULL, + session_key TEXT NOT NULL, + channel TEXT NOT NULL, + status TEXT NOT NULL, + due_earliest_ms INTEGER NOT NULL, + due_latest_ms INTEGER NOT NULL, + updated_at_ms INTEGER NOT NULL, + record_json TEXT NOT NULL + ); + CREATE INDEX idx_commitments_scope_due + ON commitments(agent_id, session_key, status, due_earliest_ms, due_latest_ms); + CREATE INDEX idx_commitments_status_due + ON commitments(status, due_earliest_ms, due_latest_ms); + `); } function seedLegacySessionWatchCursorSchema(stateDir: string): { @@ -1476,36 +1482,42 @@ describe("openclaw state database", () => { }, ); - it.each(["runtime open", "doctor repair"] as const)( - "retires a v6 commitments layout with missing canonical indexes through %s", - (migrationPath) => { - const stateDir = createTempStateDir(); - const options = { env: { OPENCLAW_STATE_DIR: stateDir } }; - const databasePath = materializeCurrentStateDatabase(stateDir); - const { DatabaseSync } = requireNodeSqlite(); - const legacy = new DatabaseSync(databasePath); - seedV6CommitmentSchema(legacy); - legacy.exec(` - DROP INDEX idx_commitments_scope_dedupe; - DROP INDEX idx_commitments_agent_sent; - `); - legacy.close(); - - if (migrationPath === "doctor repair") { - expect(repairOpenClawStateDatabaseSchema(options)).toEqual({ - changes: ["Retired shared state commitments table and indexes"], - warnings: [], - }); - } - const migrated = openOpenClawStateDatabase(options); - expect(readSqliteNumberPragma(migrated.db, "user_version")).toBe( - OPENCLAW_STATE_SCHEMA_VERSION, - ); - expect( - migrated.db.prepare("SELECT name FROM sqlite_schema WHERE name = 'commitments'").get(), - ).toBeUndefined(); + // The canonical v6 case above proves both runtime and Doctor orchestration, + // reporting, and markers; these variants isolate historical layout recognition. + it.each([ + { + label: "v6 commitments with missing canonical indexes", + seed: (database: DatabaseSync) => { + seedV6CommitmentSchema(database); + database.exec(` + DROP INDEX idx_commitments_scope_dedupe; + DROP INDEX idx_commitments_agent_sent; + `); + }, }, - ); + { + label: "supported additive v6 commitments layout", + seed: seedAdditiveV6CommitmentSchema, + }, + { + label: "partially additive v6 commitments layout", + seed: seedPartiallyAdditiveV6CommitmentSchema, + }, + ])("retires the $label through runtime open", ({ seed }) => { + const stateDir = createTempStateDir(); + const options = { env: { OPENCLAW_STATE_DIR: stateDir } }; + const databasePath = materializeCurrentStateDatabase(stateDir); + const { DatabaseSync } = requireNodeSqlite(); + const legacy = new DatabaseSync(databasePath); + seed(legacy); + legacy.close(); + + const migrated = openOpenClawStateDatabase(options); + expect(readSqliteNumberPragma(migrated.db, "user_version")).toBe(OPENCLAW_STATE_SCHEMA_VERSION); + expect( + migrated.db.prepare("SELECT name FROM sqlite_schema WHERE name = 'commitments'").get(), + ).toBeUndefined(); + }); it.each(["runtime open", "doctor repair"] as const)( "retires the supported early commitments layout through %s", @@ -1516,30 +1528,7 @@ describe("openclaw state database", () => { fs.mkdirSync(path.dirname(databasePath), { recursive: true }); const { DatabaseSync } = requireNodeSqlite(); const early = new DatabaseSync(databasePath); - early.exec(` - CREATE TABLE commitments ( - id TEXT NOT NULL PRIMARY KEY, - agent_id TEXT NOT NULL, - session_key TEXT NOT NULL, - channel TEXT NOT NULL, - status TEXT NOT NULL, - due_earliest_ms INTEGER NOT NULL, - due_latest_ms INTEGER NOT NULL, - updated_at_ms INTEGER NOT NULL, - record_json TEXT NOT NULL - ); - CREATE INDEX idx_commitments_scope_due - ON commitments(agent_id, session_key, status, due_earliest_ms, due_latest_ms); - CREATE INDEX idx_commitments_status_due - ON commitments(status, due_earliest_ms, due_latest_ms); - INSERT INTO commitments ( - id, agent_id, session_key, channel, status, due_earliest_ms, - due_latest_ms, updated_at_ms, record_json - ) VALUES ( - 'early-commitment', 'main', 'agent:main:main', 'telegram', 'pending', - 10, 20, 1, '{}' - ); - `); + seedEarlyCommitmentSchema(early); early.close(); if (migrationPath === "doctor repair") { @@ -1557,59 +1546,6 @@ describe("openclaw state database", () => { }, ); - it.each(["runtime open", "doctor repair"] as const)( - "retires the supported additive v6 commitments layout through %s", - (migrationPath) => { - const stateDir = createTempStateDir(); - const options = { env: { OPENCLAW_STATE_DIR: stateDir } }; - const databasePath = materializeCurrentStateDatabase(stateDir); - const { DatabaseSync } = requireNodeSqlite(); - const additive = new DatabaseSync(databasePath); - seedAdditiveV6CommitmentSchema(additive); - additive.close(); - - if (migrationPath === "doctor repair") { - const result = repairOpenClawStateDatabaseSchema(options); - expect(result.warnings).toEqual([]); - expect(result.changes).toContain("Retired shared state commitments table and indexes"); - } - const migrated = openOpenClawStateDatabase(options); - expect(readSqliteNumberPragma(migrated.db, "user_version")).toBe( - OPENCLAW_STATE_SCHEMA_VERSION, - ); - expect( - migrated.db.prepare("SELECT name FROM sqlite_schema WHERE name = 'commitments'").get(), - ).toBeUndefined(); - }, - ); - - it.each(["runtime open", "doctor repair"] as const)( - "retires a partially additive v6 commitments layout through %s", - (migrationPath) => { - const stateDir = createTempStateDir(); - const options = { env: { OPENCLAW_STATE_DIR: stateDir } }; - const databasePath = materializeCurrentStateDatabase(stateDir); - const { DatabaseSync } = requireNodeSqlite(); - const partial = new DatabaseSync(databasePath); - seedPartiallyAdditiveV6CommitmentSchema(partial); - partial.close(); - - if (migrationPath === "doctor repair") { - expect(repairOpenClawStateDatabaseSchema(options)).toEqual({ - changes: ["Retired shared state commitments table and indexes"], - warnings: [], - }); - } - const migrated = openOpenClawStateDatabase(options); - expect(readSqliteNumberPragma(migrated.db, "user_version")).toBe( - OPENCLAW_STATE_SCHEMA_VERSION, - ); - expect( - migrated.db.prepare("SELECT name FROM sqlite_schema WHERE name = 'commitments'").get(), - ).toBeUndefined(); - }, - ); - it.each(["runtime open", "doctor repair"] as const)( "preserves a foreign commitments table and colliding index through %s", (migrationPath) => { @@ -1635,7 +1571,7 @@ describe("openclaw state database", () => { CREATE INDEX idx_commitments_scope_due ON foreign_commitment_index_owner(marker); `); - markStateDatabaseAsV6(foreign); + markStateDatabaseVersion(foreign, 6); foreign.close(); if (migrationPath === "doctor repair") { @@ -2877,9 +2813,16 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're } }); - it.each(["runtime open", "doctor repair"] as const)( - "rejects a missing stable v5 table before the v7 migration through %s", - (migrationPath) => { + it.each( + ([5, 6] as const).flatMap((version) => + (["runtime open", "doctor repair"] as const).map((migrationPath) => ({ + migrationPath, + version, + })), + ), + )( + "rejects a missing stable v$version table before the v7 migration through $migrationPath", + ({ migrationPath, version }) => { const stateDir = createTempStateDir(); const options = { env: { OPENCLAW_STATE_DIR: stateDir } }; const databasePath = materializeCurrentStateDatabase(stateDir); @@ -2887,7 +2830,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're const { DatabaseSync } = requireNodeSqlite(); const damaged = new DatabaseSync(databasePath); damaged.exec("DROP TABLE auth_profile_stores;"); - markStateDatabaseAsV5(damaged); + markStateDatabaseVersion(damaged, version); damaged.close(); if (migrationPath === "runtime open") { @@ -2910,7 +2853,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ) .get(), ).toBeUndefined(); - expect(readSqliteNumberPragma(after, "user_version")).toBe(5); + expect(readSqliteNumberPragma(after, "user_version")).toBe(version); } finally { after.close(); } @@ -2928,7 +2871,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're DROP TABLE worker_session_tool_operations; DROP TABLE worker_turn_tool_authorities; `); - markStateDatabaseAsV5(legacy); + markStateDatabaseVersion(legacy, 5); legacy.close(); const migrated = openOpenClawStateDatabase(options); @@ -2946,46 +2889,6 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ]); }); - it.each(["runtime open", "doctor repair"] as const)( - "rejects a missing stable v6 table before the v7 migration through %s", - (migrationPath) => { - const stateDir = createTempStateDir(); - const options = { env: { OPENCLAW_STATE_DIR: stateDir } }; - const databasePath = materializeCurrentStateDatabase(stateDir); - - const { DatabaseSync } = requireNodeSqlite(); - const damaged = new DatabaseSync(databasePath); - damaged.exec("DROP TABLE auth_profile_stores;"); - markStateDatabaseAsV6(damaged); - damaged.close(); - - if (migrationPath === "runtime open") { - expect(() => openOpenClawStateDatabase(options)).toThrow( - /missing table auth_profile_stores/iu, - ); - } else { - expect(repairOpenClawStateDatabaseSchema(options)).toEqual({ - changes: [], - warnings: [expect.stringContaining("missing table auth_profile_stores")], - }); - } - - const after = new DatabaseSync(databasePath, { readOnly: true }); - try { - expect( - after - .prepare( - "SELECT name FROM sqlite_schema WHERE type = 'table' AND name = 'auth_profile_stores'", - ) - .get(), - ).toBeUndefined(); - expect(readSqliteNumberPragma(after, "user_version")).toBe(6); - } finally { - after.close(); - } - }, - ); - it("rejects an inline unique constraint hidden behind a SQLite autoindex", () => { const stateDir = createTempStateDir(); const options = { env: { OPENCLAW_STATE_DIR: stateDir } }; @@ -3993,7 +3896,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're const { DatabaseSync } = requireNodeSqlite(); const legacyDb = new DatabaseSync(databasePath); legacyDb.exec("ALTER TABLE gateway_boot_lifecycle DROP COLUMN startup_reason"); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4035,7 +3938,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're 5678, ); legacyDb.exec("ALTER TABLE claw_package_refs DROP COLUMN updated_at_ms"); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4062,7 +3965,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ALTER TABLE claw_package_refs DROP COLUMN extension_unavailable_json; ALTER TABLE claw_package_refs DROP COLUMN extension_adapter_identity; `); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ env: { OPENCLAW_STATE_DIR: stateDir } }); @@ -4096,7 +3999,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ALTER TABLE worker_environments DROP COLUMN teardown_terminal_state; ALTER TABLE worker_environments DROP COLUMN ssh_host_key; `); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4134,7 +4037,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ALTER TABLE worker_session_placements DROP COLUMN terminal_at_ms; ALTER TABLE worker_session_placements DROP COLUMN terminal_reason; `); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4187,7 +4090,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're DROP TABLE worker_transcript_commits; DROP TABLE worker_transcript_commit_heads; `); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4251,7 +4154,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're DROP INDEX idx_operator_approvals_resolution_ref; ALTER TABLE operator_approvals DROP COLUMN resolution_ref; `); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ env: { OPENCLAW_STATE_DIR: stateDir } }); @@ -4424,7 +4327,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ('alpha', 'tie-second', '{}', 10), ('beta', 'only', '{}', 30); `); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase(options); @@ -4464,7 +4367,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're const { DatabaseSync } = requireNodeSqlite(); const legacyDb = new DatabaseSync(databasePath); legacyDb.exec("ALTER TABLE apns_registrations DROP COLUMN relay_origin"); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase(options); @@ -4604,7 +4507,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're 110, 110, ); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4768,7 +4671,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ALTER TABLE official_external_plugin_catalog_snapshots DROP COLUMN trust_threshold; ALTER TABLE official_external_plugin_catalog_snapshots DROP COLUMN trust_verified_at; `); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4797,7 +4700,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're const { DatabaseSync } = requireNodeSqlite(); const legacyDb = new DatabaseSync(databasePath); legacyDb.exec("ALTER TABLE task_runs DROP COLUMN detail_json"); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); const reopened = openOpenClawStateDatabase({ @@ -4856,7 +4759,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're 100, 100, ); - markStateDatabaseAsV5(legacyDb); + markStateDatabaseVersion(legacyDb, 5); legacyDb.close(); expect(() => diff --git a/src/state/openclaw-state-db.ts b/src/state/openclaw-state-db.ts index 600d20057a9e..33beb6953142 100644 --- a/src/state/openclaw-state-db.ts +++ b/src/state/openclaw-state-db.ts @@ -81,6 +81,11 @@ import { import { getOpenClawStateRuntimeSchema } from "./openclaw-state-schema-compatibility.js"; import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js"; +const OPENCLAW_STATE_MIGRATION_ASSERTIONS = { + 5: assertOpenClawStateDatabaseV5ForMigration, + 6: assertOpenClawStateDatabaseV6ForMigration, +} as const; + export { OPENCLAW_DATABASE_SCHEMA_DOCS_URL, OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, @@ -172,10 +177,8 @@ function repairOpenClawStateDatabaseSchemaWithWriteAccess( assertSqliteSchemaTablesPresent(db, pathname, OPENCLAW_STATE_SCHEMA_SQL, { allowedMissingTables: LAZY_ADDITIVE_STATE_TABLES, }); - } else if (previousVersion === 6) { - assertOpenClawStateDatabaseV6ForMigration(db, { pathname }); - } else if (previousVersion === 5) { - assertOpenClawStateDatabaseV5ForMigration(db, { pathname }); + } else if (previousVersion === 5 || previousVersion === 6) { + OPENCLAW_STATE_MIGRATION_ASSERTIONS[previousVersion](db, { pathname }); } if (rebuiltIndexNames.size === 0) { assertSqliteIntegrity(db, pathname); @@ -362,10 +365,8 @@ function ensureSchema(db: DatabaseSync, pathname: string, env: NodeJS.ProcessEnv }); ensureAdditiveStateColumns(db); assertCurrentStateRuntimeSchema(db, pathname); - } else if (previousVersion === 6) { - assertOpenClawStateDatabaseV6ForMigration(db, { pathname }); - } else if (previousVersion === 5) { - assertOpenClawStateDatabaseV5ForMigration(db, { pathname }); + } else if (previousVersion === 5 || previousVersion === 6) { + OPENCLAW_STATE_MIGRATION_ASSERTIONS[previousVersion](db, { pathname }); } dropLegacyStateTables(db); migrateRetiredCommitmentsSchema(db, previousVersion);