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

11 KiB
Raw Permalink Blame History

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)

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)

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