Skip to content

Milestone 7 — Graph Relationships and Contradiction Detection

Overview

Milestone 7 adds a Neo4j property graph projection of the KGS registry, enabling:

  • Full contradiction detection — assertion grammar (subject + predicate + scope) comparison across all approved answers
  • Supersession chain traversal — retrieve the complete version history for any answer
  • Related-answers traversal — retrieve all assertions about a concept (e.g. "platform")
  • Periodic sync — Celery tasks keep the graph current with PostgreSQL

Architecture

API (FastAPI)
  └─ GraphService (application)
       ├─ GraphRepository (domain protocol → Neo4jAdapter)
       ├─ RegistryRepository (domain protocol → PostgresRegistryRepository)
       └─ ContradictionDetector (pure domain service)

Key Design Decisions

Contradiction Detection via Assertion Grammar

Contradictions are detected at the grammar level: two approved answers conflict when they share the same subject and predicate but have different object_value fields. Scope acts as a context boundary — answers in different scopes (e.g. enterprise vs community) are not considered contradictory.

This logic lives entirely in the ContradictionDetector domain service (zero I/O), making it testable without any running infrastructure.

Neo4j for Relationship Storage

The graph stores five relationship types: | Relationship | Direction | Meaning | |---|---|---| | ANSWERED_BY | Question → Answer | A transcript question has a validated answer | | SUPPORTED_BY | Answer → Evidence | An answer is backed by a source document | | ABOUT | Answer → Concept | An answer asserts something about a concept | | SUPERSEDES | New Answer → Old Answer | Version chain link | | CONFLICTS_WITH | Answer ↔ Answer | Contradiction recorded at publish time |

Relational Store Remains Source of Truth

PostgreSQL remains the authoritative store. Neo4j is a derived projection. If Neo4j is unavailable or out of sync, the API returns an error rather than serving stale data. The Celery sync worker reconciles the two stores.

Protocol-Backed Testability

GraphRepository is a Protocol in the domain layer. Tests use FakeGraphRepo (pure Python dict) and FakeRegistryRepo — no Neo4j, no Postgres required. All 35 unit tests run in < 1 second.

Files Added

Domain

File Description
src/raw_to_knowledge/domain/models/graph.py AnswerNode, QuestionNode, ConceptNode, EvidenceNode, ContradictionResult, SupersessionChain, RelatedAnswersResult
src/raw_to_knowledge/domain/protocols/graph.py GraphRepository Protocol (10 async methods)
src/raw_to_knowledge/domain/services/contradiction_detector.py ContradictionDetector — pure domain, zero I/O

Infrastructure

File Description
src/raw_to_knowledge/infrastructure/graph/cypher_queries.py All Cypher query constants (UPSERT, LINK, TRAVERSE, CONSTRAINT)
src/raw_to_knowledge/infrastructure/graph/neo4j_adapter.py Neo4jAdapter — async neo4j driver, ensure_constraints(), close()

Application

File Description
src/raw_to_knowledge/application/graph/graph_service.py GraphService — sync, detect, traverse
src/raw_to_knowledge/application/graph/sync_tasks.py Celery tasks: sync_graph_task, detect_contradictions_task

API

File Description
src/raw_to_knowledge/api/v1/graph.py 5 FastAPI routes

API Endpoints

Method Path Description
POST /v1/graph/answers/{id}/sync Sync one answer from Postgres → Neo4j
GET /v1/graph/answers/{id}/contradictions List known contradictions (graph-stored)
POST /v1/graph/answers/{id}/detect-contradictions Run fresh detection against all fact answers
GET /v1/graph/answers/{id}/supersessions Get the full version chain
GET /v1/graph/concepts/{label}/answers Get all answers about a concept

Graph Schema

(Question)-[:ANSWERED_BY]->(Answer)
(Answer)-[:SUPPORTED_BY]->(Evidence)
(Answer)-[:ABOUT]->(Concept)
(Answer)-[:SUPERSEDES]->(Answer)
(Answer)-[:CONFLICTS_WITH]->(Answer)

Configuration

New settings added to src/raw_to_knowledge/config.py:

Setting Default Description
NEO4J_URI bolt://localhost:7687 Neo4j Bolt URI
NEO4J_USERNAME neo4j Neo4j username
NEO4J_PASSWORD "" Neo4j password

Celery Workers

# Start the graph sync worker
celery -A raw_to_knowledge.infrastructure.celery_app worker --loglevel=info -Q raw_to_knowledge_graph

# Trigger a full sync manually
celery -A raw_to_knowledge.infrastructure.celery_app call raw_to_knowledge.application.graph.sync_tasks.sync_graph

# Trigger contradiction detection for one answer
celery -A raw_to_knowledge.infrastructure.celery_app call \
  raw_to_knowledge.application.graph.sync_tasks.detect_contradictions \
  --args '["<answer_id>"]'

Quality Gates

Gate Threshold Measurement
Contradiction detection rate ≥ 90% of seeded fixtures detected Unit test: test_contradiction_detector.py
Supersession chain integrity Full chain returned for versioned answers Integration test
Graph sync consistency All approved answers projected within 60 s Celery periodic task
Graph query performance p95 traversal ≤ 500 ms for two-hop queries Integration benchmark

Testing

# Unit tests (no external deps)
pytest tests/unit/test_contradiction_detector.py tests/unit/test_graph_service.py

# Integration tests (requires NEO4J_URI)
NEO4J_URI=bolt://localhost:7687 NEO4J_PASSWORD=password \
  pytest tests/integration/test_graph_roundtrip.py -m integration