# Implementation Plan: RBAC Troubleshooting & Tenant UI Bugfix Pack v1

**Branch**: `067-rbac-troubleshooting` | **Date**: 2026-01-31 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from [specs/067-rbac-troubleshooting/spec.md](spec.md)

**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.

## Summary

Tighten tenant-plane (`/admin`) RBAC UX and eliminate sharp edges by:

- Applying the existing `UiEnforcement` pattern consistently on Tenant admin surfaces (especially Tenant view header actions).
- Ensuring archived (soft-deleted) tenants can still resolve in tenant-scoped routes for entitled members (404 only for non-members).
- Adding a tenant-scoped diagnostics surface to detect and repair membership invariants (missing owner) and prevent GUID-vs-bigint mistakes.
- Adding targeted Pest tests to lock in 404 vs 403 semantics and action disable/tooltip UX, and re-running existing last-owner invariant tests as regression coverage.

## Technical Context

**Language/Version**: PHP 8.4 (per repo guidelines)  
**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4  
**Storage**: PostgreSQL (via Laravel Sail)  
**Testing**: Pest v4 (PHPUnit v12 runner)  
**Target Platform**: Laravel Sail (Docker) on macOS/Linux  
**Project Type**: Web application (Laravel monolith)  
**Performance Goals**: N/A (admin UX + correctness)  
**Constraints**: Tenant-plane only (`/admin`); no `/system` expansion; no new outbound integration calls for diagnostics; must preserve RBAC-UX semantics (404 vs 403)  
**Scale/Scope**: Touches Tenant admin UI, tenancy binding for archived tenants, membership invariants + tests

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- Inventory-first: clarify what is “last observed” vs snapshots/backups
- Read/write separation: any writes require preview + confirmation + audit + tests
- Graph contract path: Graph calls only via `GraphClientInterface` + `config/graph_contracts.php`
- Deterministic capabilities: capability derivation is testable (snapshot/golden tests)
- RBAC-UX: two planes (/admin vs /system) remain separated; cross-plane is 404; non-member tenant access is 404; member-but-missing-capability is 403; authorization checks use Gates/Policies + capability registries (no raw strings, no role-string checks)
- RBAC-UX: destructive-like actions require `->requiresConfirmation()` and clear warning text
- RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics)
- Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
- Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on `/auth/*` without `OperationRun`
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
- Badge semantics (BADGE-001): status-like badges use `BadgeCatalog` / `BadgeRenderer`; no ad-hoc mappings; new values include tests

**Gate evaluation**: PASS (no constitution violations intended).

- No new Microsoft Graph call paths are introduced.
- Mutations involved (archive/restore, membership repairs) keep explicit confirmation and server-side enforcement.
- Auditing: membership repairs use existing audit logger patterns; tenant lifecycle actions already log.

**Post-design re-check**: PASS (design artifacts: [research.md](research.md), [data-model.md](data-model.md), [contracts/](contracts/)).

## Project Structure

### Documentation (this feature)

```text
specs/[###-feature]/
├── plan.md              # This file (/speckit.plan command output)
├── research.md          # Phase 0 output (/speckit.plan command)
├── data-model.md        # Phase 1 output (/speckit.plan command)
├── quickstart.md        # Phase 1 output (/speckit.plan command)
├── contracts/           # Phase 1 output (/speckit.plan command)
└── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```

### Source Code (repository root)
```text
app/
├── Filament/
│   ├── Resources/
│   ├── Pages/
│   └── Widgets/
├── Models/
├── Policies/
├── Services/
└── Support/

database/
├── migrations/
└── factories/

resources/
├── views/
└── css/

routes/
└── web.php

tests/
├── Feature/
└── Unit/
```

**Structure Decision**: Laravel monolith. Changes will land in `app/Filament/**` (tenant UI), `app/Models/**` (tenant binding/invariants), `app/Services/**` (membership repairs), and `tests/**` (Pest feature tests).

## Complexity Tracking

> **Fill ONLY if Constitution Check has violations that must be justified**

N/A.

## Phase 0 — Research (repo-backed)

### Key findings (evidence)

- Tenancy config: [app/Providers/Filament/AdminPanelProvider.php](../../app/Providers/Filament/AdminPanelProvider.php#L32-L45) uses `->tenant(Tenant::class, slugAttribute: 'external_id')` and `->tenantRoutePrefix('t')`.
- Tenant archiving is soft delete + status update: [app/Models/Tenant.php](../../app/Models/Tenant.php#L63-L92) sets `status='archived'` during soft delete and restores back to `active`.
- Tenant view header actions currently bypass UI enforcement:
  - `EditAction::make()` is unconditional (can lead to 403-on-click UX): [app/Filament/Resources/TenantResource/Pages/ViewTenant.php](../../app/Filament/Resources/TenantResource/Pages/ViewTenant.php#L19-L24)
  - `Deactivate` currently executes without capability checks (security issue): [app/Filament/Resources/TenantResource/Pages/ViewTenant.php](../../app/Filament/Resources/TenantResource/Pages/ViewTenant.php#L51-L74)
- Tenant list Restore action lacks icon: [app/Filament/Resources/TenantResource.php](../../app/Filament/Resources/TenantResource.php#L406-L454)
- Membership invariant “last owner” is already enforced server-side: [app/Services/Auth/TenantMembershipManager.php](../../app/Services/Auth/TenantMembershipManager.php#L253-L287)
- Membership uniqueness is already enforced at DB level: [database/migrations/2026_01_25_022729_create_tenant_memberships_table.php](../../database/migrations/2026_01_25_022729_create_tenant_memberships_table.php#L12-L26)

### Decisions

- Archived tenant 404s for members: implement tenant route binding that includes soft-deleted tenants, then rely on membership middleware to enforce deny-as-not-found for non-members.
- Replace ad-hoc tooltips on tenant actions with centralized `UiTooltips` constants (add a tenant-archived tooltip string).
- Standardize tenant page header actions using `UiEnforcement` so member-without-capability sees disabled actions with tooltip (no normal-click 403).

**Output**: [research.md](research.md)

## Phase 1 — Design (data model + contracts)

### Design outline

- Tenant status UX
  - Archived tenants remain viewable for entitled members.
  - Actions become status-aware (e.g., Deactivate disabled when already archived).
- Diagnostics
  - Tenant-scoped, DB-only page that reports:
    - Missing owner (0 owners)
    - Identifier misuse risk (guardrails against using external GUID where an internal FK is expected)
  - Repair actions are capability-gated and audit-logged.

### Artifacts

- [data-model.md](data-model.md)
- [contracts/](contracts/)
- [quickstart.md](quickstart.md)

## Phase 2 — Planning (for tasks.md)

Dependency-ordered implementation outline (will be expanded in `tasks.md`):

1. Fix tenant view header actions (Edit/Deactivate) to use UI enforcement + tooltips.
2. Add Restore icon consistency on tenant list row menu.
3. Ensure archived tenant is resolvable in tenant-scoped routes for members (avoid false 404).
4. Add tenant status banner on view page.
5. Add diagnostics page + safe repair actions for “missing owner”.
6. Add regression tests for:
   - Readonly UX (disabled + tooltip)
   - Readonly cannot deactivate
   - Archived tenant view access (member OK, non-member 404)
   - GUID vs bigint guard test
7. Keep the no-ad-hoc authorization guard green.
