-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathforge_schema.py
More file actions
134 lines (104 loc) · 5.01 KB
/
forge_schema.py
File metadata and controls
134 lines (104 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""Nova Forge schema validation — loads V11 JSON schemas and validates data.
Provides a SchemaValidator that loads all 8 schemas from the schemas/ directory
and exposes validate() for any schema by name. Used by TaskStore (task-metadata),
formations (formation-registry, formation-config), AutonomyManager (autonomy-state),
and AgentRegistry (agent-registry).
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
import jsonschema
from jsonschema import Draft202012Validator, ValidationError
logger = logging.getLogger(__name__)
# ── Schema names ────────────────────────────────────────────────────────────
SCHEMA_NAMES = (
"task-metadata",
"task-state",
"agent-registry",
"autonomy-state",
"formation-config",
"formation-registry",
"memory-index",
"tool-policy",
)
# ── Schema directory resolution ─────────────────────────────────────────────
_SCHEMAS_DIR = Path(__file__).parent / "schemas"
def _load_schema(name: str, schemas_dir: Path | None = None) -> dict:
"""Load a single JSON schema by name."""
base = schemas_dir or _SCHEMAS_DIR
path = base / f"{name}.schema.json"
if not path.exists():
raise FileNotFoundError(f"Schema not found: {path}")
return json.loads(path.read_text(encoding="utf-8"))
# ── SchemaValidator ─────────────────────────────────────────────────────────
class SchemaValidator:
"""Validates data against V11/Nova Forge JSON schemas.
Usage::
sv = SchemaValidator()
errors = sv.validate("task-metadata", {"project": "myapp", "sprint": "sprint-01-init", "risk": "low"})
if errors:
print(f"Validation failed: {errors}")
"""
def __init__(self, schemas_dir: Path | None = None) -> None:
self._schemas: dict[str, dict] = {}
self._validators: dict[str, Draft202012Validator] = {}
self._schemas_dir = schemas_dir or _SCHEMAS_DIR
self._load_all()
def _load_all(self) -> None:
"""Load all schemas from the schemas directory."""
for name in SCHEMA_NAMES:
try:
schema = _load_schema(name, self._schemas_dir)
self._schemas[name] = schema
self._validators[name] = Draft202012Validator(schema)
logger.debug("Loaded schema: %s", name)
except FileNotFoundError:
logger.warning("Schema not found: %s — skipping", name)
except json.JSONDecodeError as exc:
logger.warning("Invalid JSON in schema %s: %s", name, exc)
@property
def available(self) -> list[str]:
"""Return names of successfully loaded schemas."""
return sorted(self._schemas.keys())
def get_schema(self, name: str) -> dict:
"""Return the raw schema dict for *name*.
Raises:
KeyError: If the schema was not loaded.
"""
if name not in self._schemas:
raise KeyError(
f"Unknown schema {name!r}. Available: {', '.join(self.available)}"
)
return self._schemas[name]
def validate(self, schema_name: str, data: Any) -> list[str]:
"""Validate *data* against the named schema.
Returns:
Empty list if valid, or a list of human-readable error strings.
"""
if schema_name not in self._validators:
return [f"Schema {schema_name!r} not loaded"]
validator = self._validators[schema_name]
errors: list[str] = []
for error in sorted(validator.iter_errors(data), key=lambda e: list(e.path)):
path = ".".join(str(p) for p in error.absolute_path) or "(root)"
errors.append(f"{path}: {error.message}")
return errors
def is_valid(self, schema_name: str, data: Any) -> bool:
"""Quick boolean check — is *data* valid against the named schema?"""
if schema_name not in self._validators:
return False
return self._validators[schema_name].is_valid(data)
def validate_task_metadata(self, metadata: dict) -> list[str]:
"""Convenience: validate task metadata dict."""
return self.validate("task-metadata", metadata)
def validate_autonomy_state(self, state: dict) -> list[str]:
"""Convenience: validate autonomy state dict."""
return self.validate("autonomy-state", state)
def validate_formation_registry(self, registry: dict) -> list[str]:
"""Convenience: validate formation registry dict."""
return self.validate("formation-registry", registry)
def validate_agent_registry(self, registry: dict) -> list[str]:
"""Convenience: validate agent registry dict."""
return self.validate("agent-registry", registry)