TenantAtlas/specs/086-retire-legacy-runs-into-operation-runs/research.md
2026-02-10 01:35:24 +01:00

6.9 KiB

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.