require_auth now uses HTTPBearer(auto_error=False) and raises 401 explicitly
when the Authorization header is missing or non-Bearer, matching
require_auth_401. Unauthenticated requests are 401 for every user endpoint;
authenticated-but-forbidden (cross-entity / role) stays 403.
- app/auth_middleware.py: single optional bearer, 401 on missing token
(require_auth_401 kept as a compatibility alias)
- tests: update 4 pre-existing cases that asserted 403 without a token
(test_auth, test_reports, test_kpi_causality, test_expenses,
test_api_auth_surface_20260914) - intentional behavior change
- add tests/test_auth_401_unification_20260915.py: sample + full sweep
(all AUTHED GET routes -> 401), invalid token -> 401, cross-entity and
role-denied still 403, bot channel (X-BOT-KEY) and public paths unchanged
- data_quality.py: remove GET /check-governance (no caller repo-wide; its
entity_id param had no session auth -> cross-tenant read; duplicate of
governance-check) -> the path now returns 404
- reports.py: /reports/dupont resolves the analysis target from the session
account (assert_entity_slug_scope / resolve_session_slug); explicit
cross-entity entity param -> 403, illegal value -> 400; the 2026-08-31
cross-entity exemption is revoked (docstring updated)
- frontend: drop the checkGovernance api definition; DupontAnalysis.vue
entity radio group -> read-only session entity tag, request without param
- tests: TestCheckGovernanceRetired (404 x2), TestDupontSessionScope (3),
TestDupont reworked to account-bound tokens (8 tests)
- docs/api-verification-split.md: drop the retired row, note 334/404 -> 333/403
pytest: 10 failed / 957 passed / 1 xfailed vs HEAD baseline 10 failed /
951 passed / 1 xfailed (identical failure set -> zero regression)
(cma-residual-cross-entity-cleanup-20260914)