Ports & Adapters
Vinxi Kernel — Ports & Adapters
The stack, restructured as interfaces and implementations. Each port is a generic, kernel-owned contract the operating system depends on; each adapter is a concrete technology that satisfies it. Ports are stable and versioned with the kernel; adapters are chosen per deployment, scale, and mode. Supersedes the flat component catalog of hld-historical.md; honors the invariants of the-seam.md.
1. Why ports and adapters
This is the design law turned on the kernel’s own infrastructure. The port is the closed mechanism the kernel owns — a narrow contract phrased in the vocabulary of the six verbs and three planes. The adapter is the open choice — a swappable technology behind that contract. It is also the literal realization of the “device drivers for infrastructure” thesis: the kernel defines the driver interface; FoundationDB, Spark, SedonaDB, ClickHouse are drivers.
A port is drawn correctly when one test holds: swapping its adapter is invisible to userland. If replacing Postgres with FoundationDB, or Spark with SedonaDB, changes what a workflow sees or how an Action behaves, the port has leaked implementation and must be re-cut. Three rules keep ports honest:
- Narrow and verb-aligned. The contract exposes only what a verb needs, never the store’s native surface. No SQL leaks through the State port; no Kafka semantics leak through the Log port.
- Every port has a fake. An in-memory adapter exists for tests and local dev. If you can’t write the fake, the contract is too broad.
- Capability-injected, never ambient. Adapters receive their credentials and scope from the kernel; they hold no ambient authority of their own.
The catalog below is the full port surface, grouped by concern, each with candidate adapters and the axis that decides between them. §3 resolves them into reference profiles per deployment mode.
2. The interface catalog
I. Storage & data planes
P1 · Transactional State Store (operational plane) Contract: a serializable transaction that mutates typed records and appends the outbox row atomically, returning a monotonic ordering token; point reads for read-your-write; secondary, geo (H3/S2 cell), and graph-adjacency index lookups. Rebuildable from P3 + P2.
- FoundationDB + Record Layer — ordered KV, serializable ACID, versionstamp is the ordering token; graph/geo via keyspace. Scales far, air-gap-clean; real build cost.
- TiKV — Raft-replicated ordered KV, simpler ops than FDB, CNCF; smaller ecosystem for typed records.
- Postgres + Citus + PostGIS — richest geo, most familiar, LSN as ordering token; sharded scale has a ceiling.
- CockroachDB / YugabyteDB — distributed SQL, geo-partitioning, SQL ergonomics; heavier, geo less rich than PostGIS. Chooses on: scale ceiling vs geo richness vs operational familiarity.
P2 · Ordered Effect Log (spine)
Contract: append a fact preserving global order (per P1’s token); consume from an offset with exactly-once semantics; tail; tiered archive to blob. Carries Act events and Call facts only.
- Redpanda — Kafka API, single C++ binary, tiered storage; light for on-prem/edge.
- Apache Kafka — the ecosystem standard; heavier (JVM, coordination).
- NATS JetStream — lightweight, simple, great for edge; lower throughput ceiling.
- Apache Pulsar — native tiered storage, multi-tenant; operationally complex. Chooses on: throughput vs operational weight vs edge footprint.
P3 · Canonical State / Lakehouse (analytical plane) Contract: write canonical current-state; snapshot on commit; as-of read; branch/clone (copy-on-write); schema evolution. Open format for air-gap portability.
- Iceberg + Nessie — open, snapshots for free, git-like catalog branching that maps onto reality-branches.
- Iceberg + Polaris — same format, governance-forward REST catalog; branching less central.
- Delta Lake — strong tooling, Databricks-native; less open across engines.
- Apache Hudi — upsert/CDC-optimized; best when writes are mutation-heavy. Chooses on: branch semantics vs ecosystem vs upsert-heavy workloads.
P4 · Time-Series / Assert Store
Contract: append bitemporal declarations (trait, value, validTime, systemTime, source); last-value; windowed aggregation; retention horizon. High volume, off the decision log.
- ClickHouse — columnar, enormous scale, windows, materialized views.
- TimescaleDB — Postgres extension, familiar, moderate scale.
- Apache Druid / Pinot — sub-second real-time OLAP; ops-heavy.
- QuestDB — lean, very fast ingest; smaller ecosystem. Chooses on: raw volume vs Postgres-familiarity vs sub-second serving.
P5 · Search Index Contract: index an entity document; full-text + faceted + filter search. Derived, rebuildable from P3/P2.
- Quickwit — object-storage-native, Rust, cheap, decoupled compute/storage; air-gap-friendly.
- OpenSearch / Elasticsearch — mature, rich query DSL; heavier to run.
- Tantivy — embedded Rust index for edge/single-node.
- Typesense / Meilisearch — simple, fast, small-to-mid scale. Chooses on: scale vs query richness vs footprint.
P6 · Blob Store Contract: get / put / list immutable objects. Substrate under P3 and the log archive.
- MinIO — S3-compatible, self-hosted; the air-gap/on-prem default.
- S3 / Azure Blob / GCS — managed, in cloud modes.
- Ceph — on-prem block + object where storage is owned end-to-end. Chooses on: deployment mode.
II. Compute & execution
P7 · Batch / Transform Pipeline Contract: run a transform job (datasets → dataset), including spatial operations; feature computation; reindex and backfill. Idempotent, re-runnable.
- Spark on Kubernetes + Sedona — cluster-scale, rich spatial; heavy JVM footprint.
- SedonaDB / Sedona — spatial-first; ideal when geo dominates the transform.
- DuckDB — single-node, fast, embeddable, zero cluster; bounded by one machine.
- Daft — Rust/Python distributed dataframe, modern, multimodal; younger ecosystem.
- Polars — single-node Rust dataframe, very fast; no distribution. Chooses on: data size (single-node vs cluster) vs geo-heaviness vs ML needs.
P8 · Stream Materializer (the Funnel role) Contract: consume the log → derive P1/P4/P5 state; stateful, exactly-once; separated from the query path.
- Apache Flink — stateful, exactly-once, mature; heavy.
- Arroyo — Rust, SQL streaming, lighter footprint.
- RisingWave — streaming database, materialized views in SQL.
- Custom Rust consumer — simplest for direct sinks with little state. Chooses on: state complexity vs operational weight.
P9 · Function Sandbox Contract: instantiate with only the kernel-injected verb host-functions; execute; resource-limit; deny-by-default. This is “no ambient authority,” mechanized.
- WASM (Wasmtime / WasmEdge, Component Model) — capability-clean, multi-language, light; some runtime maturity gaps.
- Firecracker microVM — strong isolation, runs arbitrary code; heavier, slower start.
- gVisor — syscall interception, container-compatible; syscall-coverage caveats.
- V8 isolates / Deno — fast start, JS/TS only. Chooses on: capability-cleanliness vs code-compatibility vs startup latency.
P10 · Workflow Engine Contract: durable workflow of activities (each a verb); signals; timers; retries; deterministic replay.
- Temporal — mature, durable, polyglot; a real cluster to run.
- Restate — Rust, lighter, durable execution; younger.
- DBOS — Postgres-backed durable execution; minimal infra.
- Windmill — scripts + flows, low-code leaning. Chooses on: maturity vs footprint vs Postgres-native simplicity.
P11 · Analytical Query Engine Contract: federated SQL over P3 + external sources; as-of via snapshots; cross-entity and historical reads.
- Trino — federation king; query lakehouse + external in place.
- DuckDB — embedded, single-node, fast; in-branch and edge reads.
- DataFusion — Rust embeddable engine to build on.
- StarRocks — fast lakehouse OLAP serving. Chooses on: federation vs embeddability vs raw OLAP speed.
III. Governance & identity
P12 · Relationship / Authorization Store (ReBAC)
Contract: write a relation; check(subject, permission, resource); reverse-lookup resources/subjects; watch for changes.
- SpiceDB — Zanzibar-faithful, mature, watch API, caveats.
- OpenFGA — CNCF, lighter, simpler model.
- Ory Keto — Zanzibar-style, Go. Chooses on: scale/features vs simplicity.
P13 · Policy Decision (ABAC)
Contract: decide(principal, action, resource, context) → permit/deny with reasons. Context predicates evaluated at acquire/invoke.
- Cedar — purpose-built for authz, formally analyzable, Rust.
- OPA / Rego — ubiquitous, general-purpose policy.
- Casbin — lightweight, embeddable. Chooses on: analyzability vs generality.
P14 · Capability Token
Contract: mint(scope, caveats) a signed, attenuable token; verify(token, context) offline. The typed token the SDK carries.
- Biscuit — attenuable, offline-verifiable, Datalog caveats; ideal for air-gap.
- Macaroons — caveat-based, older, less tooling.
- Signed JWT — simple, ubiquitous; no attenuation. Chooses on: attenuation + offline verification vs simplicity.
P15 · Identity Provider Contract: authenticate an Actor → a verifiable identity assertion. Human and workload are distinct sub-ports.
- Human: Zitadel · Keycloak · Authentik · Ory — self-hostable OIDC for air-gap.
- Workload / agent: SPIFFE / SPIRE — attestable SVIDs for every service and agent. Chooses on: self-hostability (air-gap) and human-vs-workload.
IV. Boundary
P16 · Egress Gateway (the Call chokepoint)
Contract: crossing(driverCapability, request) → audited response; enforce which driver holds which external capability; rate-limit; record the fact.
- Envoy — mature proxy,
ext_authz, rich policy. - Custom Rust proxy — tailored, minimal footprint.
- Cilium / eBPF egress — network-level enforcement. Chooses on: policy richness vs footprint.
P17 · Connector / CDC Ingest (the Assert source)
Contract: source(config) → a stream of asserted facts; external changes become Asserts, never decision Events.
- Debezium — high-fidelity CDC from databases, Kafka-native.
- Airbyte — broad connector catalog, batch + CDC.
- Custom driver SDK — bespoke systems the connectors don’t cover. Chooses on: CDC fidelity vs connector breadth.
V. Contract & SDK
P18 · SDK Projection Target Contract: Reality IR → a typed, branded client package for language L (value types, Actions, Events, capability tokens). Re-emitted on schema change.
- Conjure-style generators — reuse/fork the IR→client machinery (TS · Python · Rust · Java).
- Smithy — rich API modeling with multi-language codegen.
- Buf/Protobuf plugins — if the base contract is protobuf-first. Chooses on: which languages userland writes in, and how rich the branded types must be.
P19 · Transport / RPC
Contract: the wire contract for verb calls; browser and backend; streaming for Subscribe.
- Connect / gRPC (Buf) — one contract for browser + backend, streaming, fast.
- Conjure + Dialogue — HTTP/JSON with client-side load balancing and concurrency limiting.
- Plain REST/JSON — simplest, for low-stakes clients. Chooses on: streaming/performance vs simplicity.
VI. Runtime & delivery
P20 · Compute Orchestrator Contract: schedule services and sandboxes; scale; health; secrets injection.
- Kubernetes — universal target for every mode; hardened (Rubix-style) for security.
- Nomad — lighter, simpler; good for smaller/edge.
- systemd / bare — the thinnest edge slice. Chooses on: ecosystem vs footprint (edge).
P21 · Desired-State Deployment (multi-mode / air-gap) Contract: publish desired state; reconcile at each environment; transfer as signed bundles across a gap; release channels per environment.
- Argo CD / Flux — GitOps for connected environments.
- Apollo-style hub/spoke — a Hub computes desired state, spokes reconcile signed bundles; the air-gap answer.
- Fleet / Rancher — multi-cluster management. Chooses on: connectivity — connected vs disconnected/air-gapped.
P22 · Observability Sink Contract: OTLP ingest of traces/metrics/logs; query. (Audit is not here — it falls out of P2.)
- OpenTelemetry → ClickHouse → Grafana — unified store, reuses P4.
- SigNoz — bundled OTel-native stack.
- Prometheus + Tempo + Loki — best-of-breed Grafana stack. Chooses on: unified store vs best-of-breed components.
3. Reference profiles
The same ports, resolved to one adapter each for three deployment shapes. Lean builds fast at moderate scale; Scale is the Jio-scale target; Air-gapped is fully self-hosted with offline verification and open formats.
| Port | Lean / starter | Scale | Air-gapped |
|---|---|---|---|
| P1 State store | Postgres + Citus + PostGIS | FoundationDB + Record Layer | FoundationDB (or Postgres+Citus) |
| P2 Effect log | NATS JetStream | Redpanda | Redpanda |
| P3 Lakehouse | Iceberg + Nessie / MinIO | Iceberg + Nessie / S3 | Iceberg + Nessie / MinIO |
| P4 Assert store | TimescaleDB | ClickHouse | ClickHouse |
| P5 Search | Meilisearch | Quickwit | Quickwit |
| P6 Blob | MinIO | S3 | MinIO / Ceph |
| P7 Transform | DuckDB | Spark + Sedona / Daft | Spark / DuckDB |
| P8 Materializer | Custom Rust consumer | Flink | Flink / Arroyo |
| P9 Sandbox | WASM (Wasmtime) | WASM + Firecracker | WASM + Firecracker |
| P10 Workflow | DBOS | Temporal | Temporal / Restate |
| P11 Query | DuckDB | Trino | Trino / DuckDB |
| P12 ReBAC | OpenFGA | SpiceDB | SpiceDB |
| P13 ABAC | Cedar | Cedar | Cedar |
| P14 Capability | Biscuit | Biscuit | Biscuit (offline) |
| P15 Identity | Keycloak + SPIRE | Zitadel + SPIRE | Keycloak + SPIRE |
| P16 Egress | Custom Rust proxy | Envoy | Custom Rust proxy |
| P17 Ingest | Debezium | Debezium | Custom driver SDK |
| P18 SDK gen | TS + Python | TS + Python + Rust | TS + Python + Rust |
| P19 Transport | Connect | Connect + gRPC | Connect |
| P20 Orchestrator | Nomad / K8s | Kubernetes (Rubix-style) | Kubernetes (hardened) |
| P21 Deployment | Argo CD | Argo + Apollo-style hub/spoke | Apollo-style signed bundles |
| P22 Observability | SigNoz | OTel → ClickHouse → Grafana | SigNoz / OTel → ClickHouse |
The value of the matrix: the rows are permanent (the OS surface), the columns move. A customer graduating from Lean to Scale swaps adapters column-by-column without the kernel or any userland program changing — which is the whole point of drawing the ports first.
4. What stays fixed, what moves
- Fixed (the operating system): the port contracts, the six verbs they are phrased in, the three planes they populate, and the invariant that swapping an adapter is invisible above the seam.
- Moves (the engineering): every adapter, chosen per scale, geo-intensity, connectivity, and footprint — and re-chosen over a product’s life without a rewrite.
The forks from the HLD now have their proper home: they are not architecture decisions, they are adapter selections behind stable ports — FoundationDB vs Postgres is a P1 choice, Redpanda vs Kafka a P2 choice, Spark vs SedonaDB a P7 choice, WASM vs microVM a P9 choice. The only genuine architecture questions left are the three upstream ones in the seam doc — the Assert→Act promotion boundary, the atomicity boundary, and the trait-kinds closed set — because those change the ports, not the adapters.
5. Adjudicating an external stack proposal
A separate Foundry-inspired HLD proposed a full technology stack. Run against these ports, it resolves cleanly: it is a well-chosen Lean profile with four component-level gaps and five contested rows, plus it copies Palantir’s product shape (Ontology, Actions, Funnel, SDK, MCP) while dropping three of its infrastructure lessons (Conjure-generated SDK, catalog branching, the federated read path). That split — product shape kept, plumbing missed — is exactly what a proposal grounded in Palantir’s public product docs rather than its open-source infra produces. The matrix below folds its picks into the profile column they belong to and marks the verdict.
| Port | Proposed pick | Verdict |
|---|---|---|
| P1 State | Postgres + PostGIS | = Lean; omits the FDB scale path |
| P2 Log | NATS → Kafka/Redpanda | = Lean→Scale, agrees |
| P3 Lakehouse | Iceberg / MinIO | agrees on format; misses the catalog (Nessie) — where branching lives |
| P4 Assert store | ClickHouse | Scale pick used in a Lean stack; defensible default |
| P5 Search | OpenSearch / Typesense | misses Quickwit — the air-gap-native option |
| P7 Transform | (conflated with P8) | conflates transform and materialize — keep them split |
| P8 Materializer | custom → Flink / RisingWave | = Lean→Scale, agrees |
| P9 Sandbox | isolated containers | contested — too weak a default (see below) |
| P10 Workflow | Temporal or custom | = Scale / Lean, agrees |
| P11 Query | DuckDB / ClickHouse | omits Trino — the federation / don’t-ingest-everything path |
| P12 ReBAC | ”custom relationship checks” | contested — reject custom authz (see below) |
| P13 ABAC | OPA or Cedar | agrees |
| P14 Capability | (none specified) | gap — the policy model rests on a token; add Biscuit |
| P15 Identity | Keycloak | = Lean; omits workload identity (SPIRE) — agents need attestable identity |
| P16 Egress | container isolation | agrees in spirit |
| P17 Ingest | CDC (Debezium implied) | agrees |
| P18/19 SDK/RPC | FastAPI + gRPC | contested — gRPC fine for transport; FastAPI is not the generated SDK (see below) |
| P20 Orchestrator | Compose → K8s | agrees (adds Compose for dev) |
| P21 Deploy | offline bundle | agrees; doesn’t name the pull / hub-spoke mechanism |
| P22 Observability | Prometheus + Grafana + Loki | best-of-breed vs unified — genuine taste fork |
The five contested rows, resolved against ours:
- P7 / P8 — un-conflate. The proposal folds batch transform and stream materialization into one bucket. They are two ports that graduate independently: you can run custom-Rust materializers (P8) for years while batch transform (P7) is already Spark + Sedona for geo. Keep them separate.
- P9 — hold WASM as the function/agent default, not containers. A container still carries ambient authority inside it — sockets, env-var credentials, a reachable DB if misconfigured. WASM with the component model has no imports except the injected verbs; there is nothing to reach around. The “million agent loops are safe” invariant depends on deny-by-default-with-zero-ambient-authority, which containers lack natively. Containers/microVMs are for drivers and heavy code only.
- P12 — reject custom ReBAC. Authorization is the highest-blast-radius code in the system and a Zanzibar-shaped engine is a solved problem. OpenFGA is the Lean answer, SpiceDB the Scale one. Rolling your own relationship checks is precisely the freeze-the-primitive failure the policy split exists to prevent.
- P14 — add the capability token. The proposal specifies none, but the entire policy model — coarse-capabilities-in-types, acquire/invoke, attenuated delegation for sub-agents — rests on an attenuable, offline-verifiable token. That is Biscuit, in every profile. Not a Lean-vs-Scale choice; a missing load-bearing piece.
- P18/19 — keep the generated SDK, not FastAPI. gRPC/Connect for transport is fine. But FastAPI gives a hand-written API, not the IR-driven, per-reality generated typed client that is the whole “compile reality into a typed SDK” payoff. The Conjure-style projector cannot be replaced by FastAPI.
Three infrastructure lessons the proposal drops (worth restoring even in a Lean build): the Conjure-style SDK projector (P18 — generate the client from the live schema, don’t hand-write it), the Nessie catalog (P3 — catalog-level branching is where simulation physically lives), and Trino / federation (P11 — federate what you needn’t mirror, the read path agents actually hammer).
What the proposal adds that we should take — tracked for the seam doc, not the ports: multi-source fusion with a ResolvedValue (value · uncertainty · settledness · derivedFrom · policy) as the World-plane answer to contested truth; driver authority (EXTERNAL_SOR / KERNEL_SOR / PEER_SOR) and actuationMode (SYNC / ROUND_TRIP) with correlation-id reconciliation on the driver contract; and the five-plane cut (Process / Truth / World / Serving / Materialization) which splits Process from Serving and World from Serving more cleanly than our three planes. Two additions to resist: “Statement” as an ingestion primitive that competes with the verb (the verb stays the primary classifier — a Statement is the payload shape of an Assert, not an alternative to it), and the flat stack as the architecture (it is the Lean profile).
6. Catalog amendments — adjudicated and adopted
The following amendments were adjudicated (against the two external proposals) and are now canonical; full contracts live in lld.md. This catalog’s §2 should be read with these applied.
New ports.
- P0 · Ontology Registry — versioned ontology per Realm; schema changes are Acts; kind/value-type registry; compatibility checks; the Reality-IR projection feed. Numbered zero because every other port depends on schema. (LLD §3.)
- P23 · World Projection Store — the World plane’s store:
ResolvedValue(value · uncertainty · settledness · derivedFrom · policy), never a naked scalar; fusion declared in P0, executed in P8, stored here; branch overlays; rebuildable from Truth. Relieves P1 and P3 of semantic weight they were never contracted for. (LLD §3.) - P24 · Visual Projection — tiles, 3D tilesets, rasters, and the live trait-overlay channel as Serving-plane projections;
featureId == entityId; bake-geometry / stream-traits per the Act/Assert split; policy-partitioned serving. (LLD §5.)
Refinements to existing ports.
- P2 is a log family, not one log: StatementLog · DecisionLog · CallLog · SchemaLog · PolicyLog — distinct retention, markings, and replay semantics under one ordered-append contract.
- Statement, corrected: an evidence claim — usually carried by an Assert, but also produced by Acts as state effects and returned by Calls. Cross-cutting record shape; not an eighth primitive; the verb remains the primary classifier.
- Branch semantics are kernel-owned. A branch is base-snapshot pointer + statement overlay + branch-local act log + call stubs + merge policy, defined by the kernel; Nessie/Iceberg are demoted to mechanisms that may implement the copy-on-write, never the definition.
- Ports are tiered. Tier 1 (kernel-critical, day-one contracts): P0–P4, P8–P10, P12–P19, P21, P23. Tier 2 (secondary/read-model): P5–P7, P11, P20, P22, P24. Tier is about dependency order, not importance — P24 is tier 2 and product-critical.
- The truth phrasing, corrected: the log is truth of history; the Process plane is truth of action; the World plane is truth of coherent current interpretation; the Serving plane is truth of nothing. “Analytical” names a physical store where World materializes — it is a projection, and it still wins state conflicts against caches because it is rebuilt from truth.
- Lean is the honest first build. Postgres/PostGIS + outbox, NATS/Redpanda, custom materializer, DuckDB, OpenFGA/Cedar, signed-token-then-Biscuit, TS SDK first. FDB, Flink, Trino, Quickwit are Scale-tier graduations gated on observed evidence, never day-one commitments. The Apollo air-gap mechanics and Rubix node-cycling details are marked inspiration, not verified Palantir design facts.
Second amendment — post-LLD sync (journeys run, type system completed)
- P25 · Model Inference and P26 · Realtime Session Gateway are canonical (LLD §11, §17.2). P24 remains tier-2/product-critical.
graph-edgeis renamedlinkand all kinds are now parameterized constructors —numeric(decimal(p,s)),categorical(scheme, hierarchical?),link(target, class, cardinality)— nine constructors total, incl.text,vector,document(LLD §19–20). Interfaces (structural contracts) join P0.- Three IRs are canonical: Reality IR (schema), Query IR (reads), Surface IR (apps) — every IR machine-produced and machine-consumed; hand-written instances are defects (LLD §21).
- The acceptance test was run (two journeys, two stacks, 17 steps): all steps mapped to existing ports — the catalog’s port set is complete for the tested journeys. Nine findings (G1–G9) are contract-depth items tracked in the LLD register, none port-level.
- The flat HLD (
hld-historical.md) is now historical: superseded by this catalog (architecture contract) + the LLD (full depth). Its remaining unique value — the Palantir-lessons table — was absorbed into both.