# Research — Action Surface Contract + CI Enforcement This document resolves planning unknowns for Spec 082 and records key design decisions. ## Decision 1 — Scope enumeration (Tenant + Admin panels; exclude Widgets) **Decision**: The validator enumerates in-scope Filament classes by scanning `app/Filament/**` for Resources, Pages, and RelationManagers and then filtering to: - `app/Filament/Resources/**` (tenant panel discovery) - `app/Filament/Pages/**` (tenant panel discovery) - `app/Filament/RelationManagers/**` (used by resources) - plus any admin-panel explicit resources/pages registered via `app/Providers/Filament/AdminPanelProvider.php` Widgets are explicitly excluded. **Rationale**: - Matches clarified spec scope (Tenant + Admin panels; Resources/Pages/RelationManagers; exclude Widgets). - Deterministic and fast (filesystem + reflection; no UI runtime required). - Aligns with existing repo patterns (guard tests that scan filesystem and fail with actionable output). **Alternatives considered**: - Discovering via Filament panel runtime discovery: rejected (adds runtime coupling and can be non-deterministic in CI). - Enumerating only by filesystem: accepted as baseline, but we still need a small allowlist for admin explicit registrations. ## Decision 2 — Declaration attachment mechanism **Decision**: Each in-scope class provides a declaration via a static method (e.g., `public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration`). **Rationale**: - PHP-first, simple, no attribute parsing or config conventions. - Easy to require/validate by reflection. - Plays well with existing Filament static patterns (Resources already use statics like `$recordTitleAttribute`). **Alternatives considered**: - PHP 8 attributes: rejected for now (slower adoption, harder to keep structured without additional tooling). - Central registry config: rejected (drifts away from the component, discourages ownership). ## Decision 3 — Contract enforcement: “declare or exempt” + representative runtime checks **Decision**: CI enforces that each in-scope class has a declaration and that every required slot is either marked satisfied or has an explicit exemption with a non-empty reason. The validator does not attempt to prove that every underlying Filament `table()` / `getHeaderActions()` actually contains those actions. Instead, representative runtime tests cover constitution-critical behavior (grouping, confirmation, RBAC semantics, canonical run links). **Rationale**: - Deterministic and robust across Filament internal changes. - Avoids brittle source parsing and avoids needing Livewire/Filament runtime contexts. - Still forces explicit human intent on each surface, which is the governance goal. **Alternatives considered**: - AST/static parsing of PHP to verify `->actions()` / `->bulkActions()` usage: rejected (high complexity and brittleness). - Instantiating Filament objects and introspecting action definitions for every surface in CI: rejected initially (risk of boot/DI coupling). Representative runtime tests are used instead for critical guarantees. ## Decision 4 — Profiles and required “slots” **Decision**: Model “profiles” that map to component archetypes (e.g., list-only, list+detail, read-only list, run-log list). Each profile defines required slots: - List header actions - List row actions (max 2 visible; rest in “More”) - List bulk actions (grouped; “More” label) - List empty-state actions - Detail header actions (if the component has a detail view/edit/view page) **Rationale**: - Keeps enforcement simple and consistent. - Allows targeted exceptions by profile without exploding per-component logic. **Alternatives considered**: - One giant universal profile: rejected (too many exemptions, low signal). ## Decision 5 — Exemption policy **Decision**: Exemptions require: - a non-empty reason string - optional tracking reference (issue/PR link) - no deadline requirement **Rationale**: - Matches clarified spec. - Keeps CI deterministic (no time-based expiry). **Alternatives considered**: - Required expiry dates: rejected (creates “time bombs” in CI). ## Decision 6 — Standard grouping label and safe-default Export **Decision**: - The canonical group label for secondary row actions and bulk action groups is `More`. - For `ReadOnly` + `RunLog` profiles, the default expected safe bulk action is `Export` (otherwise exemption required). **Rationale**: - Matches clarified spec. - Provides a consistent, predictable UX baseline. **Alternatives considered**: - Allowing arbitrary labels: rejected (inconsistent UX; harder to audit). ## Decision 7 — Typed confirmation threshold **Decision**: The contract model includes a `requiresTypedConfirmation` flag for actions that are destructive/irreversible, affect > 25 records, or change access/credentials/safety gates. **Rationale**: - Matches clarified spec. - Keeps enforcement declarative and reviewable. **Alternatives considered**: - Always requiring typed confirmations: rejected (too heavy for routine admin workflows).