Explanation
Ports and adapters
Why the product codebase is shaped the way it is.
The product repo follows a ports-and-adapters (hexagonal) architecture, per
ADR-0001 in the project repo.
The idea
Domain logic lives in Effect services behind ports — interfaces that describe capabilities (transcription sources, calendar, notifications) without naming a provider. Adapters at the edge implement those ports for a specific vendor: a transcription API, a calendar provider, Cloudflare services.
Why it matters
- Swapping a provider means writing a new adapter, not rewriting domain code.
- Tests run against in-memory port implementations — no network, no mocks of vendor SDKs.
- The boundary is enforced mechanically: an arch-lint policy
(
tools/arch-policy.mjsinproduct) forbids plugins and ports from importing adapters or touching platform APIs (node:*,Bun,process, hardcoded provider literals like@cf/*model ids).
Where it shows up
plugins/— feature plugins that must not import fromapps/; composition happens inapps/web/src/routes/via injectedapiobjects.infra/— provider adapters (e.g.infra/cloudflare) implementing the@ktb/infra-portsinterfaces, selected at the edge.