"""HITL pre-execution attestation gates (REQ-108, D-084). Records the approver identity (`gitea.actor` / `github.actor`) to the DynamoDB outbox for the contractId (attribute `approver_qa` / `approver_prod` / `approver_dr`), runs the separation-of-duties check on prod, invokes the 8-concern attestation matrix for the target env, and returns (ok, reason). Dev skips (autonomous). `scripts/run_platform.sh` calls `attest` before apply for qa/prod/dr. """ import os import sys from typing import Optional, Tuple def _approver_attr(env: str) -> str: return {"qa": "approver_qa", "prod": "approver_prod", "dr": "approver_dr"}.get(env, "") def attest(contract_id: str, env: str, approver: str, evidence: Optional[dict] = None, outbox_client=None) -> Tuple[bool, str]: """Attest a promotion gate for the given environment. Args: contract_id: the contract UUID. env: dev/qa/prod/dr. approver: the approver's username (`gitea.actor` / `github.actor`). evidence: optional operator-supplied evidence artifacts (for the attestation matrix operator-supplied concerns). outbox_client: optional moto-mocked DynamoDB outbox client for tests. Returns: (ok, reason). ok=False means block the promotion. """ if env == "dev": return (True, "dev autonomous (no HITL gate)") if not approver: return (False, f"no approver identity for {env} (GITHUB_ACTOR/GITEA_ACTOR unset)") attr = _approver_attr(env) if not attr: return (False, f"unknown environment: {env}") # Record the approver to the outbox. if outbox_client is not None: outbox_client.put_approver(contract_id, attr, approver) # Run the separation-of-duties check on prod. if env == "prod": from core.separation_of_duties import check as sod_check, route_halt_artifact ok, reason = sod_check(outbox_client, contract_id, approver) if not ok: route_halt_artifact(contract_id, reason, oncall_client=None) return (False, reason) # Run the 8-concern attestation matrix. from core.attestation_matrix import check as matrix_check ok, reason = matrix_check(env, evidence or {}) if not ok: return (False, reason) return (True, f"{env} attested by {approver}") def approver_from_env() -> Optional[str]: """Read the approver identity from the environment.""" return os.environ.get("GITHUB_ACTOR") or os.environ.get("GITEA_ACTOR") if __name__ == "__main__": # CLI: hitl_gates.py [evidence.json] if len(sys.argv) < 3: print("usage: hitl_gates.py [evidence.json]", file=sys.stderr) sys.exit(2) _cid = sys.argv[1] _env = sys.argv[2] _evidence = {} if len(sys.argv) >= 4 and os.path.isfile(sys.argv[3]): import json with open(sys.argv[3]) as f: _evidence = json.load(f) _approver = approver_from_env() or "" ok, reason = attest(_cid, _env, _approver, _evidence) if ok: print(f"HITL PASS: {reason}") sys.exit(0) else: print(f"HITL BLOCK: {reason}", file=sys.stderr) sys.exit(1)