TenantAtlas/specs/091-backupschedule-retention-lifecycle/research.md
2026-02-14 14:43:53 +01:00

3.5 KiB
Raw Blame History

Research — Spec 091 (BackupSchedule Retention & Lifecycle)

Decisions

Decision 1 — Use Eloquent SoftDeletes for “Archived” state

  • Chosen: Add SoftDeletes to App\Models\BackupSchedule and add deleted_at to backup_schedules.
  • Rationale: The repo already models reversible “archive” lifecycles using SoftDeletes (e.g., BackupSet) and Filaments TrashedFilter. SoftDeletes also makes “archived schedules never execute” the default behavior in scheduler queries.
  • Alternatives considered:
    • archived_at boolean/timestamp field → rejected (would require manual scoping in every query and more room for mistakes).

Decision 2 — Archived schedules MUST never be dispatched nor executed

  • Chosen:
    • Scheduler dispatch path (BackupScheduleDispatcher::dispatchDue) relies on default SoftDeletes query scoping (archived schedules excluded).
    • Job execution path (RunBackupScheduleJob) will add an explicit guard to skip if the schedule is archived at execution time.
  • Rationale: Prevents race conditions where a schedule is archived after dispatch but before job execution.
  • Alternatives considered:
    • Only guard in dispatcher → rejected (not sufficient for race conditions).

Decision 3 — Reuse central RBAC enforcement helpers for lifecycle actions

  • Chosen: Wrap Filament actions using App\Support\Rbac\UiEnforcement.
  • Rationale: UiEnforcement implements the constitutions RBAC-UX semantics (non-member hidden + server guard; member missing capability disabled + server guard) and validates capabilities against the canonical registry.
  • Alternatives considered:
    • Inline ->visible(), ->disabled(), ->authorize() scattered in the resource → rejected (drift + violates “central helper” guidance).

Decision 4 — Force delete capability and constraints

  • Chosen:
    • Force delete is available only when the schedule is archived.
    • Force delete is gated by Capabilities::TENANT_DELETE (stricter than manage).
    • Force delete is blocked if historical runs exist.
  • Rationale: Matches the spec clarification: permanent deletion must be highly privileged and must not break historical traceability.
  • Alternatives considered:
    • Allow force delete with cascading deletion of historical runs → rejected (breaks auditability).

Decision 5 — Define “historical runs exist” using existing operational records

  • Chosen: Treat “historical runs exist” as: BackupSchedule::operationRuns()->exists().
  • Rationale: In this repo, the scheduler and run lifecycle already track schedule activity via OperationRun with context->backup_schedule_id.
  • Alternatives considered:
    • Use backup_schedule_runs table → not currently used by the scheduling runtime (it exists in migrations but the runtime path is OperationRun).

Decision 6 — Audit logging via existing tenant audit logger

  • Chosen: Use App\Services\Intune\AuditLogger with stable action identifiers:
    • backup_schedule.archived
    • backup_schedule.restored
    • backup_schedule.force_deleted
  • Rationale: The repo already centralizes audit logging and sanitizes metadata.

Repo Evidence (Key Findings)

  • Scheduling dispatch is centralized in App\Services\BackupScheduling\BackupScheduleDispatcher and streams schedules via cursor().
  • Existing tenant capabilities include:
    • Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE
    • Capabilities::TENANT_DELETE
  • Filament already uses SoftDeletes patterns with TrashedFilter and lifecycle actions in BackupSetResource.