mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(state): simplify schema migration validation
This commit is contained in:
@@ -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<number, readonly string[]>;
|
||||
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 {
|
||||
|
||||
@@ -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<string>(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,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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(() =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user