4. Solution Strategy¶
Six architectural moves define Finnest. Each trades complexity against predictability; each is ratified by an ADR.
1. Supervised Modular Monolith¶
What: 21 independently-supervised OTP applications in one Elixir release, sharing one PostgreSQL cluster.
Why: 90 % of microservice benefits (independent failure domains, independent supervision, independent scaling via process allocation) for 10 % of microservice operational cost (no Kubernetes, no service mesh, no distributed tracing assembly, no saga orchestration).
Ratified: ADR-002-F, origin idea: brainstorm-02.
2. Three-tier AI agent architecture¶
What: Every agent is classified as conversational (sync, chat), workflow (async, multi-step), or autonomous (long-running, budgeted). Each tier has distinct process lifetime, memory, and tool authorisation.
Why: Prevents a runaway autonomous agent from blocking a chat reply; makes per-tier cost ceilings enforceable; isolates failure domains.
Ratified: ADR-003-F, origin idea: brainstorm-03.
3. MCP at every domain boundary¶
What: Every one of the 21 domains publishes its capabilities as typed tools via Model Context Protocol. AI agents and the UI (Cmd+K command bar) both consume via the same MCP interface.
Why: One mental model for "how do I invoke X?" whether you're an agent or a human. Domain extraction into a separate service later is a config change, not a rewrite.
Ratified: ADR-004-F.
4. Events-only cross-domain (one documented exception)¶
What: Domains emit events to the shared event store; downstream domains
subscribe. Exactly one synchronous exception: Compliance.check/2, called
before write operations in five domains (roster, timekeep, payroll, clients,
assets).
Why: Event sourcing satisfies both cross-domain communication and IRAP audit trail from the same substrate. The one synchronous exception is necessary for the compliance auto-blocking constraint.
Ratified: ADR-005-F, reinforcement: ADR-011-F.
5. Hexagonal ports for every external integration¶
What: Every outbound API sits behind a behaviour module with at least two planned adapters. AI provider, award engine, file storage, secrets, communications, accounting, government APIs, document verification.
Why: Vendor replaceability is an architectural driver (D11). Testing against a stub adapter is trivial when the port is a behaviour.
Ratified: ADR-006-F.
6. Single release, dual deployment¶
What: Commercial SaaS (Anthropic Direct) and IRAP-certified (AWS Bedrock Sydney) deployments are produced from the same Elixir release via runtime configuration. A small Go proxy (~500 LOC) sits at the IRAP deployment boundary.
Why: Two codebases would double maintenance cost; conditional compilation
would poison every module with when :irap branches. Runtime config +
edge proxy is the only clean path.
Ratified: ADR-007-F, deep dive: architecture/irap.md.
How the moves compose¶
The six moves compose into a pattern sometimes called the "boring-core, sharp-edges" pattern:
- Boring core: one language (Elixir), one database (Postgres 17), one framework (Phoenix), one job system (Oban). No Redis, no Kafka, no Elasticsearch. Postgres does cache, pub/sub, queue, event store, and search.
- Sharp edges: aggressive isolation at domain boundaries (OTP apps, MCP, events), aggressive enforcement at integration boundaries (hexagonal ports, Compliance gate, IRAP Go proxy).
The trade-off is deliberate: operational simplicity inside, architectural discipline outside. See architecture/architecture.md for the complete synthesis.