# WP2 — The invariant test: the guard that outlives us

**Repo:** BE (`/home/moonui2/moon-erp-be`) · **Branch:** `hazemdev2` · **Migration:** no · **FE change:** none
**This lands BEFORE any controller is wired.** That ordering is the point.

## Why this WP exists, and why it comes before the wiring

The architecture analysis found the decisive fact about this codebase:

> There are **zero** global scopes (`addGlobalScope` count: 0). Company scoping is hand-written per
> controller method — **8 occurrences inside `InventoryIssueController` alone**. `scopeTenant()` was
> written to centralise exactly that and is barely used.

And the proof of what that costs: the same team that wrote `DataScope` into **~35** LIS and Clinic
call sites wrote it into **zero** Inventory ones. Two ownership columns
(`warehouses.manager_id`, `petty_cash.custodian_id`) sat in the schema for months, settable from the
UI, filtering nothing.

**So there is no seam in this codebase where a new controller gets scoping for free.** WP1 built the
engine; nothing makes anyone call it. This test is the only thing that will.

It must land **before** WP3 so that WP3's work is measured against it — and so it fails loudly today,
which is the honest starting state.

## What to build

A test that **enumerates the real routes** of the scoped modules and asserts that a restricted user
gets restricted data — and that **fails when someone adds a new endpoint without scoping it**.

Design it deliberately; these are the decisions that make it useful rather than decorative:

1. **Enumerate from the router, not from a hand-written list.** Use Laravel's route collection filtered
   to the modules in scope. A hand-maintained list is exactly the thing that rots — the failure mode
   this test exists to prevent.
2. **An explicit, documented allow-list of exemptions.** Some endpoints legitimately need no resource
   scoping (lookups of company-wide reference data, settings, the warehouse *catalogue* itself if the
   owner decides pickers are handled elsewhere). Each exemption must carry a **written reason** in the
   test file. The allow-list is the feature: adding a route forces a developer to either scope it or
   justify it in writing — a reviewable act either way.
3. **Assert on data, not on code shape.** Don't grep controllers for a call to `ResourceScope`. Seed
   two warehouses, assign the user to one, set the mode to `assigned`, call the endpoint, and assert
   the response contains **only** the assigned warehouse's rows. A test that greps for a method call
   passes when someone calls it with the wrong arguments.
4. **Cover the mutation paths too, not just the lists.** `show`, `update`, `approve`, `cancel` and the
   like fetch by id with only a company guard today. Scoping the lists while leaving direct-id access
   open would be a false sense of protection — and per the settled decision, an out-of-scope record
   must yield **404**, not 403 (403 confirms the record exists).
5. **Make the failure message actionable.** When it fails it should name the route and say what to do
   — "route `inventory/issues` returned rows from an unassigned warehouse; scope it with
   `ResourceScope::apply(...)` or add it to `EXEMPT` with a reason". A test whose failure is a bare
   assertion diff will get commented out at 2am.

## Scope of the enumeration

**Inventory first**, since WP3 wires it next. Structure the test so a second module (Accounting/cash
boxes, WP6) is added by extending a data structure, not by copying the test.

## ⚠️ Expected outcome of this WP: THE TEST FAILS

Nothing is wired yet. **A red suite is the correct result of this work package**, and the failure list
is WP3's exact worklist.

Therefore:
- **Do not** wire any controller to make it pass. That is WP3.
- **Do not** weaken the test to make it green.
- **Do** mark it in whatever way this project's Pest setup supports so that a red result here does not
  masquerade as an unrelated regression for the next few WPs — e.g. a group/`todo` marker, or a
  documented expected-failure list that shrinks as WP3 progresses. **State clearly in your report
  which mechanism you used and how WP3's implementer flips it on.**
- **Do** include, in your report, the **explicit list of every route it currently fails on**. That list
  is handed to WP3 as its checklist.

## Read first

`../tasks/WP1-report.md` — the exact engine API, table names and setting keys. This test is written
against them.

## Acceptance criteria

1. The test enumerates routes from the router, not a hardcoded list.
2. Every exemption carries a written reason in the file.
3. Assertions are on returned data, not on the presence of a method call.
4. Both list endpoints and by-id endpoints are covered; by-id out-of-scope expects **404**.
5. Adding a new unscoped route to the module causes a failure — **prove this**: temporarily register a
   throwaway unscoped route, show the test catching it, then remove it. Paste the output.
6. The failure message names the route and the remedy.
7. The report contains the full current failure list (WP3's worklist).

## Environment / rules

- Tests: `cd /home/moonui2/moon-erp-be && /opt/cpanel/ea-php82/root/usr/bin/php -d memory_limit=1G vendor/bin/pest --filter='…'`
  (bare `php` is php-cgi → "Undefined constant STDOUT").
- **Run your own test file only.** Full suites run once at Phase C.
- ⛔ **Pest loads every test file into ONE process** — prefix every top-level helper with its file's
  subject. Duplicate top-level function = fatal redeclare, exit 255, zero output (5 occurrences here).
- ⛔ NEVER `migrate:fresh` / `migrate:refresh` / `db:wipe` on `moonui2_dev_be` — not binlogged.
- API auth header is `X-Authorization: Bearer` (not `Authorization`).
- `./vendor/bin/pint` on touched files only. `chown moonui2:moonui2` every edited file.
- No CHANGELOG bullet — nothing user-visible.
- Commit on `hazemdev2`, conventional. **Do not push, do not merge.**
- moonui2 ONLY — never `/home/moonui`. Never print a git remote URL.

## Out of scope

Wiring any controller (WP3) · any FE change · extending the enumeration beyond Inventory (WP6 adds
Accounting).
