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:
amosblomqvist
2026-09-28 16:54:44 +03:00
parent cd09c12b56
commit 1b50013551
22 changed files with 909 additions and 1064 deletions
+44 -16
View File
@@ -1563,13 +1563,54 @@ export function GraphView() {
>
<div className="text-sm font-medium leading-snug">{selected.node.name}</div>
<div className="mt-1 text-xs leading-relaxed text-muted-foreground">{selected.node.statement}</div>
{(() => {
// EDGES (owner 2026-09-27): edge classification was entirely
// hidden in the UI — the card now shows every edge this node
// participates in, grouped by direction + type, above the
// sessions list. Only present groups render.
const id = selected.node.id;
const nodeById = new Map((snapshot?.nodes ?? []).map((n) => [n.id, n]));
const label = (nid: string) => nodeById.get(nid)?.name ?? nid;
const groups: { title: string; names: string[] }[] = [
{
title: "Follows from",
names: (snapshot?.edges ?? []).filter((e) => e.from === id && e.type === "follows").map((e) => label(e.to)),
},
{
title: "Supported by",
names: (snapshot?.edges ?? []).filter((e) => e.from === id && e.type === "supports").map((e) => label(e.to)),
},
{
title: "What follows from this",
names: (snapshot?.edges ?? []).filter((e) => e.to === id && e.type === "follows").map((e) => label(e.from)),
},
{
title: "What this supports",
names: (snapshot?.edges ?? []).filter((e) => e.to === id && e.type === "supports").map((e) => label(e.from)),
},
].filter((g) => g.names.length > 0);
if (groups.length === 0) return null;
return (
<div className="mt-3 flex flex-col gap-1">
{groups.map((g) => (
<div key={g.title} className="flex flex-col gap-0.5">
<div className="text-xs text-muted-foreground">{g.title}</div>
<div className="flex flex-wrap gap-1">
{g.names.map((name, i) => (
<span key={`${name}-${i}`} className="rounded-full border px-2 py-0.5 text-[11px] text-muted-foreground">
{name}
</span>
))}
</div>
</div>
))}
</div>
);
})()}
{selected.sourceIds.length > 0 ? (
<div className="mt-3 flex flex-col gap-1">
<div className="text-xs text-muted-foreground">Sessions</div>
{selected.sourceIds.map((sourceId) => {
// That source's understanding observation — the
// extractor's named-word note (confidence history).
const note = snapshot?.nodeObservations?.[selected.node.id]?.[sourceId];
// An imported-notes file: a STATIC entry — the path,
// plain text, never a link (no session to open).
if (snapshot?.fileSources?.[sourceId] !== undefined) {
@@ -1579,11 +1620,6 @@ export function GraphView() {
<span className="min-w-0 flex-1 truncate px-1 py-1 text-sm text-muted-foreground">
{snapshot.fileSources[sourceId]}
</span>
{note && (
<span className="max-w-28 shrink-0 truncate whitespace-nowrap rounded-full border px-2 py-0.5 text-[10px] text-muted-foreground">
{note}
</span>
)}
</div>
);
}
@@ -1596,14 +1632,6 @@ export function GraphView() {
>
{snapshot?.sessions[sourceId] ?? sourceId}
</Link>
{/* The note is a fixed-scale word (solid/shaky/...) — the
session name is the main thing; the chip is bounded so
it can never push or overflow the row. */}
{note && (
<span className="max-w-28 shrink-0 truncate whitespace-nowrap rounded-full border px-2 py-0.5 text-[10px] text-muted-foreground">
{note}
</span>
)}
</div>
);
})}