## Summary - implement the Action Surface Contract v1.1 runtime changes for Spec 169 - add the new explicit ActionSurfaceType contract, validator/discovery updates, and enrolled surface declarations - update Filament action-surface documentation, focused guard tests, and spec artifacts for the completed feature ## Included - clickable-row vs explicit-inspect enforcement across monitoring, reporting, CRUD, and system reference surfaces - helper-first, workflow-next, destructive-last overflow ordering checks - system panel list discovery in the primary action-surface validator - Spec 169 artifacts: spec, plan, tasks, research, data model, quickstart, and logical contract ## Verification - focused Pest verification pack completed for: - tests/Feature/Guards/ActionSurfaceValidatorTest.php - tests/Feature/Guards/ActionSurfaceContractTest.php - tests/Feature/Rbac/TenantActionSurfaceConsistencyTest.php - integrated browser smoke test completed for admin-side reference surfaces: - /admin/operations - /admin/audit-log - /admin/finding-exceptions/queue - /admin/reviews - /admin/tenants ## Notes - system panel browser smoke coverage could not be exercised in the same session because /system routes require platform authentication in the integrated browser - Livewire target remains v4-compliant and no provider registration or asset strategy changes are introduced by this PR Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #200
258 lines
22 KiB
Markdown
258 lines
22 KiB
Markdown
# 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.
|