# Shifa One — Tenancy & Permission Audit + Migration Path (P0-T4)

> How auth/tenancy works **today**, and how we reach the spec's RBAC + ABAC + data-scope model (Specs §4) **without breaking existing users** and **without core edits**.

## 1. Current state

### Tenancy
- Tenant = `business`; branch = `business_locations` (flat). Session middleware (`SetSessionData`) puts `user.business_id` in session; queries scope manually with `where('business_id', …)` — no global scopes.
- ClinicCore adds `clc_departments` and `clc_clinic_locations` (→ `business_locations`); all `clc_*` tables carry `business_id`.
- SaaS layer: Superadmin subscriptions gate modules per business via `package_details` JSON; quotas (users/locations/products/invoices) enforced by `ModuleUtil::isQuotaAvailable()`.
- Multi-project host: SaasSwitcher switches the default DB connection per project (Shifa One = `shifa_one_demo`).

### AuthN
- Guards: `web` (staff, `App\User`), `api` (Passport), `customer` (contact portals), plus module token guards (PortalApi owner tokens, ClinicStaffPortal staff tokens — hashed bearer, stateless).

### AuthZ (today)
- **Spatie laravel-permission**, roles per business (`Admin#{business_id}` naming).
- Permission naming: `module.action` / `module.feature.action` (e.g. `essentials.approve_leave`), registered in module migrations, surfaced in role UI via `DataController::user_permissions()`.
- Existing scopes:
  - Facility/location: `permission.location.{id}` + `access_all_locations` → `User::permitted_locations()`.
  - Own-records: `customer.view_own`-style permissions filtering by `created_by`/`user_contact_access`.
  - Record grants: `clc_authorized_vets` (explicit per-patient user grants).
- **Missing:** department scope, ABAC (attribute rules), field-level gates, alert/consent-driven blocks.

## 2. Target model (Specs §4) → implementation mapping

| Spec layer | Implementation (all inside new module; zero core edits) |
|---|---|
| 14 predefined roles | Seed per business as `doctor#{biz}`, `nurse#{biz}`, … via wizard/installer. Existing `Admin#`/`Cashier#` untouched; `super_admin` = existing superadmin user_type; `facility_admin` = admin perms + location restriction; `patient` = portal owner (never an ERP user) |
| module_level | existing subscription gate + `module_instances` check |
| feature/action level | existing Spatie convention (`clinic.encounter.create`) |
| facility scope | existing `permission.location.{id}` — reuse everywhere |
| department scope | new `org_user_departments` m2m + `OrgScope::departmentIds($user)` helper applied in module queries (mirrors `permitted_locations()` pattern) |
| own scope | permission `*.view_own` + assigned-provider column checks (e.g. `clc_appointments.doctor_id → clc_doctors.user_id`) |
| custom scope | Gate hooks resolved by a `MedAccess` policy service (rule rows: role × entity × predicate) |
| ABAC overrides | `MedAccess` service consulted by module policies. Config-driven rules seeded from the spec examples: doctor→assigned patients (`doctor_id`, `clc_authorized_vets`, department fallback); nurse→can update `vitals` group not `assessment` group; billing clerk→no clinical-note read; pharmacist→Rx read-only |
| field_level | sparse **field-group** permissions (`clinic.notes.assessment.edit`, `clinic.vitals.edit`) enforced in FormRequests + Blade/API resource filtering. Field-by-field matrix only where the spec demands it — groups keep it maintainable |
| record_level | generalize `clc_authorized_vets` → `med_record_grants` (patient_id × user_id × capability); consulted by `MedAccess` |

## 3. Migration path (non-breaking, additive-only)

1. **Phase 1 step 1 — seed, don't rewrite.** New roles/permissions are *added*; no existing role, permission, or user assignment is modified. Businesses that never run the medical wizard see zero change.
2. **Default-allow shim.** `MedAccess` checks apply only when the calling module is enabled for the business; disabled → gates return the legacy result (Spatie check only). Rules ship default-permissive except the seeded ABAC examples.
3. **Location scope reuse.** Facility scoping uses the existing `permission.location.{id}` machinery so location-restricted staff keep working unchanged.
4. **Department scope is opt-in** per module query helper; modules that don't call it behave exactly as today.
5. **Existing demo users** (`dr.yasmin.shifa`, `reception.shifa`, …) get mapped to the new roles by the seeder (doctor/receptionist/nurse/pharmacist/accountant) *in addition to* their current roles — nothing is revoked.
6. **Portal `patient` role** stays in PortalApi token middleware (never Spatie web guard) — no change.
7. Rollback = disable the module: all new gates disappear behind the module-enabled check.

## 4. Decisions & flagged ambiguities (per plan Ground Rule 6)

| # | Decision / ambiguity | Resolution chosen (flag if spec differs) |
|---|---|---|
| D1 | Spec `tenant` vs new tenant table | **Reuse `business`.** No parallel tenant entity (Ground Rule 1). Spec-only fields live in satellite tables if/when a task needs them |
| D2 | Spec wants `uuid` PKs | Platform uses auto-increment ints everywhere — **keep ints** for consistency and FK compatibility. Flagged: deviation from spec field types, sanctioned by "respect existing conventions" (Ground Rule 2) |
| D3 | Encounter as separate entity vs appointment | **Keep `clc_appointments` as the encounter** (extend). A separate encounter table would duplicate a production-proven flow; `parent_encounter_id` covers linking. Flagged for review if inpatient encounters (Phase 6) strain this — admissions get their own entity anyway |
| D4 | Spec `module_registry` full lifecycle (download/update/rollback) | Marketplace-style download/rollback **out of scope** — registry manages install/enable/disable/soft-lock over nwidart + subscription flags. Update = existing `/install/update` endpoints |
| D5 | Facility = `business_locations`, hierarchy via satellite | Nesting implemented in satellite table (parent chain, depth ≤ 5); core `business_locations` stays flat/leaf for POS compatibility |
| D6 | Field-level permissions granularity | Implemented as **field groups**, not per-column matrix (maintainability); spec's granularity achievable by adding groups |
| D7 | WebSocket in-app notifications | Start with existing polling/`notifications` table + push (portal web-push exists); websockets deferred |
| D8 | Insurance payer entity | Dedicated payer table in billing module (not `contacts`) because claims/price-agreement semantics differ; payer *contacts* for AP/AR can still link to `contacts` |

## 5. Exit criteria check (Phase 0)

- [x] EXISTING-FEATURES.md — full inventory (core, clinical, infra)
- [x] REUSE-MAP.md — every spec module annotated use-as-is/extend/wrap/bind/build-new
- [x] GAP-LIST.md — net-new items tagged by phase
- [x] Tenancy & permission migration path (this doc)
