TenantAtlas/specs/195-action-surface-closure/research.md
ahmido 1c291fb9fe feat: close spec 195 action surface residuals (#230)
## Summary
- add the full Spec 195 residual action-surface design package under `specs/195-action-surface-closure`
- implement residual surface inventory and validator enforcement for uncatalogued system and special Filament pages
- add focused regression coverage for residual guards, system directory pages, managed-tenants landing, and readonly register-tenant / tenant-dashboard access
- fix the system workspace detail surface by loading tenant route keys and disabling lazy system database notifications to avoid the Livewire 404 on `/system/directory/workspaces/{workspace}`

## Testing
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php tests/Feature/Filament/DatabaseNotificationsPollingTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Notes
- branch: `195-action-surface-closure`
- target: `dev`
- no new assets, migrations, or provider-registration changes

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #230
2026-04-13 07:47:58 +00:00

7.7 KiB
Raw Blame History

Research: Action Surface Enforcement, Enrollment, and Exception Closure

Decision: Preserve the current primary discovery boundary and close the gap with a supplemental residual inventory

Rationale

ActionSurfaceDiscovery already has a clear shape: it discovers resources, relation managers, normal pages, and system pages only when those system pages are declaration-backed table surfaces. The real problem in Spec 195 is not that this boundary exists, but that the repo does not currently make the boundary explicit enough for residual system/detail/workflow surfaces that live outside it.

Keeping the primary discovery boundary stable avoids turning the generic contract into a catch-all framework and lets Spec 195 solve the actual problem: uncatalogued outliers.

Alternatives considered

  • Auto-discover every class under app/Filament/System/Pages: rejected because many of those pages are not list/detail contract surfaces and would force the generic contract into shapes it does not currently model well.
  • Expand ActionSurfaceDiscovery to scan every wizard, dashboard, and selector page in all namespaces: rejected because it would blur the difference between generic contract coverage and legitimate special workflows.

Decision: Add a parallel spec195ResidualSurfaceInventory() instead of refactoring baseline() or stretching ActionSurfaceDeclaration()

Rationale

The existing baseline() API is a simple string-reason allowlist for discovered pages that intentionally lack declarations. It is useful, but too narrow for Spec 195 because Spec 195 must also classify non-discovered system/detail pages and distinguish closure outcomes like separately_governed, harmless_special_case, and retired_no_longer_relevant.

The narrowest solution is to add one parallel inventory specifically for residual closure. That keeps the current baseline exemption behavior stable while giving the validator the structured data it needs.

Alternatives considered

  • Change baseline() into a structured object registry: rejected because it would create avoidable churn in existing tests and validator behavior for a problem that only Spec 195 needs to solve.
  • Encode the entire Spec 195 closure model inside ActionSurfaceDeclaration(): rejected because many residual surfaces are not natural declaration-backed list/detail surfaces.

Decision: Represent closure decisions and reason categories as validator-checked strings, not new PHP enums or persisted data

Rationale

Spec 195 adds review and CI truth, not product-domain behavior. The closure states matter for planning and enforcement, but they do not need to become persisted entities or first-class runtime business state.

Using validator-checked string values in the inventory mirrors the existing Spec 192 and Spec 193 inventory style and avoids adding new runtime types whose only purpose would be internal categorization.

Alternatives considered

  • Add new PHP enums for closure decisions and reason categories: rejected because the validator can enforce the allowed string values without importing extra runtime structure.
  • Persist residual closure rows in the database: rejected because this is repository governance truth, not user-facing data truth.

Decision: Default residual system pages to separately_governed or harmless_special_case instead of forcing generic contract enrollment

Rationale

The system residuals called out by the spec do not currently behave like the declaration-backed table/resource surfaces that Specs 192 and 193 govern. ViewRun is a system decision detail page, Runbooks is a workflow utility hub, RepairWorkspaceOwners is a break-glass repair utility, and the directory detail pages are read-mostly context pages.

Existing focused tests already exercise many of these surfaces directly. The narrowest correct implementation is therefore explicit classification plus explicit evidence, not generic normalization for its own sake.

Alternatives considered

  • Enroll ViewRun, Runbooks, and RepairWorkspaceOwners into the current actionSurfaceDeclaration() system immediately: rejected because the current contract is list/detail-slot oriented and would fit some of these surfaces awkwardly.
  • Leave the system pages uncatalogued because dedicated tests already exist: rejected because that is exactly the gray zone Spec 195 exists to close.

Decision: Use existing focused test suites as closure evidence and add only gap tests

Rationale

The repo already has strong dedicated coverage for Runbooks, ViewRun, RepairWorkspaceOwners, registration, choosers, onboarding, and dashboard behavior. Spec 195 should leverage that fact instead of duplicating equivalent tests under a new surface framework.

The only clear weak spot from current evidence is ManagedTenantsLanding, and system directory detail pages also need more explicit closure-level assertions than they have today.

Alternatives considered

  • Add one brand-new comprehensive browser suite over every residual surface: rejected because many of these surfaces are already deeply covered through feature or Livewire tests.
  • Add only inventory validation with no page-level follow-up: rejected because the weakest residuals still need focused proof.

Decision: Treat BreakGlassRecovery as a stale-exemption candidate rather than assuming it is still an active governed surface

Rationale

The current BreakGlassRecovery page has canAccess() returning false and no header actions. That is strong evidence that it may no longer be an action-bearing surface in the way the old baseline exemption reason implies.

Spec 195 should explicitly verify whether it is still a live residual surface. If not, it should be retired from the active exemption set instead of remaining as historical noise.

Alternatives considered

  • Keep the existing exemption reason unchanged because it references dedicated security specs: rejected because the current code suggests the page may no longer be a live action surface.
  • Force the page into the residual inventory as a live intentional exemption without re-auditing the code: rejected because stale exemptions are one of the specs explicit problems.

Decision: Keep selectors and dashboard shells outside the generic contract but classify them explicitly

Rationale

ChooseWorkspace, ChooseTenant, and TenantDashboard are real operator surfaces, but they are not contract-style list/detail pages in the sense governed by the earlier specs. Selectors are routing surfaces; the dashboard page is a shell whose meaningful actions live in widgets and downstream routes.

Spec 195 should classify them explicitly so they are no longer invisible to review, while still avoiding artificial normalization.

Alternatives considered

  • Treat selectors and dashboards as non-surfaces and ignore them: rejected because they clearly influence operator workflows and currently appear in the residual exemption tail.
  • Enroll them in the generic contract anyway: rejected because the generic contract is not the right fit for routing-only or widget-shell surfaces.

Decision: No new provider, asset, route, or persistence work is needed

Rationale

All evidence points to Spec 195 being a repository-governance and test-hardening slice. The existing pages, routes, panels, and tests already provide the runtime behavior. The missing part is explicit closure inventory and regression enforcement.

Alternatives considered

  • Add a new service provider or config file just for residual-surface closure: rejected because the existing action-surface support layer already provides the correct home.
  • Add new assets or UI primitives for special surfaces: rejected because the implementation does not need new rendering infrastructure.