9.3 KiB
Feature Specification: Drift MVP
Feature Branch: feat/044-drift-mvp
Created: 2026-01-07
Status: Draft
Purpose
Detect and report drift between expected and observed states using inventory and run metadata.
This MVP focuses on reporting and triage, not automatic remediation.
Clarifications
Session 2026-01-12
- Q: How should Drift pick the baseline run for a given tenant + scope? → A: Baseline = previous successful inventory run for the same scope; compare against the latest successful run.
- Q: Should Drift findings be persisted or computed on demand? → A: Persist findings in DB per comparison (baseline_run_id + current_run_id), including a deterministic fingerprint for stable identity + triage.
- Q: How define the fingerprint (Stable ID) for a drift finding? → A:
sha256(tenant_id + scope_key + subject_type + subject_external_id + change_type + baseline_hash + current_hash)(normalized; excludes volatile fields). - Q: Which inventory entities/types are in scope for Drift MVP? → A: Policies + Assignments.
- Q: When should drift findings be generated? → A: On-demand when opening Drift: if findings for (baseline,current,scope) don’t exist yet, dispatch an async job to generate them.
Session 2026-01-13
- Q: What should Drift do if there are fewer than two successful inventory runs for the same
scope_key? → A: Show a blocked/empty state (“Need at least 2 successful runs for this scope to calculate drift”) and do not dispatch drift generation. - Q: Should acknowledgement carry forward across comparisons? → A: No; acknowledgement is per comparison (
baseline_run_id+current_run_id+scope_key). The same drift may re-appear asnewin later comparisons. - Q: Which
change_typevalues are supported in Drift MVP? → A:added,removed,modified(assignment target/intent changes are covered undermodified). - Q: What is the default UI behavior for
newvsacknowledgedfindings? → A: Default UI shows onlynew;acknowledgedis accessible via an explicit filter. - Q: What should the UI do if drift generation fails for a comparison? → A: Show an explicit error state (safe message + reference/run ids) and do not show findings for that comparison until a successful generation exists.
Session 2026-01-14
- Q: How should Drift track generation status/errors/idempotency for a comparison? → A: Use
BulkOperationRunas the canonical run container (status, failures, idempotency_key, and consistent UI/ops patterns).
Pinned Decisions (MVP defaults)
- Drift is implemented as a generator that writes persisted Finding rows (not only an in-memory/on-demand diff).
- Baseline selection: baseline = previous successful inventory run for the same scope_key; comparison = latest successful inventory run for the same scope_key.
- Scope is first-class via
scope_keyand must be deterministic to support future pinned baselines and compare workflows. - Fingerprints are deterministic and stable for triage/audit workflows.
- Drift MVP only uses
finding_type=driftandstatusin {new,acknowledged}. - Default severity:
medium(until a rule engine exists). - UI must not perform render-time Graph calls. Graph access (if any) is limited to background sync/jobs.
- Drift generation is tracked via
BulkOperationRunto persist status/errors across refresh and to enforce idempotency per (tenant, scope_key, baseline_run_id, current_run_id).
Key Entities / Generic Findings (Future-proof)
Finding (generic)
We want Drift MVP to remain MVP-sized, while making it easy to add future generators (Security Suite Audits, Cross-tenant Compare) without inventing a new model.
Rationale:
-
Drift = delta engine over runs.
-
Audit = rule engine over inventory.
-
Both write Findings with the same semantics: deterministic fingerprint + triage + minimized evidence.
-
finding_type(enum):drift(MVP), lateraudit,compare -
tenant_id -
scope_key(string): deterministic scope identifier (see Scope Definition / FR1) -
baseline_run_id(nullable; e.g. audit/compare) -
current_run_id(nullable; e.g. audit) -
fingerprint(string): deterministic; unique per tenant+scope+subject+change -
subject_type(string): e.g. policy type (or other inventory entity type) -
subject_external_id(string): Graph external id -
severity(enum):low/medium/high(MVP default:medium) -
status(enum):new/acknowledged(later:snoozed/assigned/commented) -
acknowledged_at(nullable) -
acknowledged_by_user_id(nullable) -
evidence_jsonb(jsonb): sanitized, small, secrets-free (no raw payload dumps) -
Optional/nullable for later (prepared; out of MVP):
rule_id,control_id,expected_value,source
MVP implementation scope: only finding_type=drift, statuses new/acknowledged, and no rule engine.
User Scenarios & Testing
Scenario 1: View drift summary
-
Given inventory sync has run at least twice
-
When the admin opens Drift
-
Then they see a summary of changes since the last baseline
-
If there are fewer than two successful runs for the same
scope_key, Drift shows a blocked/empty state and does not start drift generation.
Scenario 2: Drill into a drift finding
- Given a drift finding exists
- When the admin opens the finding
- Then they see what changed, when, and which run observed it
Scenario 3: Acknowledge/triage
-
Given a drift finding exists
-
When the admin marks it acknowledged
-
Then it is hidden from “new” lists but remains auditable
-
Acknowledgement is per comparison; later comparisons may still surface the same drift as
new.
Functional Requirements
-
FR1: Baseline + scope
- Define
scope_keyas the deterministic Inventory selection identifier.- MVP definition:
scope_key = InventorySyncRun.selection_hash. - Rationale: selection hashing already normalizes equivalent selections; reusing it keeps drift scope stable and consistent across the product.
- MVP definition:
- Baseline run (MVP) = previous successful inventory run for the same
scope_key. - Comparison run (MVP) = latest successful inventory run for the same
scope_key.
- Define
-
FR2: Finding generation (Drift MVP)
- Findings are persisted per (
baseline_run_id,current_run_id,scope_key). - Findings cover adds, removals, and changes for supported entities (Policies + Assignments).
- MVP
change_typevalues:added,removed,modified. - Findings are deterministic: same baseline/current + scope_key ⇒ same set of fingerprints.
- Drift generation must be tracked via
BulkOperationRunwith an idempotency key derived from (tenant_id, scope_key, baseline_run_id, current_run_id). - If fewer than two successful inventory runs exist for a given
scope_key, Drift does not generate findings and must surface a clear blocked/empty state in the UI.
- Findings are persisted per (
-
FR2a: Fingerprint definition (MVP)
- Fingerprint =
sha256(tenant_id + scope_key + subject_type + subject_external_id + change_type + baseline_hash + current_hash). baseline_hash/current_hashare hashes over normalized, sanitized comparison data (exclude volatile fields like timestamps).- Goal: stable identity for triage + audit compatibility.
- Fingerprint =
-
FR2b: Drift MVP scope includes Policies and their Assignments.
- Assignment drift includes target changes (e.g., groupId) and intent changes.
-
FR3: Provide Drift UI with summary and details.
- Default lists and the Drift landing summary show only
status=newby default. - The UI must provide a filter to include
acknowledgedfindings. - If drift generation fails for a comparison, the UI must surface an explicit error state (no secrets), including reference identifiers (e.g., run ids and the
BulkOperationRunid), and must not fall back to stale/previous results.
- Default lists and the Drift landing summary show only
-
FR4: Triage (MVP)
- Admin can acknowledge a finding; record
acknowledged_by_user_id+acknowledged_at. - Acknowledgement does not carry forward across comparisons in the MVP.
- Findings are never deleted in the MVP.
- Admin can acknowledge a finding; record
Non-Functional Requirements
- NFR1: Drift generation must be deterministic for the same baseline and scope.
- NFR2: Drift must remain tenant-scoped and safe to display.
- NFR3: Evidence minimization
evidence_jsonbmust be sanitized (no tokens/secrets) and kept small.- MVP drift evidence should include only:
change_type- changed_fields / metadata summary (counts, field list)
- run refs (baseline_run_id/current_run_id, timestamps)
- No raw payload dumps.
Dependencies / Name Resolution
- Drift/Audit UI should resolve labels via Inventory + Foundations (047) + Groups Cache (051) where applicable.
- No render-time Graph calls (Graph only in background sync/jobs, never in UI render).
Success Criteria
- SC1: Admins can identify drift across supported types (Policies + Assignments) in under 3 minutes.
- SC2: Drift results are consistent across repeated generation for the same baseline.
Out of Scope
- Automatic revert/promotion.
- Rule engine in MVP (Audit later), but the data model is prepared via
rule_id/control_id/expected_value.
Future Work (non-MVP)
- Security Suite Audits: add rule-based generators that write Findings (no new Finding model).
- Cross-tenant Compare: may write Findings (
finding_type=compare) or emit a compatible format that can be stored as Findings.
Related Specs
- Program:
specs/039-inventory-program/spec.md - Core:
specs/040-inventory-core/spec.md - Compare:
specs/043-cross-tenant-compare-and-promotion/spec.md