# Implementation Plan: Action Surface Enforcement, Enrollment, and Exception Closure **Branch**: `195-action-surface-closure` | **Date**: 2026-04-12 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/195-action-surface-closure/spec.md` **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/195-action-surface-closure/spec.md` **Note**: This plan keeps the implementation inside the existing Filament v5 / Livewire v4 page layer, the current `ActionSurfaceDiscovery` + `ActionSurfaceValidator` + `ActionSurfaceExemptions` infrastructure, and the current focused RBAC, system-ops, onboarding, chooser, and dashboard test suites. It explicitly avoids adding a new runtime action-surface framework or new persistence. ## Summary Close the residual action-surface governance gap left after Specs 192 to 194 by preserving the current primary discovery boundary, adding one explicit residual-closure inventory for non-discovered and baseline-exempt special surfaces, assigning every remaining residual page exactly one closure decision, tightening stale exemptions, and extending guard coverage so no new action-bearing residual surface can enter the repo without an explicit decision. The plan favors explicit inventory plus focused tests over forcing every system, wizard, selector, or dashboard surface into the generic `actionSurfaceDeclaration()` contract. ## Technical Context **Language/Version**: PHP 8.4.15 **Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `ActionSurfaceDiscovery`, `ActionSurfaceValidator`, `ActionSurfaceExemptions`, `GovernanceActionCatalog`, `UiEnforcement`, `WorkspaceContext`, and existing system/onboarding/auth helpers **Storage**: PostgreSQL through existing workspace-owned, tenant-owned, and system-visible models; no schema change planned **Testing**: Pest feature tests, existing guard tests, existing Livewire page tests, and focused browser smoke only if a residual surface genuinely needs it; all run through Laravel Sail **Target Platform**: Laravel monolith web application under `apps/platform`, spanning admin `/admin`, tenant-context `/admin/t/{tenant}/...`, and system `/system` surfaces **Project Type**: web application **Performance Goals**: Keep residual-surface validation repo-local and deterministic, preserve DB-only render behavior on existing monitoring and dashboard surfaces, avoid new render-time outbound I/O, and avoid extra polling or runtime indirection **Constraints**: No new persistence, no new action-surface runtime framework, no provider or route-family changes, no authorization-plane changes, no silent exemptions, no weakening of 404/403 semantics, no change to existing destructive-action confirmation or audit behavior, and no new PHP enum unless validator-checked strings prove insufficient **Scale/Scope**: an initial seed of 12 residual target surfaces, plus the additionally audited `system_dashboard` residual and any future residual pages uncovered by audit, including 6 current system/detail or utility surfaces outside primary discovery and not baseline-exempt, plus 7 currently baseline-exempt special flows or dashboard-like surfaces with uneven dedicated coverage ## Constitution Check *GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.* | Principle | Pre-Research | Post-Design | Notes | |-----------|--------------|-------------|-------| | Inventory-first / snapshots-second | PASS | PASS | The feature governs residual UI enforcement only and does not change inventory, backup, or snapshot truth. | | Read/write separation | PASS | PASS | Residual surfaces reuse existing writes only; confirmation, audit, and focused tests remain unchanged. | | Graph contract path | N/A | N/A | No Graph contract or provider endpoint change is introduced. | | Deterministic capabilities | PASS | PASS | Existing capability registries and server-side checks stay authoritative. | | Workspace + tenant isolation | PASS | PASS | Closure decisions do not widen scope; non-member access remains `404`, member-without-capability remains `403`. | | RBAC-UX authorization semantics | PASS | PASS | Existing Gates, Policies, capability helpers, and destructive confirmations remain in force. | | Run observability / Ops-UX | PASS | PASS | Existing `OperationRun` surfaces and DB-only repairs remain governed exactly as they are today. | | Data minimization | PASS | PASS | No new persistence or mirrored truth is planned; all closure metadata stays derived in code and tests. | | Proportionality / anti-bloat | PASS | PASS | The plan adds one bounded residual inventory and validator pass, not a new framework. | | UI semantics / few layers | PASS | PASS | The solution uses explicit inventory records and tests rather than presenters or a new semantic stack. | | Filament-native UI | PASS | PASS | Existing Filament pages, actions, tables, and page tests remain the implementation path. | | Surface taxonomy / action-surface discipline | PASS | PASS | The plan closes uncatalogued residuals explicitly without redefining Specs 192 to 194. | | Filament v5 / Livewire v4 compliance | PASS | PASS | All touched surfaces remain inside the current Filament v5 + Livewire v4 stack. | | Provider registration location | PASS | PASS | No panel/provider registration change is planned; Laravel 11+ provider registration remains in `bootstrap/providers.php`. | | Global search hard rule | PASS | PASS | No globally searchable resource is added or modified. | | Destructive action safety | PASS | PASS | Existing destructive or recovery actions keep `->requiresConfirmation()` and current authorization. | | Asset strategy | PASS | PASS | No new global or on-demand assets are required; existing `cd apps/platform && php artisan filament:assets` deploy handling remains sufficient. | ## Filament-Specific Compliance Notes - **Livewire v4.0+ compliance**: The plan stays entirely on Filament v5 + Livewire v4 and introduces no legacy API mix. - **Provider registration location**: No provider changes are required; Laravel 11+ panel providers remain in `bootstrap/providers.php`. - **Global search**: No resource search behavior changes. Residual surfaces are pages, dashboards, selectors, or system utilities, not new searchable resources. - **Destructive actions**: Existing dangerous actions such as `Cancel`, `Repair owner state`, onboarding completion steps, and registration or recovery mutations remain routed through confirmed Filament actions with server-side authorization and existing audit behavior. - **Asset strategy**: No new assets are planned. Existing deployment handling of `cd apps/platform && php artisan filament:assets` remains unchanged. - **Testing plan**: Extend the current guard layer, reuse the existing focused system, auth, onboarding, dashboard, and RBAC suites as explicit coverage evidence, and add only the minimum new tests needed to close weak or currently uncatalogued residuals. ## Phase 0 Research Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/195-action-surface-closure/research.md`. Key decisions: - Preserve the current primary discovery boundary instead of auto-discovering every system or workflow page. - Add one parallel `spec195ResidualSurfaceInventory()` to `ActionSurfaceExemptions` rather than rewriting `baseline()` or stretching `ActionSurfaceDeclaration()` to every surface type. - Model closure decisions and reason categories as validator-checked strings in the inventory instead of adding new PHP enums or persistence. - Default residual system pages to `separately_governed` or `harmless_special_case` unless a surface already fits the existing declaration-backed list/detail contract naturally. - Reuse existing dedicated tests as coverage evidence for onboarding, selectors, runbooks, system triage, and dashboard shells; add focused tests only for weakly covered pages such as `ManagedTenantsLanding` and system directory detail pages. - Treat `BreakGlassRecovery` as a stale exemption candidate and retire it if it remains inaccessible and actionless. ## Phase 1 Design Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/195-action-surface-closure/`: - `research.md`: decisions and rejected alternatives for residual closure, discovery boundaries, and exemption cleanup - `data-model.md`: derived closure inventory, evidence, and regression-expectation models - `contracts/action-surface-closure.logical.openapi.yaml`: internal logical contract for residual-surface closure decisions and guard expectations - `quickstart.md`: implementation and verification sequence for the feature Design highlights: - Keep the generic `ActionSurfaceDeclaration()` system limited to the surfaces it already fits well: declaration-backed resources, pages, relation managers, and explicitly enrolled system table pages. - Represent every residual surface through one explicit closure inventory entry recording class, plane, discovery status, closure decision, reason category, explicit reason, structured evidence, and follow-up testing needs. - Keep `ActionSurfaceDiscovery` explicit about what it does and does not discover; close the gap through supplemental validator inventory rather than broad auto-discovery. - Use existing page-local behavior and focused tests for system triage, runbooks, onboarding, chooser flows, and dashboard shells instead of creating shared runtime resolvers. - Remove or reclassify stale baseline exemptions rather than renaming historical drift. ## Phase 1 — Agent Context Update Planned command: - `.specify/scripts/bash/update-agent-context.sh copilot` This feature does not introduce a new technology stack, but the required agent-context refresh still runs after the technical context and design artifacts are complete. ## Project Structure ### Documentation (this feature) ```text specs/195-action-surface-closure/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── spec.md ├── contracts/ │ └── action-surface-closure.logical.openapi.yaml └── checklists/ └── requirements.md ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ │ ├── Filament/ │ │ ├── Pages/ │ │ │ ├── BreakGlassRecovery.php # AUDIT / likely retire as stale exemption │ │ │ ├── ChooseWorkspace.php # REUSE / classify as harmless special case │ │ │ ├── ChooseTenant.php # REUSE / classify as harmless special case │ │ │ ├── TenantDashboard.php # REUSE / classify page shell explicitly │ │ │ ├── Tenancy/ │ │ │ │ └── RegisterTenant.php # REUSE / separately governed │ │ │ └── Workspaces/ │ │ │ ├── ManagedTenantOnboardingWizard.php # REUSE / separately governed │ │ │ └── ManagedTenantsLanding.php # AUDIT / likely add focused coverage │ │ └── System/ │ │ └── Pages/ │ │ ├── RepairWorkspaceOwners.php # AUDIT / separately governed closure │ │ ├── Directory/ │ │ │ ├── ViewTenant.php # AUDIT / likely harmless or separate governance │ │ │ └── ViewWorkspace.php # AUDIT / likely harmless or separate governance │ │ └── Ops/ │ │ ├── Runbooks.php # REUSE / separately governed closure │ │ └── ViewRun.php # REUSE / separately governed closure │ └── Support/ │ └── Ui/ │ └── ActionSurface/ │ ├── ActionSurfaceDiscovery.php # REUSE / boundary remains explicit │ ├── ActionSurfaceExemptions.php # MODIFY │ └── ActionSurfaceValidator.php # MODIFY └── tests/ └── Feature/ ├── Guards/ │ ├── ActionSurfaceContractTest.php # MODIFY │ ├── ActionSurfaceValidatorTest.php # MODIFY │ ├── Spec194GovernanceActionSemanticsGuardTest.php # REUSE │ └── Spec195ResidualActionSurfaceClosureGuardTest.php # NEW ├── Auth/ │ ├── BreakGlassWorkspaceOwnerRecoveryTest.php # REUSE / possible extend │ └── TenantChooserSelectionTest.php # REUSE ├── Workspaces/ │ ├── ChooseWorkspacePageTest.php # REUSE │ ├── ManagedTenantsWorkspaceRoutingTest.php # REUSE / possible extend │ └── Spec195ManagedTenantsLandingTest.php # NEW ├── Rbac/ │ ├── RegisterTenantAuthorizationTest.php # REUSE │ ├── OnboardingWizardUiEnforcementTest.php # REUSE │ └── TenantDashboardArrivalContextVisibilityTest.php # REUSE ├── System/ │ ├── Spec113/AuthorizationSemanticsTest.php # REUSE / runbooks auth semantics │ ├── Spec114/OpsTriageActionsTest.php # REUSE / system triage semantics │ ├── OpsRunbooks/FindingsLifecycleBackfillStartTest.php # REUSE │ └── Spec195/SystemDirectoryResidualSurfaceTest.php # NEW ├── Filament/ │ └── TenantDashboardDbOnlyTest.php # REUSE └── Onboarding/ └── OnboardingDraftAccessTest.php # REUSE / explicit wizard governance evidence ``` **Structure Decision**: Keep all work inside the existing Laravel/Filament monolith under `apps/platform`. Modify only the existing action-surface support layer plus targeted tests. Do not create a new runtime registry, new persistence, or new shared page abstraction. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | Cross-surface residual closure inventory and reason-category vocabulary (BLOAT-001 trigger) | The feature must explicitly distinguish enrolled, intentionally exempt, separately governed, retired, and harmless residual surfaces across code paths that the primary discovery system does not cover. | Leaving only free-form baseline reason strings and scattered tests would not let CI distinguish stale exemptions, uncatalogued system pages, or legitimate separately governed workflows. | ## Proportionality Review - **Current operator problem**: Reviewers cannot tell whether residual system, utility, workflow, selector, landing, and dashboard surfaces are intentionally outside the generic contract or simply missed by discovery. - **Existing structure is insufficient because**: `ActionSurfaceDiscovery` plus `baseline()` exemptions cover declaration-backed surfaces and a small discovered-exempt set, but they do not explain non-discovered system/detail pages or distinguish harmless, separate, retired, and true exemption states. - **Narrowest correct implementation**: Add one bounded residual inventory plus validator checks, keep the current discovery boundary explicit, reuse existing dedicated test suites as evidence, and add only the minimum new tests for weakly covered residuals. - **Ownership cost created**: One more derived inventory in the action-surface support layer, one new guard test, a few focused closure tests, and ongoing review discipline for future residual pages. - **Alternative intentionally rejected**: Auto-discovering every system and workflow page or forcing every residual surface into `actionSurfaceDeclaration()` was rejected because the current contract is list/detail-oriented and many residual surfaces are legitimate special workflows rather than malformed generic surfaces. - **Release truth**: current-release governance closure and regression prevention ## Implementation Strategy ### Phase A — Codify the residual closure inventory and explicit discovery boundary Goal: make every residual surface reviewable in CI without widening the runtime framework. Changes: - Add `spec195ResidualSurfaceInventory()` to `ActionSurfaceExemptions` with one entry per seeded or newly audited residual target surface. - Extend `ActionSurfaceValidator` with Spec 195 validation for allowed closure decisions, allowed reason categories, duplicate keys, required structured evidence, and explicit primary-discovery status. - Keep `ActionSurfaceDiscovery` behavior unchanged, but make the validator assert when a residual surface is outside primary discovery and lacks a supplemental closure decision. - Add `Spec195ResidualActionSurfaceClosureGuardTest.php` and extend existing guard tests so the residual inventory becomes mandatory. - Keep `baseline()` for backward-compatible discovered-page exemptions, but align its live entries with the new Spec 195 inventory. Tests: - Extend `ActionSurfaceContractTest.php` and `ActionSurfaceValidatorTest.php` with Spec 195 expectations. - Add `Spec195ResidualActionSurfaceClosureGuardTest.php`. ### Phase B — Close uncatalogued system and utility surfaces Goal: explicitly classify the system pages that are currently neither discovered nor baseline-exempt. Changes: - Classify `Dashboard` as `separately_governed`, backed by the existing control-tower and break-glass test suites rather than forcing it into the generic declaration contract. - Classify `ViewRun` as `separately_governed`, backed by Spec 114 triage tests and Spec 194 governance-action guards. - Classify `Runbooks` as `separately_governed`, backed by Spec 113 auth semantics, runbook start/preflight tests, trusted-state guards, and Ops-UX coverage. - Classify `RepairWorkspaceOwners` as `separately_governed`, backed by break-glass recovery and table-standard tests. - Classify `System Directory ViewTenant` and `System Directory ViewWorkspace` as `harmless_special_case` if they remain read-mostly contextual drilldowns; otherwise promote them to `separately_governed` with focused tests. - Do not force these pages into `actionSurfaceDeclaration()` unless implementation audit finds a natural, already-fitting declaration shape. Tests: - Reuse `Spec114/OpsTriageActionsTest.php`, `Spec113/AuthorizationSemanticsTest.php`, `FindingsLifecycleBackfillStartTest.php`, and `BreakGlassWorkspaceOwnerRecoveryTest.php`. - Add `Spec195/SystemDirectoryResidualSurfaceTest.php` for the current weakest system-detail coverage. ### Phase C — Reclassify special workflows, selectors, landings, and dashboard shells Goal: turn existing baseline exemptions into explicit closure decisions rather than historical placeholders. Changes: - Reclassify `BreakGlassRecovery` as `retired_no_longer_relevant` if it remains inaccessible and actionless; otherwise keep it as `intentional_exemption` with a security-flow reason category. - Classify `ChooseWorkspace` and `ChooseTenant` as `harmless_special_case` routing surfaces. - Classify `RegisterTenant` as `separately_governed` because its mutation path, authorization, and bootstrap audit behavior already have focused coverage. - Keep `ManagedTenantOnboardingWizard` as `separately_governed` and explicitly bind it to Spec 172 plus onboarding/RBAC/audit suites. - Classify `ManagedTenantsLanding` explicitly and add focused coverage because it currently has the weakest dedicated test evidence among the special surfaces. - Classify `TenantDashboard` as a `harmless_special_case` page shell or a light `separately_governed` shell, while leaving widget-level governance to the existing widget and arrival-context tests. Tests: - Reuse chooser, registration, onboarding, and dashboard tests already in `Auth`, `Workspaces`, `Rbac`, `Onboarding`, and `Filament` suites. - Add `Spec195ManagedTenantsLandingTest.php` if current routing coverage is insufficiently explicit for the closure inventory. ### Phase D — Final guard hardening and verification flow Goal: ensure new residual surfaces cannot appear silently after Spec 195 lands. Changes: - Fail CI when a new residual surface in the relevant namespaces is action-bearing but has no Spec 195 closure inventory entry. - Fail CI when a discovered baseline exemption lacks a reason category or explicit evidence reference in the Spec 195 inventory. - Fail CI when a retired or no-longer-relevant surface still keeps a live baseline exemption. - Run focused verification through Sail and format touched files with Pint. Tests: - New residual closure guard plus the focused reused suites above. - No full test suite is required to complete the planning phase, but the implementation quickstart defines the minimum targeted verification pack. ## Risk Assessment | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | Residual closure turns into a second UI framework | Medium | Low | Keep the solution to one derived inventory plus validator checks and focused tests. | | Old exemptions survive with new labels only | High | Medium | Require explicit reason categories, explicit reasons, structured evidence, and stale-entry cleanup in the guard. | | Special workflows are over-normalized into the wrong contract | Medium | Medium | Default special workflows to `separately_governed` or `harmless_special_case` unless the existing declaration model already fits. | | System detail pages remain invisible to reviewers because discovery still skips them | High | Medium | Add explicit residual inventory entries and validator assertions for all out-of-discovery targets. | | ManagedTenantsLanding remains weakly covered and ambiguous | Medium | Medium | Add a focused Spec 195 landing test and explicit classification. | ## Test Strategy - Extend `ActionSurfaceContractTest.php` and `ActionSurfaceValidatorTest.php` so Spec 195 becomes an explicit CI-enforced rule instead of an informal review note. - Add `Spec195ResidualActionSurfaceClosureGuardTest.php` to validate closure completeness, reason-category presence, discovery-state truth, and stale-exemption cleanup. - Reuse existing system triage, runbook, break-glass, chooser, registration, onboarding, and dashboard tests as named coverage evidence for separately governed or harmless surfaces. - Add only the minimum new targeted tests needed for current coverage gaps, expected to be `SystemDirectoryResidualSurfaceTest.php` and `Spec195ManagedTenantsLandingTest.php`. - Keep all verification through Sail and run Pint after focused tests. ## Constitution Check (Post-Design) Re-check result: PASS. - Livewire v4.0+ compliance remains intact because all touched surfaces stay inside the existing Filament v5 + Livewire v4 stack. - Provider registration remains unchanged in `bootstrap/providers.php`. - Global search behavior is unchanged because no searchable resource is added or modified. - Destructive and recovery actions keep `->requiresConfirmation()` plus current authorization and audit behavior. - No new assets are introduced; existing `filament:assets` deployment behavior remains sufficient.