# Feature index

One file per shipped feature area — the long-term product memory of
this app. When adding to one, update its **Changelog** section and
this table. Template and discipline: [../README.md](../README.md).

| File | What it covers |
|---|---|
| [auth.md](auth.md) | Sign in with BW (Pattern B): OAuth code+PKCE flow, default-deny middleware, app session, user auto-provisioning, sign-out, dev stub |
| [migration.md](migration.md) | R1 schema (alembic 0003), legacy→R1 transforms (idempotent, reconciled, straggler rule), new-side harness emitter, nightly full rehearsal + snapshot-on-boot |
| [read-mirror.md](read-mirror.md) | The read-only mirror (M3): authz engine (capability grid, can/scope, app-layer view-as + audit), reporting ports (§1 filter contract, one status predicate, totals/options/summary/rollup/expansion), /entries + /summary + /projects pages, /api/view-as |
| [imports.md](imports.md) | Clockify CSV imports (M4): the clockify.ts parser port (CLK suite + cross-validation vs the TS code of record), pending staging w/ enforced expiry, the map/create/skip resolver + incomplete buckets, transactional commit with write-time money stamps (terms + override precedence), batch list/delete w/ invoice-attachment guard, /import + /import/resolve UI |
| [entries-editing.md](entries-editing.md) | Entries editing + bulk safety (M5): cell-level editing (EntityPicker + the cascade under FKs), manual entry w/ auto-batch, re-resolve, delete, the bulk engine (scope-from-display-query, expected-count safety stop, undo via audit before-images), entity manage w/ first-class merge, /projects/manage UI |
| [cc-expenses.md](cc-expenses.md) | CC pass-through expenses (M7, slice A): statement paste + faithful parser port (byte-diffed vs the legacy TS on 102 real lines), batches, keyword auto-assign rules + re-apply (newest-wins), per-line assignment via EntityPicker, the 3-color row scheme (saved > auto-matched > highlight as tokens), and the PlusROI paste-block export (tenant-settings literal). Owner-only; `invoice_line_id` stays NULL in R1 |
| [invoices.md](invoices.md) | Invoices + locks (M6): invoice CRUD w/ first-crossing status stamps, time-kind lines snapshotting display fields at attach, the invoice_line_id lock, bulk apply/detach on the M5 engine (ONE eligibility predicate, explicit-only detach, inverse-op undo), guarded delete, scope()-limited SQL aggregates, the PlusROI paste block as tenant settings (byte-diffed vs the legacy TS code of record), /invoices UI |
| [comments.md](comments.md) | Comments board + bell (M7): the org-wide comment feed behind the 💬 nav bell — post w/ effective-user attribution, cosmetic @mentions, resolve/reopen (no state precondition), author-only delete ENFORCED server-side (explicit 403 vs the legacy silent no-op), a REAL unresolved count(*) badge, Open/Resolved tabs, timeAgo buckets; RLS → service-layer authz, view-as identity wart fixed (ONE identity for attribution + gating) |
| [team.md](team.md) | Team & engagement admin (M7): the ADMIN→Team page replacing the legacy team_members CRUD — workers as person parties + subcontract engagements + compensation_terms (kind cost/billout); add worker (reuse existing party by email), APPEND-ONLY rate history (a change ends the current term + opens a new one effective today, existing entry stamps never touched), deactivate = end engagement (history kept, Gary's 0/25 representable), project billout overrides moved onto this page (worker-specific > project-wide, XOR rate/pct, pct floored at -100%), consolidate_as label; can_manage_team gate (owner/super-admin), billout gated on can_transfer |
| [saved-views.md](saved-views.md) | Saved views (M8): per-user named views over the filtered tables (`/entries`, `/projects`, CC batches) — name-this-view from the current URL state, one-click apply, pin to the nav; new `saved_views` table (alembic 0005) + `/api/saved-views` CRUD; seeded recurring views (rian's PlusROI/pending/blocked + hourly/missing-info/admin) that RETIRE the hardcoded quick-chips; per-user, keyed on the real user, not audited |
| [projects-financials.md](projects-financials.md) | Project financials (M8, v0.9.1): owner-only income + display-only billout adjustments (pct + fixed, they COMBINE — not XOR) set inline on `/projects`; `PATCH /api/projects/{id}/financials` + `services/projects_admin.py`; re-stamps nothing (ADR #040), reporting applies the adjustment at read time; audited, gated on `can_transfer`; closes the last unbuilt tier-A gap (T-ADJ-020 / T-PRJ-028 / T-PRJ-029, ADR #010) |
| [people-organizations.md](people-organizations.md) | People + Organizations as first-class objects over the party supertype (R2, v0.10.0, ADR #012/#013): `parties` stays the shared identity anchor; new 1:1 detail tables `organizations` (currency + Drive/Mosiah folder refs satellites read) and `people` (growth home), plus `party_affiliations` (person↔org roles — owner/employee/contact/cc, the Heather-is-a-person-AND-a-company link). Additive (harness stays green); 1:1 invariant at the `services/entities/parties` creation choke point + the transform mirror. Foundation for the satellite registry API (`docs/SATELLITE-CONTRACT.md`) |
| [tenancy.md](tenancy.md) | Multi-tenant foundation (R2, v0.12.0, ADR #015): per-user tenant membership derived from the engagement graph (owner/manager/worker) + all tenants for super admins; a request-scoped current tenant via the HttpOnly `with_tenant` cookie (`tenant_id` enters the Actor in `resolve_actor`, replacing the `APP_TENANT_SLUG` pin); `GET /api/me` gains `tenants`/`tenant`; `POST /api/tenant` switches (403 TENANT_FORBIDDEN off-list); the header `TenantSwitcher` (shown only for >1 book); NO_TENANT 403 gate on data routes; view-as tenant-intersection (409 VIEW_AS_TENANT_MISMATCH). No schema change |
| [plusroi-books.md](plusroi-books.md) | PlusROI books: schema + sheet importer + settlement engine + the Books UI (R2, v0.12.0–v0.13.0, ADR #016/#017/#018). The legacy Google-Sheet books (Invoices receivables, Expenses payables, client/project registries) imported into the PlusROI tenant's ledger (additive `invoices` enrichment + NEW `expenses`/`client_profiles`/`project_billing`, alembic 0007; `sheet_books.py` in `run_all` with TRUNCATE-rebuild idempotency + per-tab reconciliation + live↔archive dedupe). The settlement engine (`services/money/settlement.py`, the Monthly Split formula + suggested BW Wave invoice, owner-only). The **Books UI** (v0.13.0): owner-only `/books/settlement` (the Monthly Split waterfall + suggested invoice + comparison strip + reimbursements), `/books/invoices` + `/books/expenses` ledgers, a dismissable parallel-run banner, and two list endpoints + `/api/books/meta` (`app/services/books.py`). Read-only while sheet-owned; PlusROI is APP-owned since 2026-07-12 (`books_owner`, ADR #021 — writes unlocked, nightly leaves it untouched) |
| [tenant-modes.md](tenant-modes.md) | Tenant modes / workspace profiles (v0.14.0–, ADR #020/#021): the declarative WorkspaceProfile (theme + shell + nav + columns; no `if (tenant)` in components), the PlusROI host-matched skin, AND the data-ownership model — `tenants.settings.books_owner` (`sheet`\|`app`, supersedes `parallel_run`), stable party uuids across rebuilds (upsert-by-slug), the tenant-scoped nightly reset, the sheet-step app-owned skip, and `scripts/flip_books_owner.py` (PlusROI flipped app-owned 2026-07-12) |
| [hub.md](hub.md) | The Hub v0 (ADR #026): a super-admin-only, cross-tenant, READ-ONLY data browser — five `GET /api/hub/*` endpoints (Projects, Organizations, People, Tenants, Accounts) that deliberately span EVERY tenant (no `scope()`), so they are gated to super admins (`is_super_admin && !is_viewing_as`, refused BEFORE any query → 403 FORBIDDEN + zero rows for a non-super-admin, incl. under view-as); grouped-count SQL, money-free. `app/services/hub.py` + `app/routers/hub.py` (tenant-exempt in `deps.py`); the `/hub` SPA area (neutral DataTables + index) with the super-admin-gated nav group. No schema change |
| [registry-api.md](registry-api.md) | Satellite registry API (R2, v0.11.0, ADR #014): the `/api/registry/*` public edge suite satellites (`hosting` first) call to resolve/search parties, create organizations (via the `create_org_party` choke point — 409 dedup + `allow_duplicate` + `near_matches`), and read a client's shared reference facts (Drive/Mosiah folders). Server-to-server Bearer service-token auth (fail-closed 503 when unconfigured; session never substitutes); default-deny session-gate bypass for `/api/registry/`; suite-global, audited create. Contract: `docs/SATELLITE-CONTRACT.md` |

The parity bar these build toward is
`/srv/apps/work/docs/rebuild/01-current-system/`.
