Add extension profile schema validation

This commit is contained in:
2026-05-15 15:13:03 +02:00
parent 955643554f
commit ab7914890e
7 changed files with 466 additions and 9 deletions

View File

@@ -8,6 +8,7 @@ from tempfile import TemporaryDirectory
from pathlib import Path
from guide_board.discovery import discover_extensions
from guide_board.errors import ValidationError
from guide_board.execution import run_assessment
from guide_board.gates import evaluate_trend_gates
from guide_board.io import load_json
@@ -143,6 +144,55 @@ class CoreArchitectureTests(unittest.TestCase):
self.assertEqual(plan["extension_snapshots"][0]["path"], str(extension_dir))
self.assertEqual([item["result"] for item in evidence], ["skipped", "manual"])
def test_applies_external_extension_profile_schemas(self) -> None:
with TemporaryDirectory() as temporary_directory:
temp_root = Path(temporary_directory)
extension_dir = temp_root / "schema-noop"
_write_schema_extension(extension_dir)
extensions = discover_extensions(ROOT, [extension_dir])
target_path = temp_root / "target.json"
assessment_path = temp_root / "assessment.json"
_write_schema_target(target_path, endpoints=[{
"id": "api",
"url": "http://127.0.0.1:8080",
"binding": "example",
}])
_write_schema_assessment(assessment_path, runtime_policy={"offline": True})
target = validate_target_profile(target_path, extensions)
assessment = validate_assessment_profile(assessment_path, extensions)
plan = build_run_plan(ROOT, target_path, assessment_path, [extension_dir])
self.assertEqual(target["subject_type"], "schema-subject")
self.assertEqual(assessment["runtime_policy"], {"offline": True})
self.assertEqual(plan["extension_snapshots"][0]["id"], "schema-noop")
_write_schema_target(target_path, endpoints=[])
with self.assertRaisesRegex(
ValidationError,
"schema-noop:schema-target profile schema validation failed",
):
validate_target_profile(target_path, extensions)
def test_rejects_extension_profile_schema_paths_outside_extension_root(self) -> None:
with TemporaryDirectory() as temporary_directory:
temp_root = Path(temporary_directory)
extension_dir = temp_root / "schema-noop"
_write_schema_extension(extension_dir, target_schema_path="../outside.schema.json")
target_path = temp_root / "target.json"
_write_schema_target(target_path, endpoints=[{
"id": "api",
"url": "http://127.0.0.1:8080",
"binding": "example",
}])
extensions = discover_extensions(ROOT, [extension_dir])
with self.assertRaisesRegex(
ValidationError,
"profile schema path escapes extension root",
):
validate_target_profile(target_path, extensions)
def test_runs_sample_noop_assessment(self) -> None:
with TemporaryDirectory() as temporary_directory:
result = run_assessment(
@@ -460,5 +510,135 @@ def _write_external_extension(extension_dir: Path) -> None:
)
def _write_schema_extension(
extension_dir: Path,
target_schema_path: str = "schemas/schema-target.schema.json",
) -> None:
extension_dir.mkdir(parents=True, exist_ok=True)
schema_dir = extension_dir / "schemas"
schema_dir.mkdir()
(schema_dir / "schema-target.schema.json").write_text(
json.dumps(
{
"type": "object",
"required": ["subject_type", "endpoints"],
"properties": {
"subject_type": {"enum": ["schema-subject"]},
"endpoints": {"type": "array", "minItems": 1},
},
}
),
encoding="utf-8",
)
(schema_dir / "schema-assessment.schema.json").write_text(
json.dumps(
{
"type": "object",
"required": ["runtime_policy"],
"properties": {
"runtime_policy": {
"type": "object",
"required": ["offline"],
"properties": {"offline": {"type": "boolean"}},
}
},
}
),
encoding="utf-8",
)
(extension_dir / "extension.json").write_text(
json.dumps(
{
"id": "schema-noop",
"name": "Schema No-op",
"version": "0.1.0",
"extension_type": "repository_quality",
"lifecycle_status": "incubating",
"supported_frameworks": ["schema.readiness.v1"],
"authorities": [],
"profile_schemas": [
"target-profile",
"assessment-profile",
{
"id": "schema-target",
"profile_kind": "target",
"path": target_schema_path,
"subject_type": "schema-subject",
},
{
"id": "schema-assessment",
"profile_kind": "assessment",
"path": "schemas/schema-assessment.schema.json",
},
],
"check_groups": [
{
"id": "shape",
"name": "Shape",
"check_type": "repository_quality",
"requirement_refs": ["schema.shape"],
"runner_ref": None,
}
],
"preflight_runner": None,
"runner_entrypoints": [],
"normalizers": [],
"mappings": [],
"report_fragments": [],
"dependencies": [],
"restricted_assets": [],
"certification_boundary": "Test fixture only.",
}
),
encoding="utf-8",
)
def _write_schema_target(path: Path, endpoints: list[dict[str, str]]) -> None:
path.write_text(
json.dumps(
{
"id": "schema-target",
"subject_type": "schema-subject",
"subject_name": "Schema Target",
"environment": "test",
"scope": ["schema"],
"endpoints": endpoints,
"artifacts": [],
"credentials_ref": None,
"declared_capabilities": [],
"known_gaps": [],
}
),
encoding="utf-8",
)
def _write_schema_assessment(path: Path, runtime_policy: dict[str, object]) -> None:
path.write_text(
json.dumps(
{
"id": "schema-assessment",
"framework_refs": ["schema.readiness.v1"],
"extension_refs": ["schema-noop"],
"target_profile_ref": "schema-target",
"selected_check_groups": {"schema-noop": ["shape"]},
"expectations_ref": None,
"waivers_ref": None,
"output_policy": {
"report_formats": ["json", "markdown"],
"artifact_retention": "summary-only",
},
"retention_policy": {
"summary_days": 365,
"raw_artifact_days": 0,
},
"runtime_policy": runtime_policy,
}
),
encoding="utf-8",
)
if __name__ == "__main__":
unittest.main()