# Feature Specification: Ops-UX Constitution Rollout (v1.3.0 Alignment)

**Feature Branch**: `055-ops-ux-rollout`  
**Created**: 2026-01-18  
**Status**: Draft  
**Input**: Repo-wide migration to align all existing operation feedback with the Operations UX Constitution (v1.3.0).

## Clarifications

### Session 2026-01-18

- Q: For the Progress Widget, what should be the visibility scope? → A: All active runs for the current tenant (visible to users who can access Monitoring → Operations).
- Q: For R7 metrics/summary contract, which field is the canonical source across the app? → A: Treat `operation_runs.summary_counts` as the canonical “metrics” source for this rollout.
- Q: If an existing run record contains an unknown `operation_type`, what should the UI do at runtime? → A: Soft fail: show `Unknown operation` (tests/CI still fail fast for code-produced operation types).
- Q: Who should receive the terminal DB notification for a run? → A: Only the initiator.
- Q: For this rollout, do we ban queued DB notifications entirely in favor of queued toast + terminal DB notification? → A: Yes, ban queued DB notifications.

## User Scenarios & Testing *(mandatory)*

<!--
  IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
  Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
  you should still have a viable MVP (Minimum Viable Product) that delivers value.
  
  Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
  Think of each story as a standalone slice of functionality that can be:
  - Developed independently
  - Tested independently
  - Deployed independently
  - Demonstrated to users independently
-->

### User Story 1 - Consistent “I started it” feedback (Priority: P1)

As a tenant admin who triggers a long-running operation, I want immediate confirmation that my action was accepted and a single, consistent way to follow progress, so I don’t retry actions or lose track.

**Why this priority**: Prevents duplicate operations and reduces confusion/support load.

**Independent Test**: Starting any operation produces a queued-only intent feedback with a canonical “View run” destination.

**Acceptance Scenarios**:

1. **Given** an operation is started and the run is created or reused in `queued`, **When** feedback is shown, **Then** it is a queued-only toast with title `{OperationLabel} queued` and body `Running in the background.`
2. **Given** an operation completes quickly (<2 seconds), **When** feedback is shown, **Then** the queued toast may be suppressed but the terminal DB notification still appears.

---

### User Story 2 - Live awareness of active operations (Priority: P2)

As a tenant admin, I want a single progress widget that shows only active operations (queued/running) with strict, predictable wording, so I can understand what’s happening without noise.

**Why this priority**: Creates a unified “what’s running?” view and eliminates feature-specific progress UIs.

**Independent Test**: The progress widget lists only active runs, with strict “Queued/Running” text and canonical “View run” links.

**Acceptance Scenarios**:

1. **Given** there are active operations in `queued` or `running`, **When** the widget is visible, **Then** it shows at most 5 runs and each row includes a canonical “View run”.
2. **Given** an operation is terminal (`succeeded|partial|failed`), **When** the widget queries its data, **Then** that run is never included.

---

### User Story 3 - Audit + outcome without spam (Priority: P3)

As a tenant admin, I want exactly one persistent notification when an operation finishes (success/partial/failure), with a consistent title/body and safe summary, so I can audit outcomes and troubleshoot.

**Why this priority**: Delivers reliable outcomes and reduces notification noise.

**Independent Test**: Terminal runs always create exactly one DB notification with canonical copy and safe summary rules.

**Acceptance Scenarios**:

1. **Given** an operation run transitions into a terminal outcome, **When** notifications are emitted, **Then** exactly one terminal DB notification exists for that run.
2. **Given** valid numeric metrics exist for an operation, **When** the notification body includes a summary, **Then** the summary renders only whitelisted numeric keys and never renders free-text.

---

### User Story 4 - Regression-safe by default (Priority: P4)

As a maintainer, I want automated guards that fail fast when the app deviates from the constitution (labels, links, surfaces, summary rules), so drift is prevented across future features.

**Why this priority**: This is a migration; without guards, the codebase will regress quickly.

**Independent Test**: A test suite enforces invariants (catalog coverage, canonical “View run”, terminal notification idempotency, widget filtering, summary whitelist).

**Acceptance Scenarios**:

1. **Given** a new/unknown `operation_type` is introduced, **When** tests run, **Then** the build fails until the OperationCatalog is updated.
2. **Given** any “View run” link is generated, **When** it is resolved, **Then** it matches the canonical Monitoring → Operations → Run Detail destination.

---

### Edge Cases

- Unknown `operation_type` appears in an existing run record.
- Multiple operations start simultaneously (more than 5 active runs).
- Runs with missing/invalid metrics (nested objects, strings, non-whitelisted keys, negative values).
- Runs that transition `queued → terminal` quickly (<2 seconds).
- UI is backgrounded/hidden while operations are active.
- A modal dialog is open while an operation is active.

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

**Operations UX alignment (required when applicable):** If this feature creates/reuses `OperationRun` records or affects
operations feedback (toasts, progress widget, DB notifications, Monitoring → Operations, run detail), the spec MUST
explicitly confirm:
- Three surfaces only (toast + progress widget + DB notification) — no feature-specific patterns
- DB is source of truth: UI renders from `operation_runs` + structured fields (`metrics`, `reason_code`, `message`)
- Labels come from a central OperationCatalog (no embedded labels/strings in feature code)
- “View run” links always target the canonical route (Monitoring → Operations → Run Detail)
- Dedupe/noise control (max 1 queued toast; exactly 1 terminal DB notification; no “running” notifications)
- Calm polling constraints (no polling while modals are open; pause when tab hidden; stop on terminal)
- Test invariants for notifications, summary whitelist, and canonical navigation

### Assumptions

- The application already records tenant-scoped operations as OperationRuns.
- “Monitoring → Operations → Run Detail” is the canonical destination for viewing a run.
- Operation feedback is intended for tenant admins with access to Monitoring/Operations.
- The progress widget is tenant-wide (within the current tenant) and respects the same access constraints as Monitoring/Operations.
- The constitution’s “metrics” terminology maps to `operation_runs.summary_counts` for this rollout (no schema rename required).
- Persistent notifications are user-scoped to the run initiator; tenant-wide audit remains the Monitoring → Operations hub.
- Queued feedback is provided via toast only; persistent DB notifications are terminal-only.
- The constitution (v1.3.0) is the authoritative definition for copy/behavior; feature-specific variants are not allowed.

### Dependencies

- A single shared OperationCatalog exists and can be treated as the source of truth for operation labels.
- A canonical “View run” helper can be used by all operation feedback surfaces.
- Existing operation producers can be migrated without changing the operation status model.

### Functional Requirements

**FR-001 (Three surfaces only)**: The system MUST express operation feedback via exactly three surfaces: toast (intent), progress widget (active awareness), and persistent notification (audit + terminal outcome).

**FR-002 (OperationCatalog label source of truth)**: The system MUST provide a central OperationCatalog mapping `operation_type → label`, and all operation labels shown in UI MUST be resolved from it.

**FR-003 (Fail-fast catalog coverage)**: The system MUST fail fast (via automated checks) if any code-produced `operation_type` used in the application is not present in the OperationCatalog.

**FR-003b (Runtime behavior for unknown types)**: If an existing run record contains an unknown `operation_type`, the UI MUST render the label `Unknown operation` (and MUST NOT render the raw type string).

**FR-004 (Canonical “View run” everywhere)**: The system MUST generate “View run” links exclusively via one canonical helper, and that destination MUST always be Monitoring → Operations → Run Detail.

**FR-005 (Centralized presentation)**: The system MUST centralize the user-facing copy for operation toasts, widget status text, and persistent notifications. Feature code MUST NOT define operation feedback strings.

**FR-006 (Toast queued-only)**: The system MUST show a toast only when a run is created or reused in `queued`, MUST NOT show toasts for `running` or any terminal outcome, MUST auto-dismiss within 3–5 seconds, and MUST use:
- Title: `{OperationLabel} queued`
- Body: `Running in the background.`

**FR-007 (Progress widget queued/running only)**: The progress widget MUST display only active runs (`queued`, `running`) for the current tenant (not just the initiating user) and MUST never display terminal runs. Status text MUST be exactly `Queued` or `Running`.

**FR-008 (Progress calculation)**: The widget MUST show a deterministic progress percentage only when numeric `total` and `processed` counts are present and valid in `summary_counts`. Otherwise it MUST show indeterminate progress. Deterministic progress MUST be clamped to 0–100%.

**FR-009 (Widget run limit + overflow)**: The widget MUST show at most 5 active runs. If more exist, it MUST show a single overflow row `+N more operations running` linking to the operations index.

**FR-010 (Terminal persistent notifications only)**: Each run MUST produce exactly one persistent notification when it becomes terminal (`succeeded|partial|failed`).

**FR-010b (Notification audience)**: Terminal persistent notifications MUST be delivered only to the run initiator (no tenant-wide notification fan-out).

**FR-010c (No queued DB notifications)**: The system MUST NOT emit queued DB notifications as part of this rollout. Queued feedback MUST be provided via the queued-only toast surface.

**FR-010d (Status normalization for Ops-UX (compatibility))**: Ops-UX surfaces MUST render terminal outcomes using the canonical statuses: `succeeded | partial | failed`.

If a run record contains legacy values (e.g. `status=completed` with `outcome=partially_succeeded`), the UI MUST normalize as follows:

- completed + outcome=succeeded            -> succeeded
- completed + outcome=partially_succeeded -> partial
- failed (or outcome=failed)              -> failed

This is a presentation/normalization rule for the rollout; it does not mandate a schema refactor.

**FR-011 (Notification copy templates)**: Persistent notifications MUST use canonical titles and bodies:
- succeeded: `{OperationLabel} completed` / `Completed successfully.`
- partial: `{OperationLabel} completed with warnings` / `Completed with warnings.`
- failed: `{OperationLabel} failed` / `Failed.` + optional sanitized message

**FR-012 (Metrics/summaries are structured and safe)**: Operation summary counts (`operation_runs.summary_counts`) MUST be flat, numeric-only, and limited to whitelisted keys. Summary rendering MUST use only normalized/validated `summary_counts` and MUST NOT render free-text.

### Canonical allowed summary keys (single source of truth)

The following keys are the ONLY allowed summary keys for Ops-UX rendering:
`total, processed, succeeded, failed, skipped, compliant, noncompliant, unknown, created, updated, deleted, items, tenants, high, medium, low, findings_created, findings_resolved, findings_reopened, findings_unchanged, errors_recorded, posture_score, report_created, report_deduped, alert_events_produced, finding_count, report_count, operation_count`

All normalizers/renderers MUST reference this canonical list (no duplicated lists in multiple places).

**FR-013 (Calm polling policy)**: Polling is allowed only for the progress widget (when visible) and run detail (only while active). Polling MUST pause when a modal is open, pause when the tab is hidden, follow the backoff schedule (1s for first 10s, then 5s, then 10s after 60s), and stop immediately on terminal.

**FR-014 (Migration scope)**: All existing operation feedback across the application MUST be migrated to these shared rules without introducing new operation types or changing the run status model.

### Key Entities *(include if feature involves data)*

- **OperationRun**: A tenant-scoped record of an operation’s type, status, timestamps, outcome, and structured metrics.
- **OperationCatalog**: A central registry of valid `operation_type` values and their user-facing labels.
- **Operation Feedback Surfaces**:
  - **Toast**: short-lived intent confirmation for queued runs.
  - **Progress Widget**: live awareness of active runs.
  - **Persistent Notification**: audit + terminal outcome notification with canonical “View run”.

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: 100% of tenant-scoped operations use exactly the three approved surfaces (toast, widget, persistent notification), with no feature-specific alternatives.
- **SC-002**: 100% of “View run” links resolve to Monitoring → Operations → Run Detail.
- **SC-003**: For terminal runs, 100% produce exactly one persistent notification (no duplicates) and 0% produce “running” notifications.
- **SC-004**: For active runs, the progress widget returns 0 terminal runs and shows strict status text (`Queued`/`Running`) 100% of the time.
- **SC-005**: Summary rendering shows only whitelisted numeric keys; invalid metrics render no summary in 100% of tested cases.
- **SC-006**: Automated guards fail fast when any new `operation_type` is not registered in OperationCatalog.
