# Phase 0 Research: Ops-UX Constitution Rollout (v1.3.0 Alignment) (055) **Date**: 2026-01-18 ## Findings ### Current operation-run storage and UX primitives - The repo has a tenant-scoped `operation_runs` table and an `OperationRun` model. - Structured “metrics” for this rollout are stored as `operation_runs.summary_counts` (JSONB). - Canonical Monitoring entrypoint exists via the Filament resource `OperationRunResource`. - Canonical route building already exists in `App\Support\OperationRunLinks`. ### Existing progress widget patterns - A bottom-right Livewire widget exists today for bulk operations: `App\Livewire\BulkOperationProgress`. - It is injected globally via a Filament render hook in `App\Providers\Filament\AdminPanelProvider`. - Current behavior is not constitution-compliant: - It shows terminal states and renders terminal wording. - It renders percentages and per-run counts unconditionally. - It is user-scoped today (filters by `user_id = auth()->id()`). ### Notifications - Filament database notifications are already used and are persistent by default. - Current code has OperationRun DB notifications (queued + completed), but this rollout requires: - no queued DB notifications - exactly one terminal notification per run - initiator-only audience ## Decisions ### Decision 1: Tenant-wide progress widget scope - **Chosen**: The progress widget shows all active runs for the current tenant (not only runs started by the current user), for users with access to Monitoring → Operations. - **Rationale**: Aligns with the constitution’s tenant-scoped operations model and avoids “why don’t I see what’s running?” support loops. - **Alternatives considered**: - User-only widget: rejected (hides tenant work and defeats the monitoring intent). ### Decision 2: Canonical metrics source - **Chosen**: Treat `operation_runs.summary_counts` as the canonical “metrics” field for this rollout. - **Rationale**: Matches existing schema; avoids adding a new `metrics` column during a UX migration. - **Alternatives considered**: - New `operation_runs.metrics` column: rejected (scope increase + migration risk). ### Decision 3: Unknown operation types in UI - **Chosen**: Soft-fail at runtime with label `Unknown operation`, and fail-fast in CI for code-produced operation types. - **Rationale**: Keeps Monitoring usable for legacy/dirty data while enforcing discipline for new work. - **Alternatives considered**: - Throw exception at runtime: rejected (breaks Monitoring for historical data). - Render raw type string: rejected (leaks internal naming and encourages drift). ### Decision 4: DB notification audience - **Chosen**: Initiator-only for terminal DB notifications. - **Rationale**: Prevents tenant-wide notification spam; Monitoring remains the tenant-wide audit surface. - **Alternatives considered**: - Tenant-wide fan-out: rejected (noisy; not necessary for monitoring). ### Decision 5: No queued DB notifications - **Chosen**: Ban queued DB notifications repo-wide for OperationRuns. - **Rationale**: Simplifies dedupe; queued intent belongs to the toast surface. - **Alternatives considered**: - Allow queued DB notifications with dedupe: rejected (still noisy; adds edge cases). ### Decision 6: Migration approach for progress widget - **Chosen**: Reuse the existing “global Livewire progress widget via render hook” pattern, but migrate it to query `OperationRun` and apply constitution rules. - **Rationale**: Low-risk way to ship a single widget surface across the app. - **Alternatives considered**: - Per-feature widgets/pages: rejected (violates the “three surfaces only” rule). ## Open Questions None (all clarifications captured in the feature spec).