evaluate_names_absent returned PASS when its step exited 0 with no output, so five mandatory checks - the ones asserting that provider API keys, forge credentials, a cluster-admin binding, and shared coordinator state are absent - could report a pass on no evidence and turn a NO_GO into a GO. Both name rules now resolve their step through one guard in _line_step, so zero observations are NOT_RUN. Regressions pin all five real catalog specs plus both reachable silence paths: a POSIX pipeline whose status comes from its last stage, and a drifted kubectl -o jsonpath. The pool claim projection emits one <volume>=<claim> line per template volume so a volume without a PVC still counts as an observation rather than reading as drift. Also closes the review's reachable hardening and evidence defects: - pin Gitea paths to atlas/titan-iac on an exact segment boundary and reject relative segments, including percent-encoded ones - forbid impersonation structurally in every mode and vantage; the inner command of kubectl exec is re-checked rather than exempted, and validate_catalog no longer guards only the operator vantage - drop flux and helm from the binary allowlist; they had no pinned release digest, so no allowlisted binary can now be admitted that the executor would refuse to attest - remove the inert --concurrency and --expect-telegram-sessions flags and the dead concurrency bound; Telegram continuity stays mandatory - read the ephemeral pull index page by page, treat the create response as an authoritative source for the pull number, close every number either source names, and surface residue_ref plus exact manual_cleanup commands when creation is uncertain - keep executable_path and executable_sha256 on unrecorded bulk-evidence steps so withholding bytes never withholds binary attestation - revert the repo-wide hygiene legacy-exception mechanism; the contract change here is purely additive and the four pre-existing over-cap files are left to the canonical contract change in PR #14/#15 - correct the runbook ruff format scope so the documented command passes Split hermes_handoff_arming.py out of hermes_handoff_ephemeral.py to keep both modules under the 500-line cap. All 16 handoff modules hold at least 95% line and branch coverage; the mutation gate is 13/13. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
497 lines
16 KiB
Python
497 lines
16 KiB
Python
"""Contracts for handoff acceptance orchestration: vantages, catalog, report."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import subprocess
|
|
from dataclasses import replace
|
|
|
|
from testing.tests.test_hermes_handoff_support import (
|
|
FakeClock,
|
|
FakeSpawn,
|
|
load_handoff_module,
|
|
step,
|
|
)
|
|
|
|
harness_run = load_handoff_module("hermes_handoff_run")
|
|
catalog = load_handoff_module("hermes_handoff_catalog")
|
|
exec_module = load_handoff_module("hermes_handoff_exec")
|
|
model = load_handoff_module("hermes_handoff_model")
|
|
|
|
NOW = dt.datetime(2026, 8, 17, 12, 0, 0, tzinfo=dt.timezone.utc)
|
|
TARGETS = catalog.Targets(now=NOW)
|
|
|
|
|
|
def completed(stdout: str = "", stderr: str = "", returncode: int = 0):
|
|
return subprocess.CompletedProcess(
|
|
args=[], returncode=returncode, stdout=stdout, stderr=stderr
|
|
)
|
|
|
|
|
|
def whoami(username: str) -> object:
|
|
return completed(stdout=username)
|
|
|
|
|
|
def runner_for(results: list, **kwargs):
|
|
return exec_module.Runner(
|
|
clock=FakeClock(),
|
|
spawn=FakeSpawn(results),
|
|
environ={},
|
|
deadline_seconds=3600,
|
|
attestor=lambda command, _environment: exec_module.Attestation(
|
|
command, "a" * 64
|
|
),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
def check(
|
|
rule: str,
|
|
expect: dict | None = None,
|
|
steps=(),
|
|
identifier="test.check",
|
|
mandatory=True,
|
|
):
|
|
return model.CheckSpec(
|
|
id=identifier,
|
|
title="t",
|
|
group="g",
|
|
rule=rule,
|
|
steps=tuple(steps),
|
|
expect=expect or {},
|
|
mandatory=mandatory,
|
|
)
|
|
|
|
|
|
def test_the_shipped_catalog_is_valid_and_covers_every_group() -> None:
|
|
specs = harness_run.build_catalog(TARGETS)
|
|
assert harness_run.validate_catalog(specs) == []
|
|
groups = {spec.group for spec in specs}
|
|
assert {
|
|
"baseline",
|
|
"identity",
|
|
"routing",
|
|
"scopes",
|
|
"access-denied",
|
|
"gitops",
|
|
} <= groups
|
|
assert {
|
|
"forge",
|
|
"nodes",
|
|
"build",
|
|
"reliability",
|
|
"pool",
|
|
"surfaces",
|
|
"ephemeral",
|
|
} <= groups
|
|
|
|
|
|
def test_catalog_validation_names_every_structural_problem() -> None:
|
|
broken = [
|
|
check("no-such-rule", identifier="a.dup"),
|
|
check("allowed", identifier="a.dup"),
|
|
check("allowed", steps=(step("s", vantage="nowhere"),), identifier="b.vantage"),
|
|
check("allowed", steps=(step("s", kind="guess"),), identifier="c.kind"),
|
|
check(
|
|
"denied", steps=(step("s", kind=model.REVIEW),), identifier="d.review-only"
|
|
),
|
|
model.CheckSpec(
|
|
id="e.impersonation",
|
|
title="t",
|
|
group="g",
|
|
rule="allowed",
|
|
steps=(
|
|
model.Step(
|
|
key="s",
|
|
vantage=catalog.OPERATOR,
|
|
argv=("kubectl", "get", "ns", "--as", "x"),
|
|
),
|
|
),
|
|
),
|
|
]
|
|
|
|
problems = " | ".join(harness_run.validate_catalog(broken))
|
|
|
|
assert "duplicate check id" in problems
|
|
assert "unknown rule" in problems
|
|
assert "unknown vantage" in problems
|
|
assert "unknown kind" in problems
|
|
assert "needs a real attempt" in problems
|
|
assert "impersonates from the operator vantage" in problems
|
|
|
|
|
|
def test_target_validation_rejects_every_vacuous_or_unbound_release_input() -> None:
|
|
broken = replace(
|
|
TARGETS,
|
|
repo="atlas/other",
|
|
remote="upstream",
|
|
reviewed_pr_number=20,
|
|
reviewed_head_ref="other",
|
|
baseline_commit="bad",
|
|
remote_main_sha="bad",
|
|
reviewed_head_sha="bad",
|
|
build_sha="bad",
|
|
agent_image="latest",
|
|
deployment_revision="0",
|
|
node_count=1,
|
|
pool_replicas=1,
|
|
chat_config_revision="",
|
|
pool_worker_env=(),
|
|
dependency_heads=((14, "bad"), (14, "bad")),
|
|
max_evidence_age_seconds=0,
|
|
)
|
|
problems = harness_run.validate_targets(broken)
|
|
assert len(problems) >= 12
|
|
sha = "1" * 40
|
|
valid = replace(
|
|
TARGETS,
|
|
remote_main_sha=sha,
|
|
reviewed_head_sha=sha,
|
|
build_sha=sha,
|
|
agent_image=f"registry.example/hermes:git-{sha}-build-7@sha256:{'2' * 64}",
|
|
deployment_revision="1",
|
|
chat_config_revision="rev",
|
|
dependency_heads=tuple(
|
|
(number, sha) for number in TARGETS.dependency_pull_requests
|
|
),
|
|
)
|
|
assert harness_run.validate_targets(valid) == []
|
|
wrong_image_source = replace(
|
|
valid,
|
|
agent_image=f"registry.example/hermes:git-{'2' * 40}-build-7@sha256:{'2' * 64}",
|
|
)
|
|
assert "image tag/digest" in " ".join(
|
|
harness_run.validate_targets(wrong_image_source)
|
|
)
|
|
wrong_build = replace(valid, build_sha="2" * 40)
|
|
assert "build SHA" in " ".join(harness_run.validate_targets(wrong_build))
|
|
malformed_heads = replace(
|
|
valid,
|
|
dependency_heads=tuple(
|
|
(number, "bad") for number in valid.dependency_pull_requests
|
|
),
|
|
)
|
|
assert "dependency heads" in " ".join(harness_run.validate_targets(malformed_heads))
|
|
|
|
|
|
def test_identity_resolution_extracts_only_the_username() -> None:
|
|
runner = runner_for([whoami("system:serviceaccount:hermes:hermes-agent")])
|
|
username, detail = harness_run.resolve_identity(
|
|
runner, exec_module.operator_vantage()
|
|
)
|
|
assert (username, detail) == ("system:serviceaccount:hermes:hermes-agent", "")
|
|
|
|
failed = harness_run.resolve_identity(
|
|
runner_for([completed(stderr="no access", returncode=1)]),
|
|
exec_module.operator_vantage(),
|
|
)
|
|
assert failed[0] == "" and "no access" in failed[1]
|
|
|
|
unparsable = harness_run.resolve_identity(
|
|
runner_for([completed(stdout="two names")]), exec_module.operator_vantage()
|
|
)
|
|
assert unparsable[0] == "" and "no username" in unparsable[1]
|
|
|
|
nameless = harness_run.resolve_identity(
|
|
runner_for([completed(stdout="")]), exec_module.operator_vantage()
|
|
)
|
|
assert nameless[0] == "" and "no username" in nameless[1]
|
|
|
|
|
|
def test_vantages_resolve_to_running_pods_and_record_what_is_missing() -> None:
|
|
runner = runner_for(
|
|
[
|
|
whoami("kubernetes-admin"),
|
|
completed(stdout="pod/hermes-agent-2\npod/hermes-agent-1\n"),
|
|
whoami("system:serviceaccount:hermes:hermes-agent"),
|
|
completed(stdout="pod/hermes-switchyard-1\n"),
|
|
completed(stderr="no pods", returncode=1),
|
|
completed(stdout="pod/hermes-chat-tenant-0\n"),
|
|
]
|
|
)
|
|
|
|
vantages, records = harness_run.resolve_vantages(runner, TARGETS)
|
|
by_name = {record.name: record for record in records}
|
|
|
|
assert set(vantages) == {
|
|
catalog.OPERATOR,
|
|
catalog.SELF,
|
|
catalog.SWITCHYARD,
|
|
catalog.CHAT,
|
|
}
|
|
assert by_name[catalog.SELF].identity == "system:serviceaccount:hermes:hermes-agent"
|
|
assert "hermes-agent-1" in by_name[catalog.SELF].description # deterministic pick
|
|
assert by_name[catalog.NODE].available is False
|
|
assert by_name[catalog.CHAT].available is True
|
|
|
|
|
|
def test_an_unreachable_operator_stops_vantage_resolution_immediately() -> None:
|
|
runner = runner_for([completed(stderr="connection refused", returncode=1)])
|
|
|
|
vantages, records = harness_run.resolve_vantages(runner, TARGETS)
|
|
|
|
assert set(vantages) == {catalog.OPERATOR}
|
|
assert records[0].available is False
|
|
assert harness_run.vantage_problems(records)[0].startswith(
|
|
"the operator vantage is unavailable"
|
|
)
|
|
|
|
|
|
def test_a_missing_chat_pod_is_recorded_rather_than_assumed_present() -> None:
|
|
runner = runner_for(
|
|
[
|
|
whoami("kubernetes-admin"),
|
|
completed(stdout="pod/hermes-agent-1\n"),
|
|
whoami("system:serviceaccount:hermes:hermes-agent"),
|
|
completed(stdout="pod/hermes-switchyard-1\n"),
|
|
completed(stdout="pod/hermes-node-1\n"),
|
|
completed(stderr="pods 'hermes-chat-tenant-0' not found", returncode=1),
|
|
]
|
|
)
|
|
|
|
vantages, records = harness_run.resolve_vantages(runner, TARGETS)
|
|
chat = next(record for record in records if record.name == catalog.CHAT)
|
|
|
|
assert catalog.CHAT not in vantages
|
|
assert chat.available is False and "not found" in chat.detail
|
|
|
|
|
|
def test_a_selector_that_matches_no_running_pod_leaves_the_vantage_absent() -> None:
|
|
runner = runner_for(
|
|
[
|
|
whoami("kubernetes-admin"),
|
|
completed(stdout="\n"),
|
|
completed(stdout="\n"),
|
|
completed(stdout="\n"),
|
|
completed(stdout="\n"),
|
|
]
|
|
)
|
|
|
|
vantages, records = harness_run.resolve_vantages(runner, TARGETS)
|
|
by_name = {record.name: record for record in records}
|
|
|
|
assert set(vantages) == {catalog.OPERATOR}
|
|
assert "no running pod matches" in by_name[catalog.SELF].detail
|
|
|
|
|
|
def test_two_vantages_that_are_the_same_principal_invalidate_the_evidence() -> None:
|
|
"""Corroboration needs two identities; one identity twice is a restatement."""
|
|
records = [
|
|
model.VantageRecord(
|
|
name=catalog.OPERATOR, description="d", identity="same", available=True
|
|
),
|
|
model.VantageRecord(
|
|
name=catalog.SELF, description="d", identity="same", available=True
|
|
),
|
|
model.VantageRecord(name=catalog.SWITCHYARD, description="d", available=True),
|
|
model.VantageRecord(name=catalog.CHAT, description="d", available=True),
|
|
]
|
|
|
|
problems = harness_run.vantage_problems(records)
|
|
|
|
assert len(problems) == 1
|
|
assert "same principal" in problems[0]
|
|
|
|
|
|
def test_vantage_problems_flag_a_missing_or_anonymous_self_probe() -> None:
|
|
operator = model.VantageRecord(
|
|
name=catalog.OPERATOR, description="d", identity="admin", available=True
|
|
)
|
|
|
|
absent = harness_run.vantage_problems([operator])
|
|
assert "in-pod Hermes vantage is unavailable" in absent[0]
|
|
|
|
anonymous = harness_run.vantage_problems(
|
|
[
|
|
operator,
|
|
model.VantageRecord(name=catalog.SELF, description="d", available=True),
|
|
]
|
|
)
|
|
assert "did not report an identity" in anonymous[0]
|
|
|
|
clean = harness_run.vantage_problems(
|
|
[
|
|
operator,
|
|
model.VantageRecord(
|
|
name=catalog.SELF, description="d", identity="agent", available=True
|
|
),
|
|
model.VantageRecord(
|
|
name=catalog.SWITCHYARD, description="d", available=True
|
|
),
|
|
model.VantageRecord(name=catalog.CHAT, description="d", available=True),
|
|
]
|
|
)
|
|
assert clean == []
|
|
|
|
|
|
def test_a_check_whose_vantage_is_absent_is_not_run() -> None:
|
|
spec = check("allowed", steps=(step("s", vantage=catalog.NODE),))
|
|
|
|
result = harness_run.run_check(runner_for([]), spec, {})
|
|
|
|
assert result.status == model.NOT_RUN
|
|
assert "vantage node is unavailable" in result.reason
|
|
assert result.outcomes[0].error == "vantage node is unavailable"
|
|
|
|
|
|
def test_validate_catalog_rejects_impersonation_from_every_vantage() -> None:
|
|
"""The operator vantage was the only one guarded; now none is exempt."""
|
|
for vantage in (catalog.OPERATOR, catalog.SELF, catalog.CHAT):
|
|
spec = check(
|
|
"stdout_matches",
|
|
{"step": "s", "equals": "x"},
|
|
steps=(
|
|
model.Step(
|
|
key="s",
|
|
vantage=vantage,
|
|
argv=("kubectl", "get", "ns", "-o", "name", "--as", "system:admin"),
|
|
),
|
|
),
|
|
)
|
|
problems = harness_run.validate_catalog([spec])
|
|
assert any("impersonates" in problem for problem in problems)
|
|
assert any(vantage in problem for problem in problems)
|
|
|
|
|
|
def test_bulk_evidence_is_evaluated_but_replaced_before_it_is_recorded() -> None:
|
|
spec = check(
|
|
"stdout_matches",
|
|
{"step": "s", "equals": "locked"},
|
|
steps=(
|
|
model.Step(
|
|
key="s",
|
|
vantage=catalog.OPERATOR,
|
|
argv=("kubectl", "version"),
|
|
record=False,
|
|
),
|
|
),
|
|
)
|
|
runner = runner_for([completed(stdout="locked", stderr="noise")])
|
|
|
|
result = harness_run.run_check(
|
|
runner, spec, {catalog.OPERATOR: exec_module.operator_vantage()}
|
|
)
|
|
|
|
assert result.status == model.PASS
|
|
assert result.outcomes[0].stdout == harness_run.UNRECORDED
|
|
assert result.outcomes[0].stderr == harness_run.UNRECORDED
|
|
# Withholding bulk bytes must not withhold the binary that produced them.
|
|
recorded = result.outcomes[0]
|
|
assert recorded.executable_path
|
|
assert recorded.executable_sha256
|
|
assert recorded.executable_path == "kubectl"
|
|
assert recorded.executable_sha256 == "a" * 64
|
|
assert recorded.as_dict()["executable_sha256"] == recorded.executable_sha256
|
|
|
|
|
|
def test_an_expired_deadline_leaves_the_report_saying_so() -> None:
|
|
clock = FakeClock()
|
|
runner = exec_module.Runner(
|
|
clock=clock,
|
|
spawn=FakeSpawn([]),
|
|
environ={},
|
|
deadline_seconds=1.0,
|
|
attestor=lambda command, _environment: exec_module.Attestation(
|
|
command, "a" * 64
|
|
),
|
|
)
|
|
clock.advance(5.0)
|
|
spec = check("allowed", steps=(step("s", vantage=catalog.OPERATOR),))
|
|
|
|
report = harness_run.build_report(
|
|
runner,
|
|
TARGETS,
|
|
[spec],
|
|
{catalog.OPERATOR: exec_module.operator_vantage()},
|
|
[],
|
|
"read-only",
|
|
"2026-08-17T00:00:00Z",
|
|
)
|
|
|
|
assert report.results[0].status == model.NOT_RUN
|
|
assert any("deadline expired" in error for error in report.harness_errors)
|
|
assert report.decision == model.NO_GO
|
|
|
|
|
|
def test_the_report_carries_the_configured_baseline_and_sorts_its_checks() -> None:
|
|
runner = runner_for([completed(stdout="ok"), completed(stdout="ok")])
|
|
specs = [
|
|
check(
|
|
"allowed", steps=(step("s", vantage=catalog.OPERATOR),), identifier="z.last"
|
|
),
|
|
check(
|
|
"allowed",
|
|
steps=(step("s", vantage=catalog.OPERATOR),),
|
|
identifier="a.first",
|
|
),
|
|
]
|
|
records = [
|
|
model.VantageRecord(
|
|
name=catalog.OPERATOR, description="d", identity="admin", available=True
|
|
),
|
|
model.VantageRecord(
|
|
name=catalog.SELF, description="d", identity="agent", available=True
|
|
),
|
|
model.VantageRecord(name=catalog.SWITCHYARD, description="d", available=True),
|
|
model.VantageRecord(name=catalog.CHAT, description="d", available=True),
|
|
]
|
|
|
|
report = harness_run.build_report(
|
|
runner,
|
|
TARGETS,
|
|
specs,
|
|
{catalog.OPERATOR: exec_module.operator_vantage()},
|
|
records,
|
|
"read-only",
|
|
"2026-08-17T00:00:00Z",
|
|
)
|
|
|
|
assert [result.spec.id for result in report.results] == ["a.first", "z.last"]
|
|
assert report.baseline["baseline_commit"] == TARGETS.baseline_commit
|
|
assert report.baseline["dependency_pull_requests"] == list(
|
|
TARGETS.dependency_pull_requests
|
|
)
|
|
assert report.decision == model.GO
|
|
assert report.finished_at
|
|
|
|
|
|
def test_armed_results_replace_the_matching_catalog_placeholders() -> None:
|
|
placeholder = check(
|
|
"not_armed", identifier="ephemeral.feature-branch-push", mandatory=False
|
|
)
|
|
armed = model.CheckResult(
|
|
spec=model.CheckSpec(
|
|
id="ephemeral.feature-branch-push",
|
|
title="t",
|
|
group="ephemeral",
|
|
rule="armed",
|
|
),
|
|
status=model.PASS,
|
|
reason="pushed",
|
|
)
|
|
|
|
report = harness_run.build_report(
|
|
runner_for([]),
|
|
TARGETS,
|
|
[placeholder],
|
|
{},
|
|
[],
|
|
"ephemeral-armed",
|
|
"2026-08-17T00:00:00Z",
|
|
[armed],
|
|
)
|
|
|
|
assert len(report.results) == 1
|
|
assert report.results[0].status == model.PASS
|
|
|
|
|
|
def test_an_unstartable_run_reports_no_go_with_a_named_reason() -> None:
|
|
report = harness_run.unavailable_report(
|
|
"read-only", "2026-08-17T00:00:00Z", "arming refused: bad phrase", TARGETS
|
|
)
|
|
|
|
assert report.decision == model.NO_GO
|
|
assert report.results[0].spec.id == "harness.startup"
|
|
assert report.results[0].status == model.NOT_RUN
|
|
assert report.harness_errors == ["arming refused: bad phrase"]
|