TenantAtlas/specs/098-settings-slices-v1-backup-drift-ops/plan.md
ahmido c57f680f39 feat: Workspace settings slices v1 (backup, drift, operations) (#120)
Implements Spec 098: workspace-level settings slices for Backup retention, Drift severity mapping, and Operations retention/threshold.

Spec
- specs/098-settings-slices-v1-backup-drift-ops/spec.md

What changed
- Workspace Settings page: grouped Backup/Drift/Operations sections, unset-input UX w/ helper text, per-setting reset actions (confirmed)
- Settings registry: adds/updates validation + normalization (incl. drift severity mapping normalization to lowercase)
- Backup retention: adds workspace default + floor clamp; job clamps effective keep-last up to floor
- Drift findings: optional workspace severity mapping; adds `critical` severity support + badge mapping
- Operations pruning: retention computed per workspace via settings; scheduler unchanged; stuck threshold is storage-only

Safety / Compliance notes
- Filament v5 / Livewire v4: no Livewire v3 usage; relies on existing Filament v5 + Livewire v4 stack
- Provider registration unchanged (Laravel 11+/12 uses bootstrap/providers.php)
- Destructive actions: per-setting reset uses Filament actions with confirmation
- Global search: not affected (no resource changes)
- Assets: no new assets registered; no `filament:assets` changes

Tests
- vendor/bin/sail artisan test --compact tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php \
  tests/Feature/SettingsFoundation/WorkspaceSettingsViewOnlyTest.php \
  tests/Feature/BackupScheduling/BackupScheduleLifecycleTest.php \
  tests/Feature/Drift/DriftPolicySnapshotDriftDetectionTest.php \
  tests/Feature/Scheduling/PruneOldOperationRunsScheduleTest.php \
  tests/Unit/Badges/FindingBadgesTest.php

Formatting
- vendor/bin/sail bin pint --dirty

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #120
2026-02-16 03:18:33 +00:00

6.4 KiB

Implementation Plan: 098 — Settings Slices v1 (Backup + Drift + Operations)

Branch: 098-settings-slices-v1-backup-drift-ops | Date: 2026-02-16 | Spec: specs/098-settings-slices-v1-backup-drift-ops/spec.md Input: Feature specification from specs/098-settings-slices-v1-backup-drift-ops/spec.md

Summary

Extend the existing Settings Foundation to expose five additional workspace-level keys on the Workspace Settings page, with strict validation, per-setting reset-to-default (confirmed), and audit logging per key changed.

Apply those settings to three behavior paths:

  • Backup retention: enforce a workspace-configurable retention default and a workspace-configurable retention floor (effective values clamped up to the floor).
  • Drift severities: allow workspace-configurable finding_type → severity mapping (default medium, values normalized).
  • Operations: allow workspace-configurable operation run retention days for pruning and store a “stuck run threshold” value (storage only; no new auto-remediation behavior).

Technical Context

Language/Version: PHP 8.4 (Laravel 12) Primary Dependencies: Filament v5, Livewire v4, Laravel Sail Storage: PostgreSQL (Sail local) Testing: Pest v4 (PHPUnit 12 runner) Target Platform: Web app (Filament admin panel) Project Type: Laravel monolith (Filament pages + services + jobs) Performance Goals: Settings resolution should remain request-local cached (no repeated DB reads per key within a request). Constraints:

  • DB-only rendering for settings UI (no Graph calls as a render side-effect)
  • Strict workspace isolation (non-member 404)
  • Capability-gated mutations (member without capability 403)
  • Destructive-like resets require confirmation
  • Audit each successful mutation; multi-key save produces one audit entry per key changed

Baselines to Preserve (When Unset)

These are the “no settings configured” behaviors that must remain unchanged and covered by regression tests.

Record the baseline values explicitly so “no change” remains mechanically verifiable over time:

  • Backup retention default:
    • When a schedule has no retention_keep_last, the job resolves backup.retention_keep_last_default and falls back to 30 if unresolved/non-numeric.
    • Current clamp behavior: values < 1 are clamped up to 1.
    • Source: app/Jobs/ApplyBackupScheduleRetentionJob.php.
    • Current system default: backup.retention_keep_last_default is 30 in app/Support/Settings/SettingsRegistry.php.
  • Drift default severity:
    • Drift findings currently default to Finding::SEVERITY_MEDIUM.
    • Source: app/Services/Drift/DriftFindingGenerator.php.
  • Operation run pruning:
    • Prune job default retention is 90 days (new PruneOldOperationRunsJob() with default constructor argument).
    • Source: app/Jobs/PruneOldOperationRunsJob.php and schedule in routes/console.php.
  • “Stuck run threshold”:
    • No baseline behavior exists today; for this feature it remains storage-only (must not introduce auto-remediation).

Constitution Check

GATE: Must pass before implementation. Re-check after design/edits.

  • DB-only rendering: PASS (Workspace Settings UI is DB-only).
  • Graph contract path: PASS (no Graph calls introduced).
  • RBAC-UX semantics: PASS-BY-DESIGN (non-member 404; member missing capability 403; server-side still authoritative).
  • Destructive-like confirmation: PASS-BY-DESIGN (per-setting reset actions must require confirmation).
  • Auditability: PASS-BY-DESIGN (settings writes are audited per key).
  • Filament Action Surface Contract: PASS (page-level action surface is explicitly declared in spec via UI Action Matrix).

Project Structure

Documentation (this feature)

specs/098-settings-slices-v1-backup-drift-ops/
├── plan.md
├── spec.md
├── tasks.md
└── checklists/
    └── requirements.md

Source Code (repository root)

app/
├── Filament/Pages/Settings/WorkspaceSettings.php
├── Jobs/
│   ├── ApplyBackupScheduleRetentionJob.php
│   └── PruneOldOperationRunsJob.php
├── Models/Finding.php
├── Services/Drift/DriftFindingGenerator.php
└── Support/
    ├── Badges/Domains/FindingSeverityBadge.php
    └── Settings/SettingsRegistry.php

routes/console.php

tests/Feature/
├── BackupScheduling/BackupScheduleLifecycleTest.php
├── Drift/DriftPolicySnapshotDriftDetectionTest.php
├── Scheduling/PruneOldOperationRunsScheduleTest.php
└── SettingsFoundation/
    ├── WorkspaceSettingsManageTest.php
    ├── WorkspaceSettingsViewOnlyTest.php
    └── WorkspaceSettingsNonMemberNotFoundTest.php

Structure Decision: Use existing Laravel structure only. No new top-level directories.

Plan Phases

Phase 0 — Align the Registry + UI primitives (shared)

  • Ensure Settings registry rules match the spec (notably the backup max bounds).
  • Refactor the Workspace Settings page to support:
    • Per-setting reset actions (no global reset)
    • Unset inputs with helper text showing the effective/default value
    • Save semantics that can “unset” (delete override) for a single key
  • Update the existing SettingsFoundation tests to reflect the new UX primitives.

Phase 1 — US1 Backup slice (MVP)

  • Add backup.retention_min_floor to the registry and UI.
  • Apply floor clamping in the retention job to both schedule overrides and workspace defaults.
  • Add/extend BackupScheduling + SettingsFoundation tests to cover both baseline behavior and clamping.

Phase 2 — US2 Drift slice

  • Add drift.severity_mapping to the registry and UI with strict JSON shape validation.
  • Normalize severity values to lowercase on save; reject unsupported severities.
  • Apply mapping in drift finding generation with default medium fallback.
  • Add drift tests for default + mapped severity, and settings validation tests.

Phase 3 — US3 Operations slice

  • Add operations keys to the registry and UI.
  • Wire pruning job to use the configured retention days when set (baseline otherwise).
  • Ensure “stuck threshold” is stored only (no new behavior in this feature).
  • Add pruning job/schedule tests and settings persistence tests.

Phase 4 — Format + focused regression

  • Run Pint for touched files.
  • Run the focused test set for SettingsFoundation + BackupScheduling + Drift + Scheduling.

Complexity Tracking

No constitution violations are required for this feature.