Browse Source

docs: design local MVP workflows

master
Codex 2 months ago
parent
commit
dd58866dbe
  1. 212
      docs/superpowers/specs/2026-09-07-local-mvp-workflows-design.md

212
docs/superpowers/specs/2026-09-07-local-mvp-workflows-design.md

@ -0,0 +1,212 @@
# Local MVP Workflows Design
## Purpose and scope
Roadmap step 5 gives the local owner a server-rendered Django interface for
managing reference data and transactions, categorizing imported activity, and
inspecting monthly summaries. It also provides durable, local exports and
PostgreSQL backup and restore-verification commands. Together these complete a
documented synthetic import-to-report-to-backup workflow.
The reviewed-import parser and decision workflow remains command based. The web
application begins with the committed financial records and links to the local
import instructions. Bank APIs, investments, forecasting, receipt recognition,
mobile clients, hosted multi-user operation, and automatic or AI categorization
remain outside the MVP.
## Chosen approach
Use Django templates, forms, and ordinary HTTP requests. Keep read behavior in
explicit query and report modules, presentation in `core`, and all mutations in
the existing audited `finance.services` boundary. This adds no JavaScript
framework and does not expose Django admin as a financial editor.
Alternatives considered were a command-only workflow and a client-rendered API
application. Command-only operation would not provide the planned local screens.
A separate API and JavaScript client would add authentication, serialization,
state-management, and build-tooling work without improving this single-owner
milestone.
## Application surface
The root page is a monthly dashboard. The local navigation exposes accounts,
transactions, categories, payees, reports, and exports, plus a link to the
reviewed-import guide. Server-rendered pages use semantic HTML and remain usable
without client-side JavaScript.
Transaction pages provide:
- a paginated list ordered by posting date and stable identifier;
- filters for inclusive date range, account, kind, payee, category, currency,
and uncategorized status;
- a detail page with relationships and source provenance;
- manual creation through the supported financial posting service; and
- annotation of description, payee, and category through
`annotate_transaction`.
The interface does not delete transactions or rewrite amount, posting date,
occurrence date, account, kind, refund, transfer, statement, installment, or
source identity. Financial corrections are represented by new entries, refunds,
or transfers through the domain services. Imported transaction evidence remains
immutable.
Reference pages list active and archived accounts, categories, and payees.
Creation and archival use `create_account`, `create_category`, `create_payee`,
and `archive`. Archived references remain visible in historical records and
cannot be selected for new assignments.
## Request security and accountability
All mutation forms use Django CSRF protection and POST requests. Views never
call model `save()` directly for financial writes. The local server derives the
actor as `Actor(kind="HUMAN", identifier="local-owner")`; actor identity is not
accepted from request data. Domain errors become safe form messages keyed by
their stable error codes and never include transaction descriptions or source
rows.
The application continues to bind only to loopback. Formal login and multi-user
authorization are outside this local single-owner release. Django session and
message support may be enabled only to carry local form feedback; no financial
values are stored in cookies.
## Query boundaries
`finance.queries` owns reusable read models for transaction and reference lists.
It validates filter values, applies deterministic ordering, selects related
objects deliberately, and returns querysets or immutable result values without
mutating records. Views remain responsible for HTTP parsing and pagination.
`finance.reports` owns aggregate calculations and returns immutable report
values. It performs decimal aggregation in PostgreSQL and never combines
currencies. Empty periods return explicit zero totals and empty category rows.
Only query shapes used by the screens and exports receive indexes. Initial
indexes cover transaction posting date and the common account-plus-posting-date
path. Their migrations are verified against PostgreSQL.
## Reporting semantics
All monthly reports use `posting_date` and an inclusive calendar month selected
by the owner. Every report selects exactly one currency. Archived accounts and
references remain included when they have activity in the selected period.
The monthly cash-flow summary uses economic activity across bank and credit-card
accounts:
- `INCOME` contributes its positive amount to inflow;
- `EXPENSE` contributes the absolute value of its negative amount to spending;
- `REFUND` reduces spending in the month in which the refund posts;
- `TRANSFER` is excluded, including credit-card payments; and
- `OPENING` is excluded.
Net activity is inflow minus net spending. Net spending may be negative when a
month contains refunds greater than expenses. A card purchase is counted as
spending when it posts; its later card-payment transfer is excluded, so it is
not counted twice.
The spending summary groups `EXPENSE` and `REFUND` amounts by expense category.
An explicit Uncategorized row includes transactions without a category.
Refunds use their own assigned category and are not automatically inherited from
the original purchase. Rows show expense, refund, and net-spending totals and
sort by net spending descending with a stable category-name tie-breaker.
Account balances are separate from cash flow. A balance is the signed sum of all
entries through the selected month end, including openings and transfer legs.
Balances are reported per account and currency and are never summed across
currencies.
## Export format and safety
An export service and management command create a versioned ZIP package under
the ignored repository-root `exports/` directory. The browser can request the
same package and receives it as a download; it does not choose a server
filesystem path.
Each package contains a JSON manifest and UTF-8 CSV files for accounts, payees,
categories, statements, installment plans, transactions, transfers, and source
records. Stable UUIDs, ISO-8601 dates and timestamps, exact decimal strings,
archival state, relationship UUIDs, and provenance metadata are retained.
Audit events and import-review working state belong to backups rather than the
portable financial export.
The manifest records the export schema version, creation time, application
version, file names, row counts, and SHA-256 checksums. CSV headers and file
ordering are fixed by export schema version. Database reads occur inside one
consistent transaction so the package represents one snapshot.
Command output contains only the package name, version, and row counts. Export
names are generated or validated as plain file names. Paths, traversal,
symlinks, existing destinations, and files outside `EXPORT_ROOT` are rejected.
The package is written to a private temporary file, flushed, and atomically
renamed. Failures remove the temporary file without replacing an existing
export.
## PostgreSQL backups and restore verification
Backup management commands invoke the PostgreSQL 17 `pg_dump` and `pg_restore`
clients from the application image. They use the management service's migration
database credentials and never accept passwords as command arguments. Backups
use PostgreSQL custom format with `--no-owner` and `--no-acl` and are stored
under the ignored repository-root `backups/` directory.
Each backup has a JSON manifest containing format version, creation time,
application version, PostgreSQL client version, database name, applied Django
migrations, byte size, and SHA-256 checksum. Creation uses a private temporary
file, `fsync`, and atomic rename; existing targets are never overwritten.
Terminal output excludes financial data and credentials.
Restore verification requires a database connection explicitly marked as a
verification target and rejects the configured live database host/name pair.
The target database must be empty. Verification checks the manifest and digest,
restores with `pg_restore --exit-on-error --single-transaction`, then runs Django
schema checks and deterministic row-count/integrity checks. The acceptance test
uses a disposable PostgreSQL service populated only with synthetic data.
Restoring over the live database is not part of the web UI or automated
verification. The owner-facing recovery guide documents a deliberate command
sequence and requires stopping the web service, creating a separate safety
backup, and naming the target database explicitly.
## Configuration and storage
`EXPORT_ROOT` and `BACKUP_ROOT` default to `/app/exports` and `/app/backups` in
containers. Docker Compose mounts the ignored repository directories into the
management and web services with only the access each workflow needs. The
PostgreSQL client major version matches the PostgreSQL 17 server.
Generated exports, backups, temporary files, database dumps, reports, and logs
remain ignored. Automated tests use synthetic records and temporary directories.
No fixture or assertion contains copied financial data.
## Error handling
Form validation errors stay on the submitted page without mutation. Domain
conflicts are mapped to stable, human-readable local messages. Missing objects
return 404. Invalid report and list filters return field-level errors rather
than silently changing the requested range.
Export and backup commands fail closed for invalid paths, existing files,
checksum mismatch, incompatible format versions, unavailable PostgreSQL tools,
database errors, or nonempty verification targets. They emit stable safe error
codes and leave no published partial artifact.
## Verification and completion
Unit and PostgreSQL-backed tests cover query filters, pagination stability,
report calculations, currency separation, refunds, transfers, openings,
uncategorized spending, archived references, and account balances. Request tests
cover page rendering, CSRF-protected POST workflows, audited service writes,
immutable financial fields, and safe errors.
Export tests inspect package schemas, ordering, exact decimals, manifests,
checksums, snapshot behavior, path rejection, non-overwrite behavior, and failed
write cleanup. Backup integration tests create a synthetic database state,
produce a dump, restore it into a separate empty database, verify migrations and
row integrity, and confirm the original database is unchanged.
The documented acceptance workflow creates synthetic reference data, previews
and commits a synthetic import, categorizes a transaction in the browser,
inspects monthly cash flow and spending, downloads an export, creates a backup,
and verifies restoration into the disposable database. Roadmap step 5 is
complete when that workflow and the full automated suite pass from the Docker
Compose environment.
Loading…
Cancel
Save