117 lines
3.7 KiB
Python
117 lines
3.7 KiB
Python
|
|
"""
|
||
|
|
Traceability models for provenance tracking.
|
||
|
|
|
||
|
|
Implements FR-11.1: Trace artifacts to producing runs, inputs, and validation.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Any, Dict, List, Optional
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class RunSummary:
|
||
|
|
"""Summary of a PromptRun for traceability output."""
|
||
|
|
|
||
|
|
run_id: str
|
||
|
|
template_id: str
|
||
|
|
status: str
|
||
|
|
stage: str
|
||
|
|
parent_run_id: Optional[str]
|
||
|
|
depth: int
|
||
|
|
input_bundle_hash: str
|
||
|
|
started_at: datetime
|
||
|
|
completed_at: Optional[datetime]
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def create(
|
||
|
|
cls,
|
||
|
|
run_id: str,
|
||
|
|
template_id: str,
|
||
|
|
status: str,
|
||
|
|
stage: str,
|
||
|
|
input_bundle_hash: str,
|
||
|
|
started_at: datetime,
|
||
|
|
parent_run_id: Optional[str] = None,
|
||
|
|
depth: int = 0,
|
||
|
|
completed_at: Optional[datetime] = None,
|
||
|
|
) -> "RunSummary":
|
||
|
|
"""Create a RunSummary."""
|
||
|
|
return cls(
|
||
|
|
run_id=run_id,
|
||
|
|
template_id=template_id,
|
||
|
|
status=status,
|
||
|
|
stage=stage,
|
||
|
|
parent_run_id=parent_run_id,
|
||
|
|
depth=depth,
|
||
|
|
input_bundle_hash=input_bundle_hash,
|
||
|
|
started_at=started_at,
|
||
|
|
completed_at=completed_at,
|
||
|
|
)
|
||
|
|
|
||
|
|
def to_dict(self) -> Dict[str, Any]:
|
||
|
|
"""Convert to dictionary."""
|
||
|
|
return {
|
||
|
|
"run_id": self.run_id,
|
||
|
|
"template_id": self.template_id,
|
||
|
|
"status": self.status,
|
||
|
|
"stage": self.stage,
|
||
|
|
"parent_run_id": self.parent_run_id,
|
||
|
|
"depth": self.depth,
|
||
|
|
"input_bundle_hash": self.input_bundle_hash,
|
||
|
|
"started_at": self.started_at.isoformat(),
|
||
|
|
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class ArtifactLineage:
|
||
|
|
"""Lineage record for a single artifact in a trace."""
|
||
|
|
|
||
|
|
artifact_id: str
|
||
|
|
name: str
|
||
|
|
space_id: str
|
||
|
|
artifact_type: str
|
||
|
|
content_digest: str
|
||
|
|
role: str # "input", "output", "template"
|
||
|
|
|
||
|
|
def to_dict(self) -> Dict[str, Any]:
|
||
|
|
"""Convert to dictionary."""
|
||
|
|
return {
|
||
|
|
"artifact_id": self.artifact_id,
|
||
|
|
"name": self.name,
|
||
|
|
"space_id": self.space_id,
|
||
|
|
"artifact_type": self.artifact_type,
|
||
|
|
"content_digest": self.content_digest,
|
||
|
|
"role": self.role,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class ProvenanceTrace:
|
||
|
|
"""Complete provenance trace for an artifact."""
|
||
|
|
|
||
|
|
artifact_id: str
|
||
|
|
producing_run: Optional[RunSummary] = None
|
||
|
|
template: Optional[ArtifactLineage] = None
|
||
|
|
input_artifacts: List[ArtifactLineage] = field(default_factory=list)
|
||
|
|
output_artifacts: List[ArtifactLineage] = field(default_factory=list)
|
||
|
|
generator_runs: List[RunSummary] = field(default_factory=list)
|
||
|
|
validation_results: List[Dict[str, Any]] = field(default_factory=list)
|
||
|
|
impact_debt: List[Dict[str, Any]] = field(default_factory=list)
|
||
|
|
dependency_chain: List[str] = field(default_factory=list)
|
||
|
|
|
||
|
|
def to_dict(self) -> Dict[str, Any]:
|
||
|
|
"""Convert to dictionary."""
|
||
|
|
return {
|
||
|
|
"artifact_id": self.artifact_id,
|
||
|
|
"producing_run": self.producing_run.to_dict() if self.producing_run else None,
|
||
|
|
"template": self.template.to_dict() if self.template else None,
|
||
|
|
"input_artifacts": [a.to_dict() for a in self.input_artifacts],
|
||
|
|
"output_artifacts": [a.to_dict() for a in self.output_artifacts],
|
||
|
|
"generator_runs": [r.to_dict() for r in self.generator_runs],
|
||
|
|
"validation_results": self.validation_results,
|
||
|
|
"impact_debt": self.impact_debt,
|
||
|
|
"dependency_chain": self.dependency_chain,
|
||
|
|
}
|