Implements Spec 091 “BackupSchedule Retention & Lifecycle (Archive/Restore/Force Delete)”.
- BackupSchedule lifecycle:
- Archive (soft delete) with confirmation; restores via Restore action; Force delete with confirmation and strict gating.
- Force delete blocked when historical runs exist.
- Archived schedules never dispatch/execute (dispatcher + job guard).
- Audit events emitted for archive/restore/force delete.
- RBAC UX semantics preserved (non-member hidden/404; member w/o capability disabled + server-side 403).
- Filament UX contract update:
- Create CTA placement rule across create-enabled list pages:
- Empty list: only large centered empty-state Create CTA.
- Non-empty list: only header Create action.
- Tests added/updated to enforce the rule.
Verification:
- `vendor/bin/sail bin pint --dirty`
- Focused tests: BackupScheduling + RBAC enforcement + EmptyState CTAs + Create CTA placement
Notes:
- Filament v5 / Livewire v4 compliant.
- Manual quickstart verification in `specs/091-backupschedule-retention-lifecycle/quickstart.md` remains to be checked (T031).
Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #109
3.5 KiB
3.5 KiB
Research — Spec 091 (BackupSchedule Retention & Lifecycle)
Decisions
Decision 1 — Use Eloquent SoftDeletes for “Archived” state
- Chosen: Add
SoftDeletestoApp\Models\BackupScheduleand adddeleted_attobackup_schedules. - Rationale: The repo already models reversible “archive” lifecycles using SoftDeletes (e.g.,
BackupSet) and Filament’sTrashedFilter. SoftDeletes also makes “archived schedules never execute” the default behavior in scheduler queries. - Alternatives considered:
archived_atboolean/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.
- Scheduler dispatch path (
- 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:
UiEnforcementimplements 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).
- Inline
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
OperationRunwithcontext->backup_schedule_id. - Alternatives considered:
- Use
backup_schedule_runstable → not currently used by the scheduling runtime (it exists in migrations but the runtime path isOperationRun).
- Use
Decision 6 — Audit logging via existing tenant audit logger
- Chosen: Use
App\Services\Intune\AuditLoggerwith stable action identifiers:backup_schedule.archivedbackup_schedule.restoredbackup_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\BackupScheduleDispatcherand streams schedules viacursor(). - Existing tenant capabilities include:
Capabilities::TENANT_BACKUP_SCHEDULES_MANAGECapabilities::TENANT_DELETE
- Filament already uses SoftDeletes patterns with
TrashedFilterand lifecycle actions inBackupSetResource.