# Branch Isolation Completion Record

Status: **Completed**

Completed on: **14 August 2026**

Final closure merge: **PR #105** (`8b64f905769f16a3c06b89033f61b5e8758adae8`)

The original design and phased implementation plan remains in `docs/BRANCH_ISOLATION_PLAN.md`. This file records the implemented end state and should be treated as the current status of that plan.

## Final Guarantees

- Non-super-administrators can access only their assigned branches.
- A legacy `primary_branch_id` remains part of accessible scope even when its pivot row is temporarily missing.
- Users with no branch fail closed for branch-owned data.
- Super-administrators retain explicit system-wide access.
- Browser-supplied branch IDs can narrow scope but cannot expand it.
- Directly branch-owned Eloquent models are protected by branch-aware global scopes.
- Parent-owned child records inherit branch visibility from their owning customer, loan, collateral, payment, or other branch-owned parent.
- Cross-branch financial-account and staff relationships are rejected before posting/mutation where the business operation requires same-branch ownership.
- Dashboard, reports, search, exports, and operational controller lists resolve branch access through the canonical branch-access service.
- Background/console execution fails closed unless it enters an explicit `BranchContext`.
- Approved global-scope bypasses are enumerated by regression tests rather than being ambient or undocumented.
- Controller source-audit coverage rejects reintroduction of the legacy pivot-only branch resolver.

## Canonical Components

- `App\Services\BranchAccessService` — single source of truth for accessible and assigned branches.
- `App\Services\UserBranchAssignmentService` — atomic primary/pivot assignment consistency.
- `App\Support\BranchContext` — explicit branch or trusted system execution context outside authenticated HTTP requests.
- `App\Models\Scopes\BranchScope` — direct branch-owned model scope.
- `App\Models\Scopes\ParentBranchScope` — parent-derived child-record scope.
- `App\Rules\SameBranchAs` — reusable same-branch relationship validation.
- `php artisan branches:repair-user-assignments` — legacy assignment consistency repair, with `--dry-run` support.

## Phase Closure

1. Phase 1 — canonical branch access: complete.
2. Phase 2 — assignment consistency and repair: complete.
3. Phase 3 — direct and financial model scoping: complete.
4. Phase 4 — indirect/parent-owned child scoping: complete, including final closure in PR #105.
5. Phase 5 — reports, dashboard, search, and exports: complete.
6. Phase 6 — cross-branch relationship integrity: complete.
7. Phase 7 — explicit background/system context: complete.
8. Phase 8 — legacy resolver cleanup, audited bypasses, and final controller closure: complete.

## Final Parent-Owned Scope Closure

The final audit found that an old Phase 4 PR had never reached `main`. PR #105 rebuilt and merged the missing protection for:

- `CustomerAddress` via `customers.branch_id`
- `NextOfKin` via `customers.branch_id`
- `Guarantor` via `customers.branch_id`
- `CustomerRiskProfile` via `customers.branch_id`
- `LoanInstallment` via `loans.branch_id`
- `LoanPaymentAllocation` via `loan_payments.branch_id`

`RemainingParentOwnedScopeTest` verifies Branch A-only, multi-branch, no-branch, and super-administrator visibility across all six models.

## Regression Gates

Branch isolation is protected by the complete Laravel feature/unit suite and the Foundation CI MySQL fresh-migration/seed job. The final PR #105 passed both gates before merge.

Important isolation-specific regression groups include:

- strict request and direct-record isolation
- explicit `BranchContext` behavior
- direct branch-owned model scoping
- indirect finance and child scoping
- cross-branch financial write validation
- staff assignment integrity
- report/search/export scope
- primary-only legacy user behavior
- approved scope-bypass inventory
- zero legacy pivot-only controller resolver audit
- remaining parent-owned child scope coverage

## Maintenance Rule

A new branch-owned feature is not complete until its read paths, writes, relationships, reports/exports, files, and background execution behavior are covered by the appropriate branch isolation layer and regression tests.

Do not reintroduce direct `$user->branches()->pluck('branches.id')` as an authorization rule. Do not add a new unscoped `BranchScope` bypass without documenting and testing the exact authorization sequence around it.
