mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-15 11:11:09 +00:00
Matrix: consolidate migration status routing (#64373)
Merged via squash.
Prepared head SHA: dfe29e36bb
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
committed by
GitHub
parent
2ccd1839f2
commit
0dd8ce72a2
@@ -112,6 +112,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/exec: prevent gateway crash ("Agent listener invoked outside active run") when a subagent exec tool produces stdout/stderr after the agent run has ended or been aborted. (#62821) Thanks @openperf.
|
||||
- Browser/tabs: route `/tabs/action` close/select through the same browser endpoint reachability and policy checks as list/new (including Playwright-backed remote tab operations), reject CDP HTTP redirects on probe requests, and sanitize blocked-endpoint error responses so tab list/focus/close flows fail closed without echoing raw policy details back to callers. (#63332)
|
||||
- Gateway/OpenAI compat: return real `usage` for non-stream `/v1/chat/completions` responses, emit the final usage chunk when `stream_options.include_usage=true`, and bound usage-gated stream finalization after lifecycle end. (#62986) Thanks @Lellansin.
|
||||
- Matrix/migration: keep packaged warning-only crypto migrations from being misclassified as actionable when only helper chunks are present, so startup and doctor stay on the warning-only path instead of creating unnecessary migration snapshots. (#64373) Thanks @gumadeiras.
|
||||
|
||||
## 2026.4.9
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function buildConfig(
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
describe("matrix native approval adapter", () => {
|
||||
describe("matrix approval capability", () => {
|
||||
it("describes the correct Matrix exec-approval setup path", () => {
|
||||
const text = matrixApprovalCapability.describeExecApprovalSetup?.({
|
||||
channel: "matrix",
|
||||
|
||||
@@ -18,11 +18,15 @@ vi.mock("./matrix-migration.runtime.js", async () => {
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
hasActionableMatrixMigration: vi.fn(() => false),
|
||||
hasPendingMatrixMigration: vi.fn(() => false),
|
||||
maybeCreateMatrixMigrationSnapshot: vi.fn(),
|
||||
autoMigrateLegacyMatrixState: vi.fn(async () => ({ changes: [], warnings: [] })),
|
||||
autoPrepareLegacyMatrixCrypto: vi.fn(async () => ({ changes: [], warnings: [] })),
|
||||
resolveMatrixMigrationStatus: vi.fn(() => ({
|
||||
legacyState: null,
|
||||
legacyCrypto: { warnings: [], plans: [] },
|
||||
pending: false,
|
||||
actionable: false,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -93,7 +97,12 @@ describe("matrix doctor", () => {
|
||||
|
||||
it("surfaces matrix sequence warnings and repair changes", async () => {
|
||||
const runtimeApi = await import("./matrix-migration.runtime.js");
|
||||
vi.mocked(runtimeApi.hasActionableMatrixMigration).mockReturnValue(true);
|
||||
vi.mocked(runtimeApi.resolveMatrixMigrationStatus).mockReturnValue({
|
||||
legacyState: null,
|
||||
legacyCrypto: { warnings: [], plans: [] },
|
||||
pending: true,
|
||||
actionable: true,
|
||||
});
|
||||
vi.mocked(runtimeApi.maybeCreateMatrixMigrationSnapshot).mockResolvedValue({
|
||||
archivePath: "/tmp/matrix-backup.tgz",
|
||||
created: true,
|
||||
|
||||
@@ -14,9 +14,8 @@ import {
|
||||
autoPrepareLegacyMatrixCrypto,
|
||||
detectLegacyMatrixCrypto,
|
||||
detectLegacyMatrixState,
|
||||
hasActionableMatrixMigration,
|
||||
hasPendingMatrixMigration,
|
||||
maybeCreateMatrixMigrationSnapshot,
|
||||
resolveMatrixMigrationStatus,
|
||||
} from "./matrix-migration.runtime.js";
|
||||
import { isRecord } from "./record-shared.js";
|
||||
|
||||
@@ -135,17 +134,13 @@ export async function applyMatrixDoctorRepair(params: {
|
||||
}): Promise<{ changes: string[]; warnings: string[] }> {
|
||||
const changes: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
const pendingMatrixMigration = hasPendingMatrixMigration({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
});
|
||||
const actionableMatrixMigration = hasActionableMatrixMigration({
|
||||
const migrationStatus = resolveMatrixMigrationStatus({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
});
|
||||
|
||||
let matrixSnapshotReady = true;
|
||||
if (actionableMatrixMigration) {
|
||||
if (migrationStatus.actionable) {
|
||||
try {
|
||||
const snapshot = await maybeCreateMatrixMigrationSnapshot({
|
||||
trigger: "doctor-fix",
|
||||
@@ -163,7 +158,7 @@ export async function applyMatrixDoctorRepair(params: {
|
||||
'- Skipping Matrix migration changes for now. Resolve the snapshot failure, then rerun "openclaw doctor --fix".',
|
||||
);
|
||||
}
|
||||
} else if (pendingMatrixMigration) {
|
||||
} else if (migrationStatus.pending) {
|
||||
warnings.push(
|
||||
"- Matrix migration warnings are present, but no on-disk Matrix mutation is actionable yet. No pre-migration snapshot was needed.",
|
||||
);
|
||||
@@ -224,15 +219,6 @@ export async function runMatrixDoctorSequence(params: {
|
||||
return { changeNotes, warningNotes };
|
||||
}
|
||||
|
||||
const legacyState = detectLegacyMatrixState({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
});
|
||||
const legacyCrypto = detectLegacyMatrixCrypto({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
});
|
||||
|
||||
if (params.shouldRepair) {
|
||||
const repair = await applyMatrixDoctorRepair({
|
||||
cfg: params.cfg,
|
||||
@@ -240,16 +226,24 @@ export async function runMatrixDoctorSequence(params: {
|
||||
});
|
||||
changeNotes.push(...repair.changes);
|
||||
warningNotes.push(...repair.warnings);
|
||||
} else if (legacyState) {
|
||||
if ("warning" in legacyState) {
|
||||
warningNotes.push(`- ${legacyState.warning}`);
|
||||
} else {
|
||||
warningNotes.push(formatMatrixLegacyStatePreview(legacyState));
|
||||
} else {
|
||||
const migrationStatus = resolveMatrixMigrationStatus({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
});
|
||||
if (migrationStatus.legacyState) {
|
||||
if ("warning" in migrationStatus.legacyState) {
|
||||
warningNotes.push(`- ${migrationStatus.legacyState.warning}`);
|
||||
} else {
|
||||
warningNotes.push(formatMatrixLegacyStatePreview(migrationStatus.legacyState));
|
||||
}
|
||||
}
|
||||
if (
|
||||
migrationStatus.legacyCrypto.warnings.length > 0 ||
|
||||
migrationStatus.legacyCrypto.plans.length > 0
|
||||
) {
|
||||
warningNotes.push(...formatMatrixLegacyCryptoPreview(migrationStatus.legacyCrypto));
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.shouldRepair && (legacyCrypto.warnings.length > 0 || legacyCrypto.plans.length > 0)) {
|
||||
warningNotes.push(...formatMatrixLegacyCryptoPreview(legacyCrypto));
|
||||
}
|
||||
|
||||
return { changeNotes, warningNotes };
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const availabilityState = vi.hoisted(() => ({
|
||||
currentFilePath: "/virtual/dist/matrix-migration.runtime.js",
|
||||
existingPaths: new Set<string>(),
|
||||
dirEntries: [] as Array<{ name: string; isFile: () => boolean }>,
|
||||
}));
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const { mockNodeBuiltinModule } = await import("../../../test/helpers/node-builtin-mocks.js");
|
||||
return mockNodeBuiltinModule(
|
||||
() => vi.importActual<typeof import("node:fs")>("node:fs"),
|
||||
{
|
||||
existsSync: (candidate: unknown) => availabilityState.existingPaths.has(String(candidate)),
|
||||
readdirSync: () => availabilityState.dirEntries as never,
|
||||
},
|
||||
{ mirrorToDefault: true },
|
||||
);
|
||||
});
|
||||
|
||||
vi.mock("node:url", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:url")>("node:url");
|
||||
return {
|
||||
...actual,
|
||||
fileURLToPath: () => availabilityState.currentFilePath,
|
||||
};
|
||||
});
|
||||
|
||||
const { isMatrixLegacyCryptoInspectorAvailable } =
|
||||
await import("./legacy-crypto-inspector-availability.js");
|
||||
|
||||
describe("isMatrixLegacyCryptoInspectorAvailable", () => {
|
||||
beforeEach(() => {
|
||||
availabilityState.currentFilePath = "/virtual/dist/matrix-migration.runtime.js";
|
||||
availabilityState.existingPaths.clear();
|
||||
availabilityState.dirEntries = [];
|
||||
});
|
||||
|
||||
it("detects the source inspector module directly", () => {
|
||||
availabilityState.currentFilePath =
|
||||
"/virtual/extensions/matrix/src/legacy-crypto-inspector-availability.js";
|
||||
availabilityState.existingPaths.add(
|
||||
"/virtual/extensions/matrix/src/matrix/legacy-crypto-inspector.ts",
|
||||
);
|
||||
|
||||
expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(true);
|
||||
});
|
||||
|
||||
it("detects hashed built inspector chunks", () => {
|
||||
availabilityState.dirEntries = [
|
||||
{
|
||||
name: "legacy-crypto-inspector-TPlLnFSE.js",
|
||||
isFile: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not confuse the availability helper artifact with the real inspector", () => {
|
||||
availabilityState.dirEntries = [
|
||||
{
|
||||
name: "legacy-crypto-inspector-availability.js",
|
||||
isFile: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not confuse hashed availability helper chunks with the real inspector", () => {
|
||||
availabilityState.dirEntries = [
|
||||
{
|
||||
name: "legacy-crypto-inspector-availability-TPlLnFSE.js",
|
||||
isFile: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,31 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const LEGACY_CRYPTO_INSPECTOR_BASENAME_RE = /^legacy-crypto-inspector(?:[-.].*)?\.js$/u;
|
||||
const LEGACY_CRYPTO_INSPECTOR_FILE = "legacy-crypto-inspector.js";
|
||||
const LEGACY_CRYPTO_INSPECTOR_CHUNK_PREFIX = "legacy-crypto-inspector-";
|
||||
const LEGACY_CRYPTO_INSPECTOR_HELPER_CHUNK_PREFIX = "availability-";
|
||||
const JAVASCRIPT_MODULE_SUFFIX = ".js";
|
||||
|
||||
function isLegacyCryptoInspectorArtifactName(name: string): boolean {
|
||||
if (name === LEGACY_CRYPTO_INSPECTOR_FILE) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
!name.startsWith(LEGACY_CRYPTO_INSPECTOR_CHUNK_PREFIX) ||
|
||||
!name.endsWith(JAVASCRIPT_MODULE_SUFFIX)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const chunkSuffix = name.slice(
|
||||
LEGACY_CRYPTO_INSPECTOR_CHUNK_PREFIX.length,
|
||||
-JAVASCRIPT_MODULE_SUFFIX.length,
|
||||
);
|
||||
return (
|
||||
chunkSuffix.length > 0 &&
|
||||
chunkSuffix !== "availability" &&
|
||||
!chunkSuffix.startsWith(LEGACY_CRYPTO_INSPECTOR_HELPER_CHUNK_PREFIX)
|
||||
);
|
||||
}
|
||||
|
||||
function hasSourceInspectorArtifact(currentDir: string): boolean {
|
||||
return [
|
||||
@@ -20,7 +44,7 @@ function hasBuiltInspectorArtifact(currentDir: string): boolean {
|
||||
}
|
||||
return fs
|
||||
.readdirSync(currentDir, { withFileTypes: true })
|
||||
.some((entry) => entry.isFile() && LEGACY_CRYPTO_INSPECTOR_BASENAME_RE.test(entry.name));
|
||||
.some((entry) => entry.isFile() && isLegacyCryptoInspectorArtifactName(entry.name));
|
||||
}
|
||||
|
||||
export function isMatrixLegacyCryptoInspectorAvailable(): boolean {
|
||||
|
||||
@@ -92,6 +92,7 @@ describe("matrix legacy encrypted-state migration", () => {
|
||||
const { cfg, rootDir } = writeDefaultLegacyCryptoFixture(home);
|
||||
|
||||
const detection = detectLegacyMatrixCrypto({ cfg, env: process.env });
|
||||
expect(detection.inspectorAvailable).toBe(true);
|
||||
expect(detection.warnings).toEqual([]);
|
||||
expect(detection.plans).toHaveLength(1);
|
||||
|
||||
@@ -210,6 +211,7 @@ describe("matrix legacy encrypted-state migration", () => {
|
||||
const { cfg } = writeDefaultLegacyCryptoFixture(home);
|
||||
|
||||
const detection = detectLegacyMatrixCrypto({ cfg, env: process.env });
|
||||
expect(detection.inspectorAvailable).toBe(false);
|
||||
expect(detection.plans).toHaveLength(1);
|
||||
expect(detection.warnings).toContain(
|
||||
"Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.",
|
||||
|
||||
@@ -57,6 +57,7 @@ type MatrixLegacyCryptoPlan = {
|
||||
};
|
||||
|
||||
type MatrixLegacyCryptoDetection = {
|
||||
inspectorAvailable: boolean;
|
||||
plans: MatrixLegacyCryptoPlan[];
|
||||
warnings: string[];
|
||||
};
|
||||
@@ -324,13 +325,20 @@ export function detectLegacyMatrixCrypto(params: {
|
||||
cfg: params.cfg,
|
||||
env: params.env ?? process.env,
|
||||
});
|
||||
if (detection.plans.length > 0 && !isMatrixLegacyCryptoInspectorAvailable()) {
|
||||
const inspectorAvailable =
|
||||
detection.plans.length === 0 || isMatrixLegacyCryptoInspectorAvailable();
|
||||
if (!inspectorAvailable && detection.plans.length > 0) {
|
||||
return {
|
||||
inspectorAvailable,
|
||||
plans: detection.plans,
|
||||
warnings: [...detection.warnings, MATRIX_LEGACY_CRYPTO_INSPECTOR_UNAVAILABLE_MESSAGE],
|
||||
};
|
||||
}
|
||||
return detection;
|
||||
return {
|
||||
inspectorAvailable,
|
||||
plans: detection.plans,
|
||||
warnings: detection.warnings,
|
||||
};
|
||||
}
|
||||
|
||||
export async function autoPrepareLegacyMatrixCrypto(params: {
|
||||
@@ -359,7 +367,7 @@ export async function autoPrepareLegacyMatrixCrypto(params: {
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
if (!params.deps?.inspectLegacyStore && !isMatrixLegacyCryptoInspectorAvailable()) {
|
||||
if (!params.deps?.inspectLegacyStore && !detection.inspectorAvailable) {
|
||||
if (warnings.length > 0) {
|
||||
params.log?.warn?.(
|
||||
`matrix: legacy encrypted-state warnings:\n${warnings.map((entry) => `- ${entry}`).join("\n")}`,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export { autoMigrateLegacyMatrixState, detectLegacyMatrixState } from "./legacy-state.js";
|
||||
export { autoPrepareLegacyMatrixCrypto, detectLegacyMatrixCrypto } from "./legacy-crypto.js";
|
||||
export { hasActionableMatrixMigration, hasPendingMatrixMigration } from "./migration-snapshot.js";
|
||||
export {
|
||||
hasActionableMatrixMigration,
|
||||
hasPendingMatrixMigration,
|
||||
resolveMatrixMigrationStatus,
|
||||
type MatrixMigrationStatus,
|
||||
} from "./migration-snapshot.js";
|
||||
export { maybeCreateMatrixMigrationSnapshot } from "./migration-snapshot-backup.js";
|
||||
|
||||
@@ -124,6 +124,7 @@ describe("matrix migration snapshots", () => {
|
||||
cfg,
|
||||
env: process.env,
|
||||
});
|
||||
expect(detection.inspectorAvailable).toBe(true);
|
||||
expect(detection.plans).toHaveLength(1);
|
||||
expect(detection.warnings).toEqual([]);
|
||||
expect(
|
||||
@@ -167,6 +168,7 @@ describe("matrix migration snapshots", () => {
|
||||
cfg,
|
||||
env: process.env,
|
||||
});
|
||||
expect(detection.inspectorAvailable).toBe(false);
|
||||
expect(detection.plans).toHaveLength(1);
|
||||
expect(detection.warnings).toContain(
|
||||
"Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isMatrixLegacyCryptoInspectorAvailable } from "./legacy-crypto-inspector-availability.js";
|
||||
import { detectLegacyMatrixCrypto } from "./legacy-crypto.js";
|
||||
import { detectLegacyMatrixState } from "./legacy-state.js";
|
||||
import {
|
||||
@@ -9,30 +8,43 @@ import {
|
||||
type MatrixMigrationSnapshotResult,
|
||||
} from "./migration-snapshot-backup.js";
|
||||
|
||||
export type MatrixMigrationStatus = {
|
||||
legacyState: ReturnType<typeof detectLegacyMatrixState>;
|
||||
legacyCrypto: ReturnType<typeof detectLegacyMatrixCrypto>;
|
||||
pending: boolean;
|
||||
actionable: boolean;
|
||||
};
|
||||
|
||||
export function resolveMatrixMigrationStatus(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): MatrixMigrationStatus {
|
||||
const env = params.env ?? process.env;
|
||||
const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env });
|
||||
const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env });
|
||||
const actionableLegacyState = legacyState !== null && !("warning" in legacyState);
|
||||
const actionableLegacyCrypto = legacyCrypto.plans.length > 0 && legacyCrypto.inspectorAvailable;
|
||||
return {
|
||||
legacyState,
|
||||
legacyCrypto,
|
||||
pending:
|
||||
legacyState !== null || legacyCrypto.plans.length > 0 || legacyCrypto.warnings.length > 0,
|
||||
actionable: actionableLegacyState || actionableLegacyCrypto,
|
||||
};
|
||||
}
|
||||
|
||||
export function hasPendingMatrixMigration(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
const env = params.env ?? process.env;
|
||||
const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env });
|
||||
if (legacyState) {
|
||||
return true;
|
||||
}
|
||||
const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env });
|
||||
return legacyCrypto.plans.length > 0 || legacyCrypto.warnings.length > 0;
|
||||
return resolveMatrixMigrationStatus(params).pending;
|
||||
}
|
||||
|
||||
export function hasActionableMatrixMigration(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
const env = params.env ?? process.env;
|
||||
const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env });
|
||||
if (legacyState && !("warning" in legacyState)) {
|
||||
return true;
|
||||
}
|
||||
const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env });
|
||||
return legacyCrypto.plans.length > 0 && isMatrixLegacyCryptoInspectorAvailable();
|
||||
return resolveMatrixMigrationStatus(params).actionable;
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome } from "../../../test/helpers/temp-home.js";
|
||||
|
||||
const legacyCryptoInspectorAvailability = vi.hoisted(() => ({
|
||||
available: true,
|
||||
}));
|
||||
|
||||
vi.mock("./legacy-crypto-inspector-availability.js", () => ({
|
||||
isMatrixLegacyCryptoInspectorAvailable: () => legacyCryptoInspectorAvailability.available,
|
||||
}));
|
||||
|
||||
import { runMatrixStartupMaintenance } from "./startup-maintenance.js";
|
||||
import { resolveMatrixAccountStorageRoot } from "./storage-paths.js";
|
||||
|
||||
async function seedLegacyMatrixState(home: string) {
|
||||
const stateDir = path.join(home, ".openclaw");
|
||||
@@ -26,6 +36,22 @@ function makeMatrixStartupConfig(includeCredentials = true) {
|
||||
} as const;
|
||||
}
|
||||
|
||||
async function seedLegacyMatrixCrypto(home: string) {
|
||||
const stateDir = path.join(home, ".openclaw");
|
||||
const { rootDir } = resolveMatrixAccountStorageRoot({
|
||||
stateDir,
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "tok-123",
|
||||
});
|
||||
await fs.mkdir(path.join(rootDir, "crypto"), { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(rootDir, "crypto", "bot-sdk.json"),
|
||||
JSON.stringify({ deviceId: "DEVICE123" }),
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
function createSuccessfulMatrixMigrationDeps() {
|
||||
return {
|
||||
maybeCreateMatrixMigrationSnapshot: vi.fn(async () => ({
|
||||
@@ -42,6 +68,10 @@ function createSuccessfulMatrixMigrationDeps() {
|
||||
}
|
||||
|
||||
describe("runMatrixStartupMaintenance", () => {
|
||||
beforeEach(() => {
|
||||
legacyCryptoInspectorAvailability.available = true;
|
||||
});
|
||||
|
||||
it("creates a snapshot before actionable startup migration", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await seedLegacyMatrixState(home);
|
||||
@@ -78,6 +108,7 @@ describe("runMatrixStartupMaintenance", () => {
|
||||
const autoMigrateLegacyMatrixStateMock = vi.fn();
|
||||
const autoPrepareLegacyMatrixCryptoMock = vi.fn();
|
||||
const info = vi.fn();
|
||||
const warn = vi.fn();
|
||||
|
||||
await runMatrixStartupMaintenance({
|
||||
cfg: makeMatrixStartupConfig(false),
|
||||
@@ -87,7 +118,7 @@ describe("runMatrixStartupMaintenance", () => {
|
||||
autoMigrateLegacyMatrixState: autoMigrateLegacyMatrixStateMock as never,
|
||||
autoPrepareLegacyMatrixCrypto: autoPrepareLegacyMatrixCryptoMock as never,
|
||||
},
|
||||
log: { info },
|
||||
log: { info, warn },
|
||||
});
|
||||
|
||||
expect(maybeCreateMatrixMigrationSnapshotMock).not.toHaveBeenCalled();
|
||||
@@ -96,6 +127,41 @@ describe("runMatrixStartupMaintenance", () => {
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet",
|
||||
);
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining("could not be resolved yet"));
|
||||
});
|
||||
});
|
||||
|
||||
it("logs the concrete unavailable-inspector warning when startup migration is warning-only", async () => {
|
||||
legacyCryptoInspectorAvailability.available = false;
|
||||
|
||||
await withTempHome(async (home) => {
|
||||
await seedLegacyMatrixCrypto(home);
|
||||
const maybeCreateMatrixMigrationSnapshotMock = vi.fn();
|
||||
const autoMigrateLegacyMatrixStateMock = vi.fn();
|
||||
const autoPrepareLegacyMatrixCryptoMock = vi.fn();
|
||||
const info = vi.fn();
|
||||
const warn = vi.fn();
|
||||
|
||||
await runMatrixStartupMaintenance({
|
||||
cfg: makeMatrixStartupConfig(),
|
||||
env: process.env,
|
||||
deps: {
|
||||
maybeCreateMatrixMigrationSnapshot: maybeCreateMatrixMigrationSnapshotMock as never,
|
||||
autoMigrateLegacyMatrixState: autoMigrateLegacyMatrixStateMock as never,
|
||||
autoPrepareLegacyMatrixCrypto: autoPrepareLegacyMatrixCryptoMock as never,
|
||||
},
|
||||
log: { info, warn },
|
||||
});
|
||||
|
||||
expect(maybeCreateMatrixMigrationSnapshotMock).not.toHaveBeenCalled();
|
||||
expect(autoMigrateLegacyMatrixStateMock).not.toHaveBeenCalled();
|
||||
expect(autoPrepareLegacyMatrixCryptoMock).not.toHaveBeenCalled();
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet",
|
||||
);
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
"matrix: legacy encrypted-state warnings:\n- Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
autoMigrateLegacyMatrixState,
|
||||
autoPrepareLegacyMatrixCrypto,
|
||||
hasActionableMatrixMigration,
|
||||
hasPendingMatrixMigration,
|
||||
maybeCreateMatrixMigrationSnapshot,
|
||||
resolveMatrixMigrationStatus,
|
||||
type MatrixMigrationStatus,
|
||||
} from "./matrix-migration.runtime.js";
|
||||
|
||||
type MatrixStartupLogger = {
|
||||
@@ -12,6 +12,21 @@ type MatrixStartupLogger = {
|
||||
warn?: (message: string) => void;
|
||||
};
|
||||
|
||||
function logWarningOnlyMatrixMigrationReasons(params: {
|
||||
status: MatrixMigrationStatus;
|
||||
log: MatrixStartupLogger;
|
||||
}): void {
|
||||
if (params.status.legacyState && "warning" in params.status.legacyState) {
|
||||
params.log.warn?.(`matrix: ${params.status.legacyState.warning}`);
|
||||
}
|
||||
|
||||
if (params.status.legacyCrypto.warnings.length > 0) {
|
||||
params.log.warn?.(
|
||||
`matrix: legacy encrypted-state warnings:\n${params.status.legacyCrypto.warnings.map((entry) => `- ${entry}`).join("\n")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function runBestEffortMatrixMigrationStep(params: {
|
||||
label: string;
|
||||
log: MatrixStartupLogger;
|
||||
@@ -48,16 +63,16 @@ export async function runMatrixStartupMaintenance(params: {
|
||||
params.deps?.autoPrepareLegacyMatrixCrypto ?? autoPrepareLegacyMatrixCrypto;
|
||||
const trigger = params.trigger?.trim() || "gateway-startup";
|
||||
const logPrefix = params.logPrefix?.trim() || "gateway";
|
||||
const actionable = hasActionableMatrixMigration({ cfg: params.cfg, env });
|
||||
const pending = actionable || hasPendingMatrixMigration({ cfg: params.cfg, env });
|
||||
const migrationStatus = resolveMatrixMigrationStatus({ cfg: params.cfg, env });
|
||||
|
||||
if (!pending) {
|
||||
if (!migrationStatus.pending) {
|
||||
return;
|
||||
}
|
||||
if (!actionable) {
|
||||
if (!migrationStatus.actionable) {
|
||||
params.log.info?.(
|
||||
"matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet",
|
||||
);
|
||||
logWarningOnlyMatrixMigrationReasons({ status: migrationStatus, log: params.log });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ type MatrixLegacyCryptoPlan = {
|
||||
};
|
||||
|
||||
type MatrixLegacyCryptoDetection = {
|
||||
inspectorAvailable: boolean;
|
||||
plans: MatrixLegacyCryptoPlan[];
|
||||
warnings: string[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user