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:
Peter Steinberger 2026-10-13 22:01:06 -07:00 • committed by GitHub
parent 1a55095dc3
commit 1edc42ce55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
472 changed files with 2981 additions and 2046 deletions

View file

@ -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");
}

View file

@ -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"));

View file

@ -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 };
}

View file

@ -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: {

View file

@ -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

View file

@ -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",

View file

@ -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++) {

View file

@ -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")

View file

@ -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;
}

View file

@ -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,

View file

@ -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 {

View file

@ -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);

View file

@ -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;

View file

@ -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(

View file

@ -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"));

View file

@ -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");

View file

@ -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))) {

View file

@ -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 {

View file

@ -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);

View file

@ -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 {

View file

@ -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;

View file

@ -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;
}

View file

@ -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 {

View file

@ -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,

View file

@ -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 = ?",

View file

@ -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":

View file

@ -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,

View file

@ -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[] {

View file

@ -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);

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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()!);

View file

@ -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)

View file

@ -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 },

View file

@ -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,
},
}),

View file

@ -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(

View file

@ -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");

View file

@ -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);

View file

@ -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)
: [

View file

@ -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") {

View file

@ -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> {

View file

@ -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,

View file

@ -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;

View file

@ -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[] = [],

View file

@ -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 }

View file

@ -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 {

View file

@ -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<

View file

@ -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(

View file

@ -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),
);

View file

@ -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(

View file

@ -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,

View file

@ -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")

View file

@ -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 {

View file

@ -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,

View file

@ -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 }>(

View file

@ -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 ?? {};

View file

@ -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

View file

@ -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;

View file

@ -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(

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

@ -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 {

View file

@ -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. */

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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) {

View file

@ -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 =

View file

@ -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") {

View file

@ -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(

View file

@ -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,

View file

@ -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(

View file

@ -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([

View file

@ -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,

View file

@ -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(

View file

@ -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 [];
}

View file

@ -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;

View file

@ -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,

View file

@ -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([

View file

@ -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,

View file

@ -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) {

View file

@ -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")

View file

@ -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;
}

View file

@ -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,

View file

@ -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 },

View file

@ -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,

View file

@ -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")

View file

@ -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(

View file

@ -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 [];
}

View file

@ -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, {

View file

@ -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, {

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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