atlas-iac/testing/tests/test_hermes_hux_contracts.py
jenkins 1faf2a8a84 hermes(hux): add contract-only foundation for the chat UX program
Schemas, examples and a flag registry for the twelve HUX cards, a dependency-free
validator, the governance rules (memory ledger, autonomy matrix, friendly modes
mapped to real Switchyard routes, privacy defaults, suggestion gating, release
state machine) and the contract doc UI work codes against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNPhwu2bsaRNg3DETSAZoM
2026-08-23 22:07:38 -03:00

325 lines
16 KiB
Python

"""Contract tests for the HUX schemas and the governance rules behind them.
Every schema must stay inside the keyword subset the in-repo validator
understands, every shipped example must validate, every friendly mode must
resolve to a Switchyard route that really exists, and the state machines must
refuse the transitions that would let merged/built/deployed be confused.
"""
from __future__ import annotations
import copy
import importlib.util
import json
import sys
import tomllib
from datetime import datetime, timezone
from pathlib import Path
import pytest
import yaml
ROOT = Path(__file__).resolve().parents[2]
SCRIPTS = ROOT / "services" / "hermes" / "scripts"
CONTRACTS = ROOT / "services" / "hermes" / "contracts" / "hux"
def _load(name: str):
spec = importlib.util.spec_from_file_location(name, SCRIPTS / f"{name}.py")
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
contracts = _load("hux_contracts")
policy = _load("hux_policy")
SCHEMAS = contracts.load_all()
EXAMPLES = {path.stem: json.loads(path.read_text()) for path in sorted((CONTRACTS / "examples").glob("*.json"))}
NOW = datetime(2026, 8, 23, 12, 0, tzinfo=timezone.utc)
def _switchyard_route_ids() -> set[str]:
manifest = next(iter(yaml.safe_load_all((ROOT / "services/hermes/switchyard-configmap.yaml").read_text())))
routes = tomllib.loads(manifest["data"]["routes.toml"])["routes"]
return {route["id"] for route in routes.values()}
# --- schema hygiene -----------------------------------------------------------
@pytest.mark.parametrize("name", contracts.SCHEMA_FILES)
def test_schema_uses_only_supported_keywords(name):
assert contracts.unsupported_keywords(SCHEMAS[name]) == []
assert SCHEMAS[name]["$id"].endswith(name)
def test_every_schema_file_is_registered():
on_disk = {path.name for path in CONTRACTS.glob("*.schema.json")}
assert on_disk == set(contracts.SCHEMA_FILES)
def test_walker_flags_unknown_keywords():
assert contracts.unsupported_keywords({"type": "object", "properties": {"x": {"format": "email"}}}) == ["#/properties/x/format"]
# --- examples -----------------------------------------------------------------
@pytest.mark.parametrize("name", sorted(EXAMPLES))
def test_example_validates_by_its_schema_field(name):
assert contracts.validate_record(EXAMPLES[name], SCHEMAS) == []
def test_every_record_schema_has_an_example():
covered = {example["schema"] for example in EXAMPLES.values()}
covered |= {"hux.mode.v1", "hux.privacy_policy.v1"}
assert set(contracts.record_schema_names(SCHEMAS)) == covered
def test_validate_record_rejects_unknown_or_missing_schema():
assert contracts.validate_record({"schema": "hux.nope.v1"}, SCHEMAS) == ["$: unknown record schema 'hux.nope.v1'"]
assert contracts.validate_record(["not", "a", "record"], SCHEMAS)[0].startswith("$: record has no")
def test_default_loading_paths_work():
assert contracts.validate_record(EXAMPLES["event"]) == []
assert contracts.validate("event.schema.json", EXAMPLES["event"]) == []
assert "HUX-01" in {card["card"] for card in contracts.load_flags()["cards"]}
# --- validator keyword coverage ----------------------------------------------
def _broken(name: str, mutate) -> list[str]:
record = copy.deepcopy(EXAMPLES[name])
mutate(record)
return contracts.validate_record(record, SCHEMAS)
def test_validator_catches_each_constraint_class():
assert any("expected integer" in e for e in _broken("event", lambda r: r.update(seq=True)))
assert any("below minimum" in e for e in _broken("event", lambda r: r.update(seq=-1)))
assert any("not in enum" in e for e in _broken("event", lambda r: r.update(kind="tool.dance")))
assert any("unexpected property" in e for e in _broken("event", lambda r: r.update(extra=1)))
assert any("missing required" in e for e in _broken("event", lambda r: r.pop("summary")))
assert any("longer than" in e for e in _broken("event", lambda r: r.update(summary="x" * 281)))
assert any("shorter than" in e for e in _broken("event", lambda r: r.update(summary="")))
assert any("does not match" in e for e in _broken("event", lambda r: r.update(ts="yesterday")))
wrong_const = copy.deepcopy(EXAMPLES["event"])
wrong_const["schema"] = "hux.event.v0"
assert any("expected constant" in e for e in contracts.validate("event.schema.json", wrong_const, SCHEMAS))
assert any("more than" in e for e in _broken("event", lambda r: r.update(evidence=[{"kind": "run", "id": "r"}] * 65)))
assert any("not unique" in e for e in _broken("project", lambda r: r.update(tags=["a", "a"])))
assert any("fewer than" in e for e in _broken("memory", lambda r: r.update(audit=[])))
assert any("above maximum" in e for e in _broken("suggestion", lambda r: r.update(priority=101)))
assert any("expected object" in e for e in _broken("event", lambda r: r.update(provenance="x")))
def test_one_of_requires_exactly_one_branch():
ambiguous = {"schema": "hux.policy.v1"}
errors = contracts.validate("permission.schema.json", ambiguous, SCHEMAS)
assert any("matched 0 oneOf branches" in e for e in errors)
def test_pointer_validation_and_bad_refs():
assert contracts.validate("permission.schema.json", EXAMPLES["approval"], SCHEMAS, "/$defs/approval") == []
with pytest.raises(contracts.ContractError):
contracts.validate("permission.schema.json", {}, SCHEMAS, "/$defs/missing")
with pytest.raises(contracts.ContractError):
contracts._resolve_ref("nowhere.schema.json#/x", "event.schema.json", SCHEMAS)
with pytest.raises(contracts.ContractError):
contracts._validate({"type": "date"}, "x", "$", [], "event.schema.json", SCHEMAS)
# --- memory ledger -------------------------------------------------------------
def test_memory_state_machine_is_append_only():
allowed = {(a, b) for a, bs in policy.MEMORY_TRANSITIONS.items() for b in bs}
assert allowed == {("proposed", "active"), ("proposed", "rejected"), ("active", "expired"), ("active", "forgotten"), ("expired", "forgotten")}
assert policy.transition_allowed(policy.MEMORY_TRANSITIONS, "proposed", "active")
assert not policy.transition_allowed(policy.MEMORY_TRANSITIONS, "forgotten", "active")
assert not policy.transition_allowed(policy.MEMORY_TRANSITIONS, "unknown", "active")
def test_memory_policy_rules():
base = EXAMPLES["memory"]
assert policy.memory_policy_violations(base) == []
restricted = {**base, "sensitivity": "restricted"}
assert "restricted content may not be remembered" in policy.memory_policy_violations(restricted)
lax = {**base, "sensitivity": "sensitive", "approval_mode": "automatic", "ttl": {"policy": "never"}}
problems = policy.memory_policy_violations(lax)
assert "sensitive memory requires approval_mode=ask" in problems
assert "sensitive memory must expire or decay" in problems
creds = {**base, "topic": "credentials"}
assert "topic credentials may not be written to memory" in policy.memory_policy_violations(creds)
assert policy.memory_policy_violations({**creds, "status": "rejected"}) == []
assert "ttl.policy=expires_at requires expires_at" in policy.memory_policy_violations({**base, "ttl": {"policy": "expires_at"}})
assert "ttl.policy=decay requires decay_days" in policy.memory_policy_violations({**base, "ttl": {"policy": "decay"}})
assert "forgotten entries must drop their content" in policy.memory_policy_violations({**base, "status": "forgotten"})
assert policy.memory_policy_violations({**base, "status": "forgotten", "content": ""}) == []
# --- autonomy -------------------------------------------------------------------
def test_capability_matrix_shape():
matrix = policy.default_capability_matrix()
assert set(matrix) == {"ask_first", "safe", "autonomous"}
for level, row in matrix.items():
assert set(row) == set(policy.CAPABILITIES)
assert row["deploy"] == "ask", level
assert row["read_files"] == "allow"
assert matrix["ask_first"]["shell"] == "ask"
assert matrix["safe"]["network"] == "deny"
assert matrix["autonomous"]["shell"] == "allow"
assert matrix["autonomous"]["network"] == "allow"
assert set(policy.CAPABILITIES) == set(SCHEMAS["permission.schema.json"]["$defs"]["capability"]["enum"])
def test_effective_decision_honours_grants_expiry_and_deny():
base = copy.deepcopy(EXAMPLES["policy"])
assert policy.effective_decision(base, "network", NOW) == "ask"
assert policy.effective_decision(base, "network", datetime(2026, 9, 1, tzinfo=timezone.utc)) == "deny"
base["grants"] = [{"capability": "shell", "decision": "allow"}, {"capability": "shell", "decision": "deny"}]
assert policy.effective_decision(base, "shell", NOW) == "deny"
base["grants"] = [{"capability": "deploy", "decision": "allow"}]
assert policy.effective_decision(base, "deploy", NOW) == "ask"
base["grants"] = [{"capability": "write_files", "decision": "allow"}]
assert policy.effective_decision(base, "write_files") == "allow"
assert policy.effective_decision(base, "read_files") == "allow"
def test_approval_states_are_terminal_after_pending():
for state, nexts in policy.APPROVAL_TRANSITIONS.items():
assert bool(nexts) == (state == "pending")
# --- friendly modes ------------------------------------------------------------
@pytest.mark.parametrize("mode", sorted(policy.MODE_CATALOG))
def test_mode_contract_validates_and_resolves_to_real_switchyard_route(mode):
record = policy.mode_contract(mode)
assert contracts.validate_record(record, SCHEMAS) == []
assert record["switchyard"]["route_id"] in _switchyard_route_ids()
assert "codex" not in record["intent"].lower() and "claude" not in record["intent"].lower()
def test_private_mode_never_leaves_the_cluster():
record = policy.mode_contract("private")
assert record["constraints"] == {
"providers": ["local"], "local_only": True, "effort": {"min": "low", "max": "medium"},
"tools": {"web": "denied", "shell": "denied", "artifacts": "allowed", "delegate": "denied"},
"memory": {"read": False, "write": False}, "citations_required": False, "retention": "ephemeral",
}
with pytest.raises(ValueError):
policy.mode_contract("private", "atlas/manual/claude/opus/high")
assert policy.mode_contract("private", "atlas/manual/local/qwen-14b")["switchyard"]["override_route_id"] == "atlas/manual/local/qwen-14b"
def test_hosted_modes_stay_provider_neutral_and_overridable():
for mode in ("fast", "thoughtful", "research", "create"):
assert set(policy.MODE_CATALOG[mode]["providers"]) == {"codex", "claude"}
override = policy.mode_contract("thoughtful", "atlas/manual/codex/sol/high")
assert override["switchyard"]["override_route_id"] in _switchyard_route_ids()
assert policy.MODE_CATALOG["research"]["citations_required"]
assert policy.MODE_CATALOG["research"]["tools"]["web"] == "required"
def test_effort_bands():
assert policy.effort_within("fast", "low")
assert not policy.effort_within("fast", "xhigh")
assert policy.effort_within("research", "xhigh")
assert not policy.effort_within("research", "medium")
# --- privacy -------------------------------------------------------------------
def test_privacy_policy_validates_and_is_strict_for_restricted_topics():
record = policy.privacy_policy()
assert contracts.validate_record(record, SCHEMAS) == []
topics = {row["topic"]: row for row in record["topics"]}
assert set(topics) == set(SCHEMAS["privacy.schema.json"]["$defs"]["topic"]["enum"])
for row in topics.values():
assert (row["sensitivity"] == "restricted") == (row["memory_write"] == "deny")
assert row["decay_days"] <= 30
assert record["topic_scoping"]["cross_surface_sharing"] == "never"
# --- suggestions ---------------------------------------------------------------
def test_suggestion_gating():
suggestion = EXAMPLES["suggestion"]
assert policy.suggestion_allowed(suggestion, None, NOW)
state = copy.deepcopy(EXAMPLES["suggestion_state"])
assert policy.suggestion_allowed(suggestion, state, NOW)
assert not policy.suggestion_allowed(suggestion, {**state, "never_again": True}, NOW)
assert not policy.suggestion_allowed(suggestion, {**state, "dismissed_at": "2026-08-22T11:00:00Z"}, NOW)
assert not policy.suggestion_allowed(suggestion, {**state, "shows": 2}, NOW)
assert not policy.suggestion_allowed(suggestion, {**state, "last_shown_at": "2026-08-23T11:30:00Z"}, NOW)
assert policy.suggestion_allowed(suggestion, {**state, "last_shown_at": None}, NOW)
# --- release follow-through ----------------------------------------------------
def test_release_cannot_skip_states_or_claim_deployed_without_evidence():
record = copy.deepcopy(EXAMPLES["release"])
assert policy.release_transition_problems(record, "verified") == ["verified requires evidence.harbor_digest", "harbor_digest must equal image_digest"]
assert "built -> deployed skips or reverses the release order" in policy.release_transition_problems(record, "deployed")
record["evidence"]["harbor_digest"] = record["evidence"]["image_digest"]
assert policy.release_transition_problems(record, "verified") == []
record["state"] = "deployed"
record["evidence"]["pod_digest"] = "sha256:" + "0" * 64
assert "pod_digest must equal image_digest" in policy.release_transition_problems(record, "converged")
record["evidence"]["pod_digest"] = record["evidence"]["image_digest"]
assert policy.release_transition_problems(record, "converged") == []
record["state"] = "converged"
record["evidence"]["health_check"] = {"url": "https://chat.bstein.dev/healthz", "status": "fail", "at": "2026-08-23T10:00:00Z"}
assert policy.release_transition_problems(record, "live_verified") == ["health_check must pass"]
record["evidence"]["health_check"]["status"] = "pass"
assert policy.release_transition_problems(record, "live_verified") == []
assert policy.release_transition_problems(record, "rolled_back") == ["rolled_back requires evidence.rollback_target"]
record["state"] = "rolled_back"
assert policy.release_transition_problems(record, "merged")[0].startswith("a rolled back release is terminal")
assert policy.release_transition_problems({"state": "merged", "evidence": {}}, "rolled_back")[0].startswith("nothing to roll back")
assert policy.release_transition_problems({"state": "reviewed", "evidence": {}}, "merged") == ["merged requires evidence.merge_commit"]
# --- flags ---------------------------------------------------------------------
def test_flag_registry_matches_program():
registry = policy.flag_registry()
assert sorted(registry) == [f"HUX-{n:02d}" for n in range(1, 13)]
waves = {"A": {"HUX-01", "HUX-02", "HUX-05", "HUX-10", "HUX-11", "HUX-12"}, "B": {"HUX-03", "HUX-04", "HUX-06", "HUX-08"}, "C": {"HUX-07", "HUX-09"}}
for wave, cards in waves.items():
assert {card for card, entry in registry.items() if entry["wave"] == wave} == cards
for entry in registry.values():
assert entry["default"] is False
assert entry["backend_owner"] == "claude" and entry["frontend_owner"] == "codex"
assert entry["rollback"]
for schema in entry["contracts"]:
assert schema in contracts.SCHEMA_FILES
for dep in entry["depends_on"]:
assert dep in registry
order = policy.dependency_order()
assert order.index("HUX-11") < order.index("HUX-01") < order.index("HUX-05")
def test_dependency_cycle_is_rejected(monkeypatch):
registry = policy.flag_registry()
registry["HUX-11"]["depends_on"] = ["HUX-01"]
monkeypatch.setattr(policy, "flag_registry", lambda: registry)
with pytest.raises(ValueError):
policy.dependency_order()
def test_flags_default_off_and_require_dependencies():
assert policy.enabled_flags({}) == set()
assert policy.enabled_flags({"HUX_FLAGS": "hux.activity_timeline, hux.bogus"}) == {"hux.activity_timeline"}
assert not policy.flag_enabled("hux.activity_timeline", {"HUX_FLAGS": "hux.activity_timeline"})
assert policy.flag_enabled("hux.activity_timeline", {"HUX_FLAGS": "hux.activity_timeline,hux.foundation"})
assert not policy.flag_enabled("hux.bogus", {"HUX_FLAGS": "hux.bogus"})
assert not policy.flag_enabled("hux.foundation", {"HUX_FLAGS": ""})
def test_flags_read_process_environment(monkeypatch):
monkeypatch.setenv("HUX_FLAGS", "hux.foundation")
assert policy.flag_enabled("hux.foundation")