2026-08-17 13:11:34 +00:00
|
|
|
"""Tests for the deterministic Hermes handoff mutation gate."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
fix(hermes): close the zero-evidence fail-open in absence checks
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>
2026-08-17 16:24:37 +00:00
|
|
|
from testing.quality_handoff_mutation import (
|
|
|
|
|
MUTATIONS,
|
|
|
|
|
Mutation,
|
|
|
|
|
MutationReport,
|
|
|
|
|
main,
|
|
|
|
|
run_gate,
|
|
|
|
|
)
|
2026-08-17 13:11:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
ROOT = Path(__file__).parents[2]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_every_security_mutant_is_killed() -> None:
|
|
|
|
|
report = run_gate(ROOT)
|
|
|
|
|
assert report.issues == ()
|
|
|
|
|
assert report.score == 100.0
|
|
|
|
|
assert report.killed == report.total >= 9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_invalid_anchor_baseline_syntax_and_survivor_are_reported() -> None:
|
|
|
|
|
path = "hermes_handoff_model.py"
|
|
|
|
|
cases = (
|
|
|
|
|
Mutation("missing", path, "not present", "x", "pass"),
|
|
|
|
|
Mutation(
|
|
|
|
|
"baseline",
|
|
|
|
|
path,
|
|
|
|
|
"SCHEMA_VERSION = 1",
|
|
|
|
|
"SCHEMA_VERSION = 2",
|
|
|
|
|
"raise SystemExit(1)",
|
|
|
|
|
),
|
|
|
|
|
Mutation("syntax", path, "SCHEMA_VERSION = 1", "if", "pass"),
|
|
|
|
|
Mutation("survivor", path, "SCHEMA_VERSION = 1", "SCHEMA_VERSION = 2", "pass"),
|
|
|
|
|
)
|
|
|
|
|
report = run_gate(ROOT, cases)
|
|
|
|
|
assert report.killed == 0
|
|
|
|
|
assert len(report.issues) == 4
|
|
|
|
|
assert report.score == 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_main_reports_a_perfect_score(capsys) -> None:
|
fix(hermes): close the zero-evidence fail-open in absence checks
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>
2026-08-17 16:24:37 +00:00
|
|
|
"""Every mutant must die, and the count must track the declared set."""
|
2026-08-17 13:11:34 +00:00
|
|
|
assert main() == 0
|
fix(hermes): close the zero-evidence fail-open in absence checks
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>
2026-08-17 16:24:37 +00:00
|
|
|
total = len(MUTATIONS)
|
|
|
|
|
assert total >= 13
|
|
|
|
|
assert f"{total}/{total} (100.00%)" in capsys.readouterr().out
|
2026-08-17 13:11:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_main_reports_mutation_issues(monkeypatch, capsys) -> None:
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"testing.quality_handoff_mutation.run_gate",
|
|
|
|
|
lambda _root: MutationReport(1, 0, ("survivor",)),
|
|
|
|
|
)
|
|
|
|
|
assert main() == 1
|
|
|
|
|
assert "survivor" in capsys.readouterr().out
|