mirror of
https://github.com/openclaw/openclaw.git
synced 2026-11-08 17:23:38 +00:00
refactor(state): engine-neutral state database handles (#171521)
Generalize state database handles and consumers to the existing engine-neutral connection contract. Keep SQLite-only operations explicitly narrowed; production SQLite behavior, schemas, admission, and durability remain unchanged. Validation: 104 SQLite capture executions are byte-identical; focused checks and Codex review passed. The PR body records the authorized CI exception: three inherited test-job failures and two unrelated flakes, plus their aggregate gates. No test expectations were weakened.
This commit is contained in:
parent
1a55095dc3
commit
1edc42ce55
472 changed files with 2981 additions and 2046 deletions
|
|
@ -181,7 +181,7 @@ it.each(["board", "session end"] as const)(
|
|||
await lifecycle.start();
|
||||
const db = openOpenClawStateDatabase().db;
|
||||
const prepare = db.prepare.bind(db);
|
||||
const hostRead = vi.spyOn(db, "prepare").mockImplementation((sql) => {
|
||||
const hostRead = vi.spyOn(db, "prepare").mockImplementation((sql: string) => {
|
||||
if (/select\b[\s\S]*\bplugin_state_entries\b/i.test(sql)) {
|
||||
throw new Error("Dashboard discovery queried plugin rows on the host");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import "fake-indexeddb/auto";
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { backup } from "node:sqlite";
|
||||
import { backup, DatabaseSync } from "node:sqlite";
|
||||
import { toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { resetFileLockStateForTest } from "openclaw/plugin-sdk/file-lock";
|
||||
|
|
@ -410,6 +410,9 @@ describe("Matrix IndexedDB persistence", () => {
|
|||
const source = openOpenClawStateDatabase({
|
||||
env: openMatrixIdbSnapshotStoreOptions(tmpDir).env,
|
||||
});
|
||||
if (!(source.db instanceof DatabaseSync)) {
|
||||
throw new Error("Matrix snapshot corruption fixture requires SQLite");
|
||||
}
|
||||
const corruptRoot = path.join(tmpDir, "corrupt-startup");
|
||||
fs.mkdirSync(path.join(corruptRoot, "state"), { recursive: true });
|
||||
await backup(source.db, path.join(corruptRoot, "state", "openclaw.sqlite"));
|
||||
|
|
|
|||
|
|
@ -53,8 +53,6 @@ describe.skipIf(!postgresUrl)("experimental PostgreSQL workboard admission and r
|
|||
owner.close();
|
||||
throw new Error("Experimental workboard open did not select PostgreSQL");
|
||||
}
|
||||
const schema = owner.db.schema;
|
||||
schemas.add(schema);
|
||||
// The host anchor owns admission until the worker has released its connections.
|
||||
const fixture = openPostgresFixture({ dbPath, postgresInput });
|
||||
let closed = false;
|
||||
|
|
@ -70,7 +68,8 @@ describe.skipIf(!postgresUrl)("experimental PostgreSQL workboard admission and r
|
|||
}
|
||||
}
|
||||
owners.push({ close });
|
||||
await fixture.state();
|
||||
const { schema } = await fixture.state();
|
||||
schemas.add(schema);
|
||||
return { fixture, schema, close };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
|
||||
import { DatabaseSync, type SQLInputValue } from "node:sqlite";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { expectDefined } from "../packages/normalization-core/src/expect.js";
|
||||
import { getSessionKysely } from "../src/config/sessions/session-accessor.sqlite-scope.js";
|
||||
|
|
@ -720,12 +720,16 @@ function main(): void {
|
|||
const started = nowMs();
|
||||
try {
|
||||
const stateDatabase = openOpenClawStateDatabase({ env });
|
||||
const stateDb = stateDatabase.db;
|
||||
if (!(stateDb instanceof DatabaseSync)) {
|
||||
throw new Error("SQLite state benchmark requires a SQLite state database");
|
||||
}
|
||||
const agentDatabases = Array.from({ length: config.agentCount }, (_, index) =>
|
||||
openOpenClawAgentDatabase({ agentId: `perf-agent-${index}`, env }),
|
||||
);
|
||||
|
||||
const seedStarted = nowMs();
|
||||
seedStateDatabase(stateDatabase.db, config);
|
||||
seedStateDatabase(stateDb, config);
|
||||
const perAgentEntries = Math.ceil(config.agentCacheEntries / config.agentCount);
|
||||
agentDatabases.forEach((database, index) =>
|
||||
seedAgentDatabase(database.db, perAgentEntries, index),
|
||||
|
|
@ -734,16 +738,16 @@ function main(): void {
|
|||
|
||||
const stateWalBefore = walSize(stateDatabase.path);
|
||||
const agentWalBefore = agentDatabases.map((database) => walSize(database.path));
|
||||
const stateIntegrity = readIntegrity(stateDatabase.db);
|
||||
const stateIntegrity = readIntegrity(stateDb);
|
||||
const agentIntegrity = agentDatabases.map((database) => readIntegrity(database.db));
|
||||
const queries = runHotQueries({
|
||||
agentDb: agentDatabases[0]?.db ?? stateDatabase.db,
|
||||
agentDb: agentDatabases[0]?.db ?? stateDb,
|
||||
config,
|
||||
stateDb: stateDatabase.db,
|
||||
stateDb,
|
||||
});
|
||||
|
||||
const checkpointStarted = nowMs();
|
||||
checkpoint(stateDatabase.db);
|
||||
checkpoint(stateDb);
|
||||
agentDatabases.forEach((database) => checkpoint(database.db));
|
||||
const checkpointMs = nowMs() - checkpointStarted;
|
||||
|
||||
|
|
@ -764,7 +768,7 @@ function main(): void {
|
|||
queries,
|
||||
versions: {
|
||||
agentSchema: OPENCLAW_AGENT_SCHEMA_VERSION,
|
||||
sqlite: readSqliteVersion(stateDatabase.db),
|
||||
sqlite: readSqliteVersion(stateDb),
|
||||
stateSchema: OPENCLAW_STATE_SCHEMA_VERSION,
|
||||
},
|
||||
rows: {
|
||||
|
|
|
|||
|
|
@ -354,7 +354,9 @@ src/config/zod-schema.database.ts
|
|||
src/infra/database-engine/admission-scope.ts
|
||||
src/infra/database-engine/json-values.ts
|
||||
src/infra/database-engine/observation.ts
|
||||
src/infra/database-engine/physical-facts.ts
|
||||
src/infra/database-engine/policy.ts
|
||||
src/infra/database-engine/postgres-catalog.ts
|
||||
src/infra/postgres-sync/connection.ts
|
||||
src/infra/postgres-sync/protocol.ts
|
||||
src/infra/postgres-sync/transaction.ts
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "../state/openclaw-state-db.js";
|
||||
import type { WorkerWriteOperationContext } from "../state/worker-operation-registry.js";
|
||||
import { withTestDir } from "../test-helpers/temp-dir.js";
|
||||
import { requireNativeSqliteDatabase } from "../test-utils/native-sqlite.js";
|
||||
import { createSqliteAcpEventLedger } from "./event-ledger.js";
|
||||
import { expectAcpReplayUtf8Accounting } from "./event-ledger.test-support.js";
|
||||
import { normalizeAcpLedgerOptions } from "./event-ledger.types.js";
|
||||
|
|
@ -31,7 +32,7 @@ describe("ACP replay worker", () => {
|
|||
for (let index = 0; index < 3; index++) {
|
||||
await ledger.recordUpdate({ ...session, update: update(`warm-${index}`) });
|
||||
}
|
||||
const { db } = openOpenClawStateDatabase(options);
|
||||
const db = requireNativeSqliteDatabase(openOpenClawStateDatabase(options).db);
|
||||
for (let index = 0; index < 4; index++) {
|
||||
await ledger.recordUpdate({
|
||||
sessionId: session.sessionId,
|
||||
|
|
@ -65,7 +66,9 @@ describe("ACP replay worker", () => {
|
|||
await ledger.recordUpdate({ ...session, update: update("after reopen") });
|
||||
const reopened = await ledger.readReplayBySessionId(session);
|
||||
expect(reopened.events.at(-1)).toMatchObject({ seq: 8, update: update("after reopen") });
|
||||
expectAcpReplayUtf8Accounting(openOpenClawStateDatabase(options).db);
|
||||
expectAcpReplayUtf8Accounting(
|
||||
requireNativeSqliteDatabase(openOpenClawStateDatabase(options).db),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -78,6 +81,7 @@ describe("ACP replay worker", () => {
|
|||
await ledger.recordUpdate({ ...session, update: update("prior payload") });
|
||||
await closeOpenClawStateDatabaseAsync();
|
||||
const database = openOpenClawStateDatabase(options);
|
||||
const db = requireNativeSqliteDatabase(database.db);
|
||||
const context: WorkerWriteOperationContext = {
|
||||
open: () => database,
|
||||
stateOptions: () => ({ path: options.path, env: process.env }),
|
||||
|
|
@ -85,7 +89,7 @@ describe("ACP replay worker", () => {
|
|||
writeAdmitted: (operation) => runOpenClawStateWriteTransaction(operation, options),
|
||||
};
|
||||
const { now: _now, ...limits } = normalizeAcpLedgerOptions();
|
||||
database.db.setAuthorizer((action, table, column) =>
|
||||
db.setAuthorizer((action, table, column) =>
|
||||
action === constants.SQLITE_READ &&
|
||||
table === "acp_replay_events" &&
|
||||
column === "update_json"
|
||||
|
|
@ -98,12 +102,12 @@ describe("ACP replay worker", () => {
|
|||
context,
|
||||
);
|
||||
} finally {
|
||||
database.db.setAuthorizer(null);
|
||||
db.setAuthorizer(null);
|
||||
}
|
||||
expect(database.db.prepare("SELECT COUNT(*) AS count FROM acp_replay_events").get()).toEqual({
|
||||
count: 2,
|
||||
});
|
||||
expectAcpReplayUtf8Accounting(database.db);
|
||||
expectAcpReplayUtf8Accounting(db);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -119,7 +123,7 @@ describe("ACP replay worker", () => {
|
|||
const large = "漢".repeat(32 * 1024);
|
||||
await ledger.recordUpdate({ ...session, update: update(large) });
|
||||
await ledger.recordUpdate({ ...session, update: update("small after large") });
|
||||
const { db } = openOpenClawStateDatabase(options);
|
||||
const db = requireNativeSqliteDatabase(openOpenClawStateDatabase(options).db);
|
||||
db.exec(`CREATE TRIGGER reject_replay_event BEFORE INSERT ON acp_replay_events
|
||||
BEGIN SELECT RAISE(ABORT, 'replay event rejected'); END`);
|
||||
try {
|
||||
|
|
@ -154,7 +158,7 @@ describe("ACP replay worker", () => {
|
|||
{ type: "text", text: "tail" },
|
||||
],
|
||||
});
|
||||
const { db } = openOpenClawStateDatabase(options);
|
||||
const db = requireNativeSqliteDatabase(openOpenClawStateDatabase(options).db);
|
||||
expect(db.prepare("SELECT seq, update_json FROM acp_replay_events").all()).toEqual([
|
||||
{
|
||||
seq: 2,
|
||||
|
|
@ -192,7 +196,7 @@ describe("ACP replay worker", () => {
|
|||
await roomy.recordUpdate({ ...session, update: update(`${sessionId}-${index}`) });
|
||||
}
|
||||
}
|
||||
const { db } = openOpenClawStateDatabase(options);
|
||||
const db = requireNativeSqliteDatabase(openOpenClawStateDatabase(options).db);
|
||||
// Imported/retained history can have gaps; next_seq is not an event count.
|
||||
db.exec(
|
||||
"UPDATE acp_replay_events SET seq = seq * 10; UPDATE acp_replay_sessions SET next_seq = 31",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
closeOpenClawStateDatabaseAsync,
|
||||
openOpenClawStateDatabase,
|
||||
} from "../state/openclaw-state-db.js";
|
||||
import { requireNativeSqliteDatabase } from "../test-utils/native-sqlite.js";
|
||||
import { createSqliteAcpEventLedger } from "./event-ledger.js";
|
||||
import {
|
||||
createTestAcpEventLedger,
|
||||
|
|
@ -137,7 +138,9 @@ describe("ACP event ledger", () => {
|
|||
cwd: "/é/e\u0301/台\0😀",
|
||||
complete: true,
|
||||
};
|
||||
const { db } = openOpenClawStateDatabase({ path: databasePath });
|
||||
const db = requireNativeSqliteDatabase(
|
||||
openOpenClawStateDatabase({ path: databasePath }).db,
|
||||
);
|
||||
await ledger.startSession(session);
|
||||
const initial = expectAcpReplayUtf8Accounting(db);
|
||||
for (let count = 0; count < 10; count++) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import {
|
||||
createSqliteQueryCache,
|
||||
executeSqliteQuerySync,
|
||||
prepareSqliteQuerySync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { coerceRequiredSqliteNumber as sqliteNumber } from "../infra/sqlite-number.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
import type {
|
||||
|
|
@ -42,7 +42,7 @@ type AcpReplaySessionRow = Pick<
|
|||
"session_id" | "session_key" | "cwd" | "complete" | "next_seq"
|
||||
>;
|
||||
|
||||
function createSqliteLedgerQueries(db: DatabaseSync) {
|
||||
function createSqliteLedgerQueries(db: EngineConnection) {
|
||||
const query = getNodeSqliteKysely<AcpLedgerDatabase>(db);
|
||||
return {
|
||||
readSession: prepareSqliteQuerySync<string, AcpReplaySessionRow>(db, (parameter) =>
|
||||
|
|
@ -271,18 +271,18 @@ function sqliteRowToLedgerEvent(row: {
|
|||
});
|
||||
}
|
||||
|
||||
function sqliteSessionMetadataQuery(db: DatabaseSync) {
|
||||
function sqliteSessionMetadataQuery(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<AcpLedgerDatabase>(db)
|
||||
.selectFrom("acp_replay_sessions")
|
||||
.select(["session_id", "session_key", "cwd", "complete", "next_seq"]);
|
||||
}
|
||||
|
||||
function readSqliteSessionById(db: DatabaseSync, sessionId: string) {
|
||||
function readSqliteSessionById(db: EngineConnection, sessionId: string) {
|
||||
return getSqliteLedgerQueries(db).readSession(sessionId).rows[0];
|
||||
}
|
||||
|
||||
function upsertSqliteSession(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
now: number,
|
||||
params: {
|
||||
sessionId: string;
|
||||
|
|
@ -338,7 +338,7 @@ function upsertSqliteSession(
|
|||
// event rows), maintained at insert/trim time. The budget check therefore
|
||||
// sums over at most maxSessions rows instead of scanning every event per
|
||||
// append, which was O(events) per message and quadratic while trimming.
|
||||
function estimateSqliteLedgerBytes(db: DatabaseSync): number {
|
||||
function estimateSqliteLedgerBytes(db: EngineConnection): number {
|
||||
const row = getSqliteLedgerQueries(db).readTotalBytes().rows[0];
|
||||
return normalizeSqliteInteger(row?.total ?? 0);
|
||||
}
|
||||
|
|
@ -346,7 +346,7 @@ function estimateSqliteLedgerBytes(db: DatabaseSync): number {
|
|||
const LEDGER_TRIM_EVENT_BATCH = 64;
|
||||
|
||||
// Keep the session's byte aggregate in sync with the deletion in the same transaction.
|
||||
function deleteOldestSqliteEvents(db: DatabaseSync, sessionId: string, limit: number): number {
|
||||
function deleteOldestSqliteEvents(db: EngineConnection, sessionId: string, limit: number): number {
|
||||
const queries = getSqliteLedgerQueries(db);
|
||||
const rows = queries.deleteOldestEvents({ sessionId, limit }).rows;
|
||||
if (rows.length === 0) {
|
||||
|
|
@ -357,7 +357,7 @@ function deleteOldestSqliteEvents(db: DatabaseSync, sessionId: string, limit: nu
|
|||
return rows.length;
|
||||
}
|
||||
|
||||
function trimSqliteLedger(db: DatabaseSync, state: AcpReplayLimits): void {
|
||||
function trimSqliteLedger(db: EngineConnection, state: AcpReplayLimits): void {
|
||||
// Indexed sequence endpoints bound the count even when retained sequences have
|
||||
// gaps. Only histories that could exceed the cap need an exact count.
|
||||
const queries = getSqliteLedgerQueries(db);
|
||||
|
|
@ -392,7 +392,7 @@ function trimSqliteLedger(db: DatabaseSync, state: AcpReplayLimits): void {
|
|||
}
|
||||
|
||||
function appendSqliteUpdate(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
state: AcpReplayLimits,
|
||||
params: AcpReplayAppendInput["events"][number] & {
|
||||
sessionId: string;
|
||||
|
|
@ -430,7 +430,7 @@ function appendSqliteUpdate(
|
|||
trimSqliteLedger(db, state);
|
||||
}
|
||||
|
||||
function readSqliteReplay(db: DatabaseSync, input: AcpReplayReadInput): AcpEventLedgerReplay {
|
||||
function readSqliteReplay(db: EngineConnection, input: AcpReplayReadInput): AcpEventLedgerReplay {
|
||||
const query = getNodeSqliteKysely<AcpLedgerDatabase>(db);
|
||||
let selected = query
|
||||
.selectFrom("acp_replay_sessions")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Insertable } from "kysely";
|
||||
import { normalizeStoreSessionKey } from "../../config/sessions/store-entry.js";
|
||||
import { registerNodeSqliteDisposeCallback } from "../../infra/kysely-sync-cache-state.js";
|
||||
|
|
@ -8,6 +7,8 @@ import {
|
|||
getNodeSqliteKysely,
|
||||
sqliteStringSet,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { sqlPublicationAnchor } from "../../infra/sql-connection.js";
|
||||
import {
|
||||
getSqliteDatabaseAdmission,
|
||||
publishSqliteDatabaseAdmission,
|
||||
|
|
@ -41,10 +42,10 @@ const metadataReceipt: SqliteDatabaseAdmissionKey<AcpMetadataReceipt> = {
|
|||
return undefined;
|
||||
},
|
||||
};
|
||||
const pendingMetadataChanges = new WeakMap<DatabaseSync, AcpMetadataReceipt>();
|
||||
const localMetadataChanges = new WeakMap<DatabaseSync, AcpMetadataReceipt>();
|
||||
const pendingMetadataChanges = new WeakMap<EngineConnection, AcpMetadataReceipt>();
|
||||
const localMetadataChanges = new WeakMap<EngineConnection, AcpMetadataReceipt>();
|
||||
const metadataRows = new WeakMap<
|
||||
DatabaseSync,
|
||||
EngineConnection,
|
||||
{
|
||||
schema: SqliteReadOperationRevision["schema"];
|
||||
receipt: AcpMetadataReceipt | undefined;
|
||||
|
|
@ -52,7 +53,7 @@ const metadataRows = new WeakMap<
|
|||
}
|
||||
>();
|
||||
|
||||
function publishAcpMetadataChanges(db: DatabaseSync, keys: readonly string[]) {
|
||||
function publishAcpMetadataChanges(db: EngineConnection, keys: readonly string[]) {
|
||||
if (keys.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -79,11 +80,11 @@ function publishAcpMetadataChanges(db: DatabaseSync, keys: readonly string[]) {
|
|||
publishSqliteDatabaseAdmission(db, metadataReceipt, receipt);
|
||||
}
|
||||
|
||||
export function getAcpSessionKysely(db: DatabaseSync) {
|
||||
export function getAcpSessionKysely(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<AcpSessionMetaDatabase>(db);
|
||||
}
|
||||
|
||||
export function selectAcpSessionRows(db: DatabaseSync): AcpSessionRow[] {
|
||||
export function selectAcpSessionRows(db: EngineConnection): AcpSessionRow[] {
|
||||
return executeSqliteQuerySync(
|
||||
db,
|
||||
getAcpSessionKysely(db)
|
||||
|
|
@ -95,7 +96,7 @@ export function selectAcpSessionRows(db: DatabaseSync): AcpSessionRow[] {
|
|||
}
|
||||
|
||||
export function selectAcpSessionRow(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
sessionKey: string,
|
||||
): AcpSessionRow | undefined {
|
||||
return executeSqliteQueryTakeFirstSync(
|
||||
|
|
@ -108,7 +109,7 @@ export function selectAcpSessionRow(
|
|||
}
|
||||
|
||||
export function* selectAcpSessionRowsByKeys(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
keys: readonly string[],
|
||||
firstCohort?: readonly AcpSessionRow[],
|
||||
) {
|
||||
|
|
@ -120,7 +121,7 @@ export function* selectAcpSessionRowsByKeys(
|
|||
if (!cached) {
|
||||
cached = { schema: revision.schema, receipt, rows: new Map() };
|
||||
metadataRows.set(db, cached);
|
||||
registerNodeSqliteDisposeCallback(db, () => metadataRows.delete(db));
|
||||
registerNodeSqliteDisposeCallback(sqlPublicationAnchor(db), () => metadataRows.delete(db));
|
||||
} else if (cached.schema !== revision.schema || cached.receipt !== receipt) {
|
||||
cached.schema = revision.schema;
|
||||
cached.receipt = receipt;
|
||||
|
|
@ -224,7 +225,7 @@ export function acpSessionRowMatchesEntry(
|
|||
}
|
||||
|
||||
export function selectAcpSessionRowForStoreEntry(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
storeSessionKey: string,
|
||||
agentId?: string,
|
||||
entry?: AcpSessionEntryBinding,
|
||||
|
|
@ -237,7 +238,7 @@ export function selectAcpSessionRowForStoreEntry(
|
|||
}
|
||||
|
||||
export function selectAcpSessionRowForRead(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
{ keys, entry }: AcpSessionReadInput,
|
||||
): AcpSessionRow | undefined {
|
||||
for (const key of keys) {
|
||||
|
|
@ -257,7 +258,7 @@ export function resolveReadableAcpSessionRow(params: {
|
|||
return row && acpSessionRowMatchesEntry(row, entry) ? row : undefined;
|
||||
}
|
||||
|
||||
export function upsertAcpSessionMetaRow(db: DatabaseSync, row: Insertable<AcpSessionsTable>) {
|
||||
export function upsertAcpSessionMetaRow(db: EngineConnection, row: Insertable<AcpSessionsTable>) {
|
||||
const written = executeSqliteQueryTakeFirstSync(
|
||||
db,
|
||||
getAcpSessionKysely(db)
|
||||
|
|
@ -289,7 +290,7 @@ export function upsertAcpSessionMetaRow(db: DatabaseSync, row: Insertable<AcpSes
|
|||
return written;
|
||||
}
|
||||
|
||||
export function deleteAcpSessionMetaRows(db: DatabaseSync, keys: readonly string[]): void {
|
||||
export function deleteAcpSessionMetaRows(db: EngineConnection, keys: readonly string[]): void {
|
||||
if (keys.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
import { tryResolveLegacyDataOwnerAgentId } from "../../agents/agent-scope-config.js";
|
||||
import { resolvePersistedSessionStoreOwnerForKey } from "../../config/sessions/session-store-owner.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { executeSqliteQuerySync } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { normalizeAgentId, parseAgentSessionKey } from "../../routing/session-key.js";
|
||||
import {
|
||||
acpSessionRowMatchesEntry,
|
||||
|
|
@ -78,7 +78,7 @@ export function legacyAcpSessionKeyCandidates(
|
|||
|
||||
/** Legacy file imports inspect every supported prior key before deciding whether metadata is superseded. */
|
||||
export function selectAcpMigrationRowForStoreEntry(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
sessionKey: string,
|
||||
agentId: string,
|
||||
cfg: OpenClawConfig,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { expectDefined } from "@openclaw/normalization-core/expect";
|
||||
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { isInternalSessionEffectsKey } from "../../config/sessions/internal-session-key.js";
|
||||
|
|
@ -8,6 +7,7 @@ import {
|
|||
getNodeSqliteKysely,
|
||||
sqliteStringSet,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { isIncognitoSessionKey, normalizeAgentId } from "../../routing/session-key.js";
|
||||
import {
|
||||
CONTENT_VERSION_KEY,
|
||||
|
|
@ -37,7 +37,7 @@ export function prepareAcpSessionMetadataRead(
|
|||
let firstCohort: AcpSessionRow[] | undefined;
|
||||
let failed: { error: unknown } | undefined;
|
||||
return {
|
||||
readContentVersionRow(this: void, db: DatabaseSync) {
|
||||
readContentVersionRow(this: void, db: EngineConnection) {
|
||||
try {
|
||||
const database = getNodeSqliteKysely<Pick<DB, "acp_sessions" | "config_machine_state">>(db);
|
||||
const rows = executeSqliteQuerySync(
|
||||
|
|
@ -91,7 +91,7 @@ export function prepareAcpSessionMetadataRead(
|
|||
return readStateSchemaContentVersionRow(db);
|
||||
}
|
||||
},
|
||||
read(db: DatabaseSync) {
|
||||
read(db: EngineConnection) {
|
||||
if (failed) {
|
||||
throw failed.error;
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ export function prepareAcpSessionMetadataRead(
|
|||
}
|
||||
|
||||
function selectAcpResumeSessions(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: Extract<AcpSessionReadCommand, { type: "acpSessions.resume" }>,
|
||||
): AcpResumeSessionRow[] {
|
||||
const rows = executeSqliteQuerySync(
|
||||
|
|
@ -148,7 +148,7 @@ function selectAcpResumeSessions(
|
|||
}
|
||||
|
||||
export function readAcpSessionCommand(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
command: AcpSessionReadCommand,
|
||||
firstCohort?: readonly AcpSessionRow[],
|
||||
): AcpSessionReadResult {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { expectDefined } from "@openclaw/normalization-core/expect";
|
||||
import type { SessionAcpMeta } from "../../config/sessions/types.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { SessionRowFacts } from "../../sessions/session-row-changes.js";
|
||||
import {
|
||||
buildAcpDatabaseSessionKey,
|
||||
|
|
@ -52,7 +52,7 @@ export function bindAcpSessionMeta(params: {
|
|||
|
||||
/** Shared SQL policy for worker writes and the retained native owner. */
|
||||
export function applyAcpSessionMutation(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: Omit<AcpSessionMutationCommit, "agentId" | "source" | "updatedAt"> & { agentId?: string },
|
||||
): Extract<SessionRowFacts, { kind: "acp" }> {
|
||||
const initialKey = buildAcpDatabaseSessionKey(input.storageSessionKey, input.agentId);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { patchSessionEntryWithKey } from "../../config/sessions/session-accessor.js";
|
||||
import { readLegacyAcpMigrationContext } from "../../config/sessions/session-accessor.sqlite-acp-provenance.js";
|
||||
import {
|
||||
|
|
@ -11,6 +10,7 @@ import {
|
|||
legacyAcpMigrationBindingMatches,
|
||||
recordLegacyAcpMigrationCompletion,
|
||||
} from "../../infra/legacy-acp-migration-source.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { sessionChanges } from "../../sessions/session-row-changes.js";
|
||||
import { runOpenClawStateWriteTransaction } from "../../state/openclaw-state-db.js";
|
||||
import type { AcpSessionControlBinding } from "./session-meta-control.types.js";
|
||||
|
|
@ -25,7 +25,7 @@ import type {
|
|||
} from "./session-meta-write.types.js";
|
||||
|
||||
function consumeLegacyAcpMigrationSources(params: {
|
||||
database: DatabaseSync;
|
||||
database: EngineConnection;
|
||||
agentId?: string;
|
||||
storePath: string;
|
||||
sessionKey: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { resolveSessionStoreCompatibilityAgentId } from "../config/legacy.default-agent-owner.js";
|
||||
import { resolveSessionStorePathCore } from "../config/sessions/paths.js";
|
||||
import { resolveSqliteTargetFromSessionStorePath } from "../config/sessions/session-sqlite-target.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { inspectOpenClawAgentDatabaseOwner } from "../state/openclaw-agent-db-lifecycle.js";
|
||||
import { readRegisteredAgentDatabaseRows } from "../state/openclaw-agent-db-registry.read.js";
|
||||
|
|
@ -16,7 +16,7 @@ import { listAgentIds } from "./agent-scope-config.js";
|
|||
|
||||
/** The caller owns the current shared-state read or write transaction. */
|
||||
export function findAgentSessionStoreDeletionBlocker(
|
||||
database: { db: DatabaseSync; path: string } | undefined,
|
||||
database: { db: EngineConnection; path: string } | undefined,
|
||||
cfg: OpenClawConfig,
|
||||
agentId: string,
|
||||
env?: NodeJS.ProcessEnv,
|
||||
|
|
@ -61,7 +61,7 @@ export function findAgentSessionStoreDeletionBlocker(
|
|||
export const agentDeletionSessionStoreReadOperations = {
|
||||
"agentDeletion.sessionStoreBlocker": (
|
||||
input: AgentDeletionSessionStoreReadOperations["agentDeletion.sessionStoreBlocker"]["input"],
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
): AgentDeletionSessionStoreReadOperations["agentDeletion.sessionStoreBlocker"]["output"] => ({
|
||||
type: "agentDeletion.sessionStoreBlocker",
|
||||
blocker: findAgentSessionStoreDeletionBlocker(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type { PersistedClawInstall } from "../claws/provenance-types.js";
|
||||
import { captureActiveCronJobAgentDeletion } from "../cron/active-jobs.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import {
|
||||
observeSqliteWorkerCommittedFacts,
|
||||
type SqliteWorkerAdmissionRequest,
|
||||
|
|
@ -528,7 +528,7 @@ function runAgentDeletionWithLease<T>(
|
|||
export function isAgentDeletionBlocked(
|
||||
agentId: string,
|
||||
options: OpenClawStateDatabaseOptions = {},
|
||||
database?: DatabaseSync,
|
||||
database?: EngineConnection,
|
||||
): boolean {
|
||||
if (database) {
|
||||
return Boolean(readAgentDeletionJournalInDatabase({ db: database }, agentId, "runtime"));
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import {
|
|||
} from "../../state/user-model-account-authority.js";
|
||||
import type { WorkerWriteOperationContext } from "../../state/worker-operation-registry.js";
|
||||
import { withEnv } from "../../test-utils/env.js";
|
||||
import { requireNativeSqliteDatabase } from "../../test-utils/native-sqlite.js";
|
||||
import { createExternalAuthRuntime } from "./external-auth.js";
|
||||
import { noteCommittedSharedAuthStoreOwnership } from "./path-resolve.js";
|
||||
import { coercePersistedAuthProfileStore } from "./persisted-store.js";
|
||||
|
|
@ -90,7 +91,7 @@ function fixture(
|
|||
agentDir,
|
||||
(owner) => {
|
||||
writeAuthProfileJsonCell(owner.db, "store", kind, store("original"));
|
||||
return owner;
|
||||
return { ...owner, db: requireNativeSqliteDatabase(owner.db) };
|
||||
},
|
||||
{ env },
|
||||
);
|
||||
|
|
@ -319,7 +320,7 @@ describe("credential authority captured before local database creation", () => {
|
|||
return owner;
|
||||
});
|
||||
expect(current.authority.readCurrentCredential()).toBeUndefined();
|
||||
const commit = observePhysicalCommit(database.db, () => {
|
||||
const commit = observePhysicalCommit(requireNativeSqliteDatabase(database.db), () => {
|
||||
expect(() => current.authority.readCurrentCredential()).toThrow("authority is unavailable");
|
||||
});
|
||||
let observed: AuthProfileCredential | undefined;
|
||||
|
|
@ -376,7 +377,7 @@ describe("credential authority captured before local database creation", () => {
|
|||
it("keeps an unknown native outcome denied on the captured absent source", () => {
|
||||
const current = absentAgentFixture();
|
||||
const database = current.transact((owner) => owner);
|
||||
const restore = failAfterNativeWrite(database.db, () => {
|
||||
const restore = failAfterNativeWrite(requireNativeSqliteDatabase(database.db), () => {
|
||||
expect(() => current.authority.readCurrentCredential()).toThrow();
|
||||
});
|
||||
try {
|
||||
|
|
@ -911,7 +912,8 @@ it.each(["commit", "rollback"])(
|
|||
(outcome) => {
|
||||
const current = fixture("shared-state");
|
||||
const database = openOpenClawStateDatabase({ env: current.env });
|
||||
const connection = createSqlPublicationTestConnection(database.db);
|
||||
const db = requireNativeSqliteDatabase(database.db);
|
||||
const connection = createSqlPublicationTestConnection(db);
|
||||
const next = store("worker-anchored");
|
||||
const writeInDatabase = machineStateWrites.writeConfigMachineStateInDatabase;
|
||||
// Exercise the real writer on SQLite while the worker receives a distinct engine handle.
|
||||
|
|
@ -919,7 +921,7 @@ it.each(["commit", "rollback"])(
|
|||
.spyOn(machineStateWrites, "writeConfigMachineStateInDatabase")
|
||||
.mockImplementation((_db, key, value) => writeInDatabase(database.db, key, value));
|
||||
const write: WorkerWriteOperationContext["write"] = (operation) =>
|
||||
runSqliteImmediateTransactionSync(database.db, () => {
|
||||
runSqliteImmediateTransactionSync(db, () => {
|
||||
const result = operation({ ...database, db: connection });
|
||||
if (outcome === "rollback") {
|
||||
throw new Error("synthetic rollback");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isMainThread } from "node:worker_threads";
|
||||
import {
|
||||
acquireStateDatabaseSchemaLease,
|
||||
|
|
@ -6,7 +5,7 @@ import {
|
|||
getStateDatabaseSchemaLease,
|
||||
type StateDatabaseSchemaLease,
|
||||
} from "../../infra/gateway-state-owner.js";
|
||||
import { sqlPublicationAnchor } from "../../infra/sql-connection.js";
|
||||
import { sqlPublicationAnchor, type EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { getSqliteDatabaseAdmissionIdentityForPath } from "../../infra/sqlite-database-admission.js";
|
||||
import { runWithSqliteCleanup } from "../../infra/sqlite-lifecycle-errors.js";
|
||||
import {
|
||||
|
|
@ -24,14 +23,14 @@ import { getWorkerAuthProfileWrites } from "./runtime-scope.js";
|
|||
|
||||
const retainedTransactions = resolveGlobalSingleton(
|
||||
Symbol.for("openclaw.authProfileCredentialWriteCustody"),
|
||||
() => new WeakMap<DatabaseSync, { lease: StateDatabaseSchemaLease; unknown: boolean }>(),
|
||||
() => new WeakMap<EngineConnection, { lease: StateDatabaseSchemaLease; unknown: boolean }>(),
|
||||
);
|
||||
|
||||
/** Reuse native custody; standalone SDK callers take the existing exclusive offline lease. */
|
||||
export function withAuthProfileCredentialOwner<T>(
|
||||
databasePath: string,
|
||||
operation: (retainTransaction?: (database: DatabaseSync) => void) => T,
|
||||
database?: DatabaseSync,
|
||||
operation: (retainTransaction?: (database: EngineConnection) => void) => T,
|
||||
database?: EngineConnection,
|
||||
): T {
|
||||
const retained = database && retainedTransactions.get(sqlPublicationAnchor(database));
|
||||
if (retained) {
|
||||
|
|
@ -74,7 +73,7 @@ export function withAuthProfileCredentialOwner<T>(
|
|||
}
|
||||
const lease = acquireStateDatabaseSchemaLease(databasePath);
|
||||
let transactionOwnsLease = false;
|
||||
const retainTransaction = (db: DatabaseSync) => {
|
||||
const retainTransaction = (db: EngineConnection) => {
|
||||
if (!hasSqlitePostCommitScope(sqlPublicationAnchor(db))) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -110,7 +109,7 @@ export function withAuthProfileCredentialOwner<T>(
|
|||
|
||||
/** The native opener captures the shared root, including agents outside the state directory. */
|
||||
export function withAuthProfileCredentialWriteCustody<T>(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
operation: () => T,
|
||||
): T {
|
||||
if (database.isTransaction && !hasSqlitePostCommitScope(sqlPublicationAnchor(database))) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { OpenClawStateWorkerErrorPayload } from "../../state/openclaw-state-worker-error.js";
|
||||
import { AUTH_STORE_VERSION } from "./constants.js";
|
||||
import { mergePersistedAuthProfileState } from "./persisted.js";
|
||||
|
|
@ -48,7 +48,7 @@ export type InlineAuthFailureOperations = AuthStoreUpdateOperations & {
|
|||
|
||||
/** The admitted agent transaction owns the fresh read, health reduction, and durable cells. */
|
||||
export function recordInlineAuthFailureInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
databasePath: string,
|
||||
input: InlineAuthFailureInput,
|
||||
): InlineAuthFailureReceipt {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
getNodeSqliteKysely,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import { openNodeSqliteDatabase } from "../../infra/node-sqlite.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { prepareSqliteReadOnlyLocationSync } from "../../infra/sqlite-snapshot-source.js";
|
||||
import { registerListener } from "../../shared/listeners.js";
|
||||
import { writeConfigMachineState } from "../../state/config-machine-state-write.js";
|
||||
|
|
@ -213,7 +214,7 @@ export function hasPendingSharedAuthCleanup(
|
|||
}
|
||||
|
||||
export function hasPendingSharedAuthCleanupInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
sourcePath: string,
|
||||
): boolean {
|
||||
const db = getNodeSqliteKysely<SharedAuthMigrationDatabase>(database);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { withAuthProfileCredentialWrite } from "../../state/user-model-account-authority.js";
|
||||
import {
|
||||
AGENT_AUTH_CELLS,
|
||||
|
|
@ -12,7 +12,7 @@ import { recordAuthProfileNativeCommit } from "./store-update-commit.js";
|
|||
|
||||
/** Write one canonical auth cell on the caller's admitted transaction connection. */
|
||||
export function writeAuthProfileJsonCell(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
target: "store" | "state",
|
||||
kind: "agent" | "shared-state",
|
||||
payload: unknown,
|
||||
|
|
@ -54,7 +54,7 @@ export function writeAuthProfileJsonCell(
|
|||
}
|
||||
|
||||
export function deleteAuthProfileJsonCell(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
target: "store" | "state",
|
||||
kind: "agent" | "shared-state",
|
||||
): void {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeWithCachedStatement } from "../../infra/kysely-sync-cache-state.js";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { isPostgresSqlConnection } from "../../infra/sql-connection.js";
|
||||
import { prepareSqliteReadCache } from "../../infra/sqlite-read-cache.js";
|
||||
import type { SqliteAuthSchema } from "../../infra/sqlite-readonly-auth-schema.js";
|
||||
import {
|
||||
|
|
@ -63,7 +64,7 @@ function authProfileSchemaObjectType(schema: SqliteSchemaFacts, name: string): s
|
|||
}
|
||||
|
||||
function inspectAuthProfileTable(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
target: "store" | "state",
|
||||
databaseKind: "agent" | "shared-state",
|
||||
): PersistedAuthProfileStoreInspection | null {
|
||||
|
|
@ -83,7 +84,7 @@ function inspectAuthProfileTable(
|
|||
|
||||
/** Read admitted auth cells without discarding malformed JSON needed for migration backups. */
|
||||
export function readAuthProfileJsonCellText(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
target: "store" | "state",
|
||||
databaseKind: "agent" | "shared-state",
|
||||
): string | undefined {
|
||||
|
|
@ -107,7 +108,7 @@ export function readAuthProfileJsonCellText(
|
|||
}
|
||||
|
||||
export function inspectAuthProfileJsonCell(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
target: "store" | "state",
|
||||
databaseKind: "agent" | "shared-state",
|
||||
): PersistedAuthProfileStoreInspection {
|
||||
|
|
@ -119,7 +120,7 @@ export function inspectAuthProfileJsonCell(
|
|||
}
|
||||
|
||||
function inspectAuthProfileJsonCellContents(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
target: "store" | "state",
|
||||
databaseKind: "agent" | "shared-state",
|
||||
): PersistedAuthProfileStoreInspection {
|
||||
|
|
@ -198,12 +199,14 @@ export function readAuthProfileRowsReadOnly(
|
|||
|
||||
/** Worker reads acquire both cells in one SQLite snapshot. */
|
||||
export function readAuthProfileRows(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
databasePath: string,
|
||||
databaseKind: "agent" | "shared-state",
|
||||
authSchema?: SqliteAuthSchema,
|
||||
): AuthProfileRowRead {
|
||||
const canCache = prepareSqliteReadCache(database, databasePath);
|
||||
const canCache = isPostgresSqlConnection(database)
|
||||
? () => false
|
||||
: prepareSqliteReadCache(database, databasePath);
|
||||
const storeTable = authProfileTableName("store", databaseKind);
|
||||
const stateTable = authProfileTableName("state", databaseKind);
|
||||
let schemaObjects: Array<Record<string, unknown>>;
|
||||
|
|
@ -314,7 +317,7 @@ export function readAuthProfileRows(
|
|||
}
|
||||
|
||||
function inspectAuthProfileRow(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
target: "store" | "state",
|
||||
databaseKind: "agent" | "shared-state",
|
||||
): PersistedAuthProfileStoreInspection {
|
||||
|
|
@ -331,7 +334,7 @@ function inspectAuthProfileRow(
|
|||
}
|
||||
|
||||
/** The selected worker returns source presence without transferring credential rows. */
|
||||
export function hasAgentAuthProfileSourceInDatabase(database: DatabaseSync): boolean {
|
||||
export function hasAgentAuthProfileSourceInDatabase(database: EngineConnection): boolean {
|
||||
const store = inspectAuthProfileRow(database, "store", "agent");
|
||||
if (store.status !== "missing") {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { resolveIdentityPathViaExistingAncestorSync } from "../../infra/boundary-path.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { isPostgresSqlConnection, sqlPublicationAnchor } from "../../infra/sql-connection.js";
|
||||
import { stageSqliteTransactionState } from "../../infra/sqlite-post-commit.js";
|
||||
import { createDeferredCore } from "../../shared/deferred.js";
|
||||
import { resolveGlobalSingleton } from "../../shared/global-singleton.js";
|
||||
import { readTrackedStateDatabaseIdentity } from "../../state/openclaw-state-db-handle.js";
|
||||
|
||||
type PendingPublication = {
|
||||
revision: number;
|
||||
|
|
@ -53,11 +55,13 @@ export async function waitForAuthProfileNativePublications(
|
|||
}
|
||||
|
||||
/** Native COMMIT can overtake a worker result before its host publication runs. */
|
||||
export function recordAuthProfileNativeCommit(database: DatabaseSync): void {
|
||||
export function recordAuthProfileNativeCommit(database: EngineConnection): void {
|
||||
if (pending.size === 0) {
|
||||
return;
|
||||
}
|
||||
const location = database.location();
|
||||
const location = isPostgresSqlConnection(database)
|
||||
? readTrackedStateDatabaseIdentity(sqlPublicationAnchor(database))?.canonicalPath
|
||||
: database.location();
|
||||
if (!location) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { MessageChannel } from "node:worker_threads";
|
||||
import { sqlPublicationAnchor } from "../../infra/sql-connection.js";
|
||||
import { sqlPublicationAnchor, type EngineConnection } from "../../infra/sql-connection.js";
|
||||
import {
|
||||
deferSqliteWorkerCommitReceipt,
|
||||
requestSqliteWorkerOperationAdmission,
|
||||
|
|
@ -73,7 +72,7 @@ export type AuthStoreUpdateResponse =
|
|||
|
||||
/** A definite mismatch needs no write lock; possible matches reread inside the transaction. */
|
||||
export function authProfilePeerGenerationMayMatch(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
input: AuthStoreUpdateInput,
|
||||
): boolean {
|
||||
const peer = input.peerGeneration;
|
||||
|
|
@ -107,7 +106,7 @@ export function authProfilePeerGenerationMayMatch(
|
|||
|
||||
/** Run under the owning transaction: callbacks see its current rows exactly once. */
|
||||
export function updateAuthProfileStoreInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
kind: "agent" | "shared-state",
|
||||
input: AuthStoreUpdateInput,
|
||||
): void {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { AUTH_STORE_VERSION } from "./constants.js";
|
||||
import { mergeAuthProfileStores, mergePersistedAuthProfileState } from "./persisted.js";
|
||||
import { pruneAuthProfileStoreReferences } from "./runtime-snapshot-owner.js";
|
||||
|
|
@ -18,7 +18,7 @@ import { resetAuthProfileFailureState } from "./usage-state.js";
|
|||
|
||||
/** Reduce the authoritative health row without rewriting credential bytes. */
|
||||
export function recordAuthProfileUsageInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
databasePath: string,
|
||||
databaseKind: "agent" | "shared-state",
|
||||
input: AuthProfileUsageInput,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import {
|
|||
} from "../../state/user-channel-identities.js";
|
||||
import { setUserProfileRole } from "../../state/user-profile-writes.worker.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { requireNativeSqliteDatabase } from "../../test-utils/native-sqlite.js";
|
||||
import { persistPendingFinalDeliveryMarker } from "../pending-final-delivery-marker.js";
|
||||
import { createAgentRunRestartAbortError } from "../run-termination.js";
|
||||
import { deliverAgentCommandResult } from "./delivery.js";
|
||||
|
|
@ -414,7 +415,7 @@ it.each(
|
|||
expect(assertOwnerCurrent).toThrow("Channel operator authority changed");
|
||||
}
|
||||
if (boundary.endsWith("reference")) {
|
||||
const db = openOpenClawStateDatabase().db;
|
||||
const db = requireNativeSqliteDatabase(openOpenClawStateDatabase().db);
|
||||
const rows = db
|
||||
.prepare(
|
||||
"SELECT id, json_remove(entry_json, '$.deliveryCompletion.commandOwnerReference') AS entry_json FROM delivery_queue_entries WHERE queue_name = ?",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { sql } from "kysely";
|
||||
import {
|
||||
readClosedTranscriptTurnInDatabase,
|
||||
type ClosedTranscriptTurnReadResult,
|
||||
} from "../../config/sessions/session-accessor.transcript-range.worker.js";
|
||||
import type { TranscriptTurnBoundary } from "../../config/sessions/transcript-entry-anchor.js";
|
||||
import { requireNativeDatabaseFile } from "../../infra/database-engine/physical-facts.js";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { isPostgresSqlConnection } from "../../infra/sql-connection.js";
|
||||
import type { SqliteWorkerCommand } from "../../infra/sqlite-worker-contract.js";
|
||||
import { ensureContextEngineTurnOutboxSchema } from "../../state/openclaw-agent-context-engine-turn-outbox-schema.js";
|
||||
import type { OpenClawAgentDatabase } from "../../state/openclaw-agent-db-contract.js";
|
||||
|
|
@ -439,9 +441,12 @@ type OutboxOutput =
|
|||
|
||||
/** Runs one outbox command's kernel on the borrowed connection. */
|
||||
export function executeContextEngineTurnOutboxCommand(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
command: OutboxCommand,
|
||||
): OutboxOutput {
|
||||
if (isPostgresSqlConnection(db)) {
|
||||
requireNativeDatabaseFile("postgres", "repair");
|
||||
}
|
||||
const database = { db };
|
||||
switch (command.type) {
|
||||
case "prepareRun":
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQueryTakeFirstSync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
|
||||
type NativeHookRelayBridgeDatabase = Pick<DB, "native_hook_relay_bridges">;
|
||||
|
||||
export function readNativeHookRelayBridgeRow(database: DatabaseSync, relayId: string) {
|
||||
export function readNativeHookRelayBridgeRow(database: EngineConnection, relayId: string) {
|
||||
const db = getNodeSqliteKysely<NativeHookRelayBridgeDatabase>(database);
|
||||
return executeSqliteQueryTakeFirstSync(
|
||||
database,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../../state/openclaw-state-db.generated.js";
|
||||
import { readNativeHookRelayBridgeRow } from "./native-hook-relay-bridge-query.js";
|
||||
import {
|
||||
|
|
@ -23,7 +23,7 @@ export type NativeHookRelayBridgePruneCandidate = {
|
|||
};
|
||||
|
||||
export function readNativeHookRelayBridgeRecordFromDatabase(params: {
|
||||
database: { db: DatabaseSync };
|
||||
database: { db: EngineConnection };
|
||||
relayId: string;
|
||||
}): NativeHookRelayBridgeRecord | undefined {
|
||||
return readNativeHookRelayBridgeRecordRow(
|
||||
|
|
@ -50,7 +50,7 @@ function nativeHookRelayBridgeRow({
|
|||
}
|
||||
|
||||
export function writeNativeHookRelayBridgeRecordInDatabase(
|
||||
database: { db: DatabaseSync },
|
||||
database: { db: EngineConnection },
|
||||
params: { record: NativeHookRelayBridgeRecord; updatedAtMs: number },
|
||||
): void {
|
||||
const { relay_id, ...fields } = nativeHookRelayBridgeRow(params);
|
||||
|
|
@ -65,7 +65,7 @@ export function writeNativeHookRelayBridgeRecordInDatabase(
|
|||
}
|
||||
|
||||
export function renewOrRestoreNativeHookRelayBridgeRecordInDatabase(
|
||||
database: { db: DatabaseSync },
|
||||
database: { db: EngineConnection },
|
||||
params: { record: NativeHookRelayBridgeRecord; updatedAtMs: number },
|
||||
): boolean {
|
||||
const { relay_id, ...fields } = nativeHookRelayBridgeRow(params);
|
||||
|
|
@ -87,7 +87,7 @@ export function renewOrRestoreNativeHookRelayBridgeRecordInDatabase(
|
|||
}
|
||||
|
||||
export function deleteNativeHookRelayBridgeRecordIfOwnedInDatabase(
|
||||
database: { db: DatabaseSync },
|
||||
database: { db: EngineConnection },
|
||||
params: { relayId: string; pid: number; token: string },
|
||||
): boolean {
|
||||
const db = getNodeSqliteKysely<NativeHookRelayBridgeDatabase>(database.db);
|
||||
|
|
@ -103,7 +103,7 @@ export function deleteNativeHookRelayBridgeRecordIfOwnedInDatabase(
|
|||
}
|
||||
|
||||
export function listNativeHookRelayBridgeRecordsInDatabase(database: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
}): NativeHookRelayBridgeRecord[] {
|
||||
const db = getNodeSqliteKysely<NativeHookRelayBridgeDatabase>(database.db);
|
||||
return executeSqliteQuerySync(
|
||||
|
|
@ -116,7 +116,7 @@ export function listNativeHookRelayBridgeRecordsInDatabase(database: {
|
|||
}
|
||||
|
||||
export function pruneNativeHookRelayBridgeRecordsInDatabase(
|
||||
database: { db: DatabaseSync },
|
||||
database: { db: EngineConnection },
|
||||
candidates: NativeHookRelayBridgePruneCandidate[],
|
||||
nowMs: number,
|
||||
): NativeHookRelayBridgePruneResult[] {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { toUSVString } from "node:util";
|
||||
import {
|
||||
OAuthClientInformationSchema,
|
||||
|
|
@ -15,6 +14,7 @@ import {
|
|||
iterateSqliteQuerySync,
|
||||
sqliteStringSet,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
import {
|
||||
|
|
@ -200,7 +200,7 @@ function storeFromRow(
|
|||
}
|
||||
|
||||
export function readMcpOAuthStoreInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
storeKey: string,
|
||||
): McpOAuthStore {
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
|
|
@ -224,7 +224,7 @@ export type McpOAuthReadOnlyOperations = {
|
|||
};
|
||||
|
||||
export function readMcpOAuthStoreIfPresentInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
storeKey: string,
|
||||
): McpOAuthStore {
|
||||
return tableExists(database, "mcp_oauth_stores")
|
||||
|
|
@ -233,7 +233,7 @@ export function readMcpOAuthStoreIfPresentInDatabase(
|
|||
}
|
||||
|
||||
export function readMcpOAuthStatusesInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
storeKeys: readonly string[],
|
||||
): McpOAuthPrincipalStatus[] {
|
||||
if (!tableExists(database, "mcp_oauth_stores")) {
|
||||
|
|
@ -253,7 +253,10 @@ export function readMcpOAuthStatusesInDatabase(
|
|||
);
|
||||
}
|
||||
|
||||
export function listMcpOAuthStoreKeysInDatabase(database: DatabaseSync, prefix: string): string[] {
|
||||
export function listMcpOAuthStoreKeysInDatabase(
|
||||
database: EngineConnection,
|
||||
prefix: string,
|
||||
): string[] {
|
||||
if (!tableExists(database, "mcp_oauth_stores")) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -268,7 +271,10 @@ export function listMcpOAuthStoreKeysInDatabase(database: DatabaseSync, prefix:
|
|||
return rows.map((row) => row.store_key);
|
||||
}
|
||||
|
||||
export function countMcpOAuthPrincipalsInDatabase(database: DatabaseSync, prefix: string): number {
|
||||
export function countMcpOAuthPrincipalsInDatabase(
|
||||
database: EngineConnection,
|
||||
prefix: string,
|
||||
): number {
|
||||
if (!tableExists(database, "mcp_oauth_stores")) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -290,7 +296,7 @@ export function countMcpOAuthPrincipalsInDatabase(database: DatabaseSync, prefix
|
|||
}
|
||||
|
||||
export function readMcpOAuthPendingInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
state: string,
|
||||
): string | undefined {
|
||||
if (!tableExists(database, "mcp_oauth_pending_authorizations")) {
|
||||
|
|
@ -307,10 +313,10 @@ export function readMcpOAuthPendingInDatabase(
|
|||
}
|
||||
|
||||
export function replaceMcpOAuthStoreInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
storeKey: string,
|
||||
next: McpOAuthStore,
|
||||
assertOwnedInTransaction?: (database: DatabaseSync) => void,
|
||||
assertOwnedInTransaction?: (database: EngineConnection) => void,
|
||||
): McpOAuthStore {
|
||||
const storeJson = JSON.stringify(next);
|
||||
parseMcpOAuthStoreJson(storeKey, storeJson);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { deferSqlitePostCommitPublication } from "../infra/sqlite-post-commit.js";
|
||||
import { requestSqliteWorkerOperationAdmission } from "../infra/sqlite-worker-operation-admission.js";
|
||||
import { getSqliteWorkerStateContext } from "../infra/sqlite-worker-state-context.js";
|
||||
|
|
@ -24,7 +24,7 @@ import type { McpOAuthMutation } from "./mcp-oauth-store.types.js";
|
|||
type McpOAuthOwnedStore = { storeKey: string; identity: OpenClawStateLeaseIdentity };
|
||||
|
||||
function assertStoreLease(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
input: McpOAuthOwnedStore,
|
||||
stage: "transaction" | "commit" = "transaction",
|
||||
): void {
|
||||
|
|
@ -34,9 +34,9 @@ function assertStoreLease(
|
|||
assertOpenClawStateLeaseWorkerOwnedInTransaction(database, input.identity, "write", stage);
|
||||
}
|
||||
|
||||
const pendingSchemaDatabases = new WeakSet<DatabaseSync>();
|
||||
const pendingSchemaDatabases = new WeakSet<EngineConnection>();
|
||||
|
||||
function ensurePendingSchema(database: DatabaseSync): void {
|
||||
function ensurePendingSchema(database: EngineConnection): void {
|
||||
if (pendingSchemaDatabases.has(database)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ function ensurePendingSchema(database: DatabaseSync): void {
|
|||
}
|
||||
|
||||
function ownedWrite<Input extends McpOAuthOwnedStore, Result>(
|
||||
operation: (database: DatabaseSync, input: Input, assertOwned: () => void) => Result,
|
||||
operation: (database: EngineConnection, input: Input, assertOwned: () => void) => Result,
|
||||
pending = false,
|
||||
) {
|
||||
return (input: Input, { open }: WorkerOperationContext): Result => {
|
||||
|
|
@ -68,7 +68,11 @@ function ownedWrite<Input extends McpOAuthOwnedStore, Result>(
|
|||
};
|
||||
}
|
||||
|
||||
function deletePending(database: DatabaseSync, storeKey: string, assertOwned: () => void): void {
|
||||
function deletePending(
|
||||
database: EngineConnection,
|
||||
storeKey: string,
|
||||
assertOwned: () => void,
|
||||
): void {
|
||||
assertOwned();
|
||||
executeSqliteQuerySync(
|
||||
database,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { normalizeAgentId } from "@openclaw/normalization-core/agent-id";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
getNodeSqliteKysely,
|
||||
sqliteStringSet,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { OpenClawAgentDatabaseReadOnlyScope } from "../state/openclaw-agent-db-readonly-scope.js";
|
||||
import { withOpenClawAgentDatabaseReadOnly } from "../state/openclaw-agent-db-readonly.js";
|
||||
import type { DB as OpenClawAgentKyselyDatabase } from "../state/openclaw-agent-db.generated.js";
|
||||
|
|
@ -66,7 +66,7 @@ export const pluginModelCatalogReadOperations = createPluginModelCatalogReadOper
|
|||
|
||||
/** The admitted worker or Doctor transaction owns the connection and commit. */
|
||||
export function replacePluginModelCatalogEntriesInDatabase(params: {
|
||||
database: DatabaseSync;
|
||||
database: EngineConnection;
|
||||
planned: ReadonlyMap<string, string>;
|
||||
migrationPayloads?: ReadonlyMap<string, string>;
|
||||
deleteMissing?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import {
|
||||
assertTransactionUsable,
|
||||
runSqliteWorkerTransactionSync,
|
||||
|
|
@ -45,7 +45,7 @@ export type PluginModelCatalogCredentialOperations = {
|
|||
|
||||
/** Apply removal-time endpoint facts to the current rows inside the writer transaction. */
|
||||
function pruneRemovedProviderCatalogEntriesInDatabase(params: {
|
||||
database: DatabaseSync;
|
||||
database: EngineConnection;
|
||||
removedProviderBaseUrls: Readonly<Record<string, string>>;
|
||||
updatedAt: number;
|
||||
}): boolean {
|
||||
|
|
@ -109,14 +109,14 @@ function pruneRemovedProviderCatalogEntriesInDatabase(params: {
|
|||
/** Reread authorization inside the admitted publication, including outside discovery snapshots. */
|
||||
function findRemovedPluginModelCatalogCredentials(
|
||||
snapshot: PluginModelCatalogAuthSnapshot,
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
databasePath: string,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): ReadonlySet<string> {
|
||||
const captured = new Set<string>();
|
||||
const authorized = new Set<string>();
|
||||
for (const owner of snapshot) {
|
||||
const read = (db: DatabaseSync) =>
|
||||
const read = (db: EngineConnection) =>
|
||||
loadPersistedAuthProfileStoreFromRows(
|
||||
readAuthProfileRows(db, owner.databasePath, owner.kind),
|
||||
owner.databasePath,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { serialize } from "node:v8";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type { Selectable } from "kysely";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { sqlPublicationAnchor } from "../../infra/sql-connection.js";
|
||||
import {
|
||||
createSqliteCommitReceipt,
|
||||
hasSqliteCommitReceiptCoverage,
|
||||
|
|
@ -37,17 +38,19 @@ type FactChange =
|
|||
| { kind: "settled"; identity: string; operation: object; outcome: "published" | "unknown" };
|
||||
|
||||
const state = resolveGlobalSingleton(Symbol.for("openclaw.sandboxRegistryPublication"), () => ({
|
||||
sources: new WeakMap<DatabaseSync, SqliteCommitSource>(),
|
||||
sources: new WeakMap<EngineConnection, SqliteCommitSource>(),
|
||||
capture: new AsyncLocalStorage<Change[]>(),
|
||||
installingFacts: 0,
|
||||
facts: new Set<(change: FactChange) => void>(),
|
||||
}));
|
||||
|
||||
function receiptFor(database: DatabaseSync, changes: readonly Change[]): Receipt {
|
||||
function receiptFor(database: EngineConnection, changes: readonly Change[]): Receipt {
|
||||
let source = state.sources.get(database);
|
||||
if (!source) {
|
||||
source = {
|
||||
identity: readTrackedStateDatabaseIdentity(database)?.key ?? Symbol("sandbox-registry"),
|
||||
identity:
|
||||
readTrackedStateDatabaseIdentity(sqlPublicationAnchor(database))?.key ??
|
||||
Symbol("sandbox-registry"),
|
||||
incarnation: randomUUID(),
|
||||
};
|
||||
state.sources.set(database, source);
|
||||
|
|
@ -94,7 +97,7 @@ export const sandboxRegistryPublication = {
|
|||
subscribeFacts(listener: (change: FactChange) => void) {
|
||||
return registerListener(state.facts, listener);
|
||||
},
|
||||
stage(database: DatabaseSync, rows: readonly RegistryRow[], removed = false) {
|
||||
stage(database: EngineConnection, rows: readonly RegistryRow[], removed = false) {
|
||||
if (state.installingFacts) {
|
||||
throw new Error("Sandbox registry cannot mutate during fact installation");
|
||||
}
|
||||
|
|
@ -126,7 +129,7 @@ export const sandboxRegistryPublication = {
|
|||
};
|
||||
|
||||
/** Capture the same postimages for native installation and transport, including no-op settlement. */
|
||||
export function withSandboxRegistryWorkerReceipt<T>(database: DatabaseSync, write: () => T): T {
|
||||
export function withSandboxRegistryWorkerReceipt<T>(database: EngineConnection, write: () => T): T {
|
||||
return state.capture.run([], () => {
|
||||
const result = write();
|
||||
const receipt = receiptFor(database, state.capture.getStore()!);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { asDateTimestampMs } from "@openclaw/normalization-core/number-coercion";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type { Insertable, Selectable, Updateable } from "kysely";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
import { sandboxRegistryPublication } from "./registry-publication.js";
|
||||
import type { SandboxBrowserRegistryEntry, SandboxRegistryEntry } from "./registry.types.js";
|
||||
|
|
@ -46,7 +46,7 @@ function rowToUpdate(row: SandboxRegistryInsert): Updateable<DB["sandbox_registr
|
|||
}
|
||||
|
||||
export function reserveSandboxRegistryInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
candidate: SandboxRegistryEntry,
|
||||
): SandboxRegistryEntry {
|
||||
const rows = readRegistryRows(db, "container", {
|
||||
|
|
@ -85,7 +85,7 @@ export function shouldPruneSandboxRegistryEntry(
|
|||
}
|
||||
|
||||
export function beginSandboxRegistryRemovalInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
{
|
||||
entry,
|
||||
reserveRuntime,
|
||||
|
|
@ -116,7 +116,10 @@ export function beginSandboxRegistryRemovalInDatabase(
|
|||
return next;
|
||||
}
|
||||
|
||||
function insertSandboxRegistryRowInDatabase(db: DatabaseSync, row: SandboxRegistryInsert): void {
|
||||
function insertSandboxRegistryRowInDatabase(
|
||||
db: EngineConnection,
|
||||
row: SandboxRegistryInsert,
|
||||
): void {
|
||||
const { rows } = executeSqliteQuerySync(
|
||||
db,
|
||||
getNodeSqliteKysely<SandboxRegistryDatabase>(db)
|
||||
|
|
@ -163,7 +166,7 @@ export function assertSandboxRegistryGenerationCurrent(
|
|||
}
|
||||
|
||||
function removeRegistryRowInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
kind: "container" | "browser",
|
||||
containerName: string,
|
||||
): void {
|
||||
|
|
@ -179,7 +182,7 @@ function removeRegistryRowInDatabase(
|
|||
}
|
||||
|
||||
export function writeSandboxRegistryInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
write: SandboxRegistryWrite,
|
||||
): void {
|
||||
if (write.operation === "removeGeneration") {
|
||||
|
|
@ -308,7 +311,7 @@ function registryEntryToRow({
|
|||
}
|
||||
|
||||
export function insertSandboxRegistryRowIfMissingInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
row: SandboxRegistryInsert,
|
||||
): void {
|
||||
const { rows } = executeSqliteQuerySync(
|
||||
|
|
@ -388,7 +391,7 @@ function normalizeSandboxRegistryEntry(entry: SandboxRegistryEntry): SandboxRegi
|
|||
}
|
||||
|
||||
function readRegistryRows(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
kind: "container" | "browser",
|
||||
filter?: { backendId: string; scopeKey: string },
|
||||
): SandboxRegistryRow[] {
|
||||
|
|
@ -410,7 +413,7 @@ function readRegistryRows(
|
|||
}
|
||||
|
||||
export function readSandboxRegistryEntryInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
containerName: string,
|
||||
): SandboxRegistryEntry | null {
|
||||
const row = readSandboxRegistryRowInDatabase(db, "container", containerName);
|
||||
|
|
@ -418,7 +421,7 @@ export function readSandboxRegistryEntryInDatabase(
|
|||
}
|
||||
|
||||
function readSandboxRegistryRowInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
kind: "container" | "browser",
|
||||
containerName: string,
|
||||
): SandboxRegistryRow | null {
|
||||
|
|
@ -435,14 +438,14 @@ function readSandboxRegistryRowInDatabase(
|
|||
);
|
||||
}
|
||||
|
||||
export function readSandboxRegistryInDatabase(db: DatabaseSync): SandboxRegistryEntry[] {
|
||||
export function readSandboxRegistryInDatabase(db: EngineConnection): SandboxRegistryEntry[] {
|
||||
return readRegistryRows(db, "container")
|
||||
.map(rowToContainerEntry)
|
||||
.filter((entry): entry is SandboxRegistryEntry => entry !== null);
|
||||
}
|
||||
|
||||
export function readSandboxRuntimeIdsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
filter: { backendId: string; scopeKey: string },
|
||||
): string[] {
|
||||
return readRegistryRows(db, "container", filter)
|
||||
|
|
@ -452,7 +455,7 @@ export function readSandboxRuntimeIdsInDatabase(
|
|||
}
|
||||
|
||||
export function readSandboxBrowserRegistryInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
): SandboxBrowserRegistryEntry[] {
|
||||
return readRegistryRows(db, "browser")
|
||||
.map(rowToBrowserEntry)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import {
|
||||
bindDeliveryQueueEntry,
|
||||
|
|
@ -14,6 +13,7 @@ import {
|
|||
SESSION_DELIVERY_QUEUE_NAME,
|
||||
type QueuedSessionDelivery,
|
||||
} from "../../../infra/session-delivery-queue.records.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import {
|
||||
deferSqliteWorkerCommitReceipt,
|
||||
requestSqliteWorkerOperationAdmission,
|
||||
|
|
@ -42,7 +42,7 @@ import type {
|
|||
SubagentCompletionRecord,
|
||||
} from "./subagent-completion-mutation.types.js";
|
||||
|
||||
const query = (db: DatabaseSync) => getNodeSqliteKysely<Pick<DB, "subagent_runs">>(db);
|
||||
const query = (db: EngineConnection) => getNodeSqliteKysely<Pick<DB, "subagent_runs">>(db);
|
||||
|
||||
function commitCompletionWrite<T extends object>(
|
||||
input: { writeId: string },
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { callGateway } from "../../../gateway/call.js";
|
|||
import * as workerCpu from "../../../infra/worker-cpu.js";
|
||||
import { openOpenClawStateDatabase } from "../../../state/openclaw-state-db.js";
|
||||
import { captureOpenClawStateWorkerContext } from "../../../state/openclaw-state-worker-context.js";
|
||||
import { requireNativeSqliteDatabase } from "../../../test-utils/native-sqlite.js";
|
||||
import { holdStateDatabaseWriteTransaction } from "../../../test-utils/state-database-contention.js";
|
||||
import { subagentRuns } from "./subagent-registry-memory.js";
|
||||
import { mutateSubagentRuns } from "./subagent-registry-persistence.js";
|
||||
|
|
@ -23,6 +24,7 @@ it("keeps ordinary subagent registration responsive while a SQLite writer is hel
|
|||
await fixture.allocateStateDir();
|
||||
vi.mocked(callGateway).mockResolvedValue({ status: "pending" });
|
||||
const database = openOpenClawStateDatabase();
|
||||
const db = requireNativeSqliteDatabase(database.db);
|
||||
const context = captureOpenClawStateWorkerContext();
|
||||
expect(context.admission.databasePath.startsWith(fixture.stateDir)).toBe(true);
|
||||
const runId = "contended-registration";
|
||||
|
|
@ -58,7 +60,7 @@ it("keeps ordinary subagent registration responsive while a SQLite writer is hel
|
|||
],
|
||||
workerData: {
|
||||
...options?.workerData,
|
||||
testRegistrationPath: database.db.location(),
|
||||
testRegistrationPath: db.location(),
|
||||
testRegistrationBegin: nativeBegin.buffer,
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import * as stateReads from "../../../state/openclaw-state-db-readonly.js";
|
|||
import { withExistingOpenClawStateSchema } from "../../../state/openclaw-state-db-schema-policy.js";
|
||||
import { openOpenClawStateDatabase } from "../../../state/openclaw-state-db.js";
|
||||
import { captureOpenClawStateWorkerContext } from "../../../state/openclaw-state-worker-context.js";
|
||||
import { requireNativeSqliteDatabase } from "../../../test-utils/native-sqlite.js";
|
||||
import {
|
||||
createOpenClawTestState,
|
||||
withOpenClawTestState,
|
||||
|
|
@ -230,7 +231,7 @@ it("omits optional hints only for a settled unavailable read without caching an
|
|||
it("rejects newer schema admission before optional compact hints can be omitted", async () => {
|
||||
saveSubagentRegistryToSqlite(runs("retained"));
|
||||
const { db } = openOpenClawStateDatabase();
|
||||
const version = readSqliteUserVersion(db);
|
||||
const version = readSqliteUserVersion(requireNativeSqliteDatabase(db));
|
||||
db.exec("PRAGMA user_version = 2147483647");
|
||||
try {
|
||||
await expect(prepareOptionalSubagentSessionListReadCache()).rejects.toThrow(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import { parseSqliteTableDefinition } from "../../../infra/sqlite-schema-contract-assembly.js";
|
||||
import {
|
||||
getAdmittedSqliteSchemaFacts,
|
||||
|
|
@ -16,7 +16,7 @@ const MAX_SUBAGENT_UPSERT_ROWS = 128;
|
|||
|
||||
const parentStoreSchemas = new WeakMap<SqliteSchemaFacts, boolean>();
|
||||
|
||||
export function hasParentStoreColumns(db: DatabaseSync): boolean {
|
||||
export function hasParentStoreColumns(db: EngineConnection): boolean {
|
||||
const schema = getAdmittedSqliteSchemaFacts(db);
|
||||
if (!schema) {
|
||||
throw new Error("Subagent rows require admitted schema facts");
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
runOpenClawStateWriteTransaction,
|
||||
} from "../../../state/openclaw-state-db.js";
|
||||
import { withEnvAsync } from "../../../test-utils/env.js";
|
||||
import { requireNativeSqliteDatabase } from "../../../test-utils/native-sqlite.js";
|
||||
import {
|
||||
createSubagentStoreRunFixture as createRun,
|
||||
loadSubagentRegistryFromSqlite,
|
||||
|
|
@ -243,7 +244,8 @@ describe("subagent registry sqlite store", () => {
|
|||
const run = createRun({ model: "original-model" });
|
||||
saveSubagentRegistryToSqlite(new Map([[run.runId, run]]));
|
||||
const database = openOpenClawStateDatabase();
|
||||
const { db, path: databasePath } = database;
|
||||
const { path: databasePath } = database;
|
||||
const db = requireNativeSqliteDatabase(database.db);
|
||||
const read = (keys: readonly string[]) =>
|
||||
loadSubagentRunsForSessionsInDatabase(database, keys, []);
|
||||
const writer = new DatabaseSync(databasePath);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { sql, type ExpressionBuilder, type RawBuilder } from "kysely";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
getNodeSqliteKysely,
|
||||
sqliteStringSet,
|
||||
} from "../../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import { runSqliteDeferredTransactionSync } from "../../../infra/sqlite-transaction.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../../../state/openclaw-state-db.generated.js";
|
||||
import type { OpenClawStateDatabase } from "../../../state/openclaw-state-db.js";
|
||||
|
|
@ -20,7 +20,7 @@ import type { SubagentRunMaintenanceRecord, SubagentRunRecord } from "./subagent
|
|||
import { collectSubagentSessionReadKeys } from "./subagent-session-read-scope.js";
|
||||
|
||||
type SubagentRegistryDatabase = Pick<OpenClawStateKyselyDatabase, "subagent_runs">;
|
||||
function parentStoreColumns(db: DatabaseSync) {
|
||||
function parentStoreColumns(db: EngineConnection) {
|
||||
return hasParentStoreColumns(db)
|
||||
? (["requester_store_path", "controller_store_path"] as const)
|
||||
: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { readAcpSessionControlInWorker } from "../../../acp/runtime/session-meta-source.worker.js";
|
||||
import { requestSessionEntryCurrentAdmission } from "../../../config/sessions/session-entry-current-admission.worker.js";
|
||||
import { getNodeSqliteKysely, iterateSqliteQuerySync } from "../../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import { isSqliteCorruptionError } from "../../../infra/sqlite-error-diagnostics.js";
|
||||
import { throwSqliteLifecycleErrors } from "../../../infra/sqlite-lifecycle-errors.js";
|
||||
import { deferSqlitePostCommitPublication } from "../../../infra/sqlite-post-commit.js";
|
||||
|
|
@ -48,7 +48,7 @@ import type {
|
|||
const log = createSubsystemLogger("state/worker");
|
||||
|
||||
export function readSubagentRunsInWorker(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
command: Extract<OpenClawStateReadCommand, { type: "subagents.runs" }>,
|
||||
): Extract<OpenClawStateReadResult, { type: "subagents.runs" }> {
|
||||
if (command.scope.kind === "maintenance") {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Result } from "@openclaw/normalization-core/result";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import { coerceRequiredSqliteNumber as sqliteNumber } from "../../../infra/sqlite-number.js";
|
||||
import {
|
||||
assertTransactionUsable,
|
||||
|
|
@ -33,7 +33,7 @@ export function bindSqliteWorkerBackend(
|
|||
_input: undefined,
|
||||
context: {
|
||||
databasePath: string;
|
||||
database: DatabaseSync;
|
||||
database: EngineConnection;
|
||||
admit(stage: "transaction" | "commit"): void;
|
||||
},
|
||||
): SqliteWorkerBackend<AcpParentStreamWorkerOperations> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type { Selectable } from "kysely";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import {
|
||||
deferSqliteWorkerCommitReceipt,
|
||||
observeSqliteWorkerCommittedFacts,
|
||||
|
|
@ -63,7 +63,7 @@ export const workspaceStatePublication = createStateDomainPublication<WorkspaceS
|
|||
},
|
||||
});
|
||||
|
||||
export function captureWorkspaceStateReceipt<T>(db: DatabaseSync, run: () => T): T {
|
||||
export function captureWorkspaceStateReceipt<T>(db: EngineConnection, run: () => T): T {
|
||||
const { result, receipt } = workspaceStatePublication.capture(db, run);
|
||||
deferSqliteWorkerCommitReceipt(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { statSync } from "node:fs";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { formatDiskSpaceBytes, tryReadDiskSpace } from "../../infra/disk-space.js";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import {
|
||||
parseStateLeaseProcessOwner,
|
||||
readStateLeaseProcessOwnerStatus,
|
||||
|
|
@ -81,7 +81,7 @@ function measureVolumes(demands: readonly { path: string; bytes: number }[]) {
|
|||
|
||||
/** The writer transaction samples disk and accounts for every retained operation before admitting writes. */
|
||||
function reserveWorktreeCapacityInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: WorktreeCapacityRequest,
|
||||
): WorktreeCapacityResult {
|
||||
const { key, owner, demands, purpose, snapshot } = input;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { safeParseJsonRecord } from "@openclaw/normalization-core/json-coercion";
|
||||
import { Value } from "typebox/value";
|
||||
import { WorktreeRecordSchema } from "../../../packages/gateway-protocol/src/schema/worktrees.js";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import {
|
||||
parseStateLeaseProcessOwner,
|
||||
readStateLeaseProcessOwnerStatus,
|
||||
|
|
@ -15,14 +15,15 @@ import { listRegistryWorktreesInDatabase } from "./registry-read.kernel.js";
|
|||
import type { ManagedWorktreeRecord } from "./types.js";
|
||||
|
||||
const PENDING_SCOPE = "core:managed-worktrees:pending-slots";
|
||||
const query = (db: DatabaseSync) => getNodeSqliteKysely<Pick<DB, "state_leases" | "worktrees">>(db);
|
||||
const query = (db: EngineConnection) =>
|
||||
getNodeSqliteKysely<Pick<DB, "state_leases" | "worktrees">>(db);
|
||||
|
||||
export type PendingWorktreeSlot = {
|
||||
record: ManagedWorktreeRecord;
|
||||
state: "pending" | "recovering";
|
||||
};
|
||||
|
||||
function readSlots(db: DatabaseSync) {
|
||||
function readSlots(db: EngineConnection) {
|
||||
return executeSqliteQuerySync(
|
||||
db,
|
||||
query(db).selectFrom("state_leases").selectAll().where("scope", "=", PENDING_SCOPE),
|
||||
|
|
@ -58,7 +59,7 @@ function isPendingRecord(value: unknown): value is ManagedWorktreeRecord {
|
|||
}
|
||||
|
||||
/** One statement observes publication as either pending or live, never neither or both. */
|
||||
export function readWorktreeSlotCountInDatabase(db: DatabaseSync): number {
|
||||
export function readWorktreeSlotCountInDatabase(db: EngineConnection): number {
|
||||
const k = query(db);
|
||||
const rows = executeSqliteQuerySync(
|
||||
db,
|
||||
|
|
@ -88,7 +89,7 @@ export function readWorktreeSlotCountInDatabase(db: DatabaseSync): number {
|
|||
);
|
||||
}
|
||||
|
||||
export function readPendingWorktreesInDatabase(db: DatabaseSync): PendingWorktreeSlot[] {
|
||||
export function readPendingWorktreesInDatabase(db: EngineConnection): PendingWorktreeSlot[] {
|
||||
return readSlots(db).map(({ record, state }) => ({ record, state }));
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ function assertPendingWorktreeMutationLease(
|
|||
}
|
||||
|
||||
export function reservePendingWorktreeInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
value: { record: ManagedWorktreeRecord; owner: StateLeaseProcessOwner },
|
||||
leases: readonly OpenClawStateLeaseIdentity[] = [],
|
||||
): void {
|
||||
|
|
@ -152,7 +153,7 @@ export function reservePendingWorktreeInDatabase(
|
|||
}
|
||||
|
||||
export function recoverPendingWorktreesInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
_input: undefined,
|
||||
leases: readonly OpenClawStateLeaseIdentity[] = [],
|
||||
): void {
|
||||
|
|
@ -182,7 +183,7 @@ export function recoverPendingWorktreesInDatabase(
|
|||
}
|
||||
|
||||
export function releasePendingWorktreeInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
{ id }: { id: string },
|
||||
leases: readonly OpenClawStateLeaseIdentity[] = [],
|
||||
): void {
|
||||
|
|
@ -198,7 +199,7 @@ export function releasePendingWorktreeInDatabase(
|
|||
}
|
||||
|
||||
export function publishPendingWorktreeInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
id: string,
|
||||
record: ManagedWorktreeRecord,
|
||||
leases: readonly OpenClawStateLeaseIdentity[] = [],
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
|
||||
export function writeProvisionedSnapshotInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { worktreeId: string } & (
|
||||
| { kind: "reset" }
|
||||
| { kind: "chunk"; path: string; chunkIndex: number; data: Uint8Array }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { serialize } from "node:v8";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type { Selectable } from "kysely";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { sqlPublicationAnchor } from "../../infra/sql-connection.js";
|
||||
import {
|
||||
createSqliteCommitReceipt,
|
||||
hasSqliteCommitReceiptCoverage,
|
||||
|
|
@ -43,17 +44,19 @@ type WorkerReceipt<T = unknown> = {
|
|||
};
|
||||
|
||||
const state = resolveGlobalSingleton(Symbol.for("openclaw.worktreeRegistryPublication"), () => ({
|
||||
sources: new WeakMap<DatabaseSync, SqliteCommitSource>(),
|
||||
sources: new WeakMap<EngineConnection, SqliteCommitSource>(),
|
||||
capture: new AsyncLocalStorage<Map<string, SqliteCommittedFact<RegistryFact>>>(),
|
||||
facts: new Set<(change: Change) => void>(),
|
||||
installing: false,
|
||||
}));
|
||||
|
||||
function sourceFor(db: DatabaseSync): SqliteCommitSource {
|
||||
function sourceFor(db: EngineConnection): SqliteCommitSource {
|
||||
let source = state.sources.get(db);
|
||||
if (!source) {
|
||||
source = {
|
||||
identity: readTrackedStateDatabaseIdentity(db)?.key ?? Symbol("untracked-worktree-registry"),
|
||||
identity:
|
||||
readTrackedStateDatabaseIdentity(sqlPublicationAnchor(db))?.key ??
|
||||
Symbol("untracked-worktree-registry"),
|
||||
incarnation: randomUUID(),
|
||||
};
|
||||
state.sources.set(db, source);
|
||||
|
|
@ -68,7 +71,7 @@ function keyFor(row: Pick<RegistryRow, "id"> | Pick<LeaseRow, "scope" | "lease_k
|
|||
}
|
||||
|
||||
function captureReceipt(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
facts: ReadonlyMap<string, SqliteCommittedFact<RegistryFact>>,
|
||||
) {
|
||||
return createSqliteCommitReceipt({
|
||||
|
|
@ -107,18 +110,18 @@ function publication(receipt: Receipt) {
|
|||
/** Exact owner-written rows only; raw writes and protecting session/placement sets remain separate. */
|
||||
export const worktreeRegistryPublication = {
|
||||
subscribeFacts: (listener: (change: Change) => void) => registerListener(state.facts, listener),
|
||||
rows(db: DatabaseSync, rows: readonly RegistryFact[]) {
|
||||
rows(db: EngineConnection, rows: readonly RegistryFact[]) {
|
||||
stage(db, new Map(rows.map((row) => [keyFor(row), { kind: "postimage", value: { ...row } }])));
|
||||
},
|
||||
deleted(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
rows: readonly (RegistryRow | Pick<LeaseRow, "scope" | "lease_key">)[],
|
||||
) {
|
||||
stage(db, new Map(rows.map((row) => [keyFor(row), { kind: "absent" }])));
|
||||
},
|
||||
};
|
||||
|
||||
function stage(db: DatabaseSync, facts: Map<string, SqliteCommittedFact<RegistryFact>>) {
|
||||
function stage(db: EngineConnection, facts: Map<string, SqliteCommittedFact<RegistryFact>>) {
|
||||
if (!facts.size) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -143,7 +146,7 @@ function stage(db: DatabaseSync, facts: Map<string, SqliteCommittedFact<Registry
|
|||
|
||||
/** The result and existing operation token survive ordinary reply loss without replaying the write. */
|
||||
export function withWorktreeRegistryWorkerReceipt<T>(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
write: () => T,
|
||||
receipt?: string,
|
||||
): T {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { safeParseJson, safeParseJsonRecord } from "@openclaw/normalization-core/json-coercion";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type { Selectable } from "kysely";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../../state/openclaw-state-db.generated.js";
|
||||
import type {
|
||||
ManagedWorktreeOwnerKind,
|
||||
|
|
@ -127,7 +127,7 @@ export function worktreeGcRevision(record: ManagedWorktreeRecord): string {
|
|||
}
|
||||
|
||||
export function getRegistryWorktreeInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
id: string,
|
||||
): ManagedWorktreeRecord | undefined {
|
||||
const query = getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "worktrees">>(db)
|
||||
|
|
@ -139,7 +139,7 @@ export function getRegistryWorktreeInDatabase(
|
|||
}
|
||||
|
||||
export function findLiveRegistryWorktreeByOwnerInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
ownerKind: ManagedWorktreeOwnerKind,
|
||||
ownerId: string,
|
||||
): ManagedWorktreeRecord | undefined {
|
||||
|
|
@ -156,7 +156,7 @@ export function findLiveRegistryWorktreeByOwnerInDatabase(
|
|||
}
|
||||
|
||||
export function findLiveRegistryWorktreeByPathInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
worktreePath: string,
|
||||
): ManagedWorktreeRecord | undefined {
|
||||
const row = executeSqliteQuerySync(
|
||||
|
|
@ -173,7 +173,7 @@ export function findLiveRegistryWorktreeByPathInDatabase(
|
|||
}
|
||||
|
||||
export function listRegistryWorktreesInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
options: WorktreeRegistryListOptions = {},
|
||||
): ManagedWorktreeRecord[] {
|
||||
let query = getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "worktrees">>(db)
|
||||
|
|
@ -187,7 +187,7 @@ export function listRegistryWorktreesInDatabase(
|
|||
return executeSqliteQuerySync(db, query).rows.map(rowToRecord);
|
||||
}
|
||||
|
||||
export function listLiveRegistryWorktreeIdsInDatabase(db: DatabaseSync): string[] {
|
||||
export function listLiveRegistryWorktreeIdsInDatabase(db: EngineConnection): string[] {
|
||||
const query = getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "worktrees">>(db)
|
||||
.selectFrom("worktrees")
|
||||
.select("id")
|
||||
|
|
@ -225,7 +225,7 @@ function parseProvisionedData(
|
|||
: undefined;
|
||||
}
|
||||
|
||||
function readProvisionedData(db: DatabaseSync, id: string) {
|
||||
function readProvisionedData(db: EngineConnection, id: string) {
|
||||
const query = getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "worktrees">>(db)
|
||||
.selectFrom("worktrees")
|
||||
.select("provisioned_paths_json")
|
||||
|
|
@ -235,7 +235,7 @@ function readProvisionedData(db: DatabaseSync, id: string) {
|
|||
}
|
||||
|
||||
export function getRegistryWorktreeProvisionedPathsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
id: string,
|
||||
): string[] | undefined {
|
||||
return readProvisionedData(db, id)?.map((entry) =>
|
||||
|
|
@ -244,7 +244,7 @@ export function getRegistryWorktreeProvisionedPathsInDatabase(
|
|||
}
|
||||
|
||||
export function getRegistryWorktreeProvisionedStateInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
id: string,
|
||||
): ProvisionedFileState[] | undefined {
|
||||
const data = readProvisionedData(db, id);
|
||||
|
|
@ -254,7 +254,7 @@ export function getRegistryWorktreeProvisionedStateInDatabase(
|
|||
}
|
||||
|
||||
export function getRegistryWorktreeProvisionedChunkInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: { worktreeId: string; path: string; chunkIndex: number },
|
||||
): Uint8Array | undefined {
|
||||
const query = getNodeSqliteKysely<
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { safeParseJson } from "@openclaw/normalization-core/json-coercion";
|
||||
import type { Selectable } from "kysely";
|
||||
import {
|
||||
|
|
@ -6,6 +5,7 @@ import {
|
|||
getNodeSqliteKysely,
|
||||
sqliteStringSet,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { requestSqliteWorkerOperationAdmission } from "../../infra/sqlite-worker-operation-admission.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
import { runOpenClawStateWriteTransaction } from "../../state/openclaw-state-db.js";
|
||||
|
|
@ -43,7 +43,8 @@ import type {
|
|||
WorktreeRegistryPredicate,
|
||||
} from "./types.js";
|
||||
|
||||
const query = (db: DatabaseSync) => getNodeSqliteKysely<Pick<DB, "worktrees" | "state_leases">>(db);
|
||||
const query = (db: EngineConnection) =>
|
||||
getNodeSqliteKysely<Pick<DB, "worktrees" | "state_leases">>(db);
|
||||
|
||||
export type WorktreeRegistryPatch = Partial<
|
||||
Pick<ManagedWorktreeRecord, "lastActiveAt" | "removedAt" | "runEndCleanup" | "snapshotRef">
|
||||
|
|
@ -54,7 +55,7 @@ export type WorktreeRegistryPatch = Partial<
|
|||
};
|
||||
|
||||
export function insertRegistryWorktreeInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
{
|
||||
record,
|
||||
provisionedPaths,
|
||||
|
|
@ -98,7 +99,7 @@ export function insertRegistryWorktreeInDatabase(
|
|||
}
|
||||
|
||||
export function updateRegistryWorktreeInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: {
|
||||
id: string;
|
||||
patch: WorktreeRegistryPatch;
|
||||
|
|
@ -150,7 +151,7 @@ export type WorktreeRunEndInput<T> = {
|
|||
predicates?: readonly WorktreeRegistryPredicate[];
|
||||
};
|
||||
|
||||
function assertRemovalToken(db: DatabaseSync, id: string, token: string) {
|
||||
function assertRemovalToken(db: EngineConnection, id: string, token: string) {
|
||||
const row = executeSqliteQuerySync(
|
||||
db,
|
||||
query(db)
|
||||
|
|
@ -167,7 +168,7 @@ function assertRemovalToken(db: DatabaseSync, id: string, token: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function assertPredicate(db: DatabaseSync, predicate: WorktreeRegistryPredicate) {
|
||||
function assertPredicate(db: EngineConnection, predicate: WorktreeRegistryPredicate) {
|
||||
if (predicate.kind === "snapshot-retirement") {
|
||||
return assertSnapshotRetirementInDatabase(db, predicate.record);
|
||||
}
|
||||
|
|
@ -333,7 +334,7 @@ function assertPredicate(db: DatabaseSync, predicate: WorktreeRegistryPredicate)
|
|||
}
|
||||
|
||||
export function assertWorktreeRegistryPredicates(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
predicates: readonly WorktreeRegistryPredicate[] = [],
|
||||
): void {
|
||||
for (const predicate of predicates) {
|
||||
|
|
@ -343,7 +344,7 @@ export function assertWorktreeRegistryPredicates(
|
|||
|
||||
/** Git equality cannot release provisioned bytes or a live lifecycle's recovery custody. */
|
||||
function assertSnapshotRetirementInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
observed: ManagedWorktreeRecord,
|
||||
reapStaleLeases = false,
|
||||
): void {
|
||||
|
|
@ -386,7 +387,7 @@ function assertSnapshotRetirementInDatabase(
|
|||
}
|
||||
|
||||
export function deleteRegistryWorktreeInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { id: string; removalToken?: string; expectedRetired?: ManagedWorktreeRecord },
|
||||
): void {
|
||||
if (input.expectedRetired) {
|
||||
|
|
@ -413,7 +414,11 @@ export function deleteRegistryWorktreeInDatabase(
|
|||
|
||||
export function worktreeRunEndMutation<Input>(
|
||||
operationLabel: string,
|
||||
mutate: (db: DatabaseSync, input: Input, leases?: readonly OpenClawStateLeaseIdentity[]) => void,
|
||||
mutate: (
|
||||
db: EngineConnection,
|
||||
input: Input,
|
||||
leases?: readonly OpenClawStateLeaseIdentity[],
|
||||
) => void,
|
||||
) {
|
||||
return (input: WorktreeRunEndInput<Input>, context: WorkerOperationContext): void => {
|
||||
const database = context.open();
|
||||
|
|
@ -458,7 +463,7 @@ export type WorktreeRemovalRowInput = {
|
|||
retiredRemoval?: true;
|
||||
};
|
||||
export function claimWorktreeRemovalInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: WorktreeRemovalRowInput,
|
||||
): void {
|
||||
const k = query(db);
|
||||
|
|
@ -528,7 +533,7 @@ export type WorktreeRemovalFinalization = {
|
|||
token?: string;
|
||||
};
|
||||
export function finalizeWorktreeRemovalInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: WorktreeRemovalFinalization,
|
||||
): void {
|
||||
const record = getRegistryWorktreeInDatabase(db, input.worktreeId);
|
||||
|
|
@ -553,7 +558,7 @@ export function finalizeWorktreeRemovalInDatabase(
|
|||
}
|
||||
|
||||
export function abortWorktreeRemovalInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { worktreeId: string; token: string },
|
||||
): void {
|
||||
const removed = executeSqliteQuerySync(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
openOpenClawStateDatabase,
|
||||
} from "../../state/openclaw-state-db.js";
|
||||
import { captureOpenClawStateWorkerContext } from "../../state/openclaw-state-worker-context.js";
|
||||
import { requireNativeSqliteDatabase } from "../../test-utils/native-sqlite.js";
|
||||
import { insertRegistryWorktreeProvisionedChunk } from "./provisioned-snapshot.test-support.js";
|
||||
import {
|
||||
getRegistryWorktreeProvisionedChunk,
|
||||
|
|
@ -278,7 +279,7 @@ describe("managed worktree registry", () => {
|
|||
);
|
||||
await insertRegistryWorktree(env, record, { provisionedPaths });
|
||||
const counter = trackSqliteStatementExecutions(
|
||||
openOpenClawStateDatabase({ env }).db,
|
||||
requireNativeSqliteDatabase(openOpenClawStateDatabase({ env }).db),
|
||||
["records"],
|
||||
(sql) => (/\bfrom\s+"worktrees"/iu.test(sql) ? "records" : null),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
getNodeSqliteKysely,
|
||||
sqliteStringSet,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { withStateDatabaseSchemaMaintenance } from "../../infra/state-database-maintenance.js";
|
||||
import {
|
||||
withExistingOpenClawStateDatabaseArtifactPreservingReadOnly,
|
||||
|
|
@ -51,11 +51,11 @@ type WorktreeRegistryDatabase = Pick<
|
|||
"worktrees" | "worktree_provisioned_file_chunks" | "state_leases"
|
||||
>;
|
||||
|
||||
function dbFor(env: NodeJS.ProcessEnv): DatabaseSync {
|
||||
function dbFor(env: NodeJS.ProcessEnv): EngineConnection {
|
||||
return openOpenClawStateDatabase({ env }).db;
|
||||
}
|
||||
|
||||
function kyselyFor(db: DatabaseSync) {
|
||||
function kyselyFor(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<WorktreeRegistryDatabase>(db);
|
||||
}
|
||||
|
||||
|
|
@ -93,9 +93,9 @@ export function listLegacyRegistryWorktreesForMigration(
|
|||
function readRegistry<T>(
|
||||
env: NodeJS.ProcessEnv,
|
||||
behavior: { artifactPreservingReadOnly?: boolean },
|
||||
read: (db: DatabaseSync) => T,
|
||||
read: (db: EngineConnection) => T,
|
||||
): T | undefined {
|
||||
const operation = ({ db }: { db: DatabaseSync }) =>
|
||||
const operation = ({ db }: { db: EngineConnection }) =>
|
||||
tableExists(db, "worktrees") ? read(db) : undefined;
|
||||
return behavior.artifactPreservingReadOnly
|
||||
? withExistingOpenClawStateDatabaseArtifactPreservingReadOnly(operation, { env })
|
||||
|
|
@ -104,7 +104,7 @@ function readRegistry<T>(
|
|||
|
||||
// Doctor/startup planning may release schema custody before these native business writes.
|
||||
function runRegistryMigration(
|
||||
write: (db: DatabaseSync) => number,
|
||||
write: (db: EngineConnection) => number,
|
||||
{ env }: { env: NodeJS.ProcessEnv },
|
||||
): number {
|
||||
return withStateDatabaseSchemaMaintenance(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { safeParseJsonRecord } from "@openclaw/normalization-core/json-coercion";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { isLockOwnerDefinitelyStale } from "../../infra/stale-lock-file.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
import { WorktreeRemovalContentionError } from "./errors.js";
|
||||
|
|
@ -33,7 +33,7 @@ type ScopeLeaseState = {
|
|||
};
|
||||
|
||||
export function collectLiveRunLeases(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
k: ReturnType<typeof getNodeSqliteKysely<WorktreeLeaseDatabase>>,
|
||||
scope: string,
|
||||
reapStale = true,
|
||||
|
|
@ -99,7 +99,7 @@ function inspectRunLeases(
|
|||
|
||||
const WORKTREE_RUN_LEASE_SCOPE_PREFIX = "worktree-run:";
|
||||
|
||||
export function readWorktreeRunLeaseStateInDatabase(db: DatabaseSync) {
|
||||
export function readWorktreeRunLeaseStateInDatabase(db: EngineConnection) {
|
||||
const k = getNodeSqliteKysely<WorktreeLeaseDatabase>(db);
|
||||
const rows = executeSqliteQuerySync(
|
||||
db,
|
||||
|
|
@ -122,7 +122,7 @@ export function readWorktreeRunLeaseStateInDatabase(db: DatabaseSync) {
|
|||
return { liveScopes: [...liveScopes], staleScopes: [...staleScopes] };
|
||||
}
|
||||
|
||||
export function reapWorktreeRunLeasesInDatabase(db: DatabaseSync, scopes: string[]): void {
|
||||
export function reapWorktreeRunLeasesInDatabase(db: EngineConnection, scopes: string[]): void {
|
||||
const k = getNodeSqliteKysely<WorktreeLeaseDatabase>(db);
|
||||
for (const scope of scopes) {
|
||||
// Recheck current owners under the transaction; the sweep grants no delete authority.
|
||||
|
|
@ -136,7 +136,7 @@ export function worktreeRunLeaseScope(worktreeId: string): string {
|
|||
|
||||
/** Removed exact snapshots retain exclusive lifecycle custody during destructive expiry. */
|
||||
export function assertRegistryMutationCustody(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
k: ReturnType<typeof getNodeSqliteKysely<WorktreeLeaseDatabase>>,
|
||||
id: string,
|
||||
token?: string,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { trackSqliteStatementExecutions } from "../../../test/helpers/sqlite-sta
|
|||
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
|
||||
import * as pidAlive from "../../shared/pid-alive.js";
|
||||
import * as stateDatabase from "../../state/openclaw-state-db.js";
|
||||
import { requireNativeSqliteDatabase } from "../../test-utils/native-sqlite.js";
|
||||
import { readWorktreeCleanupState } from "./registry-read.js";
|
||||
import { insertRegistryWorktree } from "./registry.js";
|
||||
import { readWorktreeRunLeaseStateInDatabase, worktreeRunLeaseScope } from "./run-lease-owner.js";
|
||||
|
|
@ -53,7 +54,7 @@ it("reads live, dead, reused, and unverifiable owners without reaping or writer
|
|||
{ env },
|
||||
);
|
||||
}
|
||||
const { db } = stateDatabase.openOpenClawStateDatabase({ env });
|
||||
const db = requireNativeSqliteDatabase(stateDatabase.openOpenClawStateDatabase({ env }).db);
|
||||
const writes = vi.spyOn(stateDatabase, "runOpenClawStateWriteTransaction");
|
||||
const deadPid = vi
|
||||
.spyOn(pidAlive, "isPidDefinitelyDead")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
import { worktreeRegistryPublication } from "./registry-publication.js";
|
||||
import { assertWorktreeRegistryPredicates } from "./registry-run-end.worker.js";
|
||||
|
|
@ -17,7 +17,7 @@ export type WorktreeRunLeaseRowInput = {
|
|||
};
|
||||
|
||||
export function admitWorktreeRunLeaseInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: WorktreeRunLeaseRowInput,
|
||||
): void {
|
||||
if (params.observed) {
|
||||
|
|
@ -64,7 +64,7 @@ export function admitWorktreeRunLeaseInDatabase(
|
|||
}
|
||||
|
||||
export function releaseWorktreeRunLeaseInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
worktreeId: string,
|
||||
token: string,
|
||||
): void {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { safeParseJsonRecord } from "@openclaw/normalization-core/json-coercion";
|
||||
import type { Selectable } from "kysely";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import { requestSqliteWorkerOperationAdmission } from "../../infra/sqlite-worker-operation-admission.js";
|
||||
import {
|
||||
parseStateLeaseProcessOwner,
|
||||
|
|
@ -20,7 +20,7 @@ import type { WorktreeTemplateRecord } from "./template-registry.js";
|
|||
|
||||
type TemplateDatabase = Pick<DB, "worktree_templates" | "state_leases">;
|
||||
const TEMPLATE_READER_SCOPE = "core:managed-worktrees:template-readers";
|
||||
const kyselyFor = (db: DatabaseSync) => getNodeSqliteKysely<TemplateDatabase>(db);
|
||||
const kyselyFor = (db: EngineConnection) => getNodeSqliteKysely<TemplateDatabase>(db);
|
||||
const ensureTemplateSchema = createOpenClawStateSchemaEnsurer({
|
||||
table: "worktree_templates",
|
||||
operationLabel: "agents.worktrees.templates.schema.ensure",
|
||||
|
|
@ -56,7 +56,7 @@ function openTemplateDatabase(context: WorkerOperationContext) {
|
|||
|
||||
function worktreeTemplateMutation<Input, Output>(
|
||||
operationLabel: string,
|
||||
mutate: (db: DatabaseSync, input: Input) => Output,
|
||||
mutate: (db: EngineConnection, input: Input) => Output,
|
||||
) {
|
||||
return (input: Input, context: WorkerOperationContext): Output =>
|
||||
runOpenClawStateWriteTransaction(
|
||||
|
|
@ -71,7 +71,7 @@ function worktreeTemplateMutation<Input, Output>(
|
|||
);
|
||||
}
|
||||
|
||||
function hasTemplateReaders(db: DatabaseSync, id: string): boolean {
|
||||
function hasTemplateReaders(db: EngineConnection, id: string): boolean {
|
||||
const k = kyselyFor(db);
|
||||
const rows = executeSqliteQuerySync(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Insertable, Selectable } from "kysely";
|
||||
import {
|
||||
createSqliteQueryCache,
|
||||
getNodeSqliteKysely,
|
||||
prepareSqliteQueryTakeFirstSync,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
|
||||
type AuditEventsTable = OpenClawStateKyselyDatabase["audit_events"];
|
||||
|
|
@ -12,7 +12,7 @@ type AuditDatabase = Pick<OpenClawStateKyselyDatabase, "audit_events">;
|
|||
type AuditEventRow = Selectable<AuditEventsTable>;
|
||||
export type AuditEventInsert = Required<Omit<Insertable<AuditEventsTable>, "sequence">>;
|
||||
|
||||
function createAuditEventQueries(db: DatabaseSync) {
|
||||
function createAuditEventQueries(db: EngineConnection) {
|
||||
const database = getNodeSqliteKysely<AuditDatabase>(db);
|
||||
return {
|
||||
insert: prepareSqliteQueryTakeFirstSync<AuditEventInsert, { sequence: string }>(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
import { AUDIT_EVENT_RETENTION_MS, rowToAuditEvent } from "./audit-event-store.js";
|
||||
import type { AuditEventListQuery, AuditEventListPage } from "./audit-event-types.js";
|
||||
|
||||
/** Connection-bound query kernel; production list reads execute only in the state worker. */
|
||||
export function listAuditEventsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: AuditEventListQuery,
|
||||
): AuditEventListPage {
|
||||
const filters = params.filters ?? {};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
/** SQLite persistence and stable cursor queries for metadata-only audit events. */
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Selectable } from "kysely";
|
||||
import { AUDIT_ACTIVITY_MESSAGE_KIND } from "../../packages/gateway-protocol/src/schema/audit-activity.js";
|
||||
import {
|
||||
|
|
@ -8,6 +7,7 @@ import {
|
|||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { isPostgresSqlConnection } from "../infra/sql-connection.js";
|
||||
import { normalizeSqliteNumber } from "../infra/sqlite-number.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
|
|
@ -54,7 +54,7 @@ const AUDIT_EVENT_PRUNE_BATCH_ROWS = 1_024;
|
|||
// The single audit writer owns one DB handle. Maintenance and rollback drop
|
||||
// these facts; ordinary inserts need neither a row scan nor an empty expiry DELETE.
|
||||
const auditEventRetention = new WeakMap<
|
||||
DatabaseSync,
|
||||
EngineConnection,
|
||||
{ rowCount: number; nextExpiryAt: number; nextSequence: number }
|
||||
>();
|
||||
|
||||
|
|
@ -481,7 +481,7 @@ export function rowToAuditEvent(row: AuditEventRow): AuditEventRecord {
|
|||
return corruptAuditRow(row, "invalid message direction");
|
||||
}
|
||||
|
||||
function projectMessageIdentities(db: DatabaseSync, input: MessageAuditEventInput) {
|
||||
function projectMessageIdentities(db: EngineConnection, input: MessageAuditEventInput) {
|
||||
const identity = loadOrCreateAuditIdentityKey(db);
|
||||
const conversationId =
|
||||
input.conversationId ?? (input.direction === "outbound" ? input.targetId : undefined);
|
||||
|
|
@ -508,7 +508,7 @@ function projectMessageIdentities(db: DatabaseSync, input: MessageAuditEventInpu
|
|||
};
|
||||
}
|
||||
|
||||
function bindAuditEvent(db: DatabaseSync, input: AuditEventInput) {
|
||||
function bindAuditEvent(db: EngineConnection, input: AuditEventInput) {
|
||||
const message =
|
||||
input.kind === AUDIT_ACTIVITY_MESSAGE_KIND ? projectMessageIdentities(db, input) : undefined;
|
||||
return {
|
||||
|
|
@ -545,7 +545,7 @@ function bindAuditEvent(db: DatabaseSync, input: AuditEventInput) {
|
|||
} satisfies AuditEventInsert;
|
||||
}
|
||||
|
||||
function readAuditEventRetention(db: DatabaseSync) {
|
||||
function readAuditEventRetention(db: EngineConnection) {
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
db,
|
||||
getNodeSqliteKysely<AuditDatabase>(db)
|
||||
|
|
@ -563,7 +563,7 @@ function readAuditEventRetention(db: DatabaseSync) {
|
|||
};
|
||||
}
|
||||
|
||||
function deleteExpiredAuditEvents(db: DatabaseSync, now: number) {
|
||||
function deleteExpiredAuditEvents(db: EngineConnection, now: number) {
|
||||
const kysely = getNodeSqliteKysely<AuditDatabase>(db);
|
||||
const expiredSequences = kysely
|
||||
.selectFrom("audit_events")
|
||||
|
|
@ -578,7 +578,7 @@ function deleteExpiredAuditEvents(db: DatabaseSync, now: number) {
|
|||
);
|
||||
}
|
||||
|
||||
function pruneAfterInsert(db: DatabaseSync, now: number, at: number, sequence: number): void {
|
||||
function pruneAfterInsert(db: EngineConnection, now: number, at: number, sequence: number): void {
|
||||
const kysely = getNodeSqliteKysely<AuditDatabase>(db);
|
||||
const cached = auditEventRetention.get(db);
|
||||
const retention = cached
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
/** Stable installation-local pseudonyms for sensitive audit identifiers. */
|
||||
import { createHmac, randomBytes } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Selectable } from "kysely";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { registerSecretValueForRedaction } from "../logging/secret-redaction-registry.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ const AUDIT_IDENTITY_DOMAIN = "openclaw.audit.identity.v1";
|
|||
// Only a top-level (depth-0) audit/evidence write may create the key: each
|
||||
// caller clears this cache on rollback, because a rolled-back outer transaction
|
||||
// around a nested creation would leave a cached key that was never persisted.
|
||||
const identityByDatabase = new WeakMap<DatabaseSync, AuditIdentityKey>();
|
||||
const identityByDatabase = new WeakMap<EngineConnection, AuditIdentityKey>();
|
||||
|
||||
type AuditIdentityKey = {
|
||||
keyId: string;
|
||||
|
|
@ -67,7 +67,7 @@ function parseAuditIdentityKey(row: AuditIdentityKeyRow): AuditIdentityKey {
|
|||
}
|
||||
|
||||
/** Load the stable audit identity key or create it transactionally on first use. */
|
||||
export function loadOrCreateAuditIdentityKey(db: DatabaseSync): AuditIdentityKey {
|
||||
export function loadOrCreateAuditIdentityKey(db: EngineConnection): AuditIdentityKey {
|
||||
const cached = identityByDatabase.get(db);
|
||||
if (cached) {
|
||||
return cached;
|
||||
|
|
@ -135,7 +135,7 @@ export function loadOrCreateAuditIdentityKey(db: DatabaseSync): AuditIdentityKey
|
|||
}
|
||||
|
||||
/** Forget transaction-local key state after a failed or rolled-back write. */
|
||||
export function clearAuditIdentityKeyCacheForDatabase(db: DatabaseSync): void {
|
||||
export function clearAuditIdentityKeyCacheForDatabase(db: EngineConnection): void {
|
||||
identityByDatabase.delete(db);
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ export function pseudonymizeAuditIdentity(params: {
|
|||
|
||||
/** Project one execution-identity ref without retaining its raw owner value. */
|
||||
export function pseudonymizeExecutionIdentityRef(params: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
kind: ExecutionIdentityRefKind;
|
||||
scope: string;
|
||||
value: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import {
|
||||
closeOpenClawStateDatabaseAsync,
|
||||
closeOpenClawStateDatabaseForTest,
|
||||
|
|
@ -34,7 +34,7 @@ const stages = [
|
|||
] as const;
|
||||
type Stage = (typeof stages)[number]["prefix"];
|
||||
let options: { env: { OPENCLAW_STATE_DIR: string } };
|
||||
let db: DatabaseSync;
|
||||
let db: EngineConnection;
|
||||
|
||||
function inspect(decisionCursor: string, executionId = "execution-local") {
|
||||
return inspectExecutionIdentityRun(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/** Immutable decision facts for action boundaries without an owner-native record. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import { sql, type Selectable } from "kysely";
|
||||
import { validateDecisionReceiptV1 } from "../../packages/gateway-protocol/src/audit-run-validators.js";
|
||||
import type { DecisionReceiptV1 } from "../../packages/gateway-protocol/src/schema/audit-run.js";
|
||||
|
|
@ -8,6 +8,7 @@ import {
|
|||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { normalizeSqliteNumber } from "../infra/sqlite-number.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
|
|
@ -62,7 +63,7 @@ type ExecutionDecisionFactOptions = OpenClawStateDatabaseOptions & {
|
|||
limits?: { maxRows: number; pruneBatchRows: number };
|
||||
};
|
||||
|
||||
function decisionDb(db: DatabaseSync) {
|
||||
function decisionDb(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<ExecutionDecisionDatabase>(db);
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +141,10 @@ function unknownDecisionReceipt(
|
|||
|
||||
type ExecutionDecisionContext = Pick<DecisionReceiptV1, "contextId" | "executionId" | "runId">;
|
||||
|
||||
function hasExactExecutionContext(db: DatabaseSync, context: ExecutionDecisionContext): boolean {
|
||||
function hasExactExecutionContext(
|
||||
db: EngineConnection,
|
||||
context: ExecutionDecisionContext,
|
||||
): boolean {
|
||||
if (!tableExists(db, "execution_identity_contexts")) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -157,7 +161,7 @@ function hasExactExecutionContext(db: DatabaseSync, context: ExecutionDecisionCo
|
|||
);
|
||||
}
|
||||
|
||||
function deleteExpiredDecisionFacts(db: DatabaseSync, now: number, limit: number) {
|
||||
function deleteExpiredDecisionFacts(db: EngineConnection, now: number, limit: number) {
|
||||
const kysely = decisionDb(db);
|
||||
const expiredIds = kysely
|
||||
.selectFrom("execution_decision_facts")
|
||||
|
|
@ -173,7 +177,7 @@ function deleteExpiredDecisionFacts(db: DatabaseSync, now: number, limit: number
|
|||
}
|
||||
|
||||
function pruneDecisionFactsAfterInsert(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
now: number,
|
||||
limits: { maxRows: number; pruneBatchRows: number },
|
||||
): void {
|
||||
|
|
@ -284,7 +288,11 @@ export function recordExecutionDecisionFactInDatabase(
|
|||
);
|
||||
}
|
||||
|
||||
function retainedDecisionFactsForContextQuery(db: DatabaseSync, contextId: string, now: number) {
|
||||
function retainedDecisionFactsForContextQuery(
|
||||
db: EngineConnection,
|
||||
contextId: string,
|
||||
now: number,
|
||||
) {
|
||||
return decisionDb(db)
|
||||
.selectFrom("execution_decision_facts")
|
||||
.where("context_id", "=", contextId)
|
||||
|
|
@ -310,7 +318,7 @@ function executionDecisionSelectorId(row: ExecutionDecisionMetadataRow): string
|
|||
}
|
||||
|
||||
function retainedDecisionFactMetadata(params: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
contextId: string;
|
||||
now: number;
|
||||
after?: ExecutionDecisionFactCursor;
|
||||
|
|
@ -410,7 +418,7 @@ function projectDecisionMetadata(
|
|||
|
||||
/** Summarize at most 128 owner rows; the 129th makes coverage explicitly unknown. */
|
||||
export function summarizeExecutionDecisionFactsForContextInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
context: ExecutionDecisionContext;
|
||||
now?: number;
|
||||
|
|
@ -462,7 +470,7 @@ export function summarizeExecutionDecisionFactsForContextInDatabase(
|
|||
}
|
||||
|
||||
export function hasExecutionDecisionFactsForRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
now?: number;
|
||||
|
|
@ -489,7 +497,7 @@ export function hasExecutionDecisionFactsForRunInDatabase(
|
|||
}
|
||||
|
||||
export function pageExecutionDecisionFactsForContextInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
context: ExecutionDecisionContext;
|
||||
after?: ExecutionDecisionFactCursor;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/** Bounded receipt projection across admission, owner-native, and generic decision facts. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import type {
|
||||
DecisionReceiptDisplayV1,
|
||||
DecisionReceiptV1,
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
pageOperatorApprovalReceiptsForRunInDatabase,
|
||||
summarizeOperatorApprovalReceiptsForRunInDatabase,
|
||||
} from "../gateway/operator-approval-store.receipts.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { parsePositiveAuditCursor } from "./audit-cursor.js";
|
||||
import {
|
||||
pageExecutionDecisionFactsForContextInDatabase,
|
||||
|
|
@ -210,7 +211,7 @@ function projectDecisionDisplay({
|
|||
}
|
||||
|
||||
export function presentExecutionDecisionReceiptsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
schema: OwnerLifecycleSchemaFacts;
|
||||
context: ExecutionIdentityContextV1;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/** Worker-only canonical context construction and bounded value helpers. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import { validateExecutionIdentityContextV1 } from "../../packages/gateway-protocol/src/audit-run-validators.js";
|
||||
import type { ExecutionIdentityContextV1 } from "../../packages/gateway-protocol/src/schema/audit-run.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { pseudonymizeExecutionIdentityRef } from "./audit-identity.js";
|
||||
import type { ExecutionIdentityAdmissionEnvelope } from "./execution-identity-admission.js";
|
||||
import { sortUniqueExecutionIdentityEntries } from "./execution-identity-ordering.js";
|
||||
|
|
@ -36,7 +37,7 @@ export function freezeExecutionIdentityContext<T>(value: T, seen = new WeakSet<o
|
|||
}
|
||||
|
||||
function hmacRef(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
kind: Parameters<typeof pseudonymizeExecutionIdentityRef>[0]["kind"],
|
||||
scope: string,
|
||||
value: string,
|
||||
|
|
@ -50,7 +51,7 @@ function hmacRef(
|
|||
}
|
||||
|
||||
export function buildExecutionIdentityContext(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
envelope: ExecutionIdentityAdmissionEnvelope,
|
||||
fixed: { contextId: string; createdAt: number },
|
||||
): ExecutionIdentityContextV1 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/** Immutable execution identity context storage and run-admission projection. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import type { Selectable } from "kysely";
|
||||
import { validateExecutionIdentityContextV1 } from "../../packages/gateway-protocol/src/audit-run-validators.js";
|
||||
import type { ExecutionIdentityContextV1 } from "../../packages/gateway-protocol/src/schema/audit-run.js";
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { normalizeSqliteNumber } from "../infra/sqlite-number.js";
|
||||
import { executeExistingOpenClawStateRead } from "../state/openclaw-state-db-readonly.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
|
|
@ -84,7 +85,7 @@ type ExecutionIdentityContextReadResult =
|
|||
| { status: "missing" }
|
||||
| { status: "corrupt"; runId: string; reasonCode: "identity_context_corrupt" };
|
||||
|
||||
function executionIdentityDb(db: DatabaseSync) {
|
||||
function executionIdentityDb(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<ExecutionIdentityDatabase>(db);
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +117,7 @@ function parseExecutionIdentityRow(row: ExecutionIdentityRow): ExecutionIdentity
|
|||
}
|
||||
|
||||
function readRowByExecutionId(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
executionId: string,
|
||||
): ExecutionIdentityRow | undefined {
|
||||
return executeSqliteQueryTakeFirstSync(
|
||||
|
|
@ -129,7 +130,7 @@ function readRowByExecutionId(
|
|||
}
|
||||
|
||||
function readRowsByRunId(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
runId: string,
|
||||
now: number,
|
||||
offset: number,
|
||||
|
|
@ -149,7 +150,7 @@ function readRowsByRunId(
|
|||
).rows;
|
||||
}
|
||||
|
||||
function deleteExpiredExecutionIdentityContexts(db: DatabaseSync, now: number, limit: number) {
|
||||
function deleteExpiredExecutionIdentityContexts(db: EngineConnection, now: number, limit: number) {
|
||||
const kysely = executionIdentityDb(db);
|
||||
const expiredIds = kysely
|
||||
.selectFrom("execution_identity_contexts")
|
||||
|
|
@ -165,7 +166,7 @@ function deleteExpiredExecutionIdentityContexts(db: DatabaseSync, now: number, l
|
|||
}
|
||||
|
||||
function pruneExecutionIdentityContextsAfterInsert(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
now: number,
|
||||
limits: { maxRows: number; pruneBatchRows: number },
|
||||
): void {
|
||||
|
|
@ -240,7 +241,7 @@ function persistExecutionIdentityAdmissionEnvelope(
|
|||
createdAt: envelope.createdAt,
|
||||
});
|
||||
const plannedContextJson = JSON.stringify(plannedContext);
|
||||
let transactionDatabase: DatabaseSync | undefined;
|
||||
let transactionDatabase: EngineConnection | undefined;
|
||||
try {
|
||||
return runOpenClawStateWriteTransaction(
|
||||
({ db }) => {
|
||||
|
|
@ -329,7 +330,7 @@ export function processExecutionIdentityAdmissionWorkInDatabase(
|
|||
|
||||
/** Read one exact execution while turning malformed rows into typed diagnostics. */
|
||||
function readExecutionIdentityContextByExecutionId(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
executionId: string,
|
||||
now: number,
|
||||
schema: ExecutionIdentityInspectionSchemaFacts,
|
||||
|
|
@ -440,7 +441,7 @@ function missingInspectionResult(
|
|||
}
|
||||
|
||||
function inspectExactExecution(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: Extract<ExecutionIdentityInspectionQuery, { executionId: string }>,
|
||||
schema: ExecutionIdentityInspectionSchemaFacts,
|
||||
): InternalAuditRunInspectResult {
|
||||
|
|
@ -490,7 +491,7 @@ function inspectExactExecution(
|
|||
return missingInspectionResult(selector);
|
||||
}
|
||||
|
||||
function hasAnyRunContext(db: DatabaseSync, runId: string): boolean {
|
||||
function hasAnyRunContext(db: EngineConnection, runId: string): boolean {
|
||||
return Boolean(
|
||||
executeSqliteQueryTakeFirstSync(
|
||||
db,
|
||||
|
|
@ -504,7 +505,7 @@ function hasAnyRunContext(db: DatabaseSync, runId: string): boolean {
|
|||
}
|
||||
|
||||
function hasRetainedAuditRun(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
runId: string,
|
||||
now: number,
|
||||
schema: ExecutionIdentityInspectionSchemaFacts,
|
||||
|
|
@ -527,7 +528,7 @@ function hasRetainedAuditRun(
|
|||
}
|
||||
|
||||
function inspectRunSelector(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: Extract<ExecutionIdentityInspectionQuery, { runId: string }>,
|
||||
schema: ExecutionIdentityInspectionSchemaFacts,
|
||||
): InternalAuditRunInspectResult {
|
||||
|
|
@ -650,7 +651,7 @@ function inspectRunSelector(
|
|||
|
||||
/** Query only the fixed-read worker's admitted connection. */
|
||||
export function inspectExecutionIdentityRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: ExecutionIdentityInspectionQuery,
|
||||
schema: ExecutionIdentityInspectionSchemaFacts,
|
||||
): InternalAuditRunInspectResult {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { createSqliteSchemaEnsurer } from "../infra/sqlite-schema-ensure.js";
|
||||
import type { DB as OpenClawStateDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
import { getOpenClawStateFeatureSchemaSql } from "../state/openclaw-state-feature-schema.js";
|
||||
|
|
@ -24,7 +24,7 @@ export const ensureExecutionOwnerLifecycleBindingSchema = createSqliteSchemaEnsu
|
|||
|
||||
/** The first admitted execution owns this diagnostic link for the receipt's lifetime. */
|
||||
export function bindExecutionOwnerLifecycleMetadata(params: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
ownerKind: ExecutionOwnerLifecycleKind;
|
||||
ownerId: string;
|
||||
binding: { contextId: string; executionId: string };
|
||||
|
|
@ -61,7 +61,7 @@ export function bindExecutionOwnerLifecycleMetadata(params: {
|
|||
|
||||
/** Removes exact owner metadata without allocating the opt-in table. */
|
||||
export function deleteExecutionOwnerLifecycleMetadata(params: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
ownerKind: ExecutionOwnerLifecycleKind;
|
||||
ownerIds: readonly string[];
|
||||
/** Connection-owned schema fact; deletion never allocates opt-in metadata. */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { DatabaseSync } from "node:sqlite";
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ExecutionIdentityContextV1 } from "../../packages/gateway-protocol/src/index.js";
|
||||
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_VERSION } from "../state/openclaw-state-db-contract.js";
|
||||
import { tableHasColumn, tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import {
|
||||
|
|
@ -260,7 +261,7 @@ describe("owner-native execution lifecycle receipts", () => {
|
|||
{
|
||||
stage: "cron",
|
||||
cursor: "c:0:0",
|
||||
addSuccessor: (db: DatabaseSync) => {
|
||||
addSuccessor: (db: EngineConnection) => {
|
||||
db.prepare(
|
||||
`INSERT INTO cron_run_receipts (
|
||||
receipt_id, store_key, job_id, config_revision, agent_id, request_run_id,
|
||||
|
|
@ -273,7 +274,7 @@ describe("owner-native execution lifecycle receipts", () => {
|
|||
) VALUES ('cron', 'cron-2', 'context-1', 'execution-1')`,
|
||||
).run();
|
||||
},
|
||||
deleteAnchor: (db: DatabaseSync) => {
|
||||
deleteAnchor: (db: EngineConnection) => {
|
||||
db.prepare(
|
||||
"DELETE FROM execution_owner_lifecycle_bindings WHERE owner_kind = 'cron' AND owner_id = 'cron-1'",
|
||||
).run();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/** Static owner-native cron lifecycle projection for run inspection. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import type {
|
||||
DecisionReceiptV1,
|
||||
ExecutionIdentityContextV1,
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { runSqliteDeferredTransactionSync } from "../infra/sqlite-transaction.js";
|
||||
import type { DB as OpenClawStateDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
import { EXECUTION_OWNER_LIFECYCLE_BINDING_TABLE } from "./execution-owner-lifecycle-binding-store.js";
|
||||
|
|
@ -40,7 +41,7 @@ const KNOWN_STATUSES = new Set(["running", "ok", "error", "skipped", "interrupte
|
|||
const OWNER_LIFECYCLE_CURSOR_RETAINED_ERROR = "owner lifecycle cursor is no longer retained";
|
||||
|
||||
function assertRetainedCursor(params: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
contextId: string;
|
||||
executionId: string;
|
||||
after?: OwnerLifecycleCursor;
|
||||
|
|
@ -75,7 +76,7 @@ function assertRetainedCursor(params: {
|
|||
}
|
||||
|
||||
function readRows(params: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
schema: OwnerLifecycleSchemaFacts;
|
||||
contextId: string;
|
||||
executionId: string;
|
||||
|
|
@ -126,7 +127,7 @@ function readRows(params: {
|
|||
}
|
||||
|
||||
function countRows(params: {
|
||||
db: DatabaseSync;
|
||||
db: EngineConnection;
|
||||
schema: OwnerLifecycleSchemaFacts;
|
||||
contextId: string;
|
||||
executionId?: string;
|
||||
|
|
@ -208,7 +209,7 @@ function projectReceipt(
|
|||
}
|
||||
|
||||
export function summarizeOwnerLifecycleReceiptsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
schema: OwnerLifecycleSchemaFacts;
|
||||
context: ExecutionIdentityContextV1;
|
||||
|
|
@ -232,7 +233,7 @@ export function summarizeOwnerLifecycleReceiptsInDatabase(
|
|||
}
|
||||
|
||||
export function pageOwnerLifecycleReceiptsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
schema: OwnerLifecycleSchemaFacts;
|
||||
context: ExecutionIdentityContextV1;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/** Read-only owner-native outbound message lifecycle queries for run inspection. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
/** Read-only owner-native outbound message lifecycle queries for run inspection. */
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { normalizeSqliteNumber } from "../infra/sqlite-number.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
|
|
@ -22,7 +22,7 @@ type MessageDeliveryAuditDatabase = Pick<
|
|||
"audit_events" | "outbound_message_execution_bindings"
|
||||
>;
|
||||
|
||||
function deliveryAuditDb(db: DatabaseSync) {
|
||||
function deliveryAuditDb(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<MessageDeliveryAuditDatabase>(db);
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ type MessageExecutionSelector = {
|
|||
};
|
||||
|
||||
function terminalBindingEventIds(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
runId: string,
|
||||
binding: { contextId: string; executionId: string },
|
||||
) {
|
||||
|
|
@ -82,7 +82,7 @@ function compositeMessageRowId(event: OutboundMessageAuditEventRecord): number {
|
|||
}
|
||||
|
||||
function readTerminalEventsForRun(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
contextId?: string;
|
||||
|
|
@ -152,7 +152,7 @@ function streamAfterCursor(
|
|||
|
||||
function fillMessageStream(
|
||||
stream: MessageStream,
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: MessageExecutionSelector & {
|
||||
now: number;
|
||||
},
|
||||
|
|
@ -186,7 +186,7 @@ function fillMessageStream(
|
|||
|
||||
function takeNextMessageEvent(
|
||||
streams: MessageStream[],
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: MessageExecutionSelector & {
|
||||
now: number;
|
||||
},
|
||||
|
|
@ -206,7 +206,7 @@ function takeNextMessageEvent(
|
|||
}
|
||||
|
||||
function hasTerminalCursor(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
contextId?: string;
|
||||
|
|
@ -236,7 +236,7 @@ function hasTerminalCursor(
|
|||
|
||||
/** Count retained owner-native outbound lifecycle records for one run. */
|
||||
export function countOutboundMessageAuditEventsForRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
contextId?: string;
|
||||
|
|
@ -268,7 +268,7 @@ export function countOutboundMessageAuditEventsForRunInDatabase(
|
|||
|
||||
/** Page retained owner-native outbound lifecycle records in decision order. */
|
||||
export function pageOutboundMessageAuditEventsForRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
contextId?: string;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
/** Lazy owner-native persistence for nonterminal outbound message progress. */
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Insertable, Selectable } from "kysely";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { normalizeSqliteNumber } from "../infra/sqlite-number.js";
|
||||
import { createSqliteSchemaEnsurer } from "../infra/sqlite-schema-ensure.js";
|
||||
import { ensureColumn, tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
|
|
@ -43,8 +43,8 @@ const OUTBOUND_MESSAGE_PROGRESS_RETENTION_MS = 30 * 24 * 60 * 60_000;
|
|||
const OUTBOUND_MESSAGE_PROGRESS_MAX_ROWS = 200_000;
|
||||
const OUTBOUND_MESSAGE_PROGRESS_PRUNE_BATCH_ROWS = 1_024;
|
||||
const AUDIT_HMAC_REF_RE = /^hmac-sha256:v1:[a-f0-9]{32}:[a-f0-9]{64}$/u;
|
||||
const ensuredDatabases = new WeakSet<DatabaseSync>();
|
||||
const progressRowCounts = new WeakMap<DatabaseSync, number>();
|
||||
const ensuredDatabases = new WeakSet<EngineConnection>();
|
||||
const progressRowCounts = new WeakMap<EngineConnection, number>();
|
||||
|
||||
function progressSchemaSql(): string {
|
||||
return getOpenClawStateFeatureSchemaSql("outbound_message_progress", {
|
||||
|
|
@ -53,7 +53,7 @@ function progressSchemaSql(): string {
|
|||
});
|
||||
}
|
||||
|
||||
function progressDb(db: DatabaseSync) {
|
||||
function progressDb(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<ProgressDatabase>(db);
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ function ensureProgressSchema(options: OpenClawStateDatabaseOptions): void {
|
|||
ensuredDatabases.add(database.db);
|
||||
}
|
||||
|
||||
function projectProgressIdentities(db: DatabaseSync, input: OutboundMessageProgressInput) {
|
||||
function projectProgressIdentities(db: EngineConnection, input: OutboundMessageProgressInput) {
|
||||
const identity = loadOrCreateAuditIdentityKey(db);
|
||||
const conversationId = input.conversationId ?? input.targetId;
|
||||
const ref = (
|
||||
|
|
@ -103,7 +103,7 @@ function projectProgressIdentities(db: DatabaseSync, input: OutboundMessageProgr
|
|||
}
|
||||
|
||||
function bindProgressRow(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: OutboundMessageProgressInput,
|
||||
executionBinding?: MessageExecutionBinding,
|
||||
): Insertable<ProgressTable> {
|
||||
|
|
@ -227,7 +227,7 @@ function rowToProgressEvent(row: ProgressRow): OutboundMessageAuditEventRecord {
|
|||
: { ...common, action: row.action, status: "started", outcome: "platform_started" };
|
||||
}
|
||||
|
||||
function countProgressRows(db: DatabaseSync): number {
|
||||
function countProgressRows(db: EngineConnection): number {
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
db,
|
||||
progressDb(db)
|
||||
|
|
@ -237,7 +237,7 @@ function countProgressRows(db: DatabaseSync): number {
|
|||
return normalizeSqliteNumber(row?.count ?? null) ?? 0;
|
||||
}
|
||||
|
||||
function deleteExpiredProgressRows(db: DatabaseSync, now: number, limit: number) {
|
||||
function deleteExpiredProgressRows(db: EngineConnection, now: number, limit: number) {
|
||||
const kysely = progressDb(db);
|
||||
const expiredSequences = kysely
|
||||
.selectFrom("outbound_message_progress")
|
||||
|
|
@ -252,7 +252,7 @@ function deleteExpiredProgressRows(db: DatabaseSync, now: number, limit: number)
|
|||
);
|
||||
}
|
||||
|
||||
function pruneProgressAfterInsert(db: DatabaseSync, now: number): void {
|
||||
function pruneProgressAfterInsert(db: EngineConnection, now: number): void {
|
||||
const kysely = progressDb(db);
|
||||
const expired = deleteExpiredProgressRows(db, now, OUTBOUND_MESSAGE_PROGRESS_PRUNE_BATCH_ROWS);
|
||||
const cachedCount = progressRowCounts.get(db);
|
||||
|
|
@ -295,7 +295,7 @@ export function recordOutboundMessageProgressInDatabase(
|
|||
): OutboundMessageAuditEventRecord | undefined {
|
||||
const executionToken = planMessageExecutionBinding(input.executionIdentityToken, input.runId);
|
||||
ensureProgressSchema(options);
|
||||
let cacheDatabase: DatabaseSync | undefined;
|
||||
let cacheDatabase: EngineConnection | undefined;
|
||||
try {
|
||||
return runOpenClawStateWriteTransaction(({ db }) => {
|
||||
cacheDatabase = db;
|
||||
|
|
@ -334,7 +334,7 @@ export function recordOutboundMessageProgressInDatabase(
|
|||
}
|
||||
|
||||
export function countOutboundMessageProgressForRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
contextId?: string;
|
||||
|
|
@ -368,7 +368,7 @@ export function countOutboundMessageProgressForRunInDatabase(
|
|||
}
|
||||
|
||||
export function readOutboundMessageProgressForRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
contextId?: string;
|
||||
|
|
@ -420,7 +420,7 @@ export function readOutboundMessageProgressForRunInDatabase(
|
|||
}
|
||||
|
||||
export function hasOutboundMessageProgressCursorInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
runId: string;
|
||||
contextId?: string;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
/** Owner-native outbound message lifecycle projection for run inspection. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import type {
|
||||
DecisionReceiptV1,
|
||||
ExecutionIdentityContextV1,
|
||||
} from "../../packages/gateway-protocol/src/index.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import type { OutboundMessageAuditEventRecord } from "./audit-event-types.js";
|
||||
import {
|
||||
countOutboundMessageAuditEventsForRunInDatabase,
|
||||
|
|
@ -126,7 +127,7 @@ function projectMessageDeliveryReceipt(
|
|||
}
|
||||
|
||||
export function summarizeMessageDeliveryReceiptsForRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
context: ExecutionIdentityContextV1;
|
||||
now: number;
|
||||
|
|
@ -146,7 +147,7 @@ export function summarizeMessageDeliveryReceiptsForRunInDatabase(
|
|||
}
|
||||
|
||||
export function pageMessageDeliveryReceiptsForRunInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: {
|
||||
context: ExecutionIdentityContextV1;
|
||||
after?: OutboundMessageAuditEventCursor;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/** Exact execution binding for owner-native outbound message lifecycle facts. */
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
/** Exact execution binding for owner-native outbound message lifecycle facts. */
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { createSqliteSchemaEnsurer } from "../infra/sqlite-schema-ensure.js";
|
||||
import { tableExists, tableHasColumn } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
|
|
@ -23,7 +23,7 @@ type ExecutionBindingDatabase = Pick<
|
|||
OpenClawStateKyselyDatabase,
|
||||
"execution_identity_contexts" | "outbound_message_execution_bindings"
|
||||
>;
|
||||
const ensuredTerminalBindingDatabases = new WeakSet<DatabaseSync>();
|
||||
const ensuredTerminalBindingDatabases = new WeakSet<EngineConnection>();
|
||||
|
||||
export type MessageExecutionBinding = Readonly<{
|
||||
contextId: string;
|
||||
|
|
@ -44,7 +44,7 @@ export function selectMessageExecutionBinding(params: {
|
|||
}
|
||||
|
||||
export function hasMessageExecutionBindingColumns(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
tableName: "outbound_message_progress",
|
||||
): boolean {
|
||||
return (
|
||||
|
|
@ -100,7 +100,7 @@ export function planMessageExecutionBinding(
|
|||
|
||||
/** Confirm the exact retained admission row; run correlation alone never binds a receipt. */
|
||||
export function confirmMessageExecutionBinding(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
token: ExecutionIdentityAdmissionToken | undefined,
|
||||
): MessageExecutionBinding | undefined {
|
||||
if (!token || !tableExists(db, "execution_identity_contexts")) {
|
||||
|
|
@ -120,7 +120,7 @@ export function confirmMessageExecutionBinding(
|
|||
}
|
||||
|
||||
function recordTerminalMessageExecutionBinding(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: MessageExecutionBinding & { eventId: string; runId: string },
|
||||
): void {
|
||||
executeSqliteQuerySync(
|
||||
|
|
@ -137,7 +137,7 @@ function recordTerminalMessageExecutionBinding(
|
|||
}
|
||||
|
||||
export function recordConfirmedTerminalMessageExecutionBinding(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
params: { eventId: string | undefined; token?: ExecutionIdentityAdmissionToken },
|
||||
): void {
|
||||
if (!params.eventId) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../../state/openclaw-state-db.generated.js";
|
||||
import { INGRESS_CLAIM_LEASE_MS } from "./ingress-claim-owner.js";
|
||||
import type {
|
||||
|
|
@ -10,7 +10,7 @@ import { DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS } from "./ingress-retry-policy.js";
|
|||
|
||||
/** Count failed channel ingress events per channel account for operator health surfaces. */
|
||||
export function countFailedChannelIngressQueueEntriesInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
): ChannelIngressFailedHealth[] {
|
||||
const queueDb =
|
||||
getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "channel_ingress_events">>(db);
|
||||
|
|
@ -36,7 +36,7 @@ export function countFailedChannelIngressQueueEntriesInDatabase(
|
|||
|
||||
/** Aggregate active lanes whose retry or claim state can block later ingress. */
|
||||
export function countChannelIngressQueuePressureInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
now: number,
|
||||
): ChannelIngressPressureSnapshot {
|
||||
const queueDb =
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import {
|
||||
countChannelIngressQueuePressureInDatabase,
|
||||
countFailedChannelIngressQueueEntriesInDatabase,
|
||||
|
|
@ -10,7 +10,7 @@ import type {
|
|||
import { listChannelIngressAccountsInDatabase } from "./ingress-queue.kernel.js";
|
||||
|
||||
export function readChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
command: ChannelIngressReadCommand,
|
||||
): ChannelIngressReadReply {
|
||||
if (command.type === "channelIngress.accounts") {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import {
|
||||
executeSqliteQuerySync,
|
||||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
sqliteStringSet,
|
||||
} from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
import {
|
||||
baseRecord,
|
||||
|
|
@ -25,7 +25,8 @@ import type {
|
|||
ChannelIngressScope,
|
||||
} from "./ingress-queue.types.js";
|
||||
|
||||
const getQueue = (db: DatabaseSync) => getNodeSqliteKysely<Pick<DB, "channel_ingress_events">>(db);
|
||||
const getQueue = (db: EngineConnection) =>
|
||||
getNodeSqliteKysely<Pick<DB, "channel_ingress_events">>(db);
|
||||
const affectedRows = (result: { numAffectedRows?: bigint }) => Number(result.numAffectedRows ?? 0n);
|
||||
const CLEARED_CLAIM_COLUMNS = {
|
||||
claim_token: null,
|
||||
|
|
@ -34,7 +35,7 @@ const CLEARED_CLAIM_COLUMNS = {
|
|||
};
|
||||
|
||||
function executeMutation(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
query: Parameters<typeof executeSqliteQuerySync>[1],
|
||||
): boolean {
|
||||
return affectedRows(executeSqliteQuerySync(db, query)) > 0;
|
||||
|
|
@ -48,14 +49,14 @@ function normalizeLimit(limit: number | "all" | undefined): number {
|
|||
return limit === "all" ? Number.MAX_SAFE_INTEGER : Math.max(1, Math.floor(limit ?? 100));
|
||||
}
|
||||
|
||||
function selectQueueRows(db: DatabaseSync, queueName: string) {
|
||||
function selectQueueRows(db: EngineConnection, queueName: string) {
|
||||
return getQueue(db)
|
||||
.selectFrom("channel_ingress_events")
|
||||
.selectAll()
|
||||
.where("queue_name", "=", queueName);
|
||||
}
|
||||
|
||||
function selectRow(db: DatabaseSync, queueName: string, id: string) {
|
||||
function selectRow(db: EngineConnection, queueName: string, id: string) {
|
||||
return executeSqliteQueryTakeFirstSync(
|
||||
db,
|
||||
selectQueueRows(db, queueName).where("event_id", "=", id),
|
||||
|
|
@ -63,7 +64,7 @@ function selectRow(db: DatabaseSync, queueName: string, id: string) {
|
|||
}
|
||||
|
||||
export function readChannelIngressClaimSnapshotInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressClaimRequest,
|
||||
): ChannelIngressClaimSnapshot {
|
||||
const base = selectQueueRows(db, input.queueName);
|
||||
|
|
@ -109,7 +110,7 @@ export function readChannelIngressClaimSnapshotInDatabase(
|
|||
}
|
||||
|
||||
function tombstoneCorruptRow(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
row: ChannelIngressRow,
|
||||
now: number,
|
||||
reason: "corrupt_payload" | "corrupt_claim",
|
||||
|
|
@ -142,7 +143,7 @@ function tombstoneCorruptRow(
|
|||
}
|
||||
|
||||
export function enqueueChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressScope & {
|
||||
id: string;
|
||||
payloadJson: string;
|
||||
|
|
@ -193,7 +194,7 @@ export function enqueueChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function claimChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { queueName: string; id: string; ownerId: string; laneKey?: string },
|
||||
now: () => number = Date.now,
|
||||
): ChannelIngressRow | null {
|
||||
|
|
@ -227,7 +228,7 @@ export function claimChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function claimNextChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: {
|
||||
request: ChannelIngressClaimRequest;
|
||||
snapshot: ChannelIngressClaimSnapshot;
|
||||
|
|
@ -262,7 +263,7 @@ export function claimNextChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function recoverChannelIngressClaimInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { row: ChannelIngressRow; cutoff: number; now: number },
|
||||
): boolean {
|
||||
const current = selectRow(db, input.row.queue_name, input.row.event_id);
|
||||
|
|
@ -300,7 +301,7 @@ type ChannelIngressMutation = {
|
|||
now: number;
|
||||
};
|
||||
|
||||
function selectedMutation(db: DatabaseSync, input: ChannelIngressMutation) {
|
||||
function selectedMutation(db: EngineConnection, input: ChannelIngressMutation) {
|
||||
const base = getQueue(db)
|
||||
.updateTable("channel_ingress_events")
|
||||
.where("queue_name", "=", input.queueName)
|
||||
|
|
@ -311,7 +312,7 @@ function selectedMutation(db: DatabaseSync, input: ChannelIngressMutation) {
|
|||
}
|
||||
|
||||
export function refreshChannelIngressClaimInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressMutation,
|
||||
): boolean {
|
||||
return executeMutation(
|
||||
|
|
@ -321,7 +322,7 @@ export function refreshChannelIngressClaimInDatabase(
|
|||
}
|
||||
|
||||
export function completeChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressMutation & ChannelIngressScope & { metadataJson: string | null },
|
||||
): boolean {
|
||||
const update = executeSqliteQuerySync(
|
||||
|
|
@ -368,7 +369,7 @@ export function completeChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function releaseChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressMutation & { recordAttempt?: boolean; lastError?: string },
|
||||
): boolean {
|
||||
return executeMutation(
|
||||
|
|
@ -388,7 +389,7 @@ export function releaseChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function failChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressMutation & { reason: string; message?: string },
|
||||
): boolean {
|
||||
return executeMutation(
|
||||
|
|
@ -411,7 +412,7 @@ export function failChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function pruneChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: {
|
||||
queueName: string;
|
||||
options: Omit<ChannelIngressQueuePruneOptions, "protectIds"> & { protectIds?: string[] };
|
||||
|
|
@ -490,7 +491,7 @@ export function pruneChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function listStaleChannelIngressClaimsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { queueName: string; cutoff: number },
|
||||
): ChannelIngressRow[] {
|
||||
return executeSqliteQuerySync(
|
||||
|
|
@ -511,7 +512,7 @@ export function listStaleChannelIngressClaimsInDatabase(
|
|||
}
|
||||
|
||||
export function deleteChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressMutation,
|
||||
): boolean {
|
||||
const base = getQueue(db)
|
||||
|
|
@ -527,7 +528,7 @@ export function deleteChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function resubmitChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { queueName: string; id: string; now: number },
|
||||
):
|
||||
| { kind: "not-found" }
|
||||
|
|
@ -579,7 +580,7 @@ export function resubmitChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function purgeChannelIngressInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { queueName: string },
|
||||
): number {
|
||||
return affectedRows(
|
||||
|
|
@ -591,7 +592,7 @@ export function purgeChannelIngressInDatabase(
|
|||
}
|
||||
|
||||
export function listChannelIngressAccountsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: { channelId: string },
|
||||
): string[] {
|
||||
return executeSqliteQuerySync(
|
||||
|
|
@ -606,7 +607,7 @@ export function listChannelIngressAccountsInDatabase(
|
|||
}
|
||||
|
||||
export function listChannelIngressRowsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
input: ChannelIngressListInput,
|
||||
): ChannelIngressRow[] {
|
||||
const select = selectQueueRows(db, input.queueName).where(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
openOpenClawStateDatabase,
|
||||
runOpenClawStateWriteTransaction,
|
||||
} from "../../state/openclaw-state-db.js";
|
||||
import { requireNativeSqliteDatabase } from "../../test-utils/native-sqlite.js";
|
||||
import { createTestIngressQueue, withTempState } from "./ingress-drain.test-helpers.js";
|
||||
import { pruneChannelIngressInDatabase } from "./ingress-queue.kernel.js";
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ describe("channel ingress pruning", () => {
|
|||
it("prunes pending overflow past a protected page without materializing rows", async () => {
|
||||
await withTempState(async (stateDir) => {
|
||||
const env = { OPENCLAW_STATE_DIR: stateDir };
|
||||
const { db } = openOpenClawStateDatabase({ env });
|
||||
const db = requireNativeSqliteDatabase(openOpenClawStateDatabase({ env }).db);
|
||||
const queueName = JSON.stringify(["test", "a"]);
|
||||
executeSqliteQuerySync(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// Claw doctor diagnostics project the lifecycle ownership ledger into health findings.
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import { coerceErrorMessage } from "@openclaw/normalization-core";
|
||||
import { listConfiguredMcpServers } from "../config/mcp-config.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveDefaultCronStaggerMs } from "../cron/stagger.js";
|
||||
import type { CronJob } from "../cron/types.js";
|
||||
import type { HealthFinding } from "../flows/health-checks.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import {
|
||||
openExistingOpenClawStateDatabaseReadOnly,
|
||||
|
|
@ -278,7 +279,7 @@ function orphanedAgentIds(options: OpenClawStateDatabaseOptions): string[] {
|
|||
return [...referenced].filter((agentId) => !installed.has(agentId)).toSorted();
|
||||
}
|
||||
|
||||
function hasClawMcpServerRefs(db: DatabaseSync): boolean {
|
||||
function hasClawMcpServerRefs(db: EngineConnection): boolean {
|
||||
return (
|
||||
tableExists(db, "claw_mcp_server_refs") &&
|
||||
Boolean(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Selectable } from "kysely";
|
||||
import { getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { coerceRequiredSqliteNumber as sqliteNumber } from "../infra/sqlite-number.js";
|
||||
import type { DB } from "../state/openclaw-state-db.generated.js";
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ export type PersistedClawMcpServerRef = {
|
|||
export type McpDatabase = Pick<DB, "claw_mcp_server_refs">;
|
||||
export type McpRefRow = Selectable<DB["claw_mcp_server_refs"]>;
|
||||
|
||||
export function selectMcpRefs(db: DatabaseSync) {
|
||||
export function selectMcpRefs(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<McpDatabase>(db)
|
||||
.selectFrom("claw_mcp_server_refs")
|
||||
.select([
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import type { DB } from "../state/openclaw-state-db.generated.js";
|
||||
import type {
|
||||
ClawPackageRefStatus,
|
||||
|
|
@ -7,7 +7,7 @@ import type {
|
|||
} from "./package-extension-provenance.js";
|
||||
|
||||
export function updateClawPackageRefStatusInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
ref: PersistedClawPackageRef,
|
||||
status: ClawPackageRefStatus,
|
||||
nowMs: number,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQueryTakeFirstSync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { isPostgresSqlConnection } from "../infra/sql-connection.js";
|
||||
import { coerceRequiredSqliteNumber as sqliteNumber } from "../infra/sqlite-number.js";
|
||||
import type { DB } from "../state/openclaw-state-db.generated.js";
|
||||
import { CLAW_SCHEMA_VERSION } from "./manifest-contract.js";
|
||||
|
|
@ -67,7 +68,7 @@ function clawInstallRecordFromRow(row: ClawInstallRow): PersistedClawInstall {
|
|||
};
|
||||
}
|
||||
|
||||
function selectClawInstallRow(db: DatabaseSync, agentId: string): ClawInstallRow | undefined {
|
||||
function selectClawInstallRow(db: EngineConnection, agentId: string): ClawInstallRow | undefined {
|
||||
return (
|
||||
db /* sqlite-allow-raw: this Claw prototype state-table read is scoped to one owned row. */
|
||||
.prepare(
|
||||
|
|
@ -85,14 +86,14 @@ function selectClawInstallRow(db: DatabaseSync, agentId: string): ClawInstallRow
|
|||
}
|
||||
|
||||
export function readClawInstallRecordFromDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
agentId: string,
|
||||
): PersistedClawInstall | undefined {
|
||||
const row = selectClawInstallRow(db, agentId);
|
||||
return row ? clawInstallRecordFromRow(row) : undefined;
|
||||
}
|
||||
|
||||
export function readClawInstallRecordsInDatabase(db: DatabaseSync): PersistedClawInstall[] {
|
||||
export function readClawInstallRecordsInDatabase(db: EngineConnection): PersistedClawInstall[] {
|
||||
const rows =
|
||||
db /* sqlite-allow-raw: read-only Claw install inventory ordered by stable agent id. */
|
||||
.prepare(
|
||||
|
|
@ -121,9 +122,12 @@ export type ClawPackageRefQuery = {
|
|||
};
|
||||
|
||||
export function readClawPackageRefsInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
options: ClawPackageRefQuery = {},
|
||||
): PersistedClawPackageRef[] {
|
||||
if (isPostgresSqlConnection(db)) {
|
||||
throw new Error("Claw package provenance requires SQLite named-parameter support.");
|
||||
}
|
||||
const conditions: string[] = [];
|
||||
const params: Record<string, string> = {};
|
||||
for (const [column, value] of [
|
||||
|
|
@ -159,7 +163,7 @@ export function readClawPackageRefsInDatabase(
|
|||
}
|
||||
|
||||
export function readClawOrphanWorkspaceInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
agentId: string,
|
||||
): ClawOrphanWorkspace | undefined {
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import type { DB } from "../state/openclaw-state-db.generated.js";
|
||||
|
||||
|
|
@ -9,7 +9,9 @@ export type ClawInstallSchemaVersionRow = {
|
|||
agentConfigDigest: string;
|
||||
};
|
||||
|
||||
export function readClawInstallSchemaVersionRows(db: DatabaseSync): ClawInstallSchemaVersionRow[] {
|
||||
export function readClawInstallSchemaVersionRows(
|
||||
db: EngineConnection,
|
||||
): ClawInstallSchemaVersionRow[] {
|
||||
if (!tableExists(db, "claw_installs")) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
|
||||
const CLAW_SECONDARY_REFERENCE_TABLES = [
|
||||
|
|
@ -7,7 +7,7 @@ const CLAW_SECONDARY_REFERENCE_TABLES = [
|
|||
"claw_cron_refs",
|
||||
] as const;
|
||||
|
||||
export function readClawSecondaryReferenceTables(db: DatabaseSync, agentId: string): string[] {
|
||||
export function readClawSecondaryReferenceTables(db: EngineConnection, agentId: string): string[] {
|
||||
return CLAW_SECONDARY_REFERENCE_TABLES.filter((table) => {
|
||||
if (!tableExists(db, table)) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { stableStringify } from "@openclaw/normalization-core";
|
||||
import { assertAgentDeletionAllowsMutation } from "../agents/agent-lifecycle-registry.js";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import { isPostgresSqlConnection } from "../infra/sql-connection.js";
|
||||
import type { DB } from "../state/openclaw-state-db.generated.js";
|
||||
import {
|
||||
openOpenClawStateDatabase,
|
||||
|
|
@ -305,6 +306,9 @@ export function persistClawPackageRef(
|
|||
updatedAtMs: nowMs,
|
||||
};
|
||||
runOpenClawStateWriteTransaction(({ db }) => {
|
||||
if (isPostgresSqlConnection(db)) {
|
||||
throw new Error("Claw package provenance requires SQLite named-parameter support.");
|
||||
}
|
||||
const existing = db /* sqlite-allow-raw: exact owned package-ref replay lookup. */
|
||||
.prepare(
|
||||
`SELECT schema_version, agent_id, claw_name, package_kind, package_source,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Creates Claw-owned bootstrap and supporting files inside the new agent workspace.
|
||||
import { realpath } from "node:fs/promises";
|
||||
import { resolve, sep } from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { coerceErrorMessage } from "@openclaw/normalization-core/error-coercion";
|
||||
import type { Selectable } from "kysely";
|
||||
import { root as fsSafeRoot, FsSafeError, type Root } from "../infra/fs-safe.js";
|
||||
|
|
@ -11,6 +10,7 @@ import {
|
|||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { coerceRequiredSqliteNumber as sqliteNumber } from "../infra/sqlite-number.js";
|
||||
import { tableExists } from "../state/openclaw-state-db-schema-helpers.js";
|
||||
import type { DB } from "../state/openclaw-state-db.generated.js";
|
||||
|
|
@ -56,7 +56,7 @@ class ClawWorkspaceSourceAliasError extends Error {}
|
|||
type WorkspaceDatabase = Pick<DB, "claw_workspace_files">;
|
||||
type WorkspaceFileRow = Selectable<DB["claw_workspace_files"]>;
|
||||
|
||||
function selectWorkspaceFiles(db: DatabaseSync) {
|
||||
function selectWorkspaceFiles(db: EngineConnection) {
|
||||
return getNodeSqliteKysely<WorkspaceDatabase>(db)
|
||||
.selectFrom("claw_workspace_files")
|
||||
.select([
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
initializeNativeOpenClawStateDatabase,
|
||||
} from "../state/openclaw-state-db.js";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import { requireNativeSqliteDatabase } from "../test-utils/native-sqlite.js";
|
||||
import { ensureCliExecutionBootstrap } from "./command-execution-startup.js";
|
||||
import { resolveCliStartupPolicy } from "./command-startup-policy.js";
|
||||
import { testApi as configGuardTestApi } from "./program/config-guard.js";
|
||||
|
|
@ -140,7 +141,7 @@ function readGatewayState() {
|
|||
return withExistingOpenClawStateDatabaseArtifactPreservingReadOnly(({ db }) => {
|
||||
const query = getNodeSqliteKysely<DB>(db);
|
||||
return {
|
||||
version: readSqliteUserVersion(db),
|
||||
version: readSqliteUserVersion(requireNativeSqliteDatabase(db)),
|
||||
// Includes the installed plugin index and Doctor's state/startup checkpoints.
|
||||
machine: executeSqliteQuerySync(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { EventEmitter } from "node:events";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createDeferred } from "../../test/helpers/promise.js";
|
||||
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
|
||||
|
|
@ -15,6 +14,7 @@ import {
|
|||
} from "../config/sessions/session-accessor.js";
|
||||
import * as embeddedStateLock from "../infra/embedded-state-lock.js";
|
||||
import { acquireGatewayLock } from "../infra/gateway-lock.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { registerSqliteAuditRecordAsync } from "../infra/sqlite-audit-record-store.async.js";
|
||||
import { createSqliteAuditRecordStore } from "../infra/sqlite-audit-record-store.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
|
@ -180,7 +180,7 @@ it("closes temporary databases before removal and preserves independent handles"
|
|||
let runStateDir: string | undefined;
|
||||
let agentPath: string | undefined;
|
||||
let statePath: string | undefined;
|
||||
const handles: DatabaseSync[] = [];
|
||||
const handles: EngineConnection[] = [];
|
||||
const remove = fs.rm.bind(fs);
|
||||
const removed = vi.spyOn(fs, "rm").mockImplementation(async (pathname, options) => {
|
||||
if (pathname === runStateDir) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import { randomUUID } from "node:crypto";
|
|||
import path from "node:path";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { requireNativeDatabaseFile } from "../infra/database-engine/physical-facts.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { writeTextAtomic } from "../infra/json-files.js";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import { isPostgresSqlConnection } from "../infra/sql-connection.js";
|
||||
import { isSqliteCorruptionError } from "../infra/sqlite-error-diagnostics.js";
|
||||
import { SQLITE_SIDECAR_SUFFIXES } from "../infra/sqlite-files.js";
|
||||
import { invalidateSqliteSchemaFacts } from "../infra/sqlite-schema-facts.js";
|
||||
|
|
@ -35,6 +37,9 @@ import { isReservedSystemAgentId } from "../system-agent/agent-id.js";
|
|||
type JournalDatabase = Pick<OpenClawStateDatabase, "db" | "path">;
|
||||
|
||||
function readRecoverySource(database: JournalDatabase) {
|
||||
if (isPostgresSqlConnection(database.db)) {
|
||||
requireNativeDatabaseFile("postgres", "repair");
|
||||
}
|
||||
// Recovery must inspect the damaged catalog instead of the runtime's admitted shape.
|
||||
invalidateSqliteSchemaFacts(database.db);
|
||||
const held = tableExists(database.db, "migration_sources")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from "node:fs";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion";
|
||||
import type { SqlConnection } from "../infra/sql-connection.js";
|
||||
import { SqliteSnapshotCleanupError } from "../infra/sqlite-readonly-location-cleanup.js";
|
||||
import { prepareSqliteReadOnlyLocationSync } from "../infra/sqlite-snapshot-source.js";
|
||||
import { readRegisteredAgentDatabases } from "../state/openclaw-agent-db-registry-listing.js";
|
||||
|
|
@ -50,7 +50,7 @@ function readSqliteBloatStats(pathname: string): SqliteBloatStats | null {
|
|||
}
|
||||
}
|
||||
|
||||
function readPragmaNumber(db: DatabaseSync, pragma: string): number | null {
|
||||
function readPragmaNumber(db: SqlConnection, pragma: string): number | null {
|
||||
const row = db.prepare(`PRAGMA ${pragma}`).get();
|
||||
return asFiniteNumber(row?.[pragma]) ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/** Doctor-owned migration from heartbeat scratch `tasks:` blocks into cron jobs. */
|
||||
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
import { note } from "../../packages/terminal-core/src/note.js";
|
||||
|
|
@ -31,6 +30,7 @@ import { formatErrorMessage as errorMessage } from "../infra/errors.js";
|
|||
import { resolveHeartbeatAgents, resolveHeartbeatIntervalMs } from "../infra/heartbeat-config.js";
|
||||
import { resolveHeartbeatSession } from "../infra/heartbeat-runner-session.js";
|
||||
import { executeSqliteQuerySync } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import {
|
||||
openOpenClawStateDatabase,
|
||||
runOpenClawStateWriteTransaction,
|
||||
|
|
@ -247,7 +247,7 @@ async function loadCronPlanningSnapshot(
|
|||
};
|
||||
}
|
||||
|
||||
function readScratchRevision(db: DatabaseSync, storeKey: string, jobId: string): number {
|
||||
function readScratchRevision(db: EngineConnection, storeKey: string, jobId: string): number {
|
||||
return (
|
||||
executeSqliteQuerySync(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths
|
|||
import * as leaseAcquisition from "../state/openclaw-state-lease-acquisition.js";
|
||||
import { withOpenClawStateLease } from "../state/openclaw-state-lease.js";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import { requireNativeSqliteDatabase } from "../test-utils/native-sqlite.js";
|
||||
import { withOpenClawTestState } from "../test-utils/openclaw-test-state.js";
|
||||
import { collectDoctorFindings } from "./doctor-lint-runner.js";
|
||||
import { runDoctorLintCli } from "./doctor-lint.js";
|
||||
|
|
@ -485,7 +486,7 @@ describe("doctor lint state isolation", () => {
|
|||
observed.push(
|
||||
withExistingOpenClawStateDatabaseReadOnly(
|
||||
({ db }) => {
|
||||
locations.push(db.location()!);
|
||||
locations.push(requireNativeSqliteDatabase(db).location()!);
|
||||
return db.prepare("SELECT value FROM marker").all();
|
||||
},
|
||||
{ env: ctx.env },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { sql } from "kysely";
|
||||
import {
|
||||
|
|
@ -6,6 +5,7 @@ import {
|
|||
executeSqliteQueryTakeFirstSync,
|
||||
getNodeSqliteKysely,
|
||||
} from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import { withExistingOpenClawStateDatabaseReadOnly } from "../state/openclaw-state-db-readonly.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ type SharedStateSchemaDatabase = {
|
|||
|
||||
type DynamicSharedStateDatabase = Record<string, Record<string, unknown>>;
|
||||
|
||||
function listTuiLastSessionStateRows(database: DatabaseSync) {
|
||||
function listTuiLastSessionStateRows(database: EngineConnection) {
|
||||
const db =
|
||||
getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "config_machine_state">>(database);
|
||||
return executeSqliteQuerySync(
|
||||
|
|
@ -43,7 +43,9 @@ function sqliteSchemaIdentifier(value: string) {
|
|||
}
|
||||
|
||||
// Shared state has many owner-specific session-key columns; schema discovery keeps this migration complete.
|
||||
function listSharedStateSessionKeyColumns(database: DatabaseSync): SharedStateSessionKeyColumn[] {
|
||||
function listSharedStateSessionKeyColumns(
|
||||
database: EngineConnection,
|
||||
): SharedStateSessionKeyColumn[] {
|
||||
const db = getNodeSqliteKysely<SharedStateSchemaDatabase>(database);
|
||||
const tables = executeSqliteQuerySync(
|
||||
database,
|
||||
|
|
@ -69,7 +71,7 @@ function listSharedStateSessionKeyColumns(database: DatabaseSync): SharedStateSe
|
|||
});
|
||||
}
|
||||
|
||||
export function collectSharedStateSessionKeys(database: DatabaseSync): Set<string> {
|
||||
export function collectSharedStateSessionKeys(database: EngineConnection): Set<string> {
|
||||
const db = getNodeSqliteKysely<DynamicSharedStateDatabase>(database);
|
||||
const keys = new Set<string>();
|
||||
for (const { table, column, json } of listSharedStateSessionKeyColumns(database)) {
|
||||
|
|
@ -103,7 +105,7 @@ export function collectSharedStateSessionKeys(database: DatabaseSync): Set<strin
|
|||
}
|
||||
|
||||
export function rewriteSharedStateSessionKeys(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
renames: ReadonlyMap<string, string>,
|
||||
): void {
|
||||
const db = getNodeSqliteKysely<DynamicSharedStateDatabase>(database);
|
||||
|
|
@ -181,7 +183,7 @@ function collectJsonStringValues(value: unknown, values: Set<string>): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function readRepairJournal(database: DatabaseSync): ReservedKeyRename[] {
|
||||
export function readRepairJournal(database: EngineConnection): ReservedKeyRename[] {
|
||||
const db = getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "state_leases">>(database);
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
database,
|
||||
|
|
@ -215,7 +217,7 @@ export function readRepairJournalReadOnly(env: NodeJS.ProcessEnv): ReservedKeyRe
|
|||
}
|
||||
|
||||
export function writeRepairJournal(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
renames: readonly ReservedKeyRename[],
|
||||
): void {
|
||||
const now = Date.now();
|
||||
|
|
@ -244,7 +246,7 @@ export function writeRepairJournal(
|
|||
);
|
||||
}
|
||||
|
||||
export function deleteRepairJournal(database: DatabaseSync): void {
|
||||
export function deleteRepairJournal(database: EngineConnection): void {
|
||||
const db = getNodeSqliteKysely<Pick<OpenClawStateKyselyDatabase, "state_leases">>(database);
|
||||
executeSqliteQuerySync(
|
||||
database,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import { resolveCanonicalWorkspacePath } from "../agents/workspace-state-identit
|
|||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { sha256Hex } from "../infra/crypto-digest.js";
|
||||
import { requireNativeDatabaseFile } from "../infra/database-engine/physical-facts.js";
|
||||
import { isMissingPathError } from "../infra/errors.js";
|
||||
import { pathExists, root } from "../infra/fs-safe.js";
|
||||
import { acquireGatewayLock } from "../infra/gateway-lock.js";
|
||||
import { isPathInside } from "../infra/path-guards.js";
|
||||
import { isPostgresSqlConnection } from "../infra/sql-connection.js";
|
||||
import type { MigrationMessages } from "../infra/state-migrations.types.js";
|
||||
import { isUpdateRehearsalReadOnlyPath } from "../infra/update-rehearsal-paths.js";
|
||||
import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js";
|
||||
|
|
@ -544,6 +546,9 @@ async function retireProposals(params: {
|
|||
assertCurrent();
|
||||
const dropped = runOpenClawStateWriteTransaction(
|
||||
({ db }) => {
|
||||
if (isPostgresSqlConnection(db)) {
|
||||
requireNativeDatabaseFile("postgres", "repair");
|
||||
}
|
||||
assertCurrent();
|
||||
// Reread inside the transaction: a proposal written since planning keeps the tables.
|
||||
const pending = tableExists(db, "skill_workshop_proposals")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Doctor cron storage repair mechanics for quarantine, payloads, and Codex refs.
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import {
|
||||
|
|
@ -29,6 +29,7 @@ import {
|
|||
collectErrorGraphCandidates,
|
||||
formatErrorMessage as errorMessage,
|
||||
} from "../../../infra/errors.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import { listRetiredCronStateFiles } from "../../../infra/state-migrations.retired-cron-files.js";
|
||||
import {
|
||||
assertNoRetiredStateFiles,
|
||||
|
|
@ -373,7 +374,7 @@ export async function applyLegacyCronStoreRepair(params: {
|
|||
source.admission.assertCurrent();
|
||||
source.maintenanceScope?.assertDatabaseAccess(source.admission.databasePath);
|
||||
};
|
||||
const assertSnapshotCurrent = (db: DatabaseSync): undefined => {
|
||||
const assertSnapshotCurrent = (db: EngineConnection): undefined => {
|
||||
assertCurrent();
|
||||
if (
|
||||
!isDeepStrictEqual(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { safeParseJsonRecord } from "@openclaw/normalization-core/json-coercion";
|
||||
import { getInvalidPersistedCronJobReason } from "../../../cron/persisted-shape.js";
|
||||
import { getCronStoreKysely } from "../../../cron/store/schema.js";
|
||||
import { executeSqliteQuerySync } from "../../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import type { PluginDoctorCronJob } from "../../../plugins/doctor-contract-module.js";
|
||||
import { tableExists } from "../../../state/openclaw-state-db-schema-helpers.js";
|
||||
|
||||
/** SQL ownership is independently authoritative when legacy JSON omits it. */
|
||||
export function inspectCronOwnerRowsForDoctor(db: DatabaseSync, storeKey: string) {
|
||||
export function inspectCronOwnerRowsForDoctor(db: EngineConnection, storeKey: string) {
|
||||
if (!tableExists(db, "cron_jobs")) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ export function inspectCronOwnerRowsForDoctor(db: DatabaseSync, storeKey: string
|
|||
}
|
||||
|
||||
/** Raw inspection deliberately bypasses runtime loading and its repair/filter policies. */
|
||||
export function inspectCronRowsForDoctor(db: DatabaseSync): PluginDoctorCronJob[] {
|
||||
export function inspectCronRowsForDoctor(db: EngineConnection): PluginDoctorCronJob[] {
|
||||
if (!tableExists(db, "cron_jobs")) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { randomUUID } from "node:crypto";
|
||||
import { existsSync } from "node:fs";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { safeParseJsonRecord } from "@openclaw/normalization-core/json-coercion";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
|
|
@ -17,6 +16,7 @@ import {
|
|||
} from "../../../cron/store/row-codec.js";
|
||||
import { getCronStoreKysely } from "../../../cron/store/schema.js";
|
||||
import { executeSqliteQuerySync } from "../../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../../infra/sql-connection.js";
|
||||
import { deferSqlitePostCommitPublication } from "../../../infra/sqlite-post-commit.js";
|
||||
import { createVerifiedSqliteSnapshot } from "../../../infra/sqlite-snapshot.js";
|
||||
import type { PluginDoctorRepairAuthority } from "../../../infra/state-migrations.types.js";
|
||||
|
|
@ -210,7 +210,7 @@ export async function backupCronStoreForDoctor(
|
|||
scope: DoctorCronScope,
|
||||
repair: {
|
||||
assertCurrent: () => void;
|
||||
assertRowsUnchanged: (db: DatabaseSync) => void;
|
||||
assertRowsUnchanged: (db: EngineConnection) => void;
|
||||
},
|
||||
): Promise<string> {
|
||||
repair.assertCurrent();
|
||||
|
|
@ -233,8 +233,8 @@ async function commitCronDoctorRepair(
|
|||
scope: DoctorCronScope,
|
||||
authority: PluginDoctorRepairAuthority,
|
||||
repair: {
|
||||
assertRowsUnchanged: (db: DatabaseSync) => void;
|
||||
write: (db: DatabaseSync) => void;
|
||||
assertRowsUnchanged: (db: EngineConnection) => void;
|
||||
write: (db: EngineConnection) => void;
|
||||
},
|
||||
): Promise<string> {
|
||||
const backupPath = await backupCronStoreForDoctor(scope, {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import {
|
||||
createSqliteAuditRecordKernel,
|
||||
type PreparedSqliteAuditRecord,
|
||||
|
|
@ -14,7 +14,7 @@ export type ConfigSnapshotAuditRecord = {
|
|||
};
|
||||
|
||||
export function readConfigSnapshotAuditRecordInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
): ConfigSnapshotAuditRecord | null {
|
||||
return (
|
||||
createSqliteAuditRecordKernel<ConfigSnapshotAuditRecord>(database, {
|
||||
|
|
@ -27,7 +27,7 @@ export function readConfigSnapshotAuditRecordInDatabase(
|
|||
}
|
||||
|
||||
export function upsertConfigSnapshotAuditRecordInDatabase(
|
||||
database: DatabaseSync,
|
||||
database: EngineConnection,
|
||||
input: { record: PreparedSqliteAuditRecord | null; expectedPayloadJson?: string | null },
|
||||
): boolean {
|
||||
const store = createSqliteAuditRecordKernel(database, {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { safeParseJson } from "@openclaw/normalization-core/json-coercion";
|
||||
import type { Insertable } from "kysely";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../infra/sql-connection.js";
|
||||
import type { DB } from "../state/openclaw-state-db.generated.js";
|
||||
import type { WorkerOperationHandlers } from "../state/worker-operation-registry.js";
|
||||
import type {
|
||||
|
|
@ -31,7 +31,7 @@ function stringifyFingerprint(value: ConfigHealthFingerprint | null | undefined)
|
|||
return value ? JSON.stringify(value) : null;
|
||||
}
|
||||
|
||||
function selectConfigHealthRows(db: DatabaseSync) {
|
||||
function selectConfigHealthRows(db: EngineConnection) {
|
||||
const query = getNodeSqliteKysely<ConfigHealthDatabase>(db)
|
||||
.selectFrom("config_health_entries")
|
||||
.select([
|
||||
|
|
@ -61,7 +61,7 @@ function decodeConfigHealthRows(
|
|||
};
|
||||
}
|
||||
|
||||
export function readConfigHealthStateInDatabase(db: DatabaseSync): ConfigHealthState {
|
||||
export function readConfigHealthStateInDatabase(db: EngineConnection): ConfigHealthState {
|
||||
return decodeConfigHealthRows(selectConfigHealthRows(db));
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ export const configHealthReadOperations = {
|
|||
},
|
||||
};
|
||||
},
|
||||
} satisfies WorkerOperationHandlers<DatabaseSync>;
|
||||
} satisfies WorkerOperationHandlers<EngineConnection>;
|
||||
|
||||
/** Omitted fields remain untouched; explicit undefined and null clear their stored value. */
|
||||
export function prepareConfigHealthPatch(changes: ConfigHealthEntryChanges): ConfigHealthPatch {
|
||||
|
|
@ -105,7 +105,7 @@ export function prepareConfigHealthPatch(changes: ConfigHealthEntryChanges): Con
|
|||
|
||||
/** Compare the original read and merge in one write transaction, preserving untouched JSON. */
|
||||
export function patchConfigHealthEntryInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
configPath: string,
|
||||
patch: ConfigHealthPatch,
|
||||
expected: ConfigHealthEntryBasis | null | undefined,
|
||||
|
|
@ -142,7 +142,7 @@ export function patchConfigHealthEntryInDatabase(
|
|||
|
||||
/** The caller owns the transaction; omitted fields and sibling paths remain unchanged. */
|
||||
export function writeConfigHealthPatchInDatabase(
|
||||
db: DatabaseSync,
|
||||
db: EngineConnection,
|
||||
configPath: string,
|
||||
patch: ConfigHealthPatch,
|
||||
updatedAtMs: number,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { sha256Hex } from "@openclaw/normalization-core/node-crypto";
|
||||
import type { Selectable } from "kysely";
|
||||
import {
|
||||
|
|
@ -81,7 +82,7 @@ function mapRow(row: ConversationDeliveryRow): ConversationDeliveryRecord {
|
|||
};
|
||||
}
|
||||
|
||||
const operationQuery = createSqliteQueryCache((database) => {
|
||||
const operationQuery = createSqliteQueryCache((database: DatabaseSync) => {
|
||||
const db = getSessionKysely(database);
|
||||
return prepareSqliteQuerySync<string, ConversationDeliveryRow>(database, (parameter) =>
|
||||
// Session pruning removes only session_conversations. The canonical
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { executeSqliteQueryTakeFirstSync, getNodeSqliteKysely } from "../../infra/kysely-sync.js";
|
||||
import type { EngineConnection } from "../../infra/sql-connection.js";
|
||||
import type { DB } from "../../state/openclaw-state-db.generated.js";
|
||||
|
||||
export const legacySessionMigrationReadOperations = {
|
||||
"legacySessionMigration.readLedger": (_input: undefined, database: DatabaseSync) => ({
|
||||
"legacySessionMigration.readLedger": (_input: undefined, database: EngineConnection) => ({
|
||||
type: "legacySessionMigration.readLedger" as const,
|
||||
row: executeSqliteQueryTakeFirstSync(
|
||||
database,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ function cacheSessionEntryQuery<Row extends ResolvedSessionEntryRow["row"]>(
|
|||
}
|
||||
|
||||
// Each query retains only its last exact row at the connection's admitted revision.
|
||||
export const getExactSessionEntryQueries = createSqliteQueryCache((database) => {
|
||||
export const getExactSessionEntryQueries = createSqliteQueryCache((database: DatabaseSync) => {
|
||||
const rowQueries = new Map<string, (key: string) => ResolvedSessionEntryRow["row"] | undefined>();
|
||||
const canonicalQueries = new Map<
|
||||
string,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { sql } from "kysely";
|
||||
import { createSqliteQueryCache, prepareSqliteQuerySync } from "../../infra/kysely-sync.js";
|
||||
import { getSessionKysely } from "./session-accessor.sqlite-scope.js";
|
||||
|
|
@ -9,7 +10,7 @@ type SessionWindowWriteRow = Omit<ReturnType<typeof bindSessionRoot>, "primary_c
|
|||
};
|
||||
|
||||
// Cache fixed SQL shapes only; every write binds fresh rows through the normal executor.
|
||||
export const getSessionEntryWriteQueries = createSqliteQueryCache((database) => {
|
||||
export const getSessionEntryWriteQueries = createSqliteQueryCache((database: DatabaseSync) => {
|
||||
const db = getSessionKysely(database);
|
||||
const window = (retainOwner: boolean) =>
|
||||
prepareSqliteQuerySync<SessionWindowWriteRow>(database, (parameter) =>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue