The reviewed PR stack is now merged into main, so an open draft PR #19 is no longer proof that the reviewed code is what runs. The mandatory release.exact-lineage-is-running check now requires the merged terminal state instead: PR #19 closed with merged=true, base main, the existing feature ref, and the exact reviewed head, plus a new merge-ancestry step that proves the reviewed head is an ancestor of the pinned origin/main via git merge-base --is-ancestor. An open PR, a PR closed without merging, a mismatched head, or a head that is not a proven ancestor of main still fails closed; an undecidable ancestry probe is NOT_RUN. The recorded pre-merge base SHA is no longer compared against current main. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
485 lines
17 KiB
Python
485 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""Orchestration for the Hermes full-handoff acceptance harness.
|
|
|
|
Three things happen before any check runs, and each of them can stop the run on
|
|
its own:
|
|
|
|
* the catalog is validated structurally — every rule exists, every live-denial
|
|
evaluator carries a read-only attempt, no operator-side step can impersonate;
|
|
* each vantage is resolved and asked who it is; and
|
|
* the operator and in-pod identities are compared. If they are the same
|
|
principal there is only one vantage, the dual-vantage evidence would be a
|
|
restatement rather than a corroboration, and the run is a NO_GO.
|
|
|
|
Everything after that is uniform: run a check's steps against its vantage, hand
|
|
the outcomes to the named evaluator, and record the classification. A vantage
|
|
that could not be resolved yields ``NOT_RUN`` for every check that needs it,
|
|
which is exactly the fail-closed answer.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from collections.abc import Iterable, Mapping, Sequence
|
|
|
|
from hermes_handoff_catalog import (
|
|
CHAT,
|
|
NODE,
|
|
OPERATOR,
|
|
SELF,
|
|
SWITCHYARD,
|
|
VANTAGE_NAMES,
|
|
Targets,
|
|
)
|
|
from hermes_handoff_checks_access import access_checks
|
|
from hermes_handoff_checks_delivery import delivery_checks
|
|
from hermes_handoff_checks_platform import platform_checks
|
|
from hermes_handoff_checks_workers import worker_checks
|
|
from hermes_handoff_evaluators import EVALUATORS, evaluate
|
|
from hermes_handoff_exec import Outcome, Runner, Vantage, operator_vantage, pod_vantage
|
|
from hermes_handoff_model import (
|
|
ATTEMPT,
|
|
EPHEMERAL,
|
|
NOT_RUN,
|
|
STEP_KINDS,
|
|
CheckResult,
|
|
CheckSpec,
|
|
Report,
|
|
VantageRecord,
|
|
utc_now,
|
|
)
|
|
from hermes_handoff_policy import (
|
|
EXPECTED_REMOTE,
|
|
EXPECTED_REPO,
|
|
IMPERSONATION_ARGS,
|
|
PolicyError,
|
|
check_argv,
|
|
projection,
|
|
)
|
|
|
|
UNRECORDED = "[output not recorded]"
|
|
|
|
|
|
def build_catalog(targets: Targets) -> list[CheckSpec]:
|
|
"""Return the whole acceptance catalog for a set of targets."""
|
|
return [
|
|
*platform_checks(targets),
|
|
*access_checks(targets),
|
|
*delivery_checks(targets),
|
|
*worker_checks(targets),
|
|
]
|
|
|
|
|
|
def validate_catalog(specs: Sequence[CheckSpec]) -> list[str]:
|
|
"""Return the structural problems that make a catalog unsafe to run."""
|
|
problems: list[str] = []
|
|
if not specs:
|
|
return ["catalog is empty"]
|
|
seen: set[str] = set()
|
|
fake_operator = operator_vantage()
|
|
fake_pod = pod_vantage("hermes", "acceptance-probe", "probe", fake_operator)
|
|
for spec in specs:
|
|
if spec.id in seen:
|
|
problems.append(f"{spec.id}: duplicate check id")
|
|
seen.add(spec.id)
|
|
if spec.rule not in EVALUATORS and spec.scope != EPHEMERAL:
|
|
problems.append(f"{spec.id}: unknown rule {spec.rule!r}")
|
|
step_names: set[str] = set()
|
|
for step in spec.steps:
|
|
if step.key in step_names:
|
|
problems.append(f"{spec.id}: duplicate step key {step.key!r}")
|
|
step_names.add(step.key)
|
|
if step.vantage not in VANTAGE_NAMES:
|
|
problems.append(
|
|
f"{spec.id}: step {step.key} names unknown vantage {step.vantage!r}"
|
|
)
|
|
if step.kind not in STEP_KINDS:
|
|
problems.append(
|
|
f"{spec.id}: step {step.key} has unknown kind {step.kind!r}"
|
|
)
|
|
if set(step.argv) & set(IMPERSONATION_ARGS):
|
|
problems.append(
|
|
f"{spec.id}: step {step.key} impersonates from the "
|
|
f"{step.vantage} vantage"
|
|
)
|
|
try:
|
|
addressed = (
|
|
fake_operator if step.vantage == OPERATOR else fake_pod
|
|
).wrap(step.argv)
|
|
check_argv(addressed)
|
|
except PolicyError as exc:
|
|
problems.append(f"{spec.id}: step {step.key} violates policy: {exc}")
|
|
if spec.rule == "denied" and not any(
|
|
step.kind == ATTEMPT for step in spec.steps
|
|
):
|
|
problems.append(
|
|
f"{spec.id}: a deny check needs a real attempt, not only a review"
|
|
)
|
|
return problems
|
|
|
|
|
|
def validate_targets(targets: Targets) -> list[str]:
|
|
"""Return release-input problems before a runner or network call exists."""
|
|
problems: list[str] = []
|
|
sha = re.compile(r"[0-9a-f]{40}\Z")
|
|
image = re.compile(
|
|
r"[^@\s]+:git-([0-9a-f]{40})-build-[1-9][0-9]*@sha256:[0-9a-f]{64}\Z"
|
|
)
|
|
if targets.repo != EXPECTED_REPO or targets.remote != EXPECTED_REMOTE:
|
|
problems.append(
|
|
"repository and remote must be the fixed Atlas titan-iac origin"
|
|
)
|
|
if (
|
|
targets.reviewed_pr_number != 19
|
|
or targets.reviewed_head_ref != "feature/hermes-full-handoff-acceptance"
|
|
):
|
|
problems.append(
|
|
"reviewed PR identity must remain fixed to PR #19 and its existing branch"
|
|
)
|
|
for label, value in (
|
|
("baseline commit", targets.baseline_commit),
|
|
("remote main SHA", targets.remote_main_sha),
|
|
("reviewed head SHA", targets.reviewed_head_sha),
|
|
("build SHA", targets.build_sha),
|
|
):
|
|
if not sha.fullmatch(value):
|
|
problems.append(f"{label} must be an exact lowercase 40-character SHA")
|
|
image_match = image.fullmatch(targets.agent_image)
|
|
if not image_match or image_match.group(1) != targets.build_sha:
|
|
problems.append("agent image tag/digest must bind the exact build SHA")
|
|
if targets.build_sha != targets.remote_main_sha:
|
|
problems.append("build SHA must equal the exact release main SHA")
|
|
if not re.fullmatch(r"[1-9][0-9]{0,8}", str(targets.deployment_revision)):
|
|
problems.append("deployment revision must be a positive integer string")
|
|
if targets.node_count != 3 or targets.pool_replicas != 3:
|
|
problems.append("node and pool counts are fixed at exactly three")
|
|
if not targets.chat_config_revision.strip():
|
|
problems.append(
|
|
"Telegram continuity and its chat config revision are mandatory"
|
|
)
|
|
if set(targets.pool_worker_env) != {"HERMES_WORKER_NODE", "HERMES_WORKER_ORDINAL"}:
|
|
problems.append("pool assignment evidence must include worker node and ordinal")
|
|
heads = dict(targets.dependency_heads)
|
|
if len(heads) != len(targets.dependency_heads) or set(heads) != set(
|
|
targets.dependency_pull_requests
|
|
):
|
|
problems.append("every dependency PR must have one exact current head")
|
|
elif any(not sha.fullmatch(value) for value in heads.values()):
|
|
problems.append("dependency heads must be exact lowercase 40-character SHAs")
|
|
if (
|
|
not isinstance(targets.max_evidence_age_seconds, int)
|
|
or targets.max_evidence_age_seconds <= 0
|
|
):
|
|
problems.append("maximum evidence age must be a positive integer")
|
|
return problems
|
|
|
|
|
|
def resolve_identity(runner: Runner, vantage: Vantage) -> tuple[str, str]:
|
|
"""Return the authenticated username of a vantage and any failure detail."""
|
|
outcome = runner.run(
|
|
(
|
|
"kubectl",
|
|
"auth",
|
|
"whoami",
|
|
"-o",
|
|
projection("jsonpath={.status.userInfo.username}"),
|
|
),
|
|
vantage,
|
|
)
|
|
if not outcome.ok:
|
|
return ("", outcome.error or outcome.combined[:200])
|
|
username = outcome.stdout.strip()
|
|
if not username or any(character.isspace() for character in username):
|
|
return ("", "identity response carried no username")
|
|
return (username, "")
|
|
|
|
|
|
def _first_pod(
|
|
runner: Runner, operator: Vantage, namespace: str, selector: str
|
|
) -> tuple[str, str]:
|
|
outcome = runner.run(
|
|
(
|
|
"kubectl",
|
|
"--namespace",
|
|
namespace,
|
|
"get",
|
|
"pods",
|
|
"--selector",
|
|
selector,
|
|
"--field-selector",
|
|
"status.phase=Running",
|
|
"-o",
|
|
"name",
|
|
),
|
|
operator,
|
|
)
|
|
if not outcome.ok:
|
|
return ("", outcome.error or outcome.combined[:200])
|
|
names = [
|
|
line.strip().removeprefix("pod/")
|
|
for line in outcome.stdout.splitlines()
|
|
if line.strip()
|
|
]
|
|
if not names:
|
|
return ("", f"no running pod matches {selector}")
|
|
return (sorted(names)[0], "")
|
|
|
|
|
|
def resolve_vantages(
|
|
runner: Runner,
|
|
targets: Targets,
|
|
kubeconfig: str | None = None,
|
|
context: str | None = None,
|
|
) -> tuple[dict[str, Vantage], list[VantageRecord]]:
|
|
"""Resolve every vantage the catalog names, recording what each one is."""
|
|
operator = operator_vantage(kubeconfig, context)
|
|
username, detail = resolve_identity(runner, operator)
|
|
records = [
|
|
VantageRecord(
|
|
name=OPERATOR,
|
|
description=operator.description or "external read-only operator",
|
|
identity=username,
|
|
available=bool(username),
|
|
detail=detail,
|
|
)
|
|
]
|
|
vantages: dict[str, Vantage] = {OPERATOR: operator}
|
|
if not username:
|
|
return (vantages, records)
|
|
|
|
for name, selector, container, description in (
|
|
(
|
|
SELF,
|
|
f"app={targets.agent_deployment}",
|
|
targets.agent_container,
|
|
"the Hermes agent itself",
|
|
),
|
|
(
|
|
SWITCHYARD,
|
|
f"app={targets.switchyard_deployment}",
|
|
targets.switchyard_container,
|
|
"the Switchyard routing evidence log",
|
|
),
|
|
(
|
|
NODE,
|
|
f"app={targets.node_daemonset}",
|
|
targets.node_container,
|
|
"a node hardening probe",
|
|
),
|
|
):
|
|
pod, failure = _first_pod(runner, operator, targets.namespace, selector)
|
|
if not pod:
|
|
records.append(
|
|
VantageRecord(
|
|
name=name, description=description, available=False, detail=failure
|
|
)
|
|
)
|
|
continue
|
|
vantage = pod_vantage(targets.namespace, pod, container, operator)
|
|
identity, identity_detail = (
|
|
resolve_identity(runner, vantage) if name == SELF else ("", "")
|
|
)
|
|
records.append(
|
|
VantageRecord(
|
|
name=name,
|
|
description=f"{description} ({targets.namespace}/{pod})",
|
|
identity=identity,
|
|
available=True,
|
|
detail=identity_detail,
|
|
)
|
|
)
|
|
vantages[name] = vantage
|
|
|
|
chat_pod = f"{targets.chat_statefulset}-{targets.chat_ordinal}"
|
|
present = runner.run(
|
|
(
|
|
"kubectl",
|
|
"--namespace",
|
|
targets.namespace,
|
|
"get",
|
|
f"pod/{chat_pod}",
|
|
"-o",
|
|
"name",
|
|
),
|
|
operator,
|
|
)
|
|
if present.ok and present.stdout.strip():
|
|
vantages[CHAT] = pod_vantage(
|
|
targets.namespace, chat_pod, targets.chat_container, operator
|
|
)
|
|
records.append(
|
|
VantageRecord(
|
|
name=CHAT,
|
|
description=f"the chat surface ({targets.namespace}/{chat_pod})",
|
|
available=True,
|
|
)
|
|
)
|
|
else:
|
|
records.append(
|
|
VantageRecord(
|
|
name=CHAT,
|
|
description=f"the chat surface ({targets.namespace}/{chat_pod})",
|
|
available=False,
|
|
detail=present.error
|
|
or present.combined[:200]
|
|
or "chat tenant pod not found",
|
|
)
|
|
)
|
|
return (vantages, records)
|
|
|
|
|
|
def vantage_problems(records: Iterable[VantageRecord]) -> list[str]:
|
|
"""Return the identity problems that invalidate dual-vantage evidence."""
|
|
by_name = {record.name: record for record in records}
|
|
operator = by_name.get(OPERATOR)
|
|
hermes = by_name.get(SELF)
|
|
problems: list[str] = []
|
|
if operator is None or not operator.available:
|
|
problems.append(
|
|
"the operator vantage is unavailable; no evidence can be collected"
|
|
)
|
|
return problems
|
|
if hermes is None or not hermes.available:
|
|
problems.append(
|
|
"the in-pod Hermes vantage is unavailable; self-probe evidence is missing"
|
|
)
|
|
return problems
|
|
if not hermes.identity:
|
|
problems.append("the in-pod vantage did not report an identity")
|
|
elif operator.identity == hermes.identity:
|
|
problems.append(
|
|
"the operator and in-pod vantages authenticate as the same principal "
|
|
f"({operator.identity}); run the harness with a separate operator kubeconfig"
|
|
)
|
|
for name, label in ((SWITCHYARD, "Switchyard"), (CHAT, "chat/Telegram")):
|
|
record = by_name.get(name)
|
|
if record is None or not record.available:
|
|
problems.append(f"the required {label} vantage is unavailable")
|
|
return problems
|
|
|
|
|
|
def run_check(
|
|
runner: Runner, spec: CheckSpec, vantages: Mapping[str, Vantage]
|
|
) -> CheckResult:
|
|
"""Run one check's steps and classify the result."""
|
|
outcomes: dict[str, Outcome] = {}
|
|
recorded: list[Outcome] = []
|
|
for step in spec.steps:
|
|
vantage = vantages.get(step.vantage)
|
|
if vantage is None:
|
|
outcome = Outcome(
|
|
argv=step.argv,
|
|
vantage=step.vantage,
|
|
error=f"vantage {step.vantage} is unavailable",
|
|
)
|
|
else:
|
|
outcome = runner.run(step.argv, vantage, step.max_bytes)
|
|
outcomes[step.key] = outcome
|
|
recorded.append(
|
|
outcome
|
|
if step.record
|
|
else Outcome(
|
|
argv=outcome.argv,
|
|
vantage=outcome.vantage,
|
|
returncode=outcome.returncode,
|
|
stdout=UNRECORDED if outcome.stdout else "",
|
|
stderr=UNRECORDED if outcome.stderr else "",
|
|
truncated=outcome.truncated,
|
|
duration_ms=outcome.duration_ms,
|
|
error=outcome.error,
|
|
executable_path=outcome.executable_path,
|
|
executable_sha256=outcome.executable_sha256,
|
|
)
|
|
)
|
|
evaluation = evaluate(spec, outcomes)
|
|
return CheckResult(
|
|
spec=spec,
|
|
status=evaluation.status,
|
|
reason=evaluation.reason,
|
|
evidence=evaluation.evidence,
|
|
outcomes=recorded,
|
|
)
|
|
|
|
|
|
def run_catalog(
|
|
runner: Runner, specs: Sequence[CheckSpec], vantages: Mapping[str, Vantage]
|
|
) -> list[CheckResult]:
|
|
"""Run every check in the catalog.
|
|
|
|
Nothing is skipped once the deadline passes: the runner returns a
|
|
deadline outcome for each remaining command, which classifies as
|
|
``NOT_RUN``, so a truncated sweep is visible in the report rather than
|
|
silently shorter.
|
|
"""
|
|
return [run_check(runner, spec, vantages) for spec in specs]
|
|
|
|
|
|
def build_report(
|
|
runner: Runner,
|
|
targets: Targets,
|
|
specs: Sequence[CheckSpec],
|
|
vantages: Mapping[str, Vantage],
|
|
records: Sequence[VantageRecord],
|
|
mode: str,
|
|
started_at: str,
|
|
extra_results: Sequence[CheckResult] = (),
|
|
) -> Report:
|
|
"""Run the catalog and assemble the report, including structural problems."""
|
|
problems = validate_catalog(specs) + vantage_problems(records)
|
|
report = Report(
|
|
mode=mode,
|
|
started_at=started_at,
|
|
baseline={
|
|
"namespace": targets.namespace,
|
|
"baseline_commit": targets.baseline_commit,
|
|
"dependency_pull_requests": list(targets.dependency_pull_requests),
|
|
"expected_suspensions": list(targets.expected_suspensions),
|
|
"node_count": targets.node_count,
|
|
"pool_replicas": targets.pool_replicas,
|
|
},
|
|
vantages=list(records),
|
|
harness_errors=problems,
|
|
)
|
|
armed_ids = {result.spec.id for result in extra_results}
|
|
runnable = [spec for spec in specs if spec.id not in armed_ids]
|
|
if problems:
|
|
report.results = [
|
|
CheckResult(spec=spec, status=NOT_RUN, reason="harness preflight failed")
|
|
for spec in runnable
|
|
]
|
|
report.results.extend(extra_results)
|
|
else:
|
|
report.results = [*run_catalog(runner, runnable, vantages), *extra_results]
|
|
report.results.sort(key=lambda result: result.spec.id)
|
|
if runner.remaining_seconds <= 0:
|
|
report.harness_errors.append(
|
|
"the run deadline expired before every check completed"
|
|
)
|
|
report.finished_at = utc_now()
|
|
return report
|
|
|
|
|
|
def unavailable_report(
|
|
mode: str, started_at: str, reason: str, targets: Targets
|
|
) -> Report:
|
|
"""Return a NO_GO report for a run that could not start at all."""
|
|
return Report(
|
|
mode=mode,
|
|
started_at=started_at,
|
|
finished_at=utc_now(),
|
|
baseline={"namespace": targets.namespace},
|
|
harness_errors=[reason],
|
|
results=[
|
|
CheckResult(
|
|
spec=CheckSpec(
|
|
id="harness.startup",
|
|
title="The harness could start and resolve its vantages",
|
|
group="harness",
|
|
rule="allowed",
|
|
),
|
|
status=NOT_RUN,
|
|
reason=reason,
|
|
)
|
|
],
|
|
)
|