# Research — Spec 083 (Required Permissions hardening)

## Context recap
- Canonical manage-plane surface already exists as a Filament Page: `App\Filament\Pages\TenantRequiredPermissions` with slug `tenants/{tenant}/required-permissions` (admin panel).
- A legacy tenant-plane path prefix exists (`/admin/t/...`) via the tenant panel; spec requires `/admin/t/{tenant}/required-permissions` to remain non-existent and return 404.

## Decisions

### Decision 1 — Canonical route stays `/admin/tenants/{tenant}/required-permissions`
- **Chosen**: Keep the canonical manage URL exactly as specified in Spec 083.
- **Rationale**: Already aligned with the existing page slug and with the established route contract in Spec 080.
- **Alternatives considered**:
  - Redirect from `/admin/t/...` → rejected (spec requires 404, no redirect).

### Decision 2 — Deny-as-not-found is implemented explicitly (404), not via “canAccess() only”
- **Chosen**: Enforce 404 using explicit `abort(404)` checks on request entry (e.g., `mount()`), instead of relying solely on Filament’s `canAccess()` return value.
- **Rationale**: Filament’s `canAccess()` may produce behavior that is not guaranteed to be a 404. Spec 083 requires strict 404 semantics for non-members / non-entitled.
- **Alternatives considered**:
  - Only `canAccess()` returning false → rejected (status code semantics uncertain).
  - Route-level middleware on just this page → possible, but still needs explicit entitlement checks; can be added later if desired.

### Decision 3 — Tenant entitlement is checked via `User::canAccessTenant($tenant)`
- **Chosen**: Use `User::canAccessTenant()` for tenant entitlement (no workspace-wide “view all tenants” override).
- **Rationale**: This matches existing patterns across the codebase, uses `tenant_memberships`, and aligns with the clarification outcome.
- **Alternatives considered**:
  - Workspace membership only → rejected (Spec 083 requires tenant entitlement).
  - Capability checks for read-only view → rejected (read-only access is entitlement-only; mutations are capability-gated elsewhere).

### Decision 4 — Remove cross-plane tenant fallback (`Tenant::current()`) from this surface
- **Chosen**: `TenantRequiredPermissions::resolveScopedTenant()` must be strict: resolve only from route parameter `{tenant}` (external_id or bound model). If absent/invalid → 404.
- **Rationale**: `Tenant::current()` can leak cross-plane context and violates FR-083-007.
- **Alternatives considered**:
  - Keep fallback for convenience → rejected (security hardening goal).

### Decision 5 — DB-only render is guaranteed by using stored `tenant_permissions`
- **Chosen**: Continue using `TenantPermissionService::compare(... liveCheck:false ...)` for this page (no Graph calls).
- **Rationale**: With `liveCheck=false`, compare reads stored `tenant_permissions` only.
- **Alternatives considered**:
  - Allow live-check on render → rejected (violates FR-083-010).

### Decision 6 — Freshness (“Last refreshed”) comes from `tenant_permissions.last_checked_at`
- **Chosen**: Define page “Last refreshed” as the max timestamp of stored permission checks for the tenant. Stale if missing or older than 30 days.
- **Rationale**: This is already stored in the database and does not require provider calls.
- **Alternatives considered**:
  - Use latest verification run timestamps → possible, but increases coupling; not necessary for Spec 083.

### Decision 7 — Summary status logic is centralized in the view-model builder
- **Chosen**: Update `TenantRequiredPermissionsViewModelBuilder::deriveOverallStatus()` so:
  - Blocked if any missing application permission (blocker)
  - Else Needs attention if any warning exists (missing delegated, error rows folded into warning, or stale freshness)
  - Else Ready
- **Rationale**: Aligns with Spec 083 summary rules and keeps mapping centralized.
- **Alternatives considered**:
  - Compute in Blade view → rejected (harder to test, risks drift).

### Decision 8 — “Re-run verification” CTA links to `/admin/onboarding` (“Start verification” surface)
- **Chosen**: Link-only CTA points to the existing onboarding wizard page (admin panel slug `onboarding`).
- **Rationale**: Clarification outcome; capability gating occurs on the start/execute surface, not on this read-only page.
- **Alternatives considered**:
  - Link to provider connection edit → rejected (not the requested primary action).

## Open questions
None remaining for Spec 083 (clarifications already settled).
