Files
finance/docs/adr/0001-financial-space-ownership-and-rls.md
k1nq d582f15681 feat: add tests for import polling, login page, money utilities, and query keys
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses.
- Created tests for the login page, validating user interactions and error handling.
- Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values.
- Developed tests for query keys, ensuring proper scoping and invalidation roots.

chore: setup testing environment and configuration

- Added setup file for testing with Vitest and Testing Library.
- Configured TypeScript and Vite for the frontend project.
- Created Docker and Nginx configurations for local development and production environments.
- Established a PostgreSQL initialization script for setting up application roles.

docs: add README and environment configuration for infra

- Included README for the infra directory, detailing local development setup and service descriptions.
- Created an example environment configuration file for local development with placeholders for sensitive information.
2026-09-14 09:26:33 +05:00

2.1 KiB

ADR 0001: Financial space ownership and RLS

  • Status: Accepted
  • Date: 2026-09-11
  • Scope: tenancy model, data isolation, authorization boundary

Context

Every financial fact (accounts, operations, postings, budgets, categories, payees, imports) belongs to exactly one financial space. All members of a space see all of its data; there is no per-user row visibility in MVP. Support staff must not be able to browse financial content. PostgreSQL is the only datastore, with pooled connections shared by API processes and Celery workers, so a leaked tenant context is a critical failure mode.

Decision

  • financial_spaces is the single ownership boundary. Every financial table carries space_id from day one.
  • Membership links a user to a space with role owner or member; all members have full visibility inside the space. At least one owner must always remain.
  • Authorization is enforced twice:
    1. Application services check membership before any query.
    2. Inside the request/task transaction the service runs SET LOCAL app.space_id, and PostgreSQL RLS policies re-check every access. A missing context fails closed.
  • Workers receive IDs only, re-validate ownership, and set the same transaction-local RLS context.
  • Cross-space references are forbidden by composite foreign keys (space_id, id) between financial tables.
  • A bypass/owner role exists only for migrations and audited break-glass tooling; the API and worker roles are always RLS-restricted.

Consequences

  • Retrofitting space_id + RLS onto existing tables is expensive, so this must hold from the first migration (hard to reverse).
  • Pooled connections must never leak context: settings are transaction-local only, and tenant-escape negative tests are mandatory.
  • Break-glass requires a separate role, a mandatory reason, short-lived grants, and append-only audit.

Alternatives rejected

  • Application-level filtering only — one missed filter leaks tenant data.
  • Database-per-tenant — operational cost unjustified at beta scale (≤ 100 users).
  • Per-user row visibility — contradicts the space ownership model.