mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
1ea2640f54
* refactor(state): make cron and subagent rows JSON-canonical * refactor(state): make gateway origin device tokens canonical at v13 The lazy ensure predates the table joining the canonical schema; at the v13 bump the schema owns creation, so the feature-local DDL, WeakSet dedupe, and lazy-list entry retire. The legacy-file guard the ensure carried stays at each call site. * test: drop obsolete lazy-ensure coverage for origin device tokens The table is canonical at v13; same-version lazy creation no longer exists to protect. Origin CRUD, isolation, and rotation coverage remains in the surviving cases. * refactor(state): fold installed_plugin_index into config_machine_state The singleton index row becomes one JSON value under plugins.installedIndex with its rollback-fencing revision inside the value; reads, CAS restore, and the lease-held write transactions use direct Kysely on config_machine_state so the state_leases assertion stays in-transaction. The v13 migration imports the row and drops the table; the additive workspace_dir entry folds with it. Doctor guidance, docker staging, and the e2e probes name the machine-state row. * refactor(state): merge workspace_attestations into workspace_setup_state One row per workspace now carries both setup milestones and the attestation clock: nullable setup columns represent attestation-only workspaces (replaceWorkspaceAttestation can precede any setup write) and setupExists derives from a non-null version. The bootstrap-hash FK repoints to the merged table; migration receipts keep the historical workspace_attestations discriminator string. The v13 migration grows and rebuilds the table, merges attestation rows (orphans without a path alias drop — their hashes re-derive at the next bootstrap attestation), and the consolidation kind is renamed state-consolidation-v13 to cover the batch. * test(state): cover the workspace merge and consolidation fallout The v12-to-v13 regression seeds merged, attestation-only, and orphan attestation workspaces; the 13-to-12 downgrade fixture recreates workspace_attestations and installed_plugin_index from the folded data; the fold-in migration gates the additive workspace_dir column for pre-additive rows; the workspace merge now triggers on the setup table's own shape so stable-era databases without an attestations table still reshape; the consolidation applied-message covers the batch. * refactor(state): fold shared auth profile singletons into config_machine_state The shared-state auth_profile_stores/auth_profile_state rows (fixed key 'shared') become authProfiles.store/authProfiles.state machine-state values; the agent-DB tables of the same names are untouched. Git-backup redaction moves from table-drop to the authProfiles. secret prefix with seeded-secret absence proof; migration receipts keep the historical table-name discriminators; the shared-auth relocation and receipt verification project the KV cells back to the receipt-era row shapes so persisted digests stay byte-compatible. mcp_oauth_stores stays a table — its multi-key fold is a named follow-up. * test(state): finish shared-auth fold coverage and annotate boundary casts Auth seeders and assertions across the e2e/scripts/secrets suites target the authProfiles machine-state cells; the v12-to-v13 regression proves payload-byte fidelity, non-shared-row drop, and insert-if-absent precedence; the downgrade fixture recreates and repopulates both v12 tables. Boundary type assertions in the plugin-index store carry SAFETY invariants per the ratchet. * chore: shrink assertion-safety baseline for plugin-index store * refactor(doctor): delete the dead onboarding-recommendations migration Its input — the unscoped 'primary' onboarding row — existed only between9a93a52a8aand473962b7de, a two-day beta window; no shipped stable can produce it and the runtime table folded away at v12. The audit backup list keeps recognizing system-agent.jsonl artifacts because beta installs that ran that import may still carry its backups. * docs: sync the 13-to-12 downgrade example with the executable fixture * style: format the synced downgrade example * style: drop unused import and duplicate union constituent * fix(state): keep orphan attestations across the v13 workspace merge The merged workspace_setup_state required a workspace path, but legacy orphan hashed-key attestations never recorded one. workspace_path is now nullable (setup rows still enforce it via CHECK), the v13 migration and the doctor file import keep orphans with a NULL path that heals on the next live access, and the 13-to-12 downgrade keeps attestation-owned hashes. Doctor test seeds move to the folded KV row. * perf(state): retire unused cron indexes * fix(state): preserve v13 migration recovery * fix(state): preserve v12 lazy-table upgrade * docs(state): document v13 auth relocation --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
134 lines
4.4 KiB
TypeScript
134 lines
4.4 KiB
TypeScript
// Plugin Index SQLite tests cover shared E2E install-index readers.
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
import { pathToFileURL } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
import { withEnvAsync } from "../../src/test-utils/env.js";
|
|
|
|
const MODULE_URL = pathToFileURL(path.resolve("scripts/e2e/lib/plugin-index-sqlite.mjs")).href;
|
|
let importCounter = 0;
|
|
|
|
async function loadPluginIndex(env: Record<string, string> = {}) {
|
|
return await withEnvAsync(env, async () => {
|
|
return await import(`${MODULE_URL}?case=${importCounter++}`);
|
|
});
|
|
}
|
|
|
|
function writeLegacyIndex(root: string, text: string) {
|
|
const file = path.join(root, "plugins", "installs.json");
|
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
writeFileSync(file, text, "utf8");
|
|
}
|
|
|
|
function configPath(root: string) {
|
|
return path.join(root, "openclaw.json");
|
|
}
|
|
|
|
function writeSqliteIndex(root: string, installRecordsJson: string) {
|
|
const dbPath = path.join(root, "state", "openclaw.sqlite");
|
|
mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
const db = new DatabaseSync(dbPath);
|
|
try {
|
|
db.exec(`
|
|
CREATE TABLE config_machine_state (
|
|
state_key TEXT NOT NULL PRIMARY KEY,
|
|
value_json TEXT NOT NULL,
|
|
updated_at_ms INTEGER NOT NULL
|
|
);
|
|
`);
|
|
db.prepare(
|
|
"INSERT INTO config_machine_state (state_key, value_json, updated_at_ms) VALUES (?, ?, ?)",
|
|
).run(
|
|
"plugins.installedIndex",
|
|
JSON.stringify({
|
|
revision: Date.now(),
|
|
index: {
|
|
version: 1,
|
|
hostContractVersion: "1",
|
|
compatRegistryVersion: "1",
|
|
migrationVersion: 1,
|
|
policyHash: "hash",
|
|
generatedAtMs: Date.now(),
|
|
installRecords: JSON.parse(installRecordsJson) as unknown,
|
|
plugins: [],
|
|
diagnostics: [],
|
|
},
|
|
}),
|
|
Date.now(),
|
|
);
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
describe("plugin index SQLite E2E helpers", () => {
|
|
it("reads legacy install records when SQLite index state is absent", async () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-index-"));
|
|
try {
|
|
writeLegacyIndex(
|
|
root,
|
|
JSON.stringify({ records: { demo: { installPath: "/tmp/demo", source: "npm" } } }),
|
|
);
|
|
|
|
const { readPluginInstallRecords } = await loadPluginIndex();
|
|
|
|
expect(readPluginInstallRecords({ stateDir: root, configPath: configPath(root) })).toEqual({
|
|
demo: { installPath: "/tmp/demo", source: "npm" },
|
|
});
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("keeps malformed legacy install JSON as an empty fallback", async () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-index-"));
|
|
try {
|
|
writeLegacyIndex(root, "{not-json");
|
|
|
|
const { readPluginInstallRecords } = await loadPluginIndex();
|
|
|
|
expect(readPluginInstallRecords({ stateDir: root, configPath: configPath(root) })).toEqual(
|
|
{},
|
|
);
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rejects oversized legacy install JSON before parsing it", async () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-index-"));
|
|
try {
|
|
writeLegacyIndex(root, JSON.stringify({ records: {}, filler: "x".repeat(128) }));
|
|
|
|
const { readPluginInstallRecords } = await loadPluginIndex({
|
|
OPENCLAW_PLUGIN_INDEX_JSON_MAX_BYTES: "64",
|
|
});
|
|
|
|
expect(() =>
|
|
readPluginInstallRecords({ stateDir: root, configPath: configPath(root) }),
|
|
).toThrow("plugin index JSON artifact exceeded 64 bytes");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rejects oversized SQLite install index JSON before parsing it", async () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-index-"));
|
|
try {
|
|
writeSqliteIndex(root, JSON.stringify({ filler: "x".repeat(128) }));
|
|
|
|
const { readPluginInstallIndex } = await loadPluginIndex({
|
|
OPENCLAW_PLUGIN_INDEX_JSON_MAX_BYTES: "64",
|
|
});
|
|
|
|
expect(() =>
|
|
readPluginInstallIndex({ stateDir: root, configPath: configPath(root) }),
|
|
).toThrow("plugin index value_json exceeded 64 bytes");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
});
|