Files
openclaw/src/transcripts/sqlite-schema.ts
Peter Steinberger a2be4efb63 refactor(transcripts): store meeting captures in SQLite (#112910)
* refactor(transcripts): move meeting transcripts to sqlite

* perf(transcripts): batch legacy utterance staging

* fix(transcripts): report recovery moves on migration failure

* fix(transcripts): refresh archive membership after recovery

* fix(transcripts): omit failed summary export paths

* fix(transcripts): type restore metadata tuple

* fix(transcripts): align migration contract gates

* fix(transcripts): verify case-aliased export ownership

* fix(transcripts): allowlist doctor verifier sqlite query

* fix(transcripts): preflight partial artifact recovery

* fix(transcripts): stabilize exports and legacy path checks

* fix(transcripts): resolve case-renamed doctor ownership

* fix(transcripts): harden canonical export recovery

* fix(transcripts): preserve pending import boundaries

* refactor(transcripts): split migration insert transaction

* perf(transcripts): query selected summary existence
2026-07-23 07:18:55 -04:00

85 lines
3.0 KiB
TypeScript

// Additive meeting-transcript schema used by the feature's one-time lazy ensure.
import type { DatabaseSync } from "node:sqlite";
import {
openOpenClawStateDatabase,
runOpenClawStateWriteTransaction,
type OpenClawStateDatabaseOptions,
} from "../state/openclaw-state-db.js";
const ensuredDatabases = new WeakSet<DatabaseSync>();
const MEETING_TRANSCRIPTS_SCHEMA_SQL = `
CREATE TABLE IF NOT EXISTS meeting_transcript_sessions (
session_id TEXT NOT NULL,
started_at TEXT NOT NULL,
selector TEXT NOT NULL UNIQUE,
export_key TEXT NOT NULL,
session_slug TEXT NOT NULL,
provider_id TEXT NOT NULL,
title TEXT,
source_json TEXT NOT NULL,
stopped_at TEXT,
metadata_json TEXT,
export_manifest_json TEXT NOT NULL DEFAULT '{}',
export_pending_json TEXT NOT NULL DEFAULT '[]',
next_utterance_seq INTEGER NOT NULL DEFAULT 0 CHECK (next_utterance_seq >= 0),
created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),
updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),
PRIMARY KEY (session_id, started_at)
) STRICT;
CREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_started
ON meeting_transcript_sessions(started_at DESC, session_id);
CREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_id
ON meeting_transcript_sessions(session_id, started_at DESC);
CREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_slug
ON meeting_transcript_sessions(session_slug, started_at DESC);
CREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_export_key
ON meeting_transcript_sessions(export_key);
CREATE TABLE IF NOT EXISTS meeting_transcript_utterances (
session_id TEXT NOT NULL,
session_started_at TEXT NOT NULL,
sequence INTEGER NOT NULL CHECK (sequence >= 0),
utterance_id TEXT,
started_at TEXT,
ended_at TEXT,
speaker_id TEXT,
speaker_label TEXT,
text TEXT NOT NULL,
final INTEGER CHECK (final IN (0, 1)),
metadata_json TEXT,
PRIMARY KEY (session_id, session_started_at, sequence),
FOREIGN KEY (session_id, session_started_at)
REFERENCES meeting_transcript_sessions(session_id, started_at)
ON DELETE CASCADE
) STRICT;
CREATE TABLE IF NOT EXISTS meeting_transcript_summaries (
session_id TEXT NOT NULL,
session_started_at TEXT NOT NULL,
generated_at TEXT,
summary_json TEXT,
markdown TEXT,
utterance_count INTEGER NOT NULL CHECK (utterance_count >= 0),
PRIMARY KEY (session_id, session_started_at),
FOREIGN KEY (session_id, session_started_at)
REFERENCES meeting_transcript_sessions(session_id, started_at)
ON DELETE CASCADE,
CHECK (summary_json IS NOT NULL OR markdown IS NOT NULL)
) STRICT;
`;
export function ensureMeetingTranscriptsSchema(options: OpenClawStateDatabaseOptions = {}): void {
const database = openOpenClawStateDatabase(options);
if (ensuredDatabases.has(database.db)) {
return;
}
runOpenClawStateWriteTransaction(({ db }) => db.exec(MEETING_TRANSCRIPTS_SCHEMA_SQL), options, {
operationLabel: "meeting-transcripts.schema.ensure",
});
ensuredDatabases.add(database.db);
}