@titan-design/session-graph 0.10.0 → 0.11.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/README.md +38 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +101 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -174,6 +174,44 @@ Everything except `transcript` is derivable, which is what makes the schema safe
|
|
|
174
174
|
by drop-and-rederive.
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
## Injected context
|
|
178
|
+
|
|
179
|
+
A `user` record carries more than the human typed. The harness, hooks and agent-chat put
|
|
180
|
+
their own blocks there, and indexing them would make every session match its own
|
|
181
|
+
reminders and briefs. `stripInjected` removes them before a prompt reaches `search_fts`,
|
|
182
|
+
and `readIndexedText` applies it again on readback, so a miner excerpt shows the same
|
|
183
|
+
text. Only prompt spans change. `fact`, `normalized_event` and the audit tables keep
|
|
184
|
+
every line, and a turn with nothing left indexes no prompt span.
|
|
185
|
+
An index built before this rule keeps its old prompt spans until `resetIndex` rebuilds it.
|
|
186
|
+
|
|
187
|
+
- **Whole line.** A line whose session-read inbound cause is anything but `human_typed` or
|
|
188
|
+
`tool_result` indexes no prompt. That covers `isMeta` lines (hook and SessionStart
|
|
189
|
+
bootstrap output, skill loads, harness resumes), peer channel messages, task
|
|
190
|
+
notifications, compaction summaries, scheduled wakeups, image notes and local commands.
|
|
191
|
+
- **Headless prompts.** A line whose `promptSource` is `sdk` indexes no prompt. The host
|
|
192
|
+
writes `sdk` for every headless turn, which is how an agent-chat spawn brief with no
|
|
193
|
+
framing is told apart from a typed prompt. The cost: a person running `claude -p` also
|
|
194
|
+
writes `sdk` turns, and those are excluded too. Pass `indexSdkPrompts: true` to
|
|
195
|
+
`refreshCorpus`, `indexTranscript` or `applyDelta` to keep them. `isUntypedPrompt` exposes
|
|
196
|
+
the rule.
|
|
197
|
+
- **Blocks cut on their own lines.** session-read's closed `INJECTED_MARKERS`
|
|
198
|
+
(`<system-reminder>`, `<channel source="...">`, `<task-notification>`), plus
|
|
199
|
+
`<user-prompt-submit-hook>`, `<command-name>`, `<command-message>`,
|
|
200
|
+
`<local-command-caveat>`, `<local-command-stdout>`, `<local-command-stderr>`,
|
|
201
|
+
`<bash-stdout>` and `<bash-stderr>`. A block is cut only when its opening tag starts a
|
|
202
|
+
line and its closing tag ends one (another cut block may follow on the same line). Nested
|
|
203
|
+
tags of the same name are balanced. A tag quoted inside a sentence stays.
|
|
204
|
+
`[Request interrupted by user…]` notes are cut wherever they sit.
|
|
205
|
+
- **Kept words.** `<command-args>` and `<bash-input>` lose their tags and keep their
|
|
206
|
+
contents, because the human typed them.
|
|
207
|
+
- **Whole turn by text.** session-read's unclosed markers heading the text (local-command
|
|
208
|
+
output, compaction summary, image note, loop wakeup, the agent-chat orientation header).
|
|
209
|
+
agent-chat puts no tag around a spawn brief, so a brief typed into a live session is known
|
|
210
|
+
by the framing agent-chat writes itself: a `# Predecessor:` handover, or the isolated or
|
|
211
|
+
shared worktree note that ends the brief.
|
|
212
|
+
- **Tool-result echoes.** `tool_result` blocks never enter prompt text; a user line's prompt
|
|
213
|
+
span holds its text blocks only. Background results arrive as `<task-notification>`.
|
|
214
|
+
|
|
177
215
|
## Mixed harnesses
|
|
178
216
|
|
|
179
217
|
`indexCodexSource(graph, source)` consumes descriptors returned by session-read's
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Db, WatermarkTable, EdgeTable, SpanFtsTables, Migration } from '@titan-design/store-sqlite';
|
|
2
|
-
import { TranscriptDelta, DiscoveredTranscript, SessionSourceDescriptor, SessionUsageSummary } from '@titan-design/session-read';
|
|
2
|
+
import { TranscriptDelta, DiscoveredTranscript, SessionSourceDescriptor, WakeCause, Inbound, SessionUsageSummary } from '@titan-design/session-read';
|
|
3
3
|
|
|
4
4
|
/** One open session graph: the connection plus the kit helpers bound to its tables. */
|
|
5
5
|
interface SessionGraph {
|
|
@@ -111,6 +111,8 @@ declare function applyDelta(graph: SessionGraph, transcriptId: number, delta: Tr
|
|
|
111
111
|
interface DeltaSource {
|
|
112
112
|
/** The Claude config dir the transcript was found under; `null` when discovery did not say. */
|
|
113
113
|
account?: string | null;
|
|
114
|
+
/** Index prompts the host labels `promptSource: "sdk"` (spawned agents and `claude -p`). Off by default; see README "Injected context". */
|
|
115
|
+
indexSdkPrompts?: boolean;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
/**
|
|
@@ -365,6 +367,8 @@ interface IndexOptions {
|
|
|
365
367
|
resolveOrigins?: OriginResolver;
|
|
366
368
|
/** Fill PR state, merge, close and review rounds from the caller's forge. Runs once per `refreshCorpus` pass. Absent means transcripts alone. */
|
|
367
369
|
resolvePrs?: PrResolver;
|
|
370
|
+
/** Index prompts the host labels `promptSource: "sdk"`: spawned agents' briefs, and `claude -p` runs a person typed. Default false. */
|
|
371
|
+
indexSdkPrompts?: boolean;
|
|
368
372
|
}
|
|
369
373
|
interface RefreshOptions extends IndexOptions {
|
|
370
374
|
/** Which sender profiles' chat verdicts count toward review rounds. Defaults to `reviewer` and `*-reviewer`. */
|
|
@@ -485,6 +489,12 @@ interface NormalizedIndexResult {
|
|
|
485
489
|
/** Replay changed sources into temporary staging; swap rows and watermark atomically. */
|
|
486
490
|
declare function indexCodexSource(graph: SessionGraph, source: SessionSourceDescriptor): Promise<NormalizedIndexResult>;
|
|
487
491
|
|
|
492
|
+
declare function isInjectedCause(cause: WakeCause): boolean;
|
|
493
|
+
/** A line nobody typed: an injected cause, or a headless `sdk` prompt unless the caller keeps those. */
|
|
494
|
+
declare function isUntypedPrompt(inbound: Pick<Inbound, "cause" | "promptSource">, indexSdkPrompts?: boolean): boolean;
|
|
495
|
+
/** Prompt text with every injected block removed; empty when nothing the human wrote is left. */
|
|
496
|
+
declare function stripInjected(text: string): string;
|
|
497
|
+
|
|
488
498
|
interface IndexedSpan {
|
|
489
499
|
sourceId: number;
|
|
490
500
|
byteOffset: number;
|
|
@@ -520,4 +530,4 @@ declare function normalizedUsage(graph: SessionGraph, ref: string): NormalizedUs
|
|
|
520
530
|
/** Ambiguity is explicit; workspace session bodies remain in their original namespace. */
|
|
521
531
|
declare function resolveConversationAlias(db: Db, legacyRef: string): string | null;
|
|
522
532
|
|
|
523
|
-
export { AUDIT_DDL, AUDIT_FACET, AUDIT_MIGRATION_NAME, AUDIT_TABLES, type BackfillOptions, type BackfillSummary, type ConversationSummary, DEFAULT_FACET_LIMIT, DERIVED_TABLES, DOMAIN_DDL, type DeltaSource, EPISODE_TABLE, type EpisodeRow, type ExternalEvent, FACET_TABLE, type IndexOptions, type IndexedSpan, KIT, MIGRATIONS, NO_ENRICHMENT, NO_ORIGINS, NO_PR_OUTCOMES, NO_TASK_LINK, type NormalizedIndexResult, type NormalizedUsageSummary, ORIGIN_DDL, ORIGIN_MIGRATION_NAME, ORIGIN_TABLES, ORIGIN_TASK_LINK_MIGRATION_NAME, ORIGIN_VIEWS, type OpenSessionGraphOptions, type OriginEnrichment, type OriginResolution, type OriginResolver, type PrEnrichment, type PrKey, type PrResolution, type PrResolver, type PriceInput, REVIEW_DDL, REVIEW_TABLE, REVIEW_VERDICT_MIGRATION_NAME, type ReconcileCounts, type RefreshOptions, type RefreshSummary, type ResolvedOrigin, type ResolvedPr, type ResolvedReview, type ResolvedTask, type ReviewProjection, type ReviewRoundOptions, type ReviewerProfilePredicate, type SessionGraph, type SyncPricesOptions, type TaskEnrichment, type TaskLinkSource, type TaskResolution, type TaskResolver, type TranscriptOutcome, allSessionIds, allTaskIds, applyAudit, applyDelta, backfillFacets, countRounds, enrichPrs, enrichTasks, indexCodexSource, indexTranscript, isReviewerProfile, normalizedSessions, normalizedUsage, openSessionGraph, projectReviewRounds, prsNeedingOutcome, purgeTranscript, readIndexedText, reconcile, refreshCorpus, replaceEpisodes, resetIndex, resolveConversationAlias, resolveOrigins, rollupSessions, sessionsNeedingOrigin, syncPrices };
|
|
533
|
+
export { AUDIT_DDL, AUDIT_FACET, AUDIT_MIGRATION_NAME, AUDIT_TABLES, type BackfillOptions, type BackfillSummary, type ConversationSummary, DEFAULT_FACET_LIMIT, DERIVED_TABLES, DOMAIN_DDL, type DeltaSource, EPISODE_TABLE, type EpisodeRow, type ExternalEvent, FACET_TABLE, type IndexOptions, type IndexedSpan, KIT, MIGRATIONS, NO_ENRICHMENT, NO_ORIGINS, NO_PR_OUTCOMES, NO_TASK_LINK, type NormalizedIndexResult, type NormalizedUsageSummary, ORIGIN_DDL, ORIGIN_MIGRATION_NAME, ORIGIN_TABLES, ORIGIN_TASK_LINK_MIGRATION_NAME, ORIGIN_VIEWS, type OpenSessionGraphOptions, type OriginEnrichment, type OriginResolution, type OriginResolver, type PrEnrichment, type PrKey, type PrResolution, type PrResolver, type PriceInput, REVIEW_DDL, REVIEW_TABLE, REVIEW_VERDICT_MIGRATION_NAME, type ReconcileCounts, type RefreshOptions, type RefreshSummary, type ResolvedOrigin, type ResolvedPr, type ResolvedReview, type ResolvedTask, type ReviewProjection, type ReviewRoundOptions, type ReviewerProfilePredicate, type SessionGraph, type SyncPricesOptions, type TaskEnrichment, type TaskLinkSource, type TaskResolution, type TaskResolver, type TranscriptOutcome, allSessionIds, allTaskIds, applyAudit, applyDelta, backfillFacets, countRounds, enrichPrs, enrichTasks, indexCodexSource, indexTranscript, isInjectedCause, isReviewerProfile, isUntypedPrompt, normalizedSessions, normalizedUsage, openSessionGraph, projectReviewRounds, prsNeedingOutcome, purgeTranscript, readIndexedText, reconcile, refreshCorpus, replaceEpisodes, resetIndex, resolveConversationAlias, resolveOrigins, rollupSessions, sessionsNeedingOrigin, stripInjected, syncPrices };
|
package/dist/index.js
CHANGED
|
@@ -667,6 +667,89 @@ function advanceFacet(db, transcriptId, fromByteOffset, indexedTo, version = EXT
|
|
|
667
667
|
db.prepare(`UPDATE ${FACET_TABLE} SET indexed_to = ? WHERE transcript_id = ? AND facet = ? AND version = ?`).run(indexedTo, transcriptId, AUDIT_FACET, version);
|
|
668
668
|
}
|
|
669
669
|
|
|
670
|
+
// src/injected-text.ts
|
|
671
|
+
import { hasMarker, INJECTED_MARKERS } from "@titan-design/session-read";
|
|
672
|
+
var INJECTED_CAUSES = /* @__PURE__ */ new Set([
|
|
673
|
+
"compaction",
|
|
674
|
+
"channel_message",
|
|
675
|
+
"channel_system",
|
|
676
|
+
"task_notification",
|
|
677
|
+
"scheduled_wakeup",
|
|
678
|
+
"usage_limit_resume",
|
|
679
|
+
"harness_resume",
|
|
680
|
+
"local_command",
|
|
681
|
+
"image_meta",
|
|
682
|
+
"hook_or_reminder"
|
|
683
|
+
]);
|
|
684
|
+
function isInjectedCause(cause) {
|
|
685
|
+
return INJECTED_CAUSES.has(cause);
|
|
686
|
+
}
|
|
687
|
+
function isUntypedPrompt(inbound, indexSdkPrompts = false) {
|
|
688
|
+
return isInjectedCause(inbound.cause) || !indexSdkPrompts && inbound.promptSource === "sdk";
|
|
689
|
+
}
|
|
690
|
+
var CUT_TAGS = [
|
|
691
|
+
...Object.values(INJECTED_MARKERS).flatMap((m) => m.close ? [m.close.slice(2, -1)] : []),
|
|
692
|
+
"user-prompt-submit-hook",
|
|
693
|
+
"command-name",
|
|
694
|
+
"command-message",
|
|
695
|
+
"local-command-caveat",
|
|
696
|
+
"local-command-stdout",
|
|
697
|
+
"local-command-stderr",
|
|
698
|
+
"bash-stdout",
|
|
699
|
+
"bash-stderr"
|
|
700
|
+
].join("|");
|
|
701
|
+
var CUT_OPEN = new RegExp(`<(${CUT_TAGS})(?=[\\s>])[^>]*>`, "g");
|
|
702
|
+
var LINE_END_OR_BLOCK = new RegExp(`^[ \\t]*(?:\\r?\\n|$|<(?:${CUT_TAGS})(?=[\\s>]))`);
|
|
703
|
+
function opensBlock(text, at, keptFrom) {
|
|
704
|
+
const lead = text.slice(Math.max(text.lastIndexOf("\n", at - 1) + 1, keptFrom), at);
|
|
705
|
+
return lead.trim() === "";
|
|
706
|
+
}
|
|
707
|
+
function balancedClose(text, tag, from) {
|
|
708
|
+
const tags = new RegExp(`<(/?)${tag}(?=[\\s>])[^>]*>`, "g");
|
|
709
|
+
tags.lastIndex = from;
|
|
710
|
+
let depth = 1;
|
|
711
|
+
for (let m = tags.exec(text); m; m = tags.exec(text)) {
|
|
712
|
+
depth += m[1] ? -1 : 1;
|
|
713
|
+
if (depth === 0) return tags.lastIndex;
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
function closesLine(text, at) {
|
|
718
|
+
return LINE_END_OR_BLOCK.test(text.slice(at));
|
|
719
|
+
}
|
|
720
|
+
function cutBlocks(text) {
|
|
721
|
+
let kept = "";
|
|
722
|
+
let keptFrom = 0;
|
|
723
|
+
CUT_OPEN.lastIndex = 0;
|
|
724
|
+
for (let open = CUT_OPEN.exec(text); open; open = CUT_OPEN.exec(text)) {
|
|
725
|
+
if (!opensBlock(text, open.index, keptFrom)) continue;
|
|
726
|
+
const end = balancedClose(text, open[1], CUT_OPEN.lastIndex);
|
|
727
|
+
if (end === null || !closesLine(text, end)) continue;
|
|
728
|
+
kept += text.slice(keptFrom, open.index);
|
|
729
|
+
keptFrom = end;
|
|
730
|
+
CUT_OPEN.lastIndex = end;
|
|
731
|
+
}
|
|
732
|
+
return kept + text.slice(keptFrom);
|
|
733
|
+
}
|
|
734
|
+
var INTERRUPTION = /\[Request interrupted by user[^\]\n]*\]/g;
|
|
735
|
+
var HUMAN_WRAPPERS = ["command-args", "bash-input"].map((tag) => new RegExp(`</?${tag}>`, "g"));
|
|
736
|
+
var WHOLE_TURN_MARKERS = Object.keys(INJECTED_MARKERS).filter((name) => INJECTED_MARKERS[name].close === null);
|
|
737
|
+
var SPAWN_BRIEF_FRAMING = [
|
|
738
|
+
/^# Predecessor: .*\n\nYou are taking over from /,
|
|
739
|
+
/Commit your work there; nothing outside it is yours to change\.\s*$/,
|
|
740
|
+
/You may be sharing it with other agents, so stay inside the paths you were given\.\s*$/
|
|
741
|
+
];
|
|
742
|
+
function isWholeTurnInjected(text) {
|
|
743
|
+
const trimmed = text.trim();
|
|
744
|
+
return WHOLE_TURN_MARKERS.some((name) => hasMarker(trimmed, name)) || SPAWN_BRIEF_FRAMING.some((re) => re.test(trimmed));
|
|
745
|
+
}
|
|
746
|
+
function stripInjected(text) {
|
|
747
|
+
let kept = cutBlocks(text);
|
|
748
|
+
for (const re of [INTERRUPTION, ...HUMAN_WRAPPERS]) kept = kept.replace(re, "");
|
|
749
|
+
if (isWholeTurnInjected(kept)) return "";
|
|
750
|
+
return kept.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
751
|
+
}
|
|
752
|
+
|
|
670
753
|
// src/apply.ts
|
|
671
754
|
import { RELATIONS, sessionRef } from "@titan-design/session-read";
|
|
672
755
|
function applyDelta(graph, transcriptId, delta, source = {}) {
|
|
@@ -677,6 +760,7 @@ function applyDelta(graph, transcriptId, delta, source = {}) {
|
|
|
677
760
|
applyAssets(graph.db, delta);
|
|
678
761
|
applyPhases(graph.db, transcriptId, delta);
|
|
679
762
|
applyLinkedRows(graph, transcriptId, delta);
|
|
763
|
+
applySpans(graph, transcriptId, delta, source.indexSdkPrompts ?? false);
|
|
680
764
|
applyReviewVerdicts(graph.db, transcriptId, delta);
|
|
681
765
|
applyAudit(graph.db, transcriptId, delta);
|
|
682
766
|
})();
|
|
@@ -815,8 +899,14 @@ function applyLinkedRows(graph, transcriptId, delta) {
|
|
|
815
899
|
const linkTranscript = db.prepare("INSERT INTO subagent (agent_ref, child_session_id) VALUES (?, ?) ON CONFLICT (agent_ref) DO UPDATE SET child_session_id = excluded.child_session_id");
|
|
816
900
|
for (const l of delta.subagentTranscripts) linkTranscript.run(l.agentRef, l.childSessionId);
|
|
817
901
|
for (const e of delta.edges) assertTranscriptEdge(graph, { sourceRef: e.sourceRef, relation: e.relation, targetRef: e.targetRef, tValid: e.ts, factId: factId(e.byteOffset) });
|
|
902
|
+
}
|
|
903
|
+
function applySpans(graph, transcriptId, delta, indexSdkPrompts) {
|
|
904
|
+
const injectedLines = new Set(delta.inbound.filter((i) => isUntypedPrompt(i, indexSdkPrompts)).map((i) => i.byteOffset));
|
|
818
905
|
for (const span of delta.spans) {
|
|
819
|
-
|
|
906
|
+
const isPrompt = span.field === "prompt";
|
|
907
|
+
const text = !isPrompt ? span.text : injectedLines.has(span.byteOffset) ? "" : stripInjected(span.text);
|
|
908
|
+
if (isPrompt && !text) continue;
|
|
909
|
+
graph.spans.index({ ownerRef: sessionRef(span.sessionId), field: span.field, sourceId: transcriptId, byteOffset: span.byteOffset, byteLength: span.byteLength }, text);
|
|
820
910
|
}
|
|
821
911
|
}
|
|
822
912
|
function assertTranscriptEdge(graph, edge) {
|
|
@@ -1403,7 +1493,7 @@ async function indexTranscript(graph, transcript, options = {}) {
|
|
|
1403
1493
|
});
|
|
1404
1494
|
const rewound = point.state === "rewritten" || delta.restartedFromZero;
|
|
1405
1495
|
if (rewound) purgeTranscript(graph, row.sourceId);
|
|
1406
|
-
applyDelta(graph, row.sourceId, delta, { account: transcript.account });
|
|
1496
|
+
applyDelta(graph, row.sourceId, delta, { account: transcript.account, indexSdkPrompts: options.indexSdkPrompts });
|
|
1407
1497
|
advanceFacet(graph.db, row.sourceId, rewound ? 0 : point.start, delta.lastByteOffset);
|
|
1408
1498
|
const stat2 = await fs.stat(transcript.absolutePath);
|
|
1409
1499
|
graph.transcripts.advance(transcript.displayPath, {
|
|
@@ -1556,7 +1646,8 @@ function insertObservation(graph, transcriptId, o) {
|
|
|
1556
1646
|
);
|
|
1557
1647
|
}
|
|
1558
1648
|
function observationText(o) {
|
|
1559
|
-
if (o.kind === "message") return o.content.map((p) => ({ field:
|
|
1649
|
+
if (o.kind === "message" && o.role === "user") return o.content.map((p) => ({ field: "prompt", text: stripInjected(p.text).slice(0, SPAN_TEXT_CAP), locator: p.locator })).filter((p) => p.text);
|
|
1650
|
+
if (o.kind === "message") return o.content.map((p) => ({ field: "assistant_response", text: p.text.slice(0, SPAN_TEXT_CAP), locator: p.locator }));
|
|
1560
1651
|
if (o.kind === "tool_call" && o.inputLocator) return [{ field: "tool_input", text: normalizedSearchText(o.input), locator: o.inputLocator }];
|
|
1561
1652
|
if (o.kind === "tool_result" && o.outputLocator) return [{ field: "tool_result", text: normalizedSearchText(o.output), locator: o.outputLocator }];
|
|
1562
1653
|
return [];
|
|
@@ -1659,6 +1750,10 @@ function* stagedRows(graph, table) {
|
|
|
1659
1750
|
import { contentHash as contentHash3, prefixHash } from "@titan-design/locator";
|
|
1660
1751
|
import { readSessionSourceText, readSessionText, SessionUsageAccumulator } from "@titan-design/session-read";
|
|
1661
1752
|
async function readIndexedText(graph, span) {
|
|
1753
|
+
const text = await readSourceText(graph, span);
|
|
1754
|
+
return text !== null && span.field === "prompt" ? stripInjected(text) : text;
|
|
1755
|
+
}
|
|
1756
|
+
async function readSourceText(graph, span) {
|
|
1662
1757
|
const row = graph.db.prepare(`SELECT n.locators FROM normalized_span n JOIN search_span s USING(span_id)
|
|
1663
1758
|
WHERE s.source_id = ? AND s.byte_offset = ? AND s.field = ?`).get(span.sourceId, span.byteOffset, span.field);
|
|
1664
1759
|
if (row) {
|
|
@@ -1766,7 +1861,9 @@ export {
|
|
|
1766
1861
|
enrichTasks,
|
|
1767
1862
|
indexCodexSource,
|
|
1768
1863
|
indexTranscript,
|
|
1864
|
+
isInjectedCause,
|
|
1769
1865
|
isReviewerProfile,
|
|
1866
|
+
isUntypedPrompt,
|
|
1770
1867
|
normalizedSessions,
|
|
1771
1868
|
normalizedUsage,
|
|
1772
1869
|
openSessionGraph,
|
|
@@ -1782,6 +1879,7 @@ export {
|
|
|
1782
1879
|
resolveOrigins,
|
|
1783
1880
|
rollupSessions,
|
|
1784
1881
|
sessionsNeedingOrigin,
|
|
1882
|
+
stripInjected,
|
|
1785
1883
|
syncPrices
|
|
1786
1884
|
};
|
|
1787
1885
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/graph.ts","../src/audit-schema.ts","../src/audit-schema-v5.ts","../src/audit-schema-v6.ts","../src/audit-schema-v7.ts","../src/audit-schema-v8.ts","../src/normalized-schema.ts","../src/schema.ts","../src/audit-apply.ts","../src/facet.ts","../src/apply.ts","../src/purge.ts","../src/audit-rollup.ts","../src/rollup.ts","../src/refresh.ts","../src/review-rounds.ts","../src/outcomes.ts","../src/origin.ts","../src/tasks.ts","../src/episodes.ts","../src/prices.ts","../src/normalized-index.ts","../src/normalized-project.ts","../src/normalized-query.ts"],"sourcesContent":["import { EdgeTable, SpanFtsTables, WatermarkTable, openDatabase, runMigrations, type Db } from \"@titan-design/store-sqlite\";\nimport { DERIVED_TABLES, KIT, MIGRATIONS } from \"./schema.js\";\n\n/** One open session graph: the connection plus the kit helpers bound to its tables. */\nexport interface SessionGraph {\n db: Db;\n transcripts: WatermarkTable;\n edges: EdgeTable;\n spans: SpanFtsTables;\n}\n\nexport interface OpenSessionGraphOptions {\n /**\n * The highest migration version the caller owns, checked before any migration runs.\n * A product layering its own tables on this graph passes its own top version, not this\n * package's: `MIGRATIONS` here is one band of a shared database, never the top of it.\n */\n schemaVersion?: number;\n}\n\nexport function openSessionGraph(dbPath: string, options: OpenSessionGraphOptions = {}): SessionGraph {\n const db = openDatabase(dbPath, { schemaVersion: options.schemaVersion });\n runMigrations(db, MIGRATIONS);\n return {\n db,\n transcripts: new WatermarkTable(db, { name: KIT.watermark }),\n edges: new EdgeTable(db, { name: KIT.edge }),\n spans: new SpanFtsTables(db, { name: KIT.spanFts }),\n };\n}\n\n/**\n * Clear every derived row and rewind all watermarks so the next refresh\n * rebuilds from byte 0. Rewinding alone is not enough: the accumulating\n * upserts would add a second copy of every count.\n */\nexport function resetIndex(graph: SessionGraph): void {\n graph.db.transaction(() => {\n for (const table of DERIVED_TABLES) graph.db.exec(`DELETE FROM \"${table}\"`);\n graph.spans.clearIndex();\n for (const row of graph.transcripts.list()) graph.transcripts.rewind(row.sourceKey);\n })();\n}\n\nexport function allSessionIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT session_id FROM session\").all() as { session_id: string }[]).map((r) => r.session_id);\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const AUDIT_MIGRATION_NAME = \"audit tables\";\n\n/** Tables written per transcript by `applyAudit` and dropped by `purgeTranscript`. */\nexport const AUDIT_TABLES = [\n \"request\",\n \"tool_call\",\n \"inbound\",\n \"context_block\",\n \"compaction\",\n \"queue_op\",\n \"session_signal\",\n \"cost_state_observation\",\n] as const;\n\n/** Per-transcript read state for each audit facet; cleared with the audit rows so a re-read starts clean. */\nexport const FACET_TABLE = \"transcript_facet\";\n\nexport const AUDIT_DDL = `\n CREATE TABLE IF NOT EXISTS request (\n transcript_id INTEGER NOT NULL,\n request_id TEXT NOT NULL,\n byte_offset INTEGER NOT NULL, -- first line seen for this request\n session_id TEXT NOT NULL,\n message_id TEXT,\n ts TEXT NOT NULL,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_5m INTEGER NOT NULL DEFAULT 0,\n cache_creation_1h INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n thinking_tokens INTEGER NOT NULL DEFAULT 0,\n context_tokens INTEGER NOT NULL DEFAULT 0, -- input + cache_read + cache_creation\n service_tier TEXT,\n is_sidechain INTEGER NOT NULL DEFAULT 0,\n -- rollup-owned, recomputed for touched sessions:\n seq_in_session INTEGER,\n gap_ms INTEGER, -- since previous request in the session\n ctx_delta INTEGER, -- context_tokens - (prev.context_tokens + prev.output_tokens)\n wake_cause TEXT,\n wake_delivery TEXT,\n wake_detail TEXT, -- tool name, channel sender, or NULL\n wake_tool_family TEXT,\n wake_mcp_server TEXT,\n wake_offset INTEGER, -- byte_offset of the inbound row\n PRIMARY KEY (transcript_id, request_id)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_request_session_ts ON request(session_id, ts);\n CREATE INDEX IF NOT EXISTS idx_request_ts ON request(ts);\n CREATE INDEX IF NOT EXISTS idx_request_id ON request(request_id);\n\n CREATE TABLE IF NOT EXISTS tool_call (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, tool_use_id TEXT NOT NULL,\n name TEXT NOT NULL, family TEXT NOT NULL, mcp_server TEXT, input_chars INTEGER NOT NULL,\n PRIMARY KEY (transcript_id, byte_offset, block_index)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_tool_call_use ON tool_call(tool_use_id);\n\n CREATE TABLE IF NOT EXISTS inbound (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL,\n cause TEXT NOT NULL, delivery TEXT NOT NULL, detail TEXT,\n origin_server TEXT, from_name TEXT, msg_id TEXT, tool_use_id TEXT,\n is_error INTEGER NOT NULL DEFAULT 0, content_hash TEXT NOT NULL, chars INTEGER NOT NULL,\n queued_ms INTEGER, -- rollup-owned\n PRIMARY KEY (transcript_id, byte_offset, block_index)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_inbound_session ON inbound(session_id, byte_offset);\n\n CREATE TABLE IF NOT EXISTS context_block (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, source TEXT NOT NULL,\n tool_use_id TEXT, attachment_type TEXT, chars INTEGER NOT NULL, is_media INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (transcript_id, byte_offset, block_index)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_context_block_session ON context_block(session_id, source);\n\n CREATE TABLE IF NOT EXISTS compaction (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, trigger TEXT,\n pre_tokens INTEGER, post_tokens INTEGER, dropped_tokens INTEGER, duration_ms INTEGER,\n mid_loop INTEGER, -- rollup-owned: previous inbound was a tool_result\n PRIMARY KEY (transcript_id, byte_offset)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS queue_op (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, operation TEXT NOT NULL,\n content_hash TEXT, origin_server TEXT,\n PRIMARY KEY (transcript_id, byte_offset)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_queue_op_hash ON queue_op(session_id, content_hash);\n\n CREATE TABLE IF NOT EXISTS session_signal (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, signal TEXT NOT NULL, detail TEXT, tool_use_id TEXT,\n -- one Bash block can emit up to three signals (commit, push, pr_create); the signal name disambiguates them\n PRIMARY KEY (transcript_id, byte_offset, block_index, signal)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_signal_session ON session_signal(session_id, ts);\n\n CREATE TABLE IF NOT EXISTS cost_state_observation (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT, total_cost_usd REAL NOT NULL, model_usage TEXT NOT NULL,\n PRIMARY KEY (transcript_id, byte_offset)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS transcript_facet (\n transcript_id INTEGER NOT NULL, facet TEXT NOT NULL,\n version INTEGER NOT NULL, indexed_to INTEGER NOT NULL,\n PRIMARY KEY (transcript_id, facet)\n ) WITHOUT ROWID;\n`;\n\n/** `[table, column, type]`; `turn.wake_cause` is rollup-owned. */\nexport const AUDIT_COLUMNS = [\n [\"session\", \"account\", \"TEXT\"],\n [\"turn\", \"wake_cause\", \"TEXT\"],\n [\"task\", \"estimate\", \"REAL\"],\n [\"pr\", \"review_rounds\", \"INTEGER\"],\n [\"pr\", \"closed_at\", \"TEXT\"],\n [\"pr\", \"outcome_checked_at\", \"TEXT\"],\n] as const;\n\n/** The version 4 body. Safe to run twice: a database can reach version 4 after a partial manual repair. */\nexport function applyAuditSchema(db: Db): void {\n db.exec(AUDIT_DDL);\n for (const [table, column, type] of AUDIT_COLUMNS) addColumnIfMissing(db, table, column, type);\n}\n\n/** SQLite's `ADD COLUMN` has no `IF NOT EXISTS`, and a repeat throws \"duplicate column name\". */\nexport function addColumnIfMissing(db: Db, table: string, column: string, type: string): void {\n const present = db.prepare(\"SELECT 1 FROM pragma_table_info(?) WHERE name = ?\").get(table, column);\n if (!present) db.exec(`ALTER TABLE \"${table}\" ADD COLUMN \"${column}\" ${type}`);\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const ORIGIN_MIGRATION_NAME = \"origin, episodes, prices\";\n\n/** Re-derivable from their own sources rather than from transcripts, so `resetIndex` clears them and the next pass refills them. */\nexport const ORIGIN_TABLES = [\"session_origin\", \"session_external_event\"] as const;\n\n/** Written from transcript offsets by `replaceEpisodes`; a rewritten transcript invalidates them, so purge drops them by session. */\nexport const EPISODE_TABLE = \"episode\";\n\nexport const ORIGIN_DDL = `\n CREATE TABLE IF NOT EXISTS session_origin ( -- not derived from transcripts\n session_id TEXT PRIMARY KEY,\n origin_system TEXT NOT NULL, -- 'agent-chat'\n agent_id TEXT, agent_name TEXT, parent_name TEXT, parent_session_id TEXT,\n profile TEXT, model_alias TEXT, surface TEXT, isolation TEXT, depth INTEGER,\n origin_kind TEXT, -- spawned | adopted | inherited | human\n config_dir TEXT, spawn_cwd TEXT, spawned_at TEXT,\n brief_chars INTEGER, brief_excerpt TEXT, brief_path TEXT, launch_args TEXT,\n resolved_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_origin_parent ON session_origin(parent_session_id);\n\n CREATE TABLE IF NOT EXISTS session_external_event ( -- teleport, handoff, retire, exit\n session_id TEXT NOT NULL, ts TEXT NOT NULL, origin_system TEXT NOT NULL,\n kind TEXT NOT NULL, detail TEXT,\n PRIMARY KEY (session_id, ts, kind)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS episode ( -- provisional, see section 9\n session_id TEXT NOT NULL, episode_index INTEGER NOT NULL,\n heuristic TEXT NOT NULL, heuristic_version INTEGER NOT NULL,\n started_at TEXT NOT NULL, ended_at TEXT NOT NULL,\n start_offset INTEGER NOT NULL, end_offset INTEGER NOT NULL,\n opened_by TEXT NOT NULL, -- brief | channel_followup | idle_gap | compaction\n assignment_offset INTEGER, first_deliverable_offset INTEGER, first_deliverable_signal TEXT,\n first_status_offset INTEGER,\n PRIMARY KEY (session_id, heuristic, episode_index)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS price (\n model TEXT NOT NULL, effective_from TEXT NOT NULL, table_version INTEGER NOT NULL,\n input_usd_mtok REAL NOT NULL, cache_read_usd_mtok REAL NOT NULL,\n cache_write_5m_usd_mtok REAL NOT NULL, cache_write_1h_usd_mtok REAL NOT NULL,\n output_usd_mtok REAL NOT NULL, source TEXT,\n PRIMARY KEY (model, effective_from)\n ) WITHOUT ROWID;\n`;\n\nconst REQUEST_COLUMNS = `transcript_id, request_id, byte_offset, session_id, message_id, ts, model,\n input_tokens, cache_read_tokens, cache_creation_tokens, cache_creation_5m, cache_creation_1h,\n output_tokens, thinking_tokens, context_tokens, service_tier, is_sidechain,\n seq_in_session, gap_ms, ctx_delta, wake_cause, wake_delivery, wake_detail,\n wake_tool_family, wake_mcp_server, wake_offset`;\n\n/** Fan-out copies of one request across transcripts collapse to the earliest `(ts, transcript_id)`. Every cost query reads this, never `request`. */\nconst REQUEST_DEDUP = `\n CREATE VIEW IF NOT EXISTS request_dedup AS\n SELECT ${REQUEST_COLUMNS} FROM (\n SELECT r.*, ROW_NUMBER() OVER (PARTITION BY r.request_id ORDER BY r.ts, r.transcript_id) AS copy_rank\n FROM request r\n ) WHERE copy_rank = 1;\n`;\n\n/**\n * Longest model prefix wins, so `claude-opus-5[1m]` prices as `claude-opus-5`; then the latest\n * `effective_from` at or before the request. An unmatched model reads `priced = 0` and costs 0,\n * never a guessed default. A split-less cache write (`5m + 1h = 0`) prices at the 5m rate.\n */\nconst REQUEST_COST = `\n CREATE VIEW IF NOT EXISTS request_cost AS\n WITH candidate AS (\n SELECT d.request_id, p.model AS price_model, p.effective_from AS price_effective_from,\n 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,\n ROW_NUMBER() OVER (PARTITION BY d.request_id ORDER BY length(p.model) DESC, p.effective_from DESC) AS match_rank\n FROM request_dedup d\n JOIN price p ON substr(d.model, 1, length(p.model)) = p.model AND p.effective_from <= d.ts\n ), component AS (\n SELECT d.*, c.price_model, c.price_effective_from, c.price_model IS NOT NULL AS priced,\n COALESCE(d.input_tokens * c.input_usd_mtok, 0) / 1e6 AS input_cost_usd,\n COALESCE(d.cache_read_tokens * c.cache_read_usd_mtok, 0) / 1e6 AS cache_read_cost_usd,\n COALESCE(CASE WHEN d.cache_creation_5m + d.cache_creation_1h = 0 THEN d.cache_creation_tokens ELSE d.cache_creation_5m END\n * c.cache_write_5m_usd_mtok, 0) / 1e6 AS cache_write_5m_cost_usd,\n COALESCE(d.cache_creation_1h * c.cache_write_1h_usd_mtok, 0) / 1e6 AS cache_write_1h_cost_usd,\n COALESCE(d.output_tokens * c.output_usd_mtok, 0) / 1e6 AS output_cost_usd\n FROM request_dedup d\n LEFT JOIN candidate c ON c.request_id = d.request_id AND c.match_rank = 1\n )\n SELECT *,\n input_cost_usd + cache_read_cost_usd + cache_write_5m_cost_usd + cache_write_1h_cost_usd + output_cost_usd AS cost_usd,\n (cache_read_tokens < 0.2 * context_tokens AND cache_creation_tokens >= 20000) AS is_cold,\n CASE WHEN context_tokens < 50000 THEN '<50k' WHEN context_tokens < 100000 THEN '50-100k'\n WHEN context_tokens < 200000 THEN '100-200k' ELSE '200k+' END AS context_band,\n CASE WHEN gap_ms IS NULL THEN NULL WHEN gap_ms < 300000 THEN '<5m'\n WHEN gap_ms < 3600000 THEN '5-60m' ELSE '>60m' END AS gap_band\n FROM component;\n`;\n\nconst ASSISTANT_OUTPUT = \"'assistant_text', 'assistant_thinking', 'assistant_tool_input'\";\n\n/**\n * A block belongs to the first request after it in its transcript; a block on the request's own\n * line is that request's output and so feeds the next one. Blocks whose request is a fan-out copy\n * drop out with it, keeping estimates aligned with `request_dedup`. Assistant output is already\n * subtracted from `ctx_delta`, so those rows stay visible with a NULL `est_tokens` and no share.\n */\nconst CONTEXT_CONTRIBUTION = `\n CREATE VIEW IF NOT EXISTS context_contribution AS\n WITH stream AS (\n SELECT transcript_id, byte_offset, 0 AS lane, block_index, NULL AS request_id FROM context_block\n UNION ALL\n SELECT transcript_id, byte_offset, -1 AS lane, 0, request_id FROM request\n ), ordered AS (\n SELECT *, COUNT(request_id) OVER (PARTITION BY transcript_id ORDER BY byte_offset, lane, block_index ROWS UNBOUNDED PRECEDING) AS requests_before\n FROM stream\n ), owner AS (\n SELECT b.transcript_id, b.byte_offset, b.block_index, r.request_id\n 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\n WHERE b.lane = 0\n ), tool AS (\n SELECT tool_use_id, name, family, mcp_server,\n ROW_NUMBER() OVER (PARTITION BY tool_use_id ORDER BY transcript_id, byte_offset, block_index) AS copy_rank\n FROM tool_call\n )\n SELECT cb.transcript_id, cb.byte_offset, cb.block_index, cb.session_id, cb.ts, cb.source,\n cb.tool_use_id, cb.attachment_type, cb.chars, cb.is_media, o.request_id, d.ctx_delta,\n CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN NULL\n ELSE d.ctx_delta * cb.chars * 1.0 / NULLIF(SUM(CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN 0 ELSE cb.chars END)\n OVER (PARTITION BY o.transcript_id, o.request_id), 0) END AS est_tokens,\n t.name AS tool_name, t.family AS tool_family, t.mcp_server\n FROM context_block cb\n JOIN owner o USING (transcript_id, byte_offset, block_index)\n JOIN request_dedup d ON d.transcript_id = o.transcript_id AND d.request_id = o.request_id\n LEFT JOIN tool t ON t.tool_use_id = cb.tool_use_id AND t.copy_rank = 1;\n`;\n\nexport const ORIGIN_VIEWS = [REQUEST_DEDUP, REQUEST_COST, CONTEXT_CONTRIBUTION] as const;\n\n/** The version 5 body. Every statement is `IF NOT EXISTS`, so a repeat run is a no-op. */\nexport function applyOriginSchema(db: Db): void {\n db.exec(ORIGIN_DDL);\n for (const view of ORIGIN_VIEWS) db.exec(view);\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\nimport { addColumnIfMissing } from \"./audit-schema.js\";\nimport { EPISODE_TABLE } from \"./audit-schema-v5.js\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const EPISODE_TRANSCRIPT_MIGRATION_NAME = \"episode transcript ids\";\n\n/**\n * Nullable so existing episode rows survive; a rewritten transcript's episodes are\n * dropped and rebuilt by `replaceEpisodes` anyway. `transcript_id` is INTEGER\n * everywhere else in this schema (`request`, `inbound`, `session_signal`, ...), so\n * these match rather than introducing a TEXT id for the same concept.\n */\nexport function applyEpisodeTranscriptSchema(db: Db): void {\n addColumnIfMissing(db, EPISODE_TABLE, \"start_transcript_id\", \"INTEGER\");\n addColumnIfMissing(db, EPISODE_TABLE, \"end_transcript_id\", \"INTEGER\");\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\nimport { addColumnIfMissing } from \"./audit-schema.js\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const ORIGIN_TASK_LINK_MIGRATION_NAME = \"origin task link\";\n\n/**\n * `task_ids` is a JSON array, primary id first. Both start null; a null `task_source`\n * re-offers the row to the resolver until a task-aware one answers, which is the backfill.\n */\nexport function applyOriginTaskLinkSchema(db: Db): void {\n addColumnIfMissing(db, \"session_origin\", \"task_ids\", \"TEXT\");\n addColumnIfMissing(db, \"session_origin\", \"task_source\", \"TEXT\");\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\nimport { addColumnIfMissing } from \"./audit-schema.js\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const REVIEW_VERDICT_MIGRATION_NAME = \"review verdicts\";\n\nexport const REVIEW_TABLE = \"pr_review\";\n\n/**\n * One row per review verdict, from either surface. Only parsed fields are kept, never message text.\n * `source_key` is `chat:<tool_use_id>:<n>` or `gh:<pr_ref>:<submitted_at>`; `pr_ref` is resolved later.\n */\nexport const REVIEW_DDL = `\n CREATE TABLE IF NOT EXISTS ${REVIEW_TABLE} (\n source_key TEXT PRIMARY KEY,\n surface TEXT NOT NULL,\n verdict TEXT NOT NULL,\n ts TEXT NOT NULL,\n session_id TEXT,\n transcript_id INTEGER,\n repo TEXT,\n repo_hint TEXT,\n cwd_repo TEXT,\n number INTEGER NOT NULL,\n pr_ref TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_${REVIEW_TABLE}_pr_ref ON ${REVIEW_TABLE}(pr_ref);\n CREATE INDEX IF NOT EXISTS idx_${REVIEW_TABLE}_transcript ON ${REVIEW_TABLE}(transcript_id);\n`;\n\nconst PR_COLUMNS = [\n [\"review_rounds_gh\", \"INTEGER\"],\n [\"review_rounds_chat\", \"INTEGER\"],\n [\"commit_times\", \"TEXT\"],\n] as const;\n\n/**\n * The reset re-queues every PR once so a resolver can send commit times; `prsNeedingOutcome`\n * also offers any PR whose `commit_times` is null, so a legacy resolver cannot spend it.\n */\nexport function applyReviewVerdictSchema(db: Db): void {\n db.exec(REVIEW_DDL);\n for (const [column, type] of PR_COLUMNS) addColumnIfMissing(db, \"pr\", column, type);\n db.exec(\"UPDATE pr SET review_rounds_gh = review_rounds\");\n db.exec(\"UPDATE pr SET outcome_checked_at = NULL\");\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\n/** Additive: legacy rows, aliases and original locators are never rewritten. */\nexport const NORMALIZED_DDL = `\nCREATE TABLE IF NOT EXISTS conversation (\n ref TEXT PRIMARY KEY, harness TEXT NOT NULL, namespace TEXT NOT NULL,\n native_id TEXT NOT NULL, legacy_session_id TEXT,\n UNIQUE(harness, namespace, native_id)\n);\nCREATE TABLE IF NOT EXISTS conversation_alias (\n legacy_ref TEXT NOT NULL, conversation_ref TEXT NOT NULL,\n PRIMARY KEY(legacy_ref, conversation_ref)\n);\nCREATE TABLE IF NOT EXISTS normalized_source (\n transcript_id INTEGER PRIMARY KEY, source_id TEXT NOT NULL UNIQUE,\n conversation_ref TEXT NOT NULL, descriptor TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS normalized_event (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n subrecord_index INTEGER NOT NULL, byte_length INTEGER NOT NULL,\n conversation_ref TEXT NOT NULL, kind TEXT NOT NULL, ts TEXT,\n turn_ref TEXT, call_ref TEXT, item_ref TEXT, phase TEXT, is_error INTEGER,\n usage TEXT, metadata TEXT, history_origin TEXT, related_ref TEXT, relationship TEXT,\n PRIMARY KEY(transcript_id, byte_offset, subrecord_index)\n);\nCREATE INDEX IF NOT EXISTS idx_normalized_event_conversation ON normalized_event(conversation_ref, ts);\nCREATE TABLE IF NOT EXISTS normalized_span (\n span_id INTEGER PRIMARY KEY, locators TEXT NOT NULL\n);\n`;\n\n/** Only the old transcript session table proves Claude provenance, never a ref prefix. */\nexport function backfillClaudeAliases(db: Db, sessionIds?: readonly string[]): void {\n const insert = db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id,legacy_session_id) VALUES (?,?,?,?,?)\");\n const alias = db.prepare(\"INSERT OR IGNORE INTO conversation_alias VALUES (?,?)\");\n const rows = sessionIds?.map(session_id => ({ session_id })) ?? db.prepare(\"SELECT session_id FROM session\").all() as Iterable<{ session_id: string }>;\n for (const row of rows) {\n const ref = conversationRef({ harness: \"claude-code\", namespace: \"legacy\", nativeId: row.session_id });\n insert.run(ref, \"claude-code\", \"legacy\", row.session_id, row.session_id);\n alias.run(`session:${row.session_id}`, ref);\n }\n}\n\n/** Ambiguity is explicit; workspace session bodies remain in their original namespace. */\nexport function resolveConversationAlias(db: Db, legacyRef: string): string | null {\n const rows = db.prepare(\"SELECT conversation_ref FROM conversation_alias WHERE legacy_ref = ?\").all(legacyRef) as { conversation_ref: string }[];\n if (rows.length > 1) throw new Error(`ambiguous conversation alias: ${legacyRef}`);\n return rows[0]?.conversation_ref ?? null;\n}\n","import { AUDIT_MIGRATION_NAME, AUDIT_TABLES, FACET_TABLE, applyAuditSchema } from \"./audit-schema.js\";\nimport { EPISODE_TABLE, ORIGIN_MIGRATION_NAME, ORIGIN_TABLES, applyOriginSchema } from \"./audit-schema-v5.js\";\nimport { EPISODE_TRANSCRIPT_MIGRATION_NAME, applyEpisodeTranscriptSchema } from \"./audit-schema-v6.js\";\nimport { ORIGIN_TASK_LINK_MIGRATION_NAME, applyOriginTaskLinkSchema } from \"./audit-schema-v7.js\";\nimport { REVIEW_TABLE, REVIEW_VERDICT_MIGRATION_NAME, applyReviewVerdictSchema } from \"./audit-schema-v8.js\";\nimport { NORMALIZED_DDL, backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { SQL_NOW, kitMigration, type Migration } from \"@titan-design/store-sqlite\";\n\n/** Kit table names this graph uses. `watermark` rows are transcripts; `search_*` are the FTS spans. */\nexport const KIT = { watermark: \"transcript\", edge: \"edge\", spanFts: \"search\" } as const;\n\n/**\n * Legacy domain tables. Original sources can be pruned; schema migrations must\n * preserve these rows rather than assuming a replay is possible.\n * Locators are `(transcript_id, byte_offset, byte_length)`; `fact_id` rows\n * resolve through the facts table's unique `(transcript_id, byte_offset)`.\n */\nexport const DOMAIN_DDL = `\n CREATE TABLE IF NOT EXISTS fact (\n fact_id INTEGER PRIMARY KEY,\n transcript_id INTEGER NOT NULL,\n byte_offset INTEGER NOT NULL,\n byte_length INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n ts TEXT NOT NULL,\n seq INTEGER NOT NULL,\n session_id TEXT NOT NULL,\n prompt_id TEXT,\n tool_use_id TEXT,\n t_indexed TEXT NOT NULL DEFAULT (${SQL_NOW}),\n UNIQUE (transcript_id, byte_offset)\n );\n CREATE INDEX IF NOT EXISTS idx_fact_session_ts ON fact(session_id, ts);\n CREATE INDEX IF NOT EXISTS idx_fact_prompt ON fact(prompt_id);\n CREATE INDEX IF NOT EXISTS idx_fact_tool_use ON fact(tool_use_id);\n\n CREATE TABLE IF NOT EXISTS session (\n session_id TEXT PRIMARY KEY,\n transcript_id INTEGER,\n started_at TEXT,\n ended_at TEXT,\n start_type TEXT,\n cwd TEXT,\n git_branch TEXT,\n ai_title TEXT,\n seed_prompt TEXT,\n cli_version TEXT,\n turn_count INTEGER NOT NULL DEFAULT 0,\n commit_count INTEGER NOT NULL DEFAULT 0,\n push_count INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX IF NOT EXISTS idx_session_started ON session(started_at);\n\n CREATE TABLE IF NOT EXISTS session_model_usage (\n session_id TEXT NOT NULL,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n thinking_tokens INTEGER NOT NULL DEFAULT 0,\n request_count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (session_id, model)\n );\n\n CREATE TABLE IF NOT EXISTS turn (\n prompt_id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n turn_index INTEGER NOT NULL,\n started_at TEXT NOT NULL,\n ended_at TEXT,\n duration_ms INTEGER,\n tool_call_count INTEGER NOT NULL DEFAULT 0,\n thinking_ms INTEGER NOT NULL DEFAULT 0,\n fact_id_start INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_turn_session ON turn(session_id, turn_index);\n\n CREATE TABLE IF NOT EXISTS permission_phase (\n phase_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n from_mode TEXT,\n to_mode TEXT NOT NULL,\n trigger TEXT NOT NULL,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_phase_session ON permission_phase(session_id, t_valid);\n\n CREATE TABLE IF NOT EXISTS human_edit (\n edit_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n ts TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, ts)\n );\n\n CREATE TABLE IF NOT EXISTS file_checkpoint (\n checkpoint_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n backup_file_name TEXT NOT NULL,\n version INTEGER NOT NULL,\n backup_time TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, backup_file_name)\n );\n\n CREATE TABLE IF NOT EXISTS pr (\n pr_ref TEXT PRIMARY KEY, number INTEGER, repo TEXT, title TEXT, state TEXT, url TEXT, merged_at TEXT\n );\n CREATE TABLE IF NOT EXISTS branch (\n branch_ref TEXT PRIMARY KEY, repo TEXT, name TEXT NOT NULL, base TEXT, created_at TEXT, deleted_at TEXT\n );\n CREATE TABLE IF NOT EXISTS file (\n file_ref TEXT PRIMARY KEY, repo TEXT, path TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS task (\n task_ref TEXT PRIMARY KEY, task_id TEXT NOT NULL, initiative TEXT, title TEXT, status TEXT\n );\n CREATE TABLE IF NOT EXISTS subagent (\n agent_ref TEXT PRIMARY KEY,\n session_id TEXT,\n child_session_id TEXT,\n parent_agent_ref TEXT,\n agent_type TEXT,\n label TEXT,\n started_at TEXT,\n ended_at TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_subagent_child ON subagent(child_session_id);\n CREATE TABLE IF NOT EXISTS artifact (\n artifact_ref TEXT PRIMARY KEY, kind TEXT, title TEXT, url TEXT, path TEXT, created_at TEXT\n );\n CREATE TABLE IF NOT EXISTS pr_merge_observation (\n number INTEGER NOT NULL, repo_hint TEXT, merged_at TEXT NOT NULL,\n PRIMARY KEY (number, repo_hint, merged_at)\n );\n CREATE TABLE IF NOT EXISTS pr_create_observation (\n tool_use_id TEXT PRIMARY KEY, title TEXT, number INTEGER, repo TEXT, url TEXT\n );\n`;\n\n/** Every derived table, in an order safe to clear. The watermark table is not derived. */\nexport const DERIVED_TABLES = [\n \"normalized_span\",\n \"normalized_event\",\n \"normalized_source\",\n `${KIT.spanFts}_span`,\n KIT.edge,\n \"turn\",\n \"permission_phase\",\n \"human_edit\",\n \"file_checkpoint\",\n \"subagent\",\n ...AUDIT_TABLES,\n FACET_TABLE,\n ...ORIGIN_TABLES,\n EPISODE_TABLE,\n \"session_model_usage\",\n \"session\",\n \"fact\",\n \"pr\",\n \"pr_merge_observation\",\n \"pr_create_observation\",\n REVIEW_TABLE,\n \"branch\",\n \"file\",\n \"task\",\n \"artifact\",\n] as const;\n\nexport const MIGRATIONS: Migration[] = [\n kitMigration(1, { watermark: KIT.watermark, edge: KIT.edge, spanFts: KIT.spanFts }, \"kit tables\"),\n { version: 2, name: \"session graph tables\", up: (db) => db.exec(DOMAIN_DDL) },\n { version: 3, name: \"normalized conversations and source evidence\", up: (db) => { db.exec(NORMALIZED_DDL); backfillClaudeAliases(db); } },\n { version: 4, name: AUDIT_MIGRATION_NAME, up: applyAuditSchema },\n { version: 5, name: ORIGIN_MIGRATION_NAME, up: applyOriginSchema },\n { version: 6, name: EPISODE_TRANSCRIPT_MIGRATION_NAME, up: applyEpisodeTranscriptSchema },\n { version: 7, name: ORIGIN_TASK_LINK_MIGRATION_NAME, up: applyOriginTaskLinkSchema },\n { version: 8, name: REVIEW_VERDICT_MIGRATION_NAME, up: applyReviewVerdictSchema },\n];\n","import type { TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * One API response is written as several assistant lines, each repeating its\n * usage. The row keeps the first line's identity and the largest count seen,\n * so a partial streaming copy never wins and chunk order cannot change it.\n */\nconst UPSERT_REQUEST = `\n INSERT INTO request (transcript_id, request_id, byte_offset, session_id, message_id, ts, model,\n input_tokens, cache_read_tokens, cache_creation_tokens, cache_creation_5m, cache_creation_1h,\n output_tokens, thinking_tokens, context_tokens, service_tier, is_sidechain)\n VALUES (@transcriptId, @requestId, @byteOffset, @sessionId, @messageId, @ts, @model,\n @inputTokens, @cacheReadTokens, @cacheCreationTokens, @cacheCreation5mTokens, @cacheCreation1hTokens,\n @outputTokens, @thinkingTokens, @inputTokens + @cacheReadTokens + @cacheCreationTokens, @serviceTier, @isSidechain)\n ON CONFLICT (transcript_id, request_id) DO UPDATE SET\n session_id = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.session_id ELSE session_id END,\n message_id = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.message_id ELSE message_id END,\n model = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.model ELSE model END,\n service_tier = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.service_tier ELSE service_tier END,\n is_sidechain = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.is_sidechain ELSE is_sidechain END,\n byte_offset = MIN(byte_offset, excluded.byte_offset),\n ts = MIN(ts, excluded.ts),\n input_tokens = MAX(input_tokens, excluded.input_tokens),\n cache_read_tokens = MAX(cache_read_tokens, excluded.cache_read_tokens),\n cache_creation_tokens = MAX(cache_creation_tokens, excluded.cache_creation_tokens),\n cache_creation_5m = MAX(cache_creation_5m, excluded.cache_creation_5m),\n cache_creation_1h = MAX(cache_creation_1h, excluded.cache_creation_1h),\n output_tokens = MAX(output_tokens, excluded.output_tokens),\n thinking_tokens = MAX(thinking_tokens, excluded.thinking_tokens),\n context_tokens = MAX(input_tokens, excluded.input_tokens) + MAX(cache_read_tokens, excluded.cache_read_tokens)\n + MAX(cache_creation_tokens, excluded.cache_creation_tokens)`;\n\n/** Line-located rows are facts about one position in the file, so a re-read of the same bytes is a no-op. */\nconst INSERTS = {\n toolCalls: `INSERT INTO tool_call (transcript_id, byte_offset, block_index, session_id, ts, tool_use_id, name, family, mcp_server, input_chars)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @toolUseId, @name, @family, @mcpServer, @inputChars)\n ON CONFLICT DO NOTHING`,\n 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)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @cause, @delivery, @detail, @originServer, @fromName, @msgId, @toolUseId, @isError, @contentHash, @chars)\n ON CONFLICT DO NOTHING`,\n contextBlocks: `INSERT INTO context_block (transcript_id, byte_offset, block_index, session_id, ts, source, tool_use_id, attachment_type, chars, is_media)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @source, @toolUseId, @attachmentType, @chars, @isMedia)\n ON CONFLICT DO NOTHING`,\n compactions: `INSERT INTO compaction (transcript_id, byte_offset, session_id, ts, trigger, pre_tokens, post_tokens, dropped_tokens, duration_ms)\n VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @trigger, @preTokens, @postTokens, @droppedTokens, @durationMs)\n ON CONFLICT DO NOTHING`,\n queueOps: `INSERT INTO queue_op (transcript_id, byte_offset, session_id, ts, operation, content_hash, origin_server)\n VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @operation, @contentHash, @originServer)\n ON CONFLICT DO NOTHING`,\n signals: `INSERT INTO session_signal (transcript_id, byte_offset, block_index, session_id, ts, signal, detail, tool_use_id)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @signal, @detail, @toolUseId)\n ON CONFLICT DO NOTHING`,\n costStates: `INSERT INTO cost_state_observation (transcript_id, byte_offset, session_id, ts, total_cost_usd, model_usage)\n VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @totalCostUsd, @modelUsageJson)\n ON CONFLICT DO NOTHING`,\n} as const;\n\n/** Write one delta's audit events. Runs inside `applyDelta`'s transaction, so it opens none of its own. */\nexport function applyAudit(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n run(db, UPSERT_REQUEST, transcriptId, delta.requests);\n for (const [list, sql] of Object.entries(INSERTS) as [keyof typeof INSERTS, string][]) {\n run(db, sql, transcriptId, delta[list]);\n }\n}\n\nfunction run(db: Db, sql: string, transcriptId: number, events: readonly object[]): void {\n if (events.length === 0) return;\n const statement = db.prepare(sql);\n const names = [...new Set([...sql.matchAll(/@(\\w+)/g)].map((m) => m[1]!))];\n for (const event of events) statement.run(bind(names, { ...(event as Row), transcriptId }));\n}\n\n/** better-sqlite3 rejects unused named parameters and cannot bind booleans. */\nfunction bind(names: readonly string[], row: Row): Row {\n return Object.fromEntries(names.map((name) => {\n const value = row[name] ?? null;\n return [name, typeof value === \"boolean\" ? Number(value) : value];\n }));\n}\n","import { EXTRACT_VERSION, TranscriptParseError, extractTranscript, type DiscoveredTranscript, type TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db, WatermarkRow } from \"@titan-design/store-sqlite\";\nimport { applyAudit } from \"./audit-apply.js\";\nimport { AUDIT_TABLES, FACET_TABLE } from \"./audit-schema.js\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/** The one facet today: the eight audit tables, all written from the same extraction. */\nexport const AUDIT_FACET = \"audit\";\n\n/** Transcripts re-extracted per pass, so a daemon's pass stays short while the backlog drains. */\nexport const DEFAULT_FACET_LIMIT = 40;\n\nexport interface BackfillOptions {\n /** Transcripts visited this pass. `Infinity` clears the whole backlog. */\n limit?: number;\n /** The extraction version a facet must reach. Defaults to session-read's `EXTRACT_VERSION`. */\n version?: number;\n}\n\nexport interface BackfillSummary {\n backfilled: number;\n /** Stale transcripts left for later passes. */\n backlog: number;\n /** Sessions whose audit rows were rebuilt, for the caller's rollup. */\n sessionIds: string[];\n}\n\ninterface Candidate {\n row: WatermarkRow;\n transcript: DiscoveredTranscript;\n}\n\n/**\n * Bring the audit facet of already-indexed transcripts up to `version` without a\n * full rebuild: re-read each one up to its watermark and replace only its audit\n * rows. Legacy tables are never written, so their accumulating counts cannot double.\n */\nexport async function backfillFacets(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], options: BackfillOptions = {}): Promise<BackfillSummary> {\n const version = options.version ?? EXTRACT_VERSION;\n const stale = staleTranscripts(graph, transcripts, version);\n const visit = stale.slice(0, options.limit ?? DEFAULT_FACET_LIMIT);\n const sessionIds = new Set<string>();\n let backfilled = 0;\n for (const candidate of visit) {\n const touched = await backfillOne(graph, candidate, version);\n if (!touched) continue;\n backfilled += 1;\n for (const id of touched) sessionIds.add(id);\n }\n return { backfilled, backlog: stale.length - visit.length, sessionIds: [...sessionIds] };\n}\n\n/** Discovered, status `ok`, and below `version`; newest first so recent cost history lands first. */\nfunction staleTranscripts(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], version: number): Candidate[] {\n const byPath = new Map(transcripts.map((t) => [t.displayPath, t]));\n const versions = facetVersions(graph.db);\n const stale: Candidate[] = [];\n for (const row of graph.transcripts.list()) {\n const transcript = byPath.get(row.sourceKey);\n if (!transcript || row.status !== \"ok\" || (versions.get(row.sourceId) ?? -1) >= version) continue;\n stale.push({ row, transcript });\n }\n return stale.sort((a, b) => (b.row.fileMtime ?? \"\").localeCompare(a.row.fileMtime ?? \"\"));\n}\n\nfunction facetVersions(db: Db): Map<number, number> {\n const rows = db.prepare(`SELECT transcript_id, version FROM ${FACET_TABLE} WHERE facet = ?`).all(AUDIT_FACET) as { transcript_id: number; version: number }[];\n return new Map(rows.map((r) => [r.transcript_id, r.version]));\n}\n\n/** Returns the sessions touched, or `null` when the file could not be re-read and was marked instead. */\nasync function backfillOne(graph: SessionGraph, { row, transcript }: Candidate, version: number): Promise<string[] | null> {\n let delta: TranscriptDelta & { lastByteOffset: number };\n try {\n delta = await extractTranscript(transcript.absolutePath, { untilByteOffset: row.lastOffset, subagentId: transcript.subagentId });\n } catch (err) {\n // Mark rather than skip, or a broken file would hold a slot at the head of every pass.\n const status = unreadableStatus(err);\n if (!status) throw err;\n graph.transcripts.markStatus(row.sourceKey, status, err instanceof Error ? err.message : String(err));\n return null;\n }\n graph.db.transaction(() => {\n for (const table of AUDIT_TABLES) graph.db.prepare(`DELETE FROM \"${table}\" WHERE transcript_id = ?`).run(row.sourceId);\n applyAudit(graph.db, row.sourceId, delta);\n writeFacet(graph.db, row.sourceId, version, delta.lastByteOffset);\n })();\n return delta.sessions.map((s) => s.sessionId);\n}\n\nfunction unreadableStatus(err: unknown): \"missing\" | \"quarantined\" | null {\n if (err instanceof TranscriptParseError) return \"quarantined\";\n if ((err as NodeJS.ErrnoException | null)?.code === \"ENOENT\") return \"missing\";\n return null;\n}\n\nfunction writeFacet(db: Db, transcriptId: number, version: number, indexedTo: number): void {\n db.prepare(`\n INSERT INTO ${FACET_TABLE} (transcript_id, facet, version, indexed_to) VALUES (?, ?, ?, ?)\n ON CONFLICT (transcript_id, facet) DO UPDATE SET version = excluded.version, indexed_to = excluded.indexed_to`)\n .run(transcriptId, AUDIT_FACET, version, indexedTo);\n}\n\n/**\n * Record a forward read. A read from byte 0 covers the whole file, so it sets the\n * facet outright; a resumed read only extends a facet that is already current,\n * because the bytes before it may predate the audit tables.\n */\nexport function advanceFacet(db: Db, transcriptId: number, fromByteOffset: number, indexedTo: number, version: number = EXTRACT_VERSION): void {\n if (fromByteOffset === 0) {\n writeFacet(db, transcriptId, version, indexedTo);\n return;\n }\n db.prepare(`UPDATE ${FACET_TABLE} SET indexed_to = ? WHERE transcript_id = ? AND facet = ? AND version = ?`)\n .run(indexedTo, transcriptId, AUDIT_FACET, version);\n}\n","import { applyAudit } from \"./audit-apply.js\";\nimport { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport { backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { RELATIONS, sessionRef, type TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db, EdgeInput } from \"@titan-design/store-sqlite\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * Apply one transcript chunk's delta in a single transaction, so a crash can\n * never leave rows committed behind a stale watermark or ahead of their rows.\n * Append-only tables are idempotent through unique indexes; session rows\n * accumulate; usage is left to the rollup, which recomputes it from `request`;\n * ref-keyed assets insert-if-absent with COALESCE merges that mirror\n * `EventFolder`'s rules.\n */\nexport function applyDelta(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta, source: DeltaSource = {}): void {\n graph.db.transaction(() => {\n applyFacts(graph.db, transcriptId, delta);\n applySessions(graph.db, transcriptId, delta, source.account ?? null);\n backfillClaudeAliases(graph.db, delta.sessions.map(s => s.sessionId));\n applyAssets(graph.db, delta);\n applyPhases(graph.db, transcriptId, delta);\n applyLinkedRows(graph, transcriptId, delta);\n applyReviewVerdicts(graph.db, transcriptId, delta);\n applyAudit(graph.db, transcriptId, delta);\n })();\n}\n\n/** What discovery knows about the file that the lines themselves do not carry. */\nexport interface DeltaSource {\n /** The Claude config dir the transcript was found under; `null` when discovery did not say. */\n account?: string | null;\n}\n\nconst INSERT_FACT = `\n INSERT INTO fact (transcript_id, byte_offset, byte_length, event_type, ts, seq, session_id, prompt_id, tool_use_id)\n VALUES (@transcriptId, @byteOffset, @byteLength, @eventType, @ts, @seq, @sessionId, @promptId, @toolUseId)\n ON CONFLICT (transcript_id, byte_offset) DO NOTHING`;\n\n/** Facts carry a per-transcript `seq` continuing from whatever is stored. */\nfunction applyFacts(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const next = db.prepare(\"SELECT COALESCE(MAX(seq), -1) + 1 AS next FROM fact WHERE transcript_id = ?\").get(transcriptId) as { next: number };\n const insert = db.prepare(INSERT_FACT);\n let seq = next.next;\n for (const f of delta.facts) {\n insert.run({ transcriptId, byteOffset: f.byteOffset, byteLength: f.byteLength, eventType: f.eventType, ts: f.ts, seq: seq++, sessionId: f.sessionId, promptId: f.promptId, toolUseId: f.toolUseId });\n }\n}\n\nconst UPSERT_SESSION = `\n 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)\n VALUES (@sessionId, @transcriptId, @startedAt, @endedAt, @startType, @cwd, @gitBranch, @aiTitle, @seedPrompt, @cliVersion, @account, @turnDelta, @commitDelta, @pushDelta)\n ON CONFLICT (session_id) DO UPDATE SET\n started_at = MIN(COALESCE(started_at, excluded.started_at), COALESCE(excluded.started_at, started_at)),\n ended_at = MAX(COALESCE(ended_at, excluded.ended_at), COALESCE(excluded.ended_at, ended_at)),\n start_type = CASE\n WHEN excluded.start_type IS NULL THEN start_type\n WHEN start_type IS NULL THEN excluded.start_type\n WHEN COALESCE(excluded.started_at, started_at) < COALESCE(started_at, excluded.started_at) THEN excluded.start_type\n ELSE start_type END,\n cwd = COALESCE(excluded.cwd, cwd),\n git_branch = COALESCE(git_branch, excluded.git_branch),\n ai_title = COALESCE(excluded.ai_title, ai_title),\n seed_prompt = COALESCE(seed_prompt, excluded.seed_prompt),\n cli_version = COALESCE(excluded.cli_version, cli_version),\n account = COALESCE(excluded.account, account),\n turn_count = turn_count + excluded.turn_count,\n commit_count = commit_count + excluded.commit_count,\n push_count = push_count + excluded.push_count`;\n\nfunction applySessions(db: Db, transcriptId: number, delta: TranscriptDelta, account: string | null): void {\n const upsertSession = db.prepare(UPSERT_SESSION);\n for (const s of delta.sessions) upsertSession.run({ ...s, transcriptId, account });\n}\n\nconst ASSET_UPSERTS = {\n prs: `INSERT INTO pr (pr_ref, number, repo, title, url) VALUES (@prRef, @number, @repo, @title, @url)\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(title, excluded.title), url = COALESCE(url, excluded.url)`,\n branches: `INSERT INTO branch (branch_ref, repo, name, base, created_at, deleted_at) VALUES (@branchRef, @repo, @name, @base, @createdAt, @deletedAt)\n ON CONFLICT (branch_ref) DO UPDATE SET base = COALESCE(base, excluded.base),\n created_at = MIN(COALESCE(created_at, excluded.created_at), COALESCE(excluded.created_at, created_at)),\n deleted_at = MAX(COALESCE(deleted_at, excluded.deleted_at), COALESCE(excluded.deleted_at, deleted_at))`,\n files: `INSERT INTO file (file_ref, repo, path) VALUES (@fileRef, @repo, @path) ON CONFLICT (file_ref) DO NOTHING`,\n tasks: `INSERT INTO task (task_ref, task_id, status) VALUES (@taskRef, @taskId, @status)\n ON CONFLICT (task_ref) DO UPDATE SET status = COALESCE(excluded.status, status)`,\n artifacts: `INSERT INTO artifact (artifact_ref, kind, title, url, path, created_at) VALUES (@artifactRef, @artifactKind, @title, @url, @path, @ts)\n ON CONFLICT (artifact_ref) DO NOTHING`,\n prMerges: `INSERT INTO pr_merge_observation (number, repo_hint, merged_at) VALUES (@number, @repoHint, @ts)\n ON CONFLICT (number, repo_hint, merged_at) DO NOTHING`,\n prCreates: `INSERT INTO pr_create_observation (tool_use_id, title, number, repo, url) VALUES (@toolUseId, @title, @number, @repo, @url)\n ON CONFLICT (tool_use_id) DO UPDATE SET title = COALESCE(title, excluded.title), number = COALESCE(number, excluded.number),\n repo = COALESCE(repo, excluded.repo), url = COALESCE(url, excluded.url)`,\n} as const;\n\nfunction applyAssets(db: Db, delta: TranscriptDelta): void {\n for (const [kind, sql] of Object.entries(ASSET_UPSERTS) as [keyof typeof ASSET_UPSERTS, string][]) {\n const statement = db.prepare(sql);\n for (const row of delta[kind] as unknown as Record<string, unknown>[]) statement.run(pick(row, sql));\n }\n}\n\n/** better-sqlite3 rejects unused named parameters, so pass only the ones the statement binds. */\nfunction pick(row: Record<string, unknown>, sql: string): Record<string, unknown> {\n const names = new Set([...sql.matchAll(/@(\\w+)/g)].map((m) => m[1]!));\n return Object.fromEntries([...names].map((name) => [name, row[name] ?? null]));\n}\n\n/** A re-read refreshes the parsed fields and keeps a resolved `pr_ref`. */\nconst UPSERT_CHAT_VERDICT = `\n INSERT INTO ${REVIEW_TABLE} (source_key, surface, verdict, ts, session_id, transcript_id, repo, repo_hint, cwd_repo, number)\n VALUES (@sourceKey, 'chat', @verdict, @ts, @sessionId, @transcriptId, @repo, @repoHint, @cwdRepo, @number)\n ON CONFLICT (source_key) DO UPDATE SET verdict = excluded.verdict, ts = excluded.ts, session_id = excluded.session_id,\n transcript_id = excluded.transcript_id, repo = excluded.repo, repo_hint = excluded.repo_hint,\n cwd_repo = excluded.cwd_repo, number = excluded.number`;\n\n/** The ordinal comes from the event, so one tool use's verdicts keep distinct keys across chunk boundaries. */\nfunction applyReviewVerdicts(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const upsert = db.prepare(UPSERT_CHAT_VERDICT);\n const keys = chatVerdictKeys(delta.reviewVerdicts);\n delta.reviewVerdicts.forEach((v, i) => {\n upsert.run({\n sourceKey: keys[i], verdict: v.verdict, ts: v.ts, sessionId: v.sessionId, transcriptId,\n repo: v.repo, repoHint: v.repoHint, cwdRepo: v.cwdRepo, number: v.number,\n });\n });\n}\n\ntype ChatVerdict = TranscriptDelta[\"reviewVerdicts\"][number];\n\nconst hasOrdinal = (v: ChatVerdict): boolean => Number.isInteger(v.ordinal) && v.ordinal >= 0;\n\n/**\n * A session-read that predates `ordinal` sends none, so those verdicts take the next index\n * this call has not used for their tool use, skipping any ordinal another event carries.\n * Per call is enough because that session-read emits all of one tool use's verdicts from one line.\n */\nfunction chatVerdictKeys(verdicts: readonly ChatVerdict[]): string[] {\n const used = new Map<string, Set<number>>();\n const usedBy = (id: string) => used.get(id) ?? used.set(id, new Set()).get(id)!;\n for (const v of verdicts) if (hasOrdinal(v)) usedBy(v.toolUseId).add(v.ordinal);\n return verdicts.map((v) => {\n if (hasOrdinal(v)) return `chat:${v.toolUseId}:${v.ordinal}`;\n const taken = usedBy(v.toolUseId);\n let n = 0;\n while (taken.has(n)) n += 1;\n taken.add(n);\n return `chat:${v.toolUseId}:${n}`;\n });\n}\n\n/**\n * Bi-temporal phase collapse: a candidate matching the session's open phase is a\n * no-op; otherwise the open phase closes at the new `t_valid` and a replacement\n * row is inserted. Done here so a chunk boundary mid-run cannot add a phase.\n */\nfunction applyPhases(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const current = db.prepare(\"SELECT phase_id, to_mode FROM permission_phase WHERE session_id = ? AND trigger = ? AND t_invalid IS NULL ORDER BY phase_id DESC LIMIT 1\");\n const close = db.prepare(\"UPDATE permission_phase SET t_invalid = ? WHERE phase_id = ?\");\n const insert = db.prepare(\"INSERT INTO permission_phase (session_id, from_mode, to_mode, trigger, t_valid, fact_id) VALUES (?, ?, ?, ?, ?, ?)\");\n for (const phase of delta.phases) {\n const open = current.get(phase.sessionId, phase.trigger) as { phase_id: number; to_mode: string } | undefined;\n if (open?.to_mode === phase.toMode) continue;\n if (open) close.run(phase.ts, open.phase_id);\n insert.run(phase.sessionId, open?.to_mode ?? null, phase.toMode, phase.trigger, phase.ts, factIdFor(db, transcriptId, phase.byteOffset));\n }\n}\n\nfunction applyLinkedRows(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta): void {\n const { db } = graph;\n const factId = (byteOffset: number) => factIdFor(db, transcriptId, byteOffset);\n\n const upsertTurn = db.prepare(`\n INSERT INTO turn (prompt_id, session_id, turn_index, started_at, fact_id_start)\n VALUES (?, ?, (SELECT COALESCE(MAX(turn_index), -1) + 1 FROM turn WHERE session_id = ?), ?, ?)\n ON CONFLICT (prompt_id) DO UPDATE SET session_id = MIN(session_id, excluded.session_id)`);\n for (const t of delta.turns) upsertTurn.run(t.promptId, t.sessionId, t.sessionId, t.ts, factId(t.byteOffset));\n\n const insertEdit = db.prepare(\"INSERT INTO human_edit (session_id, file_path, ts, fact_id) VALUES (?, ?, ?, ?) ON CONFLICT (session_id, file_path, ts) DO NOTHING\");\n for (const e of delta.humanEdits) insertEdit.run(e.sessionId, e.filePath, e.ts, factId(e.byteOffset));\n\n const insertCheckpoint = db.prepare(`\n INSERT INTO file_checkpoint (session_id, file_path, backup_file_name, version, backup_time, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (session_id, file_path, backup_file_name) DO NOTHING`);\n for (const c of delta.fileCheckpoints) insertCheckpoint.run(c.sessionId, c.filePath, c.backupFileName, c.version, c.backupTime, factId(c.byteOffset));\n\n const upsertSubagent = db.prepare(`\n INSERT INTO subagent (agent_ref, session_id, agent_type, label, started_at, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (agent_ref) DO UPDATE SET session_id = COALESCE(session_id, excluded.session_id),\n agent_type = COALESCE(agent_type, excluded.agent_type), label = COALESCE(label, excluded.label), started_at = COALESCE(started_at, excluded.started_at)`);\n for (const s of delta.subagents) upsertSubagent.run(s.agentRef, s.sessionId, s.agentType, s.label, s.ts || null, factId(s.byteOffset));\n\n // The dispatch row and this bridge come from different lines; an upsert survives a chunk boundary between them.\n const linkTranscript = db.prepare(\"INSERT INTO subagent (agent_ref, child_session_id) VALUES (?, ?) ON CONFLICT (agent_ref) DO UPDATE SET child_session_id = excluded.child_session_id\");\n for (const l of delta.subagentTranscripts) linkTranscript.run(l.agentRef, l.childSessionId);\n\n for (const e of delta.edges) assertTranscriptEdge(graph, { sourceRef: e.sourceRef, relation: e.relation, targetRef: e.targetRef, tValid: e.ts, factId: factId(e.byteOffset) });\n\n for (const span of delta.spans) {\n graph.spans.index({ ownerRef: sessionRef(span.sessionId), field: span.field, sourceId: transcriptId, byteOffset: span.byteOffset, byteLength: span.byteLength }, span.text);\n }\n}\n\n/**\n * A transcript's `ran` claim outranks a spawn record's: an origin-made edge for the same\n * task is superseded without `via`, so origin expiry cannot later remove it.\n */\nfunction assertTranscriptEdge(graph: SessionGraph, edge: EdgeInput): void {\n const existing = edge.relation === RELATIONS.RAN ? graph.edges.current(edge.sourceRef, edge.relation, edge.targetRef) : undefined;\n if (existing?.attrs?.via === \"origin\") graph.edges.supersede(edge);\n else graph.edges.assert(edge);\n}\n\nfunction factIdFor(db: Db, transcriptId: number, byteOffset: number): number | null {\n const row = db.prepare(\"SELECT fact_id FROM fact WHERE transcript_id = ? AND byte_offset = ?\").get(transcriptId, byteOffset) as { fact_id: number } | undefined;\n return row?.fact_id ?? null;\n}\n","import { sessionRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { AUDIT_TABLES, FACET_TABLE } from \"./audit-schema.js\";\nimport { EPISODE_TABLE } from \"./audit-schema-v5.js\";\nimport { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport { KIT } from \"./schema.js\";\n\n/** Derived tables keyed by `session_id`, reachable from a transcript through `session`. */\nconst SESSION_SCOPED = [\"turn\", \"permission_phase\", \"human_edit\", \"file_checkpoint\", \"subagent\", \"session_model_usage\", EPISODE_TABLE] as const;\n\n/**\n * Drop every derived row one transcript produced, so re-reading it from byte 0\n * rebuilds those rows instead of doubling them.\n *\n * `resetIndex` already states the reason at corpus scale: rewinding alone is not\n * enough, because the accumulating upserts add a second copy of every count. A\n * rewritten single transcript has the same problem one file at a time.\n * `session.turn_count`, the `session_model_usage` token buckets and the\n * commit/push counts all sum into what is already there, and rows the rewrite\n * removed are never deleted — `fact` survives on its unique\n * `(transcript_id, byte_offset)` index, so stale facts simply persist.\n *\n * Shared identity rows survive on purpose. `pr`, `branch`, `file`, `task` and\n * `artifact` are keyed by ref, many transcripts assert the same one, and the\n * inserts are insert-if-absent; a single transcript cannot know whether it was\n * the only source. The two observation tables are left for the same reason —\n * `reconcile` folds them, and re-reading re-asserts the same rows.\n *\n * Audit rows, facet state and chat review verdicts carry `transcript_id`\n * themselves, so they go by it directly rather than through `session`.\n */\nexport function purgeTranscript(graph: SessionGraph, transcriptId: number): void {\n graph.db.transaction(() => {\n const owned = graph.db.prepare(\"SELECT session_id FROM session WHERE transcript_id = ?\").all(transcriptId) as { session_id: string }[];\n for (const { session_id } of owned) graph.spans.purgeOwner(sessionRef(session_id));\n\n // Children first: each subquery reads the `session` rows deleted last.\n for (const table of SESSION_SCOPED) {\n graph.db.prepare(`DELETE FROM \"${table}\" WHERE session_id IN (SELECT session_id FROM session WHERE transcript_id = ?)`).run(transcriptId);\n }\n graph.db.prepare(`DELETE FROM \"${KIT.edge}\" WHERE fact_id IN (SELECT fact_id FROM fact WHERE transcript_id = ?)`).run(transcriptId);\n for (const table of [...AUDIT_TABLES, FACET_TABLE, REVIEW_TABLE]) {\n graph.db.prepare(`DELETE FROM \"${table}\" WHERE transcript_id = ?`).run(transcriptId);\n }\n graph.db.prepare(\"DELETE FROM fact WHERE transcript_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM session WHERE transcript_id = ?\").run(transcriptId);\n })();\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\n\nconst IN_BATCH = \"SELECT value FROM json_each(@sessionIds)\";\n\n/** Order, gap and context growth by request position; `seq_in_session` is 0-based like `turn.turn_index`. */\nconst REQUEST_ORDER = `\n WITH ordered AS (\n SELECT transcript_id, request_id, ts, context_tokens,\n ROW_NUMBER() OVER w - 1 AS seq,\n LAG(ts) OVER w AS prev_ts,\n LAG(context_tokens) OVER w AS prev_context,\n LAG(output_tokens) OVER w AS prev_output\n FROM request WHERE session_id IN (${IN_BATCH})\n WINDOW w AS (PARTITION BY session_id ORDER BY ts, byte_offset, transcript_id, request_id)\n )\n UPDATE request SET seq_in_session = ordered.seq,\n gap_ms = CAST(ROUND((julianday(ordered.ts) - julianday(ordered.prev_ts)) * 86400000) AS INTEGER),\n ctx_delta = ordered.context_tokens - (ordered.prev_context + ordered.prev_output)\n FROM ordered WHERE request.transcript_id = ordered.transcript_id AND request.request_id = ordered.request_id`;\n\n/**\n * A request is woken by the last arrival before it in its own transcript and\n * session. A tool result names its tool through `tool_call`, and an\n * `AskUserQuestion` result is the human answering, not a tool reporting.\n */\nconst REQUEST_WAKE = `\n WITH hit AS (\n SELECT r.transcript_id, r.request_id,\n (SELECT MAX(i.byte_offset) FROM inbound i\n WHERE i.transcript_id = r.transcript_id AND i.session_id = r.session_id AND i.byte_offset < r.byte_offset) AS wake_offset\n FROM request r WHERE r.session_id IN (${IN_BATCH})\n ),\n tools AS (\n SELECT transcript_id, tool_use_id, name, family, mcp_server,\n ROW_NUMBER() OVER (PARTITION BY transcript_id, tool_use_id ORDER BY byte_offset, block_index) AS pick\n FROM tool_call WHERE session_id IN (${IN_BATCH})\n ),\n wake AS (\n SELECT hit.transcript_id, hit.request_id, hit.wake_offset, i.cause, i.delivery, i.detail, i.from_name,\n t.name AS tool_name, t.family, t.mcp_server\n FROM hit\n LEFT JOIN inbound i ON i.transcript_id = hit.transcript_id AND i.byte_offset = hit.wake_offset\n 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)\n LEFT JOIN tools t ON t.transcript_id = i.transcript_id AND t.tool_use_id = i.tool_use_id AND t.pick = 1\n )\n UPDATE request SET\n wake_cause = CASE WHEN wake.cause IS NULL THEN 'session_start'\n WHEN wake.tool_name = 'AskUserQuestion' THEN 'ask_user_answer' ELSE wake.cause END,\n wake_delivery = wake.delivery,\n wake_detail = COALESCE(wake.tool_name, wake.from_name, wake.detail),\n wake_tool_family = wake.family,\n wake_mcp_server = wake.mcp_server,\n wake_offset = wake.wake_offset\n FROM wake WHERE request.transcript_id = wake.transcript_id AND request.request_id = wake.request_id`;\n\n/** How long a message waited: its delivery paired with the latest earlier enqueue of the same content. */\nconst INBOUND_QUEUED = `\n UPDATE inbound SET queued_ms = (\n SELECT CAST(ROUND((julianday(inbound.ts) - julianday(q.ts)) * 86400000) AS INTEGER)\n FROM queue_op q\n WHERE q.transcript_id = inbound.transcript_id AND q.session_id = inbound.session_id\n AND q.operation = 'enqueue' AND q.content_hash = inbound.content_hash AND q.byte_offset < inbound.byte_offset\n ORDER BY q.byte_offset DESC LIMIT 1)\n WHERE session_id IN (${IN_BATCH})`;\n\n/** A compaction whose last arrival was a tool result fired inside a tool loop, not between turns. */\nconst COMPACTION_MID_LOOP = `\n UPDATE compaction SET mid_loop = COALESCE((\n SELECT i.delivery = 'tool_result' FROM inbound i\n WHERE i.transcript_id = compaction.transcript_id AND i.session_id = compaction.session_id AND i.byte_offset < compaction.byte_offset\n ORDER BY i.byte_offset DESC, i.block_index DESC LIMIT 1), 0)\n WHERE session_id IN (${IN_BATCH})`;\n\nconst TURN_WAKE = `\n UPDATE turn SET wake_cause = (\n SELECT i.cause FROM fact f JOIN inbound i ON i.transcript_id = f.transcript_id AND i.byte_offset = f.byte_offset\n WHERE f.fact_id = turn.fact_id_start ORDER BY i.block_index LIMIT 1)\n WHERE session_id IN (${IN_BATCH})`;\n\n/**\n * Usage is a sum over deduplicated requests, never an accumulation of lines.\n * A request copied into a second transcript counts once. Sessions with no\n * request rows keep what they have: their transcripts predate the table or are gone.\n */\nconst CLEAR_USAGE = `\n DELETE FROM session_model_usage\n WHERE session_id IN (${IN_BATCH}) AND session_id IN (SELECT session_id FROM request WHERE session_id IN (${IN_BATCH}))`;\n\nconst RECOMPUTE_USAGE = `\n WITH ranked AS (\n SELECT *, ROW_NUMBER() OVER (PARTITION BY session_id, request_id ORDER BY ts, transcript_id) AS pick\n FROM request WHERE session_id IN (${IN_BATCH})\n )\n INSERT INTO session_model_usage (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, thinking_tokens, request_count)\n SELECT session_id, model, SUM(input_tokens), SUM(output_tokens), SUM(cache_read_tokens), SUM(cache_creation_tokens), SUM(thinking_tokens), COUNT(*)\n FROM ranked WHERE pick = 1 GROUP BY session_id, model`;\n\nconst STEPS = [REQUEST_ORDER, REQUEST_WAKE, INBOUND_QUEUED, COMPACTION_MID_LOOP, TURN_WAKE, CLEAR_USAGE, RECOMPUTE_USAGE] as const;\n\nexport type AuditRollup = (sessionIdsJson: string) => void;\n\n/** Prepare the audit recomputes once; the caller runs them per batch inside its own transaction. */\nexport function prepareAuditRollup(db: Db): AuditRollup {\n const statements = STEPS.map((sql) => db.prepare(sql));\n return (sessionIds) => {\n for (const statement of statements) statement.run({ sessionIds });\n };\n}\n","import { prepareAuditRollup } from \"./audit-rollup.js\";\nimport type { SessionGraph } from \"./graph.js\";\n\nconst BATCH = 400;\n\n/**\n * Turn aggregates that no single line can supply. Contract: recompute, never\n * accumulate. Every value is a pure function of the fact rows, so incremental\n * refreshes and full rebuilds converge regardless of chunk boundaries.\n */\nconst ROLLUP = `\n WITH ordered AS (\n SELECT fact_id, session_id, event_type, ts, prompt_id,\n COUNT(CASE WHEN event_type = 'user_prompt' THEN 1 END) OVER (\n PARTITION BY session_id ORDER BY ts, transcript_id, seq ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS turn_no,\n LAG(event_type) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_type,\n LAG(ts) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_ts\n FROM fact WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n ),\n owners AS (SELECT session_id, turn_no, prompt_id FROM ordered WHERE event_type = 'user_prompt'),\n agg AS (\n SELECT owners.prompt_id AS prompt_id, MAX(ordered.ts) AS ended_at,\n CAST(ROUND((julianday(MAX(ordered.ts)) - julianday(MIN(ordered.ts))) * 86400000) AS INTEGER) AS duration_ms,\n SUM(CASE WHEN ordered.event_type = 'tool_decision' THEN 1 ELSE 0 END) AS tool_call_count,\n CAST(COALESCE(SUM(CASE\n WHEN ordered.event_type IN ('assistant_response', 'tool_decision') AND ordered.prev_type IN ('user_prompt', 'tool_result', 'tool_result_error')\n THEN ROUND((julianday(ordered.ts) - julianday(ordered.prev_ts)) * 86400000) END), 0) AS INTEGER) AS thinking_ms\n FROM ordered JOIN owners ON owners.session_id = ordered.session_id AND owners.turn_no = ordered.turn_no\n GROUP BY owners.prompt_id\n )\n UPDATE turn SET ended_at = agg.ended_at, duration_ms = COALESCE(agg.duration_ms, 0),\n tool_call_count = agg.tool_call_count, thinking_ms = COALESCE(agg.thinking_ms, 0)\n FROM agg WHERE turn.prompt_id = agg.prompt_id`;\n\n/** `turn_index` is insert-order at write time; recomputing it from `started_at` makes it a property of the corpus. */\nconst RENUMBER_TURNS = `\n WITH ordered AS (\n SELECT prompt_id, ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY started_at, prompt_id) - 1 AS idx\n FROM turn WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n )\n UPDATE turn SET turn_index = ordered.idx FROM ordered WHERE turn.prompt_id = ordered.prompt_id`;\n\n/** Recompute turn aggregates and the audit rollup for the given sessions in one transaction. */\nexport function rollupSessions(graph: SessionGraph, sessionIds: readonly string[]): number {\n const unique = [...new Set(sessionIds)];\n if (unique.length === 0) return 0;\n const aggregate = graph.db.prepare(ROLLUP);\n const renumber = graph.db.prepare(RENUMBER_TURNS);\n const audit = prepareAuditRollup(graph.db);\n return graph.db.transaction(() => {\n let updated = 0;\n for (let i = 0; i < unique.length; i += BATCH) {\n const batch = JSON.stringify(unique.slice(i, i + BATCH));\n renumber.run({ sessionIds: batch });\n updated += aggregate.run({ sessionIds: batch }).changes;\n audit(batch);\n }\n return updated;\n })();\n}\n\n/**\n * A merge sighting and the `pr-link` naming the same PR routinely live in different transcripts; fold at pass end.\n * Once the forge has checked a PR (`outcome_checked_at` set), its `merged_at` is forge-accurate and must not be\n * overwritten by a transcript sighting time on a later pass.\n */\nconst RECONCILE_PR_MERGES = `\n UPDATE pr SET state = 'merged',\n merged_at = (SELECT MIN(o.merged_at) FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))\n WHERE pr.outcome_checked_at IS NULL\n AND EXISTS (SELECT 1 FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))`;\n\n/** Both halves of a `gh pr create` make a real PR whether or not a `pr-link` ever mentioned it. */\nconst RECONCILE_PR_CREATES = `\n INSERT INTO pr (pr_ref, number, repo, title, url)\n SELECT 'pr:' || repo || '#' || number, number, repo, title, url FROM pr_create_observation\n WHERE number IS NOT NULL AND repo IS NOT NULL AND title IS NOT NULL\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(pr.title, excluded.title), url = COALESCE(pr.url, excluded.url)`;\n\n/**\n * A subagent ends when its child session's last line does, not when the\n * dispatch's tool_result returns (most dispatches are async and return at\n * launch). Parentage comes from the dispatch that ran inside a child transcript.\n */\nconst RECONCILE_SUBAGENTS = `\n UPDATE subagent SET\n ended_at = COALESCE((SELECT s.ended_at FROM session s WHERE s.session_id = subagent.child_session_id), ended_at),\n parent_agent_ref = COALESCE((SELECT p.agent_ref FROM subagent p WHERE p.child_session_id = subagent.session_id), parent_agent_ref)`;\n\nexport interface ReconcileCounts {\n prMerges: number;\n prCreates: number;\n subagents: number;\n}\n\n/** Whole-table reconciliations, run once at the end of a refresh pass after the rollup. */\nexport function reconcile(graph: SessionGraph): ReconcileCounts {\n return {\n prCreates: graph.db.prepare(RECONCILE_PR_CREATES).run().changes,\n prMerges: graph.db.prepare(RECONCILE_PR_MERGES).run().changes,\n subagents: graph.db.prepare(RECONCILE_SUBAGENTS).run().changes,\n };\n}\n","import { promises as fs } from \"node:fs\";\nimport { contentHash, resumePoint } from \"@titan-design/locator\";\nimport { TranscriptParseError, extractTranscript, type DiscoveredTranscript } from \"@titan-design/session-read\";\nimport { applyDelta } from \"./apply.js\";\nimport { advanceFacet, backfillFacets } from \"./facet.js\";\nimport { allSessionIds, type SessionGraph } from \"./graph.js\";\nimport { enrichPrs, type PrEnrichment, type PrResolver } from \"./outcomes.js\";\nimport { resolveOrigins, type OriginEnrichment, type OriginResolver } from \"./origin.js\";\nimport { purgeTranscript } from \"./purge.js\";\nimport { projectReviewRounds, type ReviewerProfilePredicate, type ReviewProjection } from \"./review-rounds.js\";\nimport { reconcile, rollupSessions, type ReconcileCounts } from \"./rollup.js\";\nimport { allTaskIds, enrichTasks, NO_ENRICHMENT, type TaskEnrichment, type TaskResolver } from \"./tasks.js\";\n\nexport interface IndexOptions {\n /** Re-hash the already-read prefix to catch same-length rewrites. O(file), so opt in. */\n verifyHash?: boolean;\n /** Store a whole-file content hash for durability reporting. O(file), so opt in. */\n withContentHash?: boolean;\n /** Fill task title, initiative and present status from the caller's store. Absent means transcripts alone. */\n resolveTasks?: TaskResolver;\n /** Record who launched each session. Runs once per `refreshCorpus` pass, never per transcript. Absent means no origin rows. */\n resolveOrigins?: OriginResolver;\n /** Fill PR state, merge, close and review rounds from the caller's forge. Runs once per `refreshCorpus` pass. Absent means transcripts alone. */\n resolvePrs?: PrResolver;\n}\n\nexport interface RefreshOptions extends IndexOptions {\n /** Which sender profiles' chat verdicts count toward review rounds. Defaults to `reviewer` and `*-reviewer`. */\n isReviewerProfile?: ReviewerProfilePredicate;\n /** Roll up every session, not just the ones this pass touched. */\n full?: boolean;\n /** Stale audit facets re-extracted per pass (default 40). `Infinity` clears the backlog. */\n facetLimit?: number;\n}\n\nexport type TranscriptOutcome =\n | { status: \"unchanged\" | \"indexed\" | \"rewound\"; transcriptId: number; sessionIds: string[]; facts: number; tasks: TaskEnrichment }\n | { status: \"missing\" | \"quarantined\"; transcriptId: number; sessionIds: string[]; facts: 0; tasks: TaskEnrichment; reason: string };\n\n/**\n * Bring one transcript up to date: read from its watermark (or byte 0 after a\n * rewrite), apply the delta, advance the watermark. A malformed line\n * quarantines the transcript instead of poisoning the pass.\n */\nexport async function indexTranscript(graph: SessionGraph, transcript: DiscoveredTranscript, options: IndexOptions = {}): Promise<TranscriptOutcome> {\n const row = graph.transcripts.ensure(transcript.displayPath);\n const entry = { path: transcript.displayPath, lastByteOffset: row.lastOffset, prefixHash: row.prefixHash, mtime: row.fileMtime };\n const point = await resumePoint(entry, transcript.absolutePath, { verifyHash: options.verifyHash });\n const base = { transcriptId: row.sourceId, sessionIds: [] as string[], tasks: NO_ENRICHMENT };\n if (point.state === \"missing\") {\n graph.transcripts.markStatus(transcript.displayPath, \"missing\", \"source file no longer exists\");\n return { ...base, status: \"missing\", facts: 0, reason: \"source file no longer exists\" };\n }\n if (point.state === \"unchanged\") {\n // A file that vanished and came back unchanged resumes at EOF, so there are no\n // bytes to read and `advance()` — the only writer of status 'ok' — never runs,\n // stranding the row on 'missing' forever. Clear it here rather than forcing a\n // re-read, which would rewrite the prefix hash from a zero-byte delta.\n // Quarantine is deliberately not cleared: the same bytes still will not parse.\n if (row.status === \"missing\") graph.transcripts.markStatus(transcript.displayPath, \"ok\", null);\n return { ...base, status: \"unchanged\", facts: 0 };\n }\n\n try {\n const delta = await extractTranscript(transcript.absolutePath, {\n fromByteOffset: point.start,\n priorPrefixHash: point.state === \"rewritten\" ? null : row.prefixHash,\n subagentId: transcript.subagentId,\n });\n // Two ways to learn a transcript was rewritten, and both must purge. `resumePoint`\n // catches a file that shrank; `extractTranscript` re-hashes the prefix it was asked\n // to skip and restarts from zero on its own when the bytes moved — a rotation that\n // left the file the same length or longer. Ignoring its answer replays the whole\n // file onto rows that were never removed, which is the accumulation purge exists\n // to prevent, reached by a different road.\n const rewound = point.state === \"rewritten\" || delta.restartedFromZero;\n // After extraction, never before: a parse failure quarantines the transcript,\n // and purging first would destroy rows we could then no longer rebuild.\n if (rewound) purgeTranscript(graph, row.sourceId);\n applyDelta(graph, row.sourceId, delta, { account: transcript.account });\n advanceFacet(graph.db, row.sourceId, rewound ? 0 : point.start, delta.lastByteOffset);\n const stat = await fs.stat(transcript.absolutePath);\n graph.transcripts.advance(transcript.displayPath, {\n lastOffset: delta.lastByteOffset,\n prefixHash: delta.prefixHash,\n fileSize: stat.size,\n fileMtime: stat.mtime.toISOString(),\n contentHash: options.withContentHash ? await contentHash(transcript.absolutePath) : null,\n });\n const sessionIds = delta.sessions.map((s) => s.sessionId);\n const taskIds = delta.tasks.map((t) => t.taskId);\n const tasks = taskIds.length > 0 ? await enrichTasks(graph, options.resolveTasks, taskIds) : NO_ENRICHMENT;\n return { ...base, sessionIds, tasks, status: rewound ? \"rewound\" : \"indexed\", facts: delta.facts.length };\n } catch (err) {\n if (!(err instanceof TranscriptParseError)) throw err;\n graph.transcripts.markStatus(transcript.displayPath, \"quarantined\", err.message);\n return { ...base, status: \"quarantined\", facts: 0, reason: err.message };\n }\n}\n\nexport interface RefreshSummary {\n transcripts: number;\n indexed: number;\n unchanged: number;\n rewound: number;\n missing: number;\n quarantined: number;\n facts: number;\n turnsRolledUp: number;\n reconciled: ReconcileCounts;\n tasks: TaskEnrichment;\n origins: OriginEnrichment;\n prs: PrEnrichment;\n reviews: ReviewProjection;\n markedMissing: number;\n facetsBackfilled: number;\n /** Transcripts whose audit facet is still stale after this pass. */\n facetBacklog: number;\n}\n\n/**\n * One pass over a corpus: index every transcript, re-extract a bounded batch of\n * stale audit facets, roll up the sessions that changed, resolve session\n * origins, reconcile cross-transcript observations, resolve PR outcomes,\n * project review rounds, enrich tasks, and mark rows whose source file is gone. Idempotent: a second pass over unchanged\n * files changes nothing.\n *\n * The resolver is hoisted out of the per-transcript loop and run once over the\n * whole task table, so a corpus of N transcripts costs one resolver call rather\n * than N, and a task whose store row changed refreshes even when no transcript\n * did. That bounds staleness to one pass.\n */\nexport async function refreshCorpus(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], options: RefreshOptions = {}): Promise<RefreshSummary> {\n const { resolveTasks, resolveOrigins: originResolver, resolvePrs, isReviewerProfile, full, facetLimit, ...perTranscript } = options;\n const counts = { indexed: 0, unchanged: 0, rewound: 0, missing: 0, quarantined: 0 };\n const touched: string[] = [];\n let facts = 0;\n for (const transcript of transcripts) {\n const outcome = await indexTranscript(graph, transcript, perTranscript);\n counts[outcome.status] += 1;\n facts += outcome.facts;\n touched.push(...outcome.sessionIds);\n }\n const facets = await backfillFacets(graph, transcripts, { limit: facetLimit });\n touched.push(...facets.sessionIds);\n const turnsRolledUp = rollupSessions(graph, full ? allSessionIds(graph) : touched);\n // Before reconcile, so a worker's subagent row gets its ended_at in the same pass.\n const origins = await resolveOrigins(graph, originResolver);\n const reconciled = reconcile(graph);\n // After reconcile, so a merge a transcript witnessed is already sticky when the forge answers.\n const prs = await enrichPrs(graph, resolvePrs);\n const reviews = projectReviewRounds(graph, { isReviewerProfile });\n const tasks = await enrichTasks(graph, resolveTasks, allTaskIds(graph));\n const markedMissing = await markMissing(graph, transcripts);\n const facetSummary = { facetsBackfilled: facets.backfilled, facetBacklog: facets.backlog };\n return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, origins, prs, reviews, markedMissing, ...facetSummary };\n}\n\n/** Rows absent from discovery are only nominated; an `fs.stat` decides, so an empty scan cannot condemn the corpus. */\nasync function markMissing(graph: SessionGraph, discovered: readonly DiscoveredTranscript[]): Promise<number> {\n const present = new Set(discovered.map((t) => t.displayPath));\n const byPath = new Map(discovered.map((t) => [t.displayPath, t.absolutePath]));\n let marked = 0;\n for (const row of graph.transcripts.list()) {\n if (row.status === \"missing\" || present.has(row.sourceKey)) continue;\n const normalized = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n const absolute = normalized ? (JSON.parse(normalized.descriptor) as { path: string }).path : byPath.get(row.sourceKey) ?? row.sourceKey;\n if (await exists(absolute)) continue;\n graph.transcripts.markStatus(row.sourceKey, \"missing\", \"source file no longer exists\");\n marked += 1;\n }\n return marked;\n}\n\nasync function exists(absolutePath: string): Promise<boolean> {\n try {\n await fs.stat(absolutePath);\n return true;\n } catch {\n return false;\n }\n}\n","import { RELATIONS } from \"@titan-design/session-read\";\nimport { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { KIT } from \"./schema.js\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\n/** Decides from a sender's `session_origin.profile` whether its chat verdicts count. */\nexport type ReviewerProfilePredicate = (profile: string) => boolean;\n\n/** The default role set: `reviewer`, or any profile ending in `-reviewer`. */\nexport const isReviewerProfile: ReviewerProfilePredicate = (profile) => profile === \"reviewer\" || profile.endsWith(\"-reviewer\");\n\nexport interface ReviewRoundOptions {\n isReviewerProfile?: ReviewerProfilePredicate;\n}\n\nexport interface ReviewProjection {\n /** Chat verdicts given a `pr_ref` this pass. */\n resolved: number;\n /** Chat verdicts whose PR is still unknown. */\n unresolved: number;\n /** Reviews ignored for a time that does not parse, plus PRs whose commit times were unusable. */\n invalidTimes: number;\n}\n\n/**\n * The number of distinct heads among changes-requested reviews that a later commit answered.\n * A review's head is the count of commits at or before it, so two reviews on one head count once.\n * A review whose time does not parse is ignored. Returns null when any commit time does not\n * parse, because a missing commit would shift every later head.\n */\nexport function countRounds(changesRequestedAt: readonly string[], commitTimes: readonly string[]): number | null {\n const commits = parseCommitTimes(commitTimes);\n return commits ? answeredHeads(changesRequestedAt, commits).heads.size : null;\n}\n\nfunction parseCommitTimes(commitTimes: readonly string[]): number[] | null {\n const commits = commitTimes.map(Date.parse);\n return commits.some(Number.isNaN) ? null : commits;\n}\n\nfunction answeredHeads(reviewTimes: readonly string[], commits: readonly number[]): { heads: Set<number>; ignored: number } {\n const heads = new Set<number>();\n let ignored = 0;\n for (const ts of reviewTimes) {\n const at = Date.parse(ts);\n if (Number.isNaN(at)) {\n ignored += 1;\n continue;\n }\n const head = commits.filter((c) => c <= at).length;\n if (head < commits.length) heads.add(head);\n }\n return { heads, ignored };\n}\n\n/**\n * Resolve each unresolved chat verdict to a PR, then write `review_rounds_chat` and `review_rounds`\n * for every PR. Runs after `enrichPrs`, so forge reviews and commit times are current.\n */\nexport function projectReviewRounds(graph: SessionGraph, options: ReviewRoundOptions = {}): ReviewProjection {\n const isReviewer = options.isReviewerProfile ?? isReviewerProfile;\n return graph.db.transaction(() => {\n const resolved = resolveChatVerdicts(graph.db);\n const invalidTimes = writeRounds(graph.db, isReviewer);\n const { n } = graph.db.prepare(`SELECT count(*) AS n FROM ${REVIEW_TABLE} WHERE surface = 'chat' AND pr_ref IS NULL`).get() as { n: number };\n return { resolved, unresolved: n, invalidTimes };\n })();\n}\n\ninterface ChatVerdictRow {\n source_key: string;\n session_id: string | null;\n repo: string | null;\n repo_hint: string | null;\n cwd_repo: string | null;\n number: number;\n}\n\ninterface Candidate {\n pr_ref: string;\n repo: string;\n}\n\nfunction resolveChatVerdicts(db: Db): number {\n const rows = db.prepare(`SELECT source_key, session_id, repo, repo_hint, cwd_repo, number FROM ${REVIEW_TABLE} WHERE surface = 'chat' AND pr_ref IS NULL`).all() as ChatVerdictRow[];\n const update = db.prepare(`UPDATE ${REVIEW_TABLE} SET pr_ref = ? WHERE source_key = ?`);\n let resolved = 0;\n for (const row of rows) {\n const prRef = resolvePrRef(db, row);\n if (prRef) resolved += update.run(prRef, row.source_key).changes;\n }\n return resolved;\n}\n\n/** An exact repo must exist; a hint that matches nothing falls through to the family links and then the sender's repo. */\nfunction resolvePrRef(db: Db, row: ChatVerdictRow): string | null {\n const candidates = db.prepare(\"SELECT pr_ref, repo FROM pr WHERE number = ? AND repo IS NOT NULL\").all(row.number) as Candidate[];\n if (row.repo) return candidates.find((c) => c.repo === row.repo)?.pr_ref ?? null;\n const hinted = candidates.filter((c) => sameRepoName(c.repo, row.repo_hint));\n if (hinted.length === 1) return hinted[0]!.pr_ref;\n const pool = hinted.length > 0 ? hinted : candidates;\n const linked = familyLinks(db, row.session_id);\n const bySibling = pool.filter((c) => linked.has(c.pr_ref));\n if (bySibling.length === 1) return bySibling[0]!.pr_ref;\n const byCwd = (bySibling.length > 0 ? bySibling : pool).filter((c) => sameRepoName(c.repo, row.cwd_repo));\n return byCwd.length === 1 ? byCwd[0]!.pr_ref : null;\n}\n\n/** Compares a bare repo name with the last path segment of an `owner/name` repo. */\nfunction sameRepoName(repo: string, name: string | null): boolean {\n return name !== null && repo.split(\"/\").pop()!.toLowerCase() === name.toLowerCase();\n}\n\n/** The sender, its parent, and every session that parent spawned, whether the spawn is known from an origin or a transcript. */\nconst FAMILY_LINKS = `\n WITH parent(id) AS (\n SELECT parent_session_id FROM session_origin WHERE session_id = @sessionId AND parent_session_id IS NOT NULL\n UNION SELECT session_id FROM subagent WHERE child_session_id = @sessionId AND session_id IS NOT NULL\n ), family(id) AS (\n SELECT @sessionId UNION SELECT id FROM parent\n UNION SELECT session_id FROM session_origin WHERE parent_session_id IN (SELECT id FROM parent)\n UNION SELECT child_session_id FROM subagent WHERE session_id IN (SELECT id FROM parent) AND child_session_id IS NOT NULL\n UNION SELECT substr(target_ref, 9) FROM \"${KIT.edge}\" WHERE relation = '${RELATIONS.SPAWNED}' AND t_expired IS NULL\n AND target_ref LIKE 'session:%' AND source_ref IN (SELECT 'session:' || id FROM parent)\n )\n SELECT DISTINCT target_ref FROM \"${KIT.edge}\" WHERE relation = '${RELATIONS.LINKED}' AND t_expired IS NULL\n AND source_ref IN (SELECT 'session:' || id FROM family)`;\n\nfunction familyLinks(db: Db, sessionId: string | null): Set<string> {\n if (!sessionId) return new Set();\n const rows = db.prepare(FAMILY_LINKS).all({ sessionId }) as { target_ref: string }[];\n return new Set(rows.map((r) => r.target_ref));\n}\n\ninterface PrRoundRow {\n pr_ref: string;\n commit_times: string | null;\n review_rounds_gh: number | null;\n}\n\ninterface ChangesRequested {\n pr_ref: string;\n surface: string;\n ts: string;\n profile: string | null;\n}\n\nconst CHANGES_REQUESTED = `\n SELECT r.pr_ref, r.surface, r.ts, o.profile FROM ${REVIEW_TABLE} r LEFT JOIN session_origin o ON o.session_id = r.session_id\n WHERE r.verdict = 'changes_requested' AND r.pr_ref IS NOT NULL`;\n\n/** Returns the count of unusable times met, for the pass summary. */\nfunction writeRounds(db: Db, isReviewer: ReviewerProfilePredicate): number {\n const byPr = new Map<string, ChangesRequested[]>();\n for (const review of db.prepare(CHANGES_REQUESTED).all() as ChangesRequested[]) {\n if (review.surface === \"chat\" && !(review.profile && isReviewer(review.profile))) continue;\n byPr.set(review.pr_ref, [...(byPr.get(review.pr_ref) ?? []), review]);\n }\n const update = db.prepare(\"UPDATE pr SET review_rounds_chat = ?, review_rounds = ? WHERE pr_ref = ?\");\n let invalidTimes = 0;\n for (const pr of db.prepare(\"SELECT pr_ref, commit_times, review_rounds_gh FROM pr\").all() as PrRoundRow[]) {\n const { chat, total, invalid } = roundsFor(pr, byPr.get(pr.pr_ref) ?? []);\n update.run(chat, total, pr.pr_ref);\n invalidTimes += invalid;\n }\n return invalidTimes;\n}\n\n/**\n * Chat heads join the forge count only where no forge review already sits on that head,\n * so a resolver that sends only a count still adds to the chat rounds unchanged.\n */\nfunction roundsFor(pr: PrRoundRow, reviews: readonly ChangesRequested[]): { chat: number | null; total: number | null; invalid: number } {\n const commits = pr.commit_times === null ? null : parseCommitTimes(JSON.parse(pr.commit_times) as string[]);\n if (!commits) return { chat: null, total: pr.review_rounds_gh, invalid: pr.commit_times === null ? 0 : 1 };\n const timesOn = (surface: string) => reviews.filter((r) => r.surface === surface).map((r) => r.ts);\n const forge = answeredHeads(timesOn(\"github\"), commits);\n const chat = answeredHeads(timesOn(\"chat\"), commits);\n const chatOnly = [...chat.heads].filter((head) => !forge.heads.has(head)).length;\n return { chat: chat.heads.size, total: (pr.review_rounds_gh ?? 0) + chatOnly, invalid: forge.ignored + chat.ignored };\n}\n","import { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { countRounds } from \"./review-rounds.js\";\n\n/** A PR the graph holds, as the resolver needs it to ask a forge. */\nexport interface PrKey {\n prRef: string;\n repo: string;\n number: number;\n}\n\n/**\n * What a forge knows and a transcript cannot state: a merge done by another\n * session or in the browser, a close, and the review history. Every field is\n * optional; an omitted or null field leaves whatever the transcripts derived.\n */\nexport interface ResolvedPr {\n /** `open`, `closed` or `merged`, in any case. */\n state?: string | null;\n mergedAt?: string | null;\n closedAt?: string | null;\n /** Stored as the forge's round count when `reviews` or `commitTimes` is absent; otherwise the round rule counts it. */\n reviewRounds?: number | null;\n /** The PR's commit times; an empty array is stored as known-empty, an omitted field leaves the stored value. */\n commitTimes?: readonly string[];\n /** Replaces the PR's stored forge reviews; an omitted field leaves them. */\n reviews?: readonly ResolvedReview[];\n}\n\n/** One forge review. `APPROVED` and `CHANGES_REQUESTED`, in any case, are stored; other states are ignored. */\nexport interface ResolvedReview {\n state: string;\n submittedAt: string;\n}\n\n/** Resolutions keyed by `pr_ref` (`pr:acme/demo#7`). */\nexport type PrResolution = ReadonlyMap<string, ResolvedPr | null | undefined>;\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn which forge a product talks to. Called once per pass with every\n * PR whose outcome may still change.\n */\nexport type PrResolver = (prs: readonly PrKey[]) => PromiseLike<PrResolution> | PrResolution;\n\nexport interface PrEnrichment {\n /** PRs handed to the resolver. */\n requested: number;\n /** PR rows the resolver updated. */\n applied: number;\n /** The resolver threw; rows stand as the transcripts left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_PR_OUTCOMES: PrEnrichment = Object.freeze({ requested: 0, applied: 0, failed: false });\n\n/**\n * A merge is final, so a merged PR is asked about once commit times are known; open and closed PRs\n * can still move. Never-checked PRs lead and missing commit times trail, so a per-pass cap cannot starve open PRs.\n */\nconst PRS_NEEDING_OUTCOME = `\n SELECT pr_ref, repo, number FROM pr\n WHERE repo IS NOT NULL AND number IS NOT NULL\n AND (outcome_checked_at IS NULL OR state IS NOT 'merged' OR commit_times IS NULL)\n ORDER BY CASE WHEN outcome_checked_at IS NULL THEN 0 WHEN state IS NOT 'merged' THEN 1 ELSE 2 END, pr_ref`;\n\n/** A stale forge cache must not reopen a PR a transcript saw merged, so `merged` is sticky. */\nconst APPLY_OUTCOME = `\n UPDATE pr SET\n state = CASE WHEN state = 'merged' THEN state ELSE COALESCE(lower(@state), state) END,\n merged_at = COALESCE(@mergedAt, merged_at),\n closed_at = COALESCE(@closedAt, closed_at),\n review_rounds = COALESCE(@reviewRounds, review_rounds),\n review_rounds_gh = COALESCE(@reviewRounds, review_rounds_gh),\n commit_times = COALESCE(@commitTimes, commit_times),\n outcome_checked_at = @checkedAt\n WHERE pr_ref = @prRef`;\n\nexport function prsNeedingOutcome(graph: SessionGraph): PrKey[] {\n const rows = graph.db.prepare(PRS_NEEDING_OUTCOME).all() as { pr_ref: string; repo: string; number: number }[];\n return rows.map((r) => ({ prRef: r.pr_ref, repo: r.repo, number: r.number }));\n}\n\n/**\n * Fill PR outcomes from a caller-supplied resolver. Runs after `reconcile`, so\n * it sees the merges the transcripts witnessed and only adds what they missed.\n * Updates rows and never inserts one: a PR enters the graph from a transcript.\n * A resolver that throws costs this pass its outcomes and nothing else.\n */\nexport async function enrichPrs(graph: SessionGraph, resolver: PrResolver | undefined): Promise<PrEnrichment> {\n if (!resolver) return NO_PR_OUTCOMES;\n const prs = prsNeedingOutcome(graph);\n if (prs.length === 0) return NO_PR_OUTCOMES;\n try {\n const resolved = await resolver(prs);\n return { requested: prs.length, applied: write(graph, resolved, new Date().toISOString()), failed: false };\n } catch (err) {\n return { requested: prs.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction write(graph: SessionGraph, resolved: PrResolution, checkedAt: string): number {\n const apply = graph.db.prepare(APPLY_OUTCOME);\n const stored = graph.db.prepare(\"SELECT commit_times FROM pr WHERE pr_ref = ?\");\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [prRef, fields] of resolved) {\n if (!fields) continue;\n applied += apply.run({\n prRef,\n checkedAt,\n state: fields.state ?? null,\n mergedAt: fields.mergedAt ?? null,\n closedAt: fields.closedAt ?? null,\n reviewRounds: forgeRounds(fields, () => storedCommitTimes(stored.get(prRef))),\n commitTimes: fields.commitTimes ? JSON.stringify(fields.commitTimes) : null,\n }).changes;\n if (fields.reviews) replaceForgeReviews(graph, prRef, fields.reviews);\n }\n return applied;\n })();\n}\n\nconst isChangesRequested = (review: ResolvedReview) => review.state.toUpperCase() === \"CHANGES_REQUESTED\";\n\n/** Counts against sent commit times, else stored ones; unusable or absent times leave `reviewRounds` as sent. */\nfunction forgeRounds(fields: ResolvedPr, storedTimes: () => string[] | null): number | null {\n if (!fields.reviews) return fields.reviewRounds ?? null;\n const commitTimes = fields.commitTimes ?? storedTimes();\n const counted = commitTimes ? countRounds(fields.reviews.filter(isChangesRequested).map((r) => r.submittedAt), commitTimes) : null;\n return counted ?? fields.reviewRounds ?? null;\n}\n\nfunction storedCommitTimes(row: unknown): string[] | null {\n const times = (row as { commit_times: string | null } | undefined)?.commit_times;\n return times ? (JSON.parse(times) as string[]) : null;\n}\n\nconst FORGE_VERDICTS: Record<string, string> = { APPROVED: \"approve\", CHANGES_REQUESTED: \"changes_requested\" };\n\n/** Two reviews in the same second share a key; a changes-requested one is kept over an approval. */\nconst INSERT_FORGE_REVIEW = `\n INSERT INTO ${REVIEW_TABLE} (source_key, surface, verdict, ts, repo, number, pr_ref)\n SELECT 'gh:' || pr_ref || ':' || @ts, 'github', @verdict, @ts, repo, number, pr_ref FROM pr WHERE pr_ref = @prRef\n ON CONFLICT (source_key) DO UPDATE SET verdict = CASE WHEN verdict = 'changes_requested' THEN verdict ELSE excluded.verdict END`;\n\nfunction replaceForgeReviews(graph: SessionGraph, prRef: string, reviews: readonly ResolvedReview[]): void {\n graph.db.prepare(`DELETE FROM ${REVIEW_TABLE} WHERE surface = 'github' AND pr_ref = ?`).run(prRef);\n const insert = graph.db.prepare(INSERT_FORGE_REVIEW);\n for (const review of reviews) {\n const verdict = FORGE_VERDICTS[review.state.toUpperCase()];\n if (verdict) insert.run({ prRef, verdict, ts: review.submittedAt });\n }\n}\n","import { RELATIONS, sessionRef, taskRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * Who started a session and why, as a launcher such as agent-chat recorded it.\n * A transcript cannot state this: a `claude -p` worker and a headless miner look\n * the same from inside. Every field but `originSystem` is optional.\n */\nexport interface ResolvedOrigin {\n originSystem: string;\n agentId?: string | null;\n agentName?: string | null;\n parentName?: string | null;\n parentSessionId?: string | null;\n profile?: string | null;\n modelAlias?: string | null;\n surface?: string | null;\n isolation?: string | null;\n depth?: number | null;\n /** `spawned`, `adopted`, `inherited` or `human`. */\n originKind?: string | null;\n configDir?: string | null;\n spawnCwd?: string | null;\n spawnedAt?: string | null;\n briefChars?: number | null;\n briefExcerpt?: string | null;\n briefPath?: string | null;\n launchArgs?: string | null;\n /**\n * Task ids the spawn record assigned, primary first; each projects a `ran` edge. Leave it\n * undefined only if the resolver does not look for tasks: a stored link then stands and a\n * row without one is offered again. A task-aware resolver sets it, empty when nothing links.\n */\n taskIds?: readonly string[] | null;\n /** How the ids were found. Stored as `none` when `taskIds` is given but empty. */\n taskSource?: TaskLinkSource | null;\n}\n\n/** Stored when a task-aware resolver found no id, so the row is not offered again. */\nexport const NO_TASK_LINK = \"none\";\n\nexport type TaskLinkSource = \"name\" | \"name-over-brief\" | \"brief-anchor\" | \"brief-paragraph\" | typeof NO_TASK_LINK;\n\n/** A lifecycle event the launcher saw outside the transcript: `teleport`, `handoff`, `retired`, `exited`. */\nexport interface ExternalEvent {\n sessionId: string;\n ts: string;\n kind: string;\n detail?: string | null;\n /** Defaults to the session's resolved origin system. */\n originSystem?: string | null;\n}\n\nexport interface OriginResolution {\n /** Keyed by session id. */\n origins: Record<string, ResolvedOrigin>;\n externalEvents?: readonly ExternalEvent[];\n}\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn where a launcher keeps its records. Called once per pass with\n * every session that has no origin row, a stale one, or a null `task_source`.\n * A task-aware resolver returns an entry for every requested session it examined,\n * with `taskIds` empty when nothing links; a session left out is offered again.\n */\nexport type OriginResolver = (sessionIds: readonly string[]) => PromiseLike<OriginResolution> | OriginResolution;\n\nexport interface OriginEnrichment {\n /** Session ids handed to the resolver. */\n requested: number;\n /** `session_origin` rows the resolver wrote. */\n applied: number;\n /** `session_external_event` rows the resolver wrote. */\n events: number;\n /** The resolver threw; origin rows stand as the last pass left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_ORIGINS: OriginEnrichment = Object.freeze({ requested: 0, applied: 0, events: 0, failed: false });\n\n/**\n * A session whose transcript grew after its origin was resolved may have been retired or handed off since.\n * A null `task_source` means no task-aware resolver has answered for the row yet.\n */\nconst SESSIONS_NEEDING_ORIGIN = `\n SELECT s.session_id FROM session s LEFT JOIN session_origin o ON o.session_id = s.session_id\n WHERE o.session_id IS NULL OR o.resolved_at < s.ended_at OR o.task_source IS NULL\n ORDER BY s.session_id`;\n\nconst ORIGIN_COLUMNS = [\n \"origin_system\", \"agent_id\", \"agent_name\", \"parent_name\", \"parent_session_id\", \"profile\", \"model_alias\", \"surface\", \"isolation\", \"depth\",\n \"origin_kind\", \"config_dir\", \"spawn_cwd\", \"spawned_at\", \"brief_chars\", \"brief_excerpt\", \"brief_path\", \"launch_args\", \"resolved_at\",\n] as const;\n\n/** A null `task_source` means the resolver did not look for tasks, so the stored link stands; `none` clears it. */\nconst TASK_LINK_UPDATE = `task_source = COALESCE(excluded.task_source, task_source),\n task_ids = CASE WHEN excluded.task_source IS NOT NULL THEN excluded.task_ids ELSE task_ids END`;\n\n/** An update, not a replace, so a column this writer does not name keeps its value. */\nconst UPSERT_ORIGIN = `\n INSERT INTO session_origin (session_id, origin_system, agent_id, agent_name, parent_name, parent_session_id,\n profile, model_alias, surface, isolation, depth, origin_kind, config_dir, spawn_cwd, spawned_at,\n brief_chars, brief_excerpt, brief_path, launch_args, task_ids, task_source, resolved_at)\n VALUES (@sessionId, @originSystem, @agentId, @agentName, @parentName, @parentSessionId,\n @profile, @modelAlias, @surface, @isolation, @depth, @originKind, @configDir, @spawnCwd, @spawnedAt,\n @briefChars, @briefExcerpt, @briefPath, @launchArgs, @taskIds, @taskSource, @resolvedAt)\n ON CONFLICT (session_id) DO UPDATE SET ${ORIGIN_COLUMNS.map((c) => `${c} = excluded.${c}`).join(\", \")},\n ${TASK_LINK_UPDATE}`;\n\nconst UPSERT_EVENT = `\n INSERT INTO session_external_event (session_id, ts, origin_system, kind, detail)\n VALUES (@sessionId, @ts, COALESCE(@originSystem, (SELECT origin_system FROM session_origin WHERE session_id = @sessionId), 'unknown'), @kind, @detail)\n ON CONFLICT (session_id, ts, kind) DO UPDATE SET origin_system = excluded.origin_system, detail = excluded.detail`;\n\n/** Existing subagent queries then cover launcher-spawned workers unchanged; `reconcile` fills `ended_at` from the child session. */\nconst PROJECT_SUBAGENTS = `\n INSERT INTO subagent (agent_ref, session_id, child_session_id, agent_type, label, started_at)\n SELECT 'agent:' || origin_system || ':' || agent_id, parent_session_id, session_id, profile, agent_name, spawned_at\n FROM session_origin WHERE parent_session_id IS NOT NULL AND agent_id IS NOT NULL\n ON CONFLICT (agent_ref) DO UPDATE SET session_id = excluded.session_id, child_session_id = excluded.child_session_id,\n agent_type = excluded.agent_type, label = excluded.label, started_at = COALESCE(excluded.started_at, started_at)`;\n\nexport function sessionsNeedingOrigin(graph: SessionGraph): string[] {\n return (graph.db.prepare(SESSIONS_NEEDING_ORIGIN).all() as { session_id: string }[]).map((r) => r.session_id);\n}\n\n/**\n * Resolve origins for the sessions that lack a current one, then project every\n * stored origin into `spawned` edges and `subagent` rows. Projecting from the\n * table, not from this pass's answer, restores rows a parent transcript's purge\n * removed. A resolver that throws costs this pass its origins and nothing else.\n */\nexport async function resolveOrigins(graph: SessionGraph, resolver: OriginResolver | undefined): Promise<OriginEnrichment> {\n if (!resolver) return NO_ORIGINS;\n const sessionIds = sessionsNeedingOrigin(graph);\n let resolved: readonly string[] = [];\n try {\n const resolution = await resolver(sessionIds);\n const written = writeResolution(graph, resolution, new Date().toISOString());\n resolved = Object.keys(resolution.origins);\n return { requested: sessionIds.length, ...written, failed: false };\n } catch (err) {\n return { requested: sessionIds.length, applied: 0, events: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n } finally {\n projectOrigins(graph, resolved);\n }\n}\n\nfunction writeResolution(graph: SessionGraph, resolution: OriginResolution, resolvedAt: string): { applied: number; events: number } {\n const upsertOrigin = graph.db.prepare(UPSERT_ORIGIN);\n const upsertEvent = graph.db.prepare(UPSERT_EVENT);\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [sessionId, origin] of Object.entries(resolution.origins)) {\n applied += upsertOrigin.run(originRow(sessionId, origin, resolvedAt)).changes;\n }\n let events = 0;\n for (const e of resolution.externalEvents ?? []) {\n events += upsertEvent.run({ sessionId: e.sessionId, ts: e.ts, kind: e.kind, detail: e.detail ?? null, originSystem: e.originSystem ?? null }).changes;\n }\n return { applied, events };\n })();\n}\n\nfunction originRow(sessionId: string, o: ResolvedOrigin, resolvedAt: string): Record<string, string | number | null> {\n return {\n sessionId, originSystem: o.originSystem, resolvedAt,\n agentId: o.agentId ?? null, agentName: o.agentName ?? null, parentName: o.parentName ?? null, parentSessionId: o.parentSessionId ?? null,\n profile: o.profile ?? null, modelAlias: o.modelAlias ?? null, surface: o.surface ?? null, isolation: o.isolation ?? null,\n depth: o.depth ?? null, originKind: o.originKind ?? null, configDir: o.configDir ?? null, spawnCwd: o.spawnCwd ?? null,\n spawnedAt: o.spawnedAt ?? null, briefChars: o.briefChars ?? null, briefExcerpt: o.briefExcerpt ?? null,\n briefPath: o.briefPath ?? null, launchArgs: o.launchArgs ?? null,\n ...taskLinkColumns(o),\n };\n}\n\n/** `undefined` ids mean a resolver that predates task links; null or empty ids mean it looked and found none. */\nfunction taskLinkColumns(o: ResolvedOrigin): { taskIds: string | null; taskSource: string | null } {\n if (o.taskIds === undefined) return { taskIds: null, taskSource: null };\n if (!o.taskIds?.length) return { taskIds: null, taskSource: NO_TASK_LINK };\n return { taskIds: JSON.stringify(o.taskIds), taskSource: o.taskSource ?? NO_TASK_LINK };\n}\n\nfunction projectOrigins(graph: SessionGraph, resolved: readonly string[]): void {\n 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() as {\n session_id: string; parent_session_id: string; spawned_at: string | null; resolved_at: string;\n }[];\n graph.db.transaction(() => {\n graph.db.prepare(PROJECT_SUBAGENTS).run();\n for (const o of spawned) {\n graph.edges.assert({ sourceRef: sessionRef(o.parent_session_id), relation: RELATIONS.SPAWNED, targetRef: sessionRef(o.session_id), tValid: o.spawned_at ?? o.resolved_at });\n }\n projectTaskLinks(graph, resolved);\n })();\n}\n\n/** A launcher's name is the coordinator's own choice, so it outranks anything read from a brief. */\nconst LINK_CONFIDENCE: Record<string, number> = { name: 1, \"name-over-brief\": 1, \"brief-anchor\": 0.9, \"brief-paragraph\": 0.6 };\nconst LOWEST_CONFIDENCE = 0.6;\n\ninterface TaskLinkRow { session_id: string; task_ids: string | null; task_source: string | null; spawned_at: string | null; resolved_at: string }\n\n/**\n * Assert `session ran task` for every id a spawn record assigned, with a task row so\n * the same pass's `enrichTasks` fills its initiative. Only edges this projection made\n * (`attrs.via = 'origin'`) are expired; a transcript's `ran` edge carries no `via`.\n * Only this pass's rows are read: task links change only when a row is re-resolved.\n */\nfunction projectTaskLinks(graph: SessionGraph, resolved: readonly string[]): void {\n const insertTask = graph.db.prepare(\"INSERT OR IGNORE INTO task (task_ref, task_id) VALUES (?, ?)\");\n const select = graph.db.prepare(\"SELECT session_id, task_ids, task_source, spawned_at, resolved_at FROM session_origin WHERE session_id = ?\");\n for (const sessionId of resolved) {\n const o = select.get(sessionId) as TaskLinkRow | undefined;\n if (!o) continue;\n const taskIds: string[] = o.task_ids ? (JSON.parse(o.task_ids) as string[]) : [];\n const source = sessionRef(o.session_id);\n const attrs = { via: \"origin\", source: o.task_source };\n const confidence = LINK_CONFIDENCE[o.task_source ?? \"\"] ?? LOWEST_CONFIDENCE;\n for (const taskId of taskIds) {\n insertTask.run(taskRef(taskId), taskId);\n graph.edges.assert({ sourceRef: source, relation: RELATIONS.RAN, targetRef: taskRef(taskId), tValid: o.spawned_at ?? o.resolved_at, attrs, confidence });\n }\n expireStaleTaskLinks(graph, source, new Set(taskIds.map(taskRef)));\n }\n}\n\nfunction expireStaleTaskLinks(graph: SessionGraph, source: string, linked: ReadonlySet<string>): void {\n for (const edge of graph.edges.from(source)) {\n if (edge.relation === RELATIONS.RAN && edge.attrs?.via === \"origin\" && !linked.has(edge.targetRef)) {\n graph.edges.expire(source, RELATIONS.RAN, edge.targetRef);\n }\n }\n}\n","import { taskRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * What a product's task store knows and a transcript cannot state: the task's\n * present title, its initiative, and the status it holds right now rather than\n * the one some command was observed to set. Every field is optional; an omitted\n * or null field leaves whatever the transcripts derived in place.\n */\nexport interface ResolvedTask {\n initiative?: string | null;\n title?: string | null;\n status?: string | null;\n /** The store's size estimate for the task, in whatever unit it keeps. */\n estimate?: number | null;\n}\n\n/** Resolutions keyed by bare task id (`AW-23`, not `task:AW-23`). */\nexport type TaskResolution = ReadonlyMap<string, ResolvedTask | null | undefined>;\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn where any product keeps its tasks. Called once per pass with\n * every task id at once, because a real resolver reads a store. Returning an id\n * that no transcript mentioned inserts that task; that is how a task nobody\n * worked on gets into the graph.\n */\nexport type TaskResolver = (taskIds: readonly string[]) => PromiseLike<TaskResolution> | TaskResolution;\n\nexport interface TaskEnrichment {\n /** Task ids handed to the resolver. */\n requested: number;\n /** Task rows the resolver wrote. */\n applied: number;\n /** The resolver threw; rows stand as the transcripts left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_ENRICHMENT: TaskEnrichment = Object.freeze({ requested: 0, applied: 0, failed: false });\n\n/**\n * COALESCE gives the resolver precedence over the transcripts on every field it\n * states, and none on the fields it omits: the store is the system of record for\n * a task's present status, while a transcript only witnesses a command that ran.\n */\nconst UPSERT_TASK = `\n INSERT INTO task (task_ref, task_id, initiative, title, status, estimate) VALUES (@taskRef, @taskId, @initiative, @title, @status, @estimate)\n ON CONFLICT (task_ref) DO UPDATE SET\n initiative = COALESCE(excluded.initiative, initiative),\n title = COALESCE(excluded.title, title),\n status = COALESCE(excluded.status, status),\n estimate = COALESCE(excluded.estimate, estimate)`;\n\nexport function allTaskIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT task_id FROM task\").all() as { task_id: string }[]).map((r) => r.task_id);\n}\n\n/**\n * Enrich task rows from a caller-supplied resolver. Runs outside the delta's\n * transaction because a resolver is async; a crash between the two leaves rows\n * un-enriched, which the next pass corrects. A resolver that throws costs this\n * pass its enrichment and nothing else.\n */\nexport async function enrichTasks(graph: SessionGraph, resolver: TaskResolver | undefined, taskIds: readonly string[]): Promise<TaskEnrichment> {\n const unique = [...new Set(taskIds)];\n if (!resolver) return NO_ENRICHMENT;\n try {\n const resolved = await resolver(unique);\n return { requested: unique.length, applied: write(graph, resolved), failed: false };\n } catch (err) {\n return { requested: unique.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction write(graph: SessionGraph, resolved: TaskResolution): number {\n const upsert = graph.db.prepare(UPSERT_TASK);\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [taskId, fields] of resolved) {\n if (!fields) continue;\n applied += upsert.run({\n taskRef: taskRef(taskId),\n taskId,\n initiative: fields.initiative ?? null,\n title: fields.title ?? null,\n status: fields.status ?? null,\n estimate: fields.estimate ?? null,\n }).changes;\n }\n return applied;\n })();\n}\n","import { EPISODE_TABLE } from \"./audit-schema-v5.js\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * One stretch of a session as some segmentation heuristic cut it. The rule\n * lives above storage (session-analytics); this package only keeps its output.\n * Offsets are byte offsets into the session's transcript.\n */\nexport interface EpisodeRow {\n episodeIndex: number;\n heuristicVersion: number;\n startedAt: string;\n endedAt: string;\n startOffset: number;\n endOffset: number;\n /** `session_start`, `brief`, `channel_followup`, `idle_gap`, `pr_merge`, `spawn_wave_complete`, `task_wrap`, `context_reset`, or a newer heuristic's term. */\n openedBy: string;\n /** The transcript `startOffset`/`endOffset` fall in; a session resumed across transcripts can differ between the two. */\n startTranscriptId?: number | null;\n endTranscriptId?: number | null;\n assignmentOffset?: number | null;\n firstDeliverableOffset?: number | null;\n firstDeliverableSignal?: string | null;\n firstStatusOffset?: number | null;\n}\n\nconst DELETE_EPISODES = `DELETE FROM ${EPISODE_TABLE} WHERE session_id = ? AND heuristic = ?`;\n\nconst INSERT_EPISODE = `\n INSERT INTO ${EPISODE_TABLE} (session_id, episode_index, heuristic, heuristic_version, started_at, ended_at,\n start_offset, end_offset, start_transcript_id, end_transcript_id, opened_by, assignment_offset,\n first_deliverable_offset, first_deliverable_signal, first_status_offset)\n VALUES (@sessionId, @episodeIndex, @heuristic, @heuristicVersion, @startedAt, @endedAt,\n @startOffset, @endOffset, @startTranscriptId, @endTranscriptId, @openedBy, @assignmentOffset,\n @firstDeliverableOffset, @firstDeliverableSignal, @firstStatusOffset)`;\n\n/**\n * The only writer of `episode`. Replaces one heuristic's segmentation of one\n * session atomically and leaves every other heuristic's rows alone, so two rule\n * sets can coexist while one is compared against the other. Returns rows written.\n */\nexport function replaceEpisodes(graph: SessionGraph, sessionId: string, heuristic: string, rows: readonly EpisodeRow[]): number {\n const remove = graph.db.prepare(DELETE_EPISODES);\n const insert = graph.db.prepare(INSERT_EPISODE);\n return graph.db.transaction(() => {\n remove.run(sessionId, heuristic);\n for (const row of rows) insert.run(episodeParams(sessionId, heuristic, row));\n return rows.length;\n })();\n}\n\nfunction episodeParams(sessionId: string, heuristic: string, row: EpisodeRow): Record<string, string | number | null> {\n return {\n sessionId, heuristic,\n episodeIndex: row.episodeIndex, heuristicVersion: row.heuristicVersion,\n startedAt: row.startedAt, endedAt: row.endedAt, startOffset: row.startOffset, endOffset: row.endOffset, openedBy: row.openedBy,\n startTranscriptId: row.startTranscriptId ?? null, endTranscriptId: row.endTranscriptId ?? null,\n assignmentOffset: row.assignmentOffset ?? null, firstDeliverableOffset: row.firstDeliverableOffset ?? null,\n firstDeliverableSignal: row.firstDeliverableSignal ?? null, firstStatusOffset: row.firstStatusOffset ?? null,\n };\n}\n","import type { SessionGraph } from \"./graph.js\";\n\n/** USD per million tokens for one model prefix from one date. Shaped so session-analytics' `PRICE_TABLE` passes straight through. */\nexport interface PriceInput {\n /** Matched against `request.model` by longest prefix in `request_cost`. */\n modelPrefix: string;\n /** ISO date or timestamp; compared as text against `request.ts`. */\n effectiveFrom: string;\n input: number;\n cacheRead: number;\n cacheWrite5m: number;\n cacheWrite1h: number;\n output: number;\n}\n\nexport interface SyncPricesOptions {\n /** Stamped on every row so a report can name the table it priced with. */\n tableVersion: number;\n source?: string;\n}\n\n/** Replace every `price` row with `rows` in one transaction, so `request_cost` never reads a half-written table. */\nexport function syncPrices(graph: SessionGraph, rows: readonly PriceInput[], options: SyncPricesOptions): number {\n const insert = graph.db.prepare(\n `INSERT INTO price (model, effective_from, table_version, input_usd_mtok, cache_read_usd_mtok,\n cache_write_5m_usd_mtok, cache_write_1h_usd_mtok, output_usd_mtok, source)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n const source = options.source ?? null;\n graph.db.transaction(() => {\n graph.db.exec(\"DELETE FROM price\");\n for (const r of rows) {\n insert.run(r.modelPrefix, r.effectiveFrom, options.tableVersion, r.input, r.cacheRead, r.cacheWrite5m, r.cacheWrite1h, r.output, source);\n }\n })();\n return rows.length;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { stat } from \"node:fs/promises\";\nimport { contentHash } from \"@titan-design/locator\";\nimport { conversationRef } from \"@titan-design/agent-protocol\";\nimport { SPAN_TEXT_CAP, readCodexObservations, type CodexReadResult, type NormalizedSessionObservation, type SessionSourceDescriptor, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { insertObservation, observationText } from \"./normalized-project.js\";\n\nexport interface NormalizedIndexResult { status: \"indexed\" | \"unchanged\" | \"missing\" | \"quarantined\"; conversationRef: string; observations: number; reason?: string }\n\n/** Replay changed sources into temporary staging; swap rows and watermark atomically. */\nexport async function indexCodexSource(graph: SessionGraph, source: SessionSourceDescriptor): Promise<NormalizedIndexResult> {\n const ref = conversationRef(source.conversation);\n const base = { conversationRef: ref, observations: 0 };\n const row = graph.transcripts.ensure(source.sourceId);\n let info;\n try { info = await stat(source.path); } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n graph.transcripts.markStatus(source.sourceId, \"missing\", \"source unavailable\");\n return { ...base, status: \"missing\" };\n }\n // Always replay: the decoder verifies bytes, detects projections that arrived later,\n // and avoids trusting mtime alone. Equality of the resulting prefix is the fast no-op.\n const stage = `stage_${randomUUID().replaceAll(\"-\", \"\")}`;\n graph.db.exec(`CREATE TEMP TABLE ${stage}(seq INTEGER PRIMARY KEY, observation TEXT NOT NULL)`);\n let result: CodexReadResult | undefined;\n try {\n const fingerprint = await contentHash(source.path);\n const insert = graph.db.prepare(`INSERT INTO ${stage}(observation) VALUES (?)`);\n for await (const observation of readCodexObservations(source, {}, done => { result = done; })) insert.run(JSON.stringify(observation));\n if (!result) throw new Error(\"decoder completed without a resume boundary\");\n const boundary = (result as CodexReadResult).resumeBoundary;\n const prior = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n if (prior && row.contentHash === fingerprint && row.prefixHash === boundary.prefixHash && row.lastOffset === boundary.byteOffset) {\n graph.db.prepare(\"UPDATE normalized_source SET descriptor = ? WHERE transcript_id = ?\").run(JSON.stringify(source), row.sourceId);\n graph.transcripts.markStatus(source.sourceId, \"ok\", null);\n return { ...base, status: \"unchanged\" };\n }\n const count = graph.db.transaction(() => {\n purgeNormalizedSource(graph, row.sourceId);\n graph.db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id) VALUES (?,?,?,?)\").run(ref, source.harness, source.namespace, source.conversation.nativeId);\n graph.db.prepare(\"INSERT OR REPLACE INTO normalized_source VALUES (?,?,?,?)\").run(row.sourceId, source.sourceId, ref, JSON.stringify(source));\n const n = applyStaged(graph, stage, row.sourceId, ref);\n graph.transcripts.advance(source.sourceId, { lastOffset: boundary.byteOffset, prefixHash: boundary.prefixHash, fileSize: info.size, fileMtime: info.mtime.toISOString(), contentHash: fingerprint });\n return n;\n })();\n return { ...base, status: \"indexed\", observations: count };\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n graph.transcripts.markStatus(source.sourceId, \"quarantined\", reason);\n return { ...base, status: \"quarantined\", reason };\n } finally { graph.db.exec(`DROP TABLE ${stage}`); }\n}\n\nfunction applyStaged(graph: SessionGraph, stage: string, transcriptId: number, ref: string): number {\n const spans = `${stage}_spans`;\n graph.db.exec(`CREATE TEMP TABLE ${spans}(field TEXT, offset INTEGER, length INTEGER, text TEXT, locators TEXT, PRIMARY KEY(field,offset))`);\n let count = 0;\n try {\n for (const row of stagedRows<{ observation: string }>(graph, stage)) {\n const o = JSON.parse(row.observation) as NormalizedSessionObservation;\n insertObservation(graph, transcriptId, o);\n for (const p of observationText(o)) stageText(graph, spans, p.field, p.text, p.locator);\n count++;\n }\n for (const s of stagedRows<{ field: string; offset: number; length: number; text: string; locators: string }>(graph, spans)) {\n const id = graph.spans.index({ ownerRef: ref, sourceId: transcriptId, field: s.field, byteOffset: s.offset, byteLength: s.length }, s.text);\n graph.db.prepare(\"INSERT INTO normalized_span VALUES (?,?)\").run(id, s.locators);\n }\n } finally { graph.db.exec(`DROP TABLE ${spans}`); }\n return count;\n}\n\nfunction stageText(graph: SessionGraph, table: string, field: string, text: string, locator: SourceTextLocator): void {\n const line = locator.evidence.line;\n const prior = graph.db.prepare(`SELECT text,locators FROM ${table} WHERE field = ? AND offset = ?`).get(field, line.byteOffset) as { text: string; locators: string } | undefined;\n const locators = prior ? JSON.parse(prior.locators) as unknown[] : [];\n locators.push(locator);\n graph.db.prepare(`INSERT OR REPLACE INTO ${table} VALUES (?,?,?,?,?)`).run(field, line.byteOffset, line.byteLength, (prior ? `${prior.text}\\n${text}` : text).slice(0, SPAN_TEXT_CAP), JSON.stringify(locators));\n}\n\nfunction purgeNormalizedSource(graph: SessionGraph, transcriptId: number): void {\n graph.db.prepare(\"DELETE FROM normalized_span WHERE span_id IN (SELECT span_id FROM search_span WHERE source_id = ?)\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM search_span WHERE source_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM normalized_event WHERE transcript_id = ?\").run(transcriptId);\n}\n\n\n/** Finish each SELECT before writing; SQLite forbids writes during an active iterator. */\nfunction* stagedRows<T>(graph: SessionGraph, table: string): Generator<T> {\n const select = graph.db.prepare(`SELECT rowid AS cursor, * FROM ${table} WHERE rowid > ? ORDER BY rowid LIMIT 128`);\n let cursor = 0;\n for (;;) {\n const rows = select.all(cursor) as (T & { cursor: number })[];\n if (!rows.length) return;\n for (const row of rows) { cursor = row.cursor; yield row; }\n }\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport { normalizedSearchText, SPAN_TEXT_CAP, scopedConversationItemRef, type NormalizedSessionObservation as Observation, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/** Persist structural evidence only; prose remains in the source and transient FTS input. */\nexport function insertObservation(graph: SessionGraph, transcriptId: number, o: Observation): void {\n graph.db.prepare(`INSERT INTO normalized_event VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n transcriptId, o.evidence.line.byteOffset, o.evidence.subrecord.index, o.evidence.line.byteLength,\n conversationRef(o.conversation), o.kind, o.timestamp,\n \"turn\" in o && o.turn ? scopedConversationItemRef(o.turn) : null,\n \"call\" in o ? scopedConversationItemRef(o.call) : null,\n \"item\" in o && o.item ? scopedConversationItemRef(o.item) : null,\n o.kind === \"native_turn\" ? o.phase : null,\n o.kind === \"tool_result\" && o.isError !== null ? Number(o.isError) : null,\n o.kind === \"usage\" ? JSON.stringify(o.measurement) : null,\n o.kind === \"metadata\" ? JSON.stringify(o.entries.filter(e => [\"cwd\", \"model\", \"cli_version\", \"git_branch\", \"title\"].includes(e.name))) : null,\n o.historyOrigin ? conversationRef(o.historyOrigin) : null,\n o.kind === \"lineage\" ? conversationRef(o.relatedConversation) : null,\n o.kind === \"lineage\" ? o.relationship : null,\n );\n}\n\nexport interface ObservationText { field: string; text: string; locator: SourceTextLocator }\nexport function observationText(o: Observation): ObservationText[] {\n if (o.kind === \"message\") return o.content.map(p => ({ field: o.role === \"user\" ? \"prompt\" : \"assistant_response\", text: p.text.slice(0, SPAN_TEXT_CAP), locator: p.locator }));\n if (o.kind === \"tool_call\" && o.inputLocator) return [{ field: \"tool_input\", text: normalizedSearchText(o.input), locator: o.inputLocator }];\n if (o.kind === \"tool_result\" && o.outputLocator) return [{ field: \"tool_result\", text: normalizedSearchText(o.output), locator: o.outputLocator }];\n return [];\n}\n","import { contentHash, prefixHash } from \"@titan-design/locator\";\nimport type { UsageMeasurement } from \"@titan-design/agent-protocol\";\nimport { readSessionSourceText, readSessionText, SessionUsageAccumulator, type SessionUsageSummary, type SourceTextLocator, type SessionSourceDescriptor, type SpanField } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\nexport interface IndexedSpan { sourceId: number; byteOffset: number; byteLength: number; field: string; spanId?: number }\n/** One resolver for search and error clustering; never display a raw JSON line. */\nexport async function readIndexedText(graph: SessionGraph, span: IndexedSpan): Promise<string | null> {\n const row = graph.db.prepare(`SELECT n.locators FROM normalized_span n JOIN search_span s USING(span_id)\n WHERE s.source_id = ? AND s.byte_offset = ? AND s.field = ?`).get(span.sourceId, span.byteOffset, span.field) as { locators: string } | undefined;\n if (row) {\n const source = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId) as { descriptor: string } | undefined;\n if (!source) return null;\n const descriptor = JSON.parse(source.descriptor) as SessionSourceDescriptor;\n const watermark = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n try { if (!watermark?.prefixHash || await prefixHash(descriptor.path, watermark.lastOffset) !== watermark.prefixHash) return null; }\n catch { return null; }\n if (span.byteOffset + span.byteLength + 1 > watermark.lastOffset) {\n try { if (!watermark.contentHash || await contentHash(descriptor.path) !== watermark.contentHash) return null; } catch { return null; }\n }\n const texts = await Promise.all((JSON.parse(row.locators) as SourceTextLocator[]).map(locator => readSessionSourceText(locator, { sources: [JSON.parse(source.descriptor) as SessionSourceDescriptor] })));\n return texts.some(text => text === null) ? null : texts.join(\"\\n\");\n }\n const normalized = graph.db.prepare(\"SELECT 1 FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId);\n if (normalized) return null;\n const legacy = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n return legacy ? readSessionText({ path: legacy.sourceKey, byteOffset: span.byteOffset, byteLength: span.byteLength, field: span.field as SpanField }) : null;\n}\n\nexport interface ConversationSummary {\n sessionId: string; harness: string; nativeId: string; namespace: string;\n title: string | null; startedAt: string | null; endedAt: string | null;\n cwd: string | null; gitBranch: string | null; turnCount: number; commitCount: number | null; pushCount: number | null;\n}\nexport function normalizedSessions(graph: SessionGraph, options: { ref?: string; limit?: number; since?: string } = {}): ConversationSummary[] {\n const rows = graph.db.prepare(`SELECT c.ref,c.harness,c.native_id,c.namespace,MIN(e.ts) AS started,\n COUNT(DISTINCT CASE WHEN e.kind = 'native_turn' THEN e.turn_ref END) AS turns\n FROM conversation c JOIN (SELECT DISTINCT conversation_ref FROM normalized_source) s ON s.conversation_ref = c.ref\n LEFT JOIN normalized_event e ON e.conversation_ref = c.ref AND e.history_origin IS NULL\n WHERE c.legacy_session_id IS NULL AND (? IS NULL OR c.ref = ?)\n GROUP BY c.ref HAVING (? IS NULL OR MIN(e.ts) >= ?)\n ORDER BY started DESC LIMIT ?`).all(options.ref ?? null,options.ref ?? null,options.since ?? null,options.since ?? null,options.limit ?? -1) as\n { ref: string; harness: string; native_id: string; namespace: string; started: string | null; turns: number }[];\n const meta = graph.db.prepare(`SELECT json_extract(j.value,'$.value') AS value\n FROM normalized_event e,json_each(e.metadata) j WHERE e.conversation_ref = ?\n AND e.history_origin IS NULL AND json_extract(j.value,'$.name') = ? ORDER BY e.ts DESC,e.byte_offset DESC,e.subrecord_index DESC LIMIT 1`);\n return rows.map(c => {\n const text = (key: string) => { const value = (meta.get(c.ref,key) as { value: unknown } | undefined)?.value; return typeof value === \"string\" ? value : null; };\n return { sessionId:c.ref,harness:c.harness,nativeId:c.native_id,namespace:c.namespace,title:text(\"title\"),startedAt:c.started,\n endedAt:null,cwd:text(\"cwd\"),gitBranch:text(\"git_branch\"),turnCount:c.turns,commitCount:null,pushCount:null };\n });\n}\n\nexport type NormalizedUsageSummary = SessionUsageSummary;\n/** Shared storage-free usage policy keeps graph and direct readers consistent. */\nexport function normalizedUsage(graph: SessionGraph, ref: string): NormalizedUsageSummary[] {\n // Snapshot epochs are local to a physical source; never add copies from different files.\n // Pick the source with the latest native timestamp (then fullest coverage, then stable ID).\n const preferred = graph.db.prepare(`SELECT transcript_id FROM normalized_event\n WHERE conversation_ref = ? AND usage IS NOT NULL AND history_origin IS NULL\n GROUP BY transcript_id ORDER BY MAX(ts) DESC, COUNT(*) DESC, transcript_id ASC LIMIT 1`).get(ref) as { transcript_id: number } | undefined;\n const rows = graph.db.prepare(`SELECT usage FROM normalized_event WHERE conversation_ref = ?\n AND usage IS NOT NULL AND history_origin IS NULL\n AND (json_extract(usage,'$.kind') = 'delta' OR transcript_id = ?)\n ORDER BY byte_offset,subrecord_index`);\n const accumulator = new SessionUsageAccumulator();\n for (const row of rows.iterate(ref, preferred?.transcript_id ?? -1) as Iterable<{ usage: string }>) {\n accumulator.add(JSON.parse(row.usage) as UsageMeasurement);\n }\n return accumulator.summaries();\n}\n"],"mappings":"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","names":["sessionRef","sessionRef","TranscriptParseError","extractTranscript","RELATIONS","RELATIONS","RELATIONS","sessionRef","taskRef","write","extractTranscript","stat","TranscriptParseError","isReviewerProfile","contentHash","conversationRef","SPAN_TEXT_CAP","conversationRef","conversationRef","contentHash","SPAN_TEXT_CAP","contentHash"]}
|
|
1
|
+
{"version":3,"sources":["../src/graph.ts","../src/audit-schema.ts","../src/audit-schema-v5.ts","../src/audit-schema-v6.ts","../src/audit-schema-v7.ts","../src/audit-schema-v8.ts","../src/normalized-schema.ts","../src/schema.ts","../src/audit-apply.ts","../src/facet.ts","../src/injected-text.ts","../src/apply.ts","../src/purge.ts","../src/audit-rollup.ts","../src/rollup.ts","../src/refresh.ts","../src/review-rounds.ts","../src/outcomes.ts","../src/origin.ts","../src/tasks.ts","../src/episodes.ts","../src/prices.ts","../src/normalized-index.ts","../src/normalized-project.ts","../src/normalized-query.ts"],"sourcesContent":["import { EdgeTable, SpanFtsTables, WatermarkTable, openDatabase, runMigrations, type Db } from \"@titan-design/store-sqlite\";\nimport { DERIVED_TABLES, KIT, MIGRATIONS } from \"./schema.js\";\n\n/** One open session graph: the connection plus the kit helpers bound to its tables. */\nexport interface SessionGraph {\n db: Db;\n transcripts: WatermarkTable;\n edges: EdgeTable;\n spans: SpanFtsTables;\n}\n\nexport interface OpenSessionGraphOptions {\n /**\n * The highest migration version the caller owns, checked before any migration runs.\n * A product layering its own tables on this graph passes its own top version, not this\n * package's: `MIGRATIONS` here is one band of a shared database, never the top of it.\n */\n schemaVersion?: number;\n}\n\nexport function openSessionGraph(dbPath: string, options: OpenSessionGraphOptions = {}): SessionGraph {\n const db = openDatabase(dbPath, { schemaVersion: options.schemaVersion });\n runMigrations(db, MIGRATIONS);\n return {\n db,\n transcripts: new WatermarkTable(db, { name: KIT.watermark }),\n edges: new EdgeTable(db, { name: KIT.edge }),\n spans: new SpanFtsTables(db, { name: KIT.spanFts }),\n };\n}\n\n/**\n * Clear every derived row and rewind all watermarks so the next refresh\n * rebuilds from byte 0. Rewinding alone is not enough: the accumulating\n * upserts would add a second copy of every count.\n */\nexport function resetIndex(graph: SessionGraph): void {\n graph.db.transaction(() => {\n for (const table of DERIVED_TABLES) graph.db.exec(`DELETE FROM \"${table}\"`);\n graph.spans.clearIndex();\n for (const row of graph.transcripts.list()) graph.transcripts.rewind(row.sourceKey);\n })();\n}\n\nexport function allSessionIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT session_id FROM session\").all() as { session_id: string }[]).map((r) => r.session_id);\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const AUDIT_MIGRATION_NAME = \"audit tables\";\n\n/** Tables written per transcript by `applyAudit` and dropped by `purgeTranscript`. */\nexport const AUDIT_TABLES = [\n \"request\",\n \"tool_call\",\n \"inbound\",\n \"context_block\",\n \"compaction\",\n \"queue_op\",\n \"session_signal\",\n \"cost_state_observation\",\n] as const;\n\n/** Per-transcript read state for each audit facet; cleared with the audit rows so a re-read starts clean. */\nexport const FACET_TABLE = \"transcript_facet\";\n\nexport const AUDIT_DDL = `\n CREATE TABLE IF NOT EXISTS request (\n transcript_id INTEGER NOT NULL,\n request_id TEXT NOT NULL,\n byte_offset INTEGER NOT NULL, -- first line seen for this request\n session_id TEXT NOT NULL,\n message_id TEXT,\n ts TEXT NOT NULL,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_5m INTEGER NOT NULL DEFAULT 0,\n cache_creation_1h INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n thinking_tokens INTEGER NOT NULL DEFAULT 0,\n context_tokens INTEGER NOT NULL DEFAULT 0, -- input + cache_read + cache_creation\n service_tier TEXT,\n is_sidechain INTEGER NOT NULL DEFAULT 0,\n -- rollup-owned, recomputed for touched sessions:\n seq_in_session INTEGER,\n gap_ms INTEGER, -- since previous request in the session\n ctx_delta INTEGER, -- context_tokens - (prev.context_tokens + prev.output_tokens)\n wake_cause TEXT,\n wake_delivery TEXT,\n wake_detail TEXT, -- tool name, channel sender, or NULL\n wake_tool_family TEXT,\n wake_mcp_server TEXT,\n wake_offset INTEGER, -- byte_offset of the inbound row\n PRIMARY KEY (transcript_id, request_id)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_request_session_ts ON request(session_id, ts);\n CREATE INDEX IF NOT EXISTS idx_request_ts ON request(ts);\n CREATE INDEX IF NOT EXISTS idx_request_id ON request(request_id);\n\n CREATE TABLE IF NOT EXISTS tool_call (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, tool_use_id TEXT NOT NULL,\n name TEXT NOT NULL, family TEXT NOT NULL, mcp_server TEXT, input_chars INTEGER NOT NULL,\n PRIMARY KEY (transcript_id, byte_offset, block_index)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_tool_call_use ON tool_call(tool_use_id);\n\n CREATE TABLE IF NOT EXISTS inbound (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL,\n cause TEXT NOT NULL, delivery TEXT NOT NULL, detail TEXT,\n origin_server TEXT, from_name TEXT, msg_id TEXT, tool_use_id TEXT,\n is_error INTEGER NOT NULL DEFAULT 0, content_hash TEXT NOT NULL, chars INTEGER NOT NULL,\n queued_ms INTEGER, -- rollup-owned\n PRIMARY KEY (transcript_id, byte_offset, block_index)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_inbound_session ON inbound(session_id, byte_offset);\n\n CREATE TABLE IF NOT EXISTS context_block (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, source TEXT NOT NULL,\n tool_use_id TEXT, attachment_type TEXT, chars INTEGER NOT NULL, is_media INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (transcript_id, byte_offset, block_index)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_context_block_session ON context_block(session_id, source);\n\n CREATE TABLE IF NOT EXISTS compaction (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, trigger TEXT,\n pre_tokens INTEGER, post_tokens INTEGER, dropped_tokens INTEGER, duration_ms INTEGER,\n mid_loop INTEGER, -- rollup-owned: previous inbound was a tool_result\n PRIMARY KEY (transcript_id, byte_offset)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS queue_op (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, operation TEXT NOT NULL,\n content_hash TEXT, origin_server TEXT,\n PRIMARY KEY (transcript_id, byte_offset)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_queue_op_hash ON queue_op(session_id, content_hash);\n\n CREATE TABLE IF NOT EXISTS session_signal (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL, block_index INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT NOT NULL, signal TEXT NOT NULL, detail TEXT, tool_use_id TEXT,\n -- one Bash block can emit up to three signals (commit, push, pr_create); the signal name disambiguates them\n PRIMARY KEY (transcript_id, byte_offset, block_index, signal)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_signal_session ON session_signal(session_id, ts);\n\n CREATE TABLE IF NOT EXISTS cost_state_observation (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n session_id TEXT NOT NULL, ts TEXT, total_cost_usd REAL NOT NULL, model_usage TEXT NOT NULL,\n PRIMARY KEY (transcript_id, byte_offset)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS transcript_facet (\n transcript_id INTEGER NOT NULL, facet TEXT NOT NULL,\n version INTEGER NOT NULL, indexed_to INTEGER NOT NULL,\n PRIMARY KEY (transcript_id, facet)\n ) WITHOUT ROWID;\n`;\n\n/** `[table, column, type]`; `turn.wake_cause` is rollup-owned. */\nexport const AUDIT_COLUMNS = [\n [\"session\", \"account\", \"TEXT\"],\n [\"turn\", \"wake_cause\", \"TEXT\"],\n [\"task\", \"estimate\", \"REAL\"],\n [\"pr\", \"review_rounds\", \"INTEGER\"],\n [\"pr\", \"closed_at\", \"TEXT\"],\n [\"pr\", \"outcome_checked_at\", \"TEXT\"],\n] as const;\n\n/** The version 4 body. Safe to run twice: a database can reach version 4 after a partial manual repair. */\nexport function applyAuditSchema(db: Db): void {\n db.exec(AUDIT_DDL);\n for (const [table, column, type] of AUDIT_COLUMNS) addColumnIfMissing(db, table, column, type);\n}\n\n/** SQLite's `ADD COLUMN` has no `IF NOT EXISTS`, and a repeat throws \"duplicate column name\". */\nexport function addColumnIfMissing(db: Db, table: string, column: string, type: string): void {\n const present = db.prepare(\"SELECT 1 FROM pragma_table_info(?) WHERE name = ?\").get(table, column);\n if (!present) db.exec(`ALTER TABLE \"${table}\" ADD COLUMN \"${column}\" ${type}`);\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const ORIGIN_MIGRATION_NAME = \"origin, episodes, prices\";\n\n/** Re-derivable from their own sources rather than from transcripts, so `resetIndex` clears them and the next pass refills them. */\nexport const ORIGIN_TABLES = [\"session_origin\", \"session_external_event\"] as const;\n\n/** Written from transcript offsets by `replaceEpisodes`; a rewritten transcript invalidates them, so purge drops them by session. */\nexport const EPISODE_TABLE = \"episode\";\n\nexport const ORIGIN_DDL = `\n CREATE TABLE IF NOT EXISTS session_origin ( -- not derived from transcripts\n session_id TEXT PRIMARY KEY,\n origin_system TEXT NOT NULL, -- 'agent-chat'\n agent_id TEXT, agent_name TEXT, parent_name TEXT, parent_session_id TEXT,\n profile TEXT, model_alias TEXT, surface TEXT, isolation TEXT, depth INTEGER,\n origin_kind TEXT, -- spawned | adopted | inherited | human\n config_dir TEXT, spawn_cwd TEXT, spawned_at TEXT,\n brief_chars INTEGER, brief_excerpt TEXT, brief_path TEXT, launch_args TEXT,\n resolved_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_origin_parent ON session_origin(parent_session_id);\n\n CREATE TABLE IF NOT EXISTS session_external_event ( -- teleport, handoff, retire, exit\n session_id TEXT NOT NULL, ts TEXT NOT NULL, origin_system TEXT NOT NULL,\n kind TEXT NOT NULL, detail TEXT,\n PRIMARY KEY (session_id, ts, kind)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS episode ( -- provisional, see section 9\n session_id TEXT NOT NULL, episode_index INTEGER NOT NULL,\n heuristic TEXT NOT NULL, heuristic_version INTEGER NOT NULL,\n started_at TEXT NOT NULL, ended_at TEXT NOT NULL,\n start_offset INTEGER NOT NULL, end_offset INTEGER NOT NULL,\n opened_by TEXT NOT NULL, -- brief | channel_followup | idle_gap | compaction\n assignment_offset INTEGER, first_deliverable_offset INTEGER, first_deliverable_signal TEXT,\n first_status_offset INTEGER,\n PRIMARY KEY (session_id, heuristic, episode_index)\n ) WITHOUT ROWID;\n\n CREATE TABLE IF NOT EXISTS price (\n model TEXT NOT NULL, effective_from TEXT NOT NULL, table_version INTEGER NOT NULL,\n input_usd_mtok REAL NOT NULL, cache_read_usd_mtok REAL NOT NULL,\n cache_write_5m_usd_mtok REAL NOT NULL, cache_write_1h_usd_mtok REAL NOT NULL,\n output_usd_mtok REAL NOT NULL, source TEXT,\n PRIMARY KEY (model, effective_from)\n ) WITHOUT ROWID;\n`;\n\nconst REQUEST_COLUMNS = `transcript_id, request_id, byte_offset, session_id, message_id, ts, model,\n input_tokens, cache_read_tokens, cache_creation_tokens, cache_creation_5m, cache_creation_1h,\n output_tokens, thinking_tokens, context_tokens, service_tier, is_sidechain,\n seq_in_session, gap_ms, ctx_delta, wake_cause, wake_delivery, wake_detail,\n wake_tool_family, wake_mcp_server, wake_offset`;\n\n/** Fan-out copies of one request across transcripts collapse to the earliest `(ts, transcript_id)`. Every cost query reads this, never `request`. */\nconst REQUEST_DEDUP = `\n CREATE VIEW IF NOT EXISTS request_dedup AS\n SELECT ${REQUEST_COLUMNS} FROM (\n SELECT r.*, ROW_NUMBER() OVER (PARTITION BY r.request_id ORDER BY r.ts, r.transcript_id) AS copy_rank\n FROM request r\n ) WHERE copy_rank = 1;\n`;\n\n/**\n * Longest model prefix wins, so `claude-opus-5[1m]` prices as `claude-opus-5`; then the latest\n * `effective_from` at or before the request. An unmatched model reads `priced = 0` and costs 0,\n * never a guessed default. A split-less cache write (`5m + 1h = 0`) prices at the 5m rate.\n */\nconst REQUEST_COST = `\n CREATE VIEW IF NOT EXISTS request_cost AS\n WITH candidate AS (\n SELECT d.request_id, p.model AS price_model, p.effective_from AS price_effective_from,\n 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,\n ROW_NUMBER() OVER (PARTITION BY d.request_id ORDER BY length(p.model) DESC, p.effective_from DESC) AS match_rank\n FROM request_dedup d\n JOIN price p ON substr(d.model, 1, length(p.model)) = p.model AND p.effective_from <= d.ts\n ), component AS (\n SELECT d.*, c.price_model, c.price_effective_from, c.price_model IS NOT NULL AS priced,\n COALESCE(d.input_tokens * c.input_usd_mtok, 0) / 1e6 AS input_cost_usd,\n COALESCE(d.cache_read_tokens * c.cache_read_usd_mtok, 0) / 1e6 AS cache_read_cost_usd,\n COALESCE(CASE WHEN d.cache_creation_5m + d.cache_creation_1h = 0 THEN d.cache_creation_tokens ELSE d.cache_creation_5m END\n * c.cache_write_5m_usd_mtok, 0) / 1e6 AS cache_write_5m_cost_usd,\n COALESCE(d.cache_creation_1h * c.cache_write_1h_usd_mtok, 0) / 1e6 AS cache_write_1h_cost_usd,\n COALESCE(d.output_tokens * c.output_usd_mtok, 0) / 1e6 AS output_cost_usd\n FROM request_dedup d\n LEFT JOIN candidate c ON c.request_id = d.request_id AND c.match_rank = 1\n )\n SELECT *,\n input_cost_usd + cache_read_cost_usd + cache_write_5m_cost_usd + cache_write_1h_cost_usd + output_cost_usd AS cost_usd,\n (cache_read_tokens < 0.2 * context_tokens AND cache_creation_tokens >= 20000) AS is_cold,\n CASE WHEN context_tokens < 50000 THEN '<50k' WHEN context_tokens < 100000 THEN '50-100k'\n WHEN context_tokens < 200000 THEN '100-200k' ELSE '200k+' END AS context_band,\n CASE WHEN gap_ms IS NULL THEN NULL WHEN gap_ms < 300000 THEN '<5m'\n WHEN gap_ms < 3600000 THEN '5-60m' ELSE '>60m' END AS gap_band\n FROM component;\n`;\n\nconst ASSISTANT_OUTPUT = \"'assistant_text', 'assistant_thinking', 'assistant_tool_input'\";\n\n/**\n * A block belongs to the first request after it in its transcript; a block on the request's own\n * line is that request's output and so feeds the next one. Blocks whose request is a fan-out copy\n * drop out with it, keeping estimates aligned with `request_dedup`. Assistant output is already\n * subtracted from `ctx_delta`, so those rows stay visible with a NULL `est_tokens` and no share.\n */\nconst CONTEXT_CONTRIBUTION = `\n CREATE VIEW IF NOT EXISTS context_contribution AS\n WITH stream AS (\n SELECT transcript_id, byte_offset, 0 AS lane, block_index, NULL AS request_id FROM context_block\n UNION ALL\n SELECT transcript_id, byte_offset, -1 AS lane, 0, request_id FROM request\n ), ordered AS (\n SELECT *, COUNT(request_id) OVER (PARTITION BY transcript_id ORDER BY byte_offset, lane, block_index ROWS UNBOUNDED PRECEDING) AS requests_before\n FROM stream\n ), owner AS (\n SELECT b.transcript_id, b.byte_offset, b.block_index, r.request_id\n 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\n WHERE b.lane = 0\n ), tool AS (\n SELECT tool_use_id, name, family, mcp_server,\n ROW_NUMBER() OVER (PARTITION BY tool_use_id ORDER BY transcript_id, byte_offset, block_index) AS copy_rank\n FROM tool_call\n )\n SELECT cb.transcript_id, cb.byte_offset, cb.block_index, cb.session_id, cb.ts, cb.source,\n cb.tool_use_id, cb.attachment_type, cb.chars, cb.is_media, o.request_id, d.ctx_delta,\n CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN NULL\n ELSE d.ctx_delta * cb.chars * 1.0 / NULLIF(SUM(CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN 0 ELSE cb.chars END)\n OVER (PARTITION BY o.transcript_id, o.request_id), 0) END AS est_tokens,\n t.name AS tool_name, t.family AS tool_family, t.mcp_server\n FROM context_block cb\n JOIN owner o USING (transcript_id, byte_offset, block_index)\n JOIN request_dedup d ON d.transcript_id = o.transcript_id AND d.request_id = o.request_id\n LEFT JOIN tool t ON t.tool_use_id = cb.tool_use_id AND t.copy_rank = 1;\n`;\n\nexport const ORIGIN_VIEWS = [REQUEST_DEDUP, REQUEST_COST, CONTEXT_CONTRIBUTION] as const;\n\n/** The version 5 body. Every statement is `IF NOT EXISTS`, so a repeat run is a no-op. */\nexport function applyOriginSchema(db: Db): void {\n db.exec(ORIGIN_DDL);\n for (const view of ORIGIN_VIEWS) db.exec(view);\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\nimport { addColumnIfMissing } from \"./audit-schema.js\";\nimport { EPISODE_TABLE } from \"./audit-schema-v5.js\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const EPISODE_TRANSCRIPT_MIGRATION_NAME = \"episode transcript ids\";\n\n/**\n * Nullable so existing episode rows survive; a rewritten transcript's episodes are\n * dropped and rebuilt by `replaceEpisodes` anyway. `transcript_id` is INTEGER\n * everywhere else in this schema (`request`, `inbound`, `session_signal`, ...), so\n * these match rather than introducing a TEXT id for the same concept.\n */\nexport function applyEpisodeTranscriptSchema(db: Db): void {\n addColumnIfMissing(db, EPISODE_TABLE, \"start_transcript_id\", \"INTEGER\");\n addColumnIfMissing(db, EPISODE_TABLE, \"end_transcript_id\", \"INTEGER\");\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\nimport { addColumnIfMissing } from \"./audit-schema.js\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const ORIGIN_TASK_LINK_MIGRATION_NAME = \"origin task link\";\n\n/**\n * `task_ids` is a JSON array, primary id first. Both start null; a null `task_source`\n * re-offers the row to the resolver until a task-aware one answers, which is the backfill.\n */\nexport function applyOriginTaskLinkSchema(db: Db): void {\n addColumnIfMissing(db, \"session_origin\", \"task_ids\", \"TEXT\");\n addColumnIfMissing(db, \"session_origin\", \"task_source\", \"TEXT\");\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\nimport { addColumnIfMissing } from \"./audit-schema.js\";\n\n/** Recorded in `_migration`; store-sqlite refuses a database whose applied name differs, so never rename it. */\nexport const REVIEW_VERDICT_MIGRATION_NAME = \"review verdicts\";\n\nexport const REVIEW_TABLE = \"pr_review\";\n\n/**\n * One row per review verdict, from either surface. Only parsed fields are kept, never message text.\n * `source_key` is `chat:<tool_use_id>:<n>` or `gh:<pr_ref>:<submitted_at>`; `pr_ref` is resolved later.\n */\nexport const REVIEW_DDL = `\n CREATE TABLE IF NOT EXISTS ${REVIEW_TABLE} (\n source_key TEXT PRIMARY KEY,\n surface TEXT NOT NULL,\n verdict TEXT NOT NULL,\n ts TEXT NOT NULL,\n session_id TEXT,\n transcript_id INTEGER,\n repo TEXT,\n repo_hint TEXT,\n cwd_repo TEXT,\n number INTEGER NOT NULL,\n pr_ref TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_${REVIEW_TABLE}_pr_ref ON ${REVIEW_TABLE}(pr_ref);\n CREATE INDEX IF NOT EXISTS idx_${REVIEW_TABLE}_transcript ON ${REVIEW_TABLE}(transcript_id);\n`;\n\nconst PR_COLUMNS = [\n [\"review_rounds_gh\", \"INTEGER\"],\n [\"review_rounds_chat\", \"INTEGER\"],\n [\"commit_times\", \"TEXT\"],\n] as const;\n\n/**\n * The reset re-queues every PR once so a resolver can send commit times; `prsNeedingOutcome`\n * also offers any PR whose `commit_times` is null, so a legacy resolver cannot spend it.\n */\nexport function applyReviewVerdictSchema(db: Db): void {\n db.exec(REVIEW_DDL);\n for (const [column, type] of PR_COLUMNS) addColumnIfMissing(db, \"pr\", column, type);\n db.exec(\"UPDATE pr SET review_rounds_gh = review_rounds\");\n db.exec(\"UPDATE pr SET outcome_checked_at = NULL\");\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\n/** Additive: legacy rows, aliases and original locators are never rewritten. */\nexport const NORMALIZED_DDL = `\nCREATE TABLE IF NOT EXISTS conversation (\n ref TEXT PRIMARY KEY, harness TEXT NOT NULL, namespace TEXT NOT NULL,\n native_id TEXT NOT NULL, legacy_session_id TEXT,\n UNIQUE(harness, namespace, native_id)\n);\nCREATE TABLE IF NOT EXISTS conversation_alias (\n legacy_ref TEXT NOT NULL, conversation_ref TEXT NOT NULL,\n PRIMARY KEY(legacy_ref, conversation_ref)\n);\nCREATE TABLE IF NOT EXISTS normalized_source (\n transcript_id INTEGER PRIMARY KEY, source_id TEXT NOT NULL UNIQUE,\n conversation_ref TEXT NOT NULL, descriptor TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS normalized_event (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n subrecord_index INTEGER NOT NULL, byte_length INTEGER NOT NULL,\n conversation_ref TEXT NOT NULL, kind TEXT NOT NULL, ts TEXT,\n turn_ref TEXT, call_ref TEXT, item_ref TEXT, phase TEXT, is_error INTEGER,\n usage TEXT, metadata TEXT, history_origin TEXT, related_ref TEXT, relationship TEXT,\n PRIMARY KEY(transcript_id, byte_offset, subrecord_index)\n);\nCREATE INDEX IF NOT EXISTS idx_normalized_event_conversation ON normalized_event(conversation_ref, ts);\nCREATE TABLE IF NOT EXISTS normalized_span (\n span_id INTEGER PRIMARY KEY, locators TEXT NOT NULL\n);\n`;\n\n/** Only the old transcript session table proves Claude provenance, never a ref prefix. */\nexport function backfillClaudeAliases(db: Db, sessionIds?: readonly string[]): void {\n const insert = db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id,legacy_session_id) VALUES (?,?,?,?,?)\");\n const alias = db.prepare(\"INSERT OR IGNORE INTO conversation_alias VALUES (?,?)\");\n const rows = sessionIds?.map(session_id => ({ session_id })) ?? db.prepare(\"SELECT session_id FROM session\").all() as Iterable<{ session_id: string }>;\n for (const row of rows) {\n const ref = conversationRef({ harness: \"claude-code\", namespace: \"legacy\", nativeId: row.session_id });\n insert.run(ref, \"claude-code\", \"legacy\", row.session_id, row.session_id);\n alias.run(`session:${row.session_id}`, ref);\n }\n}\n\n/** Ambiguity is explicit; workspace session bodies remain in their original namespace. */\nexport function resolveConversationAlias(db: Db, legacyRef: string): string | null {\n const rows = db.prepare(\"SELECT conversation_ref FROM conversation_alias WHERE legacy_ref = ?\").all(legacyRef) as { conversation_ref: string }[];\n if (rows.length > 1) throw new Error(`ambiguous conversation alias: ${legacyRef}`);\n return rows[0]?.conversation_ref ?? null;\n}\n","import { AUDIT_MIGRATION_NAME, AUDIT_TABLES, FACET_TABLE, applyAuditSchema } from \"./audit-schema.js\";\nimport { EPISODE_TABLE, ORIGIN_MIGRATION_NAME, ORIGIN_TABLES, applyOriginSchema } from \"./audit-schema-v5.js\";\nimport { EPISODE_TRANSCRIPT_MIGRATION_NAME, applyEpisodeTranscriptSchema } from \"./audit-schema-v6.js\";\nimport { ORIGIN_TASK_LINK_MIGRATION_NAME, applyOriginTaskLinkSchema } from \"./audit-schema-v7.js\";\nimport { REVIEW_TABLE, REVIEW_VERDICT_MIGRATION_NAME, applyReviewVerdictSchema } from \"./audit-schema-v8.js\";\nimport { NORMALIZED_DDL, backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { SQL_NOW, kitMigration, type Migration } from \"@titan-design/store-sqlite\";\n\n/** Kit table names this graph uses. `watermark` rows are transcripts; `search_*` are the FTS spans. */\nexport const KIT = { watermark: \"transcript\", edge: \"edge\", spanFts: \"search\" } as const;\n\n/**\n * Legacy domain tables. Original sources can be pruned; schema migrations must\n * preserve these rows rather than assuming a replay is possible.\n * Locators are `(transcript_id, byte_offset, byte_length)`; `fact_id` rows\n * resolve through the facts table's unique `(transcript_id, byte_offset)`.\n */\nexport const DOMAIN_DDL = `\n CREATE TABLE IF NOT EXISTS fact (\n fact_id INTEGER PRIMARY KEY,\n transcript_id INTEGER NOT NULL,\n byte_offset INTEGER NOT NULL,\n byte_length INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n ts TEXT NOT NULL,\n seq INTEGER NOT NULL,\n session_id TEXT NOT NULL,\n prompt_id TEXT,\n tool_use_id TEXT,\n t_indexed TEXT NOT NULL DEFAULT (${SQL_NOW}),\n UNIQUE (transcript_id, byte_offset)\n );\n CREATE INDEX IF NOT EXISTS idx_fact_session_ts ON fact(session_id, ts);\n CREATE INDEX IF NOT EXISTS idx_fact_prompt ON fact(prompt_id);\n CREATE INDEX IF NOT EXISTS idx_fact_tool_use ON fact(tool_use_id);\n\n CREATE TABLE IF NOT EXISTS session (\n session_id TEXT PRIMARY KEY,\n transcript_id INTEGER,\n started_at TEXT,\n ended_at TEXT,\n start_type TEXT,\n cwd TEXT,\n git_branch TEXT,\n ai_title TEXT,\n seed_prompt TEXT,\n cli_version TEXT,\n turn_count INTEGER NOT NULL DEFAULT 0,\n commit_count INTEGER NOT NULL DEFAULT 0,\n push_count INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX IF NOT EXISTS idx_session_started ON session(started_at);\n\n CREATE TABLE IF NOT EXISTS session_model_usage (\n session_id TEXT NOT NULL,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n thinking_tokens INTEGER NOT NULL DEFAULT 0,\n request_count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (session_id, model)\n );\n\n CREATE TABLE IF NOT EXISTS turn (\n prompt_id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n turn_index INTEGER NOT NULL,\n started_at TEXT NOT NULL,\n ended_at TEXT,\n duration_ms INTEGER,\n tool_call_count INTEGER NOT NULL DEFAULT 0,\n thinking_ms INTEGER NOT NULL DEFAULT 0,\n fact_id_start INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_turn_session ON turn(session_id, turn_index);\n\n CREATE TABLE IF NOT EXISTS permission_phase (\n phase_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n from_mode TEXT,\n to_mode TEXT NOT NULL,\n trigger TEXT NOT NULL,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_phase_session ON permission_phase(session_id, t_valid);\n\n CREATE TABLE IF NOT EXISTS human_edit (\n edit_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n ts TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, ts)\n );\n\n CREATE TABLE IF NOT EXISTS file_checkpoint (\n checkpoint_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n backup_file_name TEXT NOT NULL,\n version INTEGER NOT NULL,\n backup_time TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, backup_file_name)\n );\n\n CREATE TABLE IF NOT EXISTS pr (\n pr_ref TEXT PRIMARY KEY, number INTEGER, repo TEXT, title TEXT, state TEXT, url TEXT, merged_at TEXT\n );\n CREATE TABLE IF NOT EXISTS branch (\n branch_ref TEXT PRIMARY KEY, repo TEXT, name TEXT NOT NULL, base TEXT, created_at TEXT, deleted_at TEXT\n );\n CREATE TABLE IF NOT EXISTS file (\n file_ref TEXT PRIMARY KEY, repo TEXT, path TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS task (\n task_ref TEXT PRIMARY KEY, task_id TEXT NOT NULL, initiative TEXT, title TEXT, status TEXT\n );\n CREATE TABLE IF NOT EXISTS subagent (\n agent_ref TEXT PRIMARY KEY,\n session_id TEXT,\n child_session_id TEXT,\n parent_agent_ref TEXT,\n agent_type TEXT,\n label TEXT,\n started_at TEXT,\n ended_at TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_subagent_child ON subagent(child_session_id);\n CREATE TABLE IF NOT EXISTS artifact (\n artifact_ref TEXT PRIMARY KEY, kind TEXT, title TEXT, url TEXT, path TEXT, created_at TEXT\n );\n CREATE TABLE IF NOT EXISTS pr_merge_observation (\n number INTEGER NOT NULL, repo_hint TEXT, merged_at TEXT NOT NULL,\n PRIMARY KEY (number, repo_hint, merged_at)\n );\n CREATE TABLE IF NOT EXISTS pr_create_observation (\n tool_use_id TEXT PRIMARY KEY, title TEXT, number INTEGER, repo TEXT, url TEXT\n );\n`;\n\n/** Every derived table, in an order safe to clear. The watermark table is not derived. */\nexport const DERIVED_TABLES = [\n \"normalized_span\",\n \"normalized_event\",\n \"normalized_source\",\n `${KIT.spanFts}_span`,\n KIT.edge,\n \"turn\",\n \"permission_phase\",\n \"human_edit\",\n \"file_checkpoint\",\n \"subagent\",\n ...AUDIT_TABLES,\n FACET_TABLE,\n ...ORIGIN_TABLES,\n EPISODE_TABLE,\n \"session_model_usage\",\n \"session\",\n \"fact\",\n \"pr\",\n \"pr_merge_observation\",\n \"pr_create_observation\",\n REVIEW_TABLE,\n \"branch\",\n \"file\",\n \"task\",\n \"artifact\",\n] as const;\n\nexport const MIGRATIONS: Migration[] = [\n kitMigration(1, { watermark: KIT.watermark, edge: KIT.edge, spanFts: KIT.spanFts }, \"kit tables\"),\n { version: 2, name: \"session graph tables\", up: (db) => db.exec(DOMAIN_DDL) },\n { version: 3, name: \"normalized conversations and source evidence\", up: (db) => { db.exec(NORMALIZED_DDL); backfillClaudeAliases(db); } },\n { version: 4, name: AUDIT_MIGRATION_NAME, up: applyAuditSchema },\n { version: 5, name: ORIGIN_MIGRATION_NAME, up: applyOriginSchema },\n { version: 6, name: EPISODE_TRANSCRIPT_MIGRATION_NAME, up: applyEpisodeTranscriptSchema },\n { version: 7, name: ORIGIN_TASK_LINK_MIGRATION_NAME, up: applyOriginTaskLinkSchema },\n { version: 8, name: REVIEW_VERDICT_MIGRATION_NAME, up: applyReviewVerdictSchema },\n];\n","import type { TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * One API response is written as several assistant lines, each repeating its\n * usage. The row keeps the first line's identity and the largest count seen,\n * so a partial streaming copy never wins and chunk order cannot change it.\n */\nconst UPSERT_REQUEST = `\n INSERT INTO request (transcript_id, request_id, byte_offset, session_id, message_id, ts, model,\n input_tokens, cache_read_tokens, cache_creation_tokens, cache_creation_5m, cache_creation_1h,\n output_tokens, thinking_tokens, context_tokens, service_tier, is_sidechain)\n VALUES (@transcriptId, @requestId, @byteOffset, @sessionId, @messageId, @ts, @model,\n @inputTokens, @cacheReadTokens, @cacheCreationTokens, @cacheCreation5mTokens, @cacheCreation1hTokens,\n @outputTokens, @thinkingTokens, @inputTokens + @cacheReadTokens + @cacheCreationTokens, @serviceTier, @isSidechain)\n ON CONFLICT (transcript_id, request_id) DO UPDATE SET\n session_id = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.session_id ELSE session_id END,\n message_id = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.message_id ELSE message_id END,\n model = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.model ELSE model END,\n service_tier = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.service_tier ELSE service_tier END,\n is_sidechain = CASE WHEN excluded.byte_offset < byte_offset THEN excluded.is_sidechain ELSE is_sidechain END,\n byte_offset = MIN(byte_offset, excluded.byte_offset),\n ts = MIN(ts, excluded.ts),\n input_tokens = MAX(input_tokens, excluded.input_tokens),\n cache_read_tokens = MAX(cache_read_tokens, excluded.cache_read_tokens),\n cache_creation_tokens = MAX(cache_creation_tokens, excluded.cache_creation_tokens),\n cache_creation_5m = MAX(cache_creation_5m, excluded.cache_creation_5m),\n cache_creation_1h = MAX(cache_creation_1h, excluded.cache_creation_1h),\n output_tokens = MAX(output_tokens, excluded.output_tokens),\n thinking_tokens = MAX(thinking_tokens, excluded.thinking_tokens),\n context_tokens = MAX(input_tokens, excluded.input_tokens) + MAX(cache_read_tokens, excluded.cache_read_tokens)\n + MAX(cache_creation_tokens, excluded.cache_creation_tokens)`;\n\n/** Line-located rows are facts about one position in the file, so a re-read of the same bytes is a no-op. */\nconst INSERTS = {\n toolCalls: `INSERT INTO tool_call (transcript_id, byte_offset, block_index, session_id, ts, tool_use_id, name, family, mcp_server, input_chars)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @toolUseId, @name, @family, @mcpServer, @inputChars)\n ON CONFLICT DO NOTHING`,\n 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)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @cause, @delivery, @detail, @originServer, @fromName, @msgId, @toolUseId, @isError, @contentHash, @chars)\n ON CONFLICT DO NOTHING`,\n contextBlocks: `INSERT INTO context_block (transcript_id, byte_offset, block_index, session_id, ts, source, tool_use_id, attachment_type, chars, is_media)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @source, @toolUseId, @attachmentType, @chars, @isMedia)\n ON CONFLICT DO NOTHING`,\n compactions: `INSERT INTO compaction (transcript_id, byte_offset, session_id, ts, trigger, pre_tokens, post_tokens, dropped_tokens, duration_ms)\n VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @trigger, @preTokens, @postTokens, @droppedTokens, @durationMs)\n ON CONFLICT DO NOTHING`,\n queueOps: `INSERT INTO queue_op (transcript_id, byte_offset, session_id, ts, operation, content_hash, origin_server)\n VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @operation, @contentHash, @originServer)\n ON CONFLICT DO NOTHING`,\n signals: `INSERT INTO session_signal (transcript_id, byte_offset, block_index, session_id, ts, signal, detail, tool_use_id)\n VALUES (@transcriptId, @byteOffset, @blockIndex, @sessionId, @ts, @signal, @detail, @toolUseId)\n ON CONFLICT DO NOTHING`,\n costStates: `INSERT INTO cost_state_observation (transcript_id, byte_offset, session_id, ts, total_cost_usd, model_usage)\n VALUES (@transcriptId, @byteOffset, @sessionId, @ts, @totalCostUsd, @modelUsageJson)\n ON CONFLICT DO NOTHING`,\n} as const;\n\n/** Write one delta's audit events. Runs inside `applyDelta`'s transaction, so it opens none of its own. */\nexport function applyAudit(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n run(db, UPSERT_REQUEST, transcriptId, delta.requests);\n for (const [list, sql] of Object.entries(INSERTS) as [keyof typeof INSERTS, string][]) {\n run(db, sql, transcriptId, delta[list]);\n }\n}\n\nfunction run(db: Db, sql: string, transcriptId: number, events: readonly object[]): void {\n if (events.length === 0) return;\n const statement = db.prepare(sql);\n const names = [...new Set([...sql.matchAll(/@(\\w+)/g)].map((m) => m[1]!))];\n for (const event of events) statement.run(bind(names, { ...(event as Row), transcriptId }));\n}\n\n/** better-sqlite3 rejects unused named parameters and cannot bind booleans. */\nfunction bind(names: readonly string[], row: Row): Row {\n return Object.fromEntries(names.map((name) => {\n const value = row[name] ?? null;\n return [name, typeof value === \"boolean\" ? Number(value) : value];\n }));\n}\n","import { EXTRACT_VERSION, TranscriptParseError, extractTranscript, type DiscoveredTranscript, type TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db, WatermarkRow } from \"@titan-design/store-sqlite\";\nimport { applyAudit } from \"./audit-apply.js\";\nimport { AUDIT_TABLES, FACET_TABLE } from \"./audit-schema.js\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/** The one facet today: the eight audit tables, all written from the same extraction. */\nexport const AUDIT_FACET = \"audit\";\n\n/** Transcripts re-extracted per pass, so a daemon's pass stays short while the backlog drains. */\nexport const DEFAULT_FACET_LIMIT = 40;\n\nexport interface BackfillOptions {\n /** Transcripts visited this pass. `Infinity` clears the whole backlog. */\n limit?: number;\n /** The extraction version a facet must reach. Defaults to session-read's `EXTRACT_VERSION`. */\n version?: number;\n}\n\nexport interface BackfillSummary {\n backfilled: number;\n /** Stale transcripts left for later passes. */\n backlog: number;\n /** Sessions whose audit rows were rebuilt, for the caller's rollup. */\n sessionIds: string[];\n}\n\ninterface Candidate {\n row: WatermarkRow;\n transcript: DiscoveredTranscript;\n}\n\n/**\n * Bring the audit facet of already-indexed transcripts up to `version` without a\n * full rebuild: re-read each one up to its watermark and replace only its audit\n * rows. Legacy tables are never written, so their accumulating counts cannot double.\n */\nexport async function backfillFacets(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], options: BackfillOptions = {}): Promise<BackfillSummary> {\n const version = options.version ?? EXTRACT_VERSION;\n const stale = staleTranscripts(graph, transcripts, version);\n const visit = stale.slice(0, options.limit ?? DEFAULT_FACET_LIMIT);\n const sessionIds = new Set<string>();\n let backfilled = 0;\n for (const candidate of visit) {\n const touched = await backfillOne(graph, candidate, version);\n if (!touched) continue;\n backfilled += 1;\n for (const id of touched) sessionIds.add(id);\n }\n return { backfilled, backlog: stale.length - visit.length, sessionIds: [...sessionIds] };\n}\n\n/** Discovered, status `ok`, and below `version`; newest first so recent cost history lands first. */\nfunction staleTranscripts(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], version: number): Candidate[] {\n const byPath = new Map(transcripts.map((t) => [t.displayPath, t]));\n const versions = facetVersions(graph.db);\n const stale: Candidate[] = [];\n for (const row of graph.transcripts.list()) {\n const transcript = byPath.get(row.sourceKey);\n if (!transcript || row.status !== \"ok\" || (versions.get(row.sourceId) ?? -1) >= version) continue;\n stale.push({ row, transcript });\n }\n return stale.sort((a, b) => (b.row.fileMtime ?? \"\").localeCompare(a.row.fileMtime ?? \"\"));\n}\n\nfunction facetVersions(db: Db): Map<number, number> {\n const rows = db.prepare(`SELECT transcript_id, version FROM ${FACET_TABLE} WHERE facet = ?`).all(AUDIT_FACET) as { transcript_id: number; version: number }[];\n return new Map(rows.map((r) => [r.transcript_id, r.version]));\n}\n\n/** Returns the sessions touched, or `null` when the file could not be re-read and was marked instead. */\nasync function backfillOne(graph: SessionGraph, { row, transcript }: Candidate, version: number): Promise<string[] | null> {\n let delta: TranscriptDelta & { lastByteOffset: number };\n try {\n delta = await extractTranscript(transcript.absolutePath, { untilByteOffset: row.lastOffset, subagentId: transcript.subagentId });\n } catch (err) {\n // Mark rather than skip, or a broken file would hold a slot at the head of every pass.\n const status = unreadableStatus(err);\n if (!status) throw err;\n graph.transcripts.markStatus(row.sourceKey, status, err instanceof Error ? err.message : String(err));\n return null;\n }\n graph.db.transaction(() => {\n for (const table of AUDIT_TABLES) graph.db.prepare(`DELETE FROM \"${table}\" WHERE transcript_id = ?`).run(row.sourceId);\n applyAudit(graph.db, row.sourceId, delta);\n writeFacet(graph.db, row.sourceId, version, delta.lastByteOffset);\n })();\n return delta.sessions.map((s) => s.sessionId);\n}\n\nfunction unreadableStatus(err: unknown): \"missing\" | \"quarantined\" | null {\n if (err instanceof TranscriptParseError) return \"quarantined\";\n if ((err as NodeJS.ErrnoException | null)?.code === \"ENOENT\") return \"missing\";\n return null;\n}\n\nfunction writeFacet(db: Db, transcriptId: number, version: number, indexedTo: number): void {\n db.prepare(`\n INSERT INTO ${FACET_TABLE} (transcript_id, facet, version, indexed_to) VALUES (?, ?, ?, ?)\n ON CONFLICT (transcript_id, facet) DO UPDATE SET version = excluded.version, indexed_to = excluded.indexed_to`)\n .run(transcriptId, AUDIT_FACET, version, indexedTo);\n}\n\n/**\n * Record a forward read. A read from byte 0 covers the whole file, so it sets the\n * facet outright; a resumed read only extends a facet that is already current,\n * because the bytes before it may predate the audit tables.\n */\nexport function advanceFacet(db: Db, transcriptId: number, fromByteOffset: number, indexedTo: number, version: number = EXTRACT_VERSION): void {\n if (fromByteOffset === 0) {\n writeFacet(db, transcriptId, version, indexedTo);\n return;\n }\n db.prepare(`UPDATE ${FACET_TABLE} SET indexed_to = ? WHERE transcript_id = ? AND facet = ? AND version = ?`)\n .run(indexedTo, transcriptId, AUDIT_FACET, version);\n}\n","import { hasMarker, INJECTED_MARKERS, type Inbound, type InjectedMarkerName, type WakeCause } from \"@titan-design/session-read\";\n\n/**\n * TP-108: what the harness, hooks and agent-chat put into a `user` record, cut out\n * of the prompt text that full-text search and the miner see. The human's own\n * words stay. The marker list is documented in the README under \"Injected context\".\n */\n\n/** Whole-line causes: session-read's inbound classifier already knows nobody typed these. */\nconst INJECTED_CAUSES: ReadonlySet<WakeCause> = new Set<WakeCause>([\n \"compaction\", \"channel_message\", \"channel_system\", \"task_notification\", \"scheduled_wakeup\",\n \"usage_limit_resume\", \"harness_resume\", \"local_command\", \"image_meta\", \"hook_or_reminder\",\n]);\n\nexport function isInjectedCause(cause: WakeCause): boolean {\n return INJECTED_CAUSES.has(cause);\n}\n\n/** A line nobody typed: an injected cause, or a headless `sdk` prompt unless the caller keeps those. */\nexport function isUntypedPrompt(inbound: Pick<Inbound, \"cause\" | \"promptSource\">, indexSdkPrompts = false): boolean {\n return isInjectedCause(inbound.cause) || (!indexSdkPrompts && inbound.promptSource === \"sdk\");\n}\n\n/** session-read's closed markers (system reminders, channel messages, task notifications) plus host echoes it does not list. */\nconst CUT_TAGS = [\n ...Object.values(INJECTED_MARKERS).flatMap(m => (m.close ? [m.close.slice(2, -1)] : [])),\n \"user-prompt-submit-hook\", \"command-name\", \"command-message\", \"local-command-caveat\",\n \"local-command-stdout\", \"local-command-stderr\", \"bash-stdout\", \"bash-stderr\",\n].join(\"|\");\nconst CUT_OPEN = new RegExp(`<(${CUT_TAGS})(?=[\\\\s>])[^>]*>`, \"g\");\nconst LINE_END_OR_BLOCK = new RegExp(`^[ \\\\t]*(?:\\\\r?\\\\n|$|<(?:${CUT_TAGS})(?=[\\\\s>]))`);\n\n/** An opening tag counts only at the start of a line or straight after a block already cut, so prose that mentions a tag stays. */\nfunction opensBlock(text: string, at: number, keptFrom: number): boolean {\n const lead = text.slice(Math.max(text.lastIndexOf(\"\\n\", at - 1) + 1, keptFrom), at);\n return lead.trim() === \"\";\n}\n\n/** The index just past the close that balances this open tag, counting nested same-name tags; null when it never closes. */\nfunction balancedClose(text: string, tag: string, from: number): number | null {\n const tags = new RegExp(`<(/?)${tag}(?=[\\\\s>])[^>]*>`, \"g\");\n tags.lastIndex = from;\n let depth = 1;\n for (let m = tags.exec(text); m; m = tags.exec(text)) {\n depth += m[1] ? -1 : 1;\n if (depth === 0) return tags.lastIndex;\n }\n return null;\n}\n\n/** A block ends a line when only whitespace or another cut block follows its close on that line. */\nfunction closesLine(text: string, at: number): boolean {\n return LINE_END_OR_BLOCK.test(text.slice(at));\n}\n\nfunction cutBlocks(text: string): string {\n let kept = \"\";\n let keptFrom = 0;\n CUT_OPEN.lastIndex = 0;\n for (let open = CUT_OPEN.exec(text); open; open = CUT_OPEN.exec(text)) {\n if (!opensBlock(text, open.index, keptFrom)) continue;\n const end = balancedClose(text, open[1]!, CUT_OPEN.lastIndex);\n if (end === null || !closesLine(text, end)) continue;\n kept += text.slice(keptFrom, open.index);\n keptFrom = end;\n CUT_OPEN.lastIndex = end;\n }\n return kept + text.slice(keptFrom);\n}\n\nconst INTERRUPTION = /\\[Request interrupted by user[^\\]\\n]*\\]/g;\n\n/** Wrappers around words the human did type: slash-command arguments and a `!` shell command. */\nconst HUMAN_WRAPPERS = [\"command-args\", \"bash-input\"].map(tag => new RegExp(`</?${tag}>`, \"g\"));\n\n/** Unclosed markers in session-read's list: when one heads the text, the whole turn is injected. */\nconst WHOLE_TURN_MARKERS = (Object.keys(INJECTED_MARKERS) as InjectedMarkerName[])\n .filter(name => INJECTED_MARKERS[name].close === null);\n\n/**\n * agent-chat puts no tag around a spawn brief, so a brief is known by the framing it\n * writes itself: the orientation header (session-read's `spawn_brief`), a predecessor\n * section, and the isolation note appended last. A coordinator wrote the rest.\n */\nconst SPAWN_BRIEF_FRAMING = [\n /^# Predecessor: .*\\n\\nYou are taking over from /,\n /Commit your work there; nothing outside it is yours to change\\.\\s*$/,\n /You may be sharing it with other agents, so stay inside the paths you were given\\.\\s*$/,\n];\n\nfunction isWholeTurnInjected(text: string): boolean {\n const trimmed = text.trim();\n return WHOLE_TURN_MARKERS.some(name => hasMarker(trimmed, name)) || SPAWN_BRIEF_FRAMING.some(re => re.test(trimmed));\n}\n\n/** Prompt text with every injected block removed; empty when nothing the human wrote is left. */\nexport function stripInjected(text: string): string {\n let kept = cutBlocks(text);\n for (const re of [INTERRUPTION, ...HUMAN_WRAPPERS]) kept = kept.replace(re, \"\");\n if (isWholeTurnInjected(kept)) return \"\";\n return kept.replace(/[ \\t]+\\n/g, \"\\n\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n","import { applyAudit } from \"./audit-apply.js\";\nimport { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport { isUntypedPrompt, stripInjected } from \"./injected-text.js\";\nimport { backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { RELATIONS, sessionRef, type TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db, EdgeInput } from \"@titan-design/store-sqlite\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * Apply one transcript chunk's delta in a single transaction, so a crash can\n * never leave rows committed behind a stale watermark or ahead of their rows.\n * Append-only tables are idempotent through unique indexes; session rows\n * accumulate; usage is left to the rollup, which recomputes it from `request`;\n * ref-keyed assets insert-if-absent with COALESCE merges that mirror\n * `EventFolder`'s rules.\n */\nexport function applyDelta(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta, source: DeltaSource = {}): void {\n graph.db.transaction(() => {\n applyFacts(graph.db, transcriptId, delta);\n applySessions(graph.db, transcriptId, delta, source.account ?? null);\n backfillClaudeAliases(graph.db, delta.sessions.map(s => s.sessionId));\n applyAssets(graph.db, delta);\n applyPhases(graph.db, transcriptId, delta);\n applyLinkedRows(graph, transcriptId, delta);\n applySpans(graph, transcriptId, delta, source.indexSdkPrompts ?? false);\n applyReviewVerdicts(graph.db, transcriptId, delta);\n applyAudit(graph.db, transcriptId, delta);\n })();\n}\n\n/** What discovery knows about the file that the lines themselves do not carry. */\nexport interface DeltaSource {\n /** The Claude config dir the transcript was found under; `null` when discovery did not say. */\n account?: string | null;\n /** Index prompts the host labels `promptSource: \"sdk\"` (spawned agents and `claude -p`). Off by default; see README \"Injected context\". */\n indexSdkPrompts?: boolean;\n}\n\nconst INSERT_FACT = `\n INSERT INTO fact (transcript_id, byte_offset, byte_length, event_type, ts, seq, session_id, prompt_id, tool_use_id)\n VALUES (@transcriptId, @byteOffset, @byteLength, @eventType, @ts, @seq, @sessionId, @promptId, @toolUseId)\n ON CONFLICT (transcript_id, byte_offset) DO NOTHING`;\n\n/** Facts carry a per-transcript `seq` continuing from whatever is stored. */\nfunction applyFacts(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const next = db.prepare(\"SELECT COALESCE(MAX(seq), -1) + 1 AS next FROM fact WHERE transcript_id = ?\").get(transcriptId) as { next: number };\n const insert = db.prepare(INSERT_FACT);\n let seq = next.next;\n for (const f of delta.facts) {\n insert.run({ transcriptId, byteOffset: f.byteOffset, byteLength: f.byteLength, eventType: f.eventType, ts: f.ts, seq: seq++, sessionId: f.sessionId, promptId: f.promptId, toolUseId: f.toolUseId });\n }\n}\n\nconst UPSERT_SESSION = `\n 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)\n VALUES (@sessionId, @transcriptId, @startedAt, @endedAt, @startType, @cwd, @gitBranch, @aiTitle, @seedPrompt, @cliVersion, @account, @turnDelta, @commitDelta, @pushDelta)\n ON CONFLICT (session_id) DO UPDATE SET\n started_at = MIN(COALESCE(started_at, excluded.started_at), COALESCE(excluded.started_at, started_at)),\n ended_at = MAX(COALESCE(ended_at, excluded.ended_at), COALESCE(excluded.ended_at, ended_at)),\n start_type = CASE\n WHEN excluded.start_type IS NULL THEN start_type\n WHEN start_type IS NULL THEN excluded.start_type\n WHEN COALESCE(excluded.started_at, started_at) < COALESCE(started_at, excluded.started_at) THEN excluded.start_type\n ELSE start_type END,\n cwd = COALESCE(excluded.cwd, cwd),\n git_branch = COALESCE(git_branch, excluded.git_branch),\n ai_title = COALESCE(excluded.ai_title, ai_title),\n seed_prompt = COALESCE(seed_prompt, excluded.seed_prompt),\n cli_version = COALESCE(excluded.cli_version, cli_version),\n account = COALESCE(excluded.account, account),\n turn_count = turn_count + excluded.turn_count,\n commit_count = commit_count + excluded.commit_count,\n push_count = push_count + excluded.push_count`;\n\nfunction applySessions(db: Db, transcriptId: number, delta: TranscriptDelta, account: string | null): void {\n const upsertSession = db.prepare(UPSERT_SESSION);\n for (const s of delta.sessions) upsertSession.run({ ...s, transcriptId, account });\n}\n\nconst ASSET_UPSERTS = {\n prs: `INSERT INTO pr (pr_ref, number, repo, title, url) VALUES (@prRef, @number, @repo, @title, @url)\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(title, excluded.title), url = COALESCE(url, excluded.url)`,\n branches: `INSERT INTO branch (branch_ref, repo, name, base, created_at, deleted_at) VALUES (@branchRef, @repo, @name, @base, @createdAt, @deletedAt)\n ON CONFLICT (branch_ref) DO UPDATE SET base = COALESCE(base, excluded.base),\n created_at = MIN(COALESCE(created_at, excluded.created_at), COALESCE(excluded.created_at, created_at)),\n deleted_at = MAX(COALESCE(deleted_at, excluded.deleted_at), COALESCE(excluded.deleted_at, deleted_at))`,\n files: `INSERT INTO file (file_ref, repo, path) VALUES (@fileRef, @repo, @path) ON CONFLICT (file_ref) DO NOTHING`,\n tasks: `INSERT INTO task (task_ref, task_id, status) VALUES (@taskRef, @taskId, @status)\n ON CONFLICT (task_ref) DO UPDATE SET status = COALESCE(excluded.status, status)`,\n artifacts: `INSERT INTO artifact (artifact_ref, kind, title, url, path, created_at) VALUES (@artifactRef, @artifactKind, @title, @url, @path, @ts)\n ON CONFLICT (artifact_ref) DO NOTHING`,\n prMerges: `INSERT INTO pr_merge_observation (number, repo_hint, merged_at) VALUES (@number, @repoHint, @ts)\n ON CONFLICT (number, repo_hint, merged_at) DO NOTHING`,\n prCreates: `INSERT INTO pr_create_observation (tool_use_id, title, number, repo, url) VALUES (@toolUseId, @title, @number, @repo, @url)\n ON CONFLICT (tool_use_id) DO UPDATE SET title = COALESCE(title, excluded.title), number = COALESCE(number, excluded.number),\n repo = COALESCE(repo, excluded.repo), url = COALESCE(url, excluded.url)`,\n} as const;\n\nfunction applyAssets(db: Db, delta: TranscriptDelta): void {\n for (const [kind, sql] of Object.entries(ASSET_UPSERTS) as [keyof typeof ASSET_UPSERTS, string][]) {\n const statement = db.prepare(sql);\n for (const row of delta[kind] as unknown as Record<string, unknown>[]) statement.run(pick(row, sql));\n }\n}\n\n/** better-sqlite3 rejects unused named parameters, so pass only the ones the statement binds. */\nfunction pick(row: Record<string, unknown>, sql: string): Record<string, unknown> {\n const names = new Set([...sql.matchAll(/@(\\w+)/g)].map((m) => m[1]!));\n return Object.fromEntries([...names].map((name) => [name, row[name] ?? null]));\n}\n\n/** A re-read refreshes the parsed fields and keeps a resolved `pr_ref`. */\nconst UPSERT_CHAT_VERDICT = `\n INSERT INTO ${REVIEW_TABLE} (source_key, surface, verdict, ts, session_id, transcript_id, repo, repo_hint, cwd_repo, number)\n VALUES (@sourceKey, 'chat', @verdict, @ts, @sessionId, @transcriptId, @repo, @repoHint, @cwdRepo, @number)\n ON CONFLICT (source_key) DO UPDATE SET verdict = excluded.verdict, ts = excluded.ts, session_id = excluded.session_id,\n transcript_id = excluded.transcript_id, repo = excluded.repo, repo_hint = excluded.repo_hint,\n cwd_repo = excluded.cwd_repo, number = excluded.number`;\n\n/** The ordinal comes from the event, so one tool use's verdicts keep distinct keys across chunk boundaries. */\nfunction applyReviewVerdicts(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const upsert = db.prepare(UPSERT_CHAT_VERDICT);\n const keys = chatVerdictKeys(delta.reviewVerdicts);\n delta.reviewVerdicts.forEach((v, i) => {\n upsert.run({\n sourceKey: keys[i], verdict: v.verdict, ts: v.ts, sessionId: v.sessionId, transcriptId,\n repo: v.repo, repoHint: v.repoHint, cwdRepo: v.cwdRepo, number: v.number,\n });\n });\n}\n\ntype ChatVerdict = TranscriptDelta[\"reviewVerdicts\"][number];\n\nconst hasOrdinal = (v: ChatVerdict): boolean => Number.isInteger(v.ordinal) && v.ordinal >= 0;\n\n/**\n * A session-read that predates `ordinal` sends none, so those verdicts take the next index\n * this call has not used for their tool use, skipping any ordinal another event carries.\n * Per call is enough because that session-read emits all of one tool use's verdicts from one line.\n */\nfunction chatVerdictKeys(verdicts: readonly ChatVerdict[]): string[] {\n const used = new Map<string, Set<number>>();\n const usedBy = (id: string) => used.get(id) ?? used.set(id, new Set()).get(id)!;\n for (const v of verdicts) if (hasOrdinal(v)) usedBy(v.toolUseId).add(v.ordinal);\n return verdicts.map((v) => {\n if (hasOrdinal(v)) return `chat:${v.toolUseId}:${v.ordinal}`;\n const taken = usedBy(v.toolUseId);\n let n = 0;\n while (taken.has(n)) n += 1;\n taken.add(n);\n return `chat:${v.toolUseId}:${n}`;\n });\n}\n\n/**\n * Bi-temporal phase collapse: a candidate matching the session's open phase is a\n * no-op; otherwise the open phase closes at the new `t_valid` and a replacement\n * row is inserted. Done here so a chunk boundary mid-run cannot add a phase.\n */\nfunction applyPhases(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const current = db.prepare(\"SELECT phase_id, to_mode FROM permission_phase WHERE session_id = ? AND trigger = ? AND t_invalid IS NULL ORDER BY phase_id DESC LIMIT 1\");\n const close = db.prepare(\"UPDATE permission_phase SET t_invalid = ? WHERE phase_id = ?\");\n const insert = db.prepare(\"INSERT INTO permission_phase (session_id, from_mode, to_mode, trigger, t_valid, fact_id) VALUES (?, ?, ?, ?, ?, ?)\");\n for (const phase of delta.phases) {\n const open = current.get(phase.sessionId, phase.trigger) as { phase_id: number; to_mode: string } | undefined;\n if (open?.to_mode === phase.toMode) continue;\n if (open) close.run(phase.ts, open.phase_id);\n insert.run(phase.sessionId, open?.to_mode ?? null, phase.toMode, phase.trigger, phase.ts, factIdFor(db, transcriptId, phase.byteOffset));\n }\n}\n\nfunction applyLinkedRows(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta): void {\n const { db } = graph;\n const factId = (byteOffset: number) => factIdFor(db, transcriptId, byteOffset);\n\n const upsertTurn = db.prepare(`\n INSERT INTO turn (prompt_id, session_id, turn_index, started_at, fact_id_start)\n VALUES (?, ?, (SELECT COALESCE(MAX(turn_index), -1) + 1 FROM turn WHERE session_id = ?), ?, ?)\n ON CONFLICT (prompt_id) DO UPDATE SET session_id = MIN(session_id, excluded.session_id)`);\n for (const t of delta.turns) upsertTurn.run(t.promptId, t.sessionId, t.sessionId, t.ts, factId(t.byteOffset));\n\n const insertEdit = db.prepare(\"INSERT INTO human_edit (session_id, file_path, ts, fact_id) VALUES (?, ?, ?, ?) ON CONFLICT (session_id, file_path, ts) DO NOTHING\");\n for (const e of delta.humanEdits) insertEdit.run(e.sessionId, e.filePath, e.ts, factId(e.byteOffset));\n\n const insertCheckpoint = db.prepare(`\n INSERT INTO file_checkpoint (session_id, file_path, backup_file_name, version, backup_time, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (session_id, file_path, backup_file_name) DO NOTHING`);\n for (const c of delta.fileCheckpoints) insertCheckpoint.run(c.sessionId, c.filePath, c.backupFileName, c.version, c.backupTime, factId(c.byteOffset));\n\n const upsertSubagent = db.prepare(`\n INSERT INTO subagent (agent_ref, session_id, agent_type, label, started_at, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (agent_ref) DO UPDATE SET session_id = COALESCE(session_id, excluded.session_id),\n agent_type = COALESCE(agent_type, excluded.agent_type), label = COALESCE(label, excluded.label), started_at = COALESCE(started_at, excluded.started_at)`);\n for (const s of delta.subagents) upsertSubagent.run(s.agentRef, s.sessionId, s.agentType, s.label, s.ts || null, factId(s.byteOffset));\n\n // The dispatch row and this bridge come from different lines; an upsert survives a chunk boundary between them.\n const linkTranscript = db.prepare(\"INSERT INTO subagent (agent_ref, child_session_id) VALUES (?, ?) ON CONFLICT (agent_ref) DO UPDATE SET child_session_id = excluded.child_session_id\");\n for (const l of delta.subagentTranscripts) linkTranscript.run(l.agentRef, l.childSessionId);\n\n for (const e of delta.edges) assertTranscriptEdge(graph, { sourceRef: e.sourceRef, relation: e.relation, targetRef: e.targetRef, tValid: e.ts, factId: factId(e.byteOffset) });\n}\n\n/** Prompt spans index only what the human typed; a line nobody typed indexes no prompt at all. */\nfunction applySpans(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta, indexSdkPrompts: boolean): void {\n const injectedLines = new Set(delta.inbound.filter(i => isUntypedPrompt(i, indexSdkPrompts)).map(i => i.byteOffset));\n for (const span of delta.spans) {\n const isPrompt = span.field === \"prompt\";\n const text = !isPrompt ? span.text : injectedLines.has(span.byteOffset) ? \"\" : stripInjected(span.text);\n if (isPrompt && !text) continue;\n graph.spans.index({ ownerRef: sessionRef(span.sessionId), field: span.field, sourceId: transcriptId, byteOffset: span.byteOffset, byteLength: span.byteLength }, text);\n }\n}\n\n/**\n * A transcript's `ran` claim outranks a spawn record's: an origin-made edge for the same\n * task is superseded without `via`, so origin expiry cannot later remove it.\n */\nfunction assertTranscriptEdge(graph: SessionGraph, edge: EdgeInput): void {\n const existing = edge.relation === RELATIONS.RAN ? graph.edges.current(edge.sourceRef, edge.relation, edge.targetRef) : undefined;\n if (existing?.attrs?.via === \"origin\") graph.edges.supersede(edge);\n else graph.edges.assert(edge);\n}\n\nfunction factIdFor(db: Db, transcriptId: number, byteOffset: number): number | null {\n const row = db.prepare(\"SELECT fact_id FROM fact WHERE transcript_id = ? AND byte_offset = ?\").get(transcriptId, byteOffset) as { fact_id: number } | undefined;\n return row?.fact_id ?? null;\n}\n","import { sessionRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { AUDIT_TABLES, FACET_TABLE } from \"./audit-schema.js\";\nimport { EPISODE_TABLE } from \"./audit-schema-v5.js\";\nimport { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport { KIT } from \"./schema.js\";\n\n/** Derived tables keyed by `session_id`, reachable from a transcript through `session`. */\nconst SESSION_SCOPED = [\"turn\", \"permission_phase\", \"human_edit\", \"file_checkpoint\", \"subagent\", \"session_model_usage\", EPISODE_TABLE] as const;\n\n/**\n * Drop every derived row one transcript produced, so re-reading it from byte 0\n * rebuilds those rows instead of doubling them.\n *\n * `resetIndex` already states the reason at corpus scale: rewinding alone is not\n * enough, because the accumulating upserts add a second copy of every count. A\n * rewritten single transcript has the same problem one file at a time.\n * `session.turn_count`, the `session_model_usage` token buckets and the\n * commit/push counts all sum into what is already there, and rows the rewrite\n * removed are never deleted — `fact` survives on its unique\n * `(transcript_id, byte_offset)` index, so stale facts simply persist.\n *\n * Shared identity rows survive on purpose. `pr`, `branch`, `file`, `task` and\n * `artifact` are keyed by ref, many transcripts assert the same one, and the\n * inserts are insert-if-absent; a single transcript cannot know whether it was\n * the only source. The two observation tables are left for the same reason —\n * `reconcile` folds them, and re-reading re-asserts the same rows.\n *\n * Audit rows, facet state and chat review verdicts carry `transcript_id`\n * themselves, so they go by it directly rather than through `session`.\n */\nexport function purgeTranscript(graph: SessionGraph, transcriptId: number): void {\n graph.db.transaction(() => {\n const owned = graph.db.prepare(\"SELECT session_id FROM session WHERE transcript_id = ?\").all(transcriptId) as { session_id: string }[];\n for (const { session_id } of owned) graph.spans.purgeOwner(sessionRef(session_id));\n\n // Children first: each subquery reads the `session` rows deleted last.\n for (const table of SESSION_SCOPED) {\n graph.db.prepare(`DELETE FROM \"${table}\" WHERE session_id IN (SELECT session_id FROM session WHERE transcript_id = ?)`).run(transcriptId);\n }\n graph.db.prepare(`DELETE FROM \"${KIT.edge}\" WHERE fact_id IN (SELECT fact_id FROM fact WHERE transcript_id = ?)`).run(transcriptId);\n for (const table of [...AUDIT_TABLES, FACET_TABLE, REVIEW_TABLE]) {\n graph.db.prepare(`DELETE FROM \"${table}\" WHERE transcript_id = ?`).run(transcriptId);\n }\n graph.db.prepare(\"DELETE FROM fact WHERE transcript_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM session WHERE transcript_id = ?\").run(transcriptId);\n })();\n}\n","import type { Db } from \"@titan-design/store-sqlite\";\n\nconst IN_BATCH = \"SELECT value FROM json_each(@sessionIds)\";\n\n/** Order, gap and context growth by request position; `seq_in_session` is 0-based like `turn.turn_index`. */\nconst REQUEST_ORDER = `\n WITH ordered AS (\n SELECT transcript_id, request_id, ts, context_tokens,\n ROW_NUMBER() OVER w - 1 AS seq,\n LAG(ts) OVER w AS prev_ts,\n LAG(context_tokens) OVER w AS prev_context,\n LAG(output_tokens) OVER w AS prev_output\n FROM request WHERE session_id IN (${IN_BATCH})\n WINDOW w AS (PARTITION BY session_id ORDER BY ts, byte_offset, transcript_id, request_id)\n )\n UPDATE request SET seq_in_session = ordered.seq,\n gap_ms = CAST(ROUND((julianday(ordered.ts) - julianday(ordered.prev_ts)) * 86400000) AS INTEGER),\n ctx_delta = ordered.context_tokens - (ordered.prev_context + ordered.prev_output)\n FROM ordered WHERE request.transcript_id = ordered.transcript_id AND request.request_id = ordered.request_id`;\n\n/**\n * A request is woken by the last arrival before it in its own transcript and\n * session. A tool result names its tool through `tool_call`, and an\n * `AskUserQuestion` result is the human answering, not a tool reporting.\n */\nconst REQUEST_WAKE = `\n WITH hit AS (\n SELECT r.transcript_id, r.request_id,\n (SELECT MAX(i.byte_offset) FROM inbound i\n WHERE i.transcript_id = r.transcript_id AND i.session_id = r.session_id AND i.byte_offset < r.byte_offset) AS wake_offset\n FROM request r WHERE r.session_id IN (${IN_BATCH})\n ),\n tools AS (\n SELECT transcript_id, tool_use_id, name, family, mcp_server,\n ROW_NUMBER() OVER (PARTITION BY transcript_id, tool_use_id ORDER BY byte_offset, block_index) AS pick\n FROM tool_call WHERE session_id IN (${IN_BATCH})\n ),\n wake AS (\n SELECT hit.transcript_id, hit.request_id, hit.wake_offset, i.cause, i.delivery, i.detail, i.from_name,\n t.name AS tool_name, t.family, t.mcp_server\n FROM hit\n LEFT JOIN inbound i ON i.transcript_id = hit.transcript_id AND i.byte_offset = hit.wake_offset\n 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)\n LEFT JOIN tools t ON t.transcript_id = i.transcript_id AND t.tool_use_id = i.tool_use_id AND t.pick = 1\n )\n UPDATE request SET\n wake_cause = CASE WHEN wake.cause IS NULL THEN 'session_start'\n WHEN wake.tool_name = 'AskUserQuestion' THEN 'ask_user_answer' ELSE wake.cause END,\n wake_delivery = wake.delivery,\n wake_detail = COALESCE(wake.tool_name, wake.from_name, wake.detail),\n wake_tool_family = wake.family,\n wake_mcp_server = wake.mcp_server,\n wake_offset = wake.wake_offset\n FROM wake WHERE request.transcript_id = wake.transcript_id AND request.request_id = wake.request_id`;\n\n/** How long a message waited: its delivery paired with the latest earlier enqueue of the same content. */\nconst INBOUND_QUEUED = `\n UPDATE inbound SET queued_ms = (\n SELECT CAST(ROUND((julianday(inbound.ts) - julianday(q.ts)) * 86400000) AS INTEGER)\n FROM queue_op q\n WHERE q.transcript_id = inbound.transcript_id AND q.session_id = inbound.session_id\n AND q.operation = 'enqueue' AND q.content_hash = inbound.content_hash AND q.byte_offset < inbound.byte_offset\n ORDER BY q.byte_offset DESC LIMIT 1)\n WHERE session_id IN (${IN_BATCH})`;\n\n/** A compaction whose last arrival was a tool result fired inside a tool loop, not between turns. */\nconst COMPACTION_MID_LOOP = `\n UPDATE compaction SET mid_loop = COALESCE((\n SELECT i.delivery = 'tool_result' FROM inbound i\n WHERE i.transcript_id = compaction.transcript_id AND i.session_id = compaction.session_id AND i.byte_offset < compaction.byte_offset\n ORDER BY i.byte_offset DESC, i.block_index DESC LIMIT 1), 0)\n WHERE session_id IN (${IN_BATCH})`;\n\nconst TURN_WAKE = `\n UPDATE turn SET wake_cause = (\n SELECT i.cause FROM fact f JOIN inbound i ON i.transcript_id = f.transcript_id AND i.byte_offset = f.byte_offset\n WHERE f.fact_id = turn.fact_id_start ORDER BY i.block_index LIMIT 1)\n WHERE session_id IN (${IN_BATCH})`;\n\n/**\n * Usage is a sum over deduplicated requests, never an accumulation of lines.\n * A request copied into a second transcript counts once. Sessions with no\n * request rows keep what they have: their transcripts predate the table or are gone.\n */\nconst CLEAR_USAGE = `\n DELETE FROM session_model_usage\n WHERE session_id IN (${IN_BATCH}) AND session_id IN (SELECT session_id FROM request WHERE session_id IN (${IN_BATCH}))`;\n\nconst RECOMPUTE_USAGE = `\n WITH ranked AS (\n SELECT *, ROW_NUMBER() OVER (PARTITION BY session_id, request_id ORDER BY ts, transcript_id) AS pick\n FROM request WHERE session_id IN (${IN_BATCH})\n )\n INSERT INTO session_model_usage (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, thinking_tokens, request_count)\n SELECT session_id, model, SUM(input_tokens), SUM(output_tokens), SUM(cache_read_tokens), SUM(cache_creation_tokens), SUM(thinking_tokens), COUNT(*)\n FROM ranked WHERE pick = 1 GROUP BY session_id, model`;\n\nconst STEPS = [REQUEST_ORDER, REQUEST_WAKE, INBOUND_QUEUED, COMPACTION_MID_LOOP, TURN_WAKE, CLEAR_USAGE, RECOMPUTE_USAGE] as const;\n\nexport type AuditRollup = (sessionIdsJson: string) => void;\n\n/** Prepare the audit recomputes once; the caller runs them per batch inside its own transaction. */\nexport function prepareAuditRollup(db: Db): AuditRollup {\n const statements = STEPS.map((sql) => db.prepare(sql));\n return (sessionIds) => {\n for (const statement of statements) statement.run({ sessionIds });\n };\n}\n","import { prepareAuditRollup } from \"./audit-rollup.js\";\nimport type { SessionGraph } from \"./graph.js\";\n\nconst BATCH = 400;\n\n/**\n * Turn aggregates that no single line can supply. Contract: recompute, never\n * accumulate. Every value is a pure function of the fact rows, so incremental\n * refreshes and full rebuilds converge regardless of chunk boundaries.\n */\nconst ROLLUP = `\n WITH ordered AS (\n SELECT fact_id, session_id, event_type, ts, prompt_id,\n COUNT(CASE WHEN event_type = 'user_prompt' THEN 1 END) OVER (\n PARTITION BY session_id ORDER BY ts, transcript_id, seq ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS turn_no,\n LAG(event_type) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_type,\n LAG(ts) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_ts\n FROM fact WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n ),\n owners AS (SELECT session_id, turn_no, prompt_id FROM ordered WHERE event_type = 'user_prompt'),\n agg AS (\n SELECT owners.prompt_id AS prompt_id, MAX(ordered.ts) AS ended_at,\n CAST(ROUND((julianday(MAX(ordered.ts)) - julianday(MIN(ordered.ts))) * 86400000) AS INTEGER) AS duration_ms,\n SUM(CASE WHEN ordered.event_type = 'tool_decision' THEN 1 ELSE 0 END) AS tool_call_count,\n CAST(COALESCE(SUM(CASE\n WHEN ordered.event_type IN ('assistant_response', 'tool_decision') AND ordered.prev_type IN ('user_prompt', 'tool_result', 'tool_result_error')\n THEN ROUND((julianday(ordered.ts) - julianday(ordered.prev_ts)) * 86400000) END), 0) AS INTEGER) AS thinking_ms\n FROM ordered JOIN owners ON owners.session_id = ordered.session_id AND owners.turn_no = ordered.turn_no\n GROUP BY owners.prompt_id\n )\n UPDATE turn SET ended_at = agg.ended_at, duration_ms = COALESCE(agg.duration_ms, 0),\n tool_call_count = agg.tool_call_count, thinking_ms = COALESCE(agg.thinking_ms, 0)\n FROM agg WHERE turn.prompt_id = agg.prompt_id`;\n\n/** `turn_index` is insert-order at write time; recomputing it from `started_at` makes it a property of the corpus. */\nconst RENUMBER_TURNS = `\n WITH ordered AS (\n SELECT prompt_id, ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY started_at, prompt_id) - 1 AS idx\n FROM turn WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n )\n UPDATE turn SET turn_index = ordered.idx FROM ordered WHERE turn.prompt_id = ordered.prompt_id`;\n\n/** Recompute turn aggregates and the audit rollup for the given sessions in one transaction. */\nexport function rollupSessions(graph: SessionGraph, sessionIds: readonly string[]): number {\n const unique = [...new Set(sessionIds)];\n if (unique.length === 0) return 0;\n const aggregate = graph.db.prepare(ROLLUP);\n const renumber = graph.db.prepare(RENUMBER_TURNS);\n const audit = prepareAuditRollup(graph.db);\n return graph.db.transaction(() => {\n let updated = 0;\n for (let i = 0; i < unique.length; i += BATCH) {\n const batch = JSON.stringify(unique.slice(i, i + BATCH));\n renumber.run({ sessionIds: batch });\n updated += aggregate.run({ sessionIds: batch }).changes;\n audit(batch);\n }\n return updated;\n })();\n}\n\n/**\n * A merge sighting and the `pr-link` naming the same PR routinely live in different transcripts; fold at pass end.\n * Once the forge has checked a PR (`outcome_checked_at` set), its `merged_at` is forge-accurate and must not be\n * overwritten by a transcript sighting time on a later pass.\n */\nconst RECONCILE_PR_MERGES = `\n UPDATE pr SET state = 'merged',\n merged_at = (SELECT MIN(o.merged_at) FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))\n WHERE pr.outcome_checked_at IS NULL\n AND EXISTS (SELECT 1 FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))`;\n\n/** Both halves of a `gh pr create` make a real PR whether or not a `pr-link` ever mentioned it. */\nconst RECONCILE_PR_CREATES = `\n INSERT INTO pr (pr_ref, number, repo, title, url)\n SELECT 'pr:' || repo || '#' || number, number, repo, title, url FROM pr_create_observation\n WHERE number IS NOT NULL AND repo IS NOT NULL AND title IS NOT NULL\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(pr.title, excluded.title), url = COALESCE(pr.url, excluded.url)`;\n\n/**\n * A subagent ends when its child session's last line does, not when the\n * dispatch's tool_result returns (most dispatches are async and return at\n * launch). Parentage comes from the dispatch that ran inside a child transcript.\n */\nconst RECONCILE_SUBAGENTS = `\n UPDATE subagent SET\n ended_at = COALESCE((SELECT s.ended_at FROM session s WHERE s.session_id = subagent.child_session_id), ended_at),\n parent_agent_ref = COALESCE((SELECT p.agent_ref FROM subagent p WHERE p.child_session_id = subagent.session_id), parent_agent_ref)`;\n\nexport interface ReconcileCounts {\n prMerges: number;\n prCreates: number;\n subagents: number;\n}\n\n/** Whole-table reconciliations, run once at the end of a refresh pass after the rollup. */\nexport function reconcile(graph: SessionGraph): ReconcileCounts {\n return {\n prCreates: graph.db.prepare(RECONCILE_PR_CREATES).run().changes,\n prMerges: graph.db.prepare(RECONCILE_PR_MERGES).run().changes,\n subagents: graph.db.prepare(RECONCILE_SUBAGENTS).run().changes,\n };\n}\n","import { promises as fs } from \"node:fs\";\nimport { contentHash, resumePoint } from \"@titan-design/locator\";\nimport { TranscriptParseError, extractTranscript, type DiscoveredTranscript } from \"@titan-design/session-read\";\nimport { applyDelta } from \"./apply.js\";\nimport { advanceFacet, backfillFacets } from \"./facet.js\";\nimport { allSessionIds, type SessionGraph } from \"./graph.js\";\nimport { enrichPrs, type PrEnrichment, type PrResolver } from \"./outcomes.js\";\nimport { resolveOrigins, type OriginEnrichment, type OriginResolver } from \"./origin.js\";\nimport { purgeTranscript } from \"./purge.js\";\nimport { projectReviewRounds, type ReviewerProfilePredicate, type ReviewProjection } from \"./review-rounds.js\";\nimport { reconcile, rollupSessions, type ReconcileCounts } from \"./rollup.js\";\nimport { allTaskIds, enrichTasks, NO_ENRICHMENT, type TaskEnrichment, type TaskResolver } from \"./tasks.js\";\n\nexport interface IndexOptions {\n /** Re-hash the already-read prefix to catch same-length rewrites. O(file), so opt in. */\n verifyHash?: boolean;\n /** Store a whole-file content hash for durability reporting. O(file), so opt in. */\n withContentHash?: boolean;\n /** Fill task title, initiative and present status from the caller's store. Absent means transcripts alone. */\n resolveTasks?: TaskResolver;\n /** Record who launched each session. Runs once per `refreshCorpus` pass, never per transcript. Absent means no origin rows. */\n resolveOrigins?: OriginResolver;\n /** Fill PR state, merge, close and review rounds from the caller's forge. Runs once per `refreshCorpus` pass. Absent means transcripts alone. */\n resolvePrs?: PrResolver;\n /** Index prompts the host labels `promptSource: \"sdk\"`: spawned agents' briefs, and `claude -p` runs a person typed. Default false. */\n indexSdkPrompts?: boolean;\n}\n\nexport interface RefreshOptions extends IndexOptions {\n /** Which sender profiles' chat verdicts count toward review rounds. Defaults to `reviewer` and `*-reviewer`. */\n isReviewerProfile?: ReviewerProfilePredicate;\n /** Roll up every session, not just the ones this pass touched. */\n full?: boolean;\n /** Stale audit facets re-extracted per pass (default 40). `Infinity` clears the backlog. */\n facetLimit?: number;\n}\n\nexport type TranscriptOutcome =\n | { status: \"unchanged\" | \"indexed\" | \"rewound\"; transcriptId: number; sessionIds: string[]; facts: number; tasks: TaskEnrichment }\n | { status: \"missing\" | \"quarantined\"; transcriptId: number; sessionIds: string[]; facts: 0; tasks: TaskEnrichment; reason: string };\n\n/**\n * Bring one transcript up to date: read from its watermark (or byte 0 after a\n * rewrite), apply the delta, advance the watermark. A malformed line\n * quarantines the transcript instead of poisoning the pass.\n */\nexport async function indexTranscript(graph: SessionGraph, transcript: DiscoveredTranscript, options: IndexOptions = {}): Promise<TranscriptOutcome> {\n const row = graph.transcripts.ensure(transcript.displayPath);\n const entry = { path: transcript.displayPath, lastByteOffset: row.lastOffset, prefixHash: row.prefixHash, mtime: row.fileMtime };\n const point = await resumePoint(entry, transcript.absolutePath, { verifyHash: options.verifyHash });\n const base = { transcriptId: row.sourceId, sessionIds: [] as string[], tasks: NO_ENRICHMENT };\n if (point.state === \"missing\") {\n graph.transcripts.markStatus(transcript.displayPath, \"missing\", \"source file no longer exists\");\n return { ...base, status: \"missing\", facts: 0, reason: \"source file no longer exists\" };\n }\n if (point.state === \"unchanged\") {\n // A file that vanished and came back unchanged resumes at EOF, so there are no\n // bytes to read and `advance()` — the only writer of status 'ok' — never runs,\n // stranding the row on 'missing' forever. Clear it here rather than forcing a\n // re-read, which would rewrite the prefix hash from a zero-byte delta.\n // Quarantine is deliberately not cleared: the same bytes still will not parse.\n if (row.status === \"missing\") graph.transcripts.markStatus(transcript.displayPath, \"ok\", null);\n return { ...base, status: \"unchanged\", facts: 0 };\n }\n\n try {\n const delta = await extractTranscript(transcript.absolutePath, {\n fromByteOffset: point.start,\n priorPrefixHash: point.state === \"rewritten\" ? null : row.prefixHash,\n subagentId: transcript.subagentId,\n });\n // Two ways to learn a transcript was rewritten, and both must purge. `resumePoint`\n // catches a file that shrank; `extractTranscript` re-hashes the prefix it was asked\n // to skip and restarts from zero on its own when the bytes moved — a rotation that\n // left the file the same length or longer. Ignoring its answer replays the whole\n // file onto rows that were never removed, which is the accumulation purge exists\n // to prevent, reached by a different road.\n const rewound = point.state === \"rewritten\" || delta.restartedFromZero;\n // After extraction, never before: a parse failure quarantines the transcript,\n // and purging first would destroy rows we could then no longer rebuild.\n if (rewound) purgeTranscript(graph, row.sourceId);\n applyDelta(graph, row.sourceId, delta, { account: transcript.account, indexSdkPrompts: options.indexSdkPrompts });\n advanceFacet(graph.db, row.sourceId, rewound ? 0 : point.start, delta.lastByteOffset);\n const stat = await fs.stat(transcript.absolutePath);\n graph.transcripts.advance(transcript.displayPath, {\n lastOffset: delta.lastByteOffset,\n prefixHash: delta.prefixHash,\n fileSize: stat.size,\n fileMtime: stat.mtime.toISOString(),\n contentHash: options.withContentHash ? await contentHash(transcript.absolutePath) : null,\n });\n const sessionIds = delta.sessions.map((s) => s.sessionId);\n const taskIds = delta.tasks.map((t) => t.taskId);\n const tasks = taskIds.length > 0 ? await enrichTasks(graph, options.resolveTasks, taskIds) : NO_ENRICHMENT;\n return { ...base, sessionIds, tasks, status: rewound ? \"rewound\" : \"indexed\", facts: delta.facts.length };\n } catch (err) {\n if (!(err instanceof TranscriptParseError)) throw err;\n graph.transcripts.markStatus(transcript.displayPath, \"quarantined\", err.message);\n return { ...base, status: \"quarantined\", facts: 0, reason: err.message };\n }\n}\n\nexport interface RefreshSummary {\n transcripts: number;\n indexed: number;\n unchanged: number;\n rewound: number;\n missing: number;\n quarantined: number;\n facts: number;\n turnsRolledUp: number;\n reconciled: ReconcileCounts;\n tasks: TaskEnrichment;\n origins: OriginEnrichment;\n prs: PrEnrichment;\n reviews: ReviewProjection;\n markedMissing: number;\n facetsBackfilled: number;\n /** Transcripts whose audit facet is still stale after this pass. */\n facetBacklog: number;\n}\n\n/**\n * One pass over a corpus: index every transcript, re-extract a bounded batch of\n * stale audit facets, roll up the sessions that changed, resolve session\n * origins, reconcile cross-transcript observations, resolve PR outcomes,\n * project review rounds, enrich tasks, and mark rows whose source file is gone. Idempotent: a second pass over unchanged\n * files changes nothing.\n *\n * The resolver is hoisted out of the per-transcript loop and run once over the\n * whole task table, so a corpus of N transcripts costs one resolver call rather\n * than N, and a task whose store row changed refreshes even when no transcript\n * did. That bounds staleness to one pass.\n */\nexport async function refreshCorpus(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], options: RefreshOptions = {}): Promise<RefreshSummary> {\n const { resolveTasks, resolveOrigins: originResolver, resolvePrs, isReviewerProfile, full, facetLimit, ...perTranscript } = options;\n const counts = { indexed: 0, unchanged: 0, rewound: 0, missing: 0, quarantined: 0 };\n const touched: string[] = [];\n let facts = 0;\n for (const transcript of transcripts) {\n const outcome = await indexTranscript(graph, transcript, perTranscript);\n counts[outcome.status] += 1;\n facts += outcome.facts;\n touched.push(...outcome.sessionIds);\n }\n const facets = await backfillFacets(graph, transcripts, { limit: facetLimit });\n touched.push(...facets.sessionIds);\n const turnsRolledUp = rollupSessions(graph, full ? allSessionIds(graph) : touched);\n // Before reconcile, so a worker's subagent row gets its ended_at in the same pass.\n const origins = await resolveOrigins(graph, originResolver);\n const reconciled = reconcile(graph);\n // After reconcile, so a merge a transcript witnessed is already sticky when the forge answers.\n const prs = await enrichPrs(graph, resolvePrs);\n const reviews = projectReviewRounds(graph, { isReviewerProfile });\n const tasks = await enrichTasks(graph, resolveTasks, allTaskIds(graph));\n const markedMissing = await markMissing(graph, transcripts);\n const facetSummary = { facetsBackfilled: facets.backfilled, facetBacklog: facets.backlog };\n return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, origins, prs, reviews, markedMissing, ...facetSummary };\n}\n\n/** Rows absent from discovery are only nominated; an `fs.stat` decides, so an empty scan cannot condemn the corpus. */\nasync function markMissing(graph: SessionGraph, discovered: readonly DiscoveredTranscript[]): Promise<number> {\n const present = new Set(discovered.map((t) => t.displayPath));\n const byPath = new Map(discovered.map((t) => [t.displayPath, t.absolutePath]));\n let marked = 0;\n for (const row of graph.transcripts.list()) {\n if (row.status === \"missing\" || present.has(row.sourceKey)) continue;\n const normalized = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n const absolute = normalized ? (JSON.parse(normalized.descriptor) as { path: string }).path : byPath.get(row.sourceKey) ?? row.sourceKey;\n if (await exists(absolute)) continue;\n graph.transcripts.markStatus(row.sourceKey, \"missing\", \"source file no longer exists\");\n marked += 1;\n }\n return marked;\n}\n\nasync function exists(absolutePath: string): Promise<boolean> {\n try {\n await fs.stat(absolutePath);\n return true;\n } catch {\n return false;\n }\n}\n","import { RELATIONS } from \"@titan-design/session-read\";\nimport { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { KIT } from \"./schema.js\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\n/** Decides from a sender's `session_origin.profile` whether its chat verdicts count. */\nexport type ReviewerProfilePredicate = (profile: string) => boolean;\n\n/** The default role set: `reviewer`, or any profile ending in `-reviewer`. */\nexport const isReviewerProfile: ReviewerProfilePredicate = (profile) => profile === \"reviewer\" || profile.endsWith(\"-reviewer\");\n\nexport interface ReviewRoundOptions {\n isReviewerProfile?: ReviewerProfilePredicate;\n}\n\nexport interface ReviewProjection {\n /** Chat verdicts given a `pr_ref` this pass. */\n resolved: number;\n /** Chat verdicts whose PR is still unknown. */\n unresolved: number;\n /** Reviews ignored for a time that does not parse, plus PRs whose commit times were unusable. */\n invalidTimes: number;\n}\n\n/**\n * The number of distinct heads among changes-requested reviews that a later commit answered.\n * A review's head is the count of commits at or before it, so two reviews on one head count once.\n * A review whose time does not parse is ignored. Returns null when any commit time does not\n * parse, because a missing commit would shift every later head.\n */\nexport function countRounds(changesRequestedAt: readonly string[], commitTimes: readonly string[]): number | null {\n const commits = parseCommitTimes(commitTimes);\n return commits ? answeredHeads(changesRequestedAt, commits).heads.size : null;\n}\n\nfunction parseCommitTimes(commitTimes: readonly string[]): number[] | null {\n const commits = commitTimes.map(Date.parse);\n return commits.some(Number.isNaN) ? null : commits;\n}\n\nfunction answeredHeads(reviewTimes: readonly string[], commits: readonly number[]): { heads: Set<number>; ignored: number } {\n const heads = new Set<number>();\n let ignored = 0;\n for (const ts of reviewTimes) {\n const at = Date.parse(ts);\n if (Number.isNaN(at)) {\n ignored += 1;\n continue;\n }\n const head = commits.filter((c) => c <= at).length;\n if (head < commits.length) heads.add(head);\n }\n return { heads, ignored };\n}\n\n/**\n * Resolve each unresolved chat verdict to a PR, then write `review_rounds_chat` and `review_rounds`\n * for every PR. Runs after `enrichPrs`, so forge reviews and commit times are current.\n */\nexport function projectReviewRounds(graph: SessionGraph, options: ReviewRoundOptions = {}): ReviewProjection {\n const isReviewer = options.isReviewerProfile ?? isReviewerProfile;\n return graph.db.transaction(() => {\n const resolved = resolveChatVerdicts(graph.db);\n const invalidTimes = writeRounds(graph.db, isReviewer);\n const { n } = graph.db.prepare(`SELECT count(*) AS n FROM ${REVIEW_TABLE} WHERE surface = 'chat' AND pr_ref IS NULL`).get() as { n: number };\n return { resolved, unresolved: n, invalidTimes };\n })();\n}\n\ninterface ChatVerdictRow {\n source_key: string;\n session_id: string | null;\n repo: string | null;\n repo_hint: string | null;\n cwd_repo: string | null;\n number: number;\n}\n\ninterface Candidate {\n pr_ref: string;\n repo: string;\n}\n\nfunction resolveChatVerdicts(db: Db): number {\n const rows = db.prepare(`SELECT source_key, session_id, repo, repo_hint, cwd_repo, number FROM ${REVIEW_TABLE} WHERE surface = 'chat' AND pr_ref IS NULL`).all() as ChatVerdictRow[];\n const update = db.prepare(`UPDATE ${REVIEW_TABLE} SET pr_ref = ? WHERE source_key = ?`);\n let resolved = 0;\n for (const row of rows) {\n const prRef = resolvePrRef(db, row);\n if (prRef) resolved += update.run(prRef, row.source_key).changes;\n }\n return resolved;\n}\n\n/** An exact repo must exist; a hint that matches nothing falls through to the family links and then the sender's repo. */\nfunction resolvePrRef(db: Db, row: ChatVerdictRow): string | null {\n const candidates = db.prepare(\"SELECT pr_ref, repo FROM pr WHERE number = ? AND repo IS NOT NULL\").all(row.number) as Candidate[];\n if (row.repo) return candidates.find((c) => c.repo === row.repo)?.pr_ref ?? null;\n const hinted = candidates.filter((c) => sameRepoName(c.repo, row.repo_hint));\n if (hinted.length === 1) return hinted[0]!.pr_ref;\n const pool = hinted.length > 0 ? hinted : candidates;\n const linked = familyLinks(db, row.session_id);\n const bySibling = pool.filter((c) => linked.has(c.pr_ref));\n if (bySibling.length === 1) return bySibling[0]!.pr_ref;\n const byCwd = (bySibling.length > 0 ? bySibling : pool).filter((c) => sameRepoName(c.repo, row.cwd_repo));\n return byCwd.length === 1 ? byCwd[0]!.pr_ref : null;\n}\n\n/** Compares a bare repo name with the last path segment of an `owner/name` repo. */\nfunction sameRepoName(repo: string, name: string | null): boolean {\n return name !== null && repo.split(\"/\").pop()!.toLowerCase() === name.toLowerCase();\n}\n\n/** The sender, its parent, and every session that parent spawned, whether the spawn is known from an origin or a transcript. */\nconst FAMILY_LINKS = `\n WITH parent(id) AS (\n SELECT parent_session_id FROM session_origin WHERE session_id = @sessionId AND parent_session_id IS NOT NULL\n UNION SELECT session_id FROM subagent WHERE child_session_id = @sessionId AND session_id IS NOT NULL\n ), family(id) AS (\n SELECT @sessionId UNION SELECT id FROM parent\n UNION SELECT session_id FROM session_origin WHERE parent_session_id IN (SELECT id FROM parent)\n UNION SELECT child_session_id FROM subagent WHERE session_id IN (SELECT id FROM parent) AND child_session_id IS NOT NULL\n UNION SELECT substr(target_ref, 9) FROM \"${KIT.edge}\" WHERE relation = '${RELATIONS.SPAWNED}' AND t_expired IS NULL\n AND target_ref LIKE 'session:%' AND source_ref IN (SELECT 'session:' || id FROM parent)\n )\n SELECT DISTINCT target_ref FROM \"${KIT.edge}\" WHERE relation = '${RELATIONS.LINKED}' AND t_expired IS NULL\n AND source_ref IN (SELECT 'session:' || id FROM family)`;\n\nfunction familyLinks(db: Db, sessionId: string | null): Set<string> {\n if (!sessionId) return new Set();\n const rows = db.prepare(FAMILY_LINKS).all({ sessionId }) as { target_ref: string }[];\n return new Set(rows.map((r) => r.target_ref));\n}\n\ninterface PrRoundRow {\n pr_ref: string;\n commit_times: string | null;\n review_rounds_gh: number | null;\n}\n\ninterface ChangesRequested {\n pr_ref: string;\n surface: string;\n ts: string;\n profile: string | null;\n}\n\nconst CHANGES_REQUESTED = `\n SELECT r.pr_ref, r.surface, r.ts, o.profile FROM ${REVIEW_TABLE} r LEFT JOIN session_origin o ON o.session_id = r.session_id\n WHERE r.verdict = 'changes_requested' AND r.pr_ref IS NOT NULL`;\n\n/** Returns the count of unusable times met, for the pass summary. */\nfunction writeRounds(db: Db, isReviewer: ReviewerProfilePredicate): number {\n const byPr = new Map<string, ChangesRequested[]>();\n for (const review of db.prepare(CHANGES_REQUESTED).all() as ChangesRequested[]) {\n if (review.surface === \"chat\" && !(review.profile && isReviewer(review.profile))) continue;\n byPr.set(review.pr_ref, [...(byPr.get(review.pr_ref) ?? []), review]);\n }\n const update = db.prepare(\"UPDATE pr SET review_rounds_chat = ?, review_rounds = ? WHERE pr_ref = ?\");\n let invalidTimes = 0;\n for (const pr of db.prepare(\"SELECT pr_ref, commit_times, review_rounds_gh FROM pr\").all() as PrRoundRow[]) {\n const { chat, total, invalid } = roundsFor(pr, byPr.get(pr.pr_ref) ?? []);\n update.run(chat, total, pr.pr_ref);\n invalidTimes += invalid;\n }\n return invalidTimes;\n}\n\n/**\n * Chat heads join the forge count only where no forge review already sits on that head,\n * so a resolver that sends only a count still adds to the chat rounds unchanged.\n */\nfunction roundsFor(pr: PrRoundRow, reviews: readonly ChangesRequested[]): { chat: number | null; total: number | null; invalid: number } {\n const commits = pr.commit_times === null ? null : parseCommitTimes(JSON.parse(pr.commit_times) as string[]);\n if (!commits) return { chat: null, total: pr.review_rounds_gh, invalid: pr.commit_times === null ? 0 : 1 };\n const timesOn = (surface: string) => reviews.filter((r) => r.surface === surface).map((r) => r.ts);\n const forge = answeredHeads(timesOn(\"github\"), commits);\n const chat = answeredHeads(timesOn(\"chat\"), commits);\n const chatOnly = [...chat.heads].filter((head) => !forge.heads.has(head)).length;\n return { chat: chat.heads.size, total: (pr.review_rounds_gh ?? 0) + chatOnly, invalid: forge.ignored + chat.ignored };\n}\n","import { REVIEW_TABLE } from \"./audit-schema-v8.js\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { countRounds } from \"./review-rounds.js\";\n\n/** A PR the graph holds, as the resolver needs it to ask a forge. */\nexport interface PrKey {\n prRef: string;\n repo: string;\n number: number;\n}\n\n/**\n * What a forge knows and a transcript cannot state: a merge done by another\n * session or in the browser, a close, and the review history. Every field is\n * optional; an omitted or null field leaves whatever the transcripts derived.\n */\nexport interface ResolvedPr {\n /** `open`, `closed` or `merged`, in any case. */\n state?: string | null;\n mergedAt?: string | null;\n closedAt?: string | null;\n /** Stored as the forge's round count when `reviews` or `commitTimes` is absent; otherwise the round rule counts it. */\n reviewRounds?: number | null;\n /** The PR's commit times; an empty array is stored as known-empty, an omitted field leaves the stored value. */\n commitTimes?: readonly string[];\n /** Replaces the PR's stored forge reviews; an omitted field leaves them. */\n reviews?: readonly ResolvedReview[];\n}\n\n/** One forge review. `APPROVED` and `CHANGES_REQUESTED`, in any case, are stored; other states are ignored. */\nexport interface ResolvedReview {\n state: string;\n submittedAt: string;\n}\n\n/** Resolutions keyed by `pr_ref` (`pr:acme/demo#7`). */\nexport type PrResolution = ReadonlyMap<string, ResolvedPr | null | undefined>;\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn which forge a product talks to. Called once per pass with every\n * PR whose outcome may still change.\n */\nexport type PrResolver = (prs: readonly PrKey[]) => PromiseLike<PrResolution> | PrResolution;\n\nexport interface PrEnrichment {\n /** PRs handed to the resolver. */\n requested: number;\n /** PR rows the resolver updated. */\n applied: number;\n /** The resolver threw; rows stand as the transcripts left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_PR_OUTCOMES: PrEnrichment = Object.freeze({ requested: 0, applied: 0, failed: false });\n\n/**\n * A merge is final, so a merged PR is asked about once commit times are known; open and closed PRs\n * can still move. Never-checked PRs lead and missing commit times trail, so a per-pass cap cannot starve open PRs.\n */\nconst PRS_NEEDING_OUTCOME = `\n SELECT pr_ref, repo, number FROM pr\n WHERE repo IS NOT NULL AND number IS NOT NULL\n AND (outcome_checked_at IS NULL OR state IS NOT 'merged' OR commit_times IS NULL)\n ORDER BY CASE WHEN outcome_checked_at IS NULL THEN 0 WHEN state IS NOT 'merged' THEN 1 ELSE 2 END, pr_ref`;\n\n/** A stale forge cache must not reopen a PR a transcript saw merged, so `merged` is sticky. */\nconst APPLY_OUTCOME = `\n UPDATE pr SET\n state = CASE WHEN state = 'merged' THEN state ELSE COALESCE(lower(@state), state) END,\n merged_at = COALESCE(@mergedAt, merged_at),\n closed_at = COALESCE(@closedAt, closed_at),\n review_rounds = COALESCE(@reviewRounds, review_rounds),\n review_rounds_gh = COALESCE(@reviewRounds, review_rounds_gh),\n commit_times = COALESCE(@commitTimes, commit_times),\n outcome_checked_at = @checkedAt\n WHERE pr_ref = @prRef`;\n\nexport function prsNeedingOutcome(graph: SessionGraph): PrKey[] {\n const rows = graph.db.prepare(PRS_NEEDING_OUTCOME).all() as { pr_ref: string; repo: string; number: number }[];\n return rows.map((r) => ({ prRef: r.pr_ref, repo: r.repo, number: r.number }));\n}\n\n/**\n * Fill PR outcomes from a caller-supplied resolver. Runs after `reconcile`, so\n * it sees the merges the transcripts witnessed and only adds what they missed.\n * Updates rows and never inserts one: a PR enters the graph from a transcript.\n * A resolver that throws costs this pass its outcomes and nothing else.\n */\nexport async function enrichPrs(graph: SessionGraph, resolver: PrResolver | undefined): Promise<PrEnrichment> {\n if (!resolver) return NO_PR_OUTCOMES;\n const prs = prsNeedingOutcome(graph);\n if (prs.length === 0) return NO_PR_OUTCOMES;\n try {\n const resolved = await resolver(prs);\n return { requested: prs.length, applied: write(graph, resolved, new Date().toISOString()), failed: false };\n } catch (err) {\n return { requested: prs.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction write(graph: SessionGraph, resolved: PrResolution, checkedAt: string): number {\n const apply = graph.db.prepare(APPLY_OUTCOME);\n const stored = graph.db.prepare(\"SELECT commit_times FROM pr WHERE pr_ref = ?\");\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [prRef, fields] of resolved) {\n if (!fields) continue;\n applied += apply.run({\n prRef,\n checkedAt,\n state: fields.state ?? null,\n mergedAt: fields.mergedAt ?? null,\n closedAt: fields.closedAt ?? null,\n reviewRounds: forgeRounds(fields, () => storedCommitTimes(stored.get(prRef))),\n commitTimes: fields.commitTimes ? JSON.stringify(fields.commitTimes) : null,\n }).changes;\n if (fields.reviews) replaceForgeReviews(graph, prRef, fields.reviews);\n }\n return applied;\n })();\n}\n\nconst isChangesRequested = (review: ResolvedReview) => review.state.toUpperCase() === \"CHANGES_REQUESTED\";\n\n/** Counts against sent commit times, else stored ones; unusable or absent times leave `reviewRounds` as sent. */\nfunction forgeRounds(fields: ResolvedPr, storedTimes: () => string[] | null): number | null {\n if (!fields.reviews) return fields.reviewRounds ?? null;\n const commitTimes = fields.commitTimes ?? storedTimes();\n const counted = commitTimes ? countRounds(fields.reviews.filter(isChangesRequested).map((r) => r.submittedAt), commitTimes) : null;\n return counted ?? fields.reviewRounds ?? null;\n}\n\nfunction storedCommitTimes(row: unknown): string[] | null {\n const times = (row as { commit_times: string | null } | undefined)?.commit_times;\n return times ? (JSON.parse(times) as string[]) : null;\n}\n\nconst FORGE_VERDICTS: Record<string, string> = { APPROVED: \"approve\", CHANGES_REQUESTED: \"changes_requested\" };\n\n/** Two reviews in the same second share a key; a changes-requested one is kept over an approval. */\nconst INSERT_FORGE_REVIEW = `\n INSERT INTO ${REVIEW_TABLE} (source_key, surface, verdict, ts, repo, number, pr_ref)\n SELECT 'gh:' || pr_ref || ':' || @ts, 'github', @verdict, @ts, repo, number, pr_ref FROM pr WHERE pr_ref = @prRef\n ON CONFLICT (source_key) DO UPDATE SET verdict = CASE WHEN verdict = 'changes_requested' THEN verdict ELSE excluded.verdict END`;\n\nfunction replaceForgeReviews(graph: SessionGraph, prRef: string, reviews: readonly ResolvedReview[]): void {\n graph.db.prepare(`DELETE FROM ${REVIEW_TABLE} WHERE surface = 'github' AND pr_ref = ?`).run(prRef);\n const insert = graph.db.prepare(INSERT_FORGE_REVIEW);\n for (const review of reviews) {\n const verdict = FORGE_VERDICTS[review.state.toUpperCase()];\n if (verdict) insert.run({ prRef, verdict, ts: review.submittedAt });\n }\n}\n","import { RELATIONS, sessionRef, taskRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * Who started a session and why, as a launcher such as agent-chat recorded it.\n * A transcript cannot state this: a `claude -p` worker and a headless miner look\n * the same from inside. Every field but `originSystem` is optional.\n */\nexport interface ResolvedOrigin {\n originSystem: string;\n agentId?: string | null;\n agentName?: string | null;\n parentName?: string | null;\n parentSessionId?: string | null;\n profile?: string | null;\n modelAlias?: string | null;\n surface?: string | null;\n isolation?: string | null;\n depth?: number | null;\n /** `spawned`, `adopted`, `inherited` or `human`. */\n originKind?: string | null;\n configDir?: string | null;\n spawnCwd?: string | null;\n spawnedAt?: string | null;\n briefChars?: number | null;\n briefExcerpt?: string | null;\n briefPath?: string | null;\n launchArgs?: string | null;\n /**\n * Task ids the spawn record assigned, primary first; each projects a `ran` edge. Leave it\n * undefined only if the resolver does not look for tasks: a stored link then stands and a\n * row without one is offered again. A task-aware resolver sets it, empty when nothing links.\n */\n taskIds?: readonly string[] | null;\n /** How the ids were found. Stored as `none` when `taskIds` is given but empty. */\n taskSource?: TaskLinkSource | null;\n}\n\n/** Stored when a task-aware resolver found no id, so the row is not offered again. */\nexport const NO_TASK_LINK = \"none\";\n\nexport type TaskLinkSource = \"name\" | \"name-over-brief\" | \"brief-anchor\" | \"brief-paragraph\" | typeof NO_TASK_LINK;\n\n/** A lifecycle event the launcher saw outside the transcript: `teleport`, `handoff`, `retired`, `exited`. */\nexport interface ExternalEvent {\n sessionId: string;\n ts: string;\n kind: string;\n detail?: string | null;\n /** Defaults to the session's resolved origin system. */\n originSystem?: string | null;\n}\n\nexport interface OriginResolution {\n /** Keyed by session id. */\n origins: Record<string, ResolvedOrigin>;\n externalEvents?: readonly ExternalEvent[];\n}\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn where a launcher keeps its records. Called once per pass with\n * every session that has no origin row, a stale one, or a null `task_source`.\n * A task-aware resolver returns an entry for every requested session it examined,\n * with `taskIds` empty when nothing links; a session left out is offered again.\n */\nexport type OriginResolver = (sessionIds: readonly string[]) => PromiseLike<OriginResolution> | OriginResolution;\n\nexport interface OriginEnrichment {\n /** Session ids handed to the resolver. */\n requested: number;\n /** `session_origin` rows the resolver wrote. */\n applied: number;\n /** `session_external_event` rows the resolver wrote. */\n events: number;\n /** The resolver threw; origin rows stand as the last pass left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_ORIGINS: OriginEnrichment = Object.freeze({ requested: 0, applied: 0, events: 0, failed: false });\n\n/**\n * A session whose transcript grew after its origin was resolved may have been retired or handed off since.\n * A null `task_source` means no task-aware resolver has answered for the row yet.\n */\nconst SESSIONS_NEEDING_ORIGIN = `\n SELECT s.session_id FROM session s LEFT JOIN session_origin o ON o.session_id = s.session_id\n WHERE o.session_id IS NULL OR o.resolved_at < s.ended_at OR o.task_source IS NULL\n ORDER BY s.session_id`;\n\nconst ORIGIN_COLUMNS = [\n \"origin_system\", \"agent_id\", \"agent_name\", \"parent_name\", \"parent_session_id\", \"profile\", \"model_alias\", \"surface\", \"isolation\", \"depth\",\n \"origin_kind\", \"config_dir\", \"spawn_cwd\", \"spawned_at\", \"brief_chars\", \"brief_excerpt\", \"brief_path\", \"launch_args\", \"resolved_at\",\n] as const;\n\n/** A null `task_source` means the resolver did not look for tasks, so the stored link stands; `none` clears it. */\nconst TASK_LINK_UPDATE = `task_source = COALESCE(excluded.task_source, task_source),\n task_ids = CASE WHEN excluded.task_source IS NOT NULL THEN excluded.task_ids ELSE task_ids END`;\n\n/** An update, not a replace, so a column this writer does not name keeps its value. */\nconst UPSERT_ORIGIN = `\n INSERT INTO session_origin (session_id, origin_system, agent_id, agent_name, parent_name, parent_session_id,\n profile, model_alias, surface, isolation, depth, origin_kind, config_dir, spawn_cwd, spawned_at,\n brief_chars, brief_excerpt, brief_path, launch_args, task_ids, task_source, resolved_at)\n VALUES (@sessionId, @originSystem, @agentId, @agentName, @parentName, @parentSessionId,\n @profile, @modelAlias, @surface, @isolation, @depth, @originKind, @configDir, @spawnCwd, @spawnedAt,\n @briefChars, @briefExcerpt, @briefPath, @launchArgs, @taskIds, @taskSource, @resolvedAt)\n ON CONFLICT (session_id) DO UPDATE SET ${ORIGIN_COLUMNS.map((c) => `${c} = excluded.${c}`).join(\", \")},\n ${TASK_LINK_UPDATE}`;\n\nconst UPSERT_EVENT = `\n INSERT INTO session_external_event (session_id, ts, origin_system, kind, detail)\n VALUES (@sessionId, @ts, COALESCE(@originSystem, (SELECT origin_system FROM session_origin WHERE session_id = @sessionId), 'unknown'), @kind, @detail)\n ON CONFLICT (session_id, ts, kind) DO UPDATE SET origin_system = excluded.origin_system, detail = excluded.detail`;\n\n/** Existing subagent queries then cover launcher-spawned workers unchanged; `reconcile` fills `ended_at` from the child session. */\nconst PROJECT_SUBAGENTS = `\n INSERT INTO subagent (agent_ref, session_id, child_session_id, agent_type, label, started_at)\n SELECT 'agent:' || origin_system || ':' || agent_id, parent_session_id, session_id, profile, agent_name, spawned_at\n FROM session_origin WHERE parent_session_id IS NOT NULL AND agent_id IS NOT NULL\n ON CONFLICT (agent_ref) DO UPDATE SET session_id = excluded.session_id, child_session_id = excluded.child_session_id,\n agent_type = excluded.agent_type, label = excluded.label, started_at = COALESCE(excluded.started_at, started_at)`;\n\nexport function sessionsNeedingOrigin(graph: SessionGraph): string[] {\n return (graph.db.prepare(SESSIONS_NEEDING_ORIGIN).all() as { session_id: string }[]).map((r) => r.session_id);\n}\n\n/**\n * Resolve origins for the sessions that lack a current one, then project every\n * stored origin into `spawned` edges and `subagent` rows. Projecting from the\n * table, not from this pass's answer, restores rows a parent transcript's purge\n * removed. A resolver that throws costs this pass its origins and nothing else.\n */\nexport async function resolveOrigins(graph: SessionGraph, resolver: OriginResolver | undefined): Promise<OriginEnrichment> {\n if (!resolver) return NO_ORIGINS;\n const sessionIds = sessionsNeedingOrigin(graph);\n let resolved: readonly string[] = [];\n try {\n const resolution = await resolver(sessionIds);\n const written = writeResolution(graph, resolution, new Date().toISOString());\n resolved = Object.keys(resolution.origins);\n return { requested: sessionIds.length, ...written, failed: false };\n } catch (err) {\n return { requested: sessionIds.length, applied: 0, events: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n } finally {\n projectOrigins(graph, resolved);\n }\n}\n\nfunction writeResolution(graph: SessionGraph, resolution: OriginResolution, resolvedAt: string): { applied: number; events: number } {\n const upsertOrigin = graph.db.prepare(UPSERT_ORIGIN);\n const upsertEvent = graph.db.prepare(UPSERT_EVENT);\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [sessionId, origin] of Object.entries(resolution.origins)) {\n applied += upsertOrigin.run(originRow(sessionId, origin, resolvedAt)).changes;\n }\n let events = 0;\n for (const e of resolution.externalEvents ?? []) {\n events += upsertEvent.run({ sessionId: e.sessionId, ts: e.ts, kind: e.kind, detail: e.detail ?? null, originSystem: e.originSystem ?? null }).changes;\n }\n return { applied, events };\n })();\n}\n\nfunction originRow(sessionId: string, o: ResolvedOrigin, resolvedAt: string): Record<string, string | number | null> {\n return {\n sessionId, originSystem: o.originSystem, resolvedAt,\n agentId: o.agentId ?? null, agentName: o.agentName ?? null, parentName: o.parentName ?? null, parentSessionId: o.parentSessionId ?? null,\n profile: o.profile ?? null, modelAlias: o.modelAlias ?? null, surface: o.surface ?? null, isolation: o.isolation ?? null,\n depth: o.depth ?? null, originKind: o.originKind ?? null, configDir: o.configDir ?? null, spawnCwd: o.spawnCwd ?? null,\n spawnedAt: o.spawnedAt ?? null, briefChars: o.briefChars ?? null, briefExcerpt: o.briefExcerpt ?? null,\n briefPath: o.briefPath ?? null, launchArgs: o.launchArgs ?? null,\n ...taskLinkColumns(o),\n };\n}\n\n/** `undefined` ids mean a resolver that predates task links; null or empty ids mean it looked and found none. */\nfunction taskLinkColumns(o: ResolvedOrigin): { taskIds: string | null; taskSource: string | null } {\n if (o.taskIds === undefined) return { taskIds: null, taskSource: null };\n if (!o.taskIds?.length) return { taskIds: null, taskSource: NO_TASK_LINK };\n return { taskIds: JSON.stringify(o.taskIds), taskSource: o.taskSource ?? NO_TASK_LINK };\n}\n\nfunction projectOrigins(graph: SessionGraph, resolved: readonly string[]): void {\n 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() as {\n session_id: string; parent_session_id: string; spawned_at: string | null; resolved_at: string;\n }[];\n graph.db.transaction(() => {\n graph.db.prepare(PROJECT_SUBAGENTS).run();\n for (const o of spawned) {\n graph.edges.assert({ sourceRef: sessionRef(o.parent_session_id), relation: RELATIONS.SPAWNED, targetRef: sessionRef(o.session_id), tValid: o.spawned_at ?? o.resolved_at });\n }\n projectTaskLinks(graph, resolved);\n })();\n}\n\n/** A launcher's name is the coordinator's own choice, so it outranks anything read from a brief. */\nconst LINK_CONFIDENCE: Record<string, number> = { name: 1, \"name-over-brief\": 1, \"brief-anchor\": 0.9, \"brief-paragraph\": 0.6 };\nconst LOWEST_CONFIDENCE = 0.6;\n\ninterface TaskLinkRow { session_id: string; task_ids: string | null; task_source: string | null; spawned_at: string | null; resolved_at: string }\n\n/**\n * Assert `session ran task` for every id a spawn record assigned, with a task row so\n * the same pass's `enrichTasks` fills its initiative. Only edges this projection made\n * (`attrs.via = 'origin'`) are expired; a transcript's `ran` edge carries no `via`.\n * Only this pass's rows are read: task links change only when a row is re-resolved.\n */\nfunction projectTaskLinks(graph: SessionGraph, resolved: readonly string[]): void {\n const insertTask = graph.db.prepare(\"INSERT OR IGNORE INTO task (task_ref, task_id) VALUES (?, ?)\");\n const select = graph.db.prepare(\"SELECT session_id, task_ids, task_source, spawned_at, resolved_at FROM session_origin WHERE session_id = ?\");\n for (const sessionId of resolved) {\n const o = select.get(sessionId) as TaskLinkRow | undefined;\n if (!o) continue;\n const taskIds: string[] = o.task_ids ? (JSON.parse(o.task_ids) as string[]) : [];\n const source = sessionRef(o.session_id);\n const attrs = { via: \"origin\", source: o.task_source };\n const confidence = LINK_CONFIDENCE[o.task_source ?? \"\"] ?? LOWEST_CONFIDENCE;\n for (const taskId of taskIds) {\n insertTask.run(taskRef(taskId), taskId);\n graph.edges.assert({ sourceRef: source, relation: RELATIONS.RAN, targetRef: taskRef(taskId), tValid: o.spawned_at ?? o.resolved_at, attrs, confidence });\n }\n expireStaleTaskLinks(graph, source, new Set(taskIds.map(taskRef)));\n }\n}\n\nfunction expireStaleTaskLinks(graph: SessionGraph, source: string, linked: ReadonlySet<string>): void {\n for (const edge of graph.edges.from(source)) {\n if (edge.relation === RELATIONS.RAN && edge.attrs?.via === \"origin\" && !linked.has(edge.targetRef)) {\n graph.edges.expire(source, RELATIONS.RAN, edge.targetRef);\n }\n }\n}\n","import { taskRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * What a product's task store knows and a transcript cannot state: the task's\n * present title, its initiative, and the status it holds right now rather than\n * the one some command was observed to set. Every field is optional; an omitted\n * or null field leaves whatever the transcripts derived in place.\n */\nexport interface ResolvedTask {\n initiative?: string | null;\n title?: string | null;\n status?: string | null;\n /** The store's size estimate for the task, in whatever unit it keeps. */\n estimate?: number | null;\n}\n\n/** Resolutions keyed by bare task id (`AW-23`, not `task:AW-23`). */\nexport type TaskResolution = ReadonlyMap<string, ResolvedTask | null | undefined>;\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn where any product keeps its tasks. Called once per pass with\n * every task id at once, because a real resolver reads a store. Returning an id\n * that no transcript mentioned inserts that task; that is how a task nobody\n * worked on gets into the graph.\n */\nexport type TaskResolver = (taskIds: readonly string[]) => PromiseLike<TaskResolution> | TaskResolution;\n\nexport interface TaskEnrichment {\n /** Task ids handed to the resolver. */\n requested: number;\n /** Task rows the resolver wrote. */\n applied: number;\n /** The resolver threw; rows stand as the transcripts left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_ENRICHMENT: TaskEnrichment = Object.freeze({ requested: 0, applied: 0, failed: false });\n\n/**\n * COALESCE gives the resolver precedence over the transcripts on every field it\n * states, and none on the fields it omits: the store is the system of record for\n * a task's present status, while a transcript only witnesses a command that ran.\n */\nconst UPSERT_TASK = `\n INSERT INTO task (task_ref, task_id, initiative, title, status, estimate) VALUES (@taskRef, @taskId, @initiative, @title, @status, @estimate)\n ON CONFLICT (task_ref) DO UPDATE SET\n initiative = COALESCE(excluded.initiative, initiative),\n title = COALESCE(excluded.title, title),\n status = COALESCE(excluded.status, status),\n estimate = COALESCE(excluded.estimate, estimate)`;\n\nexport function allTaskIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT task_id FROM task\").all() as { task_id: string }[]).map((r) => r.task_id);\n}\n\n/**\n * Enrich task rows from a caller-supplied resolver. Runs outside the delta's\n * transaction because a resolver is async; a crash between the two leaves rows\n * un-enriched, which the next pass corrects. A resolver that throws costs this\n * pass its enrichment and nothing else.\n */\nexport async function enrichTasks(graph: SessionGraph, resolver: TaskResolver | undefined, taskIds: readonly string[]): Promise<TaskEnrichment> {\n const unique = [...new Set(taskIds)];\n if (!resolver) return NO_ENRICHMENT;\n try {\n const resolved = await resolver(unique);\n return { requested: unique.length, applied: write(graph, resolved), failed: false };\n } catch (err) {\n return { requested: unique.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction write(graph: SessionGraph, resolved: TaskResolution): number {\n const upsert = graph.db.prepare(UPSERT_TASK);\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [taskId, fields] of resolved) {\n if (!fields) continue;\n applied += upsert.run({\n taskRef: taskRef(taskId),\n taskId,\n initiative: fields.initiative ?? null,\n title: fields.title ?? null,\n status: fields.status ?? null,\n estimate: fields.estimate ?? null,\n }).changes;\n }\n return applied;\n })();\n}\n","import { EPISODE_TABLE } from \"./audit-schema-v5.js\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * One stretch of a session as some segmentation heuristic cut it. The rule\n * lives above storage (session-analytics); this package only keeps its output.\n * Offsets are byte offsets into the session's transcript.\n */\nexport interface EpisodeRow {\n episodeIndex: number;\n heuristicVersion: number;\n startedAt: string;\n endedAt: string;\n startOffset: number;\n endOffset: number;\n /** `session_start`, `brief`, `channel_followup`, `idle_gap`, `pr_merge`, `spawn_wave_complete`, `task_wrap`, `context_reset`, or a newer heuristic's term. */\n openedBy: string;\n /** The transcript `startOffset`/`endOffset` fall in; a session resumed across transcripts can differ between the two. */\n startTranscriptId?: number | null;\n endTranscriptId?: number | null;\n assignmentOffset?: number | null;\n firstDeliverableOffset?: number | null;\n firstDeliverableSignal?: string | null;\n firstStatusOffset?: number | null;\n}\n\nconst DELETE_EPISODES = `DELETE FROM ${EPISODE_TABLE} WHERE session_id = ? AND heuristic = ?`;\n\nconst INSERT_EPISODE = `\n INSERT INTO ${EPISODE_TABLE} (session_id, episode_index, heuristic, heuristic_version, started_at, ended_at,\n start_offset, end_offset, start_transcript_id, end_transcript_id, opened_by, assignment_offset,\n first_deliverable_offset, first_deliverable_signal, first_status_offset)\n VALUES (@sessionId, @episodeIndex, @heuristic, @heuristicVersion, @startedAt, @endedAt,\n @startOffset, @endOffset, @startTranscriptId, @endTranscriptId, @openedBy, @assignmentOffset,\n @firstDeliverableOffset, @firstDeliverableSignal, @firstStatusOffset)`;\n\n/**\n * The only writer of `episode`. Replaces one heuristic's segmentation of one\n * session atomically and leaves every other heuristic's rows alone, so two rule\n * sets can coexist while one is compared against the other. Returns rows written.\n */\nexport function replaceEpisodes(graph: SessionGraph, sessionId: string, heuristic: string, rows: readonly EpisodeRow[]): number {\n const remove = graph.db.prepare(DELETE_EPISODES);\n const insert = graph.db.prepare(INSERT_EPISODE);\n return graph.db.transaction(() => {\n remove.run(sessionId, heuristic);\n for (const row of rows) insert.run(episodeParams(sessionId, heuristic, row));\n return rows.length;\n })();\n}\n\nfunction episodeParams(sessionId: string, heuristic: string, row: EpisodeRow): Record<string, string | number | null> {\n return {\n sessionId, heuristic,\n episodeIndex: row.episodeIndex, heuristicVersion: row.heuristicVersion,\n startedAt: row.startedAt, endedAt: row.endedAt, startOffset: row.startOffset, endOffset: row.endOffset, openedBy: row.openedBy,\n startTranscriptId: row.startTranscriptId ?? null, endTranscriptId: row.endTranscriptId ?? null,\n assignmentOffset: row.assignmentOffset ?? null, firstDeliverableOffset: row.firstDeliverableOffset ?? null,\n firstDeliverableSignal: row.firstDeliverableSignal ?? null, firstStatusOffset: row.firstStatusOffset ?? null,\n };\n}\n","import type { SessionGraph } from \"./graph.js\";\n\n/** USD per million tokens for one model prefix from one date. Shaped so session-analytics' `PRICE_TABLE` passes straight through. */\nexport interface PriceInput {\n /** Matched against `request.model` by longest prefix in `request_cost`. */\n modelPrefix: string;\n /** ISO date or timestamp; compared as text against `request.ts`. */\n effectiveFrom: string;\n input: number;\n cacheRead: number;\n cacheWrite5m: number;\n cacheWrite1h: number;\n output: number;\n}\n\nexport interface SyncPricesOptions {\n /** Stamped on every row so a report can name the table it priced with. */\n tableVersion: number;\n source?: string;\n}\n\n/** Replace every `price` row with `rows` in one transaction, so `request_cost` never reads a half-written table. */\nexport function syncPrices(graph: SessionGraph, rows: readonly PriceInput[], options: SyncPricesOptions): number {\n const insert = graph.db.prepare(\n `INSERT INTO price (model, effective_from, table_version, input_usd_mtok, cache_read_usd_mtok,\n cache_write_5m_usd_mtok, cache_write_1h_usd_mtok, output_usd_mtok, source)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n const source = options.source ?? null;\n graph.db.transaction(() => {\n graph.db.exec(\"DELETE FROM price\");\n for (const r of rows) {\n insert.run(r.modelPrefix, r.effectiveFrom, options.tableVersion, r.input, r.cacheRead, r.cacheWrite5m, r.cacheWrite1h, r.output, source);\n }\n })();\n return rows.length;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { stat } from \"node:fs/promises\";\nimport { contentHash } from \"@titan-design/locator\";\nimport { conversationRef } from \"@titan-design/agent-protocol\";\nimport { SPAN_TEXT_CAP, readCodexObservations, type CodexReadResult, type NormalizedSessionObservation, type SessionSourceDescriptor, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { insertObservation, observationText } from \"./normalized-project.js\";\n\nexport interface NormalizedIndexResult { status: \"indexed\" | \"unchanged\" | \"missing\" | \"quarantined\"; conversationRef: string; observations: number; reason?: string }\n\n/** Replay changed sources into temporary staging; swap rows and watermark atomically. */\nexport async function indexCodexSource(graph: SessionGraph, source: SessionSourceDescriptor): Promise<NormalizedIndexResult> {\n const ref = conversationRef(source.conversation);\n const base = { conversationRef: ref, observations: 0 };\n const row = graph.transcripts.ensure(source.sourceId);\n let info;\n try { info = await stat(source.path); } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n graph.transcripts.markStatus(source.sourceId, \"missing\", \"source unavailable\");\n return { ...base, status: \"missing\" };\n }\n // Always replay: the decoder verifies bytes, detects projections that arrived later,\n // and avoids trusting mtime alone. Equality of the resulting prefix is the fast no-op.\n const stage = `stage_${randomUUID().replaceAll(\"-\", \"\")}`;\n graph.db.exec(`CREATE TEMP TABLE ${stage}(seq INTEGER PRIMARY KEY, observation TEXT NOT NULL)`);\n let result: CodexReadResult | undefined;\n try {\n const fingerprint = await contentHash(source.path);\n const insert = graph.db.prepare(`INSERT INTO ${stage}(observation) VALUES (?)`);\n for await (const observation of readCodexObservations(source, {}, done => { result = done; })) insert.run(JSON.stringify(observation));\n if (!result) throw new Error(\"decoder completed without a resume boundary\");\n const boundary = (result as CodexReadResult).resumeBoundary;\n const prior = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n if (prior && row.contentHash === fingerprint && row.prefixHash === boundary.prefixHash && row.lastOffset === boundary.byteOffset) {\n graph.db.prepare(\"UPDATE normalized_source SET descriptor = ? WHERE transcript_id = ?\").run(JSON.stringify(source), row.sourceId);\n graph.transcripts.markStatus(source.sourceId, \"ok\", null);\n return { ...base, status: \"unchanged\" };\n }\n const count = graph.db.transaction(() => {\n purgeNormalizedSource(graph, row.sourceId);\n graph.db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id) VALUES (?,?,?,?)\").run(ref, source.harness, source.namespace, source.conversation.nativeId);\n graph.db.prepare(\"INSERT OR REPLACE INTO normalized_source VALUES (?,?,?,?)\").run(row.sourceId, source.sourceId, ref, JSON.stringify(source));\n const n = applyStaged(graph, stage, row.sourceId, ref);\n graph.transcripts.advance(source.sourceId, { lastOffset: boundary.byteOffset, prefixHash: boundary.prefixHash, fileSize: info.size, fileMtime: info.mtime.toISOString(), contentHash: fingerprint });\n return n;\n })();\n return { ...base, status: \"indexed\", observations: count };\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n graph.transcripts.markStatus(source.sourceId, \"quarantined\", reason);\n return { ...base, status: \"quarantined\", reason };\n } finally { graph.db.exec(`DROP TABLE ${stage}`); }\n}\n\nfunction applyStaged(graph: SessionGraph, stage: string, transcriptId: number, ref: string): number {\n const spans = `${stage}_spans`;\n graph.db.exec(`CREATE TEMP TABLE ${spans}(field TEXT, offset INTEGER, length INTEGER, text TEXT, locators TEXT, PRIMARY KEY(field,offset))`);\n let count = 0;\n try {\n for (const row of stagedRows<{ observation: string }>(graph, stage)) {\n const o = JSON.parse(row.observation) as NormalizedSessionObservation;\n insertObservation(graph, transcriptId, o);\n for (const p of observationText(o)) stageText(graph, spans, p.field, p.text, p.locator);\n count++;\n }\n for (const s of stagedRows<{ field: string; offset: number; length: number; text: string; locators: string }>(graph, spans)) {\n const id = graph.spans.index({ ownerRef: ref, sourceId: transcriptId, field: s.field, byteOffset: s.offset, byteLength: s.length }, s.text);\n graph.db.prepare(\"INSERT INTO normalized_span VALUES (?,?)\").run(id, s.locators);\n }\n } finally { graph.db.exec(`DROP TABLE ${spans}`); }\n return count;\n}\n\nfunction stageText(graph: SessionGraph, table: string, field: string, text: string, locator: SourceTextLocator): void {\n const line = locator.evidence.line;\n const prior = graph.db.prepare(`SELECT text,locators FROM ${table} WHERE field = ? AND offset = ?`).get(field, line.byteOffset) as { text: string; locators: string } | undefined;\n const locators = prior ? JSON.parse(prior.locators) as unknown[] : [];\n locators.push(locator);\n graph.db.prepare(`INSERT OR REPLACE INTO ${table} VALUES (?,?,?,?,?)`).run(field, line.byteOffset, line.byteLength, (prior ? `${prior.text}\\n${text}` : text).slice(0, SPAN_TEXT_CAP), JSON.stringify(locators));\n}\n\nfunction purgeNormalizedSource(graph: SessionGraph, transcriptId: number): void {\n graph.db.prepare(\"DELETE FROM normalized_span WHERE span_id IN (SELECT span_id FROM search_span WHERE source_id = ?)\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM search_span WHERE source_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM normalized_event WHERE transcript_id = ?\").run(transcriptId);\n}\n\n\n/** Finish each SELECT before writing; SQLite forbids writes during an active iterator. */\nfunction* stagedRows<T>(graph: SessionGraph, table: string): Generator<T> {\n const select = graph.db.prepare(`SELECT rowid AS cursor, * FROM ${table} WHERE rowid > ? ORDER BY rowid LIMIT 128`);\n let cursor = 0;\n for (;;) {\n const rows = select.all(cursor) as (T & { cursor: number })[];\n if (!rows.length) return;\n for (const row of rows) { cursor = row.cursor; yield row; }\n }\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport { normalizedSearchText, SPAN_TEXT_CAP, scopedConversationItemRef, type NormalizedSessionObservation as Observation, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { stripInjected } from \"./injected-text.js\";\n\n/** Persist structural evidence only; prose remains in the source and transient FTS input. */\nexport function insertObservation(graph: SessionGraph, transcriptId: number, o: Observation): void {\n graph.db.prepare(`INSERT INTO normalized_event VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n transcriptId, o.evidence.line.byteOffset, o.evidence.subrecord.index, o.evidence.line.byteLength,\n conversationRef(o.conversation), o.kind, o.timestamp,\n \"turn\" in o && o.turn ? scopedConversationItemRef(o.turn) : null,\n \"call\" in o ? scopedConversationItemRef(o.call) : null,\n \"item\" in o && o.item ? scopedConversationItemRef(o.item) : null,\n o.kind === \"native_turn\" ? o.phase : null,\n o.kind === \"tool_result\" && o.isError !== null ? Number(o.isError) : null,\n o.kind === \"usage\" ? JSON.stringify(o.measurement) : null,\n o.kind === \"metadata\" ? JSON.stringify(o.entries.filter(e => [\"cwd\", \"model\", \"cli_version\", \"git_branch\", \"title\"].includes(e.name))) : null,\n o.historyOrigin ? conversationRef(o.historyOrigin) : null,\n o.kind === \"lineage\" ? conversationRef(o.relatedConversation) : null,\n o.kind === \"lineage\" ? o.relationship : null,\n );\n}\n\nexport interface ObservationText { field: string; text: string; locator: SourceTextLocator }\nexport function observationText(o: Observation): ObservationText[] {\n if (o.kind === \"message\" && o.role === \"user\") return o.content.map(p => ({ field: \"prompt\", text: stripInjected(p.text).slice(0, SPAN_TEXT_CAP), locator: p.locator })).filter(p => p.text);\n if (o.kind === \"message\") return o.content.map(p => ({ field: \"assistant_response\", text: p.text.slice(0, SPAN_TEXT_CAP), locator: p.locator }));\n if (o.kind === \"tool_call\" && o.inputLocator) return [{ field: \"tool_input\", text: normalizedSearchText(o.input), locator: o.inputLocator }];\n if (o.kind === \"tool_result\" && o.outputLocator) return [{ field: \"tool_result\", text: normalizedSearchText(o.output), locator: o.outputLocator }];\n return [];\n}\n","import { contentHash, prefixHash } from \"@titan-design/locator\";\nimport type { UsageMeasurement } from \"@titan-design/agent-protocol\";\nimport { readSessionSourceText, readSessionText, SessionUsageAccumulator, type SessionUsageSummary, type SourceTextLocator, type SessionSourceDescriptor, type SpanField } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { stripInjected } from \"./injected-text.js\";\n\nexport interface IndexedSpan { sourceId: number; byteOffset: number; byteLength: number; field: string; spanId?: number }\n/** One resolver for search and error clustering; never display a raw JSON line. */\nexport async function readIndexedText(graph: SessionGraph, span: IndexedSpan): Promise<string | null> {\n const text = await readSourceText(graph, span);\n return text !== null && span.field === \"prompt\" ? stripInjected(text) : text;\n}\n\nasync function readSourceText(graph: SessionGraph, span: IndexedSpan): Promise<string | null> {\n const row = graph.db.prepare(`SELECT n.locators FROM normalized_span n JOIN search_span s USING(span_id)\n WHERE s.source_id = ? AND s.byte_offset = ? AND s.field = ?`).get(span.sourceId, span.byteOffset, span.field) as { locators: string } | undefined;\n if (row) {\n const source = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId) as { descriptor: string } | undefined;\n if (!source) return null;\n const descriptor = JSON.parse(source.descriptor) as SessionSourceDescriptor;\n const watermark = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n try { if (!watermark?.prefixHash || await prefixHash(descriptor.path, watermark.lastOffset) !== watermark.prefixHash) return null; }\n catch { return null; }\n if (span.byteOffset + span.byteLength + 1 > watermark.lastOffset) {\n try { if (!watermark.contentHash || await contentHash(descriptor.path) !== watermark.contentHash) return null; } catch { return null; }\n }\n const texts = await Promise.all((JSON.parse(row.locators) as SourceTextLocator[]).map(locator => readSessionSourceText(locator, { sources: [JSON.parse(source.descriptor) as SessionSourceDescriptor] })));\n return texts.some(text => text === null) ? null : texts.join(\"\\n\");\n }\n const normalized = graph.db.prepare(\"SELECT 1 FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId);\n if (normalized) return null;\n const legacy = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n return legacy ? readSessionText({ path: legacy.sourceKey, byteOffset: span.byteOffset, byteLength: span.byteLength, field: span.field as SpanField }) : null;\n}\n\nexport interface ConversationSummary {\n sessionId: string; harness: string; nativeId: string; namespace: string;\n title: string | null; startedAt: string | null; endedAt: string | null;\n cwd: string | null; gitBranch: string | null; turnCount: number; commitCount: number | null; pushCount: number | null;\n}\nexport function normalizedSessions(graph: SessionGraph, options: { ref?: string; limit?: number; since?: string } = {}): ConversationSummary[] {\n const rows = graph.db.prepare(`SELECT c.ref,c.harness,c.native_id,c.namespace,MIN(e.ts) AS started,\n COUNT(DISTINCT CASE WHEN e.kind = 'native_turn' THEN e.turn_ref END) AS turns\n FROM conversation c JOIN (SELECT DISTINCT conversation_ref FROM normalized_source) s ON s.conversation_ref = c.ref\n LEFT JOIN normalized_event e ON e.conversation_ref = c.ref AND e.history_origin IS NULL\n WHERE c.legacy_session_id IS NULL AND (? IS NULL OR c.ref = ?)\n GROUP BY c.ref HAVING (? IS NULL OR MIN(e.ts) >= ?)\n ORDER BY started DESC LIMIT ?`).all(options.ref ?? null,options.ref ?? null,options.since ?? null,options.since ?? null,options.limit ?? -1) as\n { ref: string; harness: string; native_id: string; namespace: string; started: string | null; turns: number }[];\n const meta = graph.db.prepare(`SELECT json_extract(j.value,'$.value') AS value\n FROM normalized_event e,json_each(e.metadata) j WHERE e.conversation_ref = ?\n AND e.history_origin IS NULL AND json_extract(j.value,'$.name') = ? ORDER BY e.ts DESC,e.byte_offset DESC,e.subrecord_index DESC LIMIT 1`);\n return rows.map(c => {\n const text = (key: string) => { const value = (meta.get(c.ref,key) as { value: unknown } | undefined)?.value; return typeof value === \"string\" ? value : null; };\n return { sessionId:c.ref,harness:c.harness,nativeId:c.native_id,namespace:c.namespace,title:text(\"title\"),startedAt:c.started,\n endedAt:null,cwd:text(\"cwd\"),gitBranch:text(\"git_branch\"),turnCount:c.turns,commitCount:null,pushCount:null };\n });\n}\n\nexport type NormalizedUsageSummary = SessionUsageSummary;\n/** Shared storage-free usage policy keeps graph and direct readers consistent. */\nexport function normalizedUsage(graph: SessionGraph, ref: string): NormalizedUsageSummary[] {\n // Snapshot epochs are local to a physical source; never add copies from different files.\n // Pick the source with the latest native timestamp (then fullest coverage, then stable ID).\n const preferred = graph.db.prepare(`SELECT transcript_id FROM normalized_event\n WHERE conversation_ref = ? AND usage IS NOT NULL AND history_origin IS NULL\n GROUP BY transcript_id ORDER BY MAX(ts) DESC, COUNT(*) DESC, transcript_id ASC LIMIT 1`).get(ref) as { transcript_id: number } | undefined;\n const rows = graph.db.prepare(`SELECT usage FROM normalized_event WHERE conversation_ref = ?\n AND usage IS NOT NULL AND history_origin IS NULL\n AND (json_extract(usage,'$.kind') = 'delta' OR transcript_id = ?)\n ORDER BY byte_offset,subrecord_index`);\n const accumulator = new SessionUsageAccumulator();\n for (const row of rows.iterate(ref, preferred?.transcript_id ?? -1) as Iterable<{ usage: string }>) {\n accumulator.add(JSON.parse(row.usage) as UsageMeasurement);\n }\n return accumulator.summaries();\n}\n"],"mappings":"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","names":["sessionRef","sessionRef","TranscriptParseError","extractTranscript","RELATIONS","RELATIONS","RELATIONS","sessionRef","taskRef","write","extractTranscript","stat","TranscriptParseError","isReviewerProfile","contentHash","conversationRef","SPAN_TEXT_CAP","conversationRef","conversationRef","conversationRef","contentHash","SPAN_TEXT_CAP","contentHash","contentHash"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@titan-design/session-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Fold session events into the activity graph on store-sqlite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@titan-design/cluster": "^0.1.2",
|
|
35
35
|
"@titan-design/locator": "^0.2.1",
|
|
36
|
-
"@titan-design/session-read": "^0.
|
|
36
|
+
"@titan-design/session-read": "^0.7.0",
|
|
37
37
|
"@titan-design/store-sqlite": "^0.3.1",
|
|
38
38
|
"@titan-design/agent-protocol": "^0.3.0"
|
|
39
39
|
},
|