Platform architecture

Ports, adapters, and a boundary that is actually checked

Enough of the technical shape to judge whether the platform stays testable as it grows. Implementation detail lives in the linked design documents.

The shape

Three processes, one database, one message broker. The conceptual picture first.

An HTTP service accepts requests and returns canonical state. Work that outlives a request is handed to a worker over a queue. Generation runs inside a state graph that can suspend for as long as an editorial review takes and resume where it stopped. Everything durable lives in PostgreSQL.

Nothing in that description is unusual, which is the intent. The design choices worth examining are where the boundaries fall and what enforces them.

The back of a studio equipment rack, showing two rows of patch-bay jacks and cable looms dressed away to one side.
Ports and adapters, in the older sense. What matters is that every connection is named and reachable.

The boundary rule

Hexagonal architecture is a claim many projects make. This one is testable.

Domain logic depends on ports. Infrastructure supplies adapters. Nothing in the domain imports a framework, a driver, or a provider client.

Import boundaries are enforced by lint rules in CI rather than by review.

That distinction matters more than the pattern. An import rule that only exists in a contributing guide degrades on a deadline; one that fails the build does not. The check runs as part of the standard lint target, so a violation stops a change before review rather than during it.

ADR 014: Hexagonal architecture enforcement (opens the Episodic repository)

Component catalogue

Responsibility, adapter, and where each component keeps state.

Persistence layer

Available

Stores canonical episodes, TEI headers, profiles, templates, reference documents, bindings, and ingestion records.

Adapter
SQLAlchemy repositories behind per-aggregate interfaces, with Alembic migrations gated in CI.
State
PostgreSQL. Large TEI payloads compress with standard-library Zstandard; read paths return plain text.
Repository and unit-of-work implementation (opens the Episodic repository)

Multi-source ingestion

Available

Normalizes heterogeneous sources into canonical TEI and captures provenance in the header.

Adapter
Document classifiers, source weighting heuristics, and a conflict-resolution matrix.
State
Writes canonical episodes and retains every source, preferred or rejected.
Multi-source ingestion service (opens the Episodic repository)

A request through the system

The source-to-script path, as an ordered sequence. No diagram required.

  1. Authorization middleware resolves the principal before any resource runs.
  2. A client posts a source document with an idempotency key. The upload is stored and recorded.
  3. The client creates an ingestion job and attaches the upload or a remote source URI.
  4. The job is accepted; normalization runs as worker tasks off the request path.
  5. Sources are classified, weighted, and merged into a canonical TEI episode with provenance in its header.
  6. The client polls the job until the source context is ready.
  7. Generation composes a structured brief with pinned reference revisions and guardrails, then runs the graph.
  8. Evaluators execute in parallel; findings aggregate and the graph routes on the result.
  9. The run suspends at the editorial checkpoint, holding durable state until a person decides.

The authorization and source-ingestion path is implemented. Generation and evaluation are partly built. The editorial checkpoint is designed. See the stage-by-stage maturity

Infrastructure

DigitalOcean Kubernetes, reconciled by FluxCD. The application owns less of this than it might appear.

OpenTofu provisions the clusters and shared services. FluxCD reconciles platform and application manifests from a GitOps repository. Postgres is supplied by the CloudNativePG operator, cache by the Valkey operator, and messaging by the RabbitMQ operator. Observability is Prometheus, Tempo, and Loki behind an OpenTelemetry Collector. Ingress is Traefik with cert-manager issuing certificates.

Episodic supplies an image, a runtime contract, health semantics, and a Helm chart. The platform supplies everything else. That split is the subject of its own page, because conflating the two is how a working chart gets mistaken for a deployed environment.

Application and platform ownership
ConcernOwned byDetail
Container image Episodic Builds a wheel in a builder stage, installs it into a slim runtime stage, and declares its own HEALTHCHECK.
Health semantics Episodic Liveness and readiness meanings live in the domain; the Falcon adapter maps them to status codes.
Helm chart and application values Episodic charts/episodic renders Deployment, Service, ConfigMap, ServiceAccount, and optional Ingress, ExternalSecret, and PodDisruptionBudget.
Kubernetes cluster and ingress Nile Valley Shared cluster provisioning, Traefik ingress, certificates, and DNS.
GitOps reconciliation Nile Valley FluxCD reconciles desired state from the GitOps repository. Application automation publishes an immutable image and desired Helm state; it does not deploy workloads directly.
Secret material Nile Valley Secret sync from the shared Vault appliance. Git holds references, never secret values.
Database and cache Nile Valley CloudNativePG and the Valkey operator supply the shared data services.

Read the Nile Valley hosting contract · See which interfaces are live