feat(web): memory-network graph view — the kb as a graph, pending included - #740
Open
minion1227 wants to merge 1 commit into
Open
feat(web): memory-network graph view — the kb as a graph, pending included#740minion1227 wants to merge 1 commit into
minion1227 wants to merge 1 commit into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #604.
What changed
kb.graph_export/vouch graphgainformat=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 existingArtifactDrawer, plus a "pending frontier only" filter that narrows to proposals and what they touch.dotandmermaidare untouched.Underneath,
build_graphnow walksproposed/, 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_graphto 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 collisionapprove()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 viaembeds, both off the filing session viaproposedIn. 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 invouch impacton its target, which seems right. The consequence is that the only thing separating a pending node from an approved one isstatus, 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_jsondoes zero I/O —kind,statusandlabelall come off the in-memory graph, the same place_to_dotand_to_mermaidread 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_graphalready walks every claim and page, so it captures the facts while it is there. There is a test that patchesKBStore.get_claim/get_pageto raise and asserts the export still renders.Making that survive a cache hit needed one more thing.
load_graphused to returnProvGraph(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 newprov_nodes(id, kind, status, label)table alongsideprov_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 graphoutput gains nodes. A KB with pending proposals renders them indotandmermaidtoo, not justjson. 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.prov_edgescache rebuilds once. The freshness stamp gains a fourth field (pending count), so old stamps mismatch andload_graphrebuilds 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_nodesis created byCREATE TABLE IF NOT EXISTSin the existing schema script, so an existingstate.dbpicks it up with no migration.reset,deindexandclear_prov_edgesall clear it.kb.*request shape.formatdefaults todot, anddot/mermaidcallers get the samestrthey got before.VEP
None, and I want to be explicit about the reasoning rather than leave it hanging the way #718 did.
proposals/README.mdscopes 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 newkb.*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 indot— flagged above as the thing to overrule me on. Happy to file a VEP againstmainfirst if you'd rather settle either point in writing.Screenshots
No before/after images — this is a core PR (
cli.py,server.py), soui-screenshot-gatedoes 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 inwebapp/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 goingscale(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 checkpasses locally (lint + mypy + pytest)CHANGELOG.mdupdated under## [Unreleased]pytest1568 passed,mypy srcclean,ruff check src testsclean, and diff coverage is 100% of changed python (123 lines) — verified locally with the samediff-coverinvocationci.ymlruns, and also with the--includewidened, sincesrc/vouch/*does not matchsrc/vouch/provenance/*and most of this diff lives there. On the web side, 194 vitest tests pass (9 new).Rebased on
testat 0530942 immediately before pushing;git merge-treeis clean.