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