fix(drift): dedupe semantic fingerprint repeats
This commit is contained in:
@@ -12,9 +12,11 @@ import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
SCHEMA_VERSION = "drift_repeat_state_v1"
|
||||
FINGERPRINT_VERSION = "drift_fingerprint_v1"
|
||||
SEMANTIC_FINGERPRINT_VERSION = "drift_fingerprint_v2"
|
||||
VALUE_AWARE_MATCHING_STRATEGY = "namespace_and_stable_items_v1"
|
||||
SEMANTIC_MATCHING_STRATEGY = "namespace_resource_field_level_v2"
|
||||
|
||||
|
||||
def _get(obj: Any, key: str, default: Any = None) -> Any:
|
||||
@@ -74,26 +76,38 @@ def _iso(value: Any) -> str | None:
|
||||
return parsed.isoformat() if parsed else None
|
||||
|
||||
|
||||
def drift_item_identity(item: Any) -> dict[str, Any]:
|
||||
def drift_item_identity(item: Any, *, include_values: bool = True) -> dict[str, Any]:
|
||||
"""Return the stable fields that define one drift item."""
|
||||
return {
|
||||
identity = {
|
||||
"resource_kind": str(_get(item, "resource_kind", "")),
|
||||
"resource_name": str(_get(item, "resource_name", "")),
|
||||
"namespace": str(_get(item, "namespace", "")),
|
||||
"field_path": str(_get(item, "field_path", "")),
|
||||
"drift_level": str(_enum_value(_get(item, "drift_level", ""))),
|
||||
"git_value": _jsonable(_get(item, "git_value")),
|
||||
"actual_value": _jsonable(_get(item, "actual_value")),
|
||||
"is_allowlisted": bool(_get(item, "is_allowlisted", False)),
|
||||
}
|
||||
if include_values:
|
||||
identity["git_value"] = _jsonable(_get(item, "git_value"))
|
||||
identity["actual_value"] = _jsonable(_get(item, "actual_value"))
|
||||
return identity
|
||||
|
||||
|
||||
def build_drift_fingerprint(namespace: str, items: list[Any]) -> str:
|
||||
def build_drift_fingerprint(
|
||||
namespace: str,
|
||||
items: list[Any],
|
||||
*,
|
||||
include_values: bool = True,
|
||||
) -> str:
|
||||
"""Build a deterministic fingerprint from namespace + sorted drift items."""
|
||||
identities = [drift_item_identity(item) for item in items]
|
||||
identities = [
|
||||
drift_item_identity(item, include_values=include_values)
|
||||
for item in items
|
||||
]
|
||||
identities.sort(key=_canonical_json)
|
||||
payload = {
|
||||
"version": FINGERPRINT_VERSION,
|
||||
"version": FINGERPRINT_VERSION
|
||||
if include_values
|
||||
else SEMANTIC_FINGERPRINT_VERSION,
|
||||
"namespace": namespace,
|
||||
"items": identities,
|
||||
}
|
||||
@@ -101,16 +115,26 @@ def build_drift_fingerprint(namespace: str, items: list[Any]) -> str:
|
||||
return f"dfp_{digest[:16]}"
|
||||
|
||||
|
||||
def _report_identity(report: Any) -> dict[str, Any]:
|
||||
def _report_identity(report: Any, *, include_values: bool = True) -> dict[str, Any]:
|
||||
items = _get(report, "items", []) or []
|
||||
namespace = str(_get(report, "namespace", ""))
|
||||
strict_fingerprint = build_drift_fingerprint(
|
||||
namespace,
|
||||
list(items),
|
||||
include_values=True,
|
||||
)
|
||||
return {
|
||||
"report_id": _get(report, "report_id"),
|
||||
"namespace": namespace,
|
||||
"status": str(_enum_value(_get(report, "status", ""))),
|
||||
"scanned_at": _get(report, "scanned_at"),
|
||||
"created_at": _get(report, "created_at"),
|
||||
"fingerprint": build_drift_fingerprint(namespace, list(items)),
|
||||
"fingerprint": build_drift_fingerprint(
|
||||
namespace,
|
||||
list(items),
|
||||
include_values=include_values,
|
||||
),
|
||||
"strict_fingerprint": strict_fingerprint,
|
||||
}
|
||||
|
||||
|
||||
@@ -118,11 +142,12 @@ def build_drift_repeat_state(
|
||||
report: Any,
|
||||
recent_reports: list[Any],
|
||||
*,
|
||||
include_values: bool = True,
|
||||
window_hours: int = 12,
|
||||
max_reports: int = 20,
|
||||
) -> dict[str, Any]:
|
||||
"""Summarize repeat state for one drift report using stable fingerprints."""
|
||||
current = _report_identity(report)
|
||||
current = _report_identity(report, include_values=include_values)
|
||||
current_time = (
|
||||
_parse_datetime(current.get("scanned_at"))
|
||||
or _parse_datetime(current.get("created_at"))
|
||||
@@ -132,7 +157,7 @@ def build_drift_repeat_state(
|
||||
|
||||
by_id: dict[str, dict[str, Any]] = {}
|
||||
for candidate in [report, *recent_reports]:
|
||||
identity = _report_identity(candidate)
|
||||
identity = _report_identity(candidate, include_values=include_values)
|
||||
report_id = str(identity.get("report_id") or "")
|
||||
if not report_id:
|
||||
continue
|
||||
@@ -162,7 +187,12 @@ def build_drift_repeat_state(
|
||||
return {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"fingerprint": current["fingerprint"],
|
||||
"matching_strategy": "namespace_and_stable_items_v1",
|
||||
"strict_fingerprint": current["strict_fingerprint"],
|
||||
"matching_strategy": (
|
||||
VALUE_AWARE_MATCHING_STRATEGY
|
||||
if include_values
|
||||
else SEMANTIC_MATCHING_STRATEGY
|
||||
),
|
||||
"window_hours": window_hours,
|
||||
"occurrences_12h": len(matches),
|
||||
"first_scanned_at": _iso(first.get("scanned_at") or first.get("created_at")),
|
||||
@@ -174,6 +204,7 @@ def build_drift_repeat_state(
|
||||
"scanned_at": _iso(row.get("scanned_at")),
|
||||
"created_at": _iso(row.get("created_at")),
|
||||
"status": row.get("status"),
|
||||
"strict_fingerprint": row.get("strict_fingerprint"),
|
||||
}
|
||||
for row in reversed(matches[-max_reports:])
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user