GraphStore.get_graph_overview (top-N by degree, bounded) + get_node_detail (description + linked chunks). GET /api/sources/<id>/graph + /graph/node/<id> (read-access gated, node scoped to source_id; empty graph -> {nodes:[],edges:[]}). Frontend GraphView (react-force-graph-2d) wired to config.kind=graphrag via card-click/'View graph'; node-click -> description + chunks; tooltip renders untrusted names as text (no innerHTML XSS). All read-only GraphStore methods rollback their txn (no idle-in-transaction lock). Unit G7. (Also a pre-existing prettier fix in WorkflowPreview to keep lint green.)
Per-chunk schema-constrained LLM extraction (gleanings off, one .gen() per chunk) into the GraphStore: model resolves config.graph.extraction_model -> GRAPHRAG_EXTRACTION_MODEL -> instance LLM_NAME; token_usage logged per call (_token_usage_source=graph_extraction, owner-attributed via decoded_token); chunk text fenced as untrusted; robust JSON parse (malformed/errors skip the chunk, never crash); resumable via graph_ingest_progress; max_chunks cap; entity names embedded + merged by normalized_name; chunk_id = doc_id so links match retrieval. Unit G3.
On-demand graph_nodes/graph_edges/graph_node_chunks/graph_ingest_progress in the same DB as pgvector documents (no Alembic, no cross-DB FK; Python uuids; parameterized SQL). upsert_node (merge by normalized_name, doc_freq, dedup description), add_edge (+degree), link_node_chunk, search_nodes_by_embedding (cosine), bounded get_subgraph, checkpoint, delete_by_source. Embedding dim derived from the configured model (768 fallback). Unit G2.