Implements Spec 119 (Drift Golden Master Cutover): - Baseline Compare is the only drift writer (`source = baseline.compare`). - Drift findings now store diff-compatible `evidence_jsonb` (summary.kind, baseline/current policy_version_id refs, fidelity + provenance). - Findings UI renders one-sided diffs for `missing_policy`/`unexpected_policy` when a single ref exists; otherwise shows explicit “diff unavailable”. - Removes legacy drift generator runtime (jobs/services/UI) and related tests. - Adds one-time migration to delete legacy drift findings (`finding_type=drift` where source is null or != baseline.compare). - Scopes baseline capture & landing duplicate warnings to latest completed inventory sync. - Canonicalizes compliance `scheduledActionsForRule` drift signal and keeps legacy snapshots comparable. Tests: - `vendor/bin/sail artisan test --compact` (full suite per tasks) - Focused pack: BaselinePolicyVersionResolverTest, BaselineCompareDriftEvidenceContractTest, DriftFindingDiffUnavailableTest, LegacyDriftFindingsCleanupMigrationTest, ComplianceNoncomplianceActionsDriftTest Notes: - Livewire v4+ / Filament v5 compatible (no legacy APIs). - No new external dependencies. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #144
147 lines
11 KiB
Markdown
147 lines
11 KiB
Markdown
# Implementation Plan: Drift Golden Master Cutover (Baseline Compare)
|
||
|
||
**Branch**: `feat/119-baseline-drift-engine` | **Date**: 2026-03-05 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/spec.md`
|
||
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/spec.md`
|
||
|
||
## Summary
|
||
|
||
Make Baseline Compare the only drift findings writer (`source = baseline.compare`) while preserving the existing diff UI by writing a diff-compatible `evidence_jsonb` structure (including `summary.kind` and baseline/current `policy_version_id` references when content evidence exists). `different_version` findings require both refs, while `missing_policy` and `unexpected_policy` may render against an empty side when exactly one ref exists. Align Baseline Snapshot capture with the latest completed Inventory Sync run so stale inventory rows do not create false `ambiguous_match` gaps. Treat full-content compare captures that reuse an identical existing `policy_version` as valid current evidence for the current run, instead of dropping them behind the `since` filter. Treat Intune compliance `scheduledActionsForRule` as canonical drift signal content (semantic fields only, deterministically sorted) and keep existing baseline snapshots comparable by recomputing effective baseline hashes from resolved baseline `policy_versions` when content provenance exists. Remove the legacy run-to-run drift generator (jobs, UI, run-type catalog, alerts/widget references, and tests) and delete legacy drift findings to avoid mixed evidence formats.
|
||
|
||
## Technical Context
|
||
|
||
**Language/Version**: PHP 8.4.1
|
||
**Primary Dependencies**: Laravel 12, Filament 5, Livewire 4
|
||
**Storage**: PostgreSQL (JSONB for evidence payloads)
|
||
**Testing**: Pest 4 (PHPUnit 12)
|
||
**Target Platform**: Linux containers (Dokploy) + local dev via Laravel Sail
|
||
**Project Type**: Web application (Laravel + Filament admin panels)
|
||
**Performance Goals**: Drift findings from a completed Baseline Compare run are visible within 5 minutes (spec SC-119-05); tenant pages render without timeouts under normal tenant sizes.
|
||
**Constraints**: Hard cut (no feature flags, no dual-write), strict tenant/workspace isolation (404/403 semantics), Ops-UX `OperationRun` contract must remain intact, no new external integrations.
|
||
**Scale/Scope**: Tenant-scoped drift findings + baseline compare runs; evidence enrichment must be deterministic and safe for list/detail rendering across typical governance datasets (paginated Findings list; evidence JSON schema remains stable).
|
||
|
||
## Constitution Check
|
||
|
||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||
|
||
| Principle | Status | Notes |
|
||
|---|---|---|
|
||
| Inventory-first, Snapshots-second | PASS | Baseline Compare compares workspace-owned baseline snapshots against tenant “last observed” inventory/policy-version evidence; no change to snapshot immutability. |
|
||
| Read/Write separation by default | PASS | Feature is drift detection + evidence enrichment; user-facing mutation remains Baseline Compare “Compare Now” (queued op with confirmation + audit). |
|
||
| Single contract path to Graph | PASS | No new Graph endpoints; Baseline Compare continues to use existing evidence providers / contracts. |
|
||
| Workspace + Tenant isolation (404/403 semantics) | PASS | Tenant-scoped Findings + Baseline Compare landing remain capability-gated; cleanup migration is scoped to drift findings only. |
|
||
| Run observability + Ops-UX 3-surface feedback | PASS | Baseline Compare continues using `OperationRun`; removing legacy drift generation reduces competing run types and preserves Monitoring contract. |
|
||
| Filament action surface contract | PASS | Drift entry point is Baseline Compare landing; Findings resource remains list+view with inspect affordance; destructive-like actions keep confirmations. |
|
||
|
||
Gate decision: **PASS** (no constitution exceptions required).
|
||
|
||
## Project Structure
|
||
|
||
### Documentation (this feature)
|
||
|
||
```text
|
||
specs/119-baseline-drift-engine/
|
||
├── plan.md # This file (/speckit.plan command output)
|
||
├── research.md # Phase 0 output (/speckit.plan command)
|
||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||
```
|
||
|
||
### Source Code (repository root)
|
||
|
||
```text
|
||
/Users/ahmeddarrazi/Documents/projects/TenantAtlas/
|
||
├── app/
|
||
│ ├── Filament/
|
||
│ │ ├── Pages/
|
||
│ │ │ ├── BaselineCompareLanding.php
|
||
│ │ │ └── DriftLanding.php # legacy (to remove)
|
||
│ │ ├── Resources/
|
||
│ │ │ └── FindingResource.php
|
||
│ │ └── Widgets/
|
||
│ │ └── Dashboard/NeedsAttention.php # legacy references (to update)
|
||
│ ├── Jobs/
|
||
│ │ ├── CompareBaselineToTenantJob.php
|
||
│ │ └── GenerateDriftFindingsJob.php # legacy (to remove)
|
||
│ ├── Jobs/Alerts/EvaluateAlertsJob.php # legacy compare_failed producer (to update/remove)
|
||
│ ├── Services/
|
||
│ │ ├── Baselines/
|
||
│ │ │ ├── CurrentStateHashResolver.php
|
||
│ │ │ └── Evidence/* # evidence providers + provenance
|
||
│ │ └── Drift/* # keep diff + normalizers; remove generator-only pieces
|
||
│ └── Support/
|
||
│ ├── OperationCatalog.php # legacy run type label (to remove)
|
||
│ ├── OperationRunLinks.php # legacy drift link (to update)
|
||
│ └── OperationRunType.php # legacy enum case (to remove)
|
||
├── database/migrations/
|
||
│ └── (new) *_delete_legacy_drift_findings.php # one-time cleanup migration
|
||
├── resources/views/filament/pages/
|
||
│ ├── baseline-compare-landing.blade.php
|
||
│ └── drift-landing.blade.php # legacy (to remove)
|
||
└── tests/
|
||
├── Feature/Alerts/* # update references to legacy drift type if any
|
||
└── Feature/Drift/* # legacy tests (to remove/update)
|
||
```
|
||
|
||
**Structure Decision**: Single Laravel application under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/` (Filament UI + queued jobs). Feature work stays within `app/`, `resources/`, `database/`, and `tests/` plus feature docs under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/`.
|
||
|
||
## Complexity Tracking
|
||
|
||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||
|
||
No constitution exceptions are required for this feature.
|
||
|
||
## Phase 0 — Outline & Research
|
||
|
||
**Output**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/research.md`
|
||
|
||
- Confirm existing Baseline Compare drift writer contract (where `source` is set; what evidence fields exist today).
|
||
- Identify the minimum evidence keys required for the existing diff renderer (`summary.kind`, baseline/current `policy_version_id`).
|
||
- Identify all legacy drift generation touchpoints to remove (job, generator, UI landing, dashboard widget, operation catalog/links, alerts producer, and tests).
|
||
- Define deterministic cleanup criteria for legacy drift findings deletion.
|
||
|
||
## Phase 1 — Design & Contracts
|
||
|
||
**Outputs**:
|
||
- `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/data-model.md`
|
||
- `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/contracts/*`
|
||
- `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/quickstart.md`
|
||
|
||
Design highlights:
|
||
- Drift findings remain tenant-owned (`findings.finding_type = drift`) with mandatory `source = baseline.compare`.
|
||
- Baseline Compare evidence is enriched to be diff-renderable when content evidence exists:
|
||
- Always write `evidence_jsonb.summary.kind` (allowed: `policy_snapshot`, `policy_assignments`, `policy_scope_tags`).
|
||
- Write `evidence_jsonb.baseline.policy_version_id` and `evidence_jsonb.current.policy_version_id` when available (independently); compute `evidence_jsonb.fidelity` deterministically from presence (both = `content`, one = `mixed`, none = `meta`).
|
||
- Resolve baseline `policy_version_id` deterministically using baseline snapshot item provenance (`observed_at`) + stable policy identity (`policy_type` + `subject_key`); if not resolvable, set null.
|
||
- Add `evidence_jsonb.provenance` keys: `baseline_profile_id`, `baseline_snapshot_id`, `compare_operation_run_id` (Baseline Compare `OperationRun` id), and `inventory_sync_run_id` when applicable.
|
||
- Findings UI renders one-sided diffs for `missing_policy` (baseline-only) and `unexpected_policy` (current-only); only `different_version` requires both refs.
|
||
- Drift navigation entry point after cutover is Baseline Compare landing (`/admin/t/{tenant}/baseline-compare-landing`).
|
||
|
||
Re-check Constitution post-design: **PASS** (design stays within tenant/workspace isolation and existing `OperationRun` + Filament action-surface rules).
|
||
|
||
## Phase 2 — Implementation Plan (for /speckit.tasks)
|
||
|
||
Phase 2 will be expressed as concrete tasks in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/119-baseline-drift-engine/tasks.md` by `/speckit.tasks`. High-level sequencing:
|
||
|
||
1) **Evidence contract upgrade (Baseline Compare → drift findings)**
|
||
- Extend Baseline Compare drift evidence writer to include `summary.kind`, baseline/current `policy_version_id` references (when content), explicit fidelity, and run/snapshot provenance.
|
||
- Ensure evidence keys are stable and deterministic across runs.
|
||
- Align Baseline Snapshot capture subject selection with the latest completed Inventory Sync run when available, matching Baseline Compare’s current-state boundary.
|
||
- Treat same-run reused compare versions as valid current content evidence so deduplicated captures do not surface as `missing_current`.
|
||
- Canonicalize compliance `scheduledActionsForRule` into semantic drift keys and compare content-backed baseline snapshots against recomputed baseline-version hashes so the signal can evolve without forcing snapshot recapture.
|
||
|
||
2) **Diff UX guardrails**
|
||
- Update the Findings detail view so `different_version` diffs require both `baseline.policy_version_id` and `current.policy_version_id`, while `missing_policy` and `unexpected_policy` render against an empty side when their single required reference exists; otherwise show an explicit “diff unavailable” explanation.
|
||
|
||
3) **Hard-cut legacy drift removal**
|
||
- Delete legacy drift generation job + generator-only services and remove tenant UI entry points that trigger or describe run-to-run drift generation.
|
||
- Remove legacy operation run type registrations/catalog labels and any UI/widget/alert producers that reference drift generation.
|
||
|
||
4) **Data cleanup**
|
||
- Add a one-time migration deleting findings where `finding_type = drift` AND (`source` is null OR `source` is not equal to `baseline.compare`), ensuring Baseline Compare rows remain.
|
||
|
||
5) **Tests**
|
||
- Add/adjust tests asserting Baseline Compare drift findings include the new evidence contract and `source`.
|
||
- Remove legacy drift generation tests and update any other tests that referenced `drift_generate_findings` only to simulate “some other type”.
|