mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(state): report v9 registry migration row decisions (#124862)
* fix(state): report v9 registry migration row decisions in doctor and logs * fix(state): consume registry migration summary type * fix(state): avoid registry path observer import cycle
This commit is contained in:
committed by
GitHub
parent
800a0bb52a
commit
57b1a69167
@@ -465,13 +465,20 @@ function isDefaultAgentDatabasePath(pathname: string, agentId: string): boolean
|
||||
);
|
||||
}
|
||||
|
||||
export type AgentDatabasePathMigrationSummary = {
|
||||
relativized: number;
|
||||
reanchored: string[];
|
||||
deleted: string[];
|
||||
preserved: number;
|
||||
};
|
||||
|
||||
export function migrateAgentDatabaseRelativePaths(
|
||||
db: DatabaseSync,
|
||||
previousVersion: number,
|
||||
databasePath: string,
|
||||
): boolean {
|
||||
): AgentDatabasePathMigrationSummary {
|
||||
if (previousVersion >= 9 || !tableExists(db, "agent_databases")) {
|
||||
return false;
|
||||
return { relativized: 0, reanchored: [], deleted: [], preserved: 0 };
|
||||
}
|
||||
const rows = db.prepare("SELECT agent_id, path FROM agent_databases").all();
|
||||
const updatePath = db.prepare(
|
||||
@@ -481,7 +488,9 @@ export function migrateAgentDatabaseRelativePaths(
|
||||
const hasPath = db.prepare(
|
||||
"SELECT 1 FROM agent_databases WHERE agent_id = ? AND path = ? LIMIT 1",
|
||||
);
|
||||
let changed = false;
|
||||
let relativized = 0;
|
||||
const reanchored: string[] = [];
|
||||
const deleted: string[] = [];
|
||||
for (const row of rows) {
|
||||
const agentId = row.agent_id;
|
||||
const registeredPath = row.path;
|
||||
@@ -494,7 +503,7 @@ export function migrateAgentDatabaseRelativePaths(
|
||||
const storedPath = resolveOpenClawAgentDatabaseStoredPath(databasePath, registeredPath);
|
||||
if (!path.isAbsolute(storedPath)) {
|
||||
updatePath.run(storedPath, agentId, registeredPath);
|
||||
changed = true;
|
||||
relativized += 1;
|
||||
}
|
||||
}
|
||||
const stateDir = resolveOpenClawStateDirForDatabasePath(databasePath);
|
||||
@@ -526,16 +535,21 @@ export function migrateAgentDatabaseRelativePaths(
|
||||
// The same agent already owns its in-root canonical registration. Keeping a second
|
||||
// default-layout registration guarantees duplicate canonical session keys on every list.
|
||||
deletePath.run(agentId, registeredPath);
|
||||
changed = true;
|
||||
deleted.push(registeredPath);
|
||||
} else if (existsSync(counterpartAbsolute)) {
|
||||
// Re-anchor a copied or moved state directory onto its copied database instead of
|
||||
// deleting the registration or leaving it dangling at the source root.
|
||||
updatePath.run(counterpartStored, agentId, registeredPath);
|
||||
changed = true;
|
||||
reanchored.push(registeredPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
return {
|
||||
relativized,
|
||||
reanchored,
|
||||
deleted,
|
||||
preserved: rows.length - relativized - reanchored.length - deleted.length,
|
||||
};
|
||||
}
|
||||
|
||||
function hasCanonicalAgentDatabasesPrimaryKey(db: DatabaseSync): boolean {
|
||||
|
||||
@@ -72,3 +72,54 @@ export function resolveOpenClawRegisteredAgentDatabasePath(
|
||||
? storedPath
|
||||
: `${resolveOpenClawStateDirForDatabasePath(registryDatabasePath)}${path.sep}${storedPath}`;
|
||||
}
|
||||
|
||||
type AgentPathMigrationObservation = {
|
||||
relativized: number;
|
||||
reanchored: string[];
|
||||
deleted: string[];
|
||||
};
|
||||
|
||||
type AgentPathMigrationLogger = {
|
||||
warn: (
|
||||
message: string,
|
||||
fields: { reanchored: string[]; deleted: string[]; path: string },
|
||||
) => void;
|
||||
};
|
||||
|
||||
export function describeAgentPathMigration(summary: AgentPathMigrationObservation): string[] {
|
||||
const { relativized, reanchored, deleted } = summary;
|
||||
if (relativized === 0 && reanchored.length === 0 && deleted.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const decisions = reanchored.length + deleted.length;
|
||||
const counts = [
|
||||
`${relativized} relativized`,
|
||||
reanchored.length > 0 && `${reanchored.length} re-anchored`,
|
||||
deleted.length > 0 && `${deleted.length} removed`,
|
||||
].filter(Boolean);
|
||||
return [
|
||||
`Migrated agent database registry paths to state-relative storage${decisions > 0 ? ` (${counts.join(", ")})` : ""}`,
|
||||
...reanchored.map(
|
||||
(registeredPath) =>
|
||||
`Re-anchored agent database registry path ${registeredPath} to the current state directory`,
|
||||
),
|
||||
...deleted.map(
|
||||
(registeredPath) => `Removed duplicate agent database registry path ${registeredPath}`,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
export function warnAgentPathMigration(
|
||||
log: AgentPathMigrationLogger,
|
||||
summary: AgentPathMigrationObservation,
|
||||
databasePath: string,
|
||||
): void {
|
||||
if (summary.reanchored.length === 0 && summary.deleted.length === 0) {
|
||||
return;
|
||||
}
|
||||
log.warn("agent database registry rows re-anchored or removed during v9 migration", {
|
||||
reanchored: summary.reanchored,
|
||||
deleted: summary.deleted,
|
||||
path: databasePath,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1640,6 +1640,14 @@ describe("openclaw state database", () => {
|
||||
kind: "agent-databases-relative-paths-v9",
|
||||
path: databasePath,
|
||||
});
|
||||
expect(repairOpenClawStateDatabaseSchema({ env })).toEqual({
|
||||
changes: [
|
||||
"Migrated agent database registry paths to state-relative storage (2 relativized, 1 re-anchored, 1 removed)",
|
||||
`Re-anchored agent database registry path ${copiedForeignPath} to the current state directory`,
|
||||
`Removed duplicate agent database registry path ${dualForeignPath}`,
|
||||
],
|
||||
warnings: [],
|
||||
});
|
||||
const migrated = openOpenClawStateDatabase({ env });
|
||||
expect(readSqliteNumberPragma(migrated.db, "user_version")).toBe(9);
|
||||
expect(
|
||||
|
||||
@@ -67,11 +67,12 @@ import {
|
||||
} from "./openclaw-state-db-schema-additive.js";
|
||||
import { tableExists } from "./openclaw-state-db-schema-helpers.js";
|
||||
import {
|
||||
type AgentDatabasePathMigrationSummary as AgentPathSummary,
|
||||
assertCanonicalStateSchemaShape,
|
||||
detectOpenClawStateDatabaseSchemaMigrationsFromDatabase,
|
||||
dropLegacyStateTables,
|
||||
markCurrentStateSchemaVersion,
|
||||
migrateAgentDatabaseRelativePaths,
|
||||
migrateAgentDatabaseRelativePaths as migrateAgentPaths,
|
||||
migrateRetiredCommitmentsSchema,
|
||||
migrateWorkerPlacementExecutionModeSchema,
|
||||
repairAgentDatabasesCompositePrimaryKey,
|
||||
@@ -79,6 +80,7 @@ import {
|
||||
} from "./openclaw-state-db-schema-repair.js";
|
||||
import * as sessionWatchMigration from "./openclaw-state-db-session-watch-migration.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "./openclaw-state-db.generated.js";
|
||||
import { describeAgentPathMigration, warnAgentPathMigration } from "./openclaw-state-db.paths.js";
|
||||
import {
|
||||
assertOpenClawStateWriteAllowed,
|
||||
OpenClawStateOwnershipError,
|
||||
@@ -204,9 +206,9 @@ function repairOpenClawStateDatabaseSchemaWithWriteAccess(
|
||||
if (migrateWorkerPlacementExecutionModeSchema(db, previousVersion)) {
|
||||
applied.push("Migrated cloud worker placements to execution modes");
|
||||
}
|
||||
if (migrateAgentDatabaseRelativePaths(db, previousVersion, pathname)) {
|
||||
applied.push("Migrated agent database registry paths to state-relative storage");
|
||||
}
|
||||
applied.push(
|
||||
...describeAgentPathMigration(migrateAgentPaths(db, previousVersion, pathname)),
|
||||
);
|
||||
if (repairAgentDatabasesCompositePrimaryKey(db)) {
|
||||
applied.push(`Migrated shared state agent database registry primary key → agent_id,path`);
|
||||
}
|
||||
@@ -397,7 +399,7 @@ function ensureSchema(db: DatabaseSync, pathname: string, env: NodeJS.ProcessEnv
|
||||
dropLegacyStateTables(db);
|
||||
migrateRetiredCommitmentsSchema(db, previousVersion);
|
||||
migrateWorkerPlacementExecutionModeSchema(db, previousVersion);
|
||||
migrateAgentDatabaseRelativePaths(db, previousVersion, pathname);
|
||||
const pathMigration: AgentPathSummary = migrateAgentPaths(db, previousVersion, pathname);
|
||||
ensureAdditiveStateColumns(db);
|
||||
sessionWatchMigration.migrateSessionWatchCursorProvenance(db);
|
||||
assertCanonicalStateSchemaShape(db, pathname);
|
||||
@@ -456,6 +458,7 @@ function ensureSchema(db: DatabaseSync, pathname: string, env: NodeJS.ProcessEnv
|
||||
),
|
||||
);
|
||||
assertOpenClawStateDatabaseForMaintenance(db, { pathname });
|
||||
warnAgentPathMigration(stateDbLog, pathMigration, pathname);
|
||||
},
|
||||
{
|
||||
busyTimeoutMs: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS,
|
||||
|
||||
Reference in New Issue
Block a user