diff --git a/src/pulsemesh_open_telemetry_visualization_a/catopt_bridge.py b/src/pulsemesh_open_telemetry_visualization_a/catopt_bridge.py index 64a3753..9c9b22e 100644 --- a/src/pulsemesh_open_telemetry_visualization_a/catopt_bridge.py +++ b/src/pulsemesh_open_telemetry_visualization_a/catopt_bridge.py @@ -53,6 +53,8 @@ def map_delta_to_catopt(delta_env: DeltaEnvelope) -> Dict[str, Any]: "Metadata": { "protocol_version": getattr(delta_env, "protocol_version", "PulseMesh/0.1.0"), "timestamp": delta_env.delta.timestamp if hasattr(delta_env, "delta") else None, + # Pass through optional PoeticCert provenance when present + "poetic_cert": getattr(delta_env, "poetic_cert", None), }, } return result diff --git a/src/pulsemesh_open_telemetry_visualization_a/protocol.py b/src/pulsemesh_open_telemetry_visualization_a/protocol.py index 14194bf..f5f649a 100644 --- a/src/pulsemesh_open_telemetry_visualization_a/protocol.py +++ b/src/pulsemesh_open_telemetry_visualization_a/protocol.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, asdict -from typing import List, Dict, Any +from typing import List, Dict, Any, Optional import json from .delta import Delta @@ -29,12 +29,15 @@ class DeltaEnvelope: protocol_version: str contract: TelemetryContract delta: Delta + # Optional provenance/certification for anomaly detectors (PoeticCert) + poetic_cert: Optional[Dict[str, Any]] = None def to_dict(self) -> Dict[str, Any]: return { "protocol_version": self.protocol_version, "contract": self.contract.to_dict(), "delta": self.delta.to_dict(), + "poetic_cert": self.poetic_cert, } def to_json(self) -> str: diff --git a/tests/test_poeticcert.py b/tests/test_poeticcert.py new file mode 100644 index 0000000..11da870 --- /dev/null +++ b/tests/test_poeticcert.py @@ -0,0 +1,43 @@ +import sys +import os +import time + +# Ensure package source is importable during tests +SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")) +if SRC_DIR not in sys.path: + sys.path.insert(0, SRC_DIR) + +from pulsemesh_open_telemetry_visualization_a.delta import Delta +from pulsemesh_open_telemetry_visualization_a.protocol import TelemetryContract, DeltaEnvelope +from pulsemesh_open_telemetry_visualization_a.catopt_bridge import map_delta_to_catopt + + +def test_poetic_cert_roundtrip_and_mapping(): + contract = TelemetryContract( + contract_id="c1", + device_type="test_device", + metrics=["m1"], + units={"m1": "unit"}, + quality_flags=["ok"], + version=1, + ) + + d = Delta(delta_id="d1", timestamp=time.time(), items=[{"m1": 1.0}]) + + poetic = { + "model_version": "v1", + "training_data_digest": "sha256:deadbeef", + "confidence": 0.95, + "signer": "edge-operator", + } + + env = DeltaEnvelope(protocol_version="PulseMesh/0.1.0", contract=contract, delta=d, poetic_cert=poetic) + + # Ensure envelope preserves poetic_cert + ed = env.to_dict() + assert "poetic_cert" in ed and ed["poetic_cert"]["model_version"] == "v1" + + cat = map_delta_to_catopt(env) + assert isinstance(cat, dict) + assert "Metadata" in cat and cat["Metadata"].get("poetic_cert") is not None + assert cat["Metadata"]["poetic_cert"]["signer"] == "edge-operator"