- resolver (rules RTC-R0..R4/A1..A3/G1..G2) over the official cClassTrib table 2026-06-22 and LC 214/2025 annexes; local vs official catalog comparison - ONE root-cause RTC blocker per product (missing/unresolved/ambiguous/conflict/official mismatch/catalog outdated/evidence missing) - auto-apply: C000025.CCLASSTRIB only, three switches (FIREBIRD_WRITES + RTC_AUTO_APPLY_ENABLED + Hub authorization), per-product re-read and re-decision, never overwrites, rowcount 1, audit table rtc_write_audits (migration 0027) - copy-only write E2E (280/280 applied, only CCLASSTRIB changed, 2nd run 0 writes, stale decision 0 writes); real databases untouched - docs: cclasstrib_resolution, rtc_auto_apply, unified_fiscal_completeness Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
37 lines
2.3 KiB
Python
37 lines
2.3 KiB
Python
"""Persists the Agent's RTC auto-apply audit records (append-only) and mirrors a summary event for the panel."""
|
|
from __future__ import annotations
|
|
|
|
from .events import record_event
|
|
from .models import RtcWriteAudit
|
|
|
|
MAX_AUDITS_PER_JOB = 2000
|
|
|
|
|
|
def record_rtc_write_audits(session, *, tenant_id: int, installation, agent, job_id: int | None, rtc_auto_apply: dict | None) -> int:
|
|
"""Stores every applied/aborted write of the job. Never trusts the Agent for identity: tenant/installation/agent come from the authenticated context."""
|
|
if not isinstance(rtc_auto_apply, dict):
|
|
return 0
|
|
records = list(rtc_auto_apply.get("applied") or []) + list(rtc_auto_apply.get("aborted") or [])
|
|
stored = applied = aborted = 0
|
|
for item in records[:MAX_AUDITS_PER_JOB]:
|
|
if not isinstance(item, dict) or item.get("field") != "CCLASSTRIB":
|
|
continue
|
|
session.add(RtcWriteAudit(
|
|
tenant_id=tenant_id, installation_id=installation.id, agent_id=agent.id, job_id=job_id, client=getattr(installation, "display_name", None),
|
|
product_code=str(item.get("product_code", ""))[:64], field="CCLASSTRIB", outcome=str(item.get("outcome", ""))[:16], reason=(item.get("reason") or None) and str(item["reason"])[:96],
|
|
old_value=(None if item.get("old_value") is None else str(item["old_value"])[:64]), new_value=(None if item.get("new_value") is None else str(item["new_value"])[:64]),
|
|
rule_id=(item.get("rule_id") or None) and str(item["rule_id"])[:32], evidence=item.get("evidence") or {}, official_source_version=item.get("official_source_version") or {},
|
|
local_catalog_hash=item.get("local_catalog_hash"), written_at=item.get("timestamp")))
|
|
stored += 1
|
|
if item.get("outcome") == "APPLIED":
|
|
applied += 1
|
|
else:
|
|
aborted += 1
|
|
if stored:
|
|
record_event(session, tenant_id=tenant_id, installation_id=installation.id, agent_id=agent.id, severity="WARNING" if applied else "INFO", event_type="RTC_AUTO_APPLY", job_id=job_id,
|
|
message=f"cClassTrib preenchido em {applied} produto(s); {aborted} abortado(s)",
|
|
context={"applied": applied, "aborted": aborted, "reasons": rtc_auto_apply.get("aborted_reasons") or {}, "mode": rtc_auto_apply.get("mode")})
|
|
if stored:
|
|
session.commit()
|
|
return stored
|