# Implementation Plan: Action Surface Contract v1.1 **Branch**: `169-action-surface-v11` | **Date**: 2026-03-30 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/169-action-surface-v11/spec.md` **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/169-action-surface-v11/spec.md` **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts. ## Summary Strengthen the existing action-surface contract so rendered behavior, not declaration presence alone, becomes the governing truth. The implementation adds one first-class constitution-aligned `surface_type` enum to `ActionSurfaceDeclaration` while keeping `ActionSurfaceProfile` as the slot-requirement model, extends primary discovery to the enrolled system-panel table pages under `app/Filament/System/Pages`, codifies inspect-model and `More`-menu ordering rules in the validator and reference docs, and protects the contract with both fast validator tests and representative Livewire guard tests anchored on clickable-row, explicit-inspect, and system-panel reference surfaces. ## Technical Context **Language/Version**: PHP 8.4.15 **Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, existing `ActionSurfaceDeclaration`, `ActionSurfaceValidator`, `ActionSurfaceDiscovery`, `ActionSurfaceExemptions`, and Filament Tables / Actions APIs **Storage**: PostgreSQL unchanged; no new persistence, cache store, queue payload, or durable artifact **Testing**: Pest 4 feature tests and Livewire component tests, including validator stubs and rendered table guard coverage, executed through Laravel Sail **Target Platform**: Laravel monolith web application in Sail locally and containerized Linux deployment in staging/production **Project Type**: web application **Performance Goals**: Keep repository-wide action-surface validation deterministic and CI-friendly, add no new runtime queries or cross-request state to operator surfaces, and keep behavior-aware render checks limited to representative guard surfaces rather than every declaration-backed class **Constraints**: Derived-only governance slice, no new business routes or assets, no new capability or policy family, no new baseline exemptions for already enrolled surfaces, chooser/dashboard/widget/onboarding exemptions remain explicit, and all work must stay within Filament v5 / Livewire v4 conventions **Scale/Scope**: The enrolled reference surfaces named by Spec 169 across monitoring pages, reporting/evidence registers, representative CRUD and read-only registry resources, and the six enrolled system-panel list pages; representative render coverage for clickable-row, explicit-inspect, ordered overflow behavior, and out-of-scope preservation ## Constitution Check *GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.* | Principle | Pre-Research | Post-Design | Notes | |-----------|--------------|-------------|-------| | Read/write separation | PASS | PASS | This is a UI governance and guard-coverage slice only. No domain write path, operation start surface, or Graph call changes are introduced. | | Workspace + tenant isolation / RBAC-UX | PASS | PASS | Discovery coverage broadens validation only; it does not alter route visibility, panel access, 404 vs 403 semantics, or capability enforcement on tenant or system surfaces. | | Proportionality / no premature abstraction | PASS WITH JUSTIFIED ENUM | PASS WITH JUSTIFIED ENUM | One new first-class enum and one declaration field are justified because `ActionSurfaceProfile` cannot distinguish constitution-governed inspect models across CRUD, queue, audit, and registry surfaces. No second registry or UI meta-framework is introduced. | | Persisted truth / behavioral state | PASS | PASS | No new table, artifact, cache, or persisted status family is introduced. The new `surface_type` enum is declaration-time behavior metadata only. | | UI constitution / one inspect model / placeholder ban | PASS | PASS | This feature directly enforces `UI-SURF-001`, `UI-HARD-001`, `UI-EX-001`, and `UI-REVIEW-001` by failing redundant inspect patterns, empty groups, and mismatched surface behavior. | | Filament-native UI | PASS | PASS | The implementation continues to govern native Filament `recordUrl()`, row actions, `ActionGroup`, and `BulkActionGroup` rather than inventing replacement UI. | | Filament v5 / Livewire v4 compliance | PASS | PASS | The plan keeps all work inside the current Filament v5 + Livewire v4 stack and adds only Livewire-compatible component tests. | | Provider registration location | PASS | PASS | No panel provider change is required; Laravel 11+ provider registration remains in `bootstrap/providers.php`. | | Global search hard rule | PASS | PASS | No globally searchable resource behavior changes are introduced in this slice. | | Destructive action safety | PASS | PASS | Existing destructive actions remain executed through Filament actions with confirmation and authorization; this feature only validates placement and ordering. | | Asset strategy | PASS | PASS | No new panel or shared assets are added, so there is no `filament:assets` deployment impact. | | Testing truth (TEST-TRUTH-001) | PASS | PASS | The plan uses fast validator stubs for semantic rules and representative rendered tests for business-visible behavior instead of expanding broad presentation-only test matrices. | ## Project Structure ## Phase 0 Research Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/169-action-surface-v11/research.md`. Key decisions: - Add a first-class `ActionSurfaceType` enum to `ActionSurfaceDeclaration` while keeping `ActionSurfaceProfile` as the slot-requirement model. - Keep inspect-model compatibility rules close to the existing contract stack by implementing them in the validator and enum helpers rather than a second registry or framework layer. - Extend discovery narrowly to declared system-panel table pages under `app/Filament/System/Pages`, using table + declaration opt-in so auth, dashboards, widgets, runbooks, and other deferred surfaces remain out of scope. - Keep current panel-scope metadata unchanged for this slice because no active consumer relies on a system-panel scope enum, and the feature’s requirement is validation coverage rather than a new panel taxonomy. - Split guard coverage between fast validator tests and representative Livewire render tests so declaration-level drift and rendered-behavior drift are both blocked. ## Phase 1 Design Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/169-action-surface-v11/`: - `research.md`: phase-0 decisions and rejected alternatives - `data-model.md`: declaration v1.1 contract, surface-type enum, decision rules, and discovery-scope model - `contracts/action-surface-governance.logical.openapi.yaml`: internal logical contract for declaration, discovery, and validation behavior - `quickstart.md`: focused implementation and verification workflow Design decisions: - `ActionSurfaceType` is explicit declaration data, not derived metadata. - `ActionSurfaceProfile` remains in place to drive required-slot validation. - `PrimaryLinkColumn` remains an exception path and requires an explicit reason instead of a new exception object model. - System-panel discovery uses filesystem scope plus declaration opt-in instead of a hardcoded class allowlist. - Behavior-aware ordering checks stay in representative tests rather than trying to introspect every Filament action tree generically. ## Project Structure ### Documentation (this feature) ```text specs/169-action-surface-v11/ ├── spec.md ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── action-surface-governance.logical.openapi.yaml ├── checklists/ │ └── requirements.md └── tasks.md ``` ### Source Code (repository root) ```text app/ ├── Filament/ │ ├── Pages/ │ │ └── Monitoring/ │ │ ├── AuditLog.php │ │ ├── FindingExceptionsQueue.php │ │ └── Operations.php │ ├── Resources/ │ │ ├── BackupScheduleResource.php │ │ ├── BaselineProfileResource.php │ │ ├── OperationRunResource.php │ │ ├── PolicyResource.php │ │ ├── TenantResource.php │ │ └── Workspaces/ │ │ └── WorkspaceResource.php │ └── System/ │ └── Pages/ │ ├── Directory/ │ │ ├── Tenants.php │ │ └── Workspaces.php │ ├── Ops/ │ │ ├── Failures.php │ │ ├── Runs.php │ │ ├── Runbooks.php │ │ └── Stuck.php │ ├── RepairWorkspaceOwners.php │ └── Security/ │ └── AccessLogs.php ├── Support/ │ └── Ui/ │ └── ActionSurface/ │ ├── ActionSurfaceDeclaration.php │ ├── ActionSurfaceDiscovery.php │ ├── ActionSurfaceExemptions.php │ ├── ActionSurfaceProfileDefinition.php │ ├── ActionSurfaceValidator.php │ └── Enums/ │ ├── ActionSurfaceInspectAffordance.php │ ├── ActionSurfacePanelScope.php │ ├── ActionSurfaceProfile.php │ └── ActionSurfaceType.php docs/ ├── product/ │ └── standards/ │ └── filament-actions-ux.md └── ui/ └── action-surface-contract.md tests/ ├── Feature/ │ ├── Guards/ │ │ ├── ActionSurfaceContractTest.php │ │ └── ActionSurfaceValidatorTest.php │ └── Rbac/ │ └── TenantActionSurfaceConsistencyTest.php ``` **Structure Decision**: Keep the existing Laravel monolith structure. Extend the current action-surface support stack under `app/Support/Ui/ActionSurface`, update the enrolled reference surfaces named by the spec, sync the two developer-facing standards docs, and protect the feature through the current Pest guard suite instead of creating a new module or documentation tree. ## Implementation Strategy ### Phase A — Introduce the explicit surface-type contract **Goal**: Add one constitution-aligned enum and declaration field without creating a second action-governance framework. | Step | File | Change | |------|------|--------| | A.1 | `app/Support/Ui/ActionSurface/Enums/ActionSurfaceType.php` | Add the new first-class enum with the five enforced surface families: CRUD / List-first Resource, Read-only Registry / Report, Queue / Review, History / Audit, and Config-lite. | | A.2 | `app/Support/Ui/ActionSurface/ActionSurfaceDeclaration.php` | Extend the declaration with a first-class `surfaceType` field and fluent/factory support, keeping `profile` as the slot-requirement model and preserving existing defaults, slots, exemptions, and metadata. | | A.3 | `app/Support/Ui/ActionSurface/Enums/ActionSurfaceInspectAffordance.php` and `app/Support/Ui/ActionSurface/ActionSurfaceProfileDefinition.php` | Keep current affordance/profile models intact while adding the minimum helper semantics needed to evaluate allowed affordance combinations. | ### Phase B — Roll out surface types across the enrolled reference surfaces **Goal**: Move the reference surfaces enrolled by the spec to explicit declaration-level surface typing before validator enforcement turns strict. | Step | File | Change | |------|------|--------| | B.1 | `app/Filament/Pages/Monitoring/Operations.php`, `AuditLog.php`, `FindingExceptionsQueue.php`, `EvidenceOverview.php`, and `app/Filament/Pages/Reviews/ReviewRegister.php` | Establish the primary clickable-row, explicit-inspect, and reporting-registry reference pages with explicit surface types that match the constitution. | | B.2 | Representative CRUD and read-only registry resources such as `TenantResource`, `PolicyResource`, `BackupScheduleResource`, `BaselineProfileResource`, `WorkspaceResource`, `AlertDeliveryResource`, `BaselineSnapshotResource`, `EvidenceSnapshotResource`, `ReviewPackResource`, and `TenantReviewResource` | Align the enrolled resource reference families with the new explicit surface-type field so inspect-model and ordering checks have stable anchors. | | B.3 | `app/Filament/Resources/OperationRunResource.php` and the enrolled system list pages under `app/Filament/System/Pages/**` | Keep the run-log and cross-panel registry references in the same rollout slice so the validator can fail on missing types without leaving the reference pack in a mixed state. | ### Phase C — Bring system-panel table pages into the primary discovery pass **Goal**: Eliminate the split between the main validator and the targeted system-page assertions. | Step | File | Change | |------|------|--------| | C.1 | `app/Support/Ui/ActionSurface/ActionSurfaceDiscovery.php` | Add narrow discovery for `app/Filament/System/Pages/**` that includes only table-backed pages with declarations, so the six enrolled system list pages enter the primary validator. | | C.2 | `app/Support/Ui/ActionSurface/ActionSurfaceExemptions.php` | Preserve explicit exemptions for deferred families and verify no stale baseline exemptions remain for the six enrolled system pages. | | C.3 | `app/Filament/System/Pages/Ops/Runs.php`, `Failures.php`, `Stuck.php`, `Directory/Tenants.php`, `Directory/Workspaces.php`, and `Security/AccessLogs.php` | Confirm these pages remain declaration-backed reference surfaces under the main validator without sweeping in auth, dashboard, runbook, or break-glass pages. | ### Phase D — Enforce inspect-model and ordering behavior in the validator and docs **Goal**: Make the contract fail for behaviorally wrong declarations, not just missing slots. | Step | File | Change | |------|------|--------| | D.1 | `app/Support/Ui/ActionSurface/ActionSurfaceValidator.php` | Add behavior-aware rules: require explicit `surfaceType`, validate allowed inspect affordances by surface type, reject redundant lone `View` patterns on clickable-row surfaces, and require an explicit reason when `PrimaryLinkColumn` is used as an exception path. | | D.2 | `docs/ui/action-surface-contract.md` and `docs/product/standards/filament-actions-ux.md` | Update the developer-facing reference docs together so the constitution-aligned inspect and ordering rules match the validator and the test suite. | | D.3 | `tests/Feature/Guards/ActionSurfaceValidatorTest.php` | Extend the stub-based validator suite to cover missing `surfaceType`, invalid surface-type/affordance pairings, and required exception reasons. | ### Phase E — Add representative rendered guard coverage **Goal**: Prove the declaration cannot claim conformance while the actual Filament table behavior drifts. | Step | File | Change | |------|------|--------| | E.1 | `tests/Feature/Guards/ActionSurfaceContractTest.php` | Add or extend rendered table tests for clickable-row references, explicit-inspect history/audit references, explicit-inspect queue/review references, reporting-registry coverage, system-panel discovery coverage, and helper-first / workflow-next / destructive-last `More` menu ordering. | | E.2 | `tests/Feature/Rbac/TenantActionSurfaceConsistencyTest.php` | Keep one RBAC-aware tenant resource reference proving row-click and `More`-menu semantics remain aligned with capability gating and overflow placement. | | E.3 | `vendor/bin/sail bin pint --dirty --format agent` plus focused Pest runs | Format touched files and run the narrow verification pack for validator, contract guard, and tenant action-surface consistency coverage. | ## Key Design Decisions ### D-001 — `surfaceType` is explicit and separate from `profile` `ActionSurfaceProfile` remains the technical slot-requirement model. The new `surfaceType` is the constitution-governed behavioral classification. Keeping them separate avoids forcing slot rules and operator interaction semantics into the same enum. ### D-002 — Inspect rules stay close to the existing contract stack The feature should not introduce a second registry or action-governance subsystem. The narrowest implementation is one new enum plus validator logic and small helper methods where needed. ### D-003 — System discovery is opt-in, not broad System-panel coverage is achieved by discovering declared, table-backed pages under `app/Filament/System/Pages`, not by sweeping every system page into the validator. This preserves explicit exemptions for auth, dashboards, choosers, and other deferred surfaces. ### D-004 — Representative render tests enforce business-visible truth The validator can prove declaration semantics, but it cannot prove Filament table behavior alone. Representative Livewire tests must anchor the real clickable-row, explicit-inspect, and helper-first / workflow-next / destructive-last rules. ### D-005 — `PrimaryLinkColumn` remains a justified exception path The spec needs stronger control over linked-column inspect affordances, but not a new exception object model. A required explicit reason in the declaration is sufficient for this slice. ## Risk Assessment | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | Declaration rollout is incomplete when `surfaceType` becomes required | High | Medium | Introduce the contract and update the entire enrolled reference pack in the same implementation slice before making missing `surfaceType` a validator failure. | | Discovery becomes too broad and sweeps in auth, dashboard, or deferred system surfaces | High | Medium | Limit the new discovery path to declared, table-backed system pages and preserve explicit baseline exemptions for deferred families. | | `surfaceType` and `profile` drift semantically | Medium | Medium | Document their separate responsibilities in code, docs, and tests, and anchor each critical surface family with representative declarations. | | More-menu ordering tests become brittle because of exact action sequences | Medium | Medium | Assert ordering invariants such as helper-first, workflow-next, destructive-last, and non-empty groups instead of pinning every menu to a full exact sequence. | | `PrimaryLinkColumn` remains under-specified | Medium | Low | Require explicit reason text and representative validation coverage before allowing the exception path to pass. | ## Test Strategy - Extend `tests/Feature/Guards/ActionSurfaceValidatorTest.php` with stub declarations that cover required `surfaceType`, invalid affordance combinations, and explicit exception-reason requirements. - Extend `tests/Feature/Guards/ActionSurfaceContractTest.php` with representative Livewire coverage for Monitoring Operations or `OperationRunResource`, Audit Log, Finding Exceptions Queue, a reporting or evidence register, one CRUD `More` menu that proves helper-first, workflow-next, destructive-last ordering, and the six primary system-panel list pages discovered by the validator. - Keep one tenant-plane RBAC-aware reference test in `tests/Feature/Rbac/TenantActionSurfaceConsistencyTest.php` so action-surface behavior remains compatible with disabled-vs-forbidden gating rules. - Run the narrow Sail verification pack from `quickstart.md` before considering the slice complete. - Ask whether the user wants the full suite after focused tests pass. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | New first-class `surfaceType` enum and declaration field | The existing `ActionSurfaceProfile` cannot distinguish constitution-level inspect behavior between queue, audit, registry, CRUD, and config-lite surfaces, so behavior-aware validation needs explicit type data. | Reusing `profile` or hiding the distinction in metadata would keep the rule implicit, make validator failures less actionable, and preserve the declaration-only gap this spec is fixing. | ## Proportionality Review - **Current operator problem**: The repository can already prove that declarations exist, but it cannot yet prove that the declared inspect model and overflow behavior actually match the constitution. That leaves real clickable-row and explicit-inspect surfaces vulnerable to silent behavioral drift. - **Existing structure is insufficient because**: `ActionSurfaceProfile` only describes slot requirements. It is too coarse to distinguish queue and audit surfaces that require explicit inspect from CRUD and registry surfaces that require one-click open. Primary discovery also still excludes the enrolled system-panel list pages. - **Narrowest correct implementation**: Add one first-class `surfaceType` enum to `ActionSurfaceDeclaration`, extend discovery narrowly to the already-enrolled system table pages, and strengthen the validator plus representative rendered tests. Do not add a second registry, persistence layer, or UI framework. - **Ownership cost created**: This adds one enum, one declaration field, more explicit declaration work on the enrolled reference surfaces, stronger validator logic, and a small set of focused guard tests that reviewers must maintain as the contract evolves. - **Alternative intentionally rejected**: Documentation-only guidance and declaration-only slot validation were rejected because they cannot fail when rendered behavior drifts. Replacing `ActionSurfaceProfile` entirely was rejected because slot requirements and constitution surface semantics are separate concerns. - **Release truth**: Current-release truth. The repository already contains both correct clickable-row and correct explicit-inspect patterns, and the missing work is durable enforcement.