mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
9eae43bd37
* refactor(infra): move exec approvals into the shared SQLite state DB Delete the file-runtime exec-approvals store (exec-approvals.json + .lock sidecar machinery) on both runtimes and make the reserved exec_approvals_config singleton row canonical. Doctor owns the one-time import with claim/verify/receipt discipline; runtime fails closed with a doctor instruction while un-migrated legacy state exists. The wire CAS contract, socket semantics, and gateway auth-token derivations are unchanged. Kills the #113929 lock-contention bug class structurally and nets around -2.9k lines. * fix(infra): green CI gates and retire file-era exec approvals tests Break the migration-type import cycle with a leaf contract, regenerate the plugin-SDK API and native i18n baselines for the intentional surface change, drop unused exports, and replace the macOS file-era approvals test suite with SQLite-backed behavior coverage per the obsolete-internals test policy. * chore: green max-lines ratchet, native i18n baseline, and unused-export scan
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import type { ExecApprovalsFile } from "../infra/exec-approvals-core.js";
|
|
import { saveExecApprovals } from "../infra/exec-approvals-store.js";
|
|
import { testing as execApprovalsStoreTesting } from "../infra/exec-approvals-store.test-support.js";
|
|
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
|
import { captureEnv, setTestEnvValue } from "../test-utils/env.js";
|
|
|
|
const envSnapshot = captureEnv(["HOME", "OPENCLAW_HOME", "OPENCLAW_STATE_DIR"]);
|
|
|
|
const tempHomes: string[] = [];
|
|
|
|
function useTempHome(): string {
|
|
const home = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-approval-runtime-"));
|
|
tempHomes.push(home);
|
|
setTestEnvValue("HOME", home);
|
|
setTestEnvValue("OPENCLAW_HOME", home);
|
|
setTestEnvValue("OPENCLAW_STATE_DIR", path.join(home, ".openclaw"));
|
|
closeOpenClawStateDatabaseForTest();
|
|
execApprovalsStoreTesting.reset();
|
|
return home;
|
|
}
|
|
|
|
function execApprovalsPath(home: string): string {
|
|
return path.join(home, ".openclaw", "exec-approvals.json");
|
|
}
|
|
|
|
function writeExecApprovalsToken(_home: string, token: string): void {
|
|
saveExecApprovals({
|
|
version: 1,
|
|
socket: {
|
|
path: "~/.openclaw/exec-approvals.sock",
|
|
token,
|
|
},
|
|
agents: {},
|
|
} satisfies ExecApprovalsFile);
|
|
}
|
|
|
|
async function importRuntimeTokenModule(): Promise<
|
|
typeof import("./operator-approval-runtime-token.js")
|
|
> {
|
|
vi.resetModules();
|
|
return await import("./operator-approval-runtime-token.js");
|
|
}
|
|
|
|
afterEach(() => {
|
|
closeOpenClawStateDatabaseForTest();
|
|
execApprovalsStoreTesting.reset();
|
|
vi.resetModules();
|
|
envSnapshot.restore();
|
|
for (const home of tempHomes.splice(0)) {
|
|
fs.rmSync(home, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe("operator approval runtime token", () => {
|
|
it("derives the shared approval runtime token from the exec approvals socket token", async () => {
|
|
const home = useTempHome();
|
|
writeExecApprovalsToken(home, "shared-runtime-token");
|
|
|
|
const runtimeToken = await importRuntimeTokenModule();
|
|
const sharedToken = runtimeToken.getOperatorApprovalRuntimeToken();
|
|
|
|
expect(sharedToken).toEqual(expect.any(String));
|
|
expect(sharedToken).not.toBe("shared-runtime-token");
|
|
expect(runtimeToken.isOperatorApprovalRuntimeToken(` ${sharedToken} `)).toBe(true);
|
|
expect(runtimeToken.isOperatorApprovalRuntimeToken(sharedToken.slice(0, -1))).toBe(false);
|
|
expect(runtimeToken.isOperatorApprovalRuntimeToken("shared-runtime-token")).toBe(false);
|
|
expect(runtimeToken.isOperatorApprovalRuntimeToken("different-token")).toBe(false);
|
|
});
|
|
|
|
it("does not pin the process fallback once a shared exec approvals token appears", async () => {
|
|
const home = useTempHome();
|
|
const runtimeToken = await importRuntimeTokenModule();
|
|
|
|
const fallback = runtimeToken.getOperatorApprovalRuntimeToken();
|
|
writeExecApprovalsToken(home, "late-shared-runtime-token");
|
|
const sharedToken = runtimeToken.getOperatorApprovalRuntimeToken();
|
|
|
|
expect(sharedToken).not.toBe(fallback);
|
|
expect(sharedToken).not.toBe("late-shared-runtime-token");
|
|
expect(runtimeToken.isOperatorApprovalRuntimeToken(fallback)).toBe(true);
|
|
expect(runtimeToken.isOperatorApprovalRuntimeToken(sharedToken)).toBe(true);
|
|
expect(runtimeToken.isOperatorApprovalRuntimeToken("late-shared-runtime-token")).toBe(false);
|
|
});
|
|
|
|
it("keeps a stable process fallback without creating exec-approvals.json", async () => {
|
|
const home = useTempHome();
|
|
const runtimeToken = await importRuntimeTokenModule();
|
|
|
|
const first = runtimeToken.getOperatorApprovalRuntimeToken();
|
|
const second = runtimeToken.getOperatorApprovalRuntimeToken();
|
|
|
|
expect(first).toEqual(expect.any(String));
|
|
expect(second).toBe(first);
|
|
expect(fs.existsSync(execApprovalsPath(home))).toBe(false);
|
|
});
|
|
});
|