""" Pipeline feedback — feeds execution results back into the policy engine so it can learn which nodes / actions succeed vs fail. F-04 remediation: ``report_execution()`` now actually calls ``policy.update_reputation(...)`` instead of being a stub. The singleton policy engine still works with ``db=None`` — reputation is stored in-process on the engine itself. """ from __future__ import annotations import logging from typing import Any, Dict, Optional from backend.policy.engine import PolicyEngine log = logging.getLogger(__name__) # Singleton policy engine (db=None — reputation is kept in-process on # the engine itself; pass a db to also enable the historical layer). policy = PolicyEngine(db=None) def report_execution( node: str, action: str, success: bool, duration: float, temp_before: float, temp_after: float, ) -> Dict[str, Any]: """Feed execution results back to the policy engine. Computes the thermal-spike flag and pushes a reputation update to the singleton :class:`PolicyEngine`. Returns the assessment dict (kept for backward compatibility — older callers consumed it directly). """ thermal_spike = (temp_after - temp_before) > 0.15 # Actually update the reputation store so the scheduler can learn. try: updated = policy.update_reputation( node=node, success=bool(success), duration=float(duration or 0.0), thermal_spike=bool(thermal_spike), ) log.debug( "feedback: node=%s action=%s success=%s dur=%.1fs thermal=%s -> %s", node, action, success, duration, thermal_spike, updated, ) except Exception: # Reputation updates must never break the pipeline. log.warning("policy.update_reputation failed", exc_info=True) updated = {} return { "node": node, "action": action, "success": success, "duration": duration, "thermal_spike": thermal_spike, "temp_delta": temp_after - temp_before, "reputation": updated, }