# Phase 1 — Data Model (096 Ops Polish Bundle)

No new tables are required. The feature reuses existing operational ledger and seed data structures.

## Entity: `operation_runs` (tenant-scoped and workspace-scoped)

**Purpose:** Canonical ledger for background operations (status, timestamps, outcomes, counters, failures).

**Key fields (existing):**
- `id` (PK)
- `workspace_id` (FK, required)
- `tenant_id` (FK, nullable)
  - Tenant-scoped operations: non-null
  - Workspace-scoped operations (housekeeping): null
- `user_id` (FK, nullable) — initiator user when applicable
- `initiator_name` (string) — stored label for system/user
- `type` (string) — operation type (e.g., `assignments.fetch`, `assignments.restore`, `ops.reconcile_adapter_runs`)
- `status` (string) — `queued` | `running` | `completed` (see `OperationRunStatus`)
- `outcome` (string) — `pending` | `succeeded` | `failed` (see `OperationRunOutcome`)
- `run_identity_hash` (string) — deterministic identity hash
- `summary_counts` (json/jsonb array) — normalized counters (keys constrained by `OperationCatalog::allowedSummaryKeys()`)
- `failure_summary` (json/jsonb array) — entries like `{ code: string, message: string }` (sanitized, stable)
- `context` (json/jsonb object) — non-secret context, may include selection metadata
- `started_at`, `completed_at` (timestamps)
- `created_at`, `updated_at`

**Constraints / indexes (existing):**
- Active-run dedupe is enforced at the DB layer using partial unique indexes:
  - Tenant runs: unique on `(tenant_id, run_identity_hash)` for active statuses.
  - Workspace runs: unique on `(workspace_id, run_identity_hash)` when `tenant_id IS NULL` for active statuses.

**Feature usage:**
- Assignment jobs: ensure a stable `run_identity_hash` based on the clarified identity rule; persist `summary_counts` at terminal completion.
- Reconcile housekeeping job: create/reuse a workspace-scoped run (tenant_id null) and persist success/failure + summary counts.

## Entity: `tenants`

**Purpose:** Tenant scope boundary; owns tenant-scoped `operation_runs` and policies.

**Field (in scope):**
- `external_id` (string)

**Feature requirement:**
- Seeded tenants must have `external_id` set to a UUID v4 string.

## Notes on validation / sanitization

- Summary counters must be normalized/sanitized before persistence (existing `OperationRunService` behavior).
- Failure summaries must store stable reason codes + sanitized messages (no secrets/tokens/PII/raw payload dumps).
