Skip to content

Architecture

Layered Design

TNGS uses a strict four-layer architecture. Each layer may only depend on the layer directly below it:

┌─────────────────────────────────────┐
│            REST API Layer            │  FastAPI routers, Pydantic schemas
├─────────────────────────────────────┤
│         Domain Services Layer        │  IngestService, TransformService, etc.
├─────────────────────────────────────┤
│          Repository Layer            │  GraphRepository, CypherQueries
├─────────────────────────────────────┤
│            Graph Store               │  Neo4j (Bolt :7687)
└─────────────────────────────────────┘

No layer may skip a level. Routers never issue Cypher; renderers never call services; services never import FastAPI.

Component Map

flowchart TD
    subgraph Input["Input Layer"]
        A1[Plain text / Markdown]
        A2[JSON payload]
        A3[CSV corpus]
    end

    subgraph Ingest["Ingest Pipeline"]
        B1[Text segmenter]
        B2[Entity extractor]
        B3[Event detector]
        B4[Confidence annotator]
        B5[Pattern detector]
    end

    subgraph API["REST API — FastAPI"]
        C1[/v1/notes/import]
        C2[/v1/narratives]
        C3[/v1/patterns]
        C4[/v1/transforms/apply]
        C5[/v1/render]
        C6[/v1/health]
    end

    subgraph Domain["Domain Services"]
        D1[IngestService]
        D2[PatternService]
        D3[TransformService]
        D4[RenderService]
    end

    subgraph Repo["Repository Layer"]
        E1[GraphRepository]
        E2[CypherQueries]
    end

    subgraph Store["Graph Store"]
        F1[(Neo4j)]
    end

    A1 & A2 & A3 --> C1
    C1 --> D1 --> B1 & B2 & B3 & B4 & B5
    B1 & B2 & B3 & B4 & B5 --> E1
    C2 & C3 --> D2 --> E1
    C4 --> D3 --> E1
    C5 --> D4 --> E1
    E1 --> E2 --> F1
    C6 --> F1

Ingest Pipeline

The ingest pipeline runs entirely in memory before any graph write. This guarantees the graph is never left in a partially-atomized state.

sequenceDiagram
    actor User
    participant API
    participant IS as IngestService
    participant PS as PatternService
    participant GR as GraphRepository
    participant NEO as Neo4j

    User->>API: POST /v1/notes/import
    API->>IS: ingest(payload)
    IS->>IS: segment_text()
    IS->>IS: extract_entities()
    IS->>IS: detect_events()
    IS->>IS: annotate_confidence()
    IS->>PS: detect_patterns(atoms, events)
    PS-->>IS: pattern_instances[]
    IS->>GR: save_narrative()
    IS->>GR: save_scene() × N
    GR->>NEO: MERGE nodes + relationships
    NEO-->>GR: ok
    GR-->>IS: done
    IS-->>API: IngestResult
    API-->>User: 201 + IngestResult

Deployment Topology

flowchart LR
    subgraph Host["Docker Compose Host"]
        subgraph AppContainer["app (tng-app:0.1.0)"]
            API["FastAPI / Uvicorn :8000"]
        end
        subgraph Neo4jContainer["neo4j (2026.04.0-community)"]
            NEO["Neo4j\nBolt :7687\nHTTP :7474"]
        end
        SecretFile["secrets/neo4j_auth.txt"]
    end
    Client["HTTP Client"] -->|:8000| API
    API -->|Bolt :7687| NEO
    SecretFile -.->|NEO4J_AUTH_FILE| Neo4jContainer

Design Patterns Used

Pattern Location Purpose
Repository GraphRepository Abstracts all Cypher; no raw queries leak into services
Service Layer *Service classes Encapsulates business logic; services are the only callers of the repository
Strategy RendererProtocol, PatternMatcher Pluggable renderers and pattern matchers via Python Protocols
Factory / DI dependencies.py FastAPI dependency providers; overridden in tests
DTO api/schemas.py Request/response models are separate from domain models
Value Object All enumerations Bounded vocabularies enforced at the type level

Security Boundaries

  • All API input is validated by Pydantic before reaching any service
  • All Cypher parameters are driver-parameterised (never string-interpolated)
  • Secrets are injected via Docker secrets file, never environment variables or source
  • The Neo4j HTTP port (7474) must not be exposed to the public internet in production