# Research (Spec 086)

This document resolves the unknowns needed to write an implementation plan for “Retire Legacy Runs Into Operation Runs”. It is based on repository inspection (no new external dependencies).

## Decisions

### 1) Canonical run viewer is already implemented; keep the route shape

- **Decision:** Use the existing tenantless canonical viewer route `admin.operations.view` (path pattern `/admin/operations/{run}`) implemented by the Filament page `TenantlessOperationRunViewer`.
- **Rationale:** This already enforces “tenantless deep link” while still doing workspace / tenant entitlement checks server-side through `Gate::authorize('view', $run)`.
- **Alternatives considered:** Create a second viewer page or route. Rejected because it would introduce duplicate UX and increase the chance of policy drift.

Repository anchors:
- Canonical viewer page: `app/Filament/Pages/Operations/TenantlessOperationRunViewer.php`
- Link helper: `app/Support/OperationRunLinks.php`
- Workspace selection middleware explicitly treats `/admin/operations/{id}` as workspace-optional: `app/Http/Middleware/EnsureWorkspaceSelected.php`

### 2) OperationRun is persisted and DB-rendered; schema supports workspace-tenant and workspace-only runs

- **Decision:** Treat `operation_runs` as the canonical persistence format for status/progress/results.
- **Rationale:** The schema already includes `workspace_id` (required) and `tenant_id` (nullable), enabling both tenant-plane and workspace-plane operations.
- **Alternatives considered:** Separate tables per operation family. Rejected because it breaks the Monitoring → Operations single source of truth principle.

Repository anchors:
- Migrations: `database/migrations/2026_01_16_180642_create_operation_runs_table.php`, `database/migrations/2026_02_04_090030_add_workspace_id_to_operation_runs_table.php`
- Model: `app/Models/OperationRun.php`

### 3) View authorization must be capability-gated per operation type (in addition to membership)

- **Decision:** Extend run viewing authorization to require the same capability used to start the operation type.
- **Rationale:** Spec 086 clarifications require: non-members get 404; members without capability get 403; and the “view” capability equals the “start” capability.
- **Implementation approach (planned):** Update `OperationRunPolicy::view()` to:
  1) Keep existing workspace membership and tenant entitlement checks (deny-as-not-found).
  2) Resolve required capability from `OperationRun->type` using a centralized mapping helper.
  3) If capability is known and tenant-scoped, enforce `403` when the member lacks it.

Repository anchors:
- Current policy (membership + tenant entitlement only): `app/Policies/OperationRunPolicy.php`
- Existing capability enforcement in start surfaces (examples):
  - Inventory sync start: `Capabilities::TENANT_INVENTORY_SYNC_RUN` in `app/Filament/Resources/InventoryItemResource/Pages/ListInventoryItems.php`
  - Directory groups sync start: `Capabilities::TENANT_SYNC` in `app/Filament/Resources/EntraGroupResource/Pages/ListEntraGroups.php`
  - Backup schedule run/retry: `Capabilities::TENANT_BACKUP_SCHEDULES_RUN` in `app/Filament/Resources/BackupScheduleResource.php`

### 4) Run identity / dedupe strategy varies by operation type

- **Decision:** Use existing `OperationRunService` helpers but apply type-specific identity rules:
  - `inventory.sync` and `directory_groups.sync`: **while-active dedupe** based on deterministic inputs (continue using `ensureRun(...)`-style identity).
  - `backup_schedule.run_now` and `backup_schedule.retry`: **unique per click** (no dedupe). Create a new run each time by including a nonce in identity inputs (e.g., UUID).
  - `backup_schedule.scheduled`: **strict dedupe** per `(backup_schedule_id, scheduled_for)`; create a new operation type `backup_schedule.scheduled` and use `ensureRunWithIdentity(...)` keyed by schedule + intended fire-time.
- **Rationale:** Matches explicit spec clarifications and protects against scheduler double-fire.
- **Alternatives considered:**
  - Keep using `ensureRun(...)` for manual runs → rejected (dedupes while active).
  - Use legacy table unique constraints as idempotency → rejected (spec requires OperationRun is canonical).

Repository anchors:
- `ensureRun(...)` and `ensureRunWithIdentity(...)`: `app/Services/OperationRunService.php`
- Existing partial unique index for active runs: `operation_runs_active_unique_*` in the migrations above.

### 5) Legacy run tables are real and currently written to; deterministic redirect requires an explicit mapping field

- **Decision:** Legacy tables remain viewable and read-only, but should not be relied on for current execution tracking.
- **Rationale:** Spec requires “no new legacy rows” for in-scope operations. Today, some start surfaces still create legacy rows (e.g., inventory/group sync, backup schedule runs).
- **Planned design:**
  - Stop creating new legacy rows as part of the cutover PRs.
  - Implement legacy “view” redirect behavior only when a record has a canonical mapping.
  - To make redirects deterministic without a backfill, add an optional `operation_run_id` FK column to legacy tables that we intend to redirect (only populated for rows created after the migration; older rows remain legacy-only view).
- **Alternatives considered:** Derive mapping by recomputing hashes and searching by time window. Rejected as non-deterministic and likely to pick the wrong run when identities collide historically.

Repository anchors (legacy tables):
- Inventory sync runs: `database/migrations/2026_01_07_142719_create_inventory_sync_runs_table.php`
- Directory group sync runs: `database/migrations/2026_01_11_120004_create_entra_group_sync_runs_table.php`
- Backup schedule runs: `database/migrations/**create_backup_schedule_runs**` (used in `BackupScheduleResource`)
- Restore runs (domain): `database/migrations/2025_12_10_000150_create_restore_runs_table.php`

### 6) DB-only rendering constraint is already enforced in Monitoring pages, but Tenant configuration forms still call Graph

- **Decision:** Remove outbound Graph calls from configuration-form search/labels by introducing cached directory role definitions and using cached directory groups.
- **Rationale:** Constitution OPS-EX-AUTH-001 + Spec 086 FR-006/FR-015 require render/search/label resolution to be DB-only.
- **Repository finding:** `TenantResource` currently queries Graph for role definitions in selector callbacks.

Repository anchors:
- Graph call sites inside UI callbacks: `app/Filament/Resources/TenantResource.php` (roleDefinitions search/label methods)

## Open items (resolved enough for planning)

- Exact schema for the new role definition cache tables and the sync job contract will be specified in `data-model.md` and implemented in Phase PR(s).
- The capability mapping for run viewing will be implemented via a centralized helper; the plan will enumerate required capabilities per in-scope operation type.
