extraction cleanup: events ledger dies, markdown source view, auditor-as-linker (2026-09-27 rulings)
The graph becomes raw reasoning chains only. Quiz outcomes and understanding observations are no longer formalized; assessment moves to the future practice module. Events ledger removed end-to-end: LedgerEvent/LedgerEventKind/ UnderstandingLevel/ExtractionAttribution/AuditResult.dropped and snapshot nodeObservations deleted from contracts; quizEvent builder and the events step of mergeFindings gone; appendEvent/readEvents dropped from the port and store; LEDGER_FILES loses events.jsonl with a one-time purge of a stale file next to the nodes ledger; graph-deletion and the delete dialog lose event purge + quiz-answer impact counts; GraphSessionProposition becomes a pure birth list. Whole-session markdown source view: new core/session-markdown.ts is the shared formatting engine (id-anchored speaker blocks, quiz/ recall/question blocks, tool one-liners) — the extractor's prompt view and the future user-facing export render from one engine. Coverage fingerprints deliberately stay on the old line format (content-hash semantics; re-formatting never re-opens covered history). The whole session goes to the extractor — no phase cut. Auditor refocused as the identity linker: never sees the source; explores the graph via new auditor-specific tools in agent/tools/graph.ts (find — glob over name/statement/aliases; list_sessions; list_propositions with full statement text; no timestamps) instead of an injected full-graph dump; equivalence bias — paraphrases merge, only genuinely distinct propositions stay apart; no drop channel, the extractor is fully trusted; may draw bridging edges (typed follows/supports) between existing nodes and the batch, resolved by the merger with chat-scoped message-less provenance. Agentic loop, maxTurns 16. UI: the node detail card gains an edges section above the sessions list (follows-from / supported-by / what-follows / what-it-supports, rendered only when present, click-to-select neighbors); the understanding-note chips are gone. Prober list_propositions reworded to what a session covered — names only, no levels. docs/architecture.md + README updated to the as-built pipeline. Gates: typecheck, 1059/1059, lint, build.
This commit is contained in:
@@ -13,17 +13,16 @@
|
||||
*
|
||||
* preserve-graph the chat's extraction bookkeeping dies — its coverage
|
||||
* rows and its legacy checkpoint lines — and NOTHING else:
|
||||
* nodes, edges, events, mappings stay byte-identical.
|
||||
* nodes, edges, mappings stay byte-identical.
|
||||
* The graph keeps the session's contributions; the
|
||||
* snapshot simply no longer resolves the dead chatId to
|
||||
* a title (nodeSessions/nodeObservations filter unknown
|
||||
* ids at read time — no UI ghosting).
|
||||
* a title (nodeSessions filters unknown ids at read
|
||||
* time — no UI ghosting).
|
||||
*
|
||||
* delete-nodes "this session never happened": every trace of the
|
||||
* chat leaves the ledgers — its provenance points on
|
||||
* every node and edge line, its link lines (their
|
||||
* phrasings die with them), its quiz events and
|
||||
* observations, its mapping lines (the audit trail), its
|
||||
* phrasings die with them), its mapping lines (the audit trail), its
|
||||
* coverage/checkpoint rows — and orphans die: a node
|
||||
* whose references all came from this chat disappears
|
||||
* entirely, pruned from the positions and embedding
|
||||
@@ -58,7 +57,6 @@ import type {
|
||||
CoverageEntry,
|
||||
GraphEdge,
|
||||
GraphNode,
|
||||
LedgerEvent,
|
||||
NodeMapping,
|
||||
SessionCheckpoint,
|
||||
} from "./contracts.ts";
|
||||
@@ -76,8 +74,6 @@ export interface ChatDeletionStats {
|
||||
nodesRemoved: number;
|
||||
/** Edge assertion LINES removed (empty-provenance or dead endpoint). */
|
||||
edgesRemoved: number;
|
||||
/** Events removed (quiz outcomes + observations). */
|
||||
eventsRemoved: number;
|
||||
/** Mapping lines removed (the chat's audit trail). */
|
||||
mappingsRemoved: number;
|
||||
/** Coverage rows removed. */
|
||||
@@ -89,7 +85,6 @@ export interface ChatDeletionStats {
|
||||
const EMPTY_STATS: ChatDeletionStats = {
|
||||
nodesRemoved: 0,
|
||||
edgesRemoved: 0,
|
||||
eventsRemoved: 0,
|
||||
mappingsRemoved: 0,
|
||||
coverageRemoved: 0,
|
||||
checkpointsRemoved: 0,
|
||||
@@ -126,7 +121,6 @@ export function deleteChatTraces(graphStore: GraphStore, chatId: string, mode: C
|
||||
// ── delete-nodes: the full purge ──────────────────────────────────────
|
||||
const nodeLines = graphStore.readNodes();
|
||||
const edgeLines = graphStore.readEdges();
|
||||
const events = graphStore.readEvents();
|
||||
const mappings = graphStore.readMappings();
|
||||
|
||||
// Does the chat touch the graph at all? (A never-extracted chat still
|
||||
@@ -136,7 +130,6 @@ export function deleteChatTraces(graphStore: GraphStore, chatId: string, mode: C
|
||||
const touched =
|
||||
nodeLines.some((line) => line.provenance.some((pt) => pt.chatId === chatId)) ||
|
||||
edgeLines.some((line) => line.provenance.some((pt) => pt.chatId === chatId)) ||
|
||||
events.some((event) => event.chatId === chatId) ||
|
||||
mappings.some((m) => m.chatId === chatId);
|
||||
if (!touched) {
|
||||
if (stats.coverageRemoved > 0 || stats.checkpointsRemoved > 0) {
|
||||
@@ -149,7 +142,7 @@ export function deleteChatTraces(graphStore: GraphStore, chatId: string, mode: C
|
||||
// remains — another chat/src's provenance point on any of its lines,
|
||||
// a surviving mapping line (notes-front-door nodes carry EMPTY
|
||||
// provenance: the file IS the anchor, recorded through the mapping
|
||||
// line), or a surviving event naming it. (Edge endpoints are deliberately
|
||||
// line). (Edge endpoints are deliberately
|
||||
// NOT survival references: an edge's asserting session always left
|
||||
// provenance on its endpoints, so endpoint-only liveness cannot
|
||||
// occur in a consistent ledger — and counting it would keep nodes
|
||||
@@ -163,9 +156,6 @@ export function deleteChatTraces(graphStore: GraphStore, chatId: string, mode: C
|
||||
for (const mapping of mappings) {
|
||||
if (mapping.chatId !== chatId) live.add(mapping.graphNodeId);
|
||||
}
|
||||
for (const event of events) {
|
||||
if (event.chatId !== chatId && event.nodeId !== null) live.add(event.nodeId);
|
||||
}
|
||||
|
||||
// 2. Node lines. Per-line (each line is ONE extraction run's append, so
|
||||
// per-session alias provenance exists at line granularity):
|
||||
@@ -219,19 +209,16 @@ export function deleteChatTraces(graphStore: GraphStore, chatId: string, mode: C
|
||||
outEdges.push(points.length === line.provenance.length ? line : { ...line, provenance: points });
|
||||
}
|
||||
|
||||
// 4. Events, mappings: the chat's rows die outright.
|
||||
const outEvents = events.filter((event) => event.chatId !== chatId);
|
||||
// 4. Mappings: the chat's rows die outright.
|
||||
const outMappings = mappings.filter((m) => m.chatId !== chatId);
|
||||
|
||||
stats.nodesRemoved = [...new Set(nodeLines.map((line) => line.id))].filter((id) => !live.has(id)).length;
|
||||
stats.edgesRemoved = edgeLines.length - outEdges.length;
|
||||
stats.eventsRemoved = events.length - outEvents.length;
|
||||
stats.mappingsRemoved = mappings.length - outMappings.length;
|
||||
|
||||
graphStore.rewriteLedgers({
|
||||
nodes: outNodes,
|
||||
edges: outEdges,
|
||||
events: outEvents,
|
||||
mappings: outMappings,
|
||||
coverage,
|
||||
checkpoints,
|
||||
@@ -264,8 +251,6 @@ export function deleteChatTraces(graphStore: GraphStore, chatId: string, mode: C
|
||||
*
|
||||
* - every line of the node (create + link lines)
|
||||
* - every EDGE touching it (from or to), whatever session asserted it
|
||||
* - every EVENT naming it (quiz outcomes + observations), from every
|
||||
* session — the evidence history dies with the thing it was about
|
||||
* - every mapping line that anchored it (the audit trail; for a
|
||||
* notes-born node the file IS the anchor — its mapping line is the
|
||||
* only one)
|
||||
@@ -287,20 +272,16 @@ export function deleteChatTraces(graphStore: GraphStore, chatId: string, mode: C
|
||||
export interface NodeDeletionStats {
|
||||
/** Edge assertion LINES removed (every edge touching the node). */
|
||||
edgesRemoved: number;
|
||||
/** Events removed (quiz outcomes + observations naming the node). */
|
||||
eventsRemoved: number;
|
||||
/** Mapping lines removed (the node's audit trail). */
|
||||
mappingsRemoved: number;
|
||||
}
|
||||
|
||||
/** The confirm dialog's pre-flight facts (the GET on the same node id):
|
||||
* what full death would take with it. sessions is the deduped set of
|
||||
* chats/notes sources that reference the node — provenance points,
|
||||
* mapping lines, and events all count. */
|
||||
* chats/notes sources that reference the node — provenance points and
|
||||
* mapping lines count. */
|
||||
export interface NodeDeletionImpact {
|
||||
edges: number;
|
||||
quizEvents: number;
|
||||
observations: number;
|
||||
sessions: string[];
|
||||
}
|
||||
|
||||
@@ -313,7 +294,6 @@ function nodeLinesOf(graphStore: GraphStore, nodeId: string): GraphNode[] {
|
||||
export function nodeDeletionImpact(graphStore: GraphStore, nodeId: string): NodeDeletionImpact | null {
|
||||
if (nodeLinesOf(graphStore, nodeId).length === 0) return null;
|
||||
const edges = graphStore.readEdges().filter((line) => line.from === nodeId || line.to === nodeId);
|
||||
const events = graphStore.readEvents().filter((event) => event.nodeId === nodeId);
|
||||
const sessions = new Set<string>();
|
||||
for (const line of nodeLinesOf(graphStore, nodeId)) {
|
||||
for (const pt of line.provenance) sessions.add(pt.chatId);
|
||||
@@ -321,11 +301,8 @@ export function nodeDeletionImpact(graphStore: GraphStore, nodeId: string): Node
|
||||
for (const mapping of graphStore.readMappings()) {
|
||||
if (mapping.graphNodeId === nodeId) sessions.add(mapping.chatId);
|
||||
}
|
||||
for (const event of events) sessions.add(event.chatId);
|
||||
return {
|
||||
edges: edges.length,
|
||||
quizEvents: events.filter((event) => event.kind !== "observation").length,
|
||||
observations: events.filter((event) => event.kind === "observation").length,
|
||||
sessions: [...sessions].sort(),
|
||||
};
|
||||
}
|
||||
@@ -341,11 +318,9 @@ export function deleteNodeTraces(graphStore: GraphStore, nodeId: string): NodeDe
|
||||
if (nodeLines.length === 0) return null;
|
||||
|
||||
const edgeLines = graphStore.readEdges();
|
||||
const events = graphStore.readEvents();
|
||||
const mappings = graphStore.readMappings();
|
||||
|
||||
const outEdges = edgeLines.filter((line) => line.from !== nodeId && line.to !== nodeId);
|
||||
const outEvents = events.filter((event) => event.nodeId !== nodeId);
|
||||
const outMappings = mappings.filter((m) => m.graphNodeId !== nodeId);
|
||||
|
||||
// Only the touched ledgers: the node's own lines always die (its file
|
||||
@@ -354,7 +329,6 @@ export function deleteNodeTraces(graphStore: GraphStore, nodeId: string): NodeDe
|
||||
graphStore.rewriteLedgers({
|
||||
nodes: outNodes,
|
||||
...(outEdges.length !== edgeLines.length ? { edges: outEdges } : {}),
|
||||
...(outEvents.length !== events.length ? { events: outEvents } : {}),
|
||||
...(outMappings.length !== mappings.length ? { mappings: outMappings } : {}),
|
||||
});
|
||||
|
||||
@@ -367,7 +341,6 @@ export function deleteNodeTraces(graphStore: GraphStore, nodeId: string): NodeDe
|
||||
|
||||
return {
|
||||
edgesRemoved: edgeLines.length - outEdges.length,
|
||||
eventsRemoved: events.length - outEvents.length,
|
||||
mappingsRemoved: mappings.length - outMappings.length,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user