UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

509 lines (508 loc) • 27.8 kB
import { W as sqlitePrimaryResultCode, i as readSqliteUserVersion, l as executeWithCachedStatement, n as createNewerSqliteSchemaVersionError } from "./sqlite-user-version-C7PVO8Wf.mjs"; import { A as quoteSqliteIdentifier, D as normalizeSchemaSql, E as findSqlClosingParenthesis, I as createSqliteQueryCache, M as readTableConstraintKeyword, N as splitSqlList, O as normalizeSqlIdentifier, P as runSqlitePinnedReadSnapshotSync, T as findSqlCharacter, V as prepareSqliteQuerySync, c as openNodeSqliteDatabase, j as readSqlToken, k as normalizeSqlWhitespace, n as normalizeOpenClawStateSchemaReadError, p as getAdmittedSqliteSchemaFacts, z as getNodeSqliteKysely } from "./openclaw-state-db-schema-migration-required-DtltEO1E.mjs"; //#region src/infra/sqlite-schema-contract-assembly.ts function createSqliteTableContract(tableName, table, tableList, indexes, triggers) { const normalizedTriggers = triggers.map((trigger) => ({ name: trigger.name, sql: normalizeSchemaSql(trigger.sql) })); const normalizedTableSql = table.sql?.startsWith("CREATE TABLE ") ? null : normalizeSchemaSql(table.sql); const isVirtualTable = normalizedTableSql !== null && /^CREATE VIRTUAL TABLE /iu.test(normalizedTableSql); return { definition: isVirtualTable ? null : parseSqliteTableDefinition(table.sql, tableName), indexes, strict: tableList.strict, triggers: normalizedTriggers, virtualTableSql: isVirtualTable ? normalizedTableSql : null, withoutRowid: tableList.wr }; } function createSqliteIndexContract(index, schemaSql, rows) { const terms = rows.map(({ cid, coll, desc, key, name, seqno }) => ({ coll, desc, key, kind: sqliteIndexTermKind(cid), name, seqno })); return { name: index.name.startsWith("sqlite_autoindex_") ? null : index.name, origin: index.origin, partial: index.partial, sql: normalizeSchemaSql(schemaSql), terms, unique: index.unique }; } function sqliteIndexTermKind(cid) { return cid === -2 ? "expression" : cid === -1 ? "rowid" : "column"; } function parseSqliteTableDefinition(sql, tableName) { if (sql === null) throw new Error(`Could not inspect SQLite table definition for ${tableName}.`); const open = findSqlCharacter(sql, "("); if (open === -1) throw new Error(`SQLite table ${tableName} has no column definition.`); const close = findSqlClosingParenthesis(sql, open); const columns = /* @__PURE__ */ new Map(); const constraints = []; for (const rawDefinition of splitSqlList(sql.slice(open + 1, close))) { const definition = normalizeSqlWhitespace(rawDefinition); if (!definition) continue; const token = readSqlToken(definition, 0); if (!token) throw new Error(`SQLite table ${tableName} contains an unreadable definition.`); if (readTableConstraintKeyword(definition, token)) { constraints.push(definition); continue; } const columnName = normalizeSqlIdentifier(token.raw); if (columns.has(columnName)) throw new Error(`SQLite table ${tableName} contains duplicate column ${columnName}.`); columns.set(columnName, definition); } return { columns: new Map([...columns].toSorted(([left], [right]) => left.localeCompare(right))), constraints: constraints.toSorted() }; } //#endregion //#region src/infra/sqlite-schema-issues.ts /** A completed inspection proved schema drift; native read failures retain their own type. */ var SqliteSchemaMismatchError = class extends Error { constructor(..._args) { super(..._args); this.name = "SqliteSchemaMismatchError"; } }; function defaultIssueMessage(code, objectName) { const tableName = objectName.split(".", 1)[0]; switch (code) { case "missing-table": return `missing table ${objectName}`; case "missing-column": case "unexpected-column": case "column-definition-drift": return `column definitions differ for ${tableName}`; case "table-constraint-drift": return `table constraints differ for ${objectName}`; case "table-definition-drift": return `table definition differs for ${objectName}`; case "missing-or-drifted-index": return `missing or drifted index ${objectName}`; case "unexpected-unique-index": return `unexpected unique index ${objectName}`; case "missing-or-drifted-trigger": return `missing or drifted trigger ${objectName}`; case "unexpected-trigger": return `unexpected trigger ${objectName}`; case "virtual-table-definition-drift": return `virtual table definition differs for ${objectName}`; case "table-options-drift": return `table options differ for ${objectName}`; } throw new Error("Unsupported SQLite schema issue code", { cause: code }); } function createSqliteSchemaIssue(code, objectName, message) { return { code, objectName, message: message ?? defaultIssueMessage(code, objectName) }; } function legacySqliteSchemaIssueMessages(issues) { const isColumnIssue = (issue) => issue.code === "column-definition-drift" || issue.code === "missing-column" || issue.code === "unexpected-column"; const columnIssueTables = new Set(issues.filter(isColumnIssue).map((issue) => issue.objectName.split(".", 1)[0])); return [...new Set(issues.filter((issue) => issue.code !== "table-constraint-drift" || !columnIssueTables.has(issue.objectName)).map((issue) => issue.message))]; } function throwSqliteSchemaMismatches(databaseLabel, mismatches) { const shown = mismatches.slice(0, 8); if (mismatches.length > shown.length) shown.push(`${mismatches.length - shown.length} additional mismatch(es)`); throw new SqliteSchemaMismatchError(`SQLite schema is incomplete or noncanonical for ${databaseLabel}: ${shown.join("; ")}; run openclaw doctor --fix to repair it.`); } //#endregion //#region src/infra/sqlite-schema-contract.ts const schemaContractCache = /* @__PURE__ */ new Map(); /** Reuse actual table facts only within one unchanged read transaction on this connection. */ function createSqliteTableContractReader(database) { let tables; return (tableName) => (tables ??= collectSqliteSchemaContract(database)).get(tableName); } /** * Require every object from one committed schema while allowing unrelated * tables and indexes that do not replace a canonical object. */ function assertSqliteSchemaContains(database, databaseLabel, schemaSql, compatibility = {}, readTable) { const issues = collectSqliteSchemaIssues(database, schemaSql, compatibility, readTable); if (issues.length > 0) throwSqliteSchemaMismatches(databaseLabel, legacySqliteSchemaIssueMessages(issues)); } /** Collect stable, machine-readable differences from one committed schema. */ function collectSqliteSchemaIssues(database, schemaSql, compatibility = {}, readTable) { return runSqlitePinnedReadSnapshotSync(database, () => collectSqliteSchemaIssuesInSnapshot(database, schemaSql, compatibility, readTable)); } function collectSqliteSchemaIssuesInSnapshot(database, schemaSql, compatibility, readTable) { const expected = getSqliteSchemaContract(schemaSql); const readActualTable = readTable ?? createSqliteTableContractReader(database); const allowedMissingTables = new Set(compatibility.allowedMissingTables ?? []); const allowedMissingIndexes = new Set(compatibility.allowedMissingIndexes ?? []); const issues = []; const add = (code, objectName, message) => { issues.push(createSqliteSchemaIssue(code, objectName, message)); }; for (const [tableName, expectedTable] of expected) { const actualTable = readActualTable(tableName); if (!actualTable) { if (allowedMissingTables.has(tableName)) continue; add("missing-table", tableName); continue; } issues.push(...compareTableDefinitions(tableName, actualTable.definition, expectedTable.definition, compatibility, !allowedMissingTables.has(tableName))); const actualIndexFingerprints = new Set(actualTable.indexes.map((index) => JSON.stringify(index))); const expectedIndexFingerprints = /* @__PURE__ */ new Set(); for (const expectedIndex of expectedTable.indexes) { const fingerprint = JSON.stringify(expectedIndex); expectedIndexFingerprints.add(fingerprint); if (!actualIndexFingerprints.has(fingerprint)) { const objectName = expectedIndex.name ?? tableName; if (expectedIndex.name && allowedMissingIndexes.has(expectedIndex.name) && !database.prepare("SELECT 1 FROM main.sqlite_schema WHERE type = 'index' AND name = ? COLLATE NOCASE LIMIT 1").get(expectedIndex.name)) continue; add("missing-or-drifted-index", objectName, `missing or drifted index ${expectedIndex.name ?? `on ${tableName}`}`); } } for (const actualIndex of actualTable.indexes) if (actualIndex.unique === 1 && !expectedIndexFingerprints.has(JSON.stringify(actualIndex))) add("unexpected-unique-index", actualIndex.name ?? tableName, `unexpected unique index ${actualIndex.name ?? `on ${tableName}`}`); const optionalCanonicalTriggerGroups = collectOptionalCanonicalTriggerGroups(database, compatibility, tableName); const optionalCanonicalTriggers = optionalCanonicalTriggerGroups.flatMap((group) => group.triggers); const allowedMissingCanonicalTriggers = optionalCanonicalTriggerGroups.filter((group) => group.optional).flatMap((group) => group.triggers); for (const expectedTrigger of expectedTable.triggers) { if (allowedMissingCanonicalTriggers.some((canonicalTrigger) => canonicalTrigger.name === expectedTrigger.name)) continue; if (!containsTrigger(actualTable.triggers, expectedTrigger)) add("missing-or-drifted-trigger", expectedTrigger.name); } for (const triggerGroup of optionalCanonicalTriggerGroups) { const isPresent = actualTable.triggers.some((actualTrigger) => triggerGroup.triggers.some((canonicalTrigger) => actualTrigger.name === canonicalTrigger.name)); if (triggerGroup.optional && !isPresent) continue; for (const canonicalTrigger of triggerGroup.triggers) if (!containsTrigger(actualTable.triggers, canonicalTrigger)) add("missing-or-drifted-trigger", canonicalTrigger.name); } for (const actualTrigger of actualTable.triggers) if (!containsTrigger(expectedTable.triggers, actualTrigger) && !containsTrigger(optionalCanonicalTriggers, actualTrigger)) add("unexpected-trigger", actualTrigger.name); if (actualTable.virtualTableSql !== expectedTable.virtualTableSql) add("virtual-table-definition-drift", tableName); if (actualTable.strict !== expectedTable.strict || actualTable.withoutRowid !== expectedTable.withoutRowid) add("table-options-drift", tableName); } return issues; } /** Require stable canonical tables before a version-specific additive migration. */ function assertSqliteSchemaTablesPresent(database, databaseLabel, schemaSql, options = {}) { const allowedMissingTables = new Set(options.allowedMissingTables ?? []); const requiredTables = getCanonicalSqliteTableNames(schemaSql).filter((tableName) => !allowedMissingTables.has(tableName)); const missingTables = []; const batchSize = 500; for (let offset = 0; offset < requiredTables.length; offset += batchSize) { const tables = requiredTables.slice(offset, offset + batchSize); const expected = tables.map((_table, ordinal) => `(${ordinal}, ?)`).join(", "); const present = database.prepare(`WITH expected(ordinal, name) AS (VALUES ${expected}) SELECT ordinal FROM expected WHERE EXISTS ( SELECT 1 FROM main.sqlite_schema WHERE type = 'table' AND name = expected.name LIMIT 1 )`).all(...tables); const presentOrdinals = new Set(present.map((row) => row.ordinal)); for (const [ordinal, tableName] of tables.entries()) if (!presentOrdinals.has(ordinal)) missingTables.push(`missing table ${tableName}`); } if (missingTables.length > 0) throwSqliteSchemaMismatches(databaseLabel, missingTables); } /** Return every explicit named index owned by one committed schema. */ function getCanonicalSqliteNamedIndexContracts(schemaSql) { const schema = getSqliteSchemaContract(schemaSql); const indexes = []; for (const [tableName, table] of schema) for (const fingerprint of table.indexes) { if (fingerprint.name === null || fingerprint.sql === null || fingerprint.origin !== "c") continue; indexes.push({ definition: readCanonicalIndexDefinition(fingerprint), fingerprint, name: fingerprint.name, tableName, unique: fingerprint.unique === 1 }); } return indexes; } /** Return every table owned by one committed schema. */ function getCanonicalSqliteTableNames(schemaSql) { return [...getSqliteSchemaContract(schemaSql).keys()]; } /** Inspect one explicit main-schema index using the canonical schema fingerprint shape. */ function collectSqliteNamedIndexContract(database, indexName) { const row = database.prepare(` SELECT tbl_name FROM ( SELECT name, sql, tbl_name FROM main.sqlite_schema WHERE type = 'index' AND name = ? ) `).get(indexName); if (!row || typeof row.tbl_name !== "string") return; const index = database.prepare(`PRAGMA main.index_list(${quoteSqliteIdentifier(row.tbl_name)})`).all()?.find((candidate) => candidate.name === indexName); return index ? collectSqliteIndexContract(database, index) : void 0; } function collectOptionalCanonicalTriggerGroups(database, compatibility, tableName) { return (compatibility.optionalCanonicalTriggerGroups ?? []).filter((group) => group.tableName === tableName).map((group) => ({ optional: !group.optionalWhenTableMissing || !database.prepare("SELECT 1 FROM main.sqlite_schema WHERE type = 'table' AND name = ? LIMIT 1").get(group.optionalWhenTableMissing), triggers: group.triggers.map((trigger) => ({ name: trigger.name, sql: normalizeOptionalCanonicalTriggerSql(trigger.sql) })) })); } function normalizeOptionalCanonicalTriggerSql(sql) { return normalizeSchemaSql(sql)?.replace(/^(CREATE TRIGGER) main\./iu, "$1 ") ?? null; } function getSqliteSchemaContract(schemaSql) { let expected = schemaContractCache.get(schemaSql); if (!expected) { expected = buildSqliteSchemaContract(schemaSql); schemaContractCache.set(schemaSql, expected); } return expected; } function collectSqliteSchemaFacts(database) { const tableOptions = database.prepare("PRAGMA table_list").all(); if (tableOptions.some((row) => { const name = row.name.toLowerCase(); return name === "pragma_index_list" || name === "pragma_index_xinfo"; })) return; const indexes = database.prepare(` SELECT t.name AS table_name, i.seq AS index_seq, i.name, i.origin, i.partial, i."unique", d.sql FROM main.sqlite_schema AS t CROSS JOIN pragma_index_list(t.name, 'main') AS i LEFT JOIN main.sqlite_schema AS d ON d.type = 'index' AND d.name = i.name WHERE t.type = 'table' AND t.name NOT LIKE 'sqlite_%' `).all(); const terms = database.prepare(` SELECT t.name AS table_name, i.seq AS index_seq, x.seqno, x.cid, x.name, x."desc", x.coll, x."key" FROM main.sqlite_schema AS t CROSS JOIN pragma_index_list(t.name, 'main') AS i CROSS JOIN pragma_index_xinfo(i.name, 'main') AS x WHERE t.type = 'table' AND t.name NOT LIKE 'sqlite_%' ORDER BY t.name, i.seq, x.seqno `).all(); const triggers = database.prepare(` SELECT tbl_name, name, sql FROM main.sqlite_schema WHERE type = 'trigger' ORDER BY tbl_name, name `).all(); return { tableOptions, indexes: groupCanonicalRows(indexes, (row) => row.table_name), terms: groupCanonicalRows(terms, (row) => row.table_name), triggers: groupCanonicalRows(triggers, (row) => row.tbl_name) }; } function groupCanonicalRows(rows, key) { const groups = /* @__PURE__ */ new Map(); for (const row of rows) { const name = key(row); const group = groups.get(name) ?? []; group.push(row); groups.set(name, group); } return groups; } function buildSqliteSchemaContract(schemaSql) { const database = openNodeSqliteDatabase(":memory:"); try { database.exec(schemaSql); return collectSqliteSchemaContract(database); } finally { database.close(); } } function collectSqliteSchemaContract(database) { const rows = database.prepare(` SELECT name, sql FROM main.sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name `).all(); if (rows.length === 0) return /* @__PURE__ */ new Map(); const facts = collectSqliteSchemaFacts(database); if (!facts) return new Map(rows.map((table) => [table.name, collectSqliteTableContractFromRow(database, table.name, table)])); return new Map(rows.map((table) => { const tableList = facts.tableOptions.find((entry) => entry.schema === "main" && entry.name === table.name); if (!tableList) throw new Error(`Could not inspect SQLite table options for ${table.name}.`); const termsByIndex = groupCanonicalRows(facts.terms.get(table.name) ?? [], (term) => term.index_seq); const indexes = (facts.indexes.get(table.name) ?? []).map((index) => createSqliteIndexContract(index, index.sql, termsByIndex.get(index.index_seq) ?? [])).toSorted(compareJson); return [table.name, createSqliteTableContract(table.name, table, tableList, indexes, facts.triggers.get(table.name) ?? [])]; })); } function readCanonicalIndexDefinition(index) { if (index.name === null || index.sql === null) throw new Error("Canonical SQLite named index is missing its schema definition."); const prefix = (index.unique === 1 ? /^CREATE\s+UNIQUE\s+INDEX\s+/iu : /^CREATE\s+INDEX\s+/iu).exec(index.sql); if (!prefix) throw new Error(`Canonical SQLite index ${index.name} has an unreadable definition.`); const name = readSqlToken(index.sql, prefix[0].length); if (!name || normalizeSqlIdentifier(name.raw) !== index.name.toLowerCase()) throw new Error(`Canonical SQLite index ${index.name} has an unexpected schema name.`); const definition = index.sql.slice(name.end).trim(); if (!/^ON\s+/iu.test(definition)) throw new Error(`Canonical SQLite index ${index.name} has an unreadable target.`); return definition; } function collectSqliteTableContractFromRow(database, tableName, table) { const quotedTable = quoteSqliteIdentifier(tableName); const tableList = database.prepare(`PRAGMA main.table_list(${quotedTable})`).all().find((entry) => entry.schema === "main" && entry.name === tableName); if (!tableList) throw new Error(`Could not inspect SQLite table options for ${tableName}.`); return createSqliteTableContract(tableName, table, tableList, database.prepare(`PRAGMA main.index_list(${quotedTable})`).all().map((index) => collectSqliteIndexContract(database, index)).toSorted(compareJson), executeWithCachedStatement(database, ` SELECT name, sql FROM main.sqlite_schema WHERE type = 'trigger' AND tbl_name = ? ORDER BY name `, [tableName], (statement) => statement.all(tableName))); } function compareTableDefinitions(tableName, actual, expected, compatibility, allowCompatibleAdditiveColumns) { const issues = []; const add = (code, objectName) => { issues.push(createSqliteSchemaIssue(code, objectName)); }; if (!actual || !expected) { if (actual !== expected) add("table-definition-drift", tableName); return issues; } const allowedMissingColumns = new Set(compatibility.allowedMissingColumns ?? []); for (const [columnName, definition] of actual.columns) if (!expected.columns.has(columnName)) { if (allowCompatibleAdditiveColumns && compatibility.allowCompatibleAdditiveColumns && isCompatibleAdditiveColumnDefinition(definition)) continue; add("unexpected-column", `${tableName}.${columnName}`); } for (const [columnName, expectedDefinition] of expected.columns) { const objectName = `${tableName}.${columnName}`; const actualDefinition = actual.columns.get(columnName); if (actualDefinition === void 0) { if (!allowedMissingColumns.has(objectName)) add("missing-column", objectName); continue; } if (actualDefinition === expectedDefinition) continue; if (!(compatibility.allowedColumnDefinitions?.[objectName] ?? []).some((definition) => normalizeSqlWhitespace(definition) === actualDefinition)) add("column-definition-drift", objectName); } if (JSON.stringify(actual.constraints) !== JSON.stringify(expected.constraints)) add("table-constraint-drift", tableName); return issues; } const SQLITE_STRICT_DATATYPES = /* @__PURE__ */ new Set([ "ANY", "BLOB", "INT", "INTEGER", "REAL", "TEXT" ]); function isCompatibleAdditiveColumnDefinition(definition) { const name = readSqlToken(definition, 0); const type = name ? readSqlToken(definition, name.end) : null; return Boolean(type?.keyword && SQLITE_STRICT_DATATYPES.has(type.keyword) && definition.slice(type.end).trim().length === 0); } function collectSqliteIndexContract(database, index) { const row = executeWithCachedStatement(database, "SELECT sql FROM main.sqlite_schema WHERE type = 'index' AND name = ?", [index.name], (statement) => statement.get(index.name)); const terms = database.prepare(`PRAGMA main.index_xinfo(${quoteSqliteIdentifier(index.name)})`).all(); return createSqliteIndexContract(index, typeof row?.sql === "string" ? row.sql : null, terms); } function containsTrigger(triggers, expected) { return triggers.some((trigger) => trigger.name === expected.name && trigger.sql === expected.sql); } function compareJson(left, right) { return JSON.stringify(left).localeCompare(JSON.stringify(right)); } function readSqliteSchemaCookie(database) { return database.prepare("PRAGMA schema_version").get()?.schema_version; } //#endregion //#region src/state/openclaw-state-db-schema-helpers.ts function tableHasColumn(db, tableName, columnName) { return readTableColumns(db, tableName).has(columnName); } function readTableColumns(db, tableName) { const rows = db.prepare(`PRAGMA table_info(${tableName})`).all(); return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : [])); } /** Inspect only failed queries; native unavailability must not become a schema refusal. */ function classifySqliteTableReadError(db, tableName, columnNames, error) { if (sqlitePrimaryResultCode(error) !== 1) return error; try { const existing = readTableColumns(db, tableName); if (existing.size > 0 && columnNames.some((column) => !existing.has(column))) return new SqliteSchemaMismatchError(`SQLite table ${tableName} is missing required columns; run openclaw doctor --fix to repair it.`, { cause: error }); } catch {} return error; } function tablePrimaryKeyColumns(db, tableName) { return db.prepare(`PRAGMA table_info(${tableName})`).all().filter((row) => Number(row.pk ?? 0) > 0 && typeof row.name === "string").toSorted((left, right) => Number(left.pk ?? 0) - Number(right.pk ?? 0)).map((row) => row.name); } function tableExists(db, tableName) { const schema = getAdmittedSqliteSchemaFacts(db); if (schema) return schema.tables.has(tableName); return executeWithCachedStatement(db, "SELECT 1 AS ok FROM sqlite_master WHERE type = 'table' AND name = ?", [tableName], (statement) => statement.get(tableName))?.ok === 1; } function ensureColumn(db, tableName, columnSql) { const columnName = columnSql.trim().split(/\s+/, 1)[0]; if (!columnName || !tableExists(db, tableName) || tableHasColumn(db, tableName, columnName)) return false; db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnSql};`); return true; } //#endregion //#region src/state/openclaw-state-db-schema-version.ts const CONTENT_VERSION_KEY = "state.schema.contentVersion"; const contentVersionQuery = createSqliteQueryCache((db) => prepareSqliteQuerySync(db, () => getNodeSqliteKysely(db).selectFrom("config_machine_state").select("value_json").where("state_key", "=", CONTENT_VERSION_KEY))); /** Content and its marker commit together, even while older readers retain their version floor. */ function readStateSchemaContentVersion(db) { return readContentVersion(db, getAdmittedSqliteSchemaFacts(db)?.userVersion ?? readSqliteUserVersion(db)); } function readContentVersion(db, published) { if (!tableExists(db, "config_machine_state")) return published; const row = contentVersionQuery(db)().rows[0]; if (!row) return published; let contentVersion; try { contentVersion = JSON.parse(row.value_json); } catch (cause) { throw new SqliteSchemaMismatchError(`Invalid shared state schema content version in ${CONTENT_VERSION_KEY}.`, { cause }); } if (typeof contentVersion !== "number" || !Number.isSafeInteger(contentVersion) || contentVersion < 0) throw new SqliteSchemaMismatchError(`Invalid shared state schema content version in ${CONTENT_VERSION_KEY}.`); return Math.max(published, contentVersion); } /** Cold migration planning checks physical content; admission still uses the recorded version. */ function readStateSchemaMigrationVersion(db) { const version = readStateSchemaContentVersion(db); if (version !== 16) return version; const reviewWorkspace = tableHasColumn(db, "skill_workshop_collection_reviews", "workspace_dir"); const proposalWorkspace = tableHasColumn(db, "skill_workshop_proposals", "workspace_dir"); const releasedClaim = tableHasColumn(db, "skill_workshop_proposals", "claim_released_time"); if (!reviewWorkspace && !proposalWorkspace && !releasedClaim) return version; const missingAttribution = reviewWorkspace && !proposalWorkspace; const issues = collectSqliteSchemaIssues(db, ` CREATE TABLE skill_workshop_collection_reviews ( review_id TEXT NOT NULL PRIMARY KEY, ${reviewWorkspace ? "workspace_dir" : "owner_agent_id"} TEXT NOT NULL, backup_id TEXT NOT NULL, create_time INTEGER NOT NULL, kept_names_json TEXT NOT NULL, written_names_json TEXT NOT NULL, dropped_json TEXT NOT NULL ) STRICT; CREATE TABLE skill_workshop_proposals ( proposal_id TEXT NOT NULL PRIMARY KEY, record_json TEXT NOT NULL, owner_agent_id TEXT, ${proposalWorkspace ? "workspace_dir TEXT NOT NULL," : ""} kind TEXT NOT NULL CHECK (kind IN ('create', 'update')), status TEXT NOT NULL CHECK (status IN ('pending', 'applied', 'rejected', 'quarantined', 'stale')), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, draft_hash TEXT NOT NULL, origin_agent_id TEXT, origin_session_key TEXT, origin_run_id TEXT, origin_message_id TEXT, applied_at TEXT, rejected_at TEXT, quarantined_at TEXT, stale_at TEXT, status_reason TEXT ${releasedClaim ? ", claim_released_time INTEGER" : ""} ) STRICT; `, { allowedMissingTables: ["skill_workshop_collection_reviews"], allowedColumnDefinitions: { "skill_workshop_collection_reviews.workspace_dir": ["workspace_dir TEXT NOT NULL DEFAULT ''"], "skill_workshop_proposals.workspace_dir": ["workspace_dir TEXT NOT NULL DEFAULT ''"] } }); if (!missingAttribution && issues.length === 0) return 15; throw new SqliteSchemaMismatchError("Unrecognized Skill Workshop ownership schema; cannot apply the schema 16 migration."); } function assertSupportedStateSchemaVersion(db, pathname) { try { const userVersion = getAdmittedSqliteSchemaFacts(db)?.userVersion ?? readSqliteUserVersion(db); const contentVersion = userVersion > 20 ? userVersion : readStateSchemaContentVersion(db); if (contentVersion > 20) throw createNewerSqliteSchemaVersionError("OpenClaw state database", pathname, contentVersion, 20); return userVersion; } catch (error) { throw normalizeOpenClawStateSchemaReadError(error, pathname); } } //#endregion export { getCanonicalSqliteTableNames as _, classifySqliteTableReadError as a, tableHasColumn as c, assertSqliteSchemaTablesPresent as d, collectSqliteIndexContract as f, getCanonicalSqliteNamedIndexContracts as g, createSqliteTableContractReader as h, readStateSchemaMigrationVersion as i, tablePrimaryKeyColumns as l, collectSqliteSchemaIssues as m, assertSupportedStateSchemaVersion as n, ensureColumn as o, collectSqliteNamedIndexContract as p, readStateSchemaContentVersion as r, tableExists as s, CONTENT_VERSION_KEY as t, assertSqliteSchemaContains as u, readSqliteSchemaCookie as v, SqliteSchemaMismatchError as y };