TenantAtlas/specs/169-action-surface-v11/plan.md

258 lines
22 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.

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