> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ADR-003 — Hexagonal Architecture and the Closed Port List

> Status: Proposed (RECONSTRUCTION — requires validation) · Date: 2026-08-17 (reconstructed) Refs: ../constitution/founding-constitution.md.

Status: **Proposed (RECONSTRUCTION — requires validation)** · Date: 2026-08-17 (reconstructed)
Refs: [`../constitution/founding-constitution.md`](/constitution/founding-constitution)

> ⚠️ Reconstructed from the material we have, not recovered. Tags: **\[derived]** is supported by
> evidence in the repository · **\[inferred]** is deduced · **\[proposed]** is a gap I filled.

## Context

A platform that intends to outlive its vendors has to be able to replace one without a rewrite. The
usual answer is "use interfaces", which decays because nobody agrees on what deserves one — and in a
codebase where agents write most of the code, that disagreement compounds every week. **\[inferred]**

## Decision

**Hexagonal architecture without exception.** \[derived] Domain and application code depends on ports.
A vendor SDK imported outside an adapter file is a defect, enforced by lint.

**The list of port categories is closed.** \[derived] Six categories exist; adding one requires a
constitution amendment reviewed line by line by the owner. That closure is the load-bearing part:
without it, "use a port" becomes "invent an abstraction", and an abstraction invented per pull
request is worse than none.

Bindings are declared in each module's composition root, `makeDeps(ctx)`. \[derived]

The six categories are reconstructed with low confidence in
[`../constitution/founding-constitution.md`](/constitution/founding-constitution) Part 2.
**\[inferred]** Amendment A1 adds `QueuePort` and `NotificationChannelPort` as seven and eight.

## Consequences

* A vendor change is an adapter plus an ADR, not a migration.
* Agents cannot invent abstractions, because the list of allowed ones is finite and named.
  − Some genuinely useful abstractions will be blocked until an amendment. That cost is accepted: the
  amendment ritual is cheap and rare, and the alternative is unbounded.

## What I could not determine

Whether the database and authentication were deliberately excluded from the port list or simply
never came up. R8's `getDb(tenantCtx)` suggests deliberate exclusion. **\[inferred]**
