Requirements
About this document
Audience: project owner, team, and agentic tools.
Purpose: an authoritative, checkable list of what the system must do and the constraints under which it must do it.
Scope markers
- M — MVP. Built and shipping in v1. Schema, API, code all present.
- D — Designed-for / Deferred. Not built in v1, but the architecture must accommodate it without rewrites.
- X — Out of scope. Will not be built; architecture does not go out of its way to support it.
Every requirement is referenceable by its ID (e.g., R-TRX-001). Use these IDs in commits, PRs, and agent prompts.
1. Functional Requirements
1.1 Identity & Authentication
| ID |
Requirement |
Scope |
| R-IDN-001 |
Users can register with a username and password. Username uniqueness is enforced instance-wide. |
M |
| R-IDN-002 |
Users can log in with username + password and receive a JWT access token + refresh token. |
M |
| R-IDN-003 |
Users can log in via Google OAuth. The Google account's stable identifier is stored as the user's username. |
M |
| R-IDN-004 |
Users can authenticate by linking their Telegram account to an app account, then issuing commands in the bot. |
M |
| R-IDN-005 |
Each user is issued 12 single-use recovery codes at signup. They can be used to reset a forgotten password without email infrastructure. |
M |
| R-IDN-006 |
Users can regenerate their set of recovery codes; doing so invalidates the previous set. |
M |
| R-IDN-007 |
Users can opt in to TOTP-based 2FA (Google Authenticator-compatible). |
D |
| R-IDN-008 |
Users can register WebAuthn / passkey credentials as an alternative authentication factor. |
D |
| R-IDN-009 |
Users can log out, which revokes the current refresh token. |
M |
| R-IDN-010 |
Refresh tokens rotate on use; an access token can be obtained from a valid refresh token without re-authentication. |
M |
| R-IDN-011 |
A user can list and revoke their active sessions. |
M |
| R-IDN-012 |
A user can view, edit, and delete their own profile (display name, locale, primary timezone). |
M |
1.2 Families & Membership
| ID |
Requirement |
Scope |
| R-FAM-001 |
On registration, a user has a private family auto-created. This family is undeletable but can be hidden in the UI. |
M |
| R-FAM-002 |
A user always belongs to at least one family. |
M |
| R-FAM-003 |
A user can create additional families and is the initial owner of each. |
M |
| R-FAM-004 |
A family has a name, a base currency, and a primary timezone, all editable by the owner. |
M |
| R-FAM-005 |
An owner can invite other users to a family by username. |
M |
| R-FAM-006 |
An owner can generate a shareable invite link with a baked-in role. |
M |
| R-FAM-007 |
Invitees see pending invitations in any client (web, Telegram bot) and can accept or reject them. |
M |
| R-FAM-008 |
A family supports three roles: owner, editor, viewer. |
M |
| R-FAM-009 |
An owner has full control: manage members, change roles, edit/delete any data, delete the family (except a private family). |
M |
| R-FAM-010 |
An editor can create, edit, and delete data, but can only delete entries they themselves created. |
M |
| R-FAM-011 |
A viewer has read-only access across all family data. |
M |
| R-FAM-012 |
An owner can transfer ownership to another member. The transfer is a swap: the previous owner becomes an editor; the new owner gets owner rights. |
D |
| R-FAM-013 |
A family can have only one owner at a time. |
M |
| R-FAM-014 |
A user can leave a family. The owner cannot leave without first transferring ownership. |
M |
| R-FAM-015 |
When a member is removed or leaves, data they authored remains in the family with attribution preserved. |
M |
| R-FAM-016 |
When a user deletes their own account: their private family and any owner-only families with no other members are deleted; account deletion is blocked otherwise until ownership is transferred or families are removed. |
M |
| R-FAM-017 |
All transactions, accounts, categories, tags, budgets, goals, and reports are scoped to a family. |
M |
1.3 Accounts
| ID |
Requirement |
Scope |
| R-ACC-001 |
Users can create accounts of these types: cash, debit, credit, investment. |
M |
| R-ACC-002 |
Cash accounts have a non-negative balance invariant; transactions that would violate it are rejected. |
M |
| R-ACC-003 |
Debit accounts allow any balance value. |
M |
| R-ACC-004 |
Credit accounts have a credit limit; balance can go negative down to -credit_limit. |
M |
| R-ACC-005 |
Investment accounts hold positions and a cash balance, restricted to investment-related transaction kinds. |
M |
| R-ACC-006 |
Crypto wallets are supported as a distinct account type, holding crypto assets and an optional cash balance. |
D |
| R-ACC-007 |
Each account has a single base currency, set on creation, immutable thereafter. |
M |
| R-ACC-008 |
Each account has a stored balance, synchronously updated within the same database transaction as any financial transaction touching it. |
M |
| R-ACC-009 |
Account balance recomputation is available as an admin/operator tool that detects and reports drift but does not silently self-heal. |
M |
| R-ACC-010 |
Accounts can be soft-deleted; soft-deleted accounts and their transactions are excluded from balances and reports but remain queryable for audit. |
M |
| R-ACC-011 |
Loans and debts can be tracked as informational entries (counterparty, principal, direction, interest rate, dates, status). They do not affect net worth and have no associated transactions. |
D |
1.4 Transactions
This section covers regular transactions on non-investment accounts (cash, debit, credit). Investment-account transactions have their own kinds and storage - see §1.5 Investment Transactions. Both transaction types share these baseline behaviors (logical date, amount, status, soft-delete, optimistic locking).
In this document, the uppercase labels used for investment transaction kinds are shorthand for the lowercase enum values stored in the database and returned by the API. Regular transaction kinds remain the lowercase values income, expense, and transfer.
| ID |
Requirement |
Scope |
| R-TRX-001 |
Transactions have three kinds for non-investment accounts: income, expense, transfer. |
M |
| R-TRX-002 |
A transfer between two accounts is modeled as two linked transactions sharing a transfer_group_id. Each side records its own native amount; the FX rate is implicit. |
M |
| R-TRX-003 |
Each transaction has: a logical date (calendar date, no time), an amount in account currency, a category, optional tags, an optional note, an author, a creation timestamp, an updated timestamp, and a version. |
M |
| R-TRX-004 |
Cross-currency transfers require an exchange rate. The user enters the rate manually in MVP. |
M |
| R-TRX-005 |
The system can fetch FX rates from external providers (NBU, ECB, etc.) for popular currencies. |
D |
| R-TRX-006 |
The user always enters a transaction in the account's native currency, as it appears on the source statement. Original-currency notes are informational only. |
M |
| R-TRX-007 |
Transactions support a pending vs cleared state. |
M |
| R-TRX-008 |
Transactions can have file attachments (receipts, statements). |
D |
| R-TRX-009 |
Recurring/scheduled transactions auto-generate per their schedule, with notifications on generation. |
D |
| R-TRX-010 |
Transactions can be soft-deleted. Audit log retains the deletion event. |
M |
| R-TRX-011 |
Transactions support optimistic concurrency via a version field; conflicting updates return HTTP 409 with current state. |
M |
1.5 Investment Transactions
Investment transactions live in their own table (investment.investment_transaction), separate from regular transactions (ledger.transaction). They share the transfer_group mechanism with regular transactions for cross-account transfer pairs. See Data Model §5.2.
The transaction kinds in this section are stored and exposed as lowercase enum values: buy, sell, dividend, deposit_cash, withdraw_cash, fee, and split. The uppercase forms in prose are just human-readable labels.
| ID |
Requirement |
Scope |
| R-INV-001 |
Transactions on investment accounts are restricted to: BUY, SELL, DIVIDEND, DEPOSIT_CASH, WITHDRAW_CASH, FEE, SPLIT. |
M |
| R-INV-002 |
A BUY decreases account cash and increases position quantity. Average cost per unit is recalculated. |
M |
| R-INV-003 |
A SELL increases account cash and decreases position quantity. Average cost per unit is unchanged. A position with zero quantity is soft-deleted. |
M |
| R-INV-004 |
A DIVIDEND increases account cash and is linked to a symbol; it does not affect position quantity. |
M |
| R-INV-005 |
DEPOSIT_CASH and WITHDRAW_CASH move cash into/out of the investment account. They participate in cross-account transfer pairs (sharing a transfer_group_id). |
M |
| R-INV-006 |
A FEE decreases account cash. It is tagged but not linked to a position. |
M |
| R-INV-007 |
A SPLIT is recorded as a ratio (e.g., 4:1). It updates the position's quantity and average cost per unit proportionally without affecting cash. |
M |
| R-INV-008 |
Reinvested dividends are recorded as two separate transactions (a DIVIDEND and a BUY). The system does not auto-link them. |
M |
| R-INV-009 |
Positions in non-account-currency require an FX rate at the time of BUY/SELL, same mechanism as cross-currency transfers. |
M |
1.6 Symbols & Pricing
| ID |
Requirement |
Scope |
| R-SYM-001 |
Symbols are user-created. Symbols with an external identifier (ISIN, CoinGecko ID, etc.) are deduplicated instance-wide and shared across all users. |
M |
| R-SYM-002 |
Symbols without an external identifier are private to the creating user (visible across all their families but not to other users on the instance). |
M |
| R-SYM-003 |
Each symbol records: ticker, asset class (stock, etf, bond, crypto, mutual_fund, commodity, other), natural currency (nullable for crypto), external ID + type (nullable), display name, user-defined flag. |
M |
| R-SYM-004 |
Crypto multi-chain disambiguation uses distinct symbols (e.g., USDT-TRC20 vs USDT-ERC20), each with its own external ID. |
D |
| R-SYM-005 |
Prices are stored as a history: (symbol_id, as_of_date, price, currency, source). Manual updates create new history rows. |
M |
| R-SYM-006 |
The "current price" of a symbol is the most recent price-history row for it. |
M |
| R-SYM-007 |
The system can fetch prices from external providers (CoinGecko, exchanges) for symbols with external identifiers. |
D |
| R-SYM-008 |
Symbol valuation in family base currency uses both price history (in symbol's natural currency) and FX history (natural currency to base currency) at the same as_of_date. |
M |
| ID |
Requirement |
Scope |
| R-CAT-001 |
Categories support a two-level hierarchy (parent + optional children). |
M |
| R-CAT-002 |
New families are seeded with a default set of common categories. |
M |
| R-CAT-003 |
Categories are fully editable: users can add, rename, reparent (within the two-level limit), and soft-delete. |
M |
| R-CAT-004 |
Soft-deleting a category does not delete transactions referencing it; the category remains visible in historical reports. |
M |
| R-CAT-005 |
Tags are flat (no hierarchy), free-text, family-scoped, fully user-editable. |
M |
| R-CAT-006 |
A transaction has exactly one category and zero or more tags. |
M |
1.8 Imports
| ID |
Requirement |
Scope |
| R-IMP-001 |
Users can import transactions from a CSV file conforming to the application's defined import format. |
M |
| R-IMP-002 |
CSV imports are processed asynchronously via the worker; users see status updates and a final summary (success count, error rows). |
M |
| R-IMP-003 |
Importers are pluggable via an interface. New importers can be added without modifying core code. |
M |
| R-IMP-004 |
An LLM-based importer can extract transactions from arbitrary statement formats (PDF, CSV with non-standard headers, free text). |
D |
| R-IMP-005 |
Bank-specific API integrations are pluggable per API instance and can be added by self-hosters as their own code. |
D |
1.9 Reports
| ID |
Requirement |
Scope |
| R-RPT-001 |
A user can view current balances per account and the family's total in base currency. |
M |
| R-RPT-002 |
A user can view spending broken down by category over a chosen period. |
M |
| R-RPT-003 |
A user can view income vs expense over time at chosen granularity (day, week, month, year). |
M |
| R-RPT-004 |
A user can view net worth over time, computed from all accounts (including investment positions valued at historical prices). |
M |
| R-RPT-005 |
A user can browse and filter transactions by date range, account(s), category(ies), tag(s), amount range, and free-text search. Filters combine with AND. |
M |
| R-RPT-006 |
A user can save filter sets as named custom views, including non-AND boolean combinations. |
D |
| R-RPT-007 |
Budgets: a user can set monthly limits per category and track progress. |
D |
| R-RPT-008 |
Savings goals: a user can define a goal (target amount, deadline, optional source account) and track contribution progress. |
D |
| R-RPT-009 |
A simplified cash-flow forecast projects expected balances forward based on recurring transactions. |
D |
| R-RPT-010 |
Investment portfolio view: total value, per-position value, percentage growth, absolute growth — all in family base currency. |
M |
1.10 External Services & API Keys
| ID |
Requirement |
Scope |
| R-EXT-001 |
An API instance has a configuration file listing external services. Each entry has a name, an API key, a scope (reader or writer), and a description. |
M |
| R-EXT-002 |
External services authenticate via their API key in a request header. The auth middleware verifies the key against the config. |
M |
| R-EXT-003 |
An external service can act on behalf of any user on the instance. The user_id and family_id are supplied in the request; the API does not validate ownership beyond key validity. |
M |
| R-EXT-004 |
The trust model is documented: an API key is a system credential; the key holder is fully trusted. Self-hosters are responsible for key security. |
M |
| R-EXT-005 |
The Telegram bot is one such external service, shipped by the project, registered against the project's hosted instance and runnable against any self-hosted instance. |
M |
| R-EXT-006 |
Per-API-key rate limits can be configured. |
M |
1.11 Telegram Bot
| ID |
Requirement |
Scope |
| R-BOT-001 |
A user links their Telegram account to their app account by issuing a one-time link command in the bot, providing a code obtained in the web UI. |
M |
| R-BOT-002 |
A linked user can quick-add transactions via natural-language commands (e.g., /add 50 uah coffee). |
M |
| R-BOT-003 |
A linked user can check current account balances. |
M |
| R-BOT-004 |
A linked user can view simple charts (e.g., spending by category for the current month). |
M |
| R-BOT-005 |
The bot delivers notifications: pending family invitations, import completion, large transactions, etc. (Notification system itself is D; the bot's notification capability becomes available when notifications are built.) |
M / D |
| R-BOT-006 |
A user can accept or reject family invitations directly from the bot. |
M |
1.12 CLI
| ID |
Requirement |
Scope |
| R-CLI-001 |
A CLI tool authenticates via API key (registered as an external service) and can perform batch imports and exports. |
D |
1.13 Audit & History
| ID |
Requirement |
Scope |
| R-AUD-001 |
Every create, update, and delete on family-scoped entities (transactions, accounts, categories, tags, members, roles) generates an audit log entry. |
M |
| R-AUD-002 |
An audit log entry records: timestamp, actor user_id, family_id, entity type, entity id, action, before and after state. |
M |
| R-AUD-003 |
Owners of a family can view the full audit log for their family. |
M |
| R-AUD-004 |
Editors and viewers can view audit entries they are permitted to see (i.e., entries about entities they can read). |
M |
| R-AUD-005 |
An audit log retention policy can be configured per family (e.g., delete entries older than N days). |
D |
1.14 Notifications
| ID |
Requirement |
Scope |
| R-NOT-001 |
Pending family invitations are surfaced in any client by polling the API; clients display them until accepted or rejected. |
M |
| R-NOT-002 |
A general notification system supports multiple delivery channels (in-app, Telegram bot, future email/push) with per-user channel preferences. |
D |
1.15 GDPR & Data Portability
| ID |
Requirement |
Scope |
| R-GDP-001 |
A user can export all data they have access to: as an owner, the entire family's data; as an editor, only entries they themselves authored. |
M |
| R-GDP-002 |
Exports are produced as a downloadable bundle (JSON or CSV) re-importable into another instance of the application. |
M |
| R-GDP-003 |
A user can request hard deletion of their account. The system processes per R-FAM-016 (private family deleted; ownership transfers required for other families). |
D |
1.16 Operator Surface
| ID |
Requirement |
Scope |
| R-OPS-001 |
An OPERATOR_TOKEN can be set via environment variable. The operator credential is not stored in the database. |
M |
| R-OPS-002 |
Operator endpoints expose: instance health (/health/info), user metadata (count, registration dates — no PII deep-dive), API key management, ability to disable a user account. |
M |
| R-OPS-003 |
Operator endpoints do not allow access to user data (transactions, accounts, etc.). Operator auth is separate from user auth and is bootstrapped from the OPERATOR_TOKEN environment variable. |
M |
| R-OPS-004 |
Prometheus-compatible metrics endpoint for operator use. |
D |
| R-OPS-005 |
The browser-based operator panel authenticates by exchanging OPERATOR_TOKEN at a dedicated operator session endpoint for a short-lived HttpOnly cookie session. Protected operator health/admin endpoints accept that cookie; the raw token is not stored server-side. |
M |
2. Non-Functional Requirements
2.1 Architecture & Extensibility
| ID |
Requirement |
Scope |
| N-ARC-001 |
The backend is a modular monolith composed of bounded contexts. Each context has its own domain, application, and infrastructure layers. |
M |
| N-ARC-002 |
The architecture style is Hexagonal (Ports & Adapters): domain at the center, infrastructure at the edges, dependencies pointing inward. |
M |
| N-ARC-003 |
The domain layer has no imports from infrastructure, frameworks, or third-party libraries other than language standard libraries and small pure utility libraries. |
M |
| N-ARC-004 |
Bounded contexts communicate primarily via versioned domain events. Direct cross-context calls are limited to deliberately public read APIs exposed by each context. |
M |
| N-ARC-005 |
The event bus is in-process for MVP but exposes an interface broker-ready from day 0; switching to RabbitMQ/Redis Streams is a configuration change, not a rewrite. |
M |
| N-ARC-006 |
Domain events are persisted to an events table for retry, audit, and broker migration. |
M |
| N-ARC-007 |
Each domain event has a version field; subscribers can handle multiple versions for safe schema evolution. |
M |
| N-ARC-008 |
All extension points (importers, FX providers, price providers, file storage, notification channels) are defined as ports with at least one MVP adapter. |
M |
2.2 Data & Storage
| ID |
Requirement |
Scope |
| N-DAT-001 |
All persistent data is stored in PostgreSQL. SQLite is not supported. |
M |
| N-DAT-002 |
Each bounded context owns a dedicated database schema. No cross-schema foreign keys. Cross-context references are by ID, validated at the application layer. |
M |
| N-DAT-003 |
Monetary amounts are stored as integer minor units with an associated currency code. Floating-point types are not used for money. |
M |
| N-DAT-004 |
Crypto quantities support at most 6 decimal places of precision. |
M |
| N-DAT-005 |
All entity IDs are UUIDv7. |
M |
| N-DAT-006 |
Soft-delete (a deleted_at timestamp) is used for transactions, accounts, categories, and positions. Hard-delete is used for tags, notifications, and (per R-GDP-003) GDPR deletions. |
M |
| N-DAT-007 |
All shared mutable entities have a version integer for optimistic concurrency control. |
M |
| N-DAT-008 |
Transaction logical date is stored as a calendar date (no time, no timezone). Created/updated/audit timestamps are TIMESTAMPTZ in UTC. |
M |
| N-DAT-009 |
Reports use the family's primary timezone for period boundaries; user display preferences affect rendering only. |
M |
| N-DAT-010 |
Migrations are forward-only and idempotent. Major-version migrations may include destructive changes documented in UPGRADING.md. |
M |
2.3 API
| ID |
Requirement |
Scope |
| N-API-001 |
The API is REST over HTTPS, in English. |
M |
| N-API-002 |
URI-based versioning: /v1/.... v1 is committed-stable; only additive changes within a major version. |
M |
| N-API-003 |
Errors follow a single response shape with stable string codes, HTTP status, optional details, and a request_id. |
M |
| N-API-004 |
Mutation endpoints (POST, PATCH, DELETE) accept an Idempotency-Key header. Responses are cached for 24 hours per (idempotency_key, user_id). |
M |
| N-API-005 |
Updates to mutable shared entities require a version value; mismatch returns HTTP 409 with the current entity state. |
M |
| N-API-006 |
Lists are paginated with cursor-based pagination. Filtering is by query parameter; AND-only in MVP. |
M |
| N-API-007 |
An OpenAPI spec is auto-generated from request/response schemas and published at /v1/openapi.json. |
M |
| N-API-008 |
Per-user and per-API-key rate limits are configurable; exceeding them returns HTTP 429 with Retry-After. |
M |
2.4 Security & Privacy
| ID |
Requirement |
Scope |
| N-SEC-001 |
Passwords are hashed with a memory-hard algorithm (Argon2id or scrypt) with per-user salt. |
M |
| N-SEC-002 |
Recovery codes are stored hashed; only their plaintext is shown once at generation. |
M |
| N-SEC-003 |
JWT access tokens have short lifetimes (≤ 15 minutes); refresh tokens are stored server-side and rotate on use. |
M |
| N-SEC-004 |
API keys are stored hashed; only their plaintext is shown once at creation. |
M |
| N-SEC-005 |
The application enforces that one user cannot read or modify another user's data outside of shared families. |
M |
| N-SEC-006 |
All secrets (DB password, JWT secret, OAuth client secret, operator token) are configured via environment variables. The application refuses to start if any required secret is missing or contains a placeholder value. |
M |
| N-SEC-007 |
The application provides full GDPR-style data export and deletion. |
M |
| N-SEC-008 |
TLS termination is provided by Caddy in self-hosted and cloud profiles via automatic ACME. |
M |
| N-SEC-009 |
Personally identifying data (display name, locale, etc.) is per-user; no PII is stored at the family level. |
M |
2.5 Internationalization
| ID |
Requirement |
Scope |
| N-I18-001 |
The API responds in English. Error codes are stable strings; clients perform localization. |
M |
| N-I18-002 |
The web UI supports at least Ukrainian and English. |
D |
| N-I18-003 |
Currency formatting and number formatting in the UI follow the user's locale. |
D |
| N-I18-004 |
Date formatting in the UI follows the user's locale. |
D |
2.6 Observability
| ID |
Requirement |
Scope |
| N-OBS-001 |
Logs are written to stdout in JSON format with required fields: timestamp, level, message, request_id, and (where applicable) user_id, family_id, context, error. |
M |
| N-OBS-002 |
Errors are reported to Sentry when a Sentry DSN is configured. |
M |
| N-OBS-003 |
Each request gets a unique request_id propagated through logs and returned in the response. |
M |
| N-OBS-004 |
GET /health returns 200 if the process is up. GET /health/ready checks DB and Redis connectivity. GET /health/info (operator-protected) returns extended status. |
M |
| N-OBS-005 |
Prometheus metrics for request rates, queue depth, error counts, and DB pool stats. |
D |
2.7 Deployment
| ID |
Requirement |
Scope |
| N-DEP-001 |
The system runs in three deployment profiles from the same source tree: cloud, self-hosted server, fully local. Differences are limited to environment variables and optional infrastructure swaps. |
M |
| N-DEP-002 |
All processes (API, worker, web, telegram bot) are distributed as Docker images. |
M |
| N-DEP-003 |
A docker-compose.yml provides a working out-of-the-box deployment for self-hosters. Profile-specific overrides are provided as docker-compose.<profile>.yml. |
M |
| N-DEP-004 |
Database migrations run via an init container before the API process starts. |
M |
| N-DEP-005 |
Caddy serves the web SPA at / and proxies the API at /api/*. Same origin. |
M |
| N-DEP-006 |
A documented pg_dump-based backup procedure is provided for self-hosters; a sample backup container is included in the compose file (commented out by default). |
M |
| N-DEP-007 |
Tagged Docker images per release; docker compose pull && docker compose up -d is the upgrade path. |
M |
| N-DEP-008 |
First-run setup completes in under five minutes for a self-hoster on a fresh VPS. |
M |
| ID |
Requirement |
Scope |
| N-PRF-001 |
A typical API read (e.g., list 50 transactions) responds in under 200ms p95 on a small VPS with up to 10,000 transactions per family. |
M |
| N-PRF-002 |
A typical API write (e.g., create a transaction) responds in under 300ms p95. |
M |
| N-PRF-003 |
Report queries have a Redis-backed cache with a 10–15 minute TTL, invalidated on relevant writes via event subscriptions. |
M |
| N-PRF-004 |
Account balance reads are O(1); the stored balance is updated synchronously inside the same DB transaction as financial mutations. |
M |
2.9 Reliability
| ID |
Requirement |
Scope |
| N-REL-001 |
Database writes that span multiple rows (e.g., creating a transaction and updating an account balance) are wrapped in a single SQL transaction. |
M |
| N-REL-002 |
Domain event handlers are idempotent (event ID used as deduplication key). |
M |
| N-REL-003 |
Background jobs are retryable with exponential backoff; permanently-failed jobs are written to a dead-letter table with full context for inspection. |
M |
| N-REL-004 |
The application starts in a degraded-but-safe mode if Redis is temporarily unavailable: idempotency, rate limiting, and report caching are bypassed; the user-visible API remains functional. |
D |
2.10 Development & Maintainability
| ID |
Requirement |
Scope |
| N-DEV-001 |
The repository is an Nx monorepo. Apps live under apps/; bounded contexts under contexts/; shared libraries under libs/. |
M |
| N-DEV-002 |
Shared types live in libs/shared-types. They contain types only — no logic. |
M |
| N-DEV-003 |
Domain layers can be tested without a database, HTTP server, or any external service. |
M |
| N-DEV-004 |
All public functions in domain and application layers have explicit return types. |
M |
| N-DEV-005 |
A single migration tool owns the schema. Other services (Go, Python) read the schema but never run migrations. |
M |
| N-DEV-006 |
Coding conventions, naming rules, and file layout are documented in AGENTS.md. |
M |
3. Explicit Non-Goals
These are listed here, with X scope, so that they cannot be quietly added later:
| ID |
Non-Goal |
| X-001 |
Tax calculation, reporting, or any tax-jurisdiction-specific logic. |
| X-002 |
Bill payment or any actual movement of money outside the application. |
| X-003 |
Peer-to-peer money transfer between users of the application. |
| X-004 |
Expense splitting between users (Splitwise-style). |
| X-005 |
Subscription detection or management. |
| X-006 |
Credit score tracking or credit reporting integration. |
| X-007 |
Full investment analytics (asset allocation, risk metrics, rebalancing suggestions, IRR calculations beyond simple growth percentage). |
| X-008 |
Professional or business expense tracking (categorization for accounting, invoice generation, GST/VAT handling). |
| X-009 |
Multi-tenant administrative tooling beyond the operator surface defined in R-OPS. |
| X-010 |
A native mobile application. The web SPA may evolve into a PWA; that is the mobile story. |
| X-011 |
Email-based features (notifications, password reset, marketing). The system does not depend on SMTP. |
| X-012 |
Real-time collaboration (live presence, simultaneous editing). |
4. Open Items
Decisions deferred to a later phase; tracked here so they don't get lost.
| ID |
Item |
Decision Owner |
Trigger |
| O-001 |
HTTP framework choice (Hono / Express / NestJS / AdonisJS) |
Tech stack round |
Before backend infrastructure implementation begins |
| O-002 |
ORM / query layer choice (Drizzle / Kysely / other) |
Tech stack round |
Before backend infrastructure implementation begins |
| O-003 |
Web client framework (React vs Vue) |
Tech stack round |
Before web implementation begins |
| O-004 |
Telegram bot language (TypeScript vs Python) |
Tech stack round |
Before bot implementation begins |
| O-005 |
LLM provider strategy for D-scope LLM importer |
Future design pass |
When R-IMP-004 is being built |
| O-006 |
Notification channel implementations beyond in-app and Telegram |
Future design pass |
When R-NOT-002 is being built |