"""FedMart adapter for XIC telemetry ingestion and spec map registration. Provides: - emit_telemetry(telemetry): normalize and forward telemetry to FedMart - register_spec_map(spec_map): push XIC specification status to FedMart - Control hooks for guardrail actions (pause, throttle) """ import json import time from typing import Dict, Any, List, Optional from datetime import datetime from pathlib import Path class FedMartAdapter: """Adapter for XIC → FedMart integration.""" def __init__(self, endpoint: Optional[str] = None, local_mode: bool = False): """Initialize FedMart adapter. Args: endpoint: FedMart ingestion endpoint (default: http://localhost:8080/fedmart/ingest) local_mode: If True, store telemetry locally instead of sending to remote """ self.endpoint = endpoint or "http://localhost:8080/fedmart/ingest" self.local_mode = local_mode self.telemetry_buffer: List[Dict[str, Any]] = [] self.spec_status = {} def emit_telemetry(self, telemetry: Dict[str, Any]) -> bool: """Emit telemetry event to FedMart. Args: telemetry: Telemetry dict with schema-defined fields Returns: True if successful, False otherwise """ # Normalize and validate normalized = self._normalize_telemetry(telemetry) if self.local_mode: # Store locally self.telemetry_buffer.append(normalized) print(f"[FEDMART] Telemetry buffered locally (total: {len(self.telemetry_buffer)})") return True else: # In production, this would POST to FedMart endpoint return self._send_to_fedmart(normalized) def register_spec_map(self, spec_map: Dict[str, Any]) -> bool: """Register XIC specification status with FedMart. Args: spec_map: Dict with spec entries (instruction, phase, status, coverage) Returns: True if successful, False otherwise """ self.spec_status.update(spec_map) if self.local_mode: print(f"[FEDMART] Spec map registered locally ({len(self.spec_status)} entries)") return True else: return self._send_spec_to_fedmart(spec_map) def pause_run(self, run_id: str) -> bool: """Pause a pipeline run (guardrail control action). Args: run_id: Unique run identifier Returns: True if pause command accepted """ print(f"[FEDMART-CONTROL] Pause requested for run {run_id}") if self.local_mode: return True # In production: POST to /fedmart/control/pause with run_id return True def throttle_run(self, run_id: str, factor: float = 0.5) -> bool: """Throttle a pipeline run (reduce glyph_count or slow execution). Args: run_id: Unique run identifier factor: Throttle factor (0.5 = 50% speed) Returns: True if throttle command accepted """ print(f"[FEDMART-CONTROL] Throttle {factor:.1%} requested for run {run_id}") if self.local_mode: return True # In production: POST to /fedmart/control/throttle with run_id and factor return True def _normalize_telemetry(self, telemetry: Dict[str, Any]) -> Dict[str, Any]: """Normalize telemetry to schema. Ensures timestamp is ISO 8601, adds defaults, etc. """ normalized = dict(telemetry) # Ensure timestamp is ISO 8601 if "timestamp" not in normalized: normalized["timestamp"] = datetime.utcnow().isoformat() + "Z" elif isinstance(normalized["timestamp"], float): normalized["timestamp"] = datetime.utcfromtimestamp( normalized["timestamp"] ).isoformat() + "Z" # Ensure run_id if "run_id" not in normalized: normalized["run_id"] = self._generate_run_id() # Ensure defaults normalized.setdefault("event_type", "symbolic_pipeline_run") normalized.setdefault("glyph_ids", []) normalized.setdefault("glyph_count", len(normalized.get("glyph_ids", []))) normalized.setdefault("global_resonance_score", 0.0) normalized.setdefault("steps_executed", 0) normalized.setdefault("guardrails_triggered", []) # Ensure resonance_map_summary if "resonance_map_summary" not in normalized: normalized["resonance_map_summary"] = { "top_glyphs": [], "average_resonance": 0.0, } return normalized def _send_to_fedmart(self, telemetry: Dict[str, Any]) -> bool: """Send telemetry to FedMart ingestion endpoint. Posts to local server at http://localhost:8000/fedmart/ingest/xic """ try: import requests response = requests.post( "http://localhost:8000/fedmart/ingest/xic", json=telemetry, timeout=2 ) return response.status_code in (200, 202, 204) except Exception as e: print(f"[FEDMART] Error sending telemetry: {e}") return False def _send_spec_to_fedmart(self, spec_map: Dict[str, Any]) -> bool: """Send spec map to FedMart spec registration endpoint.""" try: import requests response = requests.post( self.endpoint + "/spec_map", json=spec_map, timeout=2 ) return response.status_code in (200, 202, 204) except Exception as e: print(f"[FEDMART] Error sending spec map: {e}") return False def _generate_run_id(self) -> str: """Generate a unique run ID.""" return f"xic_{int(time.time() * 1000)}" def get_telemetry_buffer(self) -> List[Dict[str, Any]]: """Get all buffered telemetry (local mode only).""" return self.telemetry_buffer.copy() def clear_telemetry_buffer(self) -> None: """Clear telemetry buffer.""" self.telemetry_buffer.clear() # Global singleton instance _adapter_instance: Optional[FedMartAdapter] = None def get_adapter(local_mode: bool = False) -> FedMartAdapter: """Get or create the global FedMart adapter instance. Args: local_mode: If True, buffer telemetry locally. Default False sends to server. Returns: FedMartAdapter singleton """ global _adapter_instance if _adapter_instance is None: _adapter_instance = FedMartAdapter(local_mode=local_mode) return _adapter_instance def emit_telemetry(telemetry: Dict[str, Any]) -> bool: """Convenience function to emit telemetry via global adapter.""" return get_adapter().emit_telemetry(telemetry) def register_spec_map(spec_map: Dict[str, Any]) -> bool: """Convenience function to register spec map via global adapter.""" return get_adapter().register_spec_map(spec_map) def pause_run(run_id: str) -> bool: """Convenience function to pause a run via global adapter.""" return get_adapter().pause_run(run_id) def throttle_run(run_id: str, factor: float = 0.5) -> bool: """Convenience function to throttle a run via global adapter.""" return get_adapter().throttle_run(run_id, factor)