Implements Spec 082 updates to the Filament Action Surface Contract: - New required list/table slot: InspectAffordance (clickable row via recordUrl preferred; also supports View action or primary link column) - Retrofit view-only tables to remove lone View row action buttons and use clickable rows - Update validator + guard tests, add golden regression assertions - Add docs: docs/ui/action-surface-contract.md Tests (local via Sail): - vendor/bin/sail artisan test --compact tests/Feature/Guards/ActionSurfaceContractTest.php - vendor/bin/sail artisan test --compact tests/Feature/Guards/ActionSurfaceValidatorTest.php - vendor/bin/sail artisan test --compact tests/Feature/Rbac/ActionSurfaceRbacSemanticsTest.php - vendor/bin/sail artisan test --compact tests/Feature/Filament/EntraGroupSyncRunResourceTest.php Notes: - Filament v5 / Livewire v4 compatible. - No destructive-action behavior changed in this PR. Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box> Reviewed-on: #100
192 lines
16 KiB
Markdown
192 lines
16 KiB
Markdown
# Feature Specification: Action Surface Contract + CI Enforcement
|
||
|
||
**Feature Branch**: `082-action-surface-contract`
|
||
**Created**: 2026-02-08
|
||
**Status**: Draft
|
||
**Input**: User description: "Spec 082 — Action Surface Contract + CI Enforcement"
|
||
|
||
## Clarifications
|
||
|
||
### Session 2026-02-08
|
||
|
||
- Q: Group label standard: “More” vs “Actions” → A: Use “More” as the standard label for secondary row actions and bulk action groups.
|
||
- Q: Standard safe bulk action for ReadOnly/RunLog: allow Export everywhere? → A: Yes — Export is the default safe bulk action for ReadOnly and RunLog surfaces when data exists; otherwise an explicit exemption with reason is required.
|
||
- Q: Exemption policy: time-boxed vs reason-only? → A: Exemptions must include a non-empty reason; an optional tracking link/reference may be included; no deadline is required.
|
||
- Q: Audit policy: destructive-only vs all mutations? → A: Audit events are required for all mutations and for operation-start triggers (run/sync/verify/dispatch).
|
||
- Q: Validator scope/discovery: which components are in-scope for CI enforcement? → A: Tenant + Admin panels; validate Resources, Pages, and embedded relationship sub-lists; exclude Widgets.
|
||
|
||
## User Scenarios & Testing *(mandatory)*
|
||
|
||
### User Story 1 - Prevent incomplete UI action surfaces (Priority: P1)
|
||
|
||
As a developer, when I add or modify an admin-console UI component (list, detail, embedded relationship sub-list), I must explicitly declare its expected action surface (or explicitly exempt parts with a reason), so that automated checks prevent regressions before merge.
|
||
|
||
**Why this priority**: This is the regression gate. Without it, incomplete action surfaces ship repeatedly and create inconsistent UX and RBAC drift.
|
||
|
||
**Independent Test**: Add a minimal new UI component without an action surface declaration and confirm the automated gate fails with a precise message; then add a declaration (or exemption) and confirm the gate passes.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a new or modified in-scope UI component without an action surface declaration, **When** validation runs, **Then** it fails with a message naming the component and the missing required slots.
|
||
2. **Given** a component with a complete declaration (or exemptions with reasons), **When** validation runs, **Then** it passes.
|
||
3. **Given** a component uses an exemption, **When** validation runs, **Then** it fails if the exemption reason is missing or empty.
|
||
|
||
---
|
||
|
||
### User Story 2 - Consistent, enterprise-grade actions and empty-state guidance (Priority: P2)
|
||
|
||
As an admin-console user, I see predictable and consistent actions across lists and details: primary actions are obvious, secondary actions are grouped, bulk actions exist where appropriate, and empty states provide an actionable next step.
|
||
|
||
**Why this priority**: Reduces training cost and operational mistakes; improves speed and confidence in admin workflows.
|
||
|
||
**Independent Test**: Pick one CRUD-style list and one run-log style list; verify each has the expected action areas (header/row/bulk/empty-state; detail header actions) and that ordering/grouping conventions are consistent.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a list view with records, **When** a user inspects available actions, **Then** they see at most two visible row actions and all secondary actions are grouped under a consistent label.
|
||
2. **Given** a list view with zero records, **When** the page is shown, **Then** it includes at least one CTA that helps resolve the empty state.
|
||
3. **Given** a list view that supports selection, **When** the user selects one or more records, **Then** at least one bulk action is available or the UI explicitly documents why bulk is intentionally not offered.
|
||
|
||
---
|
||
|
||
### User Story 3 - Predictable RBAC behavior for members vs non-members (Priority: P3)
|
||
|
||
As a security reviewer, I need consistent authorization semantics across all in-scope actions:
|
||
- A non-member must not discover tenant/workspace surfaces (deny-as-not-found).
|
||
- A member without the required capability must be prevented from completing the action (deny-as-forbidden), and the UI must communicate why.
|
||
|
||
**Why this priority**: Prevents information disclosure and capability drift; keeps the product aligned with least privilege.
|
||
|
||
**Independent Test**: Use a representative surface and verify (a) non-member receives not-found, and (b) member without capability receives forbidden and sees disabled UI with a helpful explanation.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a user who is not a member of the relevant tenant/workspace scope, **When** they attempt to access a related page or record, **Then** the response is not-found.
|
||
2. **Given** a user who is a member but lacks capability for a specific action, **When** they attempt to execute the action, **Then** the response is forbidden.
|
||
3. **Given** a member without capability viewing the UI, **When** they inspect the action surface, **Then** disallowed actions are visible-but-disabled with a clear explanation (not silently missing).
|
||
|
||
### Edge Cases
|
||
|
||
- A surface is intentionally read-only and has no meaningful bulk actions (must be explicitly exempted with a reason).
|
||
- A user loses membership/capability between page load and action execution (server-side enforcement must still apply).
|
||
- Bulk mutations above a risk threshold must require stronger confirmation (typed confirmation) to reduce accidental damage.
|
||
- Empty states that cannot be resolved from within the current surface (CTA should route users to the correct “next step” surface).
|
||
- Global search and cross-scope links must not reveal existence of tenant/workspace data to non-members.
|
||
|
||
## Requirements *(mandatory)*
|
||
|
||
**Constitution alignment (required):** This feature is primarily a UI governance and authorization-consistency feature. It MUST not introduce new external calls or long-running operations as part of the enforcement gate.
|
||
|
||
**Constitution alignment (RBAC-UX):** This feature formalizes 404 vs 403 semantics and requires both UI signaling and server-side enforcement for in-scope actions.
|
||
|
||
**Constitution alignment (Action Surfaces):** This feature defines and enforces an Action Surface Contract, and introduces a mandatory UI Action Matrix for future admin UI-related specs.
|
||
|
||
### Functional Requirements
|
||
|
||
- **FR-001 (Contract profiles)**: The system MUST define a small set of action-surface profiles (e.g., CRUD, ReadOnly, RunLog, embedded relationship sub-lists) that describe minimum expected action areas for list and detail surfaces.
|
||
- **FR-002 (Required action slots)**: For each profile, the system MUST define required action slots for:
|
||
- list/table header actions,
|
||
- row actions,
|
||
- bulk actions,
|
||
- empty-state CTA(s),
|
||
- detail header actions,
|
||
- create/edit save/cancel conventions (where applicable).
|
||
- **FR-003 (Exemptions)**: The system MUST allow explicit exemptions for specific slots, and each exemption MUST include:
|
||
- a non-empty reason, and
|
||
- an optional tracking reference (issue/PR link or identifier).
|
||
- **FR-004 (Mandatory declaration)**: For every in-scope admin-console UI component that is new or modified, a declaration MUST exist that:
|
||
- identifies the profile,
|
||
- declares which slots are satisfied,
|
||
- lists any exemptions with reasons.
|
||
- **FR-005 (Automated gate)**: The system MUST automatically validate contract compliance for all in-scope UI components in the code review pipeline, and MUST fail with actionable messages when requirements are not met.
|
||
- **FR-006 (Consistency rules)**: The system MUST enforce the following UX conventions through (a) declaration validation for all in-scope surfaces and (b) runtime tests on representative surfaces:
|
||
- no more than two visible row actions (typically View/Edit),
|
||
- secondary actions grouped under the standard label “More”,
|
||
- bulk actions grouped under the standard label “More”,
|
||
- destructive actions are never primary.
|
||
- **FR-006a (Default safe bulk action)**: For ReadOnly and RunLog profiles, the default bulk action MUST be “Export” when the surface contains data; if “Export” is not offered, an explicit exemption with a reason is required.
|
||
- **FR-007 (Safety + confirmation)**: Any mutating or destructive action MUST require confirmation. For this feature, compliance is enforced via declaration checks plus representative runtime tests for touched surfaces.
|
||
- Mutation classes for FR-007:
|
||
- data mutation: create/update/delete/archive/restore/attach/detach,
|
||
- operation-start trigger: enqueue/dispatch/run/sync/verify/retry actions,
|
||
- access/safety mutation: role or membership changes, credential or permission updates, safety gate toggles.
|
||
- High-risk bulk actions MUST require typed confirmation when any of the following is true:
|
||
- the action is destructive/irreversible, or
|
||
- the action affects more than 25 records, or
|
||
- the action changes tenant/workspace access, credentials, or operational safety gates.
|
||
- **FR-008 (Auditability)**: All mutations and operation-start triggers MUST use existing audit sinks and MUST write an audit event that records at minimum: actor, scope (tenant/workspace), action type, target(s), timestamp, and outcome.
|
||
- Audit sinks for FR-008:
|
||
- tenant-scoped actions: `App\Services\Intune\AuditLogger` (writes `audit_logs`),
|
||
- workspace/platform-scoped actions: `App\Services\Audit\WorkspaceAuditLogger` (writes `audit_logs`).
|
||
- Operation-start triggers MAY satisfy FR-008 via canonical `OperationRun` creation when the action is run-oriented and existing architecture uses `OperationRun` as the observability record.
|
||
- For this feature, FR-008 is enforced as: no regressions on touched representative surfaces and no new ad-hoc audit sink introduced.
|
||
- **FR-009 (RBAC UI gating standard)**: UI gating MUST follow the project’s standard semantics:
|
||
- non-member → deny-as-not-found behavior,
|
||
- member without capability → deny-as-forbidden behavior,
|
||
- UI communicates “forbidden” as visible-but-disabled actions with a tooltip/explanation.
|
||
- **FR-010 (Server-side authorization)**: Server-side authorization MUST be enforced for every action execution (not just UI visibility) and MUST align with FR-009 semantics (see constitution RBAC-UX-001..004).
|
||
- **FR-011 (Canonical run links)**: Any “View run” deep link MUST use the canonical tenantless operations path as the primary link; tenant-scoped convenience links may exist but must not be primary.
|
||
- **FR-012 (Scope)**: The contract MUST cover both the tenant-scoped admin console and the platform/admin console surfaces for resources, pages, and embedded relationship sub-lists.
|
||
- **FR-012a (Explicit CI scope)**: The automated gate MUST validate Resources, Pages, and embedded relationship sub-lists in both tenant-scoped and platform/admin consoles, and MUST exclude Widgets from enforcement.
|
||
|
||
### Non-Goals
|
||
|
||
- This feature does not redesign visual layout or page styling.
|
||
- This feature does not change domain workflows (sync/restore/verify), except to ensure action surfaces are complete or explicitly exempted.
|
||
- This feature does not change panel architecture or audience boundaries.
|
||
|
||
### Assumptions
|
||
|
||
- A safe, broadly applicable bulk action exists for read-only/run-log surfaces (“Export”) where data volume and permissions allow.
|
||
- Some existing surfaces will require temporary exemptions to keep the repository in a releasable state while retrofit work is completed.
|
||
|
||
### Enforcement Boundaries
|
||
|
||
- The CI validator for this feature is declaration-driven and deterministic (filesystem + reflection only).
|
||
- Runtime behavior is verified via representative tests for each profile and each critical rule family (grouping, confirmation, RBAC semantics, canonical run links).
|
||
- Deep runtime introspection of every Filament action in CI is explicitly out of scope for this iteration.
|
||
|
||
## UI Action Matrix *(mandatory when admin UI components are changed)*
|
||
|
||
_Note: This matrix applies whenever admin UI components (resources, pages, or embedded relationship sub-lists) are added or changed._
|
||
|
||
This feature introduces a contract that applies broadly. The matrix below documents the minimum action surface expectations by profile.
|
||
|
||
| Surface | Location | Header Actions | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||
|---|---|---|---|---|---|---|---|---|---|
|
||
| CRUD list + detail | Tenant panel + Admin panel | Primary: Create (if allowed). Optional: domain CTA (e.g., Sync/Run) | Primary: View, Edit (if allowed). Secondary: grouped under “More” | At least 1 bulk action (e.g., Archive/Restore/Export) | Primary CTA: Create or domain CTA | Primary: Edit (if allowed). Secondary grouped under “More” | Consistent Save + Cancel; no destructive primary | Yes (for mutations + operation-start) | Exempt bulk only with explicit reason (e.g., no safe bulk operation exists) |
|
||
| ReadOnly list + detail | Tenant panel + Admin panel | At least 1 CTA that provides value (e.g., Export/Refresh) | Primary: View. Secondary grouped under “More” | Bulk: Export (default) | CTA that resolves empty state (e.g., Refresh now) | “More” group for secondary actions | N/A | Maybe (typically for operation-start actions) | Exempt Export only with explicit reason (e.g., legal/security constraints) |
|
||
| RunLog list + detail | Tenant panel + Admin panel | Optional CTA routing to Operations hub | Primary: View | Bulk: Export (default) or Prune if retention exists; otherwise exempt with reason | Optional CTA routing to Operations hub | “View run” canonical link; optional Export | N/A | Maybe (for prune/retention changes) | “View run” must be canonical tenantless as primary deep link |
|
||
| Embedded relationship sub-list | Under parent detail | Header: Add/Attach/Create/Refresh depending on context | Primary: View and one context action (e.g., Detach/Remove). Secondary grouped under “More” | At least 1 bulk action or exemption with reason | CTA: next step (Add/Attach/Refresh) | N/A | N/A | Yes (for mutations) | Bulk may be exempted if relationship action is inherently single-record |
|
||
|
||
### Key Entities *(include if feature involves data)*
|
||
|
||
- **Action Surface Declaration**: A structured, human-reviewed statement of what actions a surface offers (and why some may be exempt).
|
||
- **Action Surface Profile**: A category of surfaces with shared minimum action expectations (CRUD, ReadOnly, RunLog, embedded relationship sub-list).
|
||
- **Action Slot**: A required location/type of action (header, row, bulk, empty-state, detail header, save/cancel).
|
||
- **Exemption**: A documented, explicit deviation from the contract for a specific slot.
|
||
- **Capability**: The permission concept used to decide whether an action is allowed.
|
||
- **Audit Event**: A record that an action was attempted/executed and its outcome.
|
||
|
||
### Dependencies
|
||
|
||
- A single, canonical capability registry exists and is used consistently for authorization decisions.
|
||
- Standard UI gating helpers exist for tenant/workspace scopes and are the only allowed approach for UI enable/disable behavior.
|
||
- A centralized audit-event sink exists to record user-triggered mutations and operation starts.
|
||
|
||
### Acceptance Notes (verification approach)
|
||
|
||
- Contract compliance is verified via an automated validation gate that enumerates in-scope UI components and checks required slots/exemptions.
|
||
- Authorization correctness is verified via representative automated tests that prove not-found vs forbidden behavior across at least one surface per profile.
|
||
- Consistency/confirmation/canonical-link behavior is verified on representative runtime surfaces; declaration checks remain the repo-wide completeness gate.
|
||
|
||
## Success Criteria *(mandatory)*
|
||
|
||
### Measurable Outcomes
|
||
|
||
- **SC-001 (Coverage)**: 100% of in-scope UI components are contract-compliant or have explicit exemptions with reasons.
|
||
- **SC-002 (Regression prevention)**: Any new/modified in-scope UI component without a declaration fails validation in the review pipeline.
|
||
- **SC-003 (Action completeness)**: For all CRUD and ReadOnly list surfaces, the selection state always presents at least one bulk action or an explicit exemption.
|
||
- **SC-004 (Authorization correctness)**: At least one representative surface per profile has automated tests proving not-found vs forbidden behavior (non-member vs member without capability).
|
||
- **SC-005 (Developer experience)**: Validation failures provide actionable messages that identify the component and missing slot(s), enabling a fix without additional investigation.
|