# 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 Filament’s `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 constitution’s 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`.

