# @oshun/product-graph

The unified product-knowledge property graph: ONE typed node/edge/provenance
vocabulary covering every compiled product representation — curated feature
catalog, e2e journeys and checks, docs-center reader pages, TODOS checkbox
tasks, architecture/design/proposal/review documents, anchors, tours, invocation
points, and routes — with one validator, one Merkle version, and queryable
projections in Postgres and Neo4j.

## The rule that keeps everything honest

**The checked-in composed artifact is the single source of truth. Postgres and
Neo4j are PROJECTIONS of it** — rebuildable from the artifact at any time, never
hand-edited, never authoritative. Nothing writes to the knowledge plane at
runtime; the graph only changes when a PR changes the sources and the artifact
recompiles.

## Layout

- `src/types.ts` — node/edge/provenance model, knowledge/intent plane split
- `src/validate.ts` — kind-aware validator (endpoint rules, containment forest,
  DFS-block order, plane discipline, route integrity, supersedes acyclicity)
- `src/traverse.ts` — index-backed traversal (`children`/`ancestors`/
  `descendants`/`edgesFrom`/`edgesTo`/`subgraph`/`propagateUp`)
- `src/hash.ts` — canonical-JSON content addressing + `composeVersion`
- `src/queries.ts` — cross-representation lenses (`whatTouchesRoute`)
- `src/compilers/` — one pure compiler per source
- `src/curation/` — the reviewed alias/waiver tables (code, PR-gated)
- `src/compose.ts` + `src/artifact.ts` — composer and artifact reader/writer
- `src/generated/` — the checked-in artifact (manifest + per-section JSONL; the
  todos bulk is a gitignored derived cache, hash-pinned by the manifest)

## Building the artifact

```bash
node tools/build-product-graph.mjs
```

Deterministic; CI's staleness gate (`src/__tests__/artifact-gates.spec.ts`)
fails when the artifact on disk differs from a fresh recompile. Commit any merge
FIRST, then regenerate, then commit the artifact.

## Postgres projection (phase 6)

Tables `product_graph_node` / `product_graph_edge` in `oshun_dev` (Prisma
migration `20260804140000_product_graph_projection`). Loaded by transactional
full-replace per graph version — see
`apps/oshun/bff/src/assistant/product-graph-pg.ts`; boot sync behind
`OSHUN_PRODUCT_GRAPH_PG_SYNC=1`. The tested SQL pack lives in
`apps/oshun/bff/src/assistant/product-graph-pg.integration.spec.ts` and asserts
EXACT kernel-computed answers (descendants CTE, coverage rollup, TOTALITY
restated in SQL, repo-backlog-through-the-graph).

## Neo4j projection (phase 7)

Analysis surface only — never a serving dependency.

```bash
# 1. Bring up the graph profile (override the Kalika-sized memory defaults
#    on RAM-constrained machines):
KALIKA_NEO4J_MEM_LIMIT=2500m KALIKA_NEO4J_HEAP_MAX=1G \
KALIKA_NEO4J_PAGECACHE=512m KALIKA_NEO4J_TX_MEMORY_MAX=512m \
docker compose -f docker/docker-compose.dev.yml --profile graph up -d

# 2. Build the kernel once (the loader reads the compiled dist):
pnpm --dir libs/oshun/product-graph build

# 3. Load (full replace, scoped to the :ProductGraph label):
node tools/load-product-graph-neo4j.mjs
```

Node label per kind (`:Flow`, `:Journey`, `:Route`, …) plus the common
`:ProductGraph` label; relationship type per edge kind (`:CONTAINS`,
`:VERIFIES`, `:DOCUMENTS`, `:VISITS`, `:TRACKS`, …); provenance + version as
properties on both. The unique `ProductGraph.id` constraint and per-kind id
indexes are created and asserted BEFORE any load.

The Cypher query pack (`src/__tests__/neo4j-query-pack.integration.spec.ts`) is
gated: it skips with a `not_configured` message when Neo4j is down and runs for
real under the `graph` profile — domain coverage rollup matching the TypeScript
analyzer exactly, shortest path feature→journey→route, the orphan scan, the
`/tara` route join, and a cross-source neighborhood traversal.

Connection env (see root `.env.example`): `OSHUN_PRODUCT_GRAPH_NEO4J_URL` /
`_USER` / `_PASSWORD` / `_DB`.

## Two planes

Knowledge (everything above) is compiled from the repo and read-only at runtime.
Intent (work items, decisions, threads, content briefs) is born in conversation,
lives in Postgres, and reaches this graph only as `intent`- provenance
projections — never the artifact. Promotion travels through code: an agent or
human lands a PR, the artifact recompiles, and the new nodes/edges appear.

## Cross-plane queries (phase 15)

`node tools/load-product-graph-neo4j.mjs` also syncs the intent plane when
Postgres is reachable (`plane: 'intent'` nodes: `:WorkItem`/`:Decision`/
`:Thread` with `:TRACKS`/`:DECIDES`/`:SUPERSEDES`/`:DISCUSSES` into the same
graph); `--intent-only` refreshes just that projection and REFUSES when the
knowledge plane is missing or at a stale artifact version. In Postgres the
`work_item_graph_refs` view (migration `20260804230000`) unnests refs for joins
against `product_graph_node`/`_edge`.

The cross-plane pack lives in
`apps/oshun/bff/src/workbench/cross-plane.integration.spec.ts`: open work
touching `/tara` (containment closure + route prefix), accepted decisions on
UNVERIFIED curated flows, and the repo-backlog vs live-backlog reconciliation
(unchecked TODOS tasks none of whose refs any live work item touches) — answered
by the TypeScript kernel, SQL, and Cypher with EXACTLY matching sets, never just
matching counts.
