from __future__ import annotations import copy import json from pathlib import Path import pytest from jsonschema import Draft202012Validator from scripts.audit_projectbrain_contracts import audit ROOT = Path(__file__).resolve().parents[1] SNAPSHOT = ROOT / "tests/fixtures/projectbrain-discovery-20260908.json" def test_live_discovery_fields_are_available_in_reviewed_catalog() -> None: result = audit(json.loads(SNAPSHOT.read_text()), ROOT / "catalog") assert result == {"passed": True, "toolCount": 57, "issues": []} def test_every_required_projectbrain_argument_has_a_field_definition() -> None: for path in (ROOT / "catalog/tools").glob("projectbrain-*.yaml"): schema = json.loads(path.read_text())["spec"]["inputSchema"] Draft202012Validator.check_schema(schema) assert set(schema.get("required", [])) <= set(schema.get("properties", {})), path.name @pytest.mark.parametrize("drift", ["field", "required", "tool", "duplicate", "missing"]) def test_audit_detects_upstream_evolution_without_publishing_it(drift: str) -> None: discovery = copy.deepcopy(json.loads(SNAPSHOT.read_text())) tool = discovery["tools"][0] if drift == "field": tool["inputSchema"]["properties"]["future_option"] = {"type": "boolean"} elif drift == "required": tool["inputSchema"]["required"] = ["future_option"] elif drift == "tool": tool["name"] = "projectbrain.unreviewed" elif drift == "duplicate": discovery["tools"].append(tool) else: discovery["tools"].pop() assert audit(discovery, ROOT / "catalog")["passed"] is False def validator(slug: str) -> Draft202012Validator: spec = json.loads((ROOT / "catalog/tools" / f"projectbrain-{slug}.yaml").read_text())["spec"] return Draft202012Validator(spec["inputSchema"]) def test_compact_state_and_run_budget_options_are_usable_and_bounded() -> None: state = validator("autonomy-state-get") state.validate({"project_id": "demo", "view": "summary", "item_limit": 1, "event_limit": 0}) assert not state.is_valid({"project_id": "demo", "item_limit": 26}) assert not state.is_valid({"project_id": "demo", "view": "everything"}) configure = validator("autonomy-configure") configure.validate({"project_id": "demo", "max_work_items_per_run": 3, "max_run_minutes": 15}) assert not configure.is_valid({"project_id": "demo", "max_run_minutes": 56}) def test_changeset_contract_rejects_unfenced_update_and_unknown_fields() -> None: schema = validator("delivery-changeset-apply") payload = { "project_id": "demo", "delivery_id": "delivery-1", "branch": "chatgpt/demo", "expected_head_sha": "a" * 40, "message": "Update docs", "operations": [{"operation": "update", "path": "docs/example.md", "content": "text"}], } assert not schema.is_valid(payload) payload["operations"][0]["expected_sha"] = "b" * 40 schema.validate(payload) payload["operations"][0]["unknown_option"] = True assert not schema.is_valid(payload)