TenantAtlas/specs/119-baseline-drift-engine/plan.md
ahmido da1adbdeb5 Spec 119: Drift cutover to Baseline Compare (golden master) (#144)
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
2026-03-06 14:30:49 +00:00

147 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 Compares 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”.