Reapply "refactor: move runtime state to SQLite"

This reverts commit 694ca50e97.
This commit is contained in:
Peter Steinberger
2026-05-13 13:34:30 +01:00
parent 05db911775
commit c6ee68b751
3003 changed files with 128551 additions and 130603 deletions

View File

@@ -565,8 +565,6 @@ describe("scripts/changed-lanes", () => {
"guarded extension wildcard re-exports",
"plugin-sdk wildcard re-exports",
"duplicate scan target coverage",
"dependency pin guard",
"package patch guard",
"typecheck core tests",
"lint core",
"lint scripts",
@@ -1106,8 +1104,6 @@ describe("scripts/changed-lanes", () => {
args: ["lint:extensions:no-plugin-sdk-wildcard-reexports"],
},
{ name: "duplicate scan target coverage", args: ["dup:check:coverage"] },
{ name: "dependency pin guard", args: ["deps:pins:check"] },
{ name: "package patch guard", args: ["deps:patches:check"] },
]);
});
@@ -1128,8 +1124,6 @@ describe("scripts/changed-lanes", () => {
args: ["lint:extensions:no-plugin-sdk-wildcard-reexports"],
},
{ name: "duplicate scan target coverage", args: ["dup:check:coverage"] },
{ name: "dependency pin guard", args: ["deps:pins:check"] },
{ name: "package patch guard", args: ["deps:patches:check"] },
]);
});
});

View File

@@ -0,0 +1,94 @@
import { describe, expect, it } from "vitest";
import { collectKyselyGuardrailViolations } from "../../scripts/check-kysely-guardrails.mjs";
function messagesFor(content: string, relativePath = "src/example/store.sqlite.ts"): string[] {
return collectKyselyGuardrailViolations(content, relativePath).map(
(violation) => violation.message,
);
}
describe("Kysely guardrails", () => {
it("rejects explicit sync-helper row generics for builder queries", () => {
expect(
messagesFor(`
import { executeSqliteQuerySync } from "../infra/kysely-sync.js";
executeSqliteQuerySync<{ id: string }>(db, query);
`),
).toContain("sync helper row generic at call site; let Kysely infer builder result rows");
});
it("rejects persisted row casts to enum-like types in SQLite stores", () => {
expect(
messagesFor(`
type TaskStatus = "running" | "succeeded";
function rowToRecord(row: { status: string }) {
return {
status: row.status as TaskStatus,
};
}
`),
).toContain(
"persisted SQLite enum-like values must be parsed through closed validators, not cast",
);
});
it("allows explicit local escape hatches for reviewed persisted casts", () => {
expect(
messagesFor(`
type TaskStatus = "running" | "succeeded";
function rowToRecord(row: { status: string }) {
return {
status: row.status as TaskStatus, // sqlite-allow-persisted-cast
};
}
`),
).toEqual([]);
});
it("rejects typed raw SQL outside allowlisted boundaries", () => {
expect(
messagesFor(
`
import { sql } from "kysely";
const count = sql<number>\`COUNT(*)\`;
`,
"src/example/report.ts",
),
).toContain("typed raw sql snippet needs a small helper or allowlisted boundary");
});
it("rejects direct raw node:sqlite prepare in new production files", () => {
expect(
messagesFor(
`
import { requireNodeSqlite } from "../infra/node-sqlite.js";
const sqlite = requireNodeSqlite();
const db = new sqlite.DatabaseSync(":memory:");
db.prepare("select 1").get();
`,
"src/example/raw-store.ts",
),
).toContain(
"new raw node:sqlite access requires Kysely or an explicit raw SQLite allowlist entry",
);
});
it("keeps ordinary static Kysely reference strings valid", () => {
expect(
messagesFor(`
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
const query = getNodeSqliteKysely<{ task_runs: { task_id: string } }>(db)
.selectFrom("task_runs")
.select(["task_id"])
.where("task_id", "=", taskId);
executeSqliteQuerySync(db, query);
`),
).toEqual([]);
});
});

View File

@@ -1454,7 +1454,8 @@ test -f "$TMPDIR/docker-cmd-seen"
expect(runner).toContain("scripts/e2e/lib/plugin-update/unchanged-scenario.sh");
expect(probe).toContain("plugin install record changed unexpectedly");
expect(probe).toContain("index.installRecords ?? index.records ?? config.plugins?.installs");
expect(probe).toContain("readInstalledPluginRecords()");
expect(probe).toContain('records["lossless-claw"] ?? records["@example/lossless-claw"]');
expect(scenario).toContain("Config changed unexpectedly for modern package");
expect(scenario).not.toContain("before_hash");
});

View File

@@ -832,15 +832,31 @@ describe("install.sh", () => {
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-nvm-"));
const home = join(tmp, "home");
const systemBin = join(tmp, "system-bin");
const nvmBin = join(home, ".nvm/versions/node/v22.22.1/bin");
const nvmBin = join(home, ".nvm/versions/node/v24.13.0/bin");
mkdirSync(systemBin, { recursive: true });
mkdirSync(nvmBin, { recursive: true });
mkdirSync(join(home, ".nvm"), { recursive: true });
const systemNode = join(systemBin, "node");
const nvmNode = join(nvmBin, "node");
writeFileSync(systemNode, "#!/bin/sh\necho v8.11.3\n");
writeFileSync(nvmNode, "#!/bin/sh\necho v22.22.1\n");
writeFileSync(
systemNode,
[
"#!/bin/sh",
'if [ "${1:-}" = "-p" ]; then echo "8 11"; exit 0; fi',
"echo v8.11.3",
"",
].join("\n"),
);
writeFileSync(
nvmNode,
[
"#!/bin/sh",
'if [ "${1:-}" = "-p" ]; then echo "24 13"; exit 0; fi',
"echo v24.13.0",
"",
].join("\n"),
);
chmodSync(systemNode, 0o755);
chmodSync(nvmNode, 0o755);
writeFileSync(
@@ -850,7 +866,7 @@ describe("install.sh", () => {
"export NVM_DIR",
"nvm() {",
' if [ "$1" = "use" ]; then',
' export PATH="$NVM_DIR/versions/node/v22.22.1/bin:$PATH"',
' export PATH="$NVM_DIR/versions/node/v24.13.0/bin:$PATH"',
" return 0",
" fi",
" return 0",
@@ -887,7 +903,7 @@ describe("install.sh", () => {
const output = result?.stdout ?? "";
expect(output).toContain("status=0");
expect(output).toContain(`path=${nvmNode}`);
expect(output).toContain("version=v22.22.1");
expect(output).toContain("version=v24.13.0");
});
it("installs Homebrew lazily before macOS Git installs", () => {
@@ -915,8 +931,24 @@ describe("install.sh", () => {
const staleNode = join(staleBin, "node");
const supportedNode = join(supportedBin, "node");
writeFileSync(staleNode, "#!/bin/sh\necho v20.20.0\n");
writeFileSync(supportedNode, "#!/bin/sh\necho v22.22.0\n");
writeFileSync(
staleNode,
[
"#!/bin/sh",
'if [ "${1:-}" = "-p" ]; then echo "20 20"; exit 0; fi',
"echo v20.20.0",
"",
].join("\n"),
);
writeFileSync(
supportedNode,
[
"#!/bin/sh",
'if [ "${1:-}" = "-p" ]; then echo "24 13"; exit 0; fi',
"echo v24.13.0",
"",
].join("\n"),
);
chmodSync(staleNode, 0o755);
chmodSync(supportedNode, 0o755);
@@ -956,7 +988,7 @@ describe("install.sh", () => {
expect(output).toContain("promote=0");
expect(output).toContain("active=0");
expect(output).toContain(`path=${supportedNode}`);
expect(output).toContain("version=v22.22.0");
expect(output).toContain("version=v24.13.0");
});
it("persists a supported Linux Node path before noninteractive shell guards", () => {

View File

@@ -132,8 +132,11 @@ describe("production lint suppressions", () => {
"scripts/lib/plugin-npm-release.ts|typescript/no-unnecessary-type-parameters|1",
"src/agents/agent-scope.ts|no-control-regex|1",
"src/agents/code-mode.worker.ts|unicorn/require-post-message-target-origin|1",
"src/agents/embedded-agent-runner/run/images.ts|no-control-regex|1",
"src/agents/pi-embedded-runner/run/images.ts|no-control-regex|1",
"src/agents/runtime-worker.entry.ts|unicorn/require-post-message-target-origin|1",
"src/agents/runtime-worker.ts|unicorn/require-post-message-target-origin|1",
"src/agents/subagent-attachments.ts|no-control-regex|1",
"src/agents/subagent-registry.store.ts|typescript/no-unnecessary-type-parameters|1",
"src/agents/subagent-spawn.ts|no-control-regex|1",
"src/channels/plugins/channel-runtime-surface.types.ts|typescript/no-unnecessary-type-parameters|1",
"src/channels/plugins/contracts/test-helpers.ts|typescript/no-unnecessary-type-parameters|1",
@@ -159,6 +162,7 @@ describe("production lint suppressions", () => {
"src/plugin-sdk/test-helpers/package-manifest-contract.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugin-sdk/test-helpers/public-surface-loader.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugin-sdk/test-helpers/subagent-hooks.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugins/hook-types.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugins/hooks.ts|typescript/no-unnecessary-type-parameters|1",
"src/plugins/host-hook-runtime.ts|typescript/no-unnecessary-type-parameters|2",
"src/plugins/host-hook-state.ts|typescript/no-unnecessary-type-parameters|1",

View File

@@ -13,12 +13,10 @@ describe("live Docker state staging", () => {
expect(script).toContain("--exclude=.artifacts");
});
it("keeps host-only generated registry state out of the container copy", () => {
it("keeps host workspace artifacts out of the container state copy", () => {
const script = readFileSync(stageScriptPath, "utf8");
expect(script).toContain("--exclude=workspace");
expect(script).toContain("--exclude=sandboxes");
expect(script).toContain("--exclude=plugins/installs.json");
expect(script).toContain("host-absolute paths");
});
});

View File

@@ -412,10 +412,9 @@ if (isPrlctl) {
expect(missingKey.stderr).toContain("PARALLELS_TEST_MISSING_KEY is required");
});
it("seeds agent workspace state before OS smoke agent turns", () => {
it("seeds configured agent workspace files before OS smoke agent turns", () => {
const workspace = readFileSync(TS_PATHS.agentWorkspace, "utf8");
expect(workspace).toContain("workspace-state.json");
expect(workspace).toContain("IDENTITY.md");
expect(workspace).toContain("BOOTSTRAP.md");
@@ -694,7 +693,7 @@ if (isPrlctl) {
expect(script).toContain("agent turn attempt $attempt failed or finished without OK response");
expect(script).not.toContain("$config.models.providers");
expect(script).not.toContain("timeoutSeconds = 300");
expect(script).toContain('"$sessionId.jsonl"');
expect(script).not.toContain("$sessionId.jsonl");
});
it("gives GPT-5.5 enough Parallels model time on slower desktop guests", () => {

View File

@@ -0,0 +1,23 @@
import { execFileSync } from "node:child_process";
import path from "node:path";
import { describe, expect, it } from "vitest";
const scriptPath = path.join(process.cwd(), "scripts", "pre-commit", "filter-staged-files.mjs");
function filterFiles(mode: "format" | "lint", files: string[]): string[] {
const output = execFileSync(process.execPath, [scriptPath, mode, "--", ...files], {
encoding: "utf8",
});
return output.split("\0").filter(Boolean);
}
describe("pre-commit staged-file filter", () => {
it("does not format generated Kysely declaration files", () => {
expect(
filterFiles("format", [
"src/state/openclaw-state-db.generated.d.ts",
"src/state/openclaw-state-db.ts",
]),
).toEqual(["src/state/openclaw-state-db.ts"]);
});
});