16 KiB
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
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
/adminplane. 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
ArchiveandRestoreremain->action(...)->requiresConfirmation()and capability-gated. - Global search safety: PASS WITH REQUIRED ADOPTION.
TenantResourceis 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
OperationRuncreation, 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, andrun 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:assetsis 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. TenantResourceremains 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)
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)
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.
TenantOperabilityServicecurrently returns lifecycle-derived booleans and only accepts aTenant; it does not accept actor, lane, linked-record context, or capability context. TenantActionPolicySurfaceis 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.OperateHubShellcurrently mixes route-authoritative tenant resolution, Filament tenant state, and remembered tenant fallback. Its entitlement logic usesTenantPageCategory, but outside tenant-bound routes it still relies oncanSelectAsContext, which is too narrow for administrative discoverability and canonical linked-record handling.ChooseTenantandSelectTenantControllerduplicate 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 reusesapplySelectableScope(), 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,TenantPageCategoryalready classifies canonical and onboarding routes, andTenantlessOperationRunVieweralready 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
-
Evolve the existing operability seam into the single authority.
- Keep
TenantOperabilityServiceas 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.
- Keep
-
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/Tenantsso they remain close to existing lifecycle and page-category enums. TenantOperabilityContextcarries normalized record, lane, route, and workflow inputs supplied by the consumer, whileTenantOperabilityServiceresolves live membership, entitlement, capability, and archived-state facts through existing authoritative helpers so callers do not precompute authorization truth.
-
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.
-
Centralize consumer adoption around the highest-risk surfaces first.
- Selector construction and remembered-context validation.
OperateHubShellactive-tenant resolution.TenantActionPolicySurfaceandTenantResourceaction visibility.- Onboarding workflow resumability checks.
- Canonical viewer tenant-linked affordance and tenant-bound route support checks.
-
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.
-
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.
-
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
TenantOperabilityServiceandTenantOperabilityDecisioninto a central lane-aware policy boundary with structured inputs and reasoned outcomes. -
Workstream B: Selector and remembered-context adoption Refactor
ChooseTenant,SelectTenantController, andWorkspaceContextto 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
TenantActionPolicySurfaceandTenantResourcelifecycle actions to consume action-specific operability outcomes and reason codes while preservingUiEnforcement, 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
ChooseTenantandSelectTenantControllerto prove only active-lane eligible tenants can be selected. - Add focused feature tests for
WorkspaceContextor 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
TenantActionPolicySurfaceandTenantResourceto proveArchive,Restore, andResume onboardingremain 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.