Skip to content

feat(web): memory-network graph view — the kb as a graph, pending included - #740

Open
minion1227 wants to merge 1 commit into
vouchdev:testfrom
minion1227:feat/memory-network-graph
Open

feat(web): memory-network graph view — the kb as a graph, pending included#740
minion1227 wants to merge 1 commit into
vouchdev:testfrom
minion1227:feat/memory-network-graph

Conversation

@minion1227

Copy link
Copy Markdown
Contributor

closes #604.

What changed

kb.graph_export / vouch graph gain format=json{nodes: [{id, kind, label, status}], edges: [{src, dst, kind}]} — and the console gains a Memory view beside Browse that renders it: a pannable, zoomable svg graph, nodes coloured by status, click a node to open the existing ArtifactDrawer, plus a "pending frontier only" filter that narrows to proposals and what they touch. dot and mermaid are untouched.

Underneath, build_graph now walks proposed/, so pending proposals are graph nodes, and it carries each node's kind, status and label on the graph instead of leaving a formatter to re-read them.

Why

This is the second attempt at #604, filed as one piece per your close on #705: "teach build_graph to carry status (fixing the N-fetch), and land the serializer together with the consumer that needs it … happy to review that as one piece." Both halves are here, and the deferred half — the pending frontier — is the one that decides whether the view answers the question the issue was filed to answer.

Three things follow from that, and they are the design:

Pending proposals are keyed on the proposal id, not the artifact id. A claim proposal's payload["id"] is a prospective id: until approval that artifact does not exist, and the id may already be taken by a claim on disk — the collision approve() exists to catch. Keying on the proposal id means a pending claim never silently merges into the node of the claim it would overwrite.

They reuse the durable edge kinds. A pending claim hangs off the sources it cites via cites, a proposed page off its claims via embeds, both off the filing session via proposedIn. One new kind, targets, covers the artifact a pending delete would remove — the only edge in the graph pointing at something on its way out, and it makes a pending delete show up in vouch impact on its target, which seems right. The consequence is that the only thing separating a pending node from an approved one is status, which is what makes colouring by status the review gate rendered rather than decoration.

Status is captured at build time, not re-read at serialise time. _to_json does zero I/O — kind, status and label all come off the in-memory graph, the same place _to_dot and _to_mermaid read from. That is the N-fetch regression you flagged on #705, fixed at the source you pointed at rather than papered over in the serializer: build_graph already walks every claim and page, so it captures the facts while it is there. There is a test that patches KBStore.get_claim / get_page to raise and asserts the export still renders.

Making that survive a cache hit needed one more thing. load_graph used to return ProvGraph(load_edges(store)) with no node map at all, so a cache-loaded graph inferred kinds from edge shapes and could not know status at any price — a hot read and a cold read would have disagreed. Node facts now persist to a new prov_nodes(id, kind, status, label) table alongside prov_edges, and a test asserts a cached export is byte-identical to a freshly built one.

What might break

Honest list, for someone with an existing .vouch/:

  • vouch graph output gains nodes. A KB with pending proposals renders them in dot and mermaid too, not just json. The serializers themselves are unchanged — this is the same walk, with more in it. I took this over gating pending behind a flag because a graph that hides the frontier is the thing feat(web): memory-network graph view — render the kb as a pannable graph, pending included #604 says it must not be, and two different meanings of "the graph" depending on format seemed worse than one honest one. Say the word if you'd rather it were opt-in.
  • Every existing prov_edges cache rebuilds once. The freshness stamp gains a fourth field (pending count), so old stamps mismatch and load_graph rebuilds transparently on first use. That is deliberate — it is the version bump, in the one place this design has to spend it — and it also means a filed-then-rejected proposal invalidates correctly, which the previous three counts missed.
  • prov_nodes is created by CREATE TABLE IF NOT EXISTS in the existing schema script, so an existing state.db picks it up with no migration. reset, deindex and clear_prov_edges all clear it.
  • No file moves, no on-disk field changes, no change to any kb.* request shape. format defaults to dot, and dot/mermaid callers get the same str they got before.

VEP

None, and I want to be explicit about the reasoning rather than leave it hanging the way #718 did. proposals/README.md scopes VEPs to new/renamed/removed methods and changed parameter shapes; this adds a value to an existing parameter, which is how #604 itself reads it: "additive. no schema change, no storage change, no new kb.* method (existing method gains a format value, so no four-site registration needed)." The one judgement call not covered by that note is pending nodes appearing in dot — flagged above as the thing to overrule me on. Happy to file a VEP against main first if you'd rather settle either point in writing.

Screenshots

No before/after images — this is a core PR (cli.py, server.py), so ui-screenshot-gate does not apply, and I don't have a way to attach GitHub-hosted images from the environment I'm working in. In place of them, the visual contract is asserted in webapp/src/views/MemoryNetworkView.test.tsx: the legend and the four status colours, a node's accessible name (claim the older fact (superseded)), the screen-reader summary (5 nodes and 4 links, 1 pending review), the drawer opening on an approved node, a pending node routing to review instead, the pending-only filter, and the zoom transform going scale(1)scale(1.25) → reset. npm run build (tsc + vite) is clean. Layout is deterministic — one column per node kind, no force simulation — specifically so a screenshot of a given KB is stable and worth diffing against the next one.

Tests

  • make check passes locally (lint + mypy + pytest)
  • New / changed behaviour has a test
  • CHANGELOG.md updated under ## [Unreleased]

pytest 1568 passed, mypy src clean, ruff check src tests clean, and diff coverage is 100% of changed python (123 lines) — verified locally with the same diff-cover invocation ci.yml runs, and also with the --include widened, since src/vouch/* does not match src/vouch/provenance/* and most of this diff lives there. On the web side, 194 vitest tests pass (9 new).

Rebased on test at 0530942 immediately before pushing; git merge-tree is clean.

closes vouchdev#604.

`kb.graph_export` gains `format=json` and the console gains a Memory view
that renders it: pan, zoom, click a node to open the existing artifact
drawer, colour by status. the dot and mermaid serializers are untouched.

the part that carries the value is that pending proposals are graph nodes
now. `build_graph` walks `proposed/` and keys each proposal on its own id
rather than on the artifact id its payload would create — until approval
that artifact does not exist, and the prospective id may already be taken.
they hang off the graph by the same edge kinds a durable artifact uses
(cites to the sources they quote, embeds to the claims a proposed page
would collect, proposedIn to the filing session) plus one new kind,
targets, for the artifact a pending delete would remove. the only thing
separating a pending node from an approved one is its status, which is what
makes colouring by status the review gate rendered rather than decoration.

kind, status and label are captured during the build and carried on the
graph, cached in a new prov_nodes table, so the json serializer does no i/o
per node — it costs the same as dot, and a cache-loaded graph reports the
same kind, status and label as a freshly built one. the freshness stamp
gains the pending count, which catches a filed-then-rejected proposal and
also invalidates every cache written before proposals were nodes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@minion1227
minion1227 requested a review from plind-junior as a code owner July 31, 2026 18:49
@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation tests tests and fixtures size: XL 1000 or more changed non-doc lines labels Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli command line interface docs documentation, specs, examples, and repo guidance mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation size: XL 1000 or more changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web): memory-network graph view — render the kb as a pannable graph, pending included

1 participant