From d90bece2a2f4a70c577f26cfa1e80b0601c8da8d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 01:16:53 +0100 Subject: [PATCH] refactor: keep exec approvals legacy path in doctor --- docs/refactor/database-first.md | 2 +- src/commands/doctor/legacy/exec-approvals.test.ts | 9 +++------ src/commands/doctor/legacy/exec-approvals.ts | 9 +++++++-- src/infra/exec-approvals-store.test.ts | 13 +++++++------ src/infra/exec-approvals.ts | 15 ++++++++------- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/docs/refactor/database-first.md b/docs/refactor/database-first.md index 3da3ae132b6..7e738383d9c 100644 --- a/docs/refactor/database-first.md +++ b/docs/refactor/database-first.md @@ -201,7 +201,7 @@ The branch already has a real shared SQLite base: no longer reads or rewrites the legacy workspace marker. - Exec approvals now live in shared SQLite KV (`exec.approvals/current`). Doctor imports legacy `~/.openclaw/exec-approvals.json`; runtime writes no - longer create or rewrite that file. + longer create, rewrite, or report that file as its active store location. - Device identity, device auth, and bootstrap runtime modules now keep their SQLite snapshot readers/writers separate from doctor-only legacy JSON import helpers. diff --git a/src/commands/doctor/legacy/exec-approvals.test.ts b/src/commands/doctor/legacy/exec-approvals.test.ts index e3cc07302ea..cc7196b814b 100644 --- a/src/commands/doctor/legacy/exec-approvals.test.ts +++ b/src/commands/doctor/legacy/exec-approvals.test.ts @@ -2,15 +2,12 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - loadExecApprovals, - resolveExecApprovalsPath, - type ExecApprovalsFile, -} from "../../../infra/exec-approvals.js"; +import { loadExecApprovals, type ExecApprovalsFile } from "../../../infra/exec-approvals.js"; import { resetPluginStateStoreForTests } from "../../../plugin-state/plugin-state-store.js"; import { importLegacyExecApprovalsFileToSqlite, legacyExecApprovalsFileExists, + resolveLegacyExecApprovalsPath, } from "./exec-approvals.js"; const tempDirs: string[] = []; @@ -49,7 +46,7 @@ function createHomeDir(): string { } function writeApprovalsFile(homeDir: string, file: ExecApprovalsFile): string { - const approvalsPath = resolveExecApprovalsPath({ + const approvalsPath = resolveLegacyExecApprovalsPath({ ...process.env, OPENCLAW_HOME: homeDir, }); diff --git a/src/commands/doctor/legacy/exec-approvals.ts b/src/commands/doctor/legacy/exec-approvals.ts index c1bdb74f557..53225edfe4a 100644 --- a/src/commands/doctor/legacy/exec-approvals.ts +++ b/src/commands/doctor/legacy/exec-approvals.ts @@ -1,5 +1,5 @@ import fs from "node:fs"; -import { resolveExecApprovalsPath } from "../../../infra/exec-approvals.js"; +import { expandHomePrefix } from "../../../infra/home-dir.js"; import type { OpenClawStateDatabaseOptions } from "../../../state/openclaw-state-db.js"; import { writeOpenClawStateKvJson, @@ -8,6 +8,7 @@ import { const EXEC_APPROVALS_KV_SCOPE = "exec.approvals"; const EXEC_APPROVALS_KV_KEY = "current"; +const LEGACY_EXEC_APPROVALS_FILE = "~/.openclaw/exec-approvals.json"; function sqliteOptionsForEnv(env: NodeJS.ProcessEnv): OpenClawStateDatabaseOptions { return { env }; @@ -18,13 +19,17 @@ function readLegacyExecApprovalsRaw(env: NodeJS.ProcessEnv = process.env): { exists: boolean; path: string; } { - const filePath = resolveExecApprovalsPath(env); + const filePath = resolveLegacyExecApprovalsPath(env); if (!fs.existsSync(filePath)) { return { raw: null, exists: false, path: filePath }; } return { raw: fs.readFileSync(filePath, "utf8"), exists: true, path: filePath }; } +export function resolveLegacyExecApprovalsPath(env: NodeJS.ProcessEnv = process.env): string { + return expandHomePrefix(LEGACY_EXEC_APPROVALS_FILE, { env }); +} + export function legacyExecApprovalsFileExists(env: NodeJS.ProcessEnv = process.env): boolean { return readLegacyExecApprovalsRaw(env).exists; } diff --git a/src/infra/exec-approvals-store.test.ts b/src/infra/exec-approvals-store.test.ts index 57773cde118..e7abfd4da80 100644 --- a/src/infra/exec-approvals-store.test.ts +++ b/src/infra/exec-approvals-store.test.ts @@ -26,7 +26,7 @@ let readExecApprovalsSnapshot: ExecApprovalsModule["readExecApprovalsSnapshot"]; let recordAllowlistMatchesUse: ExecApprovalsModule["recordAllowlistMatchesUse"]; let recordAllowlistUse: ExecApprovalsModule["recordAllowlistUse"]; let requestExecApprovalViaSocket: ExecApprovalsModule["requestExecApprovalViaSocket"]; -let resolveExecApprovalsPath: ExecApprovalsModule["resolveExecApprovalsPath"]; +let resolveExecApprovalsStoreLocationForDisplay: ExecApprovalsModule["resolveExecApprovalsStoreLocationForDisplay"]; let resolveExecApprovalsSocketPath: ExecApprovalsModule["resolveExecApprovalsSocketPath"]; let saveExecApprovals: ExecApprovalsModule["saveExecApprovals"]; @@ -47,7 +47,7 @@ beforeAll(async () => { recordAllowlistMatchesUse, recordAllowlistUse, requestExecApprovalViaSocket, - resolveExecApprovalsPath, + resolveExecApprovalsStoreLocationForDisplay, resolveExecApprovalsSocketPath, saveExecApprovals, } = await import("./exec-approvals.js")); @@ -119,12 +119,13 @@ function expectAllowlistEntryFields( } describe("exec approvals store helpers", () => { - it("expands home-prefixed default file and socket paths for compatibility labels", () => { + it("reports the SQLite store location and expands the socket path", () => { const dir = createHomeDir(); - expect(path.normalize(resolveExecApprovalsPath())).toBe( - path.normalize(path.join(dir, ".openclaw", "exec-approvals.json")), + expect(resolveExecApprovalsStoreLocationForDisplay()).toContain( + path.join(process.env.OPENCLAW_STATE_DIR ?? "", "state", "openclaw.sqlite"), ); + expect(resolveExecApprovalsStoreLocationForDisplay()).toContain("#kv/exec.approvals/current"); expect(path.normalize(resolveExecApprovalsSocketPath())).toBe( path.normalize(path.join(dir, ".openclaw", "exec-approvals.sock")), ); @@ -168,7 +169,7 @@ describe("exec approvals store helpers", () => { expect(missing.exists).toBe(false); expect(missing.raw).toBeNull(); expect(missing.file).toEqual(normalizeExecApprovals({ version: 1, agents: {} })); - expect(path.normalize(missing.path)).toBe(path.normalize(approvalsFilePath(dir))); + expect(missing.path).toBe(resolveExecApprovalsStoreLocationForDisplay()); fs.mkdirSync(path.dirname(approvalsFilePath(dir)), { recursive: true }); fs.writeFileSync(approvalsFilePath(dir), "{invalid", "utf8"); diff --git a/src/infra/exec-approvals.ts b/src/infra/exec-approvals.ts index 88e37d2fd94..b43669ffd35 100644 --- a/src/infra/exec-approvals.ts +++ b/src/infra/exec-approvals.ts @@ -7,6 +7,7 @@ import { readStringValue, } from "../shared/string-coerce.js"; import type { OpenClawStateDatabaseOptions } from "../state/openclaw-state-db.js"; +import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js"; import { deleteOpenClawStateKvJson, readOpenClawStateKvJson, @@ -211,7 +212,6 @@ const DEFAULT_ASK: ExecAsk = "off"; export const DEFAULT_EXEC_APPROVAL_ASK_FALLBACK: ExecSecurity = "full"; const DEFAULT_AUTO_ALLOW_SKILLS = false; const DEFAULT_SOCKET = "~/.openclaw/exec-approvals.sock"; -const DEFAULT_FILE = "~/.openclaw/exec-approvals.json"; const EXEC_APPROVALS_KV_SCOPE = "exec.approvals"; const EXEC_APPROVALS_KV_KEY = "current"; @@ -222,8 +222,10 @@ function hashExecApprovalsRaw(raw: string | null): string { .digest("hex"); } -export function resolveExecApprovalsPath(env: NodeJS.ProcessEnv = process.env): string { - return expandHomePrefix(DEFAULT_FILE, { env }); +export function resolveExecApprovalsStoreLocationForDisplay( + env: NodeJS.ProcessEnv = process.env, +): string { + return `${resolveOpenClawStateSqlitePath(env)}#kv/${EXEC_APPROVALS_KV_SCOPE}/${EXEC_APPROVALS_KV_KEY}`; } export function resolveExecApprovalsSocketPath(env: NodeJS.ProcessEnv = process.env): string { @@ -465,10 +467,9 @@ function parseExecApprovalsRaw(raw: string | null): ExecApprovalsFile { } export function readExecApprovalsSnapshot(): ExecApprovalsSnapshot { - const filePath = resolveExecApprovalsPath(); const sqliteRaw = readExecApprovalsRawFromSqlite(); return { - path: filePath, + path: resolveExecApprovalsStoreLocationForDisplay(), exists: sqliteRaw !== null, raw: sqliteRaw, file: parseExecApprovalsRaw(sqliteRaw), @@ -655,7 +656,7 @@ export function resolveExecApprovals( file, agentId, overrides, - path: resolveExecApprovalsPath(), + path: resolveExecApprovalsStoreLocationForDisplay(), socketPath: expandHomePrefix(file.socket?.path ?? resolveExecApprovalsSocketPath()), token: file.socket?.token ?? "", }); @@ -731,7 +732,7 @@ export function resolveExecApprovalsFromFile(params: { ...(Array.isArray(agent.allowlist) ? agent.allowlist : []), ]; return { - path: params.path ?? resolveExecApprovalsPath(), + path: params.path ?? resolveExecApprovalsStoreLocationForDisplay(), socketPath: expandHomePrefix( params.socketPath ?? file.socket?.path ?? resolveExecApprovalsSocketPath(), ),