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
158 lines
12 KiB
Markdown
158 lines
12 KiB
Markdown
# Feature Specification: BackupSchedule Retention & Lifecycle (Archive/Restore/Force Delete)
|
||
|
||
**Feature Branch**: `091-backupschedule-retention-lifecycle`
|
||
**Created**: 2026-02-13
|
||
**Status**: Draft
|
||
**Input**: User description: "Introduce an enterprise lifecycle for tenant backup schedules: Archive (reversible) as the default destructive action, Restore, and Force Delete (irreversible) as a highly privileged retention/admin tool. Enforce tenant boundary (non-member → 404), capability-first RBAC (member without capability → 403), confirmation for destructive actions, audit events, and ensure archived schedules never execute."
|
||
|
||
## Clarifications
|
||
|
||
### Session 2026-02-13
|
||
|
||
- Q: When a BackupSchedule has historical runs referencing it, what should Force Delete do? → A: Disallow Force Delete if any historical runs exist (in this repo: `OperationRun` records linked via `BackupSchedule::operationRuns()`).
|
||
- Q: When restoring an archived schedule, what should happen to its existing enabled/disabled state? → A: Restore only un-archives; enabled/disabled stays unchanged.
|
||
- Q: Should an archived BackupSchedule record be directly accessible on its View/Edit page for authorized tenant members? → A: Yes — archived records remain accessible.
|
||
- Q: For BackupSchedule, what should be the primary inspect affordance from the list? → A: Edit page is the primary inspect path.
|
||
|
||
## User Scenarios & Testing *(mandatory)*
|
||
|
||
### User Story 1 - Archive a schedule safely (Priority: P1)
|
||
|
||
As a tenant admin working in the tenant panel (`/admin/t/{tenant}`), I can archive a backup schedule so it no longer runs and is removed from the default list, without permanently deleting it.
|
||
|
||
**Why this priority**: Archiving is the safe, reversible way to reduce clutter and stop execution while preserving auditability.
|
||
|
||
**Independent Test**: Create an active schedule, archive it (with confirmation), verify it no longer appears in the default list, verify it is visible under an “Archived” view, and verify an audit event exists.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** an active backup schedule for the current tenant, **When** a tenant admin archives it (confirmation required), **Then** the schedule becomes archived, is excluded from the default list, and an audit event is recorded.
|
||
2. **Given** an archived backup schedule, **When** the scheduler/runner evaluates schedules for execution, **Then** archived schedules are ignored and MUST NOT be executed.
|
||
|
||
---
|
||
|
||
### User Story 2 - Restore an archived schedule (Priority: P2)
|
||
|
||
As a tenant admin, I can restore an archived backup schedule back to active so it can run again and returns to the default list.
|
||
|
||
**Why this priority**: Restoration keeps archiving low-risk and supports operational recovery.
|
||
|
||
**Independent Test**: Archive a schedule, restore it, verify it appears in the default list again, and verify an audit event exists.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** an archived backup schedule for the current tenant, **When** a tenant admin restores it (confirmation is optional and MUST NOT be required), **Then** the schedule becomes active, appears in the default list, and an audit event is recorded.
|
||
2. **Given** an archived schedule that was previously disabled, **When** it is restored, **Then** it remains disabled (restore does not change enabled/disabled state).
|
||
|
||
---
|
||
|
||
### User Story 3 - Force delete as a privileged retention tool (Priority: P3)
|
||
|
||
As a highly privileged tenant admin, I can force delete an archived backup schedule to permanently remove it when governance/retention requires deletion.
|
||
|
||
**Why this priority**: Permanent deletion must exist, but must be strictly gated to prevent accidental loss.
|
||
|
||
**Independent Test**: Archive a schedule, force delete it (confirmation required), verify it no longer exists, and verify an audit event exists.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** an archived backup schedule for the current tenant, **When** a tenant admin force deletes it (confirmation required), **Then** the schedule is permanently removed and an audit event is recorded.
|
||
2. **Given** an active backup schedule, **When** a tenant admin attempts to force delete it, **Then** the operation is blocked (not available in UI and rejected server-side).
|
||
3. **Given** an archived schedule that has one or more historical backup runs, **When** a tenant admin attempts to force delete it, **Then** the operation is blocked with a clear message that historical runs prevent permanent deletion (e.g., “Cannot force delete backup schedule” with an explanation that schedules referenced by historical runs cannot be removed).
|
||
|
||
---
|
||
|
||
### Edge Cases
|
||
|
||
- Archiving an already archived schedule is idempotent (no state change) and returns a success response with a clear “already archived” message; no audit event is written.
|
||
- Restoring an already active schedule is idempotent (no state change) and returns a success response with a clear “already active” message; no audit event is written.
|
||
- If a schedule is archived after a run is queued but before it executes, the queued run is skipped (no backup is performed) and is recorded as “skipped/blocked” with a clear reason (“Schedule archived”).
|
||
- Restoring a schedule MUST NOT implicitly enable or disable it.
|
||
- Force delete is permitted only for archived schedules, even if a crafted request is sent.
|
||
- Force delete is blocked when it would break historical traceability or violate data integrity (clear, user-friendly message).
|
||
- Force delete is blocked when historical runs exist for the schedule.
|
||
- Cross-tenant access to a schedule MUST be deny-as-not-found (404 semantics), not a leak of existence.
|
||
- Non-member / not entitled → 404; member without capability → 403.
|
||
- Concurrent lifecycle changes keep consistent state and record audit entries with actor/outcome.
|
||
|
||
## Requirements *(mandatory)*
|
||
|
||
**Constitution alignment (required):** If this feature introduces any Microsoft Graph calls, any write/change behavior,
|
||
or any long-running/queued/scheduled work, the spec MUST describe contract registry updates, safety gates
|
||
(preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests.
|
||
If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries.
|
||
|
||
**Constitution alignment (RBAC-UX):** If this feature introduces or changes authorization behavior, the spec MUST:
|
||
- state which authorization plane(s) are involved (tenant `/admin/t/{tenant}` vs platform `/system`),
|
||
- ensure any cross-plane access is deny-as-not-found (404),
|
||
- explicitly define 404 vs 403 semantics:
|
||
- non-member / not entitled to workspace scope OR tenant scope → 404 (deny-as-not-found)
|
||
- member but missing capability → 403
|
||
- describe how authorization is enforced server-side (Gates/Policies) for every mutation/operation-start/credential change,
|
||
- reference the canonical capability registry (no raw capability strings; no role-string checks in feature code),
|
||
- ensure global search is tenant-scoped and non-member-safe (no hints; inaccessible results treated as 404 semantics),
|
||
- ensure destructive-like actions require confirmation,
|
||
- include at least one positive and one negative authorization test, and note any RBAC regression tests added/updated.
|
||
|
||
**Constitution alignment (OPS-EX-AUTH-001):** OIDC/SAML login handshakes may perform synchronous outbound HTTP (e.g., token exchange)
|
||
on `/auth/*` endpoints without an `OperationRun`. This MUST NOT be used for Monitoring/Operations pages.
|
||
|
||
**Constitution alignment (BADGE-001):** If this feature changes status-like badges (status/outcome/severity/risk/availability/boolean),
|
||
the spec MUST describe how badge semantics stay centralized (no ad-hoc mappings) and which tests cover any new/changed values.
|
||
|
||
**Constitution alignment (Action Surfaces):** If this feature adds or modifies any admin UI Resource / RelationManager / Page,
|
||
the spec MUST include a “UI Action Matrix” (see below) and explicitly state whether the Action Surface Contract is satisfied.
|
||
If the contract is not satisfied, the spec MUST include an explicit exemption with rationale.
|
||
|
||
**Action Surface Contract status:** Satisfied. Primary inspect/edit affordance is present; destructive actions are grouped, capability-gated, and confirmed.
|
||
|
||
### Functional Requirements
|
||
|
||
- **FR-001**: System MUST support an archived lifecycle state for tenant-scoped backup schedules.
|
||
- **FR-002**: The default schedule list view MUST show active schedules only (archived excluded) and MUST provide a way to view archived schedules.
|
||
- **FR-003**: Archive MUST require confirmation; restore confirmation is optional and MUST NOT be required.
|
||
- **FR-004**: Force delete MUST require confirmation and MUST be permitted only after a schedule is archived.
|
||
- **FR-004a**: Restore MUST only reverse archival and MUST NOT change the schedule’s enabled/disabled state.
|
||
- **FR-005**: Archived schedules MUST NOT be eligible for execution by any scheduler/runner.
|
||
- **FR-006**: Each lifecycle mutation (archive, restore, force delete) MUST write an audit event including tenant context, actor, target schedule identity, action identifier, timestamp, and outcome.
|
||
- **FR-007**: Authorization MUST be enforced on the tenant plane (`/admin/t/{tenant}`) using the canonical capability registry:
|
||
- cross-tenant access → 404 (deny-as-not-found)
|
||
- non-member/not entitled → 404
|
||
- member without capability → 403
|
||
- **FR-008**: Bulk destructive actions for this feature MUST NOT be provided (Bulk Actions: None).
|
||
- **FR-009**: Force delete MUST be blocked when it would break historical traceability or violate data integrity, with a clear, user-friendly explanation.
|
||
- **FR-010**: Audit action identifiers MUST be stable and use these event names:
|
||
- backup_schedule.archived
|
||
- backup_schedule.restored
|
||
- backup_schedule.force_deleted
|
||
- **FR-011**: Force delete MUST be disallowed when any historical runs exist for the schedule.
|
||
- **FR-012**: Archived schedules MUST remain directly accessible on their record pages for authorized tenant members (show archived state and offer Restore / Force Delete per RBAC).
|
||
- **FR-013**: The primary inspect affordance from the schedules list MUST open the Edit page (no dedicated View page is required).
|
||
|
||
## UI Action Matrix *(mandatory when Filament is changed)*
|
||
|
||
If this feature adds/modifies any admin UI Resource / RelationManager / Page, fill out the matrix below.
|
||
|
||
For each surface, list the exact action labels, whether they are destructive (confirmation? typed confirmation?),
|
||
RBAC gating (capability + enforcement helper), and whether the mutation writes an audit log.
|
||
|
||
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||
|---|---|---|---|---|---|---|---|---|---|---|
|
||
| BackupSchedule Resource | tenant panel (`/admin/t/{tenant}`) | Create Schedule | Primary inspect opens Edit page | Edit, More | None | Create Schedule | Archive / Restore / Force Delete | Save, Cancel | Yes | Default list: active only. Filter/view for “Archived”. Archived records remain directly accessible for authorized members (show “Archived” state + offer Restore / Force Delete per RBAC). “More” contains Archive/Restore/Force Delete. Archive + Force Delete require confirmation. Force Delete only visible/enabled when record is archived AND user has force-delete capability. Non-member → 404; member without capability → 403 (UI disabled with guidance). |
|
||
|
||
### Key Entities *(include if feature involves data)*
|
||
|
||
- **BackupSchedule**: A tenant-scoped schedule definition that can be active or archived; archived schedules must not run.
|
||
- **OperationRun**: A historical record of a backup execution; must remain reviewable and attributable even if the schedule is archived or later permanently removed.
|
||
- **Audit Event**: An immutable record describing who performed archive/restore/force delete, in which tenant context, and when.
|
||
|
||
## Success Criteria *(mandatory)*
|
||
|
||
### Measurable Outcomes
|
||
|
||
- **SC-001**: Tenant admins can archive a schedule in under 30 seconds, including required confirmation.
|
||
- **SC-002**: Archived schedules are never executed by the scheduler/runner.
|
||
- **SC-003**: Audit events exist for 100% of lifecycle mutations (archive, restore, force delete) and are tenant-scoped.
|
||
- **SC-004**: The default schedule list remains uncluttered (archived excluded by default) while archived items remain discoverable.
|
||
|