mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: finish commitments retirement bookkeeping (#122143)
* refactor(commitments): remove remaining retired references * refactor(state): track schema retirements
This commit is contained in:
committed by
GitHub
parent
82ba647673
commit
6792921dea
+1
-1
@@ -9,5 +9,5 @@ Use this rubric when assigning category Completeness scores for the
|
|||||||
- Event Ingress: Telegram long polling, Telegram webhook mode, Zalo polling/webhook mode, Polling stall diagnostics, iMessage watch fallback, Gmail setup wizard, Watcher start/serve, Tailscale/public routing, Push token validation, Gmail event routing, POST /hooks/wake, POST /hooks/agent, Mapped hooks, Hook auth policy, Async dispatch
|
- Event Ingress: Telegram long polling, Telegram webhook mode, Zalo polling/webhook mode, Polling stall diagnostics, iMessage watch fallback, Gmail setup wizard, Watcher start/serve, Tailscale/public routing, Push token validation, Gmail event routing, POST /hooks/wake, POST /hooks/agent, Mapped hooks, Hook auth policy, Async dispatch
|
||||||
- Automation Hooks: HOOK.md authoring, Hook discovery, Hook CLI management, Hook packs, Lifecycle event dispatch, api.on registration, Tool-call policy hooks, Message hooks, Session/lifecycle hooks, Plugin approval requests, cron_changed
|
- Automation Hooks: HOOK.md authoring, Hook discovery, Hook CLI management, Hook packs, Lifecycle event dispatch, api.on registration, Tool-call policy hooks, Message hooks, Session/lifecycle hooks, Plugin approval requests, cron_changed
|
||||||
- Background Tasks and Flows: Task list/show/cancel, Task notifications, Task audit and maintenance, Chat task board, Task pressure status, Managed flows, Mirrored flows, openclaw tasks flow, Flow audit and maintenance, Plugin managedFlows
|
- Background Tasks and Flows: Task list/show/cancel, Task notifications, Task audit and maintenance, Chat task board, Task pressure status, Managed flows, Mirrored flows, openclaw tasks flow, Flow audit and maintenance, Plugin managedFlows
|
||||||
- Heartbeat: Heartbeat scheduling, Active hours, Wake and cooldown handling, Due-only heartbeat tasks, Commitment check-ins
|
- Heartbeat: Heartbeat scheduling, Active hours, Wake and cooldown handling, Due-only heartbeat tasks
|
||||||
- Polling Controls: openclaw message poll, Telegram polls, Teams polls, Poll flags, Channel capability gates, process poll, process log, Background process status, No-progress loop detection, Process input controls
|
- Polling Controls: openclaw message poll, Telegram polls, Teams polls, Poll flags, Channel capability gates, process poll, process log, Background process status, No-progress loop detection, Process input controls
|
||||||
|
|||||||
@@ -697,10 +697,6 @@ const SOURCE_TEST_TARGETS = new Map([
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
["src/commands/doctor-memory-search.ts", ["src/commands/doctor-memory-search.test.ts"]],
|
["src/commands/doctor-memory-search.ts", ["src/commands/doctor-memory-search.test.ts"]],
|
||||||
[
|
|
||||||
"src/commitments/model-selection.runtime.ts",
|
|
||||||
["src/commitments/runtime.test.ts", "src/agents/model-selection.test.ts"],
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"src/agents/test-helpers/live-model-turn-probes.ts",
|
"src/agents/test-helpers/live-model-turn-probes.ts",
|
||||||
["src/agents/live-model-turn-probes.test.ts"],
|
["src/agents/live-model-turn-probes.test.ts"],
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"retirements": [
|
||||||
|
{
|
||||||
|
"database": "state",
|
||||||
|
"status": "planned",
|
||||||
|
"targetVersion": 7,
|
||||||
|
"table": "commitments",
|
||||||
|
"indexes": [
|
||||||
|
"idx_commitments_scope_due",
|
||||||
|
"idx_commitments_status_due",
|
||||||
|
"idx_commitments_scope_dedupe",
|
||||||
|
"idx_commitments_agent_due",
|
||||||
|
"idx_commitments_agent_sent"
|
||||||
|
],
|
||||||
|
"note": "Remove the retired commitments feature in the next shared-state schema."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database": "agent",
|
||||||
|
"status": "completed",
|
||||||
|
"targetVersion": 17,
|
||||||
|
"table": "state_leases",
|
||||||
|
"indexes": ["idx_agent_state_leases_expiry", "idx_agent_state_leases_owner"],
|
||||||
|
"note": "Removed from the canonical per-agent schema in version 17."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { DatabaseSync } from "node:sqlite";
|
||||||
|
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||||
|
import { expect, it } from "vitest";
|
||||||
|
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "./openclaw-agent-db-contract.js";
|
||||||
|
import retirementManifest from "./openclaw-schema-retirements.json" with { type: "json" };
|
||||||
|
import { OPENCLAW_STATE_SCHEMA_VERSION } from "./openclaw-state-db-contract.js";
|
||||||
|
|
||||||
|
type DatabaseOwner = "state" | "agent";
|
||||||
|
type RetirementStatus = "planned" | "completed";
|
||||||
|
|
||||||
|
type SchemaRetirement = {
|
||||||
|
database: DatabaseOwner;
|
||||||
|
status: RetirementStatus;
|
||||||
|
targetVersion: number;
|
||||||
|
table: string;
|
||||||
|
indexes: string[];
|
||||||
|
note?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function assertInvariant(condition: unknown, message: string): asserts condition {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseString(value: unknown, field: string): string {
|
||||||
|
assertInvariant(typeof value === "string" && value.length > 0, `${field} must be a string`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStringArray(value: unknown, field: string): string[] {
|
||||||
|
assertInvariant(Array.isArray(value), `${field} must be an array`);
|
||||||
|
return value.map((entry, index) => parseString(entry, `${field}[${index}]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRetirement(value: unknown, index: number): SchemaRetirement {
|
||||||
|
assertInvariant(isRecord(value), `retirements[${index}] must be an object`);
|
||||||
|
assertInvariant(
|
||||||
|
value.database === "state" || value.database === "agent",
|
||||||
|
`retirements[${index}].database must name a database owner`,
|
||||||
|
);
|
||||||
|
assertInvariant(
|
||||||
|
value.status === "planned" || value.status === "completed",
|
||||||
|
`retirements[${index}].status must be planned or completed`,
|
||||||
|
);
|
||||||
|
assertInvariant(
|
||||||
|
Number.isInteger(value.targetVersion) && Number(value.targetVersion) >= 0,
|
||||||
|
`retirements[${index}].targetVersion must be a non-negative integer`,
|
||||||
|
);
|
||||||
|
assertInvariant(
|
||||||
|
value.note === undefined || typeof value.note === "string",
|
||||||
|
`retirements[${index}].note must be a string when present`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
database: value.database,
|
||||||
|
status: value.status,
|
||||||
|
targetVersion: Number(value.targetVersion),
|
||||||
|
table: parseString(value.table, `retirements[${index}].table`),
|
||||||
|
indexes: parseStringArray(value.indexes, `retirements[${index}].indexes`),
|
||||||
|
note: value.note,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRetirementManifest(value: unknown): SchemaRetirement[] {
|
||||||
|
assertInvariant(isRecord(value), "retirement manifest must be an object");
|
||||||
|
assertInvariant(Array.isArray(value.retirements), "retirements must be an array");
|
||||||
|
return value.retirements.map(parseRetirement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertUniqueRetirements(retirements: readonly SchemaRetirement[]): void {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const retirement of retirements) {
|
||||||
|
const key = `${retirement.database}/${retirement.table}`;
|
||||||
|
assertInvariant(!seen.has(key), `duplicate schema retirement: ${key}`);
|
||||||
|
seen.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasSchemaObject(database: DatabaseSync, type: "table" | "index", name: string): boolean {
|
||||||
|
return (
|
||||||
|
database.prepare("SELECT 1 FROM sqlite_schema WHERE type = ? AND name = ?").get(type, name) !==
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("keeps the schema retirement ledger aligned with canonical database schemas", () => {
|
||||||
|
const retirements = parseRetirementManifest(retirementManifest);
|
||||||
|
assertUniqueRetirements(retirements);
|
||||||
|
|
||||||
|
const databases = {
|
||||||
|
state: {
|
||||||
|
currentVersion: OPENCLAW_STATE_SCHEMA_VERSION,
|
||||||
|
schemaUrl: new URL("./openclaw-state-schema.sql", import.meta.url),
|
||||||
|
database: new DatabaseSync(":memory:"),
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
currentVersion: OPENCLAW_AGENT_SCHEMA_VERSION,
|
||||||
|
schemaUrl: new URL("./openclaw-agent-schema.sql", import.meta.url),
|
||||||
|
database: new DatabaseSync(":memory:"),
|
||||||
|
},
|
||||||
|
} satisfies Record<
|
||||||
|
DatabaseOwner,
|
||||||
|
{ currentVersion: number; schemaUrl: URL; database: DatabaseSync }
|
||||||
|
>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const database of Object.values(databases)) {
|
||||||
|
database.database.exec(readFileSync(database.schemaUrl, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const retirement of retirements) {
|
||||||
|
const { currentVersion, database } = databases[retirement.database];
|
||||||
|
const shouldExist = retirement.status === "planned";
|
||||||
|
|
||||||
|
if (shouldExist) {
|
||||||
|
expect(retirement.targetVersion).toBe(currentVersion + 1);
|
||||||
|
} else {
|
||||||
|
expect(retirement.targetVersion).toBeLessThanOrEqual(currentVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(hasSchemaObject(database, "table", retirement.table)).toBe(shouldExist);
|
||||||
|
for (const index of retirement.indexes) {
|
||||||
|
expect(hasSchemaObject(database, "index", index)).toBe(shouldExist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
for (const database of Object.values(databases)) {
|
||||||
|
database.database.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user