@titan-design/session-graph 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,252 @@
1
1
  // src/graph.ts
2
2
  import { EdgeTable, SpanFtsTables, WatermarkTable, openDatabase, runMigrations } from "@titan-design/store-sqlite";
3
3
 
4
+ // src/audit-schema.ts
5
+ var AUDIT_MIGRATION_NAME = "audit tables";
6
+ var AUDIT_TABLES = [
7
+ "request",
8
+ "tool_call",
9
+ "inbound",
10
+ "context_block",
11
+ "compaction",
12
+ "queue_op",
13
+ "session_signal",
14
+ "cost_state_observation"
15
+ ];
16
+ var FACET_TABLE = "transcript_facet";
17
+ var AUDIT_DDL = `
18
+ CREATE TABLE IF NOT EXISTS request (
19
+ transcript_id INTEGER NOT NULL,
20
+ request_id TEXT NOT NULL,
21
+ byte_offset INTEGER NOT NULL, -- first line seen for this request
22
+ session_id TEXT NOT NULL,
23
+ message_id TEXT,
24
+ ts TEXT NOT NULL,
25
+ model TEXT NOT NULL,
26
+ input_tokens INTEGER NOT NULL DEFAULT 0,
27
+ cache_read_tokens INTEGER NOT NULL DEFAULT 0,
28
+ cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
29
+ cache_creation_5m INTEGER NOT NULL DEFAULT 0,
30
+ cache_creation_1h INTEGER NOT NULL DEFAULT 0,
31
+ output_tokens INTEGER NOT NULL DEFAULT 0,
32
+ thinking_tokens INTEGER NOT NULL DEFAULT 0,
33
+ context_tokens INTEGER NOT NULL DEFAULT 0, -- input + cache_read + cache_creation
34
+ service_tier TEXT,
35
+ is_sidechain INTEGER NOT NULL DEFAULT 0,
36
+ -- rollup-owned, recomputed for touched sessions:
37
+ seq_in_session INTEGER,
38
+ gap_ms INTEGER, -- since previous request in the session
39
+ ctx_delta INTEGER, -- context_tokens - (prev.context_tokens + prev.output_tokens)
40
+ wake_cause TEXT,
41
+ wake_delivery TEXT,
42
+ wake_detail TEXT, -- tool name, channel sender, or NULL
43
+ wake_tool_family TEXT,
44
+ wake_mcp_server TEXT,
45
+ wake_offset INTEGER, -- byte_offset of the inbound row
46
+ PRIMARY KEY (transcript_id, request_id)
47
+ ) WITHOUT ROWID;
48
+ CREATE INDEX IF NOT EXISTS idx_request_session_ts ON request(session_id, ts);
49
+ CREATE INDEX IF NOT EXISTS idx_request_ts ON request(ts);
50
+ CREATE INDEX IF NOT EXISTS idx_request_id ON request(request_id);
51
+
52
+ CREATE TABLE IF NOT EXISTS tool_call (
53
+ transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,
54
+ session_id TEXT NOT NULL, ts TEXT NOT NULL, tool_use_id TEXT NOT NULL,
55
+ name TEXT NOT NULL, family TEXT NOT NULL, mcp_server TEXT, input_chars INTEGER NOT NULL,
56
+ PRIMARY KEY (transcript_id, byte_offset, block_index)
57
+ ) WITHOUT ROWID;
58
+ CREATE INDEX IF NOT EXISTS idx_tool_call_use ON tool_call(tool_use_id);
59
+
60
+ CREATE TABLE IF NOT EXISTS inbound (
61
+ transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,
62
+ session_id TEXT NOT NULL, ts TEXT NOT NULL,
63
+ cause TEXT NOT NULL, delivery TEXT NOT NULL, detail TEXT,
64
+ origin_server TEXT, from_name TEXT, msg_id TEXT, tool_use_id TEXT,
65
+ is_error INTEGER NOT NULL DEFAULT 0, content_hash TEXT NOT NULL, chars INTEGER NOT NULL,
66
+ queued_ms INTEGER, -- rollup-owned
67
+ PRIMARY KEY (transcript_id, byte_offset, block_index)
68
+ ) WITHOUT ROWID;
69
+ CREATE INDEX IF NOT EXISTS idx_inbound_session ON inbound(session_id, byte_offset);
70
+
71
+ CREATE TABLE IF NOT EXISTS context_block (
72
+ transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,
73
+ session_id TEXT NOT NULL, ts TEXT NOT NULL, source TEXT NOT NULL,
74
+ tool_use_id TEXT, attachment_type TEXT, chars INTEGER NOT NULL, is_media INTEGER NOT NULL DEFAULT 0,
75
+ PRIMARY KEY (transcript_id, byte_offset, block_index)
76
+ ) WITHOUT ROWID;
77
+ CREATE INDEX IF NOT EXISTS idx_context_block_session ON context_block(session_id, source);
78
+
79
+ CREATE TABLE IF NOT EXISTS compaction (
80
+ transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,
81
+ session_id TEXT NOT NULL, ts TEXT NOT NULL, trigger TEXT,
82
+ pre_tokens INTEGER, post_tokens INTEGER, dropped_tokens INTEGER, duration_ms INTEGER,
83
+ mid_loop INTEGER, -- rollup-owned: previous inbound was a tool_result
84
+ PRIMARY KEY (transcript_id, byte_offset)
85
+ ) WITHOUT ROWID;
86
+
87
+ CREATE TABLE IF NOT EXISTS queue_op (
88
+ transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,
89
+ session_id TEXT NOT NULL, ts TEXT NOT NULL, operation TEXT NOT NULL,
90
+ content_hash TEXT, origin_server TEXT,
91
+ PRIMARY KEY (transcript_id, byte_offset)
92
+ ) WITHOUT ROWID;
93
+ CREATE INDEX IF NOT EXISTS idx_queue_op_hash ON queue_op(session_id, content_hash);
94
+
95
+ CREATE TABLE IF NOT EXISTS session_signal (
96
+ transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,
97
+ session_id TEXT NOT NULL, ts TEXT NOT NULL, signal TEXT NOT NULL, detail TEXT, tool_use_id TEXT,
98
+ -- one Bash block can emit up to three signals (commit, push, pr_create); the signal name disambiguates them
99
+ PRIMARY KEY (transcript_id, byte_offset, block_index, signal)
100
+ ) WITHOUT ROWID;
101
+ CREATE INDEX IF NOT EXISTS idx_signal_session ON session_signal(session_id, ts);
102
+
103
+ CREATE TABLE IF NOT EXISTS cost_state_observation (
104
+ transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,
105
+ session_id TEXT NOT NULL, ts TEXT, total_cost_usd REAL NOT NULL, model_usage TEXT NOT NULL,
106
+ PRIMARY KEY (transcript_id, byte_offset)
107
+ ) WITHOUT ROWID;
108
+
109
+ CREATE TABLE IF NOT EXISTS transcript_facet (
110
+ transcript_id INTEGER NOT NULL, facet TEXT NOT NULL,
111
+ version INTEGER NOT NULL, indexed_to INTEGER NOT NULL,
112
+ PRIMARY KEY (transcript_id, facet)
113
+ ) WITHOUT ROWID;
114
+ `;
115
+ var AUDIT_COLUMNS = [
116
+ ["session", "account", "TEXT"],
117
+ ["turn", "wake_cause", "TEXT"],
118
+ ["task", "estimate", "REAL"],
119
+ ["pr", "review_rounds", "INTEGER"],
120
+ ["pr", "closed_at", "TEXT"],
121
+ ["pr", "outcome_checked_at", "TEXT"]
122
+ ];
123
+ function applyAuditSchema(db) {
124
+ db.exec(AUDIT_DDL);
125
+ for (const [table, column, type] of AUDIT_COLUMNS) addColumnIfMissing(db, table, column, type);
126
+ }
127
+ function addColumnIfMissing(db, table, column, type) {
128
+ const present = db.prepare("SELECT 1 FROM pragma_table_info(?) WHERE name = ?").get(table, column);
129
+ if (!present) db.exec(`ALTER TABLE "${table}" ADD COLUMN "${column}" ${type}`);
130
+ }
131
+
132
+ // src/audit-schema-v5.ts
133
+ var ORIGIN_MIGRATION_NAME = "origin, episodes, prices";
134
+ var ORIGIN_TABLES = ["session_origin", "session_external_event"];
135
+ var EPISODE_TABLE = "episode";
136
+ var ORIGIN_DDL = `
137
+ CREATE TABLE IF NOT EXISTS session_origin ( -- not derived from transcripts
138
+ session_id TEXT PRIMARY KEY,
139
+ origin_system TEXT NOT NULL, -- 'agent-chat'
140
+ agent_id TEXT, agent_name TEXT, parent_name TEXT, parent_session_id TEXT,
141
+ profile TEXT, model_alias TEXT, surface TEXT, isolation TEXT, depth INTEGER,
142
+ origin_kind TEXT, -- spawned | adopted | inherited | human
143
+ config_dir TEXT, spawn_cwd TEXT, spawned_at TEXT,
144
+ brief_chars INTEGER, brief_excerpt TEXT, brief_path TEXT, launch_args TEXT,
145
+ resolved_at TEXT NOT NULL
146
+ );
147
+ CREATE INDEX IF NOT EXISTS idx_origin_parent ON session_origin(parent_session_id);
148
+
149
+ CREATE TABLE IF NOT EXISTS session_external_event ( -- teleport, handoff, retire, exit
150
+ session_id TEXT NOT NULL, ts TEXT NOT NULL, origin_system TEXT NOT NULL,
151
+ kind TEXT NOT NULL, detail TEXT,
152
+ PRIMARY KEY (session_id, ts, kind)
153
+ ) WITHOUT ROWID;
154
+
155
+ CREATE TABLE IF NOT EXISTS episode ( -- provisional, see section 9
156
+ session_id TEXT NOT NULL, episode_index INTEGER NOT NULL,
157
+ heuristic TEXT NOT NULL, heuristic_version INTEGER NOT NULL,
158
+ started_at TEXT NOT NULL, ended_at TEXT NOT NULL,
159
+ start_offset INTEGER NOT NULL, end_offset INTEGER NOT NULL,
160
+ opened_by TEXT NOT NULL, -- brief | channel_followup | idle_gap | compaction
161
+ assignment_offset INTEGER, first_deliverable_offset INTEGER, first_deliverable_signal TEXT,
162
+ first_status_offset INTEGER,
163
+ PRIMARY KEY (session_id, heuristic, episode_index)
164
+ ) WITHOUT ROWID;
165
+
166
+ CREATE TABLE IF NOT EXISTS price (
167
+ model TEXT NOT NULL, effective_from TEXT NOT NULL, table_version INTEGER NOT NULL,
168
+ input_usd_mtok REAL NOT NULL, cache_read_usd_mtok REAL NOT NULL,
169
+ cache_write_5m_usd_mtok REAL NOT NULL, cache_write_1h_usd_mtok REAL NOT NULL,
170
+ output_usd_mtok REAL NOT NULL, source TEXT,
171
+ PRIMARY KEY (model, effective_from)
172
+ ) WITHOUT ROWID;
173
+ `;
174
+ var REQUEST_COLUMNS = `transcript_id, request_id, byte_offset, session_id, message_id, ts, model,
175
+ input_tokens, cache_read_tokens, cache_creation_tokens, cache_creation_5m, cache_creation_1h,
176
+ output_tokens, thinking_tokens, context_tokens, service_tier, is_sidechain,
177
+ seq_in_session, gap_ms, ctx_delta, wake_cause, wake_delivery, wake_detail,
178
+ wake_tool_family, wake_mcp_server, wake_offset`;
179
+ var REQUEST_DEDUP = `
180
+ CREATE VIEW IF NOT EXISTS request_dedup AS
181
+ SELECT ${REQUEST_COLUMNS} FROM (
182
+ SELECT r.*, ROW_NUMBER() OVER (PARTITION BY r.request_id ORDER BY r.ts, r.transcript_id) AS copy_rank
183
+ FROM request r
184
+ ) WHERE copy_rank = 1;
185
+ `;
186
+ var REQUEST_COST = `
187
+ CREATE VIEW IF NOT EXISTS request_cost AS
188
+ WITH candidate AS (
189
+ SELECT d.request_id, p.model AS price_model, p.effective_from AS price_effective_from,
190
+ p.input_usd_mtok, p.cache_read_usd_mtok, p.cache_write_5m_usd_mtok, p.cache_write_1h_usd_mtok, p.output_usd_mtok,
191
+ ROW_NUMBER() OVER (PARTITION BY d.request_id ORDER BY length(p.model) DESC, p.effective_from DESC) AS match_rank
192
+ FROM request_dedup d
193
+ JOIN price p ON substr(d.model, 1, length(p.model)) = p.model AND p.effective_from <= d.ts
194
+ ), component AS (
195
+ SELECT d.*, c.price_model, c.price_effective_from, c.price_model IS NOT NULL AS priced,
196
+ COALESCE(d.input_tokens * c.input_usd_mtok, 0) / 1e6 AS input_cost_usd,
197
+ COALESCE(d.cache_read_tokens * c.cache_read_usd_mtok, 0) / 1e6 AS cache_read_cost_usd,
198
+ COALESCE(CASE WHEN d.cache_creation_5m + d.cache_creation_1h = 0 THEN d.cache_creation_tokens ELSE d.cache_creation_5m END
199
+ * c.cache_write_5m_usd_mtok, 0) / 1e6 AS cache_write_5m_cost_usd,
200
+ COALESCE(d.cache_creation_1h * c.cache_write_1h_usd_mtok, 0) / 1e6 AS cache_write_1h_cost_usd,
201
+ COALESCE(d.output_tokens * c.output_usd_mtok, 0) / 1e6 AS output_cost_usd
202
+ FROM request_dedup d
203
+ LEFT JOIN candidate c ON c.request_id = d.request_id AND c.match_rank = 1
204
+ )
205
+ SELECT *,
206
+ input_cost_usd + cache_read_cost_usd + cache_write_5m_cost_usd + cache_write_1h_cost_usd + output_cost_usd AS cost_usd,
207
+ (cache_read_tokens < 0.2 * context_tokens AND cache_creation_tokens >= 20000) AS is_cold,
208
+ CASE WHEN context_tokens < 50000 THEN '<50k' WHEN context_tokens < 100000 THEN '50-100k'
209
+ WHEN context_tokens < 200000 THEN '100-200k' ELSE '200k+' END AS context_band,
210
+ CASE WHEN gap_ms IS NULL THEN NULL WHEN gap_ms < 300000 THEN '<5m'
211
+ WHEN gap_ms < 3600000 THEN '5-60m' ELSE '>60m' END AS gap_band
212
+ FROM component;
213
+ `;
214
+ var ASSISTANT_OUTPUT = "'assistant_text', 'assistant_thinking', 'assistant_tool_input'";
215
+ var CONTEXT_CONTRIBUTION = `
216
+ CREATE VIEW IF NOT EXISTS context_contribution AS
217
+ WITH stream AS (
218
+ SELECT transcript_id, byte_offset, 0 AS lane, block_index, NULL AS request_id FROM context_block
219
+ UNION ALL
220
+ SELECT transcript_id, byte_offset, -1 AS lane, 0, request_id FROM request
221
+ ), ordered AS (
222
+ SELECT *, COUNT(request_id) OVER (PARTITION BY transcript_id ORDER BY byte_offset, lane, block_index ROWS UNBOUNDED PRECEDING) AS requests_before
223
+ FROM stream
224
+ ), owner AS (
225
+ SELECT b.transcript_id, b.byte_offset, b.block_index, r.request_id
226
+ FROM ordered b JOIN ordered r ON r.transcript_id = b.transcript_id AND r.lane = -1 AND r.requests_before = b.requests_before + 1
227
+ WHERE b.lane = 0
228
+ ), tool AS (
229
+ SELECT tool_use_id, name, family, mcp_server,
230
+ ROW_NUMBER() OVER (PARTITION BY tool_use_id ORDER BY transcript_id, byte_offset, block_index) AS copy_rank
231
+ FROM tool_call
232
+ )
233
+ SELECT cb.transcript_id, cb.byte_offset, cb.block_index, cb.session_id, cb.ts, cb.source,
234
+ cb.tool_use_id, cb.attachment_type, cb.chars, cb.is_media, o.request_id, d.ctx_delta,
235
+ CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN NULL
236
+ ELSE d.ctx_delta * cb.chars * 1.0 / NULLIF(SUM(CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN 0 ELSE cb.chars END)
237
+ OVER (PARTITION BY o.transcript_id, o.request_id), 0) END AS est_tokens,
238
+ t.name AS tool_name, t.family AS tool_family, t.mcp_server
239
+ FROM context_block cb
240
+ JOIN owner o USING (transcript_id, byte_offset, block_index)
241
+ JOIN request_dedup d ON d.transcript_id = o.transcript_id AND d.request_id = o.request_id
242
+ LEFT JOIN tool t ON t.tool_use_id = cb.tool_use_id AND t.copy_rank = 1;
243
+ `;
244
+ var ORIGIN_VIEWS = [REQUEST_DEDUP, REQUEST_COST, CONTEXT_CONTRIBUTION];
245
+ function applyOriginSchema(db) {
246
+ db.exec(ORIGIN_DDL);
247
+ for (const view of ORIGIN_VIEWS) db.exec(view);
248
+ }
249
+
4
250
  // src/normalized-schema.ts
5
251
  import { conversationRef } from "@titan-design/agent-protocol";
6
252
  var NORMALIZED_DDL = `
@@ -188,6 +434,10 @@ var DERIVED_TABLES = [
188
434
  "human_edit",
189
435
  "file_checkpoint",
190
436
  "subagent",
437
+ ...AUDIT_TABLES,
438
+ FACET_TABLE,
439
+ ...ORIGIN_TABLES,
440
+ EPISODE_TABLE,
191
441
  "session_model_usage",
192
442
  "session",
193
443
  "fact",
@@ -205,7 +455,9 @@ var MIGRATIONS = [
205
455
  { version: 3, name: "normalized conversations and source evidence", up: (db) => {
206
456
  db.exec(NORMALIZED_DDL);
207
457
  backfillClaudeAliases(db);
208
- } }
458
+ } },
459
+ { version: 4, name: AUDIT_MIGRATION_NAME, up: applyAuditSchema },
460
+ { version: 5, name: ORIGIN_MIGRATION_NAME, up: applyOriginSchema }
209
461
  ];
210
462
 
211
463
  // src/graph.ts
@@ -230,16 +482,152 @@ function allSessionIds(graph) {
230
482
  return graph.db.prepare("SELECT session_id FROM session").all().map((r) => r.session_id);
231
483
  }
232
484
 
485
+ // src/audit-apply.ts
486
+ var UPSERT_REQUEST = `
487
+ INSERT INTO request (transcript_id, request_id, byte_offset, session_id, message_id, ts, model,
488
+ input_tokens, cache_read_tokens, cache_creation_tokens, cache_creation_5m, cache_creation_1h,
489
+ output_tokens, thinking_tokens, context_tokens, service_tier, is_sidechain)
490
+ VALUES (@transcriptId, @requestId, @byteOffset, @sessionId, @messageId, @ts, @model,
491
+ @inputTokens, @cacheReadTokens, @cacheCreationTokens, @cacheCreation5mTokens, @cacheCreation1hTokens,
492
+ @outputTokens, @thinkingTokens, @inputTokens + @cacheReadTokens + @cacheCreationTokens, @serviceTier, @isSidechain)
493
+ ON CONFLICT (transcript_id, request_id) DO UPDATE SET
494
+ session_id = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.session_id ELSE session_id END,
495
+ message_id = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.message_id ELSE message_id END,
496
+ model = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.model ELSE model END,
497
+ service_tier = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.service_tier ELSE service_tier END,
498
+ is_sidechain = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.is_sidechain ELSE is_sidechain END,
499
+ byte_offset = MIN(byte_offset, excluded.byte_offset),
500
+ ts = MIN(ts, excluded.ts),
501
+ input_tokens = MAX(input_tokens, excluded.input_tokens),
502
+ cache_read_tokens = MAX(cache_read_tokens, excluded.cache_read_tokens),
503
+ cache_creation_tokens = MAX(cache_creation_tokens, excluded.cache_creation_tokens),
504
+ cache_creation_5m = MAX(cache_creation_5m, excluded.cache_creation_5m),
505
+ cache_creation_1h = MAX(cache_creation_1h, excluded.cache_creation_1h),
506
+ output_tokens = MAX(output_tokens, excluded.output_tokens),
507
+ thinking_tokens = MAX(thinking_tokens, excluded.thinking_tokens),
508
+ context_tokens = MAX(input_tokens, excluded.input_tokens) + MAX(cache_read_tokens, excluded.cache_read_tokens)
509
+ + MAX(cache_creation_tokens, excluded.cache_creation_tokens)`;
510
+ var INSERTS = {
511
+ toolCalls: `INSERT INTO tool_call (transcript_id, byte_offset, block_index, session_id, ts, tool_use_id, name, family, mcp_server, input_chars)
512
+ VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @toolUseId, @name, @family, @mcpServer, @inputChars)
513
+ ON CONFLICT DO NOTHING`,
514
+ inbound: `INSERT INTO inbound (transcript_id, byte_offset, block_index, session_id, ts, cause, delivery, detail, origin_server, from_name, msg_id, tool_use_id, is_error, content_hash, chars)
515
+ VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @cause, @delivery, @detail, @originServer, @fromName, @msgId, @toolUseId, @isError, @contentHash, @chars)
516
+ ON CONFLICT DO NOTHING`,
517
+ contextBlocks: `INSERT INTO context_block (transcript_id, byte_offset, block_index, session_id, ts, source, tool_use_id, attachment_type, chars, is_media)
518
+ VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @source, @toolUseId, @attachmentType, @chars, @isMedia)
519
+ ON CONFLICT DO NOTHING`,
520
+ compactions: `INSERT INTO compaction (transcript_id, byte_offset, session_id, ts, trigger, pre_tokens, post_tokens, dropped_tokens, duration_ms)
521
+ VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @trigger, @preTokens, @postTokens, @droppedTokens, @durationMs)
522
+ ON CONFLICT DO NOTHING`,
523
+ queueOps: `INSERT INTO queue_op (transcript_id, byte_offset, session_id, ts, operation, content_hash, origin_server)
524
+ VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @operation, @contentHash, @originServer)
525
+ ON CONFLICT DO NOTHING`,
526
+ signals: `INSERT INTO session_signal (transcript_id, byte_offset, block_index, session_id, ts, signal, detail, tool_use_id)
527
+ VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @signal, @detail, @toolUseId)
528
+ ON CONFLICT DO NOTHING`,
529
+ costStates: `INSERT INTO cost_state_observation (transcript_id, byte_offset, session_id, ts, total_cost_usd, model_usage)
530
+ VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @totalCostUsd, @modelUsageJson)
531
+ ON CONFLICT DO NOTHING`
532
+ };
533
+ function applyAudit(db, transcriptId, delta) {
534
+ run(db, UPSERT_REQUEST, transcriptId, delta.requests);
535
+ for (const [list, sql] of Object.entries(INSERTS)) {
536
+ run(db, sql, transcriptId, delta[list]);
537
+ }
538
+ }
539
+ function run(db, sql, transcriptId, events) {
540
+ if (events.length === 0) return;
541
+ const statement = db.prepare(sql);
542
+ const names = [...new Set([...sql.matchAll(/@(\w+)/g)].map((m) => m[1]))];
543
+ for (const event of events) statement.run(bind(names, { ...event, transcriptId }));
544
+ }
545
+ function bind(names, row) {
546
+ return Object.fromEntries(names.map((name) => {
547
+ const value = row[name] ?? null;
548
+ return [name, typeof value === "boolean" ? Number(value) : value];
549
+ }));
550
+ }
551
+
552
+ // src/facet.ts
553
+ import { EXTRACT_VERSION, TranscriptParseError, extractTranscript } from "@titan-design/session-read";
554
+ var AUDIT_FACET = "audit";
555
+ var DEFAULT_FACET_LIMIT = 40;
556
+ async function backfillFacets(graph, transcripts, options = {}) {
557
+ const version = options.version ?? EXTRACT_VERSION;
558
+ const stale = staleTranscripts(graph, transcripts, version);
559
+ const visit = stale.slice(0, options.limit ?? DEFAULT_FACET_LIMIT);
560
+ const sessionIds = /* @__PURE__ */ new Set();
561
+ let backfilled = 0;
562
+ for (const candidate of visit) {
563
+ const touched = await backfillOne(graph, candidate, version);
564
+ if (!touched) continue;
565
+ backfilled += 1;
566
+ for (const id of touched) sessionIds.add(id);
567
+ }
568
+ return { backfilled, backlog: stale.length - visit.length, sessionIds: [...sessionIds] };
569
+ }
570
+ function staleTranscripts(graph, transcripts, version) {
571
+ const byPath = new Map(transcripts.map((t) => [t.displayPath, t]));
572
+ const versions = facetVersions(graph.db);
573
+ const stale = [];
574
+ for (const row of graph.transcripts.list()) {
575
+ const transcript = byPath.get(row.sourceKey);
576
+ if (!transcript || row.status !== "ok" || (versions.get(row.sourceId) ?? -1) >= version) continue;
577
+ stale.push({ row, transcript });
578
+ }
579
+ return stale.sort((a, b) => (b.row.fileMtime ?? "").localeCompare(a.row.fileMtime ?? ""));
580
+ }
581
+ function facetVersions(db) {
582
+ const rows = db.prepare(`SELECT transcript_id, version FROM ${FACET_TABLE} WHERE facet = ?`).all(AUDIT_FACET);
583
+ return new Map(rows.map((r) => [r.transcript_id, r.version]));
584
+ }
585
+ async function backfillOne(graph, { row, transcript }, version) {
586
+ let delta;
587
+ try {
588
+ delta = await extractTranscript(transcript.absolutePath, { untilByteOffset: row.lastOffset, subagentId: transcript.subagentId });
589
+ } catch (err) {
590
+ const status = unreadableStatus(err);
591
+ if (!status) throw err;
592
+ graph.transcripts.markStatus(row.sourceKey, status, err instanceof Error ? err.message : String(err));
593
+ return null;
594
+ }
595
+ graph.db.transaction(() => {
596
+ for (const table of AUDIT_TABLES) graph.db.prepare(`DELETE FROM "${table}" WHERE transcript_id = ?`).run(row.sourceId);
597
+ applyAudit(graph.db, row.sourceId, delta);
598
+ writeFacet(graph.db, row.sourceId, version, delta.lastByteOffset);
599
+ })();
600
+ return delta.sessions.map((s) => s.sessionId);
601
+ }
602
+ function unreadableStatus(err) {
603
+ if (err instanceof TranscriptParseError) return "quarantined";
604
+ if (err?.code === "ENOENT") return "missing";
605
+ return null;
606
+ }
607
+ function writeFacet(db, transcriptId, version, indexedTo) {
608
+ db.prepare(`
609
+ INSERT INTO ${FACET_TABLE} (transcript_id, facet, version, indexed_to) VALUES (?, ?, ?, ?)
610
+ ON CONFLICT (transcript_id, facet) DO UPDATE SET version = excluded.version, indexed_to = excluded.indexed_to`).run(transcriptId, AUDIT_FACET, version, indexedTo);
611
+ }
612
+ function advanceFacet(db, transcriptId, fromByteOffset, indexedTo, version = EXTRACT_VERSION) {
613
+ if (fromByteOffset === 0) {
614
+ writeFacet(db, transcriptId, version, indexedTo);
615
+ return;
616
+ }
617
+ db.prepare(`UPDATE ${FACET_TABLE} SET indexed_to = ? WHERE transcript_id = ? AND facet = ? AND version = ?`).run(indexedTo, transcriptId, AUDIT_FACET, version);
618
+ }
619
+
233
620
  // src/apply.ts
234
621
  import { sessionRef } from "@titan-design/session-read";
235
- function applyDelta(graph, transcriptId, delta) {
622
+ function applyDelta(graph, transcriptId, delta, source = {}) {
236
623
  graph.db.transaction(() => {
237
624
  applyFacts(graph.db, transcriptId, delta);
238
- applySessions(graph.db, transcriptId, delta);
625
+ applySessions(graph.db, transcriptId, delta, source.account ?? null);
239
626
  backfillClaudeAliases(graph.db, delta.sessions.map((s) => s.sessionId));
240
627
  applyAssets(graph.db, delta);
241
628
  applyPhases(graph.db, transcriptId, delta);
242
629
  applyLinkedRows(graph, transcriptId, delta);
630
+ applyAudit(graph.db, transcriptId, delta);
243
631
  })();
244
632
  }
245
633
  var INSERT_FACT = `
@@ -255,8 +643,8 @@ function applyFacts(db, transcriptId, delta) {
255
643
  }
256
644
  }
257
645
  var UPSERT_SESSION = `
258
- INSERT INTO session (session_id, transcript_id, started_at, ended_at, start_type, cwd, git_branch, ai_title, seed_prompt, cli_version, turn_count, commit_count, push_count)
259
- VALUES (@sessionId, @transcriptId, @startedAt, @endedAt, @startType, @cwd, @gitBranch, @aiTitle, @seedPrompt, @cliVersion, @turnDelta, @commitDelta, @pushDelta)
646
+ INSERT INTO session (session_id, transcript_id, started_at, ended_at, start_type, cwd, git_branch, ai_title, seed_prompt, cli_version, account, turn_count, commit_count, push_count)
647
+ VALUES (@sessionId, @transcriptId, @startedAt, @endedAt, @startType, @cwd, @gitBranch, @aiTitle, @seedPrompt, @cliVersion, @account, @turnDelta, @commitDelta, @pushDelta)
260
648
  ON CONFLICT (session_id) DO UPDATE SET
261
649
  started_at = MIN(COALESCE(started_at, excluded.started_at), COALESCE(excluded.started_at, started_at)),
262
650
  ended_at = MAX(COALESCE(ended_at, excluded.ended_at), COALESCE(excluded.ended_at, ended_at)),
@@ -270,22 +658,13 @@ var UPSERT_SESSION = `
270
658
  ai_title = COALESCE(excluded.ai_title, ai_title),
271
659
  seed_prompt = COALESCE(seed_prompt, excluded.seed_prompt),
272
660
  cli_version = COALESCE(excluded.cli_version, cli_version),
661
+ account = COALESCE(excluded.account, account),
273
662
  turn_count = turn_count + excluded.turn_count,
274
663
  commit_count = commit_count + excluded.commit_count,
275
664
  push_count = push_count + excluded.push_count`;
276
- var UPSERT_USAGE = `
277
- INSERT INTO session_model_usage (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, thinking_tokens, request_count)
278
- VALUES (@sessionId, @model, @inputTokens, @outputTokens, @cacheReadTokens, @cacheCreationTokens, @thinkingTokens, @requestCount)
279
- ON CONFLICT (session_id, model) DO UPDATE SET
280
- input_tokens = input_tokens + excluded.input_tokens, output_tokens = output_tokens + excluded.output_tokens,
281
- cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
282
- cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,
283
- thinking_tokens = thinking_tokens + excluded.thinking_tokens, request_count = request_count + excluded.request_count`;
284
- function applySessions(db, transcriptId, delta) {
665
+ function applySessions(db, transcriptId, delta, account) {
285
666
  const upsertSession = db.prepare(UPSERT_SESSION);
286
- for (const s of delta.sessions) upsertSession.run({ ...s, transcriptId });
287
- const upsertUsage = db.prepare(UPSERT_USAGE);
288
- for (const u of delta.usage) upsertUsage.run(u);
667
+ for (const s of delta.sessions) upsertSession.run({ ...s, transcriptId, account });
289
668
  }
290
669
  var ASSET_UPSERTS = {
291
670
  prs: `INSERT INTO pr (pr_ref, number, repo, title, url) VALUES (@prRef, @number, @repo, @title, @url)
@@ -359,7 +738,7 @@ function factIdFor(db, transcriptId, byteOffset) {
359
738
 
360
739
  // src/purge.ts
361
740
  import { sessionRef as sessionRef2 } from "@titan-design/session-read";
362
- var SESSION_SCOPED = ["turn", "permission_phase", "human_edit", "file_checkpoint", "subagent", "session_model_usage"];
741
+ var SESSION_SCOPED = ["turn", "permission_phase", "human_edit", "file_checkpoint", "subagent", "session_model_usage", EPISODE_TABLE];
363
742
  function purgeTranscript(graph, transcriptId) {
364
743
  graph.db.transaction(() => {
365
744
  const owned = graph.db.prepare("SELECT session_id FROM session WHERE transcript_id = ?").all(transcriptId);
@@ -368,11 +747,97 @@ function purgeTranscript(graph, transcriptId) {
368
747
  graph.db.prepare(`DELETE FROM "${table}" WHERE session_id IN (SELECT session_id FROM session WHERE transcript_id = ?)`).run(transcriptId);
369
748
  }
370
749
  graph.db.prepare(`DELETE FROM "${KIT.edge}" WHERE fact_id IN (SELECT fact_id FROM fact WHERE transcript_id = ?)`).run(transcriptId);
750
+ for (const table of [...AUDIT_TABLES, FACET_TABLE]) {
751
+ graph.db.prepare(`DELETE FROM "${table}" WHERE transcript_id = ?`).run(transcriptId);
752
+ }
371
753
  graph.db.prepare("DELETE FROM fact WHERE transcript_id = ?").run(transcriptId);
372
754
  graph.db.prepare("DELETE FROM session WHERE transcript_id = ?").run(transcriptId);
373
755
  })();
374
756
  }
375
757
 
758
+ // src/audit-rollup.ts
759
+ var IN_BATCH = "SELECT value FROM json_each(@sessionIds)";
760
+ var REQUEST_ORDER = `
761
+ WITH ordered AS (
762
+ SELECT transcript_id, request_id, ts, context_tokens,
763
+ ROW_NUMBER() OVER w - 1 AS seq,
764
+ LAG(ts) OVER w AS prev_ts,
765
+ LAG(context_tokens) OVER w AS prev_context,
766
+ LAG(output_tokens) OVER w AS prev_output
767
+ FROM request WHERE session_id IN (${IN_BATCH})
768
+ WINDOW w AS (PARTITION BY session_id ORDER BY ts, byte_offset, transcript_id, request_id)
769
+ )
770
+ UPDATE request SET seq_in_session = ordered.seq,
771
+ gap_ms = CAST(ROUND((julianday(ordered.ts) - julianday(ordered.prev_ts)) * 86400000) AS INTEGER),
772
+ ctx_delta = ordered.context_tokens - (ordered.prev_context + ordered.prev_output)
773
+ FROM ordered WHERE request.transcript_id = ordered.transcript_id AND request.request_id = ordered.request_id`;
774
+ var REQUEST_WAKE = `
775
+ WITH hit AS (
776
+ SELECT r.transcript_id, r.request_id,
777
+ (SELECT MAX(i.byte_offset) FROM inbound i
778
+ WHERE i.transcript_id = r.transcript_id AND i.session_id = r.session_id AND i.byte_offset < r.byte_offset) AS wake_offset
779
+ FROM request r WHERE r.session_id IN (${IN_BATCH})
780
+ ),
781
+ tools AS (
782
+ SELECT transcript_id, tool_use_id, name, family, mcp_server,
783
+ ROW_NUMBER() OVER (PARTITION BY transcript_id, tool_use_id ORDER BY byte_offset, block_index) AS pick
784
+ FROM tool_call WHERE session_id IN (${IN_BATCH})
785
+ ),
786
+ wake AS (
787
+ SELECT hit.transcript_id, hit.request_id, hit.wake_offset, i.cause, i.delivery, i.detail, i.from_name,
788
+ t.name AS tool_name, t.family, t.mcp_server
789
+ FROM hit
790
+ LEFT JOIN inbound i ON i.transcript_id = hit.transcript_id AND i.byte_offset = hit.wake_offset
791
+ AND i.block_index = (SELECT MAX(block_index) FROM inbound j WHERE j.transcript_id = hit.transcript_id AND j.byte_offset = hit.wake_offset)
792
+ LEFT JOIN tools t ON t.transcript_id = i.transcript_id AND t.tool_use_id = i.tool_use_id AND t.pick = 1
793
+ )
794
+ UPDATE request SET
795
+ wake_cause = CASE WHEN wake.cause IS NULL THEN 'session_start'
796
+ WHEN wake.tool_name = 'AskUserQuestion' THEN 'ask_user_answer' ELSE wake.cause END,
797
+ wake_delivery = wake.delivery,
798
+ wake_detail = COALESCE(wake.tool_name, wake.from_name, wake.detail),
799
+ wake_tool_family = wake.family,
800
+ wake_mcp_server = wake.mcp_server,
801
+ wake_offset = wake.wake_offset
802
+ FROM wake WHERE request.transcript_id = wake.transcript_id AND request.request_id = wake.request_id`;
803
+ var INBOUND_QUEUED = `
804
+ UPDATE inbound SET queued_ms = (
805
+ SELECT CAST(ROUND((julianday(inbound.ts) - julianday(q.ts)) * 86400000) AS INTEGER)
806
+ FROM queue_op q
807
+ WHERE q.transcript_id = inbound.transcript_id AND q.session_id = inbound.session_id
808
+ AND q.operation = 'enqueue' AND q.content_hash = inbound.content_hash AND q.byte_offset < inbound.byte_offset
809
+ ORDER BY q.byte_offset DESC LIMIT 1)
810
+ WHERE session_id IN (${IN_BATCH})`;
811
+ var COMPACTION_MID_LOOP = `
812
+ UPDATE compaction SET mid_loop = COALESCE((
813
+ SELECT i.delivery = 'tool_result' FROM inbound i
814
+ WHERE i.transcript_id = compaction.transcript_id AND i.session_id = compaction.session_id AND i.byte_offset < compaction.byte_offset
815
+ ORDER BY i.byte_offset DESC, i.block_index DESC LIMIT 1), 0)
816
+ WHERE session_id IN (${IN_BATCH})`;
817
+ var TURN_WAKE = `
818
+ UPDATE turn SET wake_cause = (
819
+ SELECT i.cause FROM fact f JOIN inbound i ON i.transcript_id = f.transcript_id AND i.byte_offset = f.byte_offset
820
+ WHERE f.fact_id = turn.fact_id_start ORDER BY i.block_index LIMIT 1)
821
+ WHERE session_id IN (${IN_BATCH})`;
822
+ var CLEAR_USAGE = `
823
+ DELETE FROM session_model_usage
824
+ WHERE session_id IN (${IN_BATCH}) AND session_id IN (SELECT session_id FROM request WHERE session_id IN (${IN_BATCH}))`;
825
+ var RECOMPUTE_USAGE = `
826
+ WITH ranked AS (
827
+ SELECT *, ROW_NUMBER() OVER (PARTITION BY session_id, request_id ORDER BY ts, transcript_id) AS pick
828
+ FROM request WHERE session_id IN (${IN_BATCH})
829
+ )
830
+ INSERT INTO session_model_usage (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, thinking_tokens, request_count)
831
+ SELECT session_id, model, SUM(input_tokens), SUM(output_tokens), SUM(cache_read_tokens), SUM(cache_creation_tokens), SUM(thinking_tokens), COUNT(*)
832
+ FROM ranked WHERE pick = 1 GROUP BY session_id, model`;
833
+ var STEPS = [REQUEST_ORDER, REQUEST_WAKE, INBOUND_QUEUED, COMPACTION_MID_LOOP, TURN_WAKE, CLEAR_USAGE, RECOMPUTE_USAGE];
834
+ function prepareAuditRollup(db) {
835
+ const statements = STEPS.map((sql) => db.prepare(sql));
836
+ return (sessionIds) => {
837
+ for (const statement of statements) statement.run({ sessionIds });
838
+ };
839
+ }
840
+
376
841
  // src/rollup.ts
377
842
  var BATCH = 400;
378
843
  var ROLLUP = `
@@ -409,12 +874,14 @@ function rollupSessions(graph, sessionIds) {
409
874
  if (unique.length === 0) return 0;
410
875
  const aggregate = graph.db.prepare(ROLLUP);
411
876
  const renumber = graph.db.prepare(RENUMBER_TURNS);
877
+ const audit = prepareAuditRollup(graph.db);
412
878
  return graph.db.transaction(() => {
413
879
  let updated = 0;
414
880
  for (let i = 0; i < unique.length; i += BATCH) {
415
881
  const batch = JSON.stringify(unique.slice(i, i + BATCH));
416
882
  renumber.run({ sessionIds: batch });
417
883
  updated += aggregate.run({ sessionIds: batch }).changes;
884
+ audit(batch);
418
885
  }
419
886
  return updated;
420
887
  })();
@@ -443,7 +910,96 @@ function reconcile(graph) {
443
910
  // src/refresh.ts
444
911
  import { promises as fs } from "fs";
445
912
  import { contentHash, resumePoint } from "@titan-design/locator";
446
- import { TranscriptParseError, extractTranscript } from "@titan-design/session-read";
913
+ import { TranscriptParseError as TranscriptParseError2, extractTranscript as extractTranscript2 } from "@titan-design/session-read";
914
+
915
+ // src/origin.ts
916
+ import { RELATIONS, sessionRef as sessionRef3 } from "@titan-design/session-read";
917
+ var NO_ORIGINS = Object.freeze({ requested: 0, applied: 0, events: 0, failed: false });
918
+ var SESSIONS_NEEDING_ORIGIN = `
919
+ SELECT s.session_id FROM session s LEFT JOIN session_origin o ON o.session_id = s.session_id
920
+ WHERE o.session_id IS NULL OR o.resolved_at < s.ended_at
921
+ ORDER BY s.session_id`;
922
+ var UPSERT_ORIGIN = `
923
+ INSERT OR REPLACE INTO session_origin (session_id, origin_system, agent_id, agent_name, parent_name, parent_session_id,
924
+ profile, model_alias, surface, isolation, depth, origin_kind, config_dir, spawn_cwd, spawned_at,
925
+ brief_chars, brief_excerpt, brief_path, launch_args, resolved_at)
926
+ VALUES (@sessionId, @originSystem, @agentId, @agentName, @parentName, @parentSessionId,
927
+ @profile, @modelAlias, @surface, @isolation, @depth, @originKind, @configDir, @spawnCwd, @spawnedAt,
928
+ @briefChars, @briefExcerpt, @briefPath, @launchArgs, @resolvedAt)`;
929
+ var UPSERT_EVENT = `
930
+ INSERT INTO session_external_event (session_id, ts, origin_system, kind, detail)
931
+ VALUES (@sessionId, @ts, COALESCE(@originSystem, (SELECT origin_system FROM session_origin WHERE session_id = @sessionId), 'unknown'), @kind, @detail)
932
+ ON CONFLICT (session_id, ts, kind) DO UPDATE SET origin_system = excluded.origin_system, detail = excluded.detail`;
933
+ var PROJECT_SUBAGENTS = `
934
+ INSERT INTO subagent (agent_ref, session_id, child_session_id, agent_type, label, started_at)
935
+ SELECT 'agent:' || origin_system || ':' || agent_id, parent_session_id, session_id, profile, agent_name, spawned_at
936
+ FROM session_origin WHERE parent_session_id IS NOT NULL AND agent_id IS NOT NULL
937
+ ON CONFLICT (agent_ref) DO UPDATE SET session_id = excluded.session_id, child_session_id = excluded.child_session_id,
938
+ agent_type = excluded.agent_type, label = excluded.label, started_at = COALESCE(excluded.started_at, started_at)`;
939
+ function sessionsNeedingOrigin(graph) {
940
+ return graph.db.prepare(SESSIONS_NEEDING_ORIGIN).all().map((r) => r.session_id);
941
+ }
942
+ async function resolveOrigins(graph, resolver) {
943
+ if (!resolver) return NO_ORIGINS;
944
+ const sessionIds = sessionsNeedingOrigin(graph);
945
+ try {
946
+ const resolution = await resolver(sessionIds);
947
+ const written = writeResolution(graph, resolution, (/* @__PURE__ */ new Date()).toISOString());
948
+ return { requested: sessionIds.length, ...written, failed: false };
949
+ } catch (err) {
950
+ return { requested: sessionIds.length, applied: 0, events: 0, failed: true, error: err instanceof Error ? err.message : String(err) };
951
+ } finally {
952
+ projectOrigins(graph);
953
+ }
954
+ }
955
+ function writeResolution(graph, resolution, resolvedAt) {
956
+ const upsertOrigin = graph.db.prepare(UPSERT_ORIGIN);
957
+ const upsertEvent = graph.db.prepare(UPSERT_EVENT);
958
+ return graph.db.transaction(() => {
959
+ let applied = 0;
960
+ for (const [sessionId, origin] of Object.entries(resolution.origins)) {
961
+ applied += upsertOrigin.run(originRow(sessionId, origin, resolvedAt)).changes;
962
+ }
963
+ let events = 0;
964
+ for (const e of resolution.externalEvents ?? []) {
965
+ events += upsertEvent.run({ sessionId: e.sessionId, ts: e.ts, kind: e.kind, detail: e.detail ?? null, originSystem: e.originSystem ?? null }).changes;
966
+ }
967
+ return { applied, events };
968
+ })();
969
+ }
970
+ function originRow(sessionId, o, resolvedAt) {
971
+ return {
972
+ sessionId,
973
+ originSystem: o.originSystem,
974
+ resolvedAt,
975
+ agentId: o.agentId ?? null,
976
+ agentName: o.agentName ?? null,
977
+ parentName: o.parentName ?? null,
978
+ parentSessionId: o.parentSessionId ?? null,
979
+ profile: o.profile ?? null,
980
+ modelAlias: o.modelAlias ?? null,
981
+ surface: o.surface ?? null,
982
+ isolation: o.isolation ?? null,
983
+ depth: o.depth ?? null,
984
+ originKind: o.originKind ?? null,
985
+ configDir: o.configDir ?? null,
986
+ spawnCwd: o.spawnCwd ?? null,
987
+ spawnedAt: o.spawnedAt ?? null,
988
+ briefChars: o.briefChars ?? null,
989
+ briefExcerpt: o.briefExcerpt ?? null,
990
+ briefPath: o.briefPath ?? null,
991
+ launchArgs: o.launchArgs ?? null
992
+ };
993
+ }
994
+ function projectOrigins(graph) {
995
+ const spawned = graph.db.prepare("SELECT session_id, parent_session_id, spawned_at, resolved_at FROM session_origin WHERE parent_session_id IS NOT NULL").all();
996
+ graph.db.transaction(() => {
997
+ graph.db.prepare(PROJECT_SUBAGENTS).run();
998
+ for (const o of spawned) {
999
+ graph.edges.assert({ sourceRef: sessionRef3(o.parent_session_id), relation: RELATIONS.SPAWNED, targetRef: sessionRef3(o.session_id), tValid: o.spawned_at ?? o.resolved_at });
1000
+ }
1001
+ })();
1002
+ }
447
1003
 
448
1004
  // src/tasks.ts
449
1005
  import { taskRef } from "@titan-design/session-read";
@@ -500,14 +1056,15 @@ async function indexTranscript(graph, transcript, options = {}) {
500
1056
  return { ...base, status: "unchanged", facts: 0 };
501
1057
  }
502
1058
  try {
503
- const delta = await extractTranscript(transcript.absolutePath, {
1059
+ const delta = await extractTranscript2(transcript.absolutePath, {
504
1060
  fromByteOffset: point.start,
505
1061
  priorPrefixHash: point.state === "rewritten" ? null : row.prefixHash,
506
1062
  subagentId: transcript.subagentId
507
1063
  });
508
1064
  const rewound = point.state === "rewritten" || delta.restartedFromZero;
509
1065
  if (rewound) purgeTranscript(graph, row.sourceId);
510
- applyDelta(graph, row.sourceId, delta);
1066
+ applyDelta(graph, row.sourceId, delta, { account: transcript.account });
1067
+ advanceFacet(graph.db, row.sourceId, rewound ? 0 : point.start, delta.lastByteOffset);
511
1068
  const stat2 = await fs.stat(transcript.absolutePath);
512
1069
  graph.transcripts.advance(transcript.displayPath, {
513
1070
  lastOffset: delta.lastByteOffset,
@@ -521,13 +1078,13 @@ async function indexTranscript(graph, transcript, options = {}) {
521
1078
  const tasks = taskIds.length > 0 ? await enrichTasks(graph, options.resolveTasks, taskIds) : NO_ENRICHMENT;
522
1079
  return { ...base, sessionIds, tasks, status: rewound ? "rewound" : "indexed", facts: delta.facts.length };
523
1080
  } catch (err) {
524
- if (!(err instanceof TranscriptParseError)) throw err;
1081
+ if (!(err instanceof TranscriptParseError2)) throw err;
525
1082
  graph.transcripts.markStatus(transcript.displayPath, "quarantined", err.message);
526
1083
  return { ...base, status: "quarantined", facts: 0, reason: err.message };
527
1084
  }
528
1085
  }
529
1086
  async function refreshCorpus(graph, transcripts, options = {}) {
530
- const { resolveTasks, ...perTranscript } = options;
1087
+ const { resolveTasks, resolveOrigins: originResolver, full, facetLimit, ...perTranscript } = options;
531
1088
  const counts = { indexed: 0, unchanged: 0, rewound: 0, missing: 0, quarantined: 0 };
532
1089
  const touched = [];
533
1090
  let facts = 0;
@@ -537,11 +1094,15 @@ async function refreshCorpus(graph, transcripts, options = {}) {
537
1094
  facts += outcome.facts;
538
1095
  touched.push(...outcome.sessionIds);
539
1096
  }
540
- const turnsRolledUp = rollupSessions(graph, options.full ? allSessionIds(graph) : touched);
1097
+ const facets = await backfillFacets(graph, transcripts, { limit: facetLimit });
1098
+ touched.push(...facets.sessionIds);
1099
+ const turnsRolledUp = rollupSessions(graph, full ? allSessionIds(graph) : touched);
1100
+ const origins = await resolveOrigins(graph, originResolver);
541
1101
  const reconciled = reconcile(graph);
542
1102
  const tasks = await enrichTasks(graph, resolveTasks, allTaskIds(graph));
543
1103
  const markedMissing = await markMissing(graph, transcripts);
544
- return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, markedMissing };
1104
+ const facetSummary = { facetsBackfilled: facets.backfilled, facetBacklog: facets.backlog };
1105
+ return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, origins, markedMissing, ...facetSummary };
545
1106
  }
546
1107
  async function markMissing(graph, discovered) {
547
1108
  const present = new Set(discovered.map((t) => t.displayPath));
@@ -775,14 +1336,28 @@ function normalizedUsage(graph, ref) {
775
1336
  return accumulator.summaries();
776
1337
  }
777
1338
  export {
1339
+ AUDIT_DDL,
1340
+ AUDIT_FACET,
1341
+ AUDIT_MIGRATION_NAME,
1342
+ AUDIT_TABLES,
1343
+ DEFAULT_FACET_LIMIT,
778
1344
  DERIVED_TABLES,
779
1345
  DOMAIN_DDL,
1346
+ EPISODE_TABLE,
1347
+ FACET_TABLE,
780
1348
  KIT,
781
1349
  MIGRATIONS,
782
1350
  NO_ENRICHMENT,
1351
+ NO_ORIGINS,
1352
+ ORIGIN_DDL,
1353
+ ORIGIN_MIGRATION_NAME,
1354
+ ORIGIN_TABLES,
1355
+ ORIGIN_VIEWS,
783
1356
  allSessionIds,
784
1357
  allTaskIds,
1358
+ applyAudit,
785
1359
  applyDelta,
1360
+ backfillFacets,
786
1361
  enrichTasks,
787
1362
  indexCodexSource,
788
1363
  indexTranscript,
@@ -795,6 +1370,8 @@ export {
795
1370
  refreshCorpus,
796
1371
  resetIndex,
797
1372
  resolveConversationAlias,
798
- rollupSessions
1373
+ resolveOrigins,
1374
+ rollupSessions,
1375
+ sessionsNeedingOrigin
799
1376
  };
800
1377
  //# sourceMappingURL=index.js.map