## 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
203 lines
16 KiB
Markdown
203 lines
16 KiB
Markdown
# 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 resource’s 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.
|