Roadmap¶
About this document
Audience: project owner and team for planning; agentic tools for understanding sequencing. Purpose: describe the order in which capabilities are built, the dependencies between them, and what counts as "done" at each milestone. Companion documents: Requirements for what each requirement means; Architecture for the system context.
No date commitments
This document commits to order and scope. Pace depends on team size, time available, and unforeseen complexity.
1. How to read this document¶
Each phase has:
- Goal — the outcome that defines the phase.
- Requirements covered — the requirement IDs from
REQUIREMENTS.mdthat this phase implements (fully or partially). - Deliverables — concrete artifacts produced.
- Definition of done — the checkable conditions that close the phase.
- Out of scope for this phase — explicit deferrals to avoid scope creep.
- Dependencies — earlier phases that must be complete first.
Phases are sequential by default but can overlap where dependencies allow. Within a phase, work items are roughly ordered but not strictly so.
The phases together implement the MVP as defined in Requirements. After MVP, Designed-for / Deferred (D) features are organized into post-MVP phases (§13–§16). Anything marked X does not appear here at all.
2. Phase 0 — Foundations¶
Goal: the repository exists, builds, runs, and the team / agents can develop in it productively.
Requirements covered:
N-DEV-001, N-DEV-002, N-DEP-002, N-DEP-003, N-DEP-005, N-OBS-001, N-OBS-002, N-OBS-003, N-OBS-004 (partial — /health only), N-API-007 (partial — empty spec).
Deliverables:
- Nx monorepo initialized with
apps/,contexts/,libs/,shared/,infra/,docs/. apps/api/skeleton with one liveness route (GET /healthreturning 200).apps/worker/skeleton (boots, idle).shared/config/typed env-var loader; refuses to start with placeholder values.shared/logging/JSON-stdout logger; injected as a port from day 0.shared/event-bus/in-process implementation with the broker-ready interface.infra/docker-compose.ymlwith API, worker, Postgres, Redis, Caddy.infra/docker-compose.local.ymloverride (no TLS, exposed ports).- Migration tooling chosen and wired; one no-op migration runs on startup via init container.
- Sentry integration scaffolded (no-op when DSN unset).
- All eight documents in
docs/content/(this file and its companions) committed. - CI: lint, typecheck, run tests, build Docker images on each push.
Definition of done:
git clone && docker compose -f infra/docker-compose.yml -f infra/docker-compose.local.yml upproduces a workingGET /healthreturning 200 within five minutes on a fresh machine.- A new bounded-context skeleton can be added by a developer / agent in under 15 minutes following Agents §6.
Out of scope for this phase:
- Any business functionality.
- Web SPA, Telegram bot, CLI.
- Any user-facing endpoints.
Dependencies: none.
3. Phase 1 — Identity & Family Skeleton¶
Goal: users can register, log in, and have a private family auto-created.
Requirements covered: R-IDN-001, R-IDN-002, R-IDN-005, R-IDN-006, R-IDN-009, R-IDN-010, R-IDN-011, R-IDN-012, R-FAM-001, R-FAM-002, R-FAM-004, R-FAM-017 (the family-scoping rule applied going forward), N-SEC-001, N-SEC-002, N-SEC-003, N-SEC-005, N-SEC-006, N-SEC-009.
Deliverables:
contexts/identity/with: user, credential (password kind only), recovery_code, session entities; password hashing (Argon2id); JWT issuance and validation; refresh-token rotation; recovery-code generation and verification.contexts/family/with: family entity, family_member, role enforcement; auto-create-private-family event subscriber listening toidentity.user.registered.- HTTP routes per API Conventions §13.1, §13.2 (auth + self):
POST /v1/auth/register,/login,/refresh,/logoutPOST /v1/auth/password-reset/initiate,/completeGET /v1/me,PATCH /v1/meGET /v1/me/sessions,DELETE /v1/me/sessions/{id}POST /v1/me/recovery-codes/regenerateGET /v1/me/families(list user's families — initially just the private one).- The auth middleware that enforces JWT validation and family-role checks.
- Standardized error responses per
API_CONVENTIONS.md§8. - Tests at all three levels (domain, application, infrastructure) for everything in this phase.
Definition of done:
- A new user can register, log in, get an access + refresh token, refresh, log out, reset their password using a recovery code, and view their auto-created private family.
- Hard rules from Agents §4 are demonstrably satisfied (domain layer has no infra imports; tests verify).
- Token security passes a basic review (short access TTL, refresh rotation, hashed storage).
Out of scope:
- OAuth, Telegram link, TOTP, WebAuthn.
- Family invitations, members beyond owner.
- Any account or transaction functionality.
Dependencies: Phase 0.
4. Phase 2 — Family Membership & Audit Skeleton¶
Goal: families can have multiple members with roles; every change is audited.
Requirements covered: R-FAM-003, R-FAM-005, R-FAM-006, R-FAM-007, R-FAM-008, R-FAM-009, R-FAM-010, R-FAM-011, R-FAM-013, R-FAM-014, R-FAM-015, R-FAM-016, R-FAM-017, R-AUD-001, R-AUD-002, R-AUD-003, R-AUD-004, R-NOT-001 (in-app invitation polling).
Deliverables:
contexts/family/: invitation entity; invite-by-username and shareable-link flows; accept/reject; member removal; self-leave; deletion cascade per R-FAM-016.contexts/notification/: notification entity used to surface pending invitations (in-app only);GET /v1/me/notifications,POST /v1/me/notifications/{id}/handle.contexts/audit/: audit_entry entity; subscribers to allfamily.*andidentity.*events recording changes;GET /v1/families/{familyId}/auditwith role-based visibility.- HTTP routes per API Conventions §13.3 (families, members, invitations).
- Account deletion flow (
DELETE /v1/me) implementing the cascade rule. - Optimistic locking enforced on family, family_member, family_invitation.
Definition of done:
- A user can create a new family, invite another user, the invitee sees the pending invitation in their notifications, accepts it, and both can now interact with the family per their roles.
- Owner can transfer the family by removing their own membership only after handing off (i.e., the rule blocks them otherwise).
- Audit log shows every state change with actor attribution.
- Optimistic concurrency conflicts return 409 with
currentstate per API Conventions §7.
Out of scope:
- Ownership transfer (R-FAM-012, D — kept for a later phase).
- Channel preferences for notifications (R-NOT-002, D).
- Audit retention policy (R-AUD-005, D).
Dependencies: Phase 1.
5. Phase 3 — Currency & Ledger Core¶
Goal: users can create accounts and record income, expense, and transfer transactions in a single currency.
Requirements covered: R-ACC-001, R-ACC-002, R-ACC-003, R-ACC-004, R-ACC-007, R-ACC-008, R-ACC-009, R-ACC-010, R-TRX-001, R-TRX-002, R-TRX-003, R-TRX-006, R-TRX-007, R-TRX-010, R-TRX-011, R-CAT-001, R-CAT-002, R-CAT-003, R-CAT-004, R-CAT-005, R-CAT-006, R-RPT-001 (balances), R-RPT-005 (basic transaction listing), N-DAT-003, N-DAT-004, N-DAT-006, N-DAT-007, N-PRF-001, N-PRF-002, N-PRF-004.
Deliverables:
contexts/currency/: currency table seeded with major fiat + common crypto codes; FX rate history table (manual entries in this phase, single-currency families work without rates).contexts/ledger/:- Account entity (cash / debit / credit / investment-without-positions-yet); balance synchronously updated in same DB transaction as financial mutations.
- Transaction entity (income / expense / transfer kinds only); transfer_group for linked pairs; pending vs cleared status;
versionenforcement. - Category entity (two-level hierarchy, seeded set on family creation); soft-delete handling.
- Tag entity; transaction_tag join.
- HTTP routes per API Conventions §13.4, §13.5 (transactions sans investment kinds), §13.6 (categories & tags), §13.10 (
/balancesand/spend-by-categoryreports). - Use cases for: create/update/delete transaction, create/transfer between accounts, create/update/delete account, list with AND-only filters, mark cleared.
- Audit subscribers for all
ledger.*events. - Money helpers in
shared/(or incontexts/ledger/domain/value-objects/):Moneyvalue object enforcing the integer-minor-units rule.
Definition of done:
- A user can create accounts, post income and expense transactions, transfer between same-currency accounts, see correct stored balances after every operation, list transactions with filters, and view a current-balances report and a spend-by-category report.
- Cash account non-negative invariant is enforced at the domain layer with a domain exception mapped to HTTP 422.
- Credit account credit-limit invariant is enforced the same way.
- Reconciliation tool (admin) recomputes balances on demand and reports drift (zero in a healthy system).
Out of scope:
- Multi-currency transfers (next phase).
- Investment transactions (positions / symbols).
- Imports.
- Reports beyond balances and spend-by-category.
Dependencies: Phase 2.
6. Phase 4 — Multi-Currency & Net Worth¶
Goal: transfers across currencies work correctly; net worth in family base currency is computed and shown over time.
Requirements covered: R-TRX-004, R-FAM-004 (base currency now load-bearing), R-RPT-003, R-RPT-004, N-DAT-008, N-DAT-009, N-PRF-003.
Deliverables:
- Cross-currency transfer use case: requires explicit FX rate; records each side in its native currency; stores
fx_rate_usedon the transfer_group. - Manual FX rate entry endpoints (
GET /v1/fx-rates,POST /v1/fx-rates). - Use case: convert money between currencies for report rendering, using closest-prior FX rate from history; family base currency comes from
family.family.base_currency. - Reports: income vs expense over time; net worth over time (sum of account balances converted to base currency at each historical date).
- Redis-backed report cache (10–15 minute TTL); invalidation subscribers on relevant
ledger.*andcurrency.*events. - Family timezone-aware period boundary computation for reports.
Definition of done:
- A user with accounts in UAH and USD can transfer between them using a manual rate; balances on both sides update correctly in their own currencies; net-worth report shows both in family base currency at correct historical conversion.
- Income-vs-expense report respects family timezone for month boundaries.
- Cache hit rate is observable via metrics (or at least via logs); invalidation on writes is testable.
Out of scope:
- FX rate API integration (R-TRX-005, D).
- Investment positions / pricing.
- Custom saved filters (R-RPT-006, D).
Dependencies: Phase 3.
7. Phase 5 — Investment MVP¶
Goal: users can track basic investment holdings: positions in symbols, manual prices, transaction kinds for buy/sell/dividend/split (D), and a portfolio report.
Requirements covered: R-ACC-005, R-INV-001 through R-INV-009, R-SYM-001, R-SYM-002, R-SYM-003, R-SYM-005, R-SYM-006, R-SYM-008, R-RPT-010.
Deliverables:
contexts/investment/:- Symbol entity with the instance-shared (external-ID-bearing) vs user-private (no external ID) visibility rule.
investment_transactionentity (kinds:buy,sell,dividend,deposit_cash,withdraw_cash,fee,split(D)); pending vs cleared status;versionenforcement; soft-delete.- Position entity (one per
(account, symbol)while open; soft-delete on full sale); average-cost tracking. - Price history (manual entries in this phase).
- Use cases for: create/update/delete investment transaction, list with filters, mark cleared. Each write-side use case updates the affected position synchronously in the same DB transaction (no event-based sync; correctness of position state is part of the transaction's atomic commit).
- Cross-account transfer use case spanning regular and investment accounts (writes to both
ledger.transactionandinvestment.investment_transactionplustransfer_groupin one DB transaction; calls Ledger's narrow public write API for the regular side). - HTTP routes per API Conventions §13.7 (symbols, positions, investment-transactions, cross-account transfer) and §13.10 (
/portfolio). - Audit subscribers for all
investment.*events. - Portfolio report: total value, per-position value, % growth, absolute growth — all in family base currency, using FX history.
Definition of done:
- A user can create an investment account; create or look up a symbol (instance-shared if it has an external ID, private if not); record a
BUY; see the position appear with quantity, average cost, and value at the latest manual price; record aSELLpartial / full and see correct position state; record aSPLITand see quantity and average cost adjusted; view a portfolio report with correct growth percentages. - Investment-account cross-account transfers (cash deposit) work as a transfer pair indistinguishable from other transfer pairs in terms of audit and reporting.
- Symbol deduplication by
(external_id_type, external_id)works: two users with the same ISIN end up referencing the same symbol row.
Out of scope:
- Crypto wallets (R-ACC-006, D — investment account can hold crypto symbols, but a separate crypto-wallet account type is deferred).
- External price providers (R-SYM-007, D).
- DRIP / auto-link of dividend-to-buy (per design, MVP records as separate transactions).
Dependencies: Phase 4.
8. Phase 6 — CSV Imports¶
Goal: users can bulk-import transactions from a CSV file in the application's defined format. Imports are async via the worker.
Requirements covered: R-IMP-001, R-IMP-002, R-IMP-003, N-REL-002, N-REL-003.
Deliverables:
contexts/import/:- import_job and import_row entities.
- Importer port (
Importerinterface) with the MVP implementation:CsvAppFormatImporter. Defines the canonical CSV column layout in user-facing documentation. - Use cases: enqueue import job (returns job ID immediately); worker job consumer (parses → stages rows → applies as transactions in atomic batches); update import_job status throughout. Apply step calls Ledger's or Investment's public write API depending on the target account type.
- Worker process subscribes to the
import.job.queuedjob type via BullMQ. - File storage port (
FileStorage) withLocalFileStorageadapter writing to a Docker volume; uploaded files referenced by ID. - HTTP routes per API Conventions §13.9 (multipart upload, status polling).
- Notification on import completion (in-app, leveraging the notification entity from Phase 2).
- Dead-letter handling for permanently failing import jobs.
Definition of done:
- A user can upload a CSV file conforming to the documented format; receive an immediate job ID; poll for status; see successful rows applied as transactions and failed rows reported with row-level errors; receive an in-app notification on completion.
- The importer interface is documented well enough that a self-hoster could implement a custom importer without modifying the import context.
Out of scope:
- LLM-based imports (R-IMP-004, D).
- Bank API plugins (R-IMP-005, D).
- S3-compatible storage (N-DEP, D — only
LocalFileStoragein this phase).
Dependencies: Phase 3 (for ledger writes); benefits from but doesn't strictly need Phase 4 / 5.
9. Phase 7 — External Services & Operator Surface¶
Goal: API keys exist as a first-class concept; the operator can manage instance-level concerns; the system is ready for the Telegram bot.
Requirements covered: R-EXT-001 through R-EXT-006, R-OPS-001, R-OPS-002, R-OPS-003, R-IDN-004 (Telegram link mechanics, without the bot itself).
Deliverables:
contexts/identity/:- api_key entity with hashed-key storage, scope (
reader|writer), per-key rate limit override. - telegram_link entity and the link-code flow (
POST /v1/me/telegram-link). - Auth middleware extended to recognize
X-API-Keyheader;X-Acting-User-Idheader handling per API Conventions §9.4. - Per-API-key rate limiting (Redis-backed counters) per API Conventions §11.
POST /v1/operator/sessionbootstrap recognizingX-Operator-Token.POST /v1/operator/sessionissues the operator cookie session after validatingOPERATOR_TOKEN.- Protected operator health/admin routes also accept a short-lived HttpOnly cookie session minted after validating
OPERATOR_TOKEN. - HTTP routes per API Conventions §13.11 (operator) and the
POST /v1/me/telegram-linkroute. - External-services config file format documented and supported as the bootstrap source for API keys (DB rows are the runtime authority per Data Model §2.7).
- The
OPERATOR_TOKENenvironment variable is the canonical operator credential; there is no operator row or operator flag in the database.
Definition of done:
- A self-hoster can add an API key via config file or operator endpoint; an external service authenticated with that key can perform operations on behalf of any user on the instance per scope.
- Operator endpoints work end-to-end: list users, disable/enable a user, manage API keys, view extended health info.
- The Telegram link flow works: user obtains a code in the web flow, can supply it via an authenticated link endpoint (the bot will use this in Phase 8).
- Rate limits trigger
429responses with proper headers when exceeded.
Out of scope:
- The Telegram bot itself (next phase).
- Prometheus metrics (R-OPS-004, D).
- Webhooks (not in MVP).
Dependencies: Phase 3 (for the identity surface to be meaningful) and Phase 6 (so the bot has imports / transactions to interact with) — actually only Phase 3 is hard, but bundling with Phase 8 makes sense.
10. Phase 8 — Telegram Bot¶
Goal: users can authenticate, quick-add transactions, check balances, and view simple charts via Telegram.
Requirements covered: R-BOT-001, R-BOT-002, R-BOT-003, R-BOT-004, R-BOT-005 (partial: invitation notifications surfaced in the bot leverage Phase 2's notification entity), R-BOT-006.
Deliverables:
apps/bot/:- Telegram-API client (long-poll or webhook).
- Command parser for
/link,/add,/balance,/chart,/invitations,/accept,/reject. telegram_chat_id → user_idresolution via theidentity.telegram_linkrow created in Phase 7.- Calls the API as an external service using a writer-scoped API key plus
X-Acting-User-Id. - Renders responses as formatted Telegram messages; charts as inline images (server-rendered SVG → PNG, or text-mode for MVP).
- Bot deployment in
infra/docker-compose.ymlas an optional service (commented for self-hosters who don't want it). - A documented setup procedure for self-hosters: register a bot with BotFather, obtain a token, configure env var, start the bot service.
Definition of done:
- A linked user can: type
/balanceand see all account balances; type/add 50 uah coffee groceriesand see a transaction created with correct parsing; type/chartand receive a current-month spend-by-category chart; receive a notification when a family invitation arrives and accept it via/accept. - The bot is the canonical example of the external-service pattern. A reader of the codebase can use the bot as a reference for how a third-party integration would be built.
Out of scope:
- Voice messages, photo-of-receipt processing (would need OCR / LLM, both D).
- Group chats; the bot is one-on-one only.
Dependencies: Phase 7.
11. Phase 9 — GDPR Export & Hardening¶
Goal: users can export all data they own; the system is ready for self-hosters and a small public launch.
Requirements covered: R-GDP-001, R-GDP-002, N-API-004 (idempotency end-to-end), N-API-006 (pagination tested at scale), N-DEP-001, N-DEP-006, N-DEP-007, N-DEP-008.
Deliverables:
- Export use cases:
GET /v1/me/export— exports everything the user owns or has authored across all families.GET /v1/families/{familyId}/export— exports the entire family for owners; only own creations for editors per the agreed rule.- Export format documented and re-importable (tested by exporting from one instance and importing into a fresh one).
- Idempotency-key end-to-end testing: every mutation endpoint accepts and respects the header.
- Pagination tested at scale (synthetic data with > 50,000 transactions in a family).
- Backup procedure documented:
pg_dumpinstructions and a sample backup container indocker-compose.yml(commented out by default). UPGRADING.mdwritten, even if just stating "no upgrade steps needed yet."- Production readiness pass: full review of every endpoint for correct error codes, correct optimistic-locking handling, correct authorization, correct audit emission.
Definition of done:
- A user can export their data and re-import it into a fresh instance with full fidelity.
- A self-hoster can follow
infra/-level docs and the README to set up a new instance from scratch in under five minutes (per N-DEP-008). - The OpenAPI spec served at
/v1/openapi.jsonaccurately matches every shipped endpoint.
Out of scope:
- GDPR hard-delete (R-GDP-003, D).
- i18n in the web UI (D).
Dependencies: all prior phases.
12. MVP Complete¶
After Phase 9 is closed, the MVP per Requirements is shipped. Concrete acceptance criteria:
- Every
M-marked requirement in Requirements is implemented and tested. - Every endpoint in API Conventions §13 (MVP scope) responds correctly.
- The system runs successfully in all three deployment profiles (local, self-hosted, cloud).
- A self-hoster can clone, configure, and run the system without contacting the project.
- The hosted instance is operable with the operator surface alone.
- Documentation in
docs/content/matches the shipped behavior.
What follows is the post-MVP roadmap. It is less precise than the MVP roadmap because: (a) the order may shift based on real usage, (b) some items will reveal subtleties only when their predecessors are built.
13. Phase 10 — Post-MVP: User-Visible D Features¶
These are deferred features that are most directly user-visible, prioritized because they have the most obvious value-add for early users.
Items, in suggested order:
- Recurring transactions (R-TRX-009). Recurring rules, schedule generation, notifications on creation. Adds a scheduler component to the worker.
- Receipt attachments (R-TRX-008). Activates the
FileStorageport; users upload receipts; storage abstraction stays local in this phase. - Budgets (R-RPT-007). Per-category monthly limits; progress tracking; notifications on threshold crossings.
- Savings goals (R-RPT-008). Goal entities; progress from contributions; visualization.
- Saved custom filters / views (R-RPT-006). Persisted filter sets; named views; non-AND boolean combinations.
- Simplified cash flow forecast (R-RPT-009). Projections based on recurring transactions; depends on Phase 10 item 1.
- Ownership transfer (R-FAM-012). The owner-swap flow.
Cross-cutting:
- Notification system upgrade (R-NOT-002): channel preferences, delivery to Telegram bot for non-invitation notifications.
- Audit log retention policy (R-AUD-005).
Dependencies: MVP.
14. Phase 11 — Post-MVP: Crypto & Investment Depth¶
Goal: crypto becomes a first-class account type; investment tracking gains automated price feeds.
Items:
- Crypto wallet account type (R-ACC-006). New account type sharing most of the investment-account model; multi-asset wallets; the chain disambiguation via distinct symbols (R-SYM-004).
- External price provider integration (R-SYM-007).
PriceProvideradapters: CoinGecko for crypto, exchange APIs for stocks. Scheduled price refresh via worker. - External FX rate integration (R-TRX-005).
RatesProvideradapters: NBU for UAH-pairs, ECB for EUR-pairs, fallback provider. Scheduled rate refresh. - Info-only loans / debts (R-ACC-011). Loan info entity; status tracking; integration with reporting (excluded from net worth per design).
Dependencies: MVP. Phase 10 helpful but not required.
15. Phase 12 — Post-MVP: Imports Beyond CSV¶
Goal: users can import from a much broader range of sources.
Items:
- LLM-based statement importer (R-IMP-004). New
Importeradapter that accepts arbitrary statement formats (PDF, CSV with non-standard headers, free text); uses an LLM provider to extract structured transactions; user reviews before applying. Adds anLLMProviderport with adapters for OpenAI, Anthropic, etc. - Bank-specific API plugins (R-IMP-005). Plugin loading mechanism for self-hosters to add bank-specific code; a documented plugin interface; one or two reference implementations for common Ukrainian banks where APIs are available.
- OFX, QIF, MT940 importers (not separately required, but standard formats). Additional
Importeradapters.
Cross-cutting:
- File storage S3-compatible adapter (N-DEP, D).
S3FileStorageadapter for cloud and self-hosters who want object storage.
Dependencies: MVP.
16. Phase 13 — Post-MVP: Auth Depth & i18n¶
Goal: authentication options expand; the web UI becomes localizable.
Items:
- TOTP 2FA (R-IDN-007). Opt-in; per-user secret; recovery codes already in place.
- WebAuthn / passkeys (R-IDN-008). Additional auth factor.
- GDPR hard delete (R-GDP-003). The deletion procedure with all the cascade and attribution-anonymization rules.
- CLI (R-CLI-001). Authenticates via API key (registered as an external service); supports batch import / export workflows.
- i18n in web UI (N-I18-002, N-I18-003, N-I18-004). UA + EN at minimum; locale-aware date / number / currency formatting.
- Prometheus metrics endpoint (R-OPS-004, N-OBS-005). Operator-protected metrics; enables a self-hosted Prometheus + Grafana setup.
- Redis-degraded mode (N-REL-004). Explicit handling of Redis unavailability.
Dependencies: MVP.
17. Things Explicitly Not on the Roadmap¶
For clarity: the roadmap covers MVP and all D items. It deliberately does not cover anything marked X in Requirements §3:
- Tax calculation
- Bill payment
- Peer-to-peer money transfer
- Expense splitting
- Subscription detection
- Credit score tracking
- Full investment analytics
- Professional / business expense tracking
- Multi-tenant administrative tooling beyond the operator surface
- Native mobile applications (PWA evolution of web is the mobile story)
- Email-based features
- Real-time collaboration
If usage reveals strong demand for any of these, that's a vision-level decision (see Vision §2), not a roadmap update.
18. How this roadmap evolves¶
This document is updated when:
- A phase is closed (mark as done; capture any deviations or carryovers).
- A requirement is reclassified (M ↔ D, never to X without a vision discussion).
- A new D requirement is added (placed in the appropriate post-MVP phase).
- The order of post-MVP phases shifts based on real usage signals.
Updates to this document are normal commits, attributed to a decision and ideally referencing requirement IDs or a discussion thread.
19. Where to go next¶
- What each requirement means: Requirements
- What "done" looks like at each phase, in technical terms: Architecture, Data Model, API Conventions
- How agents work through individual tasks: Agents
- Why the product exists: Vision
- Domain term definitions: Glossary