feat: Workspace settings slices v1 (backup, drift, operations) #120
@ -0,0 +1,35 @@
|
|||||||
|
# Specification Quality Checklist: 098 — Settings Slices v1 (Backup + Drift + Operations)
|
||||||
|
|
||||||
|
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||||
|
**Created**: 2026-02-16
|
||||||
|
**Feature**: [specs/098-settings-slices-v1-backup-drift-ops/spec.md](../spec.md)
|
||||||
|
|
||||||
|
## Content Quality
|
||||||
|
|
||||||
|
- [x] No implementation details (languages, frameworks, APIs)
|
||||||
|
- [x] Focused on user value and business needs
|
||||||
|
- [x] Written for non-technical stakeholders
|
||||||
|
- [x] All mandatory sections completed
|
||||||
|
|
||||||
|
## Requirement Completeness
|
||||||
|
|
||||||
|
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||||
|
- [x] Requirements are testable and unambiguous
|
||||||
|
- [x] Success criteria are measurable
|
||||||
|
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||||
|
- [x] All acceptance scenarios are defined
|
||||||
|
- [x] Edge cases are identified
|
||||||
|
- [x] Scope is clearly bounded
|
||||||
|
- [x] Dependencies and assumptions identified
|
||||||
|
|
||||||
|
## Feature Readiness
|
||||||
|
|
||||||
|
- [x] All functional requirements have clear acceptance criteria
|
||||||
|
- [x] User scenarios cover primary flows
|
||||||
|
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||||
|
- [x] No implementation details leak into specification
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Validation pass: 1/1
|
||||||
|
- This spec intentionally includes canonical setting keys and value constraints as part of the functional contract.
|
||||||
142
specs/098-settings-slices-v1-backup-drift-ops/plan.md
Normal file
142
specs/098-settings-slices-v1-backup-drift-ops/plan.md
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# 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)
|
||||||
|
|
||||||
|
```text
|
||||||
|
specs/098-settings-slices-v1-backup-drift-ops/
|
||||||
|
├── plan.md
|
||||||
|
├── spec.md
|
||||||
|
├── tasks.md
|
||||||
|
└── checklists/
|
||||||
|
└── requirements.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
|
||||||
|
```text
|
||||||
|
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.
|
||||||
160
specs/098-settings-slices-v1-backup-drift-ops/spec.md
Normal file
160
specs/098-settings-slices-v1-backup-drift-ops/spec.md
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
# Feature Specification: 098 — Settings Slices v1 (Backup + Drift + Operations)
|
||||||
|
|
||||||
|
**Feature Branch**: `098-settings-slices-v1-backup-drift-ops`
|
||||||
|
**Created**: 2026-02-16
|
||||||
|
**Status**: Draft
|
||||||
|
**Input**: Workspace-level settings slices for Backup, Drift severity mapping, and Operations retention/thresholds with safe defaults.
|
||||||
|
|
||||||
|
## Spec Scope Fields *(mandatory)*
|
||||||
|
|
||||||
|
- **Scope**: workspace
|
||||||
|
- **Primary Routes**: Workspace Settings screen (admin UI)
|
||||||
|
- **Data Ownership**: workspace-owned settings values (key/value) + audit log entries for setting changes
|
||||||
|
- **RBAC**: workspace membership required; view vs manage capability-gated; deny-as-not-found for non-members
|
||||||
|
|
||||||
|
## Clarifications
|
||||||
|
|
||||||
|
### Session 2026-02-16
|
||||||
|
|
||||||
|
- Q: Which “Reset to default” scope do you want on the Workspace Settings UI? → A: Per-setting reset (each key individually).
|
||||||
|
- Q: When an admin clicks “Save” and multiple settings change at once, how should audit logging behave? → A: One audit log entry per key changed.
|
||||||
|
- Q: Should we enforce a validation constraint between `backup.retention_keep_last_default` and `backup.retention_min_floor`? → A: Allow any values; effective retention is always clamped to `backup.retention_min_floor`.
|
||||||
|
- Q: For settings that are currently unset (so the system uses defaults), how should the UI present them? → A: Leave input unset, show helper text with the default/effective value.
|
||||||
|
- Q: In `drift.severity_mapping`, should severity values be case-sensitive? → A: Case-insensitive; normalize to lowercase on save.
|
||||||
|
|
||||||
|
## User Scenarios & Testing *(mandatory)*
|
||||||
|
|
||||||
|
### User Story 1 - Configure backup retention defaults (Priority: P1)
|
||||||
|
|
||||||
|
As a workspace admin, I want to configure workspace-wide defaults for backup retention so that enterprise workspaces can tune policy without code/config changes.
|
||||||
|
|
||||||
|
**Why this priority**: Backup retention is a common enterprise requirement and impacts storage cost and governance.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by setting/unsetting workspace settings and verifying backup retention behavior remains unchanged by default and changes deterministically when configured.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** no workspace settings exist for backup retention, **When** a backup retention decision is made, **Then** behavior matches the current baseline (no change).
|
||||||
|
2. **Given** a workspace default retention value is configured, **When** a backup retention decision is made without a more specific override, **Then** the configured default is used.
|
||||||
|
3. **Given** a retention “floor” value is configured, **When** any calculated retention value is below the floor, **Then** the effective retention is clamped up to the floor.
|
||||||
|
4. **Given** a per-schedule override is configured, **When** that override is below the configured floor, **Then** the override is clamped up to the floor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 2 - Configure drift severity mapping (Priority: P2)
|
||||||
|
|
||||||
|
As a workspace admin, I want to map drift finding types to severities so that findings align with enterprise risk posture and triage practices.
|
||||||
|
|
||||||
|
**Why this priority**: Severity directly affects how teams triage drift; a one-size-fits-all default is too rigid for enterprise.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by saving a mapping, generating findings for mapped/unmapped types, and verifying severities are assigned correctly.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** no drift severity mapping exists, **When** a drift finding is produced, **Then** its severity defaults to “medium”.
|
||||||
|
2. **Given** a mapping exists for a specific finding type, **When** a drift finding with that type is produced, **Then** the mapped severity is applied.
|
||||||
|
3. **Given** a mapping contains an unknown/unsupported severity value, **When** an admin attempts to save it, **Then** the save is rejected and no invalid values are persisted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - Configure operations retention and stuck threshold (Priority: P3)
|
||||||
|
|
||||||
|
As a workspace admin, I want to configure retention for operations/run records and store a “stuck run” threshold so that data lifecycle and operational heuristics are workspace-tunable.
|
||||||
|
|
||||||
|
**Why this priority**: Retention policies and operational thresholds vary widely between organizations and audit requirements.
|
||||||
|
|
||||||
|
**Independent Test**: Can be tested by saving retention/threshold settings and verifying the retention cutoff used by pruning changes accordingly while no new automatic actions occur.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** no operations retention setting exists, **When** operations/run pruning is executed, **Then** the cutoff matches the current baseline (no change).
|
||||||
|
2. **Given** an operations retention setting exists, **When** pruning is executed, **Then** the cutoff is derived from the configured retention days.
|
||||||
|
3. **Given** a stuck threshold is configured, **When** the Workspace Settings screen is re-opened, **Then** the configured value is shown exactly as saved.
|
||||||
|
4. **Given** a stuck threshold is configured, **When** operations/run behavior is observed, **Then** no automatic remediation or auto-handling is performed in this feature scope.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- Attempting to save invalid numeric ranges (too low/high) is rejected and does not persist.
|
||||||
|
- Attempting to save invalid JSON (malformed) for drift mapping is rejected.
|
||||||
|
- Attempting to save drift mapping with non-string keys is rejected.
|
||||||
|
- Two admins editing settings concurrently results in deterministic persisted state (last write wins) and both attempts are auditable.
|
||||||
|
- A user without manage capability can view settings (read-only) but cannot submit changes.
|
||||||
|
- A non-member cannot discover the Workspace Settings screen or any values (deny-as-not-found).
|
||||||
|
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
**Constitution alignment (required):** This feature MUST remain DB-only for screen rendering and MUST NOT introduce Microsoft Graph calls as part of rendering or saving these settings. Any setting mutation MUST be auditable.
|
||||||
|
|
||||||
|
### Dependencies & Assumptions
|
||||||
|
|
||||||
|
- This feature depends on an existing workspace settings foundation that provides: workspace-scoped storage, consistent defaults, centralized validation, RBAC capability enforcement, and audit logging for changes.
|
||||||
|
- No tenant-specific override UI is included in v1; only workspace-wide configuration is in scope.
|
||||||
|
- The default values for all settings keys match the current baseline behavior at the time this feature ships.
|
||||||
|
|
||||||
|
**Constitution alignment (RBAC-UX):**
|
||||||
|
|
||||||
|
- Authorization planes involved: workspace-scoped admin UI.
|
||||||
|
- Non-member / not entitled to workspace scope → 404 (deny-as-not-found).
|
||||||
|
- Member but missing manage capability → 403 on mutation attempts; UI remains read-only.
|
||||||
|
|
||||||
|
**Constitution alignment (OPS/observability):** This feature does not introduce a new long-running operation type; it changes which configuration values are used by existing behavior. All admin-initiated mutations MUST produce audit log entries.
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: System MUST support workspace-level configuration for the following setting keys:
|
||||||
|
- `backup.retention_keep_last_default`
|
||||||
|
- `backup.retention_min_floor`
|
||||||
|
- `drift.severity_mapping`
|
||||||
|
- `operations.operation_run_retention_days`
|
||||||
|
- `operations.stuck_run_threshold_minutes`
|
||||||
|
- **FR-002**: System MUST preserve existing behavior when none of the above settings are configured (defaults MUST match the current baseline).
|
||||||
|
- **FR-003**: System MUST validate and reject invalid setting values, ensuring no invalid configuration is silently persisted.
|
||||||
|
- **FR-004**: Workspace Settings UI MUST present the above keys grouped into three sections (Backup, Drift, Operations) and MUST be fully functional without any external API calls.
|
||||||
|
- **FR-004a**: For any setting key that is currently unset, the UI MUST keep the input in an “unset” state and MUST display helper text indicating the default (currently effective) value.
|
||||||
|
- **FR-005**: Users with view capability MUST be able to view current effective settings but MUST NOT be able to change them.
|
||||||
|
- **FR-006**: Users with manage capability MUST be able to change settings and reset individual settings back to defaults.
|
||||||
|
- **FR-007**: Resetting a setting to default MUST be a confirmed (destructive-like) action.
|
||||||
|
- **FR-008**: System MUST write audit log entries for each settings update and reset-to-default event including: workspace identity, actor identity, setting key, old value, new value, and timestamp.
|
||||||
|
- **FR-008a**: When a single save operation changes multiple keys, the system MUST write one audit log entry per key changed.
|
||||||
|
|
||||||
|
#### Backup slice requirements
|
||||||
|
|
||||||
|
- **FR-009**: `backup.retention_keep_last_default` MUST be an integer between 1 and 365.
|
||||||
|
- **FR-010**: `backup.retention_min_floor` MUST be an integer between 1 and 365.
|
||||||
|
- **FR-011**: Effective backup retention MUST never be lower than `backup.retention_min_floor` (applies to defaults and any more specific overrides).
|
||||||
|
- **FR-011a**: The system MUST NOT reject configuration solely because `backup.retention_min_floor` exceeds `backup.retention_keep_last_default`; instead, the effective retention MUST be clamped to the floor.
|
||||||
|
|
||||||
|
#### Drift slice requirements
|
||||||
|
|
||||||
|
- **FR-012**: `drift.severity_mapping` MUST be a JSON object mapping `finding_type` (string) → severity.
|
||||||
|
- **FR-013**: Allowed severity values MUST be limited to: `low`, `medium`, `high`, `critical`.
|
||||||
|
- **FR-013a**: Severity values MUST be accepted case-insensitively and normalized to lowercase when persisted.
|
||||||
|
- **FR-014**: If a finding type has no mapping entry, severity MUST default to `medium`.
|
||||||
|
|
||||||
|
#### Operations slice requirements
|
||||||
|
|
||||||
|
- **FR-015**: `operations.operation_run_retention_days` MUST be an integer between 7 and 3650.
|
||||||
|
- **FR-016**: Pruning of operations/run records MUST use the configured retention days when set; otherwise it MUST behave as the baseline.
|
||||||
|
- **FR-017**: `operations.stuck_run_threshold_minutes` MUST be an integer between 0 and 10080.
|
||||||
|
- **FR-018**: `operations.stuck_run_threshold_minutes` MUST be a stored configuration value only; it MUST NOT introduce auto-remediation or auto-handling behavior in this feature scope.
|
||||||
|
|
||||||
|
## UI Action Matrix *(mandatory when Filament is changed)*
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| Page | Workspace Settings (admin UI) | Save; Reset setting to default (per-setting, confirmed) | N/A | N/A | N/A | N/A | N/A | Save + Cancel | Yes | View capability: read-only fields; Manage capability: editable + submit; Non-member: 404 |
|
||||||
|
|
||||||
|
### Key Entities *(include if feature involves data)*
|
||||||
|
|
||||||
|
- **Workspace Setting**: A workspace-owned key/value configuration item with validation rules and a default.
|
||||||
|
- **Audit Log Entry**: An immutable record of a settings update or reset-to-default event.
|
||||||
|
- **Drift Finding Type**: A classification string used to identify the type of drift finding for severity mapping.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: When no settings are configured for these keys, backup retention behavior, drift severity behavior, and operations pruning behavior match the baseline in automated regression tests.
|
||||||
|
- **SC-002**: An authorized workspace admin can update each of the 5 settings and observe the new effective value reflected in the Workspace Settings screen immediately after save.
|
||||||
|
- **SC-003**: 100% of settings updates and reset-to-default actions produce an audit log entry with key, old value, and new value.
|
||||||
|
- **SC-004**: Invalid configuration attempts (out-of-range numbers, invalid JSON, unsupported severities) are rejected and do not change persisted settings.
|
||||||
122
specs/098-settings-slices-v1-backup-drift-ops/tasks.md
Normal file
122
specs/098-settings-slices-v1-backup-drift-ops/tasks.md
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
description: "Task list for 098-settings-slices-v1-backup-drift-ops"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tasks: 098 — Settings Slices v1 (Backup + Drift + Operations)
|
||||||
|
|
||||||
|
**Input**: Design documents from `/specs/098-settings-slices-v1-backup-drift-ops/` (spec.md, plan.md)
|
||||||
|
**Tests**: REQUIRED (Pest) — runtime behavior changes.
|
||||||
|
**Scope**: workspace-level settings; DB-only rendering; no Graph calls.
|
||||||
|
|
||||||
|
## Phase 1: Setup (Shared Infrastructure)
|
||||||
|
|
||||||
|
- [X] T001 Confirm feature branch + clean working tree in specs/098-settings-slices-v1-backup-drift-ops/spec.md
|
||||||
|
- [X] T002 Verify Settings Foundation dependency is present by locating SettingsRegistry in app/Support/Settings/SettingsRegistry.php
|
||||||
|
- [X] T003 [P] Capture baseline behaviors + constants and record them in specs/098-settings-slices-v1-backup-drift-ops/plan.md by reviewing app/Jobs/ApplyBackupScheduleRetentionJob.php, app/Services/Drift/DriftFindingGenerator.php, app/Jobs/PruneOldOperationRunsJob.php, routes/console.php
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational (Blocking Prerequisites)
|
||||||
|
|
||||||
|
**Purpose**: Shared settings primitives + Workspace Settings page patterns used by all slices.
|
||||||
|
|
||||||
|
- [X] T004 Update Settings registry validation to match spec (max 365) in app/Support/Settings/SettingsRegistry.php
|
||||||
|
- [X] T005 [P] Add per-setting reset UX pattern scaffolding (no global reset) in app/Filament/Pages/Settings/WorkspaceSettings.php
|
||||||
|
- [X] T006 Add “unset input + helper text shows default/effective” support in app/Filament/Pages/Settings/WorkspaceSettings.php
|
||||||
|
- [X] T007 [P] Update existing workspace settings RBAC tests for new per-setting reset actions in tests/Feature/SettingsFoundation/WorkspaceSettingsViewOnlyTest.php
|
||||||
|
- [X] T008 [P] Update existing manage test to assert per-setting reset (not header reset) in tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php
|
||||||
|
|
||||||
|
**Checkpoint**: Workspace Settings page supports per-setting resets and unset presentation without changing behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 — Backup retention defaults (Priority: P1) 🎯 MVP
|
||||||
|
|
||||||
|
**Goal**: Workspace overrides for backup retention default + min floor; job clamps effective keep-last to floor.
|
||||||
|
**Independent Test**: Run ApplyBackupScheduleRetentionJob behavior with/without workspace overrides and verify clamping.
|
||||||
|
|
||||||
|
### Tests (US1)
|
||||||
|
|
||||||
|
- [X] T009 [P] [US1] Add/extend retention job tests to cover default + floor clamp in tests/Feature/BackupScheduling/BackupScheduleLifecycleTest.php
|
||||||
|
- [X] T010 [P] [US1] Add validation tests for backup settings bounds (1..365) via SettingsWriter in tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php
|
||||||
|
|
||||||
|
### Implementation (US1)
|
||||||
|
|
||||||
|
- [X] T011 [US1] Register backup floor setting and tighten keep-last rules (1..365) in app/Support/Settings/SettingsRegistry.php
|
||||||
|
- [X] T012 [US1] Extend Workspace Settings UI: Backup section adds `backup.retention_keep_last_default` + `backup.retention_min_floor` fields with per-setting reset actions in app/Filament/Pages/Settings/WorkspaceSettings.php
|
||||||
|
- [X] T013 [US1] Update Workspace Settings save logic: empty field triggers reset (delete override) instead of persisting null in app/Filament/Pages/Settings/WorkspaceSettings.php
|
||||||
|
- [X] T014 [US1] Apply floor clamping for both schedule override and resolved default in app/Jobs/ApplyBackupScheduleRetentionJob.php
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: User Story 2 — Drift severity mapping (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: Workspace-level `finding_type → severity` mapping with default `medium` and strict validation; normalize severities to lowercase.
|
||||||
|
**Independent Test**: Generate drift findings and assert severity uses mapping when present; saving invalid mapping is rejected.
|
||||||
|
|
||||||
|
### Tests (US2)
|
||||||
|
|
||||||
|
- [X] T015 [P] [US2] Add drift generator test asserting default severity remains medium when no mapping set in tests/Feature/Drift/DriftPolicySnapshotDriftDetectionTest.php
|
||||||
|
- [X] T016 [P] [US2] Add drift generator test asserting mapped severity is applied when mapping exists in tests/Feature/Drift/DriftPolicySnapshotDriftDetectionTest.php
|
||||||
|
- [X] T017 [P] [US2] Add settings save validation tests for drift severity mapping JSON shape + allowed values in tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php
|
||||||
|
|
||||||
|
### Implementation (US2)
|
||||||
|
|
||||||
|
- [X] T018 [US2] Add `Finding::SEVERITY_CRITICAL` constant and ensure severity domain remains stable in app/Models/Finding.php
|
||||||
|
- [X] T019 [US2] Extend finding severity badge mapping to include `critical` (BADGE-001 compliant) in app/Support/Badges/Domains/FindingSeverityBadge.php
|
||||||
|
- [X] T020 [US2] Register `drift.severity_mapping` setting with JSON validation + canonical normalization (lowercase values, string keys) in app/Support/Settings/SettingsRegistry.php
|
||||||
|
- [X] T021 [US2] Update DriftFindingGenerator to resolve workspace severity mapping (via SettingsResolver in workspace context) and apply mapped severity (fallback medium) in app/Services/Drift/DriftFindingGenerator.php
|
||||||
|
- [X] T022 [US2] Extend Workspace Settings UI: Drift section adds JSON textarea for `drift.severity_mapping` with unset behavior + per-setting reset in app/Filament/Pages/Settings/WorkspaceSettings.php
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Story 3 — Operations retention + stuck threshold (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: Workspace-level `operations.operation_run_retention_days` drives pruning; `operations.stuck_run_threshold_minutes` is stored only.
|
||||||
|
**Independent Test**: Create old/new OperationRuns across workspaces and verify prune respects per-workspace retention; stuck threshold persists and reloads.
|
||||||
|
|
||||||
|
### Tests (US3)
|
||||||
|
|
||||||
|
- [X] T023 [P] [US3] Update pruning schedule test to match new job behavior (per-workspace retention) in tests/Feature/Scheduling/PruneOldOperationRunsScheduleTest.php
|
||||||
|
- [X] T024 [P] [US3] Add prune job test verifying per-workspace retention cutoff using workspace settings in tests/Feature/Scheduling/PruneOldOperationRunsScheduleTest.php
|
||||||
|
- [X] T025 [P] [US3] Add workspace settings save/reset tests for operations keys in tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php
|
||||||
|
|
||||||
|
### Implementation (US3)
|
||||||
|
|
||||||
|
- [X] T026 [US3] Register operations settings keys with correct bounds in app/Support/Settings/SettingsRegistry.php
|
||||||
|
- [X] T027 [US3] Refactor PruneOldOperationRunsJob to compute retention per workspace (SettingsResolver + Workspace iteration) and prune by workspace_id in app/Jobs/PruneOldOperationRunsJob.php
|
||||||
|
- [X] T028 [US3] Ensure scheduler continues to enqueue prune job without needing a parameter in routes/console.php
|
||||||
|
- [X] T029 [US3] Extend Workspace Settings UI: Operations section adds `operation_run_retention_days` + `stuck_run_threshold_minutes` fields with unset behavior + per-setting reset in app/Filament/Pages/Settings/WorkspaceSettings.php
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
- [X] T030 [P] Confirm multi-key save emits one audit entry per key changed by reviewing app/Services/Settings/SettingsWriter.php and Workspace Settings save flow in app/Filament/Pages/Settings/WorkspaceSettings.php
|
||||||
|
- [X] T031 [P] Run Pint formatting on touched files via vendor/bin/sail bin pint --dirty (e.g., app/Filament/Pages/Settings/WorkspaceSettings.php, app/Support/Settings/SettingsRegistry.php, app/Jobs/ApplyBackupScheduleRetentionJob.php, app/Services/Drift/DriftFindingGenerator.php, app/Jobs/PruneOldOperationRunsJob.php)
|
||||||
|
- [X] T032 Run focused settings UI tests via vendor/bin/sail artisan test --compact tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php
|
||||||
|
- [X] T033 Run focused drift tests via vendor/bin/sail artisan test --compact tests/Feature/Drift/DriftPolicySnapshotDriftDetectionTest.php
|
||||||
|
- [X] T034 Run focused pruning tests via vendor/bin/sail artisan test --compact tests/Feature/Scheduling/PruneOldOperationRunsScheduleTest.php
|
||||||
|
- [X] T035 [P] Add an automated regression test asserting per-setting reset actions require confirmation (destructive-like) in tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php
|
||||||
|
- [X] T036 [P] Add an automated regression test asserting multi-key save produces one audit entry per key changed (FR-008a) in tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & Execution Order
|
||||||
|
|
||||||
|
- Setup (Phase 1) → Foundational (Phase 2) → US1 (Phase 3) → US2 (Phase 4) → US3 (Phase 5) → Polish (Phase 6)
|
||||||
|
|
||||||
|
## Parallel Execution Examples
|
||||||
|
|
||||||
|
```text
|
||||||
|
US1 parallel example: T009 + T011 + T014
|
||||||
|
US2 parallel example: T015 + T018 + T021
|
||||||
|
US3 parallel example: T023 + T027 + T029
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
- MVP = US1 only (backup defaults + floor clamp) with updated Workspace Settings UX and tests.
|
||||||
|
- Then US2 (drift mapping) and US3 (operations retention/threshold) as independent increments.
|
||||||
Loading…
Reference in New Issue
Block a user