## Summary Fixes **TRU-18 / AUT-14** — `truckwash.io` invoices were being routed to the wrong Economic (EC) account for some users. ## Root cause `getUserByCustomerNumber()` in `services/nginx/app/objects/users_o.php` trusted the **inverse Redis cache** (`customer_number → user_id`) without verifying that the user it loaded actually owned the requested EC customer_number in the local DB. When that cache went stale — e.g. after a `customer_number` re-mapping on a code path that did not clear the inverse-cache entry — `getUserByCustomerNumber()` would silently return a **different user** whose current `customer_number` no longer matched the one the caller asked for. Downstream invoice export code (`getCustomerEcocomicData()` → `$customer_economic->customer_number` → `economic_invoice_draft->setCustomerNumber(...)`) then used that wrong user's current EC customer_number, and the draft invoice was created against the **wrong Economic account**. Because this only manifests when the inverse cache is stale, it surfaces as "some users" — exactly the symptom reported. ## Fix Minimal change in `getUserByCustomerNumber()`: 1. After the Redis fast-path loads a user, read the actual `customer_number` from the DB via `getObjectProperties()`. 2. **Verify** that it equals the requested `$customer_number`. 3. If not, the inverse cache is stale: clear it (`clear_user_id_from_customer_number`) and re-fetch via the recursive call, which now falls through to the authoritative `SELECT id FROM users WHERE customer_number = ?` DB query. The DB path was always correct (it filters by exact `customer_number`); the bug was exclusively in the unchecked Redis fast-path. ## Regression test `tests/Unit/Users/GetUserByCustomerNumberStaleCacheTest.php` — wiring tests that assert the verification + cache-clear + recursive re-fetch are present, plus that the DB lookup path is the source of truth. Prevents the regression from reappearing silently. ## Test run PHP is unavailable in the sandbox, so the new test has not been executed locally. It is a pure wiring test (string assertions on the source file) and will be verified by CI on PR open. ## Out of scope - No change to `openclaw.json`, deployment config, or any other config files. - Auto-merge is intentionally **not** enabled — leaving that to the existing auto-merge cron. - Existing tests untouched. ## Linear - TRU-18 will be moved to "In Review" with the PR URL in a follow-up comment. 🤖 Generated with [MaxClaw](https://maxclaw.ai) --------- Co-authored-by: TRU-18 backend bot <bot@truckwash.dev> Co-authored-by: Jeppe B <jeppe@copenhagentruckwash.io>