Architecture Decisions
This is the Architecture Decision Record (ADR) log for Portfolixir. Each ADR
captures one decision, the context that forced it, and its consequences.
AGENTS.md
requires that architecture decisions not change silently: when a decision
changes, add a new ADR and mark the old one as superseded rather than editing
history.
The records below were written after the fact to document decisions that were already in force in the codebase.
How to add an ADR
- Copy the template to
docs/decisions/NNNN-short-title.mdwith the next number. - Fill in context, decision, and consequences. Keep it short.
- Set the status to
Acceptedand add it to the list below. - To reverse a decision, add a new ADR and set the old one’s status to
Superseded by ADR-NNNN.
Records
| ADR | Title | Status |
|---|---|---|
| 0001 | Modular Phoenix monolith with bounded contexts | Accepted |
| 0002 | Thin MCP companion over the JSON API only | Accepted |
| 0003 | Decimal for all financial values | Accepted |
| 0004 | Holdings and trades derived from transaction history | Accepted |
| 0005 | Split quote providers: search vs. history | Accepted |
| 0006 | Classifications (taxonomies) with built-in derived trees | Accepted |
| 0007 | Currency conversion with exchange rates | Accepted |
| 0008 | Target weights and target/actual allocation | Accepted |
| 0009 | Cash as balance snapshots, not a mirrored ledger | Accepted |
| 0010 | Daily valuation series and TTWROR | Accepted |
| 0011 | Unified ledger projection (single per-kind reducer) | Accepted |
| 0012 | Asset class inference at read time | Accepted (pipeline summary corrected to the code by the note of 2026-10-03) |
| 0013 | Exclude flagged securities from the allocation steering basis | Superseded by 0018 |
| 0014 | Bilingual docs site (EN baseline, DE alongside) without a custom Pages build | Accepted |
| 0015 | Cross-currency transaction settlement with a stored FX rate | Accepted (amended 2026-10-07: a cross-currency booking’s fees and taxes are in its cash account’s currency; amended 2026-10-09: a closed trade is in one currency, whatever booked its lots, a third currency is refused, and the history backfill derives missing legs) |
| 0016 | Rounding policy — full precision in compute, round only at the human display | Accepted |
| 0017 | Append-only audit journal for financial writes | Accepted |
| 0018 | Buckets — tag-based wealth scoping with view filters | Accepted |
| 0019 | View-scoped performance treats boundary transfers as external flows | Accepted |
| 0020 | target plans belong to a view | Accepted |
| 0021 | In-app broker-PDF transaction intake (sandboxed, text-only) | Accepted |
| 0022 | Task-oriented UI information architecture | Accepted |
| 0023 | Drift sign convention and display-only rebalancing hints | Accepted |
| 0024 | Buckets and views replace portfolios as the user-facing grouping | Accepted |
| 0025 | Automation recipes — docs in the repo, broker scripts outside | Accepted |
| 0026 | Epic-batch workflow — humans review decisions and behavior | Accepted (risk-tier clause amended by 0036; merge-is-signature and owner-tag amendments 2026-09-07; two-PR amendment 2026-10-08: a sprint is a planning PR and one sprint PR) |
| 0027 | Named plan versions and ledger-marker depot snapshots | Accepted (transaction-cost amendment 2026-08-15) |
| 0028 | Corporate actions as ledger events — splits as a first-class kind | Accepted |
| 0029 | Stable identities and re-import survival — identity ladder with ISIN-change aliases | Accepted (amended by 0050: the Context sentence on account renames, hash-first ordering with retired hashes, the scoped in-run key, the former-name tier, §3’s repair via the security merge; noted by 0053: a CSV row’s hash reads the file’s Betrag) |
| 0030 | Position-level SOLL targets — positions as source of truth, categories as derived roll-up | Accepted |
| 0031 | Recorded tax-statement snapshots — capture the broker’s tax pots, never derive them | Accepted |
| 0032 | Memoized derived series — cache the daily TTWROR walk in volatile memory | Superseded by 0039 |
| 0033 | Per-position P&L decomposed — price return and currency return over a security-currency cost basis | Accepted |
| 0034 | Money-weighted metrics — net invested capital, wealth multiple, hand-rolled XIRR next to TTWROR | Accepted |
| 0035 | One pricing pass per read — shared preloaded market data instead of six re-derivations | Accepted |
| 0036 | Risk-tier work rides the batch — the dedicated-small-PR exception is withdrawn | Accepted (amends 0026) |
| 0037 | Phoenix 1.8 and LiveView 1.x — taken as a security upgrade, verified in a real browser | Accepted |
| 0038 | Continuous owner feedback loop and a standing design authority | Accepted (amends 0026) |
| 0039 | Durable derived values — one memoization mechanism with a lifetime per analytic | Accepted (gate B3.2; supersedes 0032) |
| 0040 | A target plan states its unallocated remainder | Accepted |
| 0041 | Category result — the positions in a category, rolled up and decomposable | Accepted |
| 0042 | One planning structure — the requirement registry and the work breakdown stop competing | Accepted |
| 0043 | A gate-closing ADR names the asks it answers — and the ones it does not | Accepted (amends 0026) |
| 0044 | Security knowledge as an append-only log — the thesis state is its projection, retractions never vanish | Accepted (gate B4.1 + P0-6) |
| 0045 | Optional built-in authentication for the web UI, and the deployment contract that goes with it — one password by one variable, loopback and Host-validated by default | Accepted (answers OQ-8) |
| 0046 | Benchmark comparison — a fixed rate or a flagged catalog security, replayed “bought once” and as the portfolio’s own savings plan | Accepted (owner sign-off = the merge of PR #780) |
| 0047 | Derived metrics — a metric names the series it describes, and carries no verdict | Accepted (design gate for FR-39/FR-40, ladder level (a); owner sign-off = the merge of the Sprint 13 planning PR) |
| 0048 | Security events as first-class objects — calendar facts about the whole catalog, entered by hand or by an agent, fetched by nobody | Accepted (gate B3.4, FR-44; owner sign-off = the merge of the Sprint 13 planning PR) |
| 0049 | Policy rules as first-class objects — a rule is a standard in force over a period, evaluated at read into findings that carry no action | Accepted (gate B3.6, FR-43; owner sign-off = the merge of the Sprint 15 planning PR; amended by the Sprint 16 planning PR: renamable name, §8 remedy corrected) |
| 0050 | Lifecycle merges — rename, merge and delete for cash accounts and depots, and merge for duplicate securities, under a post-merge re-import contract | Accepted (design gate for #328 and #608, risk-tier; owner sign-off = the merge of the Sprint 16 planning PR; amends 0029; amended 2026-10-07: the first limit’s fail-closed probe, names compared as exact strings; amended 2026-10-09: the probe reads the dry run) |
| 0051 | Contribution analysis — which position made how much of a period’s result, summing exactly to the period’s money result | Accepted (design gate for FR-41, ladder level (b); owner sign-off = the merge of the Sprint 17 planning PR; amended 2026-10-09: a security-linked interest booking is its position’s income) |
| 0052 | Bond master data in dedicated columns, read with display-only metrics | Accepted (a story-level decision recorded by #330’s story, Sprint 18 PR γ U7; adopted by the merge of Sprint 18’s PR γ, #1054) |
| 0053 | A Portfolio Performance CSV books its Gesamtpreis — read as PP writes it, hashed as before, corrected by a separate confirm | Accepted (design gate for #1076, risk-tier; owner sign-off = the merge of the Sprint 19 planning PR; notes 0029; amended 2026-10-07: the JSON path’s negative tax and a credit row that nets to nothing; amended 2026-10-09: what the parsers refuse, and what the correction lists) |
| 0054 | The agent’s surface — one authority for every token, the journal names the credential, and the companion spells what the operator cannot see | Accepted (the architecture’s FU-6, #967; supersedes the architecture’s D4 and closes OD-4; adopted by the merge of the Sprint 20 sprint PR) |