Architecture Principles
Rules that guide every design decision. When in doubt, refer here.
1. Dependencies Point Inward
The most important rule from Clean Architecture. Inner layers never know about outer layers.
| Layer (inner → outer) | Knows about | Does NOT know about |
|---|---|---|
| Domain | Nothing | Everything else |
| Use Cases | Domain + interfaces | Adapters, Frameworks |
| Adapters | Use Cases + external SDKs | Frameworks |
| Frameworks (Program.cs) | Everything | — |
Test: Can you delete the database and the Domain layer still compiles? Yes → correct.
2. Event Store API Is the Integration Layer
All services communicate through the Event Store API. No direct service-to-service calls for writes.
graph LR
B[Book-E] --> ES[Event Store API]
R[Review-E] --> ES
ES --> A[Accounting API]
ES --> C[Cost API]
Why: - No coupling between services - Services can restart without affecting others - Natural audit trail (the event log IS the history) - Add new services by subscribing to events — no changes to existing services
3. Every Write Action Is an Event
Nothing happens without an event. No direct database writes, no direct API calls.
PROPOSED → APPROVED → EXECUTED
Why: - Full audit trail (bokføringsloven requires 5 years) - Review before execution - Replay capability - Undo via compensating events
4. Domain Has No I/O
Domain classes are pure functions. Data in, result out. No database, no HTTP, no file system.
// ✅ Correct: pure logic
var score = confidenceScorer.Score(proposal, pattern);
// ❌ Wrong: Domain fetches its own data
var score = confidenceScorer.ScoreFromDatabase(merchantName);
Why: - Unit testable without mocks - Portable — works in any context - No hidden dependencies
5. Secret Isolation
No service has secrets it doesn't need.
| Secret | Book-E | Event Store API | Accounting API | Cost API |
|---|---|---|---|---|
| Discord token | Yes | — | — | — |
| LLM key | Yes | — | — | — |
| Postgres creds | — | Yes | — | — |
| Folio token | — | — | Yes | — |
| Fiken token | — | — | Yes | — |
| Anthropic usage key | — | — | — | Yes |
| Google Cloud key | — | — | — | Yes |
| Cloudflare token | — | — | — | Yes |
6. Everything Is GitOps
No manual kubectl commands. No SSH to apply changes.
Code change → PR → Review → Merge → CI builds image →
CI updates manifest with SHA → ArgoCD syncs → New pod running
7. Agents Propose, They Don't Execute
AI agents are not trusted to execute financial operations directly.
Book-E → PROPOSE (with confidence score)
→ High confidence (known pattern): auto-approve
→ Low confidence (new merchant): Review-E or human approves
→ On approval: Event Store API dispatches to Accounting/Cost API
8. One External System Per Adapter
Each external system gets exactly one Adapter component.
| Component | Owns communication with |
|---|---|
| FolioProvider | Folio API v2 |
| FikenProvider | Fiken API v2 |
| EventStore | PostgreSQL events table |
| PatternStore | PostgreSQL patterns table |
| AnthropicUsageProvider | Anthropic Admin API |
| GoogleUsageProvider | Google Cloud Billing API |
| CloudflareUsageProvider | Cloudflare API |
9. One Repo, One Namespace, Multiple Services
All services live in the ai-accountant repo and deploy to the ai-accountant k8s namespace.
ai-accountant/
├── src/
│ ├── EventStoreApi/ ← Event Store API
│ ├── AccountingApi/ ← Accounting API
│ ├── CostApi/ ← Cost API
│ └── Shared/ ← Shared types
├── deploy/k8s/ ← All manifests
├── agent/ ← Book-E character config
└── docs-site/ ← Architecture docs
Why one repo: - Services share types (event records, interfaces) - Deploy together via ArgoCD pointing to one path - Simpler CI — one build workflow per service - Shared docs site
10. Documentation Lives With Code
Architecture docs are in the repo, deployed automatically, versioned with git.
- C4 diagrams as PlantUML source (
.puml) - Non-C4 diagrams as Mermaid (inline in markdown)
- CI test:
mkdocs build --strictfails on broken docs - No ASCII art — diagrams as code only