Reference web UI: Governance Graph panel, one-click bootstrap, References side panel#1977
Conversation
…nces side panel Items 2-4 from #1976's integration callout, built on the governanceGraph query: - GovernanceGraphGlimpse: deterministic bipartite d3-force composition — filing clusters above (component-colored, swarm-aware constellation spread), authority-grouped law shelf pinned below with staggered citation-head labels + captions, mention-weighted amber citation arcs, dashed ghosts for not-yet-ingested law. Scale-aware density handling (degree-gated shelf labels, thinned edges, short captions) keeps the 200-node cap legible. Hover focus + cross-corpus click-through. - GovernanceGraphLive owns fetch + the bootstrap CTA: analyzer discovery by task name, immediate corpus analysis, add_document CorpusAction install, weaving poll until first nodes land. - Registered as 'governance-graph' CAML embed + composed into the intelligence overview fallback. - References side panel: new 'references' sidebar tab; Cites (grouped by canonical key with mention counts, ghost rows annotated) / Cited by (grouped by source doc); canonical-link + resolve-by-id navigation via shared useNavigateToDocumentById hook. - Backend: corpusReferences(documentId:) either-side filter (+3 tests); governanceGraph .only()/select_related crash fixed via values_list (found live against the 134-doc S-1 corpus). Verified: 6 new Playwright CT tests + 38 backend tests green, tsc/lint/ pre-commit green, full live smoke on dev S-1 + DGCL corpora (CTA -> celery weave -> graph; panel row -> Rule 144 text; statute node -> DGCL § 116).
Code Review: Reference Web UI (#1977)This is a well-scoped, well-documented PR. The three-feature bundle (Governance Graph panel, one-click bootstrap CTA, References side panel) plus the backend bugfix are all clearly motivated and tested. The overall design follows established patterns in the codebase. Below are the issues worth addressing before merge, ordered by severity. Bugs / Correctness1.
// This is computed outside useMemo — new reference every render
const primaries = simNodes.filter((n) => n.kind === KINDS.PRIMARY);
const labeledIds = useMemo(() => {
...
}, [simNodes, primaries, dense, shelfMaxDegree]); // primaries is always "new"
2.
const nodeEmphasis = (n: SimNode): number => {
if (!hoveredId) return 1;
if (n.id === hoveredId) return 1;
const incident = edges.some( // ← scans ALL edges for EVERY node, EVERY render
(e) => (e.source === hoveredId && e.target === n.id) || ...
);
return incident ? 1 : 0.35;
};With up to 200 nodes and O(200²) edges this is hot during hover. Precompute the incident set when const incidentNodes = useMemo(() => {
if (!hoveredId) return null;
const set = new Set<string>();
edges.forEach((e) => {
if (e.source === hoveredId) set.add(e.target);
if (e.target === hoveredId) set.add(e.source);
});
return set;
}, [hoveredId, edges]);Then Type Safety3. const path = buildCanonicalPath(doc as any, (doc as any).corpus);
Minor / Nits4. Redundant fallback in variables: { corpusId: corpusId || "", documentId },
skip: !corpusId,The 5. GraphQL/TypeScript type mismatch for The GraphQL query declares 6. Polling not explicitly stopped on unmount In useEffect(() => {
return () => { stopPolling(); }; // belt-and-suspenders
}, [stopPolling]);7. The component is functional and well-commented, but three natural seams exist for extraction:
Not a blocker, but the surface area makes future diffs harder to reason about. What's Well-Done
|
…flow Combines four commits of local work on the reference web: 1. Clickable citations + reference-mention UX - Make citations clickable in statute/filing text; auto-merge reference mentions into the document annotation layer (useReferenceMentions discovers the corpus's enrichment Analysis by task name and merges its mention annotations one-shot, deduped by id). - Show friendly names on reference-mention chips (Law / Exhibit). 2. Enrichment denormalization hardening (DRY/integrity pass — every duplicated fact is now either reconciled or DB-guarded) - DocumentRelationship rollups are a reconciled projection of resolved doc->doc CorpusReference rows: the writer prunes stale enrichment-owned edges (data.analysis_id marker) and creates missing ones each run; user-authored rows are never deleted or shadowed. - The cross-corpus linking pass restamps link_url for ALL resolved LAW/DOCUMENT mentions from current slugs, so corpus renames no longer leave stale 404 links (target_document is the durable truth, link_url a cached projection). Result dicts gain links_restamped. - Defined-term definition sites are mention-only: no CorpusReference row until usage->definition linking exists (the OC_REF_TERM annotation already carries term:<slug> in data). - CorpusReference.save() enforces the reference_type <-> mention-label agreement (the column denormalizes the OC_REF_* label for indexing); query-free on the writer hot path. - New partial unique constraint uniq_corpusref_source_type_nullkey (annotations/0079, with pre-dedup): Postgres NULLs-are-distinct meant keyless rows had no concurrent-duplication guard. - Fixes pre-existing fresh-DB failures in test_enrichment_analyzer_integration.py: analyzer/0009+0013 data migrations pre-sync the corpus-analyzer row during migrate, colliding with the test helper's friendly-id get_or_create on the task_name unique constraint. The converge logic now lives once in EnrichmentService.get_or_create_analyzer() and the tests reuse it. 3. Authority backfill workflow (closes the production gaps around authority corpora) - Wanted-authorities queue: CorpusReferenceService.wanted_authorities() aggregates EXTERNAL law refs by authority prefix (subsection keys rolled up to their section root via candidate_keys), ranked by mention volume with per-key corpus counts. Exposed as the wantedAuthorities(corpusId?) GraphQL query and the corpus-scoped read-only agent tool list_wanted_authorities. - Reactive re-link: EnrichmentService.relink_corpora_for_keys() finds every corpus holding EXTERNAL refs satisfiable by just-bootstrapped keys and re-runs the linking pass per corpus AS THAT CORPUS'S CREATOR, so visibility semantics hold per corpus (a private authority resolves only corpora whose creators can see it). Per-corpus failures are logged and counted without aborting the sweep. - Bootstrap entry points: new composite bootstrap_authority_corpus() in enrichment/authorities.py wraps the bootstrapper with optional make_public (Corpus.save propagates to documents) and reactive re-link (default on). Exposed via the approval-gated agent tool bootstrap_authority_corpus (validated section dicts) and the bootstrap_authority management command (JSON spec, --public, --no-relink, --corpus-id) — both route through the composite. Tests: test_corpus_reference_model.py (integrity guards), test_enrichment_backfill.py (queue aggregation, root-key rollup, visibility scoping, private-authority no-leak, subsection-to-root relink matching, composite flags, command happy path + malformed-spec rejection, GraphQL malformed-ID guard), plus writer/linking/tools coverage updates. Docs: backfill workflow + refreshed defined-terms section in docs/test_scripts/corpus_reference_enrichment.md.
Two follow-ups from the reference-web appraisal: 1. Wanted-authorities panel: WantedAuthoritiesCard/Live render the wantedAuthorities GraphQL backlog (the actionable side of the governance graph's ghost nodes) beneath the graph inside GovernanceGraphLive, so every surface that shows the graph (Corpus Intelligence overview, CAML governance-graph article embed) shows what to ingest next — authorities ranked by citation demand, hottest sections as dashed ghost chips, silent when the backlog is empty. GET_WANTED_AUTHORITIES + types in queries.ts, WANTED_AUTHORITIES_MAX_ROWS constant, CT tests + doc screenshot hook. 2. Glimpse dedup: DocumentGraphGlimpse and GovernanceGraphGlimpse now share graphCardChrome.ts (card shell/header/skeleton/legend/empty/explore) and utils/graphLayout.ts (Park-Miller seeded LCG + synchronous d3 tick loop, unit-tested); ghost-key display extracted to formatCanonicalLawKey in utils/formatters.ts (unit-tested) and reused by the new panel. Also fixes frontend/tests/CorpusIntelligenceOverview.ct.tsx, broken since the overview gained GovernanceGraphLive: mounts lacked a Router context (useNavigate threw, nothing rendered) and the GET_GOVERNANCE_GRAPH mock was missing. Wrapped in MemoryRouter + added governance/wanted mocks.
|
test comment - ignore |
|
placeholder - will be replaced |
|
placeholder2 |
|
test comment |
|
Code Review Overview This PR ships the user-facing surface of the reference web: the bipartite d3-force Governance Graph panel, a one-click bootstrap CTA, a References side panel in the document viewer, the WantedAuthorities backlog, and backend plumbing (authority bootstrapper, reactive relink, link-URL restamping). Architecture is sound, the DRY cleanup (shared graphCardChrome.ts, graphLayout.ts) is a genuine improvement, and test coverage is solid. A few issues are worth fixing before merge. Issue 1 — DRY violation: displayKey duplicates formatCanonicalLawKey File: frontend/src/components/knowledge_base/document/DocumentReferencesPanel.tsx A local displayKey function is functionally identical to formatCanonicalLawKey added to frontend/src/utils/formatters.ts in this very PR. The local version also has a subtle correctness gap: it checks part === 'sec' separately rather than delegating to LAW_ACRONYM_SET — any future addition to LAW_AUTHORITY_ACRONYMS would be silently missed in this panel. Fix: delete displayKey and call formatCanonicalLawKey instead. Issue 2 — Security: outbound reference navigation bypasses URL safety check File: frontend/src/components/knowledge_base/document/DocumentReferencesPanel.tsx The outbound-row click handler calls navigate(group.linkUrl!) with the raw database value. The annotation viewers use openAnnotationUrl from urlAnnotation.ts, which gates on isSafeUrl (rejects javascript:, data:, etc.) before following any link. React Router will not execute javascript: as code, but the pattern is inconsistent with every other link-following path in the codebase. Fix: use openAnnotationUrl(group.linkUrl!, navigate) for consistency and defense in depth. Issue 3 — Unhandled async errors in useReferenceMentions File: frontend/src/components/knowledge_base/document/document_kb/useReferenceMentions.ts The inner IIFE has no try/catch. If discoverAnalyses or fetchMentions rejects (network error, cancelled Apollo client, etc.), the unhandled rejection propagates to the global error handler and causes visible warnings or crashes in React 18 strict mode. Fix: wrap the IIFE body in try/catch with a silent-failure strategy — reference mentions are a best-effort UI enhancement, not load-bearing. Issue 4 — 'as any' type casts in useNavigateToDocumentById File: frontend/src/hooks/useNavigateToDocumentById.ts Two 'as any' casts erase the type returned by GetDocumentByIdForRedirectOutput. If there is a genuine shape mismatch with buildCanonicalPath's signature, the mismatch should be resolved in the types or in buildCanonicalPath rather than suppressed at the call site. Issue 5 — O(n*m) hover scan in GovernanceGraphGlimpse File: frontend/src/components/corpuses/CorpusHome/intelligence/GovernanceGraphGlimpse.tsx nodeEmphasis is called once per node on every render, and each call does a full linear scan of the edge list to find incident edges. At the 200-node cap, every onMouseEnter triggers O(n*m) work. A memoised Set of nodes adjacent to the hovered node (recomputed only when hoveredId changes) would reduce per-render cost to O(1) per node. Not a blocker, but the component already has scale-awareness logic so this fits naturally. Minor Notes
Summary Three must-fix items before merge: (1) the displayKey duplication (DRY violation with a real correctness gap), (2) the URL safety check bypass in the References panel, and (3) missing error handling in useReferenceMentions. The 'as any' casts are a lower-priority type-safety cleanup. Everything else is either already handled well or a documented future-work item. |
The canonical CAML intelligence block predated the reference web: a corpus
created with auto-branding disabled got a structural default article with no
governance-graph embed, so the "Map the reference web" bootstrap CTA was
unreachable from the corpus home. Add [component:governance-graph] ("How
this collection is wired to the law") to CAML_INTELLIGENCE_BLOCK and the
marker tuple, mirroring CorpusIntelligenceOverview's composition; the
ensure/backfill paths pick it up via the shared tuple. Found while recording
the corpus-creation -> enrichment demo.
Code ReviewThis is a well-architected, well-tested PR. The bipartite governance graph, one-click bootstrap flow, and document References panel are clear improvements. The reactive re-link design (per-corpus creator visibility, per-corpus failure isolation) is exactly right. A few issues worth addressing before merge: Bugs / Correctness1. _reconcile_document_graph drops get_or_create safety — race window on concurrent runs The old _ensure_document_relationship used get_or_create to prevent duplicate edges when two concurrent apply() calls race on the same corpus. The new reconciliation loop uses DocumentRelationship.objects.create() directly in writer.py. Two simultaneous runs can both see (src, tgt) not in covered and both issue a CREATE, producing a duplicate edge. Swap for get_or_create (keyed on source_document_id, target_document_id, corpus, annotation_label, relationship_type) and only increment document_relationships_created when created is True. 2. useReferenceMentions — failed fetch silently kills retries mergedForRef.current = mergeKey is set before the async operations complete. If discoverAnalyses throws or enrichmentAnalyses.length === 0 in a transient-empty state (e.g. the analysis is still running), the gate is permanently stuck. On remount for the same (document, corpus) the effect short-circuits and citations never appear. Either (a) reset the gate on failure, or (b) set it only after a successful merge. 3. GovernanceGraphLive polling not stopped on unmount The startPolling call in handleBootstrap has no corresponding stopPolling in a cleanup effect. If the user navigates away while the corpus is weaving, the poll continues until the component re-mounts and the weaving && hasNodes effect fires. Add a cleanup effect: return () => { if (weaving) stopPolling(); }. DRY Violation4. displayKey in DocumentReferencesPanel.tsx duplicates formatCanonicalLawKey DocumentReferencesPanel.tsx defines a local displayKey function with a hardcoded acronym set that is nearly identical to formatCanonicalLawKey() added to frontend/src/utils/formatters.ts in this same PR. GovernanceGraphGlimpse and WantedAuthoritiesCard already import the shared function; this one should too. Also note: the local version misses "sec" as a special-cased word (the shared function handles it via LAW_AUTHORITY_ACRONYMS), so the two implementations are subtly inconsistent — "sec-rule:10b-5" renders as "Sec Rule § 10b-5" in the References panel but "SEC Rule § 10b-5" everywhere else. Security / Performance5. wantedAuthorities query has no rate limit Other resolvers in annotation_queries.py use graphql_ratelimit_dynamic. wanted_authorities performs a cross-corpus Python-side aggregation that could be expensive for users with many visible corpora. The resolver should apply the same rate-limiting decorator as its neighbours. 6. _require_type_label_agreement reaches into Django internals self._state.fields_cache is a Django internal, not a public API. It works today but has broken between minor releases. The hot path (enrichment writer already calls select_related("annotation_label")) means the cache hit is common, but the fallback ORM query in save() is benign. Consider replacing the cache inspection with a sentinel attribute set by the writer after its select_related chain, or simply tolerating the extra query on the single-row-save path. Minor7. Dual navigation hooks in DocumentReferencesPanel — both navigate (from useNavigate) for outbound rows with a stored linkUrl and navigateToDocument (from useNavigateToDocumentById) for inbound rows resolved by id are correctly used. No change needed; just confirming this is not dead code. 8. bootstrap_authority_corpus in authorities.py — corpus = Corpus.objects.get(pk=result["corpus_id"]) raises DoesNotExist with no helpful message if the bootstrapper returns an unexpected id. A get_or_none + explicit error would be cleaner, though in practice this cannot happen since the result comes from the same transaction. What is well done
Summary: Items 1 (race condition in _reconcile_document_graph) and 2 (silent gate on failed merge in useReferenceMentions) are correctness issues worth fixing before merge. Item 4 (DRY violation with a subtle inconsistency) is a quick cleanup. Items 3, 5, and 6 are lower-priority polish. |
…ence-web-ui # Conflicts: # config/graphql/annotation_queries.py # opencontractserver/enrichment/constants.py # opencontractserver/enrichment/services/governance_graph_service.py
useNavigateToDocumentById cast the redirect query's document to `any` twice (doc + doc.corpus), pushing the explicit-any count 2 over baseline. The query returns a structural subset that buildCanonicalPath fully supports (it reads only slug/creator.slug), so narrow through `unknown` to the domain types instead.
Backend: - writer.py: _reconcile_document_graph uses get_or_create (not create) for projection edges so two concurrent enrichment runs can't both INSERT the same (src, tgt) edge; counter increments only when created. - annotation_queries.py: rate-limit resolve_wanted_authorities (READ_MEDIUM) consistent with its neighbours — the cross-corpus aggregation was ungated. - models.py: document _require_type_label_agreement's deliberate use of the in-memory relation cache (perf: avoid a query per reference in the enrichment hot path), with the cold-path query as the safety net. Frontend: - useReferenceMentions: release the one-shot merge gate on transient-empty (analysis still running) and on fetch failure, so citations are no longer permanently stranded until a full remount. - GovernanceGraphLive: stop the weave poll on unmount (cleanup effect). - DocumentReferencesPanel: drop the local displayKey duplicate and use the shared formatCanonicalLawKey.
Cover the CAML embed wrapper that lacked tests: ambient-corpus-id wiring (renders the live graph) and the no-corpus null guard. Reuses the existing governance-graph mocks in this file.
Code Review — Reference Web UI (#1977)Reviewed as stacked on This is a well-architected PR shipping several interlocked features: a bipartite D3 governance graph, a one-click bootstrap flow, a document References panel, and the backend machinery (wanted-authorities queue, reactive re-link, authority bootstrap entry points). Overall design is solid. Notes below range from real correctness concerns to minor observations. Correctness / Bugs1.
2. if out["law_references_linked"]:
summary["corpora_relinked"] += 1A corpus whose references were already linked but whose mention 3. No test for the per-corpus failure path in The sweep catches per-corpus exceptions and continues. No test exercises this path. Consider adding a test that monkeypatches Performance4. for key, ref_corpus_id in qs.values_list("canonical_key", "corpus_id"):The PR notes this "stays modest" and the reasoning is fair for current scale. The Python-side aggregation is necessary because 5.
Design / Conventions6. This file handles layout math, cluster union-find, SVG rendering, hover state, and the empty-state motif in one place. The layout algorithm is non-trivial enough to justify the cohesion, but the render section (roughly lines 600+) could be split into a 7. } catch (err) {
toast.error("Couldn't start reference enrichment.");
}The flow is correct — inner guards prevent null-deref before reaching 8. The Celery task uses bare Minor / Nits9. 10. Migration deduplication is Python-side
DELETE FROM annotations_corpusreference WHERE id NOT IN (
SELECT MIN(id) FROM annotations_corpusreference
WHERE canonical_key IS NULL
GROUP BY source_annotation_id, reference_type
);11. Changelog fragments and architecture note are excellent. The SummaryStrong PR. The data-integrity additions ( Main actionable items:
|
- GovernanceGraphLive: give up the weave poll after GOVERNANCE_GRAPH_WEAVING_MAX_MS (90s) and revert to the CTA — a corpus with no law references never yields nodes, so the poll would otherwise spin forever. - GovernanceGraphGlimpse: precompute the hovered node's neighbours in a useMemo so nodeEmphasis is O(1)/node instead of O(edges)/node (~160k iters/hover at the 200-node cap). - annotations/models.py: _require_type_label_agreement now always does the single indexed lookup instead of reading _state.fields_cache (a Django internal); the writer select_relates the label to keep it cheap. - DocumentReferencesPanel: DEFINED_TERM color via GOVERNANCE_GRAPH_COLORS constant instead of a hardcoded hex. - useReferenceMentions: release the merge gate in cleanup when the run didn't complete (covers a hung fetch that never resolves or throws).
A bootstrap with many sections x many citing corpora ran relink_corpora_for_keys
inline; on the async agent-tool path (_db_sync_to_async) that holds a thread-pool
slot for minutes. Add relink_async to bootstrap_authority_corpus: when set, the
relink sweep is enqueued as relink_corpora_for_keys_task and result['relink']
carries {queued, task_id} instead of the inline summary. The async tool wrapper
abootstrap_authority_corpus passes relink_async=True (not exposed to the LLM);
the management command and sync callers keep the inline path. Adds an offload
test (eager Celery still converges the citing corpus).
Root cause of the codecov backend-patch miss: test_enrichment_backfill.py and test_enrichment_writer.py imported config.graphql.schema at MODULE level. Under --cov instrumentation that builds the graphene schema at collection time and errors (graphene-django CustomField field-resolution), so the whole file is dropped and its coverage — including the bootstrap_authority management command and the enrichment services — never reaches the upload. That is why a well-tested command showed 0% patch. Defer the schema import into the _execute helpers (the pattern already used by test_enrichment_tools.py / test_governance_graph.py), so the build happens at runtime and the files' coverage is always captured. Also close the genuinely-uncovered bootstrap_authority branches: unknown creator, unreadable spec file, and missing 'sections' list (now 100%), plus relink_corpora_for_keys per-corpus failure isolation and the empty-keys short-circuit.
Code ReviewOverall: This is a well-structured PR — the force graph layout is cleanly implemented (pure A few items worth addressing before merge: Bug — Apollo cache collisions (Frontend)
Both need corpusReferences: relayStylePagination(["corpusId", "documentId"]),
governanceGraph: { keyArgs: ["corpusId"] },Fragile — direct
|
| Severity | Location | Issue |
|---|---|---|
| Bug | cache.ts (not modified) |
corpusReferences / governanceGraph missing keyArgs — cache collisions |
| Fragile | DocumentReferencesPanel.tsx ~L2977 |
Direct navigate(linkUrl) bypasses openAnnotationUrl; no CT coverage |
| Minor | annotation_queries.py |
document_id filter does not validate doc visibility — IDOR-lite leak |
| Nit | writer.py ~L186-201 |
Lazy queryset evaluated twice around a delete |
The first two are worth fixing before merge; the rest are low-risk follow-ups.
- cache.ts: add keyArgs for corpusReferences (relayStylePagination [corpusId, documentId]), governanceGraph and wantedAuthorities ([corpusId]) — without these, switching documents/corpora served stale cached results. - DocumentReferencesPanel: route outbound link-chip clicks through a new openSafeUrl(url, navigate) helper (extracted from openAnnotationUrl) instead of navigate(linkUrl) directly, so the internal/external + isSafeUrl handling applies; adds a CT test covering the click path. - annotation_queries.py: validate the document_id filter against BaseService.filter_visible before applying it — a corpus reader could otherwise probe whether an arbitrary invisible document has references (IDOR-lite). Adds a backend test. - writer.py: materialise the projection queryset once in _reconcile_document_graph instead of evaluating it twice around the delete.
|
. |
Code Review — PR 1977This is a well-architected PR. The design choices are justified, security considerations are addressed explicitly, and tests are broad. Below are findings roughly ordered by severity. Bugs / Correctness1. Dead
2. The model-level check is correct and the enrichment writer mitigates it with Performance Concerns3. The 4. Same family of concern: loads every Minor Issues5. References tab always visible regardless of enrichment data The "References" tab in 6. If 7. The layout math, SVG arc helpers, and shelf-positioning logic could be extracted into a 8. The comment explains the structural subset issue honestly. A cleaner long-term fix is narrowing Security9. IDOR guard in The guard is sound: returning an empty result for both "invisible" and "does not exist" cases correctly prevents enumeration. The inline comment explains the rationale clearly. 10. Running the relink under each corpus's own creator preserves the permission model correctly. Per-corpus exception isolation (log + count, do not abort) is appropriate for a sweep operation. Test CoverageStrong overall. One gap: no test for the Changelog / DocsThe changelog fragments are thorough and clearly link code locations to impact. SummaryThe implementation is correct and well-thought-out. The main actionable items before merge:
Everything else is low priority or follow-up work. |
Frontend CT coverage (codecov/patch aggregate was 79.89% vs 82.53% target): - GovernanceGraph.ct.tsx: cover handleBootstrap (analyzer fetch → startAnalysis → add_document action → weaving state), the enrichment-unavailable error branch, and node click-through navigation (which also exercises useNavigateToDocumentById). - DocumentReferencesPanel.ct.tsx: cover the inbound-row click path (resolve source doc via redirect query → navigate). Review items: - Add a GraphQL-layer test for the corpusReferences(documentId:) IDOR guard: a corpus reader supplying an invisible document id gets the same empty result as one with no references (test_governance_graph.py). - relink_corpora_for_keys: pre-filter the EXTERNAL law-ref scan SQL-side by authority prefix before the Python candidate_keys match, bounding the scan to relevant authorities instead of loading the full cross-product. Dismissed: the 'dead succeeded variable' note in useReferenceMentions.ts — succeeded IS read in the effect cleanup (line 124) to distinguish teardown after a successful merge (keep the one-shot gate) from teardown mid-flight (release it so a retry can run); removing it would cause redundant re-merges.
Code Review: Reference Web UI (PR #1977)Large, well-engineered PR. Architecture is solid, tests are thorough, and proven against live data. Key observations below. Issues Worth Addressing1. The new implementation loads every resolved LAW/DOCUMENT reference for the corpus (with 2. Race condition risk in The comment correctly identifies the concurrent-writer scenario and uses 3. Extra DB query on every
4. When Positive Observations
Minor Frontend Notes
Summary: four items to address before merge
|
What this is
The user-facing surface of the reference web — items 2, 3 and 4 from #1976's integration callout, stacked on
feature/corpus-reference-enrichment(thegovernanceGraphquery +CorpusReferencesubstrate it consumes).1. Governance Graph panel — the reference web, rendered
A bipartite composition that makes the system's pitch literal: the filings above, the law below.
DocumentGraphGlimpse). Connected components over DOCUMENT edges drive per-cluster hues, so each primary + its exhibits reads as one constellation; swarm-sized clusters spread across the full band instead of packing into a disc.DGCL § 145), authority captions beneath. Citations without an in-system target render as dashed ghost nodes.governance-graphCAML embed and in theCorpusIntelligenceOverviewfallback, so both landing paths get it.2. One-click bootstrap (CorpusAction auto-wiring)
The panel's empty state is the affordance: "Map the reference web" discovers the enrichment analyzer by task name, starts an immediate corpus analysis, installs an
add_documentCorpusAction so the web grows as documents arrive, then polls ("Weaving the reference web…") until the first nodes land. Proven end-to-end live: click → Celery corpus run → woven DGCL self-citation graph + installed action.3. References side panel — citation → statute reading experience
A new References tab in the document viewer: Cites (outbound, grouped by canonical key with mention counts — "SEC Rule § 144 ×26" — ghosts annotated cited, not yet ingested) and Cited by (inbound, grouped by source document). Resolved rows navigate to the cited statute/exhibit text; inbound rows resolve the source document by id via a shared
useNavigateToDocumentByIdhook. Proven live: Fervo S-1 → 71 grouped citations → click → Rule 144's actual section text.Backend
corpusReferences(documentId:)— either-side filter (source mention's document OR resolved target), the single-fetch shape the panel needs. 3 new tests.governanceGraphcrash on real data:.only()vs the Document manager's baked-inselect_related("parent")→values_list(governance_graph_service.py). Unit tests missed it; live smoke caught it.Test plan
GovernanceGraph.ct.tsx,DocumentReferencesPanel.ct.tsx) + docScreenshots; 2,473 unit tests green; tsc + lint green.Notes / follow-ups
onExploreescape hatch is wired.