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

103 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.