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