TenantAtlas/specs/117-baseline-drift-engine/research.md
ahmido f08924525d Spec 117: Baseline Drift Engine + evidence fidelity/provenance (#142)
Implements Spec 117 (Golden Master Baseline Drift Engine):

- Adds provider-chain resolver for current state hashes (content evidence via PolicyVersion, meta evidence via inventory)
- Updates baseline capture + compare jobs to use resolver and persist provenance + fidelity
- Adds evidence_fidelity column/index + Filament UI badge/filter/provenance display for findings
- Adds performance guard test + integration tests for drift, fidelity semantics, provenance, filter behavior
- UX fix: Policies list shows "Sync from Intune" header action only when records exist; empty-state CTA remains and is functional

Tests:
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/PolicySyncCtaPlacementTest.php`
- `vendor/bin/sail artisan test --compact --filter=Baseline`

Checklist:
- specs/117-baseline-drift-engine/checklists/requirements.md ✓

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #142
2026-03-03 07:23:01 +00:00

3.9 KiB
Raw Blame History

Research — Spec 117 Baseline Drift Engine (v1.5)

This document resolves planning unknowns for implementing specs/117-baseline-drift-engine/spec.md in the existing Laravel + Filament codebase.

Decision 1 — Provider chain for evidence

Decision: Implement a batch-capable resolver (service) that selects evidence per subject via a provider chain:

  1. Content evidence: PolicyVersion.snapshot (normalized) captured since baseline snapshot captured time.
  2. Meta evidence: Inventory meta contract hash (existing BaselineSnapshotIdentity::hashItemContent(...)).
  3. No evidence: return null and record evidence gap (no finding emitted).

Rationale:

  • Matches specs “since” rule: baseline snapshot captured_at is the temporal reference.
  • Enables v1.5 requirement: compare is read-only and must not fetch upstream.
  • Batch resolution avoids N+1 DB queries.

Alternatives considered:

  • Per-subject resolving inside compare job (rejected: N+1 + harder to test).
  • Always meta-only (rejected: violates “deep settings drift” requirement).

Decision 2 — Fidelity calculation

Decision: Compute finding fidelity as the weaker-of the two sides:

  • content is stronger than meta.
  • If either side is meta, overall fidelity is meta.

Rationale:

  • Matches clarified spec rule.
  • Easy to implement and consistent with UX badge/filter semantics.

Alternatives considered:

  • “best-of” fidelity (rejected: misleading; would claim content-level confidence when one side is meta-only).

Decision 3 — Provenance storage (both sides)

Decision: Store provenance for both baseline and current evidence on each finding:

  • baseline: { fidelity, source, observed_at, observed_operation_run_id? }
  • current: { fidelity, source, observed_at, observed_operation_run_id? }

Rationale:

  • Required by accepted clarification (Q4).
  • Enables UI to show the “why” behind confidence.

Alternatives considered:

  • Store a single combined provenance blob (rejected: loses per-side data).

Decision 4 — Fidelity filter implementation (JSONB vs column)

Decision: Add a dedicated findings.evidence_fidelity column (enum-like string: content|meta) and keep full provenance in evidence_jsonb.

Rationale:

  • Filtering on a simple indexed column is clean, fast, and predictable.
  • Avoids complex JSONB query conditions and reduces coupling to evidence JSON structure.

Alternatives considered:

  • JSONB filter over evidence_jsonb->>'fidelity' (rejected: harder indexing, more brittle).

Decision 5 — Coverage and evidence-gap reporting location

Decision: Put coverage breakdown and evidence-gap counts in operation_runs.context under baseline_compare.coverage and baseline_compare.evidence_gaps.

Rationale:

  • OperationRun.summary_counts is restricted to numeric keys from OperationSummaryKeys.
  • Coverage details are operational diagnostics, not a general-purpose KPI.

Alternatives considered:

  • Add new summary keys (rejected: violates key whitelist / contract).

Decision 6 — No new HTTP APIs

Decision: No new controllers/endpoints. Changes are:

  • queued job behavior + persistence (findings + run context)
  • Filament UI (Finding list filters + columns/details)

Rationale:

  • The app is Filament-first; current functionality is already represented by panel routes.

Alternatives considered:

  • Add REST endpoints for compare (rejected: not needed for v1.5).

Notes on current codebase (facts observed)

  • Baseline capture stores meta contract hash into baseline_snapshot_items.baseline_hash and provenance-like fields in meta_jsonb.
  • Baseline compare recomputes current meta hash and currently hardcodes run context fidelity to meta.
  • Findings UI lacks fidelity filtering today.

Open Questions

None blocking Phase 1 design. Any remaining unknowns are implementation details that will be validated with focused tests.