feat: add autonomous fiscal control panel
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""Stable installation identity and safe operational metadata helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from .config import settings
|
||||
|
||||
SECRET_KEYS = {"password", "fb_password", "token", "agent_token", "api_key", "secret", "authorization", "connection_string"}
|
||||
|
||||
|
||||
def utcnow() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def ensure_installation_id(state_path: str, *, new_installation: bool = False) -> str:
|
||||
"""Persist a UUID beside the local Agent state without using hostname/IP."""
|
||||
path = Path(state_path)
|
||||
identity_path = path.with_suffix(path.suffix + ".installation_id")
|
||||
if identity_path.exists() and not new_installation:
|
||||
value = identity_path.read_text(encoding="utf-8").strip()
|
||||
try:
|
||||
return str(uuid.UUID(value))
|
||||
except ValueError:
|
||||
pass
|
||||
value = str(uuid.uuid4())
|
||||
identity_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = identity_path.with_suffix(identity_path.suffix + ".tmp")
|
||||
temporary.write_text(value + "\n", encoding="utf-8")
|
||||
temporary.replace(identity_path)
|
||||
return value
|
||||
|
||||
|
||||
def mask_cnpj(value: str | None) -> str | None:
|
||||
digits = re.sub(r"\D", "", value or "")
|
||||
if len(digits) != 14:
|
||||
return None if not value else "***"
|
||||
return f"{digits[:2]}.***.***/{digits[8:12]}-{digits[12:]}"
|
||||
|
||||
|
||||
def sanitize_database_path(value: str | None) -> str | None:
|
||||
if not value:
|
||||
return None
|
||||
normalized = str(value).replace("\\", "/")
|
||||
return normalized.rsplit("/", 1)[-1][:255]
|
||||
|
||||
|
||||
def _safe_value(key: str, value):
|
||||
if key.lower() in SECRET_KEYS:
|
||||
return "[REDACTED]"
|
||||
if isinstance(value, dict):
|
||||
return {str(k): _safe_value(str(k), v) for k, v in value.items() if str(k).lower() not in SECRET_KEYS}
|
||||
if isinstance(value, list):
|
||||
return [_safe_value(key, item) for item in value[:100]]
|
||||
return value
|
||||
|
||||
|
||||
def sanitize_registration(payload: dict) -> dict:
|
||||
allowed = {
|
||||
"installation_id", "display_name", "customer_code", "notes", "hostname", "windows_version", "machine_fingerprint",
|
||||
"company_code", "company_name", "company_cnpj", "company_uf", "company_crt", "firebird_version", "firebird_ods",
|
||||
"database_identifier", "database_path", "capabilities", "fiscal_engine_version", "dataset_versions", "started_at",
|
||||
"status", "schema_fingerprint", "agent_version", "last_job_id", "coverage", "health", "event_type", "message", "context",
|
||||
}
|
||||
result = {key: _safe_value(key, value) for key, value in payload.items() if key in allowed}
|
||||
if "company_cnpj" in result:
|
||||
result["company_cnpj_masked"] = mask_cnpj(result.pop("company_cnpj"))
|
||||
if "database_path" in result:
|
||||
result["database_identifier"] = sanitize_database_path(result.pop("database_path"))
|
||||
if result.get("installation_id"):
|
||||
result["installation_id"] = str(uuid.UUID(str(result["installation_id"])))
|
||||
return result
|
||||
|
||||
|
||||
def compute_agent_health(snapshot, now: datetime | None = None, *, supplemental=None) -> tuple[str, str]:
|
||||
now = now or utcnow()
|
||||
last_seen = getattr(snapshot, "last_seen_at", None)
|
||||
if last_seen is None:
|
||||
return "UNKNOWN", "no heartbeat received"
|
||||
if last_seen.tzinfo is None:
|
||||
last_seen = last_seen.replace(tzinfo=timezone.utc)
|
||||
offline_after = settings.agent_offline_after_seconds or settings.agent_heartbeat_interval_seconds * 3
|
||||
if (now - last_seen).total_seconds() > offline_after:
|
||||
return "OFFLINE", f"last heartbeat exceeds {offline_after}s"
|
||||
source = supplemental or snapshot
|
||||
if any(getattr(candidate, "agent_health", None) == "FAILED" or getattr(candidate, "last_status", None) == "FAILED" for candidate in (snapshot, supplemental) if candidate is not None):
|
||||
return "FAILED", "persistent technical failure"
|
||||
if getattr(source, "failed_products", 0) or getattr(source, "last_job_status", "") == "FAILED":
|
||||
return "DEGRADED", "recent technical or job failure"
|
||||
return "ONLINE", "recent heartbeat without critical technical failure"
|
||||
|
||||
|
||||
def compute_version_drift(agent_version: str | None) -> str:
|
||||
if not agent_version:
|
||||
return "UNKNOWN"
|
||||
return "CURRENT" if agent_version == settings.agent_server_version else "OUTDATED"
|
||||
|
||||
|
||||
def machine_fingerprint(parts: list[str]) -> str:
|
||||
return hashlib.sha256("|".join(parts).encode()).hexdigest()
|
||||
Reference in New Issue
Block a user