TenantAtlas/specs/195-action-surface-closure/plan.md
2026-04-13 09:46:47 +02:00

23 KiB

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)

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)

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.