# Feature Specification: Action Surface Contract Compliance & RBAC Hardening **Feature Branch**: `090-action-surface-contract-compliance` **Created**: 2026-02-12 **Status**: Draft **Input**: User description: "Action Surface Contract Compliance & RBAC Hardening" ## Clarifications ### Session 2026-02-12 - Q: Should BackupSchedule retention (soft delete / restore / force delete) be included in Spec 090? → A: Defer it to a separate follow-up spec (Spec 090 covers only P0/P1 + empty-state CTAs). - Q: Should the system record audit trail entries for denied/cancelled attempts, or only for successful executions? → A: Audit successful executions only. - Q: Should row-click navigation be removed, or kept while adding an inspection affordance? → A: Keep row-click navigation where it exists; the inspection affordance may be clickable rows, a primary linked column, or a dedicated “View” action (avoid rendering a lone “View” row action button on read-only lists). ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Safe admin actions (Priority: P1) As an admin, I can only execute actions that create changes or side-effects when I am entitled to the scope (tenant/workspace) and have the required capability. **Why this priority**: Prevents unauthorized changes and accidental destructive operations; closes RBAC gaps. **Independent Test**: Can be fully tested by attempting a small set of in-scope actions as (a) non-member, (b) member without capability, (c) member with capability. **Acceptance Scenarios**: 1. **Given** I am not a member of the tenant/workspace scope, **When** I try to access pages or execute actions in that scope, **Then** I receive a not found result (deny-as-not-found). 2. **Given** I am a member of the tenant/workspace scope but I lack the capability for an action with side-effects, **When** I attempt the action, **Then** the UI communicates it is unavailable and direct execution is denied. 3. **Given** I am a member of the tenant/workspace scope and I have the capability, **When** I execute the action, **Then** the action completes and an audit trail entry is recorded. --- ### User Story 2 - Consistent action surfaces (Priority: P2) As an admin, I can reliably discover how to inspect and manage records across the admin panel because each list/table provides a visible inspection affordance and consistent action grouping and ordering. **Why this priority**: Improves enterprise UX consistency, accessibility, and reduces operator error. **Independent Test**: Can be tested by reviewing targeted list/table screens for a clear inspection affordance, consistent ordering, and consistent “More” grouping. **Acceptance Scenarios**: 1. **Given** a record list/table for an in-scope resource, **When** I view the table’s inspection affordance, **Then** I can navigate to a record detail page using at least one of: clickable row navigation, a primary linked column, or a dedicated “View” action. 2. **Given** a record list/table has row-click navigation, **When** I cannot (or prefer not to) use row-click navigation, **Then** I can still navigate to record details using a visible alternative inspection affordance (a primary linked column and/or a dedicated “View” action). 3. **Given** a list/table is read-only and “View” would be the only row action, **When** the UI is rendered, **Then** it SHOULD avoid rendering a lone “View” row action button and instead use clickable rows or a primary linked column. --- ### User Story 3 - Productive empty states (Priority: P3) As an admin, when a resource has no records yet, the empty state clearly indicates what to do next and offers a “New …” call-to-action when I am allowed to create records. **Why this priority**: Reduces onboarding friction and “dead-end” screens. **Independent Test**: Can be tested by visiting empty list/table screens for user-created resources and verifying the create call-to-action is present only when permitted. **Acceptance Scenarios**: 1. **Given** there are no workspaces yet, **When** I open the workspaces list, **Then** I see a create call-to-action if I am allowed to create. 2. **Given** there are no backup schedules yet, **When** I open the backup schedules list, **Then** I see a create call-to-action if I am allowed to create. --- ### Edge Cases - A user attempts to execute a side-effect action via a direct request while the UI action is disabled. - Bulk actions: mixed selection where some records are outside scope or not authorized. - A destructive action is triggered unintentionally; confirmation must prevent accidental execution. - Accessibility: users who cannot use row-click navigation must still discover a usable inspection affordance. - Two admins attempt the same side-effect action concurrently; the system must behave predictably (no partial execution without an audit trail). ## Requirements *(mandatory)* **Constitution alignment (required):** If this feature introduces any Microsoft Graph calls, any write/change behavior, or any long-running/queued/scheduled work, the spec MUST describe contract registry updates, safety gates (preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests. If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries. **Constitution alignment (RBAC-UX):** If this feature introduces or changes authorization behavior, the spec MUST: - state which authorization plane(s) are involved (tenant `/admin/t/{tenant}` vs platform `/system`), - ensure any cross-plane access is deny-as-not-found (404), - explicitly define 404 vs 403 semantics: - non-member / not entitled to workspace scope OR tenant scope → 404 (deny-as-not-found) - member but missing capability → 403 - describe how authorization is enforced server-side (Gates/Policies) for every mutation/operation-start/credential change, - reference the canonical capability registry (no raw capability strings; no role-string checks in feature code), - ensure global search is tenant-scoped and non-member-safe (no hints; inaccessible results treated as 404 semantics), - ensure destructive-like actions require confirmation (`->requiresConfirmation()`), - include at least one positive and one negative authorization test, and note any RBAC regression tests added/updated. **Constitution alignment (OPS-EX-AUTH-001):** OIDC/SAML login handshakes may perform synchronous outbound HTTP (e.g., token exchange) on `/auth/*` endpoints without an `OperationRun`. This MUST NOT be used for Monitoring/Operations pages. **Constitution alignment (BADGE-001):** If this feature changes status-like badges (status/outcome/severity/risk/availability/boolean), the spec MUST describe how badge semantics stay centralized (no ad-hoc mappings) and which tests cover any new/changed values. **Constitution alignment (Filament Action Surfaces):** If this feature adds or modifies any Filament Resource / RelationManager / Page, the spec MUST include a “UI Action Matrix” (see below) and explicitly state whether the Action Surface Contract is satisfied. If the contract is not satisfied, the spec MUST include an explicit exemption with rationale. ### Functional Requirements - **FR-001 (Scope boundaries)**: The system MUST preserve deny-as-not-found for tenant/workspace scope boundaries: non-members or not-entitled users MUST receive a not found result for pages and actions in that scope. - **FR-002 (403 semantics)**: When a user is a member of the tenant/workspace scope but lacks a required capability, the system MUST deny execution of the action with a forbidden result. - **FR-003 (UI communication)**: For members without required capability, the UI MUST communicate that the action is unavailable (disabled state + explanatory hint). - **FR-004 (Side-effect protection)**: Any action that creates/updates/deletes data or triggers external side-effects MUST require capability-first gating and MUST be enforced server-side. - **FR-005 (Destructive confirmations)**: Destructive actions (archive, deactivate, delete, force delete) MUST require an explicit confirmation step. - **FR-006 (Inspect affordance)**: Each in-scope list/table MUST provide a record inspection affordance per record. Accepted forms: clickable row navigation, a primary linked column, or a dedicated “View” action. Where row-click navigation already exists, it SHOULD be preserved; any dedicated “View” action is additive for accessibility and discoverability. - **FR-007 (Action order and grouping)**: Row actions MUST follow the baseline order “View → Edit → Secondary → Destructive (last)”, and secondary actions MUST be grouped under a consistently labeled “More” group. - **FR-008 (Presentation semantics)**: Sync/run-style actions MUST NOT be presented as destructive. - **FR-009 (Empty-state CTAs)**: User-created resources in scope MUST provide an empty-state create call-to-action when the user is allowed to create. - **FR-010 (Auditability)**: For every executed side-effect action in scope, the system MUST record an audit trail entry including who performed it, what was attempted, the scope (tenant/workspace), and the outcome. - **FR-011 (Audit scope)**: Denied or cancelled attempts MUST NOT be required to create audit trail entries for this feature. #### In-scope hotspots The following hotspots MUST be brought into compliance: - Policy “Capture snapshot” action: requires Tenant Sync capability and must be treated as a side-effect action. - Workspace CRUD actions: capability-first gating across create/edit and related header actions. - Tenant archive/deactivate: destructive confirmation compliance. - Provider connections: ensure a visible inspection affordance and consistent “More” grouping/order. - Directory groups (Entra groups) and inventory items: verify they remain compliant with the inspection-affordance rule (clickable rows) and do not regress. - Findings list: action ordering consistency (View first). ### Canonical Capability Mapping (Spec 090) All capability checks in this feature MUST use constants from `App\Support\Auth\Capabilities`: | Hotspot / Surface | Required capability constant(s) | |---|---| | Policy list + view: “Sync from Intune”, “Capture snapshot” | `Capabilities::TENANT_SYNC` | | Findings acknowledge actions (row, bulk, list-header mass acknowledge) | `Capabilities::TENANT_FINDINGS_ACKNOWLEDGE` | | Backup schedules: create/edit/delete | `Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE` | | Backup schedules: run/retry (row + bulk) | `Capabilities::TENANT_BACKUP_SCHEDULES_RUN` | | Provider connections: create/edit/credentials/default/enable/disable | `Capabilities::PROVIDER_MANAGE` | | Provider connections: check connection / inventory sync / compliance snapshot | `Capabilities::PROVIDER_RUN` | | Tenants: edit/admin consent | `Capabilities::TENANT_MANAGE` | | Tenants: deactivate/force delete/restore | `Capabilities::TENANT_DELETE` | | Tenants: sync | `Capabilities::TENANT_SYNC` | | Workspaces: create/edit | `Capabilities::WORKSPACE_MANAGE` | ## UI Action Matrix *(mandatory when Filament is changed)* If this feature adds/modifies any Filament Resource / RelationManager / Page, fill out the matrix below. For each surface, list the user-facing actions (intent) and note the label when it is explicitly customized. Do not rely on framework-default labels being stable across versions. Also capture whether actions are destructive (confirmation? typed confirmation?), RBAC gating (capability + enforcement helper), and whether the mutation writes an audit log. | Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions | |---|---|---|---|---|---|---|---|---|---| | Policies (list) | Tenant panel (tenant scope) | “Sync from Intune” | Dedicated “View” row action | Visible: “View”; Group: “More” → “Ignore” / “Restore” / “Sync” / “Export to Backup” | Group: “More” → “Ignore Policies” / “Restore Policies” / “Sync Policies” / “Export to Backup” | “Sync from Intune” (same as header) | — | — | Yes | Sync/run-style actions must not be presented as destructive; side-effect actions are capability-gated and enforced server-side. | Policy (view) | Tenant panel (tenant scope) | — | — (view page) | — | — | — | “Capture snapshot” (confirmation) | — | Yes | Capture snapshot is treated as a side-effect action. | Workspaces (list + CRUD) | Admin panel (workspace scope) | Create workspace (Create action) | Dedicated “View” row action | Visible: “View” / “Edit” | — | (If table is empty) Create workspace | — | Save + Cancel | Yes | Ensure capability-first gating for create/edit and consistent action ordering. | Tenants (list) | Admin panel (platform scope) | — | Dedicated “View” row action (label: “View”) | Action group (ellipsis) containing: “View” / “Sync” / “Open” / “Edit” / “Restore” / “Admin consent” / “Open in Entra” / “Verify configuration” / “Deactivate” / “Force delete” | “Sync selected” | — | — | Save + Cancel | Yes | Destructive actions (“Deactivate”, “Force delete”) require confirmation; side-effect actions (“Sync”, “Verify configuration”) require capability gating. | Provider connections (list) | Admin panel (platform scope; tenant-filtered) | Create provider connection (Create action) | Clickable rows (record URL to view) | Action group (target label: “More”) containing: “Edit” / “Check connection” / “Inventory sync” / “Compliance snapshot” / “Set as default” / “Update credentials” / “Enable connection” / “Disable connection” | — | (If table is empty) Create provider connection | — | Save + Cancel | Yes | Current code uses a group label “Actions”; normalize to “More” to match FR-007. | Directory groups (Entra groups list) | Admin panel (tenant scope) | None (intentionally) | Clickable rows (record URL) | None (read-only list) | None | None | — | — | No | Inspection affordance is clickable rows; no row actions. | Inventory items (list) | Admin panel (tenant scope) | None (intentionally) | Clickable rows (record URL) | None (read-only list) | None | None | — | — | No | Inspection affordance is clickable rows; no row actions. | Findings (list) | Tenant panel (tenant scope) | — | Dedicated “View” row action | Visible: “View”; Secondary: “Acknowledge” (confirmation) | Group: “Acknowledge selected” (confirmation) | — | — | — | Yes | Action ordering must match baseline (View first; secondary actions grouped). | Backup schedules (list) | Tenant panel (tenant scope) | Create backup schedule (Create action) | “Edit” (opens record details/edit form) | Action group (ellipsis) containing: “Run now” / “Retry” / “Edit” / “Delete” | Group: “Run now” / “Retry” / “Delete” | (If table is empty) Create backup schedule | — | Save + Cancel | Yes | Retention/archival is deferred; destructive actions (“Delete”, bulk delete) require confirmation. ### Key Entities *(include if feature involves data)* - **Capability**: A named permission that grants access to a class of actions (e.g., “Tenant Sync”, “Workspace Manage”). - **Scope entitlement**: Whether a user is a member of / entitled to a tenant or workspace. - **Admin action surface**: Where a user can discover and trigger an action (header, row actions, bulk actions, empty state). - **Destructive action**: An action that archives, deactivates, deletes, or force deletes data. - **Audit trail entry**: A record of an attempted or completed action, including actor, scope, and outcome. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001 (Contract compliance)**: 100% of the in-scope resources satisfy Action Surface Contract v1 (clear inspection affordance, consistent “More” group, correct action ordering). - **SC-002 (RBAC hardening)**: 100% of in-scope side-effect actions are denied for members without capability and are deny-as-not-found for non-members. - **SC-003 (Confirmation compliance)**: 100% of in-scope destructive actions require confirmation prior to execution. - **SC-004 (Discoverability)**: For each in-scope list/table, at least one inspection affordance is clearly visible and usable to access record details (clickable rows, a primary linked column, and/or a dedicated “View” action). - **SC-005 (Auditability)**: 100% of executed in-scope side-effect actions produce an audit trail entry with actor + scope + outcome. ## Assumptions - Capability names used in this spec map to the canonical capability registry. - “Capture snapshot” is considered a side-effect action and therefore requires both confirmation and capability gating. - BackupSchedule retention (soft delete / restore / force delete) is explicitly deferred to a follow-up spec. - Existing row-click navigation patterns should remain unchanged; this feature ensures a clear inspection affordance (clickable rows, primary linked columns, or a dedicated “View” action where appropriate). ## Out of Scope - Navigation or information architecture redesign. - Broad refactors of policies/capabilities outside the listed hotspots. - Changing the established 404 vs 403 boundary semantics. - BackupSchedule retention behavior changes (soft delete, restore, force delete). ## Rollout Notes - This change primarily affects user experience and authorization enforcement; it should be safe to roll out behind existing access controls. - Optional retention/archival behavior (if introduced later) requires a separate rollout decision. ## Testing Notes - Add at least one positive and one negative authorization test per critical hotspot. - Add regression assertions that each in-scope list/table has a clear inspection affordance and consistent ordering. - Verify that denied attempts do not create audit trail entries for this feature.