feat: central RTC write modes (DISABLED/DRY_RUN/APPROVAL/ENABLED), R4 suggestion-only, preview/approval, per-record rollback, bulk blocker upsert; 1.2.0

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-24 22:46:05 -03:00
co-authored by Claude Sonnet 5
parent 3c10422e81
commit 42dc172763
50 changed files with 2948 additions and 915 deletions
+5 -6
View File
@@ -11,8 +11,8 @@ from .classification import ENGINE_VERSION, Classifier
from .config import settings
from .datasets.service import DatasetService
from .jobs import SYNC_KINDS, claim_next_job, complete_job, create_job, fail_job, schedule_automatic_sync
from .rtc_audit import record_rtc_write_audits
from .blocked_products import list_blockers, resolve_blocker, resolve_product_blockers, resolve_stale_fiscal_blockers, upsert_blocker
from .rtc_audit import job_authorizations, record_rtc_write_audits
from .blocked_products import list_blockers, resolve_blocker, resolve_product_blockers, resolve_stale_fiscal_blockers, upsert_blocker, upsert_blockers_bulk
from .context_keys import validate_entry
from .models import AgentSnapshot, BlockedProduct, FiscalContextEntry, ClassificationCache, ClassificationRequest, ClassificationResult, Dataset, Installation, Job
from .events import record_event
@@ -395,7 +395,7 @@ def router(session_factory, bootstrap_token):
session.commit()
logger.info("job claimed job_id=%s agent_id=%s kind=%s", job.id, agent.id, job.kind)
view = _job_view(job)
view["authorizations"] = {"rtc_auto_apply": bool(installation is not None and installation.rtc_auto_apply_authorized)} # decided centrally, per installation, default OFF
view["authorizations"] = job_authorizations(session, installation) if installation is not None else {"rtc_write_mode": "DISABLED"} # decided centrally, per installation, default OFF
return view
finally: session.close()
@@ -426,12 +426,11 @@ def router(session_factory, bootstrap_token):
result_summary = payload.get("result_summary", {})
installation = session.scalar(select(Installation).where(Installation.agent_id == agent.id))
pass_started = datetime.now(timezone.utc)
for blocker in result_summary.get("blocked_products", []):
upsert_blocker(session, agent.tenant_id, blocker, installation_id=installation.id if installation else None)
upsert_blockers_bulk(session, agent.tenant_id, list(result_summary.get("blocked_products", [])), installation_id=installation.id if installation else None)
for product_code in result_summary.get("resolved_products", []):
resolve_product_blockers(session, agent.tenant_id, product_code, installation_id=installation.id if installation else None)
if installation is not None:
record_rtc_write_audits(session, tenant_id=agent.tenant_id, installation=installation, agent=agent, job_id=job_id, rtc_auto_apply=result_summary.get("rtc_auto_apply"))
record_rtc_write_audits(session, tenant_id=agent.tenant_id, installation=installation, agent=agent, job_id=job_id, rtc_auto_apply=result_summary.get("rtc_auto_apply"), evaluated_all=bool((result_summary.get("fiscal_context") or {}).get("evaluated_all")))
if installation is not None and (result_summary.get("fiscal_context") or {}).get("evaluated_all"):
resolve_stale_fiscal_blockers(session, agent.tenant_id, installation.id, pass_started)
if not complete_job(session, job, result_summary): raise HTTPException(409, "job is not RUNNING")