# Phase 1 Data Model: Ops-UX Constitution Rollout (v1.3.0 Alignment) (055)

**Date**: 2026-01-18

This feature is a migration: it standardizes how existing `operation_runs` records are presented via three UX surfaces.

## Entities

### 1) OperationRun (existing)

**Source**: `operation_runs` table

**Fields (relevant to this feature)**

- `id` (int)
- `tenant_id` (int)
- `user_id` (int|null) — initiator user
- `initiator_name` (string)
- `type` (string) — operation type identifier
- `status` (string) — active: `queued|running`, terminal: `completed`
- `outcome` (string) — `pending|succeeded|partially_succeeded|failed|cancelled` (existing vocabulary)
- `started_at` (timestamp|null)
- `completed_at` (timestamp|null)
- `summary_counts` (jsonb) — canonical “metrics” source for this rollout
- `failure_summary` (jsonb) — array of failures with sanitized messages
- `context` (jsonb) — structured context used for related links

### Status / Outcome (UX-canonical)

The Ops-UX surfaces (toast/widget/db notifications) use the canonical statuses:

- active: `queued` | `running`
- terminal: `succeeded` | `partial` | `failed`

### Legacy / compatibility mapping (if older records exist)

Some existing records may use legacy fields/values (for example `status=completed` with an `outcome`).
These MUST be normalized for UX rendering as follows:

Normalization rules:

- `status=completed` AND `outcome=succeeded`            -> `terminal=succeeded`
- `status=completed` AND `outcome=partially_succeeded` -> `terminal=partial`
- `status=failed` OR `outcome=failed`                  -> `terminal=failed`

Notes:

- The Monitoring UI MUST remain usable if legacy values exist.
- Normalization is a presentation concern for Ops-UX; storage may remain unchanged during rollout.

### 2) OperationCatalog (new/standardized)

**Purpose**: single source of truth for operation labels.

**Fields**

- `operation_type` (string) → `label` (string)

**Rules**

- Any code-produced operation type must be registered (CI guard).
- Unknown types from historical data render as `Unknown operation`.

### 3) OperationRunUrl (new helper)

**Purpose**: canonical URL generator for “View run”.

**Rule**: all “View run” links must route to Monitoring → Operations → Run Detail.

### 4) OperationUxPresenter (new presenter/builder)

**Purpose**: centralized presentation for all three surfaces.

**Responsibilities**

- Toast copy for queued intent
- Progress widget row presentation (label, status text, optional progress)
- Terminal DB notification title/body/status and optional summary rendering

## Validation rules

### Summary counts (`operation_runs.summary_counts`)

- Must be a flat object/dictionary.
- Allowed keys only:
  - `total, processed, succeeded, failed, skipped, created, updated, deleted, items, tenants`
- Values must be numeric and normalized to integers.
- Invalid keys/values are ignored; if nothing valid remains, no summary is rendered.

### Failure messages

- Short, sanitized, no secrets/tokens, no payload dumps.
- Used only for terminal failure notification body as optional suffix.

## Relationships

- `OperationRun` belongs to `Tenant`
- `OperationRun` belongs to `User` (initiator) (nullable)

## Derived fields for presentation

- `OperationLabel` = `OperationCatalog::label(type)` (or `Unknown operation`)
- `UxStatus` = `Queued|Running|Completed|Partial|Failed` (derived)
- `ProgressPercent` (optional) = `processed / total` only when both exist and are valid
