TenantAtlas/specs/148-central-tenant-operability-policy/plan.md
ahmido 417df4f9aa feat: central tenant operability policy (#177)
## Summary
- centralize tenant operability into a lane-aware, actor-aware policy boundary
- align selector eligibility, administrative discoverability, remembered context, tenant-bound routes, and canonical run viewers
- add focused Pest coverage plus Spec 148 artifacts and final polish task completion

## Validation
- `vendor/bin/sail artisan test --compact tests/Unit/Tenants/TenantOperabilityServiceTest.php tests/Unit/Tenants/TenantOperabilityOutcomeTest.php tests/Feature/Workspaces/ChooseTenantPageTest.php tests/Feature/Workspaces/SelectTenantControllerTest.php tests/Feature/TenantRBAC/ArchivedTenantRouteAccessTest.php tests/Feature/TenantRBAC/TenantRouteDenyAsNotFoundTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/OpsUx/OperateHubShellTest.php tests/Feature/Rbac/TenantLifecycleActionVisibilityTest.php tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php tests/Feature/Rbac/TenantResourceAuthorizationTest.php tests/Feature/Filament/ManagedTenantsLandingLifecycleTest.php tests/Feature/Filament/TenantGlobalSearchLifecycleScopeTest.php tests/Feature/Onboarding/OnboardingDraftLifecycleTest.php tests/Feature/Onboarding/OnboardingDraftAuthorizationTest.php`
- `vendor/bin/sail bin pint --dirty --format agent`
- manual browser smoke checks on `/admin/choose-tenant`, `/admin/tenants`, `/admin/onboarding`, `/admin/onboarding/{draft}`, and `/admin/operations/{run}`

## Filament / platform notes
- Livewire v4 compliance preserved
- panel provider registration unchanged in `bootstrap/providers.php`
- Tenant resource global search remains backed by existing view/edit pages and is now separated from active-only selector eligibility
- destructive actions remain action closures with confirmation and authorization enforcement
- no asset pipeline changes and no new `filament:assets` deployment requirement

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #177
2026-03-17 11:48:55 +00:00

203 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Implementation Plan: Central Tenant Operability Policy
**Branch**: `148-central-tenant-operability-policy` | **Date**: 2026-03-17 | **Spec**: [specs/148-central-tenant-operability-policy/spec.md](./spec.md)
**Input**: Feature specification from `/specs/148-central-tenant-operability-policy/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
## Summary
Consolidate existing tenant lifecycle and selector semantics into one actor-aware, lane-aware tenant operability authority that returns reusable structured decisions instead of scattered booleans. The implementation will evolve the existing `TenantOperabilityService` and related tenant-support types into a central policy boundary consumed by selector membership, remembered-context revalidation, tenant action catalogs, tenant-bound route checks, onboarding workflow surfaces, and canonical workspace record viewers.
This is a support-layer hardening feature, not a persistence or UI-redesign feature. The plan therefore focuses on extending the current support seams already present in `app/Services/Tenants`, `app/Support/Tenants`, `app/Support/OperateHub`, and `app/Support/Workspaces`, then migrating the highest-risk consumers first: choose-tenant and context persistence, `OperateHubShell`, `TenantActionPolicySurface`, `TenantResource`, onboarding workflow routes, and `TenantlessOperationRunViewer`.
## Technical Context
**Language/Version**: PHP 8.4.15
**Primary Dependencies**: Laravel 12, Filament 5, Livewire 4, Pest 4, existing support-layer helpers such as `UiEnforcement`, `CapabilityResolver`, `WorkspaceContext`, `OperateHubShell`, `TenantOperabilityService`, and `TenantActionPolicySurface`
**Storage**: PostgreSQL plus existing session-backed workspace and remembered-tenant context; no schema change planned for the first implementation slice
**Testing**: Pest 4 feature, unit, and Filament or Livewire-focused tests run through Laravel Sail
**Target Platform**: Laravel Sail web application serving the Filament admin panel and workspace-canonical `/admin` routes
**Project Type**: Laravel monolith web application
**Performance Goals**: Keep operability evaluation synchronous, deterministic, and DB or session-backed only; add no render-time external calls; avoid material query-count regressions on the chooser, tenant detail, and canonical operations viewer
**Constraints**: Preserve Spec 143 lifecycle semantics, Spec 144 canonical-viewer authority, Spec 145 action taxonomy, Spec 146 badge centralization, and Spec 147 selector semantics; keep Livewire v4 and Filament v5 compliance; leave panel-provider registration unchanged in `bootstrap/providers.php`; keep `TenantResource` globally searchable only through its existing View or Edit pages; no new asset pipeline or `filament:assets` change; destructive actions remain `->action(...)->requiresConfirmation()`; 404 versus 403 semantics must stay explicit and server-side
**Scale/Scope**: One central support-layer policy boundary plus adoption across the workspace shell, choose-tenant flow, remembered-context handling, tenant management resource, onboarding workflow surfaces, canonical operation viewer, and focused regression suites under `tests/Feature` and `tests/Unit`
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
**Pre-Phase 0 Gate: PASS**
- Inventory-first: PASS. This feature does not change inventory, snapshot, or backup ownership and only hardens policy evaluation over existing records.
- Read/write separation: PASS. No new remote writes, queued workflows, or lifecycle mutation mechanics are introduced. Existing archive and restore actions remain confirmed and audited where already implemented.
- Graph contract path: PASS. No Microsoft Graph calls are added or changed.
- Deterministic capabilities: PASS. Capability checks remain routed through the existing canonical capability registry and resolver services.
- RBAC-UX planes: PASS. This feature remains in the admin `/admin` plane. Cross-plane behavior is unchanged. Non-members stay 404, in-scope capability denial stays 403.
- Workspace isolation: PASS. Workspace remains the primary operating boundary and remembered tenant context remains workspace-scoped convenience state only.
- Tenant isolation: PASS. Tenant-linked routes and canonical viewers must still enforce workspace membership plus tenant entitlement where applicable.
- Destructive confirmation: PASS. No new destructive actions are added. Existing `Archive` and `Restore` remain `->action(...)->requiresConfirmation()` and capability-gated.
- Global search safety: PASS WITH REQUIRED ADOPTION. `TenantResource` is already globally searchable and has View and Edit pages, satisfying Filament v5 global search requirements. The implementation must ensure selector eligibility is not incorrectly reused as the only discoverability rule.
- Run observability and Ops-UX: PASS. No new `OperationRun` creation, lifecycle transition, notification, or summary-count behavior is introduced.
- Badge semantics (BADGE-001): PASS. Lifecycle and status presentation stay centralized through existing badge and lifecycle presentation helpers.
- UI naming (UI-NAMING-001): PASS. Operability explanations will reuse existing domain terms such as `Archive`, `Restore`, `Resume onboarding`, `selected tenant`, `route tenant`, and `run tenant`.
- Filament Action Surface Contract: PASS. In-scope Filament surfaces already exist; this feature changes their decision authority, not their required action inventory. Any touched lifecycle mutation remains confirmation-required and audit-aware.
- Filament UX-001: PASS. No layout redesign is planned. Existing pages keep their current layout contracts while consuming central policy outcomes.
- Asset strategy: PASS. No new Filament or front-end assets are planned, so no deployment change to `php artisan filament:assets` is required.
**Post-Phase 1 Re-check: PASS**
- The design extends existing support-layer abstractions instead of adding a parallel policy subsystem.
- No database migration, queue job, Graph contract change, panel change, or asset build change is required for the first implementation slice.
- Livewire v4 and Filament v5 compliance remain intact, and provider registration continues to live in `bootstrap/providers.php`.
- `TenantResource` remains globally searchable through existing View or Edit pages; the design only changes search scoping inputs, not the resources page contract.
## Project Structure
### Documentation (this feature)
```text
specs/148-central-tenant-operability-policy/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── tenant-operability-policy.openapi.yaml
└── tasks.md
```
### Source Code (repository root)
```text
app/
├── Filament/
│ ├── Pages/
│ │ ├── Operations/
│ │ ├── Workspaces/
│ │ └── ChooseTenant.php
│ └── Resources/
│ └── TenantResource.php
├── Http/
│ └── Controllers/
├── Models/
│ ├── OperationRun.php
│ ├── Tenant.php
│ ├── TenantOnboardingSession.php
│ ├── User.php
│ └── UserTenantPreference.php
├── Policies/
├── Services/
│ ├── Auth/
│ ├── Onboarding/
│ └── Tenants/
└── Support/
├── Filament/
├── Middleware/
├── OperateHub/
├── Rbac/
├── Tenants/
└── Workspaces/
routes/
└── web.php
resources/
└── views/
└── filament/
tests/
├── Feature/
│ ├── Operations/
│ ├── Onboarding/
│ ├── Rbac/
│ └── Filament/
└── Unit/
└── Tenants/
```
**Structure Decision**: Use the existing Laravel monolith and strengthen the support layer rather than creating a new policy module outside the current architecture. The implementation seam stays inside `app/Services/Tenants` and `app/Support/Tenants`, with adoption routed through `WorkspaceContext`, `OperateHubShell`, existing Filament resources and pages, and existing auth or UI enforcement helpers.
## Phase 0 Research Summary
- The repo already contains a partial centralization layer, but it is not sufficient for Spec 148. `TenantOperabilityService` currently returns lifecycle-derived booleans and only accepts a `Tenant`; it does not accept actor, lane, linked-record context, or capability context.
- `TenantActionPolicySurface` is already centralizing labels and action descriptors, which means the right direction is to feed it richer operability outcomes rather than replace it with another action-assembly system.
- `OperateHubShell` currently mixes route-authoritative tenant resolution, Filament tenant state, and remembered tenant fallback. Its entitlement logic uses `TenantPageCategory`, but outside tenant-bound routes it still relies on `canSelectAsContext`, which is too narrow for administrative discoverability and canonical linked-record handling.
- `ChooseTenant` and `SelectTenantController` duplicate selector-eligibility checks and persistence flow. These should become thin consumers of the shared operability policy and shared remembered-context validation path.
- `TenantResource::getGlobalSearchEloquentQuery()` currently reuses `applySelectableScope()`, which means selector eligibility is already influencing discoverability. Spec 148 needs a clear separation between selector lane eligibility and administrative discoverability.
- Route safety foundations are already present. `Tenant::resolveRouteBinding()` loads soft-deleted tenants for external IDs, `TenantPageCategory` already classifies canonical and onboarding routes, and `TenantlessOperationRunViewer` already treats tenant mismatch as informational. The feature should formalize and centralize the same semantics instead of re-deriving them locally.
- Laravel documentation confirms the intended 404 versus 403 split should use authorization responses such as `Response::denyAsNotFound()` for non-members or non-entitled access and normal Gate authorization for in-scope capability denial.
- Filament documentation confirms destructive actions must remain `->action(...)->requiresConfirmation()` and that URL-only actions cannot be confirmation-modal actions. Existing lifecycle actions already align with this and the implementation should preserve that contract.
## Phase 1 Design
### Implementation Approach
1. Evolve the existing operability seam into the single authority.
- Keep `TenantOperabilityService` as the implementation anchor, but expand it from tenant-only booleans into a central policy surface that accepts explicit context.
- Preserve backward compatibility temporarily by providing adapter methods for existing consumers while migrating them to structured outcomes.
2. Add explicit lane-aware and reason-aware domain objects.
- Introduce or extend support-layer types for interaction lane, structured input context, structured outcome, and reason codes.
- Keep these types in `app/Support/Tenants` so they remain close to existing lifecycle and page-category enums.
- `TenantOperabilityContext` carries normalized record, lane, route, and workflow inputs supplied by the consumer, while `TenantOperabilityService` resolves live membership, entitlement, capability, and archived-state facts through existing authoritative helpers so callers do not precompute authorization truth.
3. Separate operability from authorization while composing them intentionally.
- Operability decides whether the requested behavior is meaningful for lifecycle and lane.
- Authorization remains server-side truth for membership, entitlement, and capability.
- The structured outcome must distinguish at least wrong lane, lifecycle mismatch, non-selector eligibility, missing capability, and non-entitled access.
4. Centralize consumer adoption around the highest-risk surfaces first.
- Selector construction and remembered-context validation.
- `OperateHubShell` active-tenant resolution.
- `TenantActionPolicySurface` and `TenantResource` action visibility.
- Onboarding workflow resumability checks.
- Canonical viewer tenant-linked affordance and tenant-bound route support checks.
5. Split selector eligibility from discoverability.
- Replace the current assumption that active-selector membership and administrative visibility are the same semantic decision.
- Create distinct policy questions for standard selector eligibility, administrative discoverability, tenant-bound viewability, and canonical reference validity.
6. Preserve route-authority semantics from Specs 144 and 147.
- Tenant-bound pages remain valid from route tenant plus entitlement.
- Canonical workspace viewers remain valid from the workspace-owned record plus entitlement.
- Selected tenant context remains a filter or convenience input only.
7. Keep the first slice schema-free and asset-free.
- No new persistent table is required for structured outcomes or reason codes.
- No new panel, provider registration, or Filament asset work is planned.
### Planned Workstreams
- **Workstream A: Operability core model**
Extend `TenantOperabilityService` and `TenantOperabilityDecision` into a central lane-aware policy boundary with structured inputs and reasoned outcomes.
- **Workstream B: Selector and remembered-context adoption**
Refactor `ChooseTenant`, `SelectTenantController`, and `WorkspaceContext` to use the new operability contract for standard selector membership and remembered-context validity.
- **Workstream C: Shell and route adoption**
Update `OperateHubShell`, route-adjacent middleware, and tenant-page classification consumers so administrative, tenant-bound, and canonical-record lanes request the right operability question instead of reusing selector booleans.
- **Workstream D: Action-surface adoption**
Refactor `TenantActionPolicySurface` and `TenantResource` lifecycle actions to consume action-specific operability outcomes and reason codes while preserving `UiEnforcement`, confirmation, and audit behavior.
- **Workstream E: Discoverability and global-search hardening**
Separate administrative discoverability from standard selector eligibility for `TenantResource`, choose-tenant, and any shared tenant-picking or search surfaces in scope.
- **Workstream F: Regression hardening**
Add focused Pest coverage for lifecycle state, lane differences, selector membership, remembered-context invalidation, canonical viewer safety, tenant-bound route safety, lifecycle-action eligibility, and capability-denied versus lifecycle-denied distinctions.
### Testing Strategy
- Add unit tests for the central operability service covering each lifecycle across each lane and asserting structured reason codes for denied outcomes.
- Add unit tests for selector eligibility, remembered-context validity, tenant-bound viewability, canonical linked-record viewability, archive eligibility, restore eligibility, and resume-onboarding eligibility.
- Add focused feature tests for `ChooseTenant` and `SelectTenantController` to prove only active-lane eligible tenants can be selected.
- Add focused feature tests for `WorkspaceContext` or route-level consumers to prove stale remembered context is cleared when the tenant becomes ineligible, missing, or out of workspace.
- Add focused feature tests for `/admin/tenants/{tenant}` showing onboarding and archived tenants remain route-valid when authorized even though they are not selector-eligible.
- Add focused feature tests for `/admin/operations/{run}` showing canonical viewer legitimacy does not depend on selected tenant context.
- Add or update tests around `TenantActionPolicySurface` and `TenantResource` to prove `Archive`, `Restore`, and `Resume onboarding` remain lifecycle-safe and capability-aware.
- Add focused tests proving onboarding-completion plus readiness or verification affordances resolve from the central operability layer with capability-aware and lifecycle-aware denial reasons.
- Add focused search or resource-query tests proving tenant global search and administrative discoverability do not collapse back to selector-only semantics.
- Run the minimum focused Pest suite through Sail; no full suite is required for planning artifacts.
## Complexity Tracking
No constitution violations or exceptional complexity are planned at this stage.