artifacts. store and some models. For deployment
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
from .models import (
|
||||
DependencyDiagnostic,
|
||||
DiagnosticSeverity,
|
||||
DriftPolicy,
|
||||
RequiredCapability,
|
||||
WorkflowArtifact,
|
||||
WorkflowDeployment,
|
||||
)
|
||||
from .store import FileWorkflowArtifactStore, WorkflowArtifactStore
|
||||
|
||||
__all__ = [
|
||||
"DependencyDiagnostic",
|
||||
"DiagnosticSeverity",
|
||||
"DriftPolicy",
|
||||
"FileWorkflowArtifactStore",
|
||||
"RequiredCapability",
|
||||
"WorkflowArtifact",
|
||||
"WorkflowArtifactStore",
|
||||
"WorkflowDeployment",
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
JsonObject = dict[str, Any]
|
||||
|
||||
|
||||
class DriftPolicy(StrEnum):
|
||||
"""Policy for dependency drift that is detected but not proven unsafe."""
|
||||
|
||||
BLOCK = "block"
|
||||
WARN = "warn"
|
||||
ALLOW = "allow"
|
||||
|
||||
|
||||
class DiagnosticSeverity(StrEnum):
|
||||
"""Severity level for dependency validation diagnostics."""
|
||||
|
||||
ERROR = "error"
|
||||
WARNING = "warning"
|
||||
|
||||
|
||||
class RequiredCapability(BaseModel):
|
||||
"""Saved contract for one capability an artifact references."""
|
||||
|
||||
logical_source: str
|
||||
capability_name: str
|
||||
kind: Literal["tool", "resource", "prompt", "node_spec", "workflow"]
|
||||
input_schema_hash: str | None = None
|
||||
input_schema_snapshot: JsonObject | None = None
|
||||
output_schema_hash: str | None = None
|
||||
output_schema_snapshot: JsonObject | None = None
|
||||
observed_concrete_source: str | None = None
|
||||
observed_at_epoch_ms: int | None = Field(default=None, ge=0)
|
||||
|
||||
|
||||
class DependencyDiagnostic(BaseModel):
|
||||
"""Machine-readable reason a deployment is degraded or unrunnable."""
|
||||
|
||||
severity: DiagnosticSeverity
|
||||
code: str
|
||||
logical_ref: str
|
||||
bound_source: str | None = None
|
||||
message: str
|
||||
repair_hint: str | None = None
|
||||
|
||||
|
||||
class WorkflowArtifact(BaseModel):
|
||||
"""Immutable saved workflow definition plus dependency contract snapshots."""
|
||||
|
||||
id: str
|
||||
version: int = Field(ge=1)
|
||||
title: str
|
||||
description: str | None = None
|
||||
input_schema: JsonObject
|
||||
output_schema: JsonObject
|
||||
outcomes: tuple[str, ...]
|
||||
plan: JsonObject
|
||||
required_capabilities: dict[str, RequiredCapability] = Field(default_factory=dict)
|
||||
workflow_dependencies: dict[str, int] = Field(default_factory=dict)
|
||||
created_from_catalog_version: str | None = None
|
||||
|
||||
|
||||
class WorkflowDeployment(BaseModel):
|
||||
"""One configured way to run an artifact version in an environment."""
|
||||
|
||||
id: str
|
||||
artifact_id: str
|
||||
artifact_version: int = Field(ge=1)
|
||||
bindings: dict[str, str] = Field(default_factory=dict)
|
||||
drift_policy: DriftPolicy = DriftPolicy.BLOCK
|
||||
@@ -0,0 +1,91 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from .models import WorkflowArtifact, WorkflowDeployment
|
||||
|
||||
|
||||
class WorkflowArtifactStore:
|
||||
"""Storage boundary for workflow artifacts and deployments."""
|
||||
|
||||
def save_artifact(self, artifact: WorkflowArtifact) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_artifact(self, artifact_id: str, version: int) -> WorkflowArtifact:
|
||||
raise NotImplementedError
|
||||
|
||||
def list_artifacts(self) -> list[WorkflowArtifact]:
|
||||
raise NotImplementedError
|
||||
|
||||
def resolve_latest(self, artifact_id: str) -> WorkflowArtifact:
|
||||
raise NotImplementedError
|
||||
|
||||
def save_deployment(self, deployment: WorkflowDeployment) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_deployment(self, deployment_id: str) -> WorkflowDeployment:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FileWorkflowArtifactStore(WorkflowArtifactStore):
|
||||
"""JSON file-backed artifact store for local development and tests."""
|
||||
|
||||
def __init__(self, root: Path) -> None:
|
||||
self.root = root
|
||||
self.artifacts_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.deployments_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@property
|
||||
def artifacts_dir(self) -> Path:
|
||||
return self.root / "workflows"
|
||||
|
||||
@property
|
||||
def deployments_dir(self) -> Path:
|
||||
return self.root / "deployments"
|
||||
|
||||
def save_artifact(self, artifact: WorkflowArtifact) -> None:
|
||||
artifact_dir = self.artifacts_dir / artifact.id
|
||||
artifact_dir.mkdir(parents=True, exist_ok=True)
|
||||
path = artifact_dir / f"{artifact.version}.json"
|
||||
path.write_text(
|
||||
json.dumps(artifact.model_dump(mode="json"), indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
def get_artifact(self, artifact_id: str, version: int) -> WorkflowArtifact:
|
||||
path = self.artifacts_dir / artifact_id / f"{version}.json"
|
||||
if not path.exists():
|
||||
raise KeyError(f"unknown workflow artifact {artifact_id}@{version}")
|
||||
return WorkflowArtifact.model_validate_json(path.read_text(encoding="utf-8"))
|
||||
|
||||
def list_artifacts(self) -> list[WorkflowArtifact]:
|
||||
artifacts: list[WorkflowArtifact] = []
|
||||
for path in sorted(self.artifacts_dir.glob("*/*.json")):
|
||||
artifacts.append(
|
||||
WorkflowArtifact.model_validate_json(path.read_text(encoding="utf-8"))
|
||||
)
|
||||
return artifacts
|
||||
|
||||
def resolve_latest(self, artifact_id: str) -> WorkflowArtifact:
|
||||
versions = [
|
||||
int(path.stem)
|
||||
for path in (self.artifacts_dir / artifact_id).glob("*.json")
|
||||
if path.stem.isdecimal()
|
||||
]
|
||||
if not versions:
|
||||
raise KeyError(f"unknown workflow artifact {artifact_id!r}")
|
||||
return self.get_artifact(artifact_id, max(versions))
|
||||
|
||||
def save_deployment(self, deployment: WorkflowDeployment) -> None:
|
||||
path = self.deployments_dir / f"{deployment.id}.json"
|
||||
path.write_text(
|
||||
json.dumps(deployment.model_dump(mode="json"), indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
def get_deployment(self, deployment_id: str) -> WorkflowDeployment:
|
||||
path = self.deployments_dir / f"{deployment_id}.json"
|
||||
if not path.exists():
|
||||
raise KeyError(f"unknown workflow deployment {deployment_id!r}")
|
||||
return WorkflowDeployment.model_validate_json(path.read_text(encoding="utf-8"))
|
||||
Reference in New Issue
Block a user