TenantAtlas/specs/096-ops-polish-assignment-dedupe-system-tracking/data-model.md
ahmido 03127a670b Spec 096: Ops polish (assignment summaries + dedupe + reconcile tracking + seed DX) (#115)
Implements Spec 096 ops polish bundle:

- Persist durable OperationRun.summary_counts for assignment fetch/restore (final attempt wins)
- Server-side dedupe for assignment jobs (15-minute cooldown + non-canonical skip)
- Track ReconcileAdapterRunsJob via workspace-scoped OperationRun + stable failure codes + overlap prevention
- Seed DX: ensure seeded tenants use UUID v4 external_id and seed satisfies workspace_id NOT NULL constraints

Verification (local / evidence-based):
- `vendor/bin/sail artisan test --compact tests/Feature/Operations/AssignmentRunSummaryCountsTest.php tests/Feature/Operations/AssignmentJobDedupeTest.php tests/Feature/Operations/ReconcileAdapterRunsJobTrackingTest.php tests/Feature/Seed/PoliciesSeederExternalIdTest.php`
- `vendor/bin/sail bin pint --dirty`

Spec artifacts included under `specs/096-ops-polish-assignment-dedupe-system-tracking/` (spec/plan/tasks/checklists).

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #115
2026-02-15 20:49:38 +00:00

50 lines
2.5 KiB
Markdown

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