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