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
4.7 KiB
4.7 KiB
Data Model — Drift Golden Master Cutover (Spec 119)
This spec extends existing models and introduces no new tables.
Entities
1) Finding (existing: App\Models\Finding)
Baseline Compare drift findings are tenant-owned rows in findings.
Key fields used/extended by this feature:
workspace_id(derived from tenant; required)tenant_id(required)finding_type = drift(required)source = baseline.compare(mandatory contract post-cutover)scope_key(baseline compare grouping key; stable across re-runs)fingerprint+recurrence_key(stable identity for idempotent upsert and lifecycle)severity(low|medium|high|critical)status(lifecycle):new,reopened, other open states, terminal statesevidence_fidelity(string):content|meta|mixedevidence_jsonb(JSONB): enriched evidence contract (see below)current_operation_run_id(the Baseline CompareOperationRunthat observed the finding)baseline_operation_run_id(unused for Baseline Compare drift; legacy run-to-run drift used it)
Evidence contract (findings.evidence_jsonb)
Minimum required keys for diff-UX compatibility:
change_type(string):missing_policy|unexpected_policy|different_versionpolicy_type(string)subject_key(string)summary.kind(string):policy_snapshot|policy_assignments|policy_scope_tagsbaseline.policy_version_id(int|null)current.policy_version_id(int|null)baseline.hash/current.hash(string; may be empty for missing/unexpected)baseline.provenance/current.provenance(object; fidelity/source/observed_at/observed_operation_run_id)fidelity(string):content|meta|mixedprovenance(object):baseline_profile_id,baseline_snapshot_id,compare_operation_run_id, andinventory_sync_run_idwhen applicable
Invariant:
different_versionrequires both policy version ids to render a detailed diff.missing_policymay render a detailed diff with onlybaseline.policy_version_id.unexpected_policymay render a detailed diff with onlycurrent.policy_version_id.- If the required policy version reference(s) for the finding’s change type are missing, the UI must treat the finding as “diff unavailable”.
2) OperationRun (existing: App\Models\OperationRun)
Baseline Compare runs:
type = baseline_comparetenant_idis required (tenant-scoped operation)status/outcometransitions are service-owned (must go throughOperationRunService)context.baseline_profile_idandcontext.baseline_snapshot_ididentify the compare baseline inputscontext.baseline_compare.*contains coverage + evidence gap breakdowns (read-only reporting)
Baseline Capture runs:
type = baseline_capturecontext.baseline_profile_idandcontext.source_tenant_ididentify the capture inputscontext.baseline_capture.inventory_sync_run_idrecords which completed Inventory Sync bounded subject selection when one existedcontext.baseline_capture.gaps.*remains the canonical reporting block for snapshot-capture ambiguity or evidence issues
Legacy run-to-run drift generation runs:
type = drift_generate_findings(no longer created post-cutover; existing rows may remain historical)
3) BaselineProfile / BaselineSnapshot / BaselineSnapshotItem (existing)
Workspace-owned baseline objects:
- Baseline Profile (
baseline_profiles) defines scope + capture mode and points toactive_snapshot_id. - Baseline Snapshot (
baseline_snapshots) is immutable and storessummary_jsonb. - Baseline Snapshot Items (
baseline_snapshot_items) store:baseline_hash(string)meta_jsonb.evidenceprovenance fields (fidelity/source/observed_at), intentionally without tenant-owned identifiers (e.g., nopolicy_version_id).
4) PolicyVersion (existing: App\Models\PolicyVersion)
Tenant-owned immutable policy snapshots used for:
- Content hashing/normalization for Baseline Compare evidence
- Rendering diffs in Findings detail view when both baseline/current policy version references exist
Relevant fields:
idtenant_idpolicy_id(ties versions to a tenant policy)capture_purpose(e.g.,baseline_capture,baseline_compare)operation_run_id(which run captured it)captured_atsnapshot,assignments,scope_tags(JSON/arrays)
Derived/Computed Values
evidence_jsonb.fidelityshould align with (or be derived from) the per-findingevidence_fidelitycolumn.summary.kindmay be set conservatively (e.g.,policy_snapshot) when dimension-level detection is not available.
Invariants
- Post-cutover drift findings must be queryable as:
finding_type = drift AND source = baseline.compare. - Legacy drift findings are deleted by a one-time migration using:
finding_type = drift AND (source IS NULL OR source <> 'baseline.compare').