Skip to main content
Status: Accepted Date: 2026-03-08 Deciders: Reflections Maintainers

Context

The legacy dashboard graph exposed raw fact edges directly to the renderer, mixed scalar-only entities into topology, and under-disclosed truncation. The shell also implied broader search and coverage guarantees than the graph could actually support. That combination made the graph look technically sophisticated while still being easy to misread. Evidence in code/config:
  • packages/db/src/queries/graph.ts (legacy fact-edge graph and new relational overview contract)
  • apps/api/src/routes/graph.ts (requestable topology, remote search route)
  • packages/schemas/src/graph.ts (shared graph overview/search schemas)
  • apps/web/src/hooks/use-graph-data.ts (relational contract parsing + mapping to canvas)
  • apps/web/src/app/(app)/dashboard/knowledge/knowledge-graph-tab.tsx (disclosure rail, inspector orchestration, shell truth)
  • apps/web/src/app/(app)/dashboard/knowledge/use-knowledge-graph-url-sync.ts (canonical graph state hydration)
  • apps/web/src/components/graph/runtime/graph-runtime-config.ts (relational node sizing)

Decision

Adopt a relational overview contract and disclosure-first shell for the web knowledge graph:
  • Overview vs. inspection truth are separated.
    • The graph overview shows bundled directed relationships between entities.
    • Scalar-only entities are excluded from overview topology and disclosed as hidden attribute-only content.
    • Provenance, scalar facts, review affordances, and recent-change detail remain sidecar/inspection responsibilities.
  • The graph API becomes requestably relational.
    • GET /reflections/:id/graph accepts topology=legacy|relational.
    • During migration, the web client explicitly requests relational.
    • Legacy remains available as a compatibility mode.
  • Shared schemas become the cross-layer source of truth.
    • packages/schemas/src/graph.ts defines:
      • GraphOverviewNodeSchema
      • GraphOverviewEdgeSchema
      • GraphOverviewMetaSchema
      • GraphOverviewScopeSchema
      • GraphOverviewResponseSchema
      • GraphSearchResponseSchema
    • The web client validates the relational graph response with Zod before mapping it into canvas-friendly node/edge types.
  • Overview edges are bundled relationships, not raw facts.
    • Each overview edge represents one directed entity-to-entity bundle with:
      • relationshipId
      • sourceId
      • targetId
      • factCount
      • primaryPredicate
      • confidenceSummary
      • latestFactAt
  • Partial graphs use deterministic structure-preserving sampling.
    • The relational overview uses samplingStrategy = rank_biased_structure_preserving_v1.
    • The server preserves a spanning tree per connected component first, then fills remaining edge budget by deterministic rank.
    • Scope and meta echo sampling state so the shell can disclose partiality truthfully.
  • The shell always distinguishes visible vs. total scope.
    • The graph header/disclosure rail shows visible-versus-total entities and relationships.
    • Partiality, hidden scalar-only entities, and search scope are disclosed in UI copy rather than left implicit.
    • The hero “coverage” badge is removed from primary graph chrome.
  • Search semantics are split cleanly.
    • Local graph search remains loaded-slice highlighting.
    • Remote search is a dedicated entity-search contract at /reflections/:id/graph/search.
    • Remote search returns entity hits only, with match reason, availability state, relationship count, and latest fact timestamp.
  • Node prominence follows relational semantics.
    • The web graph now sizes nodes from relational importance (relationship_count) using a log-scaled radius formula.
    • Legacy fact_count remains as the renderer-facing compatibility field in Wave 1, but it is mapped from relationship_count when the relational overview is used.
  • URL hydration preserves graph state unless the URL explicitly owns it.
    • Missing namespace/time params do not wipe existing store state on the initial deep-link hydration pass.
    • Explicit URL params still win and remain canonical.

Alternatives Considered

Alternative 1: Keep raw fact edges and improve only the renderer

Pros:
  • Lower backend change surface.
  • Less contract churn across packages.
Cons:
  • The renderer keeps fighting semantic noise instead of clarifying structure.
  • Scalar-only entities and repeated fact edges still distort the default overview.
  • Violates the core product requirement that the home graph be a relational map rather than a raw ontology dump.

Alternative 2: Replace the graph entirely with a sidecar-first list/workbench

Pros:
  • Easier accessibility and deterministic layout.
  • Lower renderer complexity.
Cons:
  • Sacrifices the graph as the primary product surface.
  • Loses the premium “atlas” quality that differentiates the feature.
  • Solves trust by demoting the graph instead of fixing it.

Alternative 3: Make relational overview the only API shape immediately

Pros:
  • Cleaner backend surface long term.
  • Avoids temporary dual-mode support.
Cons:
  • Higher migration risk for existing consumers.
  • Makes rollout safety worse than necessary.
  • Violates the project preference for additive, requestable contract changes.

Consequences

Benefits:
  • The graph becomes materially harder to misread because overview semantics and shell disclosures align.
  • Web rendering now sits on a typed contract with deterministic meta instead of inferring truth from legacy fields.
  • Search language is finally honest about what is loaded locally versus what requires remote lookup.
  • The premium shell can evolve without reintroducing misleading “coverage” or invisible truncation.
Costs:
  • Dual topology support adds short-term complexity to the graph route.
  • The web layer must temporarily map relational semantics into legacy canvas-friendly fields while the renderer catches up.
  • The DB query surface for graph reads is more sophisticated and therefore needs stronger fixture and regression tests.

Implementation Notes

  • New shared schemas live in packages/schemas/src/graph.ts and are re-exported from packages/schemas/src/index.ts.
  • apps/api/src/routes/graph.ts parses topology and routes relational requests to getRelationalGraphData(...).
  • packages/db/src/queries/graph.ts now exposes:
    • getGraphData(...) for legacy behavior
    • getRelationalGraphData(...) for the overview contract
    • searchGraphEntities(...) for remote entity lookup
  • apps/web/src/hooks/use-graph-data.ts validates GraphOverviewResponseSchema, requests topology=relational, and maps overview nodes/edges into the current graph canvas types.
  • apps/web/src/app/(app)/dashboard/knowledge/use-knowledge-graph-url-sync.ts preserves existing namespace/time state on initial hydration unless the URL explicitly controls those fields.
  • apps/web/src/components/graph/runtime/graph-runtime-config.ts sizes nodes from relational prominence with a log-scaled clamp.