# Tier-A cutover-gate test inventory (M8)

The cutover gate is **tier A + the diff harness** (06-test-plan.md §0):
money stamping/locks (§3), the authz parity grids (§4.1–4.2), the import
pipeline ports (§2), migration + harness checks (§10), bulk-mutation
safety (§7.2), RPC-equivalence (§6), and the currency stamp subset of
§3.1b (T-CUR-001/004/005/007, T-MIG-015). This doc maps every tier-A ID
to its implementing test(s) and status, so the gate is an inventory, not
a vibe.

## Verdict

**282 tier-A IDs · 256 MAPPED · 17 PARTIAL · 9 GAP.**

Every MAPPED row has a test whose name/docstring carries the ID (test
root `/srv/apps/with/tests/`). PARTIAL = a related assertion exists but
doesn't fully pin the row. GAP = no implementing test. The PARTIAL/GAP
rows below each carry a disposition; the money/authz/import/migration
cores (§2, §3.1/§3.6, §4.1, §10 minus one harness self-test) are fully
green.

## Closed in M8 (10 rows: 5 gaps + 5 partials → MAPPED)

The last 3 rows (T-ADJ-020, T-PRJ-028, T-PRJ-029) close the **project
income/adjustment editing** feature — the one genuinely-unbuilt tier-A
feature, built at the end of M8 (v0.9.1; ADR #010).

| ID | was | now — implementing test |
|---|---|---|
| T-PHX-007 | GAP | `tests/meta/test_phase_out_guards.py::test_no_rls_in_runtime_schema` + `::test_no_supabase_auth_client_in_runtime` + `::test_app_mints_no_identity_jwt` |
| T-PHX-009 | GAP | `tests/meta/test_phase_out_guards.py::test_one_eligibility_predicate` + `::test_no_inline_eligibility_conjunction_outside_filters` (backed by the `_sort_expr` → `status_sort_rank` dedup) |
| T-PHX-010 | GAP | `tests/meta/test_phase_out_guards.py::test_no_toggl_export_path` |
| T-ADJ-009 | PARTIAL | `tests/team_admin/test_team_admin.py::test_rate_override_pct_lower_bound` (backed by the new `-100%` floor guard) |
| T-TGL-011 | PARTIAL | `tests/reporting/test_blocked_reason.py::test_t_tgl_011_each_missing_field_flags_its_exact_message` |
| T-TGL-012 | PARTIAL | `tests/reporting/test_blocked_reason.py::test_t_tgl_012_fully_resolved_is_not_blocked` |
| T-RPT-006 | PARTIAL | `tests/perf/test_no_seq_scan.py` (the M8 perf gate — §0 re-tiers this ID to C; the CI-testable half now exists) |
| T-ADJ-020 | GAP | `tests/reporting/test_project_financials.py::test_t_adj_020_blank_clears_all` (blank save clears income + both adjustments to NULL) + `::test_t_prj_028_income_and_adjustments_parse` clear path |
| T-PRJ-028 | PARTIAL | `tests/reporting/test_project_financials.py::test_t_prj_028_income_and_adjustments_parse` (income "1,234.50" / pct "-10" / amount "5" round-trip) + `tests/routers/test_projects_financials_api.py::test_owner_financials_round_trip` |
| T-PRJ-029 | GAP | `tests/reporting/test_project_financials.py::test_t_prj_029_unassigned_group_has_no_write_surface` + `tests/routers/test_projects_financials_api.py::test_unknown_project_404` (financials attach to a project id; unassigned/unknown → PROJECT_NOT_FOUND, the structural "fix the entries first" guard) |

## Remaining GAPs (9) — disposition

| ID | area | disposition |
|---|---|---|
| **T-ADJ-011** | §3.3 re-stamp scope | **Re-scoped by architecture.** The rebuild decoupled override-save from re-stamp (append-only terms); re-stamping runs through the bulk engine over an `EntryFilters` scope. The project-wide-vs-worker differential is expressible as a bulk-recalc test but isn't written. Implementable; deferred. |
| **T-ADJ-026** | §3.3 atomicity | **Vacuous in the rebuild.** There is no combined "save override + re-stamp" transaction to be non-atomic — override authoring and re-stamp are separate, auditable actions (ADR #008 team-admin). No half-state exists to test. |
| **T-VAS-07** | §4.2 view-as team | Behavior holds by construction (the team page reads through the same scoped actor), but no team-page-specific view-as test. Deferred (low risk). |
| **T-VAS-08** | §4.2 config in view-as | **Not applicable in R1.** No app-level credential/config action exists to block: password change died with id-auth; saved views key on the REAL user (ADR #009 #2). Nothing to block while impersonating. |
| **T-RBAC-005** | §4.2 nav gating | Frontend nav gating (hide Invoices/CC links when `canTransfer` false) — a tier-B frontend concern; not pytest-shaped. Candidate for a Playwright/vitest check. Deferred. |
| **T-TEAM-14** | §4.2 empty state | Zero-teams empty-state + query short-circuit — behavior present; no explicit test. Deferred (low risk). |
| **T-PRJ-021** | §6 projects sort | NULL-income rows sort to the bottom — verify + test on the projects rollup sort. Implementable; deferred. |
| **T-SUM-006** | §6 summary hide | this=0/prev=0/all>0 row hidden but all-time still in the footer — behavior present; needs a fixture-shaped test. Deferred. |
| **T-PRJ-034** | §7.2 group bulk | The /projects group-bulk path (2 groups + project target, ignoring the date filter) — the bulk tests drive `EntryFilters`, not project groups. Deferred. |

None of the 9 blocks the money/authz/import/migration cores. The former
T-ADJ-020 / T-PRJ-028 / T-PRJ-029 gap — **project income/adjustment
editing** — is now BUILT and mapped (see "Closed in M8"; ADR #010;
`docs/UNDOCUMENTED.md` cleared). The remaining 9 are present-but-untested
or re-scoped/not-applicable.

## Remaining PARTIALs (17) — one-liners

`T-IMP-014` picker-scope is a frontend behavior (catalog asserted) ·
`T-CUR-005` per-line currency inheritance + export (batch default
asserted) · `T-CUR-007` re-stamp currency (write-path pair asserted) ·
`T-ADJ-007` specific-pct-over-project-abs combo + NULL-default pin ·
`T-ADJ-010` spec wants UPSERT, impl REJECTS a duplicate scope (reconcile
spec vs impl — no-dup holds either way) · `T-ADJ-005`/`T-ADJ-016`/
`T-ADJ-025` override-save re-stamp trigger / actor-independent stamp /
non-owner create-delete refusal (adjacent coverage exists) · `T-INV-023`
partial-visibility breakdown sums · `T-AZ-004` full route-inventory
meta-test (scope-equivalence asserted) · `T-VAS-05` exact copy string ·
`T-RBAC-002`/`T-PRJ-024` switcher render / projects view-as clamp
(frontend / entries-clamp asserted) · `T-ENT-09` page=3 packing (page=1
asserted) · `T-ENT-14s` two-step reassign guard · `T-MIG-005`
allowlist SELF-test with a seeded diff forcing RED (clean-run green
asserted) · `T-PHX-011` all-20 baseline + prepaid drawdown totals
(miniature asserted).

Highest-value follow-ups for the orchestrator: **T-MIG-005** (seed an
unexpected diff, assert the harness goes RED — the allowlist's own
regression guard) and **T-AZ-004** (the route-inventory meta-test that
no router builds its own WHERE clause — the M8 route-auth sweep in
`tests/security/` is the scaffolding for it).

## The MAPPED core (253) — where it lives

Fully mapped, ID-named tests, by area:

- **§2.1 Clockify parser (38)** — `tests/imports/test_clockify_parser.py`
  (`test_clk_*`) + the split/dedup cases.
- **§2.2 import pipeline (31)** — `tests/imports/test_commit.py`,
  `test_staging.py`, `test_resolver.py`, `test_batches.py`,
  `test_import_api.py` (`test_imp_*`).
- **§2.3 CC pipeline (48)** — `tests/cc/test_cc_parse.py` (`test_cce_*`)
  + `tests/cc/test_cc_service.py` (`test_cce_a_*`, B-002).
- **§3.1 stamping (11)** — `tests/money/test_stamping.py`
  (`test_clk_022b/023/021b`, `test_mny_001`, `test_team_07/08`, LOCK via
  `tests/team_admin/test_append_only_gate.py` + `entries_write/`).
- **§3.1b currency subset** — T-CUR-001/004 mapped
  (`money/test_stamping.py`, `invoices/test_service.py`), T-MIG-015
  (`migrate/test_transforms.py`); T-CUR-005/007 partial (above).
- **§3.2 effective rate (9)** — `tests/money/test_stamping.py`
  (`test_adj_001..004/008`), `tests/team_admin/test_team_admin.py`
  (XOR, precedence, **pct floor**).
- **§3.3 re-stamp (13)** — `tests/bulk/test_bulk_safety.py`,
  `tests/invoices/test_service.py`, `tests/team_admin/`.
- **§3.4 display adj (4)** — `tests/reporting/test_projects_service.py`,
  `test_summary_service.py`; the income/adjustment WRITE surface
  (T-ADJ-020/T-PRJ-028/029) is `tests/reporting/test_project_financials.py`
  + `tests/routers/test_projects_financials_api.py` (ADR #010).
- **§3.5 eligibility (5)** — `tests/reporting/test_filter_contract.py`,
  `tests/reporting/test_blocked_reason.py`, `tests/meta/` (T-PHX-009/010).
- **§3.6 invoices (33)** — `tests/invoices/test_service.py`,
  `tests/routers/test_invoices_api.py`, `tests/money/test_paste_block.py`
  (`test_inv_*`, `test_inv_p_*`) (T-INV-023 partial).
- **§4.1 authz grid (9)** — `tests/authz/test_capabilities_grid.py`,
  `test_scope_equivalence.py`, `test_context.py` (`test_t_az_*`,
  `test_t_prm_*`).
- **§4.2 view-as (20)** — `tests/routers/test_view_as_api.py`,
  `tests/authz/`, `tests/reporting/`, `tests/entries_write/`,
  `tests/bulk/` (gaps/partials above).
- **§6 RPC equivalence (30)** — `tests/reporting/test_filter_contract.py`,
  `test_projects_service.py`, `test_summary_service.py`,
  `test_rpt_equivalence.py`; the perf half is `tests/perf/`.
- **§7.2 bulk safety (9)** — `tests/bulk/test_bulk_safety.py`,
  `tests/routers/test_write_api.py` (`test_t_ent_16/17/25/44`).
- **§10 migration (17)** — `tests/migrate/test_harness_parity.py`,
  `test_transforms.py`, `test_schema.py`, `test_straggler.py`
  (`test_mig_*`, `test_phx_*`) (T-MIG-005/PHX-011 partial).

## Standing rule
When a PARTIAL/GAP is closed, move its row to "Closed" with the
implementing test and re-tally the verdict line. When a new tier-A
behavior lands, add its ID here in the same change (06-test-plan §11).
