#!/usr/bin/env bash
# scripts/octavia-pki.sh -- assert (and reissue) a DC's Octavia amphora control-plane PKI
#
# WHY THIS EXISTS. The PKI is minted by runbooks/phase-01-bundle-deploy.md Step 1.0-GEN,
# a copy-paste block chain. On 2026-07-29 that chain was executed for both DCs and TWO
# defects in it surfaced only because a human happened to notice:
#   - F8: the step's heredoc is a PASTE HAZARD. An indented terminator yields a config
#     with no `[alt_names]` section -- producing a controller certificate with NO SANs AT
#     ALL -- while every command in the chain still prints OK. **NOTHING in this repo
#     asserted the SAN set**, so that would have shipped silently.
#   - E2: the CA certificates landed mode 664. Group-WRITABLE trust anchors: the exposure
#     is INTEGRITY (a group member can substitute a CA), not confidentiality. The session
#     that generated them initially dismissed this as harmless "for public certs".
# Prose cannot be tested. This script can, and it has a harness, which is the whole point:
# the assertions below are the executable form of what the runbook only hoped for.
#
# SCOPE, DELIBERATELY NARROW (operator direction 2026-07-29: "Build the verify mode
# first, then we'll rule the guard fork"). `verify` VERIFIES ONLY -- it mints nothing,
# writes nothing and moves nothing.
#
# `reissue` (added 2026-07-30) replaces the CONTROLLER LEAF certificate and its key,
# signed by the EXISTING controller CA. It is NOT `generate` and does not become a route
# to it: no CA is ever regenerated, and it REFUSES unless a controller certificate is
# already there to be replaced. `generate` remains UNIMPLEMENTED and still exits 2 --
# minting a FIRST key pair is D-137 open fork 1, an UNRULED decision (option (c) would
# have the guard refuse secret-writing shapes OUTSIDE a sanctioned minter). Building
# `generate` before that ruling would convert the guard from "blocks secret minting" into
# "blocks secret minting except through any script" as a SIDE EFFECT. That is a posture
# change and it belongs to the operator, not to this script.
#
# WHY REISSUE HAD TO EXIST. A12/A13 ARM THEMSELVES from D-106's hostname work, and both
# will fail on both DCs the moment they arm, because 1.0-GEN.c bakes `dc0.vr0` names as
# literals. An armed assertion with no executable remedy behind it is a tripwire wired to
# nothing. `reissue` is that remedy, and it is held to the same standard as the checker:
# every precondition before any side effect, a full backup before the first minted byte,
# the mint asserted in STAGING before anything is promoted, and the whole result graded at
# the end by an INDEPENDENT re-run of `verify`.
#
# IT NEVER PRINTS KEY MATERIAL. This is the invariant that makes the file safe to run from
# an agent session, and it survives `reissue`: private bytes flow only into `sha256sum`,
# into `openssl` via `-passin file:`, or into a 0600 file -- never to stdout, never through
# argv (the overlay surgery has python3 read both files itself precisely because
# /proc/<pid>/cmdline is world-readable). The one thing printed verbatim is the openssl
# CONFIG under --dry-run, which contains only hostnames and VIPs. Do not weaken this: the
# harness asserts it executably by grepping a full successful run's stdout+stderr for PEM
# headers, the passphrase, and long base64 runs.
#
# RELATION TO THE REGISTER. D-137's `creds-matrix.py` checks that declared artifacts
# EXIST and are not world-readable. It cannot check that a certificate is the RIGHT
# certificate -- correct subject, correct SANs, correct issuer. This closes that gap. The
# two are complementary and neither replaces the other.
#
# FIRST CONSUMER OF THE creds-mint PATTERN, NOT A RIVAL TO IT. D-137 proposal 2(a) wants
# exactly ONE sanctioned minting path (`scripts/creds-mint.sh`, unbuilt, queued by R13).
# This script is scoped to Octavia so that pattern can be proven against a live consumer
# rather than designed in the abstract; if `creds-mint.sh` is later built, it ABSORBS the
# generate half and this file keeps only `verify`.
#
# USAGE
#   bash scripts/octavia-pki.sh verify  <site>                    # read-only, mutates nothing
#   bash scripts/octavia-pki.sh reissue <site> [--force] [--dry-run]
#   site = vr1-dc0 | vr1-dc1 (region-qualified; a bare `dc0` is REJECTED)
#   --force    overrides the already-correct REFUSE (exit 4) and NOTHING else
#   --dry-run  builds and asserts the openssl config, prints it, mints nothing
#
# RUNS ON the host the PKI lives on, which is RULED: the D-128 Plane-2 headend
# (D-109 ruling note (b), 2026-07-29 -- "Generate on voffice1"). The binding is READ from
# creds-manifests/host-identity rather than hardcoded, so there is one source of truth and
# this script cannot drift from the register. On any other host it REFUSES rather than
# reporting a filesystem it should not be measuring -- the F6 lesson.
#
# EXIT (verify):  0 all assertions pass | 1 an assertion FAILED | 2 bad args | 3 REFUSED
# (could not evaluate). Three outcomes, never two: "could not look" is never "nothing
# there". These four are UNCHANGED by the reissue work and must stay that way.
# EXIT (reissue): 0 ok or dry-run | 1 FAIL (measured defect, or a STAGED assertion failed
# -- nothing promoted) | 2 bad args | 3 REFUSE (could not evaluate, or could not make it
# safe) | 4 REFUSE, already correct | 5 PARTIAL, rollback required -- the restore command
# is printed. 4 and 5 exist because "did nothing because nothing was wrong" and "did half
# of it" are the two outcomes an operator most needs to tell apart from success.

set -uo pipefail          # deliberately NOT -e: a failing assertion must be COUNTED, not abort the run

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# REPO_ROOT and the workspace root are OVERRIDABLE so the harness can point them at
# fixtures. This is not a convenience: creds-matrix.py's V2 check shipped with ZERO harness
# cases precisely because it had no fixture path, and that is recorded in this repo as the
# reason it went untested. A check that cannot be pointed at a constructed defect cannot be
# proven capable of failing. Defaults are unchanged for every real caller.
REPO_ROOT="${OCTAVIA_PKI_REPO:-$(cd -- "$SCRIPT_DIR/.." && pwd)}"
PKI_HOME="${OCTAVIA_PKI_HOME:-$HOME}"

# THE CLOUD'S NAME AND DNS BASE, from the one place they live (2026-07-30).
# Sourced from SCRIPT_DIR, not REPO_ROOT: it is a SIBLING script, and REPO_ROOT is
# harness-overridable to a fixture that has no scripts/ of its own. lib-identity.sh has no
# side effects beyond two assignments -- deliberately NOT lib-net.sh, whose bare source
# populates a full flat plane/VIP namespace this checker never asked for (the R9 hazard).
# FAIL CLOSED: without these two values every derived zone would be silently wrong, and a
# certificate gate that invents its own idea of the estate's identity is worse than none.
if [ -r "$SCRIPT_DIR/lib-identity.sh" ]; then
  . "$SCRIPT_DIR/lib-identity.sh"
else
  echo "REFUSE: scripts/lib-identity.sh not readable -- it carries the cloud name and DNS"
  echo "        base every zone here is derived from. Refusing to guess the estate's identity."
  exit 3
fi

NFAIL=0; NREFUSE=0; NOK=0
ok()      { echo "  ok      $1"; NOK=$((NOK+1)); }
nfail()   { echo "  FAIL    $1"; NFAIL=$((NFAIL+1)); }
nrefuse() { echo "  REFUSE  $1"; NREFUSE=1; }

usage() {
  echo "usage: bash scripts/octavia-pki.sh verify  <site>"
  echo "       bash scripts/octavia-pki.sh reissue <site> [--force] [--dry-run]"
  echo "  site: region-qualified, e.g. vr1-dc0 or vr1-dc1 (bare 'dc0' is REJECTED)"
  exit 2
}

# ================================================================= SHARED DERIVATIONS
# Hoisted 2026-07-30 when `reissue` was built. These four were INLINE in `verify`; the
# minter needs the SAME answers, and two copies of a derivation is how a checker and the
# thing it checks drift apart until neither is wrong about its own copy. ONE derivation,
# TWO consumers.
#
# ON THE PURITY CLAIM, stated accurately (corrected 2026-07-30). This comment previously
# asserted the hoist was "PURE -- verify's output is byte-identical, proven by diffing the
# pre-refactor script against this one". That claim was NOT backed by anything executable:
# no pre-refactor artifact is kept, and no harness case pins it, so a reader could not check
# it and a future edit could not break it visibly. What was actually done, and all that is
# claimed: the hoisted bodies are TEXTUALLY IDENTICAL to the inline versions they replaced,
# and an independent pass compared this file against the previous commit over 17 fixtures
# (PASS, FAIL, REFUSE, no-SAN, v4-only, wrong-v4, wrong CA label, wrong host, no binding,
# missing/unparseable VIP overlay, un-generated DC, armed-correct, armed-wrong-region,
# armed+no-SAN, vr1-dc1, group-writable CA cert, un-gitignored overlay) and found the exit
# code and output identical once the deliberately-new A13/A14/A15/A16/A17 lines are removed.
# ONE KNOWN BEHAVIOUR CHANGE beyond the new assertions: the flag preamble now rejects
# unknown options before dispatch, so `verify <site> <extra-arg>` exits 2 where it used to
# ignore the extra argument. Deliberate; no caller in the repo passes one (grepped).

# HOST BINDING, read from the register rather than hardcoded (one source of truth; F6).
# Returns 0 when this host is the declared headend; 1 otherwise, with the REFUSE already
# recorded. `reissue` calls this FIRST, before it creates any file: a minter that writes to
# the wrong machine's filesystem has already done the damage by the time it notices.
assert_host_authority() {
  HOST_ID_FILE="$REPO_ROOT/creds-manifests/host-identity"
  THIS_HOST="${OCTAVIA_PKI_THIS_HOST:-$(hostname)}"
  EXPECT_HOST=""
  if [ -r "$HOST_ID_FILE" ]; then
    # `headend` is the role the D-109 ruling note (b) binds the Octavia PKI to.
    EXPECT_HOST="$(awk '!/^[[:space:]]*#/ && NF==2 && $1=="headend" {print $2; exit}' "$HOST_ID_FILE")"
  fi
  if [ -z "$EXPECT_HOST" ]; then
    nrefuse "no 'headend' binding in creds-manifests/host-identity -- cannot tell whether this host should hold the PKI, and will not assume it does"
    return 1
  fi
  if [ "${EXPECT_HOST%%.*}" != "${THIS_HOST%%.*}" ]; then
    nrefuse "this run is on '$THIS_HOST' but the PKI lives on '$EXPECT_HOST' (D-109 ruling note (b)); measuring this host's filesystem would describe the wrong machine"
    return 1
  fi
  ok "host: '$THIS_HOST' is the declared headend, so its filesystem is the right one to measure"
  return 0
}

# EXPECTED IP SANs, DERIVED. Never a hardcoded VIP: the ruled value lives in the per-DC
# overlay, and R11/ruling-3 moved these once already. Deriving means a future re-ruling
# updates both the checker and the minter for free.
# Sets VIP_OVERLAY, EXP_V4, EXP_V6. Exit: 0 ok | 2 overlay unreadable | 3 unparseable.
derive_vips() {
  VIP_OVERLAY="$REPO_ROOT/overlays/${SITE}-vips.yaml"
  if [ ! -r "$VIP_OVERLAY" ]; then
    nrefuse "VIP overlay not readable at $VIP_OVERLAY -- the expected SANs are derived from it and will not be guessed"
    return 2
  fi
  # A HERE-DOC, deliberately not `python3 ... | read`: a pipeline runs `read` in a SUBSHELL,
  # so EXP_V4/EXP_V6 would come back EMPTY in the caller and every downstream assertion
  # would compare against "". Both terminators sit at column 0 -- F8 is exactly what an
  # indented terminator does to a heredoc.
  read -r EXP_V4 EXP_V6 <<EOF
$(python3 - "$VIP_OVERLAY" <<'PY'
import sys, ipaddress, yaml
try:
    v = yaml.safe_load(open(sys.argv[1]))["applications"]["octavia"]["options"]["vip"].split()
except Exception:
    print(""); sys.exit(0)
v4 = next((a for a in v if ipaddress.ip_address(a).version == 4), "")
v6 = next((a for a in v if ipaddress.ip_address(a).version == 6), "")
# compressed/exploded IPv6 forms differ between openssl and yaml; normalise ours now
print(v4, ipaddress.ip_address(v6).compressed if v6 else "")
PY
)
EOF
  EXP_V4="${EXP_V4:-}"; EXP_V6="${EXP_V6:-}"
  if [ -z "$EXP_V4" ]; then
    nrefuse "could not derive octavia's provider v4 VIP from $VIP_OVERLAY -- refusing rather than asserting against an empty expectation"
    return 3
  fi
  return 0
}

# IP-LITERAL NORMALISER. openssl prints IPv6 EXPANDED AND UPPERCASE
# (`2602:F3E2:F02:11:0:0:0:57`) while the overlay carries the compressed form, so a literal
# string compare would FAIL a CORRECT certificate (the T13 false-fail guard). stdin: one
# literal per line; stdout: the compressed form, or UNPARSEABLE:<x> which can never match.
norm_ips() {
  python3 -c '
import sys, ipaddress
for line in sys.stdin:
    s = line.strip()
    if not s: continue
    try: print(ipaddress.ip_address(s).compressed)
    except ValueError: print("UNPARSEABLE:"+s)
'
}

# THE DNS ZONE, DERIVED AND NEVER TYPED (D-008 shape, D-117 labels -- see A12's block for
# why the label question is settled). Sets REGION, DC_DNS, EXP_ZONE.
#   REGION = ${SITE%%-*}   vr1-dc0 -> vr1     (D-008 <region>)
#   DC_DNS = ${SITE#*-}    vr1-dc0 -> dc0     (D-008 <dc>, 0-indexed per D-117)
#   zone   = ${CLOUD_NAME}.<dc>.<region>.${CLOUD_DOMAIN}
# The <cloud> label and the base domain are the ONLY typed identity left in this chain, and
# they now live in scripts/lib-identity.sh (2026-07-30) rather than here. This estate is torn
# down and rebuilt repeatedly and the names change every time, so an identity hand-edited in
# several files is a defect generator -- F9 is the proof.
derive_zone() {
  REGION="${SITE%%-*}"
  DC_DNS="${SITE#*-}"
  EXP_ZONE="${CLOUD_NAME}.${DC_DNS}.${REGION}.${CLOUD_DOMAIN}"
}

# The subject CN of a certificate, or "" if unreadable. `-nameopt multiline` is used rather
# than parsing the one-line form because the one-liner's separator is ", " -- which is also
# legal INSIDE a CN value, so splitting it can silently truncate a name.
cn_of() {
  openssl x509 -in "$1" -noout -subject -nameopt multiline 2>/dev/null \
    | sed -n 's/^ *commonName *= *//p' | head -1
}

# The DC-label CA-subject token, derived the same way the runbook derives it. Hoisted with
# the rest: `reissue` must recognise the CA it is about to sign with by the SAME name the
# checker recognises it by, or the two can disagree about which DC's CA this is.
derive_dc_label() { DC_LABEL="$(printf '%s' "$SITE" | tr 'a-z-' 'A-Z ')"; }

ACTION="${1:-}"; SITE="${2:-}"
[ -n "$ACTION" ] || usage
case "$ACTION" in
  verify|reissue) : ;;
  # `generate` stays UNIMPLEMENTED and still exits 2. `reissue` is not a backdoor to it: it
  # REFUSES unless an existing controller certificate is already there to be replaced, and
  # it regenerates NO CA. Minting a FIRST key pair remains D-137 open fork 1's to rule.
  *) echo "unknown action '$ACTION' ('verify' and 'reissue' are implemented; 'generate' deliberately is not -- see the header)"; usage ;;
esac
[ -n "$SITE" ] || usage
# Region-qualified only. A bare `dc0` has been a live defect class in this repo.
case "$SITE" in
  vr1-dc0|vr1-dc1) : ;;
  dc0|dc1|dc2) echo "REJECT: site '$SITE' is not region-qualified -- use vr1-dc0 / vr1-dc1"; exit 2 ;;
  *) echo "REJECT: unknown site '$SITE'"; exit 2 ;;
esac

# Flags exist for `reissue` ONLY. `verify` takes none, and is REJECTED rather than silently
# ignoring one -- a `verify --force` that quietly did nothing would read as an approval.
FORCE=0; DRYRUN=0
shift 2 2>/dev/null || true
for a in "$@"; do
  case "$a" in
    --force)   FORCE=1 ;;
    --dry-run) DRYRUN=1 ;;
    *) echo "REJECT: unknown option '$a'"; usage ;;
  esac
done
if [ "$ACTION" = "verify" ] && { [ "$FORCE" = 1 ] || [ "$DRYRUN" = 1 ]; }; then
  echo "REJECT: --force / --dry-run apply to 'reissue' only; 'verify' never mutates anything"; exit 2
fi

# ============================================================================= reissue
# THE MINTER. Scope is RULED and narrow: it replaces the CONTROLLER LEAF certificate and
# its key, signed by the EXISTING controller CA. It regenerates NO CA, and it changes
# exactly ONE overlay value (`lb-mgmt-controller-cert`); the other four must come out
# BYTE-IDENTICAL, which the surgery proof measures rather than assumes.
#
# PRECISION ABOUT THAT CLAIM (2026-07-30). What is PROVEN is byte-identity of the RESULT:
# the proof is a line diff, so it establishes that the other four values are unchanged, not
# that they were never rewritten. Both are true here -- the surgery carries the original
# lines through untouched and re-derives nothing -- but only the first is measured, and
# "byte-identical" and "untouched" are different claims. Stated so a future reader does not
# infer a guarantee the check cannot make: an implementation that re-encoded a value to
# coincidentally identical bytes would pass this proof, and the thing that actually rules
# it out is that the surgery never opens the other four (the plaintext passphrase among
# them), which is a code property, not a test property.
#
# WHY IT EXISTS. F9: the controller certs on both DCs carry `dc0.vr0` names -- CN and both
# DNS SANs -- from a literal paste in 1.0-GEN.c. They are inert only while
# os-public-hostname is unset (B5 IP-only), and A12/A13 arm themselves the moment that
# changes. An armed assertion with no way to ACT on it is a tripwire with nothing behind
# it, so the fix has to be executable and it has to be reproducible.
#
# THE DISCIPLINE, WHICH IS THE POINT. Every precondition runs BEFORE any side effect;
# HOST AUTHORITY runs before ANY file or directory is created and `--force` does not
# bypass it; a full backup is taken before the first byte is minted; the mint happens in a
# STAGING directory and is asserted there; promotion is the LAST thing that moves. F8 is
# the reason the openssl config is built one `printf` at a time and then asserted
# STRUCTURALLY: an indented heredoc terminator silently produced a SAN-less certificate on
# live material on 2026-07-29, and every command in that chain still printed OK.
#
# EXIT: 0 ok/dry-run | 1 FAIL (measured defect, or a staged assertion failed -- nothing
# promoted) | 2 bad args | 3 REFUSE (could not evaluate, or could not make it safe) |
# 4 REFUSE, already correct | 5 PARTIAL, rollback required.
STG=""; PROMOTING=0; RESTORE_CMD=""
SELF="${BASH_SOURCE[0]}"

cleanup_stg() {
  # `set -u` safe and idempotent: the trap is armed for the whole run, and STG is empty
  # until staging actually exists.
  if [ -n "${STG:-}" ] && [ -d "${STG:-}" ]; then rm -rf "${STG:-}"; fi
  return 0
}
on_signal() {
  cleanup_stg
  # A signal DURING promotion is the one case that leaves a half-moved workspace. It gets
  # the PARTIAL code and the restore command, not a generic abort -- exit status is how the
  # caller learns whether it must roll back.
  if [ "${PROMOTING:-0}" = 1 ]; then
    echo "octavia-pki reissue ($SITE): PARTIAL -- interrupted DURING promotion; rollback required"
    echo "  restore: $RESTORE_CMD"
    exit 5
  fi
  echo "octavia-pki reissue ($SITE): REFUSE -- interrupted before promotion; nothing was promoted"
  exit 3
}

# Terminal helpers. Each states WHERE the run stopped, because "FAIL" alone does not tell
# the operator whether anything moved.
rfail() { nfail "$1"; echo "octavia-pki reissue ($SITE): FAIL -- measured defect; NOTHING was minted, promoted or rewritten"; exit 1; }
rref()  { nrefuse "$1"; echo "octavia-pki reissue ($SITE): REFUSE -- could not evaluate or could not make it safe; NOTHING was minted"; exit 3; }
sfail() { nfail "$1"; echo "octavia-pki reissue ($SITE): FAIL -- a STAGED assertion failed; staging discarded, NOTHING promoted"; exit 1; }
pfail() { nfail "$1"; echo "octavia-pki reissue ($SITE): PARTIAL -- promotion was interrupted; rollback REQUIRED"; echo "  restore: $RESTORE_CMD"; exit 5; }

# Hex serials compare as INTEGERS, never as strings: '0x9' and '0x10' order backwards
# lexically, and openssl pads inconsistently across versions.
norm_hex() { printf '%s' "${1:-}" | tr -d ' \t\r\n' | tr 'a-f' 'A-F' | sed 's/^0*//;s/^$/0/'; }

do_reissue() {
  echo "=== octavia-pki reissue: $SITE ==="
  if [ "$DRYRUN" = 1 ]; then echo "  (--dry-run: nothing will be created, minted, promoted or rewritten)"; fi
  if [ "$FORCE" = 1 ]; then echo "  (--force: overrides the already-correct REFUSE and NOTHING else -- host authority, the backup and every assertion still apply)"; fi

  # ------------------------------------------------------------------ P0 tools (REFUSE 3)
  command -v openssl >/dev/null 2>&1 || nrefuse "openssl is not on PATH -- cannot mint or evaluate any certificate (this is a MISSING TOOL, not a bad PKI)"
  command -v python3 >/dev/null 2>&1 || nrefuse "python3 is not on PATH -- needed to parse the VIP overlay, normalise IPv6 literals and rewrite the deploy overlay"
  command -v tar     >/dev/null 2>&1 || nrefuse "tar is not on PATH -- the pre-mint backup cannot be taken, and this minter does not proceed without one"
  if [ "$NREFUSE" -ne 0 ]; then
    echo "octavia-pki reissue ($SITE): REFUSE -- preconditions unmet; NOTHING was minted"; exit 3
  fi

  # ---------------------------------------------- P1 HOST AUTHORITY, before ANY side effect
  # FIRST among substantive checks and BEFORE any file or directory is created. A minter
  # that discovers it is on the wrong machine after taking a backup has already written to
  # a filesystem it had no authority over. `--force` does not reach this: --force overrides
  # the already-correct refusal only.
  if ! assert_host_authority; then
    echo "octavia-pki reissue ($SITE): REFUSE -- not the declared headend; NOTHING was created, minted or promoted"; exit 3
  fi

  W="$PKI_HOME/octavia-pki/$SITE"
  ISS_CERT="$W/issuing-ca/issuing-ca.cert.pem"
  CA_CERT="$W/controller-ca/controller-ca.cert.pem"
  CA_KEY="$W/controller-ca/controller-ca.key.enc"
  CA_PASS="$W/controller-ca/passphrase.txt"
  CA_SRL="$W/controller-ca/controller-ca.cert.srl"
  CON_CERT="$W/controller/controller.cert.pem"
  CON_KEY="$W/controller/controller.key"
  CON_BUNDLE="$W/controller/controller.bundle.pem"
  OVERLAY="$REPO_ROOT/overlays/${SITE}-octavia-pki.yaml"
  OVL_TMP="${OVERLAY}.tmp"

  # ------------------------------------------------------- P2 the workspace (FAIL 1)
  # Absence is CONCLUSIVE -- we looked and it is not there -- so this FAILS rather than
  # refusing, matching A1/A2's ruling in `verify`.
  [ -d "$W" ] || rfail "P2 workspace $W does not exist -- this DC's PKI has never been generated, and reissue replaces; it does not create"
  MISSING=()
  for rel in issuing-ca/passphrase.txt issuing-ca/issuing-ca.key.enc issuing-ca/issuing-ca.cert.pem \
             controller-ca/passphrase.txt controller-ca/controller-ca.key.enc controller-ca/controller-ca.cert.pem \
             controller-ca/controller-ca.cert.srl \
             controller/controller.key controller/controller.cert.pem controller/controller.bundle.pem; do
    [ -f "$W/$rel" ] || MISSING+=("$rel")
  done
  [ "${#MISSING[@]}" -eq 0 ] || rfail "P2 missing ${#MISSING[@]} expected artifact(s): ${MISSING[*]} -- reissue will not run against a partial workspace"
  ok "P2 all 10 workspace artifacts present"

  # ----------------------------------------- P3 the SIGNING CA's material (FAIL 1/REFUSE 3)
  # PRESENT-BUT-UNREADABLE is a different fact from ABSENT and gets a different verdict:
  # "could not look" is never "nothing there".
  for f in "$CA_CERT" "$CA_KEY" "$CA_PASS"; do
    [ -f "$f" ] || rfail "P3 controller-CA material absent at $f -- the EXISTING CA signs this certificate and is never regenerated here"
    [ -r "$f" ] || rref  "P3 controller-CA material present but UNREADABLE at $f (permissions?) -- refusing rather than half-evaluating the CA we are about to sign with"
  done
  # BY LENGTH, NEVER BY VALUE. 1.0-GEN.b asserts the same 44 (openssl rand -base64 32,
  # newline stripped). The passphrase is never read into this process: openssl consumes it
  # directly via `-passin file:`.
  PASSLEN="$(wc -c < "$CA_PASS" 2>/dev/null | tr -d ' ')"
  [ "${PASSLEN:-0}" = "44" ] || rfail "P3 controller-CA passphrase file is ${PASSLEN:-0} bytes, expected 44 (the same number 1.0-GEN.b asserts) -- checked by LENGTH; the value is never printed or read into this process"
  ok "P3 controller-CA cert/key/passphrase present and readable; passphrase is 44 bytes (verified by length, never printed)"

  # ------------------------------------------------- P4 the CA is the RIGHT CA (FAIL 1)
  derive_dc_label
  openssl verify -CAfile "$CA_CERT" "$CA_CERT" >/dev/null 2>&1 \
    || rfail "P4 the controller CA does NOT verify against itself -- refusing to sign anything with a CA that fails its own signature check"
  CA_SUBJ="$(openssl x509 -in "$CA_CERT" -noout -subject 2>/dev/null)"
  # SUBSTRING on the DC-label token, identical to A5's check_subject: the full subject also
  # carries O=Neumatrix, so literal equality of the whole subject can never hold.
  case "$CA_SUBJ" in
    *"$DC_LABEL Omega Cloud Octavia Controller CA"*)
      ok "P4 signing CA self-verifies and its subject names THIS DC ('$DC_LABEL Omega Cloud Octavia Controller CA')" ;;
    *)
      rfail "P4 the signing CA's subject does NOT contain '$DC_LABEL Omega Cloud Octavia Controller CA' -- got: ${CA_SUBJ#subject=}; this is F1's cross-DC mix-up and signing would bind this DC's cert to another DC's root" ;;
  esac

  # -------------------------------------------------------- P5 CA issuance STATE (FAIL 1)
  # This minter signs with `-CAserial` and NOT `-CAcreateserial`, deliberately: with
  # -CAserial alone openssl FAILS on a missing serial file, whereas -CAcreateserial would
  # silently start a fresh random sequence and let the same CA issue a DUPLICATE serial.
  # The gate is here as well as in the flag so the operator gets a sentence, not a stack.
  [ -f "$CA_SRL" ] || rfail "P5 controller-ca.cert.srl is ABSENT -- the CA's issuance state is gone; minting now risks a DUPLICATE serial from this CA. Restore it before reissuing"
  SRL_BEFORE="$(norm_hex "$(tr -d ' \t\r\n' < "$CA_SRL")")"
  [ -n "$SRL_BEFORE" ] || rfail "P5 controller-ca.cert.srl is EMPTY -- openssl cannot continue a sequence that has no current value"
  ok "P5 CA serial file present (current $SRL_BEFORE); signing with -CAserial, never -CAcreateserial"

  # ------------------------------------------------- P6 the expected IP SANs (REFUSE 3)
  derive_vips || { echo "octavia-pki reissue ($SITE): REFUSE -- the expected IP SANs could not be derived; NOTHING was minted"; exit 3; }

  # ---------------------------------------------------------- P7 VIP plausibility
  # Keystone's provider v4 leg is the reference prefix. Selected BY ADDRESS FAMILY, not by
  # position: 1.0-GEN.c's own gate takes `.split()[0]`, which is only correct while the v4
  # leg happens to be listed first. Both DCs satisfy that today, so the divergence is
  # invisible right now -- which is exactly when it is cheap to fix.
  # A PARSE failure REFUSES (we could not look); a parsed address OUTSIDE the prefix FAILS
  # (we looked, and it is wrong).
  VIPCHK="$(python3 - "$VIP_OVERLAY" "$EXP_V4" <<'PY'
import sys, ipaddress, yaml
try:
    v = yaml.safe_load(open(sys.argv[1]))["applications"]["keystone"]["options"]["vip"].split()
except Exception:
    print("PARSE_FAIL"); sys.exit(0)
k4 = next((a for a in v if ipaddress.ip_address(a).version == 4), "")
if not k4:
    print("PARSE_FAIL"); sys.exit(0)
try:
    o4 = ipaddress.ip_address(sys.argv[2])
except ValueError:
    print("PARSE_FAIL"); sys.exit(0)
net = ipaddress.ip_network(k4 + "/24", strict=False)
print(("OK " if o4 in net else "OUTSIDE ") + str(net))
PY
)"
  case "$VIPCHK" in
    "OK "*)      ok "P7 octavia's provider v4 VIP $EXP_V4 sits inside keystone's provider prefix ${VIPCHK#OK }" ;;
    "OUTSIDE "*) rfail "P7 octavia's provider v4 VIP $EXP_V4 is OUTSIDE keystone's provider prefix ${VIPCHK#OUTSIDE } -- an implausible VIP would be baked into the certificate as an IP SAN" ;;
    *)           rref  "P7 could not derive keystone's provider v4 VIP from $VIP_OVERLAY -- refusing to sanity-check the SAN against an expectation we could not read" ;;
  esac

  # ------------------------------- P8 there must be something to REISSUE (REFUSE 3)
  # THE GENERATE FIREWALL. reissue REPLACES; it does not CREATE. Without this, a workspace
  # missing only its leaf certificate would quietly get a first-ever mint from a script
  # whose whole charter says minting a first cert belongs to `generate` -- which is
  # deliberately unimplemented pending D-137 open fork 1.
  # NOTE (found, not changed): P2 above already FAILs 1 when controller.cert.pem is absent,
  # per the ruled ordering, so this REFUSE is reachable only for a cert that is PRESENT but
  # unparseable. It is kept as its own gate anyway -- the firewall should be stated where a
  # future reader looks for it, not left implicit in another check's artifact list.
  OLD_CN="$(cn_of "$CON_CERT")"
  OLD_SERIAL="$(openssl x509 -in "$CON_CERT" -noout -serial 2>/dev/null | sed 's/^serial=//')"
  if [ -z "$OLD_SERIAL" ]; then
    rref "P8 there is no READABLE existing controller certificate at $CON_CERT. Minting a FIRST certificate is 'generate', which is deliberately unimplemented (D-137 open fork 1) -- reissue does not become a generate backdoor by accident"
  fi
  OLD_PUB="$(openssl x509 -in "$CON_CERT" -noout -pubkey 2>/dev/null | openssl pkey -pubin -pubout -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)"
  ok "P8 an existing controller certificate is present and readable (serial $(norm_hex "$OLD_SERIAL")) -- this is a REPLACEMENT"

  # ------------------------------------------------------ P9 the names, derived (FAIL 1)
  derive_zone
  NEW_CN="octavia-controller.${EXP_ZONE}"
  NEW_DNS1="$NEW_CN"
  NEW_DNS2="octavia.${EXP_ZONE}"
  # X.509 caps commonName at 64 characters (ub-common-name). Past that openssl truncates or
  # errors depending on version, and a truncated CN is a name that silently means something
  # else. Checked BEFORE anything is created because it is a property of the derived zone.
  [ "${#NEW_CN}" -le 64 ] || rfail "P9 the derived CN '$NEW_CN' is ${#NEW_CN} characters, over X.509's 64-character commonName limit -- the zone is too long to name this way"
  ok "P9 derived zone '$EXP_ZONE'; CN '$NEW_CN' is ${#NEW_CN} chars (<= 64)"

  # ------------------------------------- P10 the deploy overlay, BEFORE the mint (FAIL 1)
  # F4 TIMING. These are the same checks A10 makes, hoisted to run BEFORE anything is
  # minted. Order is the whole point: discovering after promotion that the overlay is not
  # gitignored means a CA private key is committable AND the workspace has already moved.
  [ -f "$OVERLAY" ] || rfail "P10 deploy overlay absent at overlays/${SITE}-octavia-pki.yaml -- reissue rewrites one value in it and will not create it"
  OVL_MODE="$(stat -c '%a' "$OVERLAY" 2>/dev/null)"
  [ "$OVL_MODE" = "600" ] || rfail "P10 overlay is $OVL_MODE -- it carries CA key blobs plus a plaintext passphrase and must be 0600 BEFORE we write to it"
  OVL_KEYS="$(grep -c 'lb-mgmt-' "$OVERLAY" 2>/dev/null || true)"
  [ "$OVL_KEYS" = "5" ] || rfail "P10 overlay declares $OVL_KEYS lb-mgmt-* key(s), expected 5 -- refusing to operate on an overlay whose shape we do not recognise"
  if LC_ALL=C grep -qP '[^\x00-\x7F]' "$OVERLAY" 2>/dev/null; then
    rfail "P10 overlay contains non-ASCII bytes -- the surgery reads and rewrites it as ASCII and would corrupt it"
  fi
  git -C "$REPO_ROOT" check-ignore -q "$OVERLAY" 2>/dev/null \
    || rfail "P10 overlay is NOT gitignored -- a CA private key is committable RIGHT NOW (F4); refusing to write MORE secret material into a committable file"
  # The TEMP path is gated too, and gated HERE rather than at the write. check-ignore is
  # pure pattern matching and needs no file to exist, so there is no reason to learn this
  # after promotion -- when the answer would arrive as an exit-5 partial instead of a clean
  # refusal with nothing touched.
  git -C "$REPO_ROOT" check-ignore -q "$OVL_TMP" 2>/dev/null \
    || rfail "P10 the surgery's temp path ${OVL_TMP##*/} is NOT gitignored -- it briefly holds the same CA key blobs as the overlay; add a rule covering it before reissuing"
  # A leftover temp file means a previous run died mid-surgery. O_EXCL would fail on it
  # later; it is REFUSED here instead, and deliberately NOT deleted -- silently removing the
  # evidence of a half-finished write is how the O_EXCL guard gets defeated.
  if [ -e "$OVL_TMP" ]; then
    rref "P10 a STALE temp file is present at $OVL_TMP -- a previous surgery did not finish. Inspect and remove it deliberately; this script will not delete it for you"
  fi
  # THE TWO CHECKS THE SURGERY'S OWN PROOF USED TO MAKE *AFTER* PROMOTION (added 2026-07-30).
  # FOUND BY ADVERSARIAL REVIEW. Both are pure functions of state that exists BEFORE this run
  # starts, yet they were only discovered by the post-surgery proof -- so a pre-existing
  # overlay defect produced exit 5 (PARTIAL, roll back) with the workspace already promoted
  # and a CA serial burned, when the honest answer was "your overlay was already wrong,
  # nothing was touched". This script's own rule is every precondition before any side
  # effect; these two were on the wrong side of it.
  #   (a) EXACTLY ONE lb-mgmt-controller-cert line. The surgery requires hits==1. A file with
  #       two would satisfy P10's `lb-mgmt-` count of 5 only if another key were missing --
  #       but the count cannot tell those apart, which is precisely why it is not sufficient.
  #   (b) lb-mgmt-controller-cacert must ALREADY decode to THIS DC's controller CA. If it
  #       does not, the overlay belongs to another DC or another generation (the F1 cross-DC
  #       mix-up), and rewriting one value in it would produce a file whose cert and CA come
  #       from different trust domains -- deployable, and broken at the amphora handshake.
  OVL_PRE="$(python3 - "$OVERLAY" "$CA_CERT" <<'PY' 2>/dev/null || true
import base64, hashlib, sys, yaml
n = sum(1 for l in open(sys.argv[1], encoding="ascii", errors="replace")
        if l.lstrip().startswith("lb-mgmt-controller-cert:"))
try:
    o = yaml.safe_load(open(sys.argv[1]))["applications"]["octavia"]["options"]
except Exception:
    print("LINES=%d CACERT=UNREADABLE" % n); raise SystemExit(0)
v = o.get("lb-mgmt-controller-cacert")
tag = "ABSENT"
if v:
    try:
        tag = "OK" if hashlib.sha256(base64.b64decode(v, validate=True)).hexdigest() == \
                       hashlib.sha256(open(sys.argv[2], "rb").read()).hexdigest() else "MISMATCH"
    except Exception:
        tag = "UNDECODABLE"
print("LINES=%d CACERT=%s" % (n, tag))
PY
)"
  case "$OVL_PRE" in
    "LINES=1 CACERT=OK") : ;;
    LINES=1*CACERT=MISMATCH)
      rfail "P10 the overlay's lb-mgmt-controller-cacert does NOT decode to this DC's controller CA -- this overlay does not belong to $SITE (the F1 cross-DC mix-up). Rewriting one value in it would ship a cert and a CA from different trust domains; nothing minted" ;;
    LINES=1*)
      rfail "P10 could not confirm the overlay's controller CA value ($OVL_PRE) -- refusing to mint against an overlay whose existing contents we cannot read" ;;
    "")
      rref "P10 could not evaluate the overlay's pre-state -- no conclusion drawn, nothing minted" ;;
    *)
      rfail "P10 the overlay carries a lb-mgmt-controller-cert line count that is not 1 ($OVL_PRE) -- the surgery rewrites exactly one line and will not guess which; nothing minted" ;;
  esac
  ok "P10 overlay pre-checks pass (0600, 5 keys, ASCII, gitignored, temp path ignored, exactly one controller-cert line, and its CA value already matches this DC), all BEFORE the mint"

  # ==================================================== THE RE-RUN GUARD (REFUSE 4)
  # Reissuing a certificate that is ALREADY correct rotates a key for nothing, burns a CA
  # serial and rewrites a deploy overlay -- three mutations with no defect behind them.
  #
  # THE VACUOUS-TRUTH GUARD IS THE LOAD-BEARING PART. The obvious implementation walks the
  # DNS SANs and refuses if none is wrong. On a cert with ZERO DNS SANs that loop iterates
  # zero times, finds nothing wrong, and REFUSES -- on the single WORST certificate the
  # estate can hold, which is F8's exact output. So the count is asserted FIRST: "already
  # correct" requires exactly 2 DNS SANs that ARE the expected pair, plus a matching CN.
  OLD_DNS="$(openssl x509 -in "$CON_CERT" -noout -ext subjectAltName 2>/dev/null | grep -oE 'DNS:[^,[:space:]]+' | sed 's/^DNS://')"
  OLD_DNS_N="$(printf '%s\n' "$OLD_DNS" | grep -c . || true)"
  ALREADY=0
  if [ "$OLD_DNS_N" = "2" ] && [ "$OLD_CN" = "$NEW_CN" ]; then
    G1=0; G2=0
    for n in $OLD_DNS; do
      if [ "$n" = "$NEW_DNS1" ]; then G1=1; fi
      if [ "$n" = "$NEW_DNS2" ]; then G2=1; fi
    done
    if [ "$G1" = 1 ] && [ "$G2" = 1 ]; then ALREADY=1; fi
  fi
  # THE GUARD IS THE WHOLE PREDICATE, NOT JUST THE NAMES (hardened 2026-07-30).
  # FOUND BY ADVERSARIAL REVIEW. The name comparison above answers "are the names right?",
  # and the first version treated that as "is the certificate right?". Measured consequence:
  # a certificate whose CN and DNS SANs matched but whose IP SAN was stale made `verify`
  # FAIL while `reissue` exited 4 saying "nothing to fix" -- the remedy refusing the exact
  # defect the checker had just reported, in the same workspace, seconds apart.
  # It does not bite on the CURRENT reissue (today the names ARE wrong, so it proceeds), but
  # it bites on every run afterwards -- including the 2-year renewal this reissue starts the
  # clock on, where an EXPIRED cert with correct names would be refused as "already correct".
  # SO: "already correct" now means the names match AND `verify` returns clean. verify is the
  # repo's own definition of a healthy PKI, so this cannot drift from it -- and A16/A17 (new
  # today) mean expiry and overlay desync are inside that definition rather than outside it.
  if [ "$ALREADY" = 1 ]; then
    if OCTAVIA_PKI_REPO="$REPO_ROOT" OCTAVIA_PKI_HOME="$PKI_HOME" \
       OCTAVIA_PKI_THIS_HOST="$THIS_HOST" bash "$SELF" verify "$SITE" >/dev/null 2>&1; then
      : # names right AND verify clean -- genuinely nothing to do
    else
      ALREADY=0
      ok "re-run guard: the NAMES already match '$EXP_ZONE', but 'verify' does NOT return clean -- something else about this certificate is wrong (stale IP SAN, expiry, key/cert mismatch, overlay desync). Reissue is warranted; run 'verify' afterwards to confirm what remains"
    fi
  fi
  if [ "$ALREADY" = 1 ] && [ "$FORCE" != 1 ]; then
    ok "re-run guard: CN and both DNS SANs already match the derived zone '$EXP_ZONE', and verify returns clean"
    echo "octavia-pki reissue ($SITE): REFUSE (already correct) -- nothing to fix, so nothing is rotated. Use --force to reissue anyway (it overrides THIS check only)"
    exit 4
  fi
  if [ "$ALREADY" = 1 ]; then
    ok "re-run guard: the certificate is already correct, but --force was given -- reissuing anyway"
  else
    ok "re-run guard: reissue is warranted (CN '$OLD_CN' and/or the $OLD_DNS_N DNS SAN(s) do not match the derived zone '$EXP_ZONE')"
  fi

  # ============================================== BACKUP, BEFORE THE FIRST BYTE IS MINTED
  # umask FIRST: the archive is the first thing created and it contains CA private keys, so
  # it must never exist group/world-readable even for the instant before chmod.
  umask 077
  ARCH=""; ARCH_SHA=""
  # DELIBERATE DEVIATION: --dry-run takes NO backup. A backup exists to make a mutation
  # reversible, and a dry run has nothing to reverse -- so the archive would be a pure
  # addition of a second at-rest copy of CA private keys plus a plaintext passphrase, for no
  # benefit. This repo has already ruled on that exact trade: 1.0-GEN.b's note records the
  # operator DECLINING a second at-rest copy on the grounds that it "widens the exposure".
  # A dry run that quietly writes a tarball of CA keys is also just a lie about its name.
  if [ "$DRYRUN" = 1 ]; then
    ok "backup: SKIPPED -- --dry-run mutates nothing, so there is nothing to roll back and no reason to write a second copy of this DC's CA keys to disk"
  else
  BKDIR="$PKI_HOME/octavia-pki/backups"
  mkdir -p "$BKDIR" || rref "backup: could not create $BKDIR -- refusing to mint without a restorable pre-image"
  # THE NAME MUST BE UNIQUE, NOT MERELY TIMESTAMPED (hardened 2026-07-30, found by adversarial
  # review). The stamp is 1-second granular and `tar czf` OVERWRITES: 12 consecutive runs were
  # measured producing only 9 archives, silently destroying 3 pre-images. That bites hardest in
  # exactly the sequence where it matters -- an exit-5 partial followed by a quick retry, where
  # the overwritten archive is the only copy of the pre-reissue overlay. $$ disambiguates, and
  # the existence check below refuses rather than clobbering if it somehow still collides.
  ARCH="$BKDIR/${SITE}-octavia-pki-$(date -u +%Y%m%dT%H%M%SZ)-$$.tar.gz"
  [ -e "$ARCH" ] && rref "backup: $ARCH already exists -- refusing to overwrite a restore point"
  # BOTH halves of the state this run can change: the workspace AND the deploy overlay.
  # Backing up only the workspace would leave the overlay unrecoverable, and the overlay is
  # the half that actually reaches the cloud.
  tar czf "$ARCH" -C "$PKI_HOME/octavia-pki" "$SITE" -C "$REPO_ROOT" "overlays/${SITE}-octavia-pki.yaml" 2>/dev/null \
    || rref "backup: tar FAILED -- refusing to mint without a restorable pre-image"
  chmod 600 "$ARCH" || rref "backup: could not chmod 600 $ARCH -- it contains CA private keys"
  ARCH_SHA="$(sha256sum "$ARCH" | cut -d' ' -f1)"
  ARCH_ENTS="$(tar tzf "$ARCH" 2>/dev/null)"
  ARCH_N="$(printf '%s\n' "$ARCH_ENTS" | grep -c . || true)"
  # A backup is only a backup if it demonstrably contains the things it is supposed to. An
  # archive that tar created happily but that holds three files is not a rollback.
  [ "${ARCH_N:-0}" -ge 11 ] || rref "backup: the archive lists only ${ARCH_N:-0} entries, expected at least 11 (10 artifacts + the overlay) -- refusing to mint behind an incomplete backup"
  grep -Fxq "overlays/${SITE}-octavia-pki.yaml" <<<"$ARCH_ENTS" \
    || rref "backup: the archive does NOT contain overlays/${SITE}-octavia-pki.yaml -- the half that reaches the cloud would be unrecoverable"
  RESTORE_CMD="tar xzf $ARCH -C $PKI_HOME/octavia-pki $SITE && tar xzf $ARCH -C $REPO_ROOT overlays/${SITE}-octavia-pki.yaml"
  ok "backup: $ARCH ($ARCH_N entries, 0600)"
  echo "  sha256:  $ARCH_SHA"
  echo "  restore: $RESTORE_CMD"
  fi

  # =========================================================== STAGING + THE F8 CONFIG
  # A LEFTOVER STAGING DIR IS REPORTED, NOT IGNORED (added 2026-07-30, found by adversarial
  # review). SIGKILL between signing and promotion leaves a staging dir holding a VALID
  # CA-signed certificate and a fresh UNENCRYPTED private key, 0600, with a serial already
  # consumed for it -- a live credential outside the D-137 register that `verify` does not
  # mention and that a later run neither noticed nor cleaned. The stale-temp-file gate in
  # P10 already applies this discipline to the overlay's `.tmp`; staging was the gap.
  # REPORTED, NOT DELETED, and deliberately not fatal: the material is the operator's to
  # inspect, and silently removing evidence of a half-finished mint is how the guard gets
  # defeated. Naming it is what makes it actionable.
  STALE_STG="$(find "$PKI_HOME/octavia-pki" -maxdepth 1 -type d -name ".reissue-${SITE}.*" 2>/dev/null | head -5)"
  if [ -n "$STALE_STG" ]; then
    echo "  NOTE    a previous reissue left staging director(ies) behind -- each may hold a CA-signed"
    echo "          certificate and an unencrypted private key that no register declares. Inspect and"
    echo "          remove them deliberately; this run will not touch them:"
    printf '%s\n' "$STALE_STG" | sed 's/^/            /'
  fi
  STG="$(mktemp -d "$PKI_HOME/octavia-pki/.reissue-${SITE}.XXXXXX" 2>/dev/null)" \
    || rref "staging: could not create a staging directory beside the workspace"
  trap cleanup_stg EXIT
  trap on_signal INT TERM HUP

  # ONE printf PER LINE, APPENDING. NO HEREDOC. This is the F8 payoff: on 2026-07-29 an
  # INDENTED heredoc terminator in 1.0-GEN.c silently yielded a config with no [alt_names]
  # section and therefore a certificate with NO SANs AT ALL, while every command in the
  # chain printed OK. A heredoc's correctness depends on invisible leading whitespace on a
  # line nobody reads; a printf's does not.
  # Values are passed as %s ARGUMENTS, never interpolated into the format string -- a '%' in
  # a hostname or an address must be data, not a directive.
  CNF="$STG/controller.cnf"
  : > "$CNF"
  printf '%s\n' '[req]'                                                  >> "$CNF"
  printf '%s\n' 'distinguished_name = req_distinguished_name'            >> "$CNF"
  printf '%s\n' 'req_extensions = v3_req'                                >> "$CNF"
  printf '%s\n' 'prompt = no'                                            >> "$CNF"
  printf '\n'                                                            >> "$CNF"
  printf '%s\n' '[req_distinguished_name]'                               >> "$CNF"
  printf 'CN = %s\n' "$NEW_CN"                                           >> "$CNF"
  printf '%s\n' 'O = Neumatrix'                                          >> "$CNF"
  printf '\n'                                                            >> "$CNF"
  printf '%s\n' '[v3_req]'                                               >> "$CNF"
  printf '%s\n' 'keyUsage = critical, digitalSignature, keyEncipherment' >> "$CNF"
  printf '%s\n' 'extendedKeyUsage = clientAuth, serverAuth'              >> "$CNF"
  printf '%s\n' 'subjectAltName = @alt_names'                            >> "$CNF"
  printf '\n'                                                            >> "$CNF"
  printf '%s\n' '[alt_names]'                                            >> "$CNF"
  printf 'DNS.1 = %s\n' "$NEW_DNS1"                                      >> "$CNF"
  printf 'DNS.2 = %s\n' "$NEW_DNS2"                                      >> "$CNF"
  printf 'IP.1 = %s\n' "$EXP_V4"                                         >> "$CNF"
  # `if/fi`, never `[ -n "$EXP_V6" ] && printf ...`: a bare && as a function's LAST statement
  # returns the test's status, so a v4-only DC would make this function "fail" on success.
  if [ -n "$EXP_V6" ]; then
    printf 'IP.2 = %s\n' "$EXP_V6"                                       >> "$CNF"
  fi

  # ASSERT THE CONFIG STRUCTURALLY, BEFORE SIGNING. F8's lesson is not "use printf" -- it is
  # that the config was never CHECKED. Building it correctly and verifying it are two
  # different claims, and only the second one survives a future edit.
  for sec in '[req]' '[req_distinguished_name]' '[v3_req]' '[alt_names]'; do
    grep -Fqx "$sec" "$CNF" || rfail "config assertion: section $sec is MISSING from the generated config -- this is F8's failure mode and the mint stops here"
  done
  grep -Fqx 'subjectAltName = @alt_names' "$CNF" || rfail "config assertion: subjectAltName is not wired to @alt_names -- the [alt_names] section would be inert and the cert would have NO SANs (F8)"
  grep -Fqx "CN = $NEW_CN"        "$CNF" || rfail "config assertion: CN line is not exactly 'CN = $NEW_CN'"
  grep -Fqx "DNS.1 = $NEW_DNS1"   "$CNF" || rfail "config assertion: DNS.1 is not exactly '$NEW_DNS1'"
  grep -Fqx "DNS.2 = $NEW_DNS2"   "$CNF" || rfail "config assertion: DNS.2 is not exactly '$NEW_DNS2'"
  CFG_NDNS="$(grep -c '^DNS\.' "$CNF" || true)"
  # EXACTLY 2. A9 requires exactly two DNS SANs, so reissue REPLACES the pair and never adds
  # a third: a cert that speaks for one more name than the gate expects fails that gate.
  [ "$CFG_NDNS" = "2" ] || rfail "config assertion: the config carries $CFG_NDNS DNS entries, expected exactly 2 (A9's contract -- replace the pair, never append)"
  grep -Fqx "IP.1 = $EXP_V4" "$CNF" || rfail "config assertion: IP.1 is not exactly this DC's provider v4 VIP '$EXP_V4'"
  CFG_NIP="$(grep -c '^IP\.' "$CNF" || true)"
  if [ -n "$EXP_V6" ]; then
    grep -Fqx "IP.2 = $EXP_V6" "$CNF" || rfail "config assertion: the overlay declares a v6 provider leg but IP.2 is not exactly '$EXP_V6' (D-109 ruling note 2026-07-29)"
    [ "$CFG_NIP" = "2" ] || rfail "config assertion: the config carries $CFG_NIP IP entries, expected exactly 2 (v4 + v6)"
  else
    [ "$CFG_NIP" = "1" ] || rfail "config assertion: the config carries $CFG_NIP IP entries but the overlay declares NO v6 leg, so exactly 1 is expected"
  fi
  ok "config assertions: 4 sections present, subjectAltName wired, CN + exactly 2 DNS + exactly $CFG_NIP IP SAN(s), all exact"

  # ------------------------------------------------------------------------ --dry-run
  if [ "$DRYRUN" = 1 ]; then
    echo
    echo "  --- the openssl config that WOULD be used (entirely PUBLIC: names and VIPs only) ---"
    sed 's/^/  | /' "$CNF"
    echo "  --- what a real run WOULD create/replace ---"
    echo "  |  $CON_KEY     (fresh EC P-256 private key)"
    echo "  |  $CON_CERT    (CN $NEW_CN, 730 days, signed by this DC's controller CA)"
    echo "  |  $CON_BUNDLE  (cert + key)"
    echo "  |  $OVERLAY     (lb-mgmt-controller-cert ONLY; the other four values unchanged)"
    echo "  |  and it would consume CA serial $SRL_BEFORE"
    echo "  |  a real run would FIRST write a 0600 backup archive under $PKI_HOME/octavia-pki/backups"
    cleanup_stg
    echo
    echo "octavia-pki reissue ($SITE): DRY-RUN OK -- nothing was minted, promoted or rewritten"
    exit 0
  fi

  # ==================================================================== THE MINT (staging)
  # In a STAGING directory, never in place. The workspace's current material stays valid and
  # untouched until every assertion below has passed; a mint that fails half way leaves the
  # DC exactly as it was.
  openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "$STG/controller.key" 2>/dev/null \
    || rref "mint: could not generate a fresh EC P-256 private key -- nothing minted"
  chmod 600 "$STG/controller.key" 2>/dev/null || true
  # THE CURVE IS ASSERTED VIA THE PUBLIC KEY ONLY. `openssl pkey -text` on the PRIVATE key
  # would print the private scalar; the public half proves the curve just as well and
  # carries no secret.
  openssl pkey -in "$STG/controller.key" -pubout -out "$STG/controller.pub" 2>/dev/null \
    || rref "mint: could not extract the public half of the freshly generated key"
  PUBTXT="$(openssl pkey -pubin -in "$STG/controller.pub" -noout -text 2>/dev/null)"
  case "$PUBTXT" in
    *prime256v1*) ok "mint: fresh private key is EC P-256 (asserted via the PUBLIC key only)" ;;
    *)            sfail "mint: the freshly generated key is not on curve prime256v1 -- nothing promoted" ;;
  esac

  openssl req -new -sha256 -key "$STG/controller.key" -config "$CNF" -out "$STG/controller.csr" 2>/dev/null \
    || rref "mint: openssl req could not build the CSR -- nothing minted"
  # The CSR is self-signed by the key we just made; verifying it proves the key works and
  # that the request was not mangled between build and sign.
  openssl req -in "$STG/controller.csr" -noout -verify >/dev/null 2>&1 \
    || sfail "mint: the CSR's self-signature does NOT verify -- nothing promoted"
  CSRTXT="$(openssl req -in "$STG/controller.csr" -noout -text 2>/dev/null)"
  CSR_NDNS="$(printf '%s\n' "$CSRTXT" | grep -oE 'DNS:[^,[:space:]]+' | wc -l | tr -d ' ')"
  # Asserted on the CSR as well as on the config and again on the certificate: F8 proved
  # that a config can be right and the artifact still wrong, so each transformation is
  # checked on its own output rather than trusted because the input was good.
  [ "$CSR_NDNS" = "2" ] || sfail "mint: the CSR carries $CSR_NDNS DNS SAN(s), expected 2 -- the request did not pick up [alt_names] (F8); nothing promoted"
  ok "mint: CSR built, self-signature verifies, carries 2 DNS SANs"

  openssl x509 -req -sha256 -in "$STG/controller.csr" \
    -CA "$CA_CERT" -CAkey "$CA_KEY" -passin file:"$CA_PASS" \
    -CAserial "$CA_SRL" -days 730 \
    -extfile "$CNF" -extensions v3_req -out "$STG/controller.cert.pem" >/dev/null 2>&1 \
    || rfail "mint: the controller CA REFUSED to sign -- the usual causes are a WRONG passphrase, an unreadable CA key, or a missing serial file. Nothing was promoted and the workspace is untouched"
  NEWC="$STG/controller.cert.pem"
  ok "mint: signed by this DC's controller CA (730 days, serial taken from the CA's own sequence)"

  # ============================ POST-MINT ASSERTIONS, IN STAGING, BEFORE PROMOTION
  # Any failure below exits 1 with NOTHING promoted. This is where a minter earns the right
  # to move files: the artifact is measured, not assumed, and it is measured before it can
  # do any harm.
  openssl verify -CAfile "$CA_CERT" "$NEWC" >/dev/null 2>&1 \
    || sfail "post-mint: the new certificate does NOT verify against the controller CA that just signed it"
  # THE NEGATIVE MATTERS AS MUCH AS THE POSITIVE (A8's reasoning): the two CAs are distinct
  # trust domains, and a chain accidentally built against the issuing CA would still look
  # like a working 'verify OK' if only the positive case were checked.
  # A NEGATIVE ASSERTION MUST PROVE ITS INSTRUMENT FIRST (hardened 2026-07-30, found by
  # adversarial review). `openssl verify -CAfile X cert` exits non-zero for "does not verify"
  # AND for "could not load X" -- so a corrupt or truncated issuing-CA file makes the check
  # below PASS VACUOUSLY and print a reassuring line about distinct trust domains. Measured:
  # with a corrupted issuing-ca.cert.pem the run printed "correctly does NOT verify against
  # the issuing CA" and promoted. So: prove the CA file LOADS before drawing any conclusion
  # from its failure to verify something.
  openssl x509 -in "$ISS_CERT" -noout >/dev/null 2>&1 \
    || rfail "post-mint: the issuing CA certificate could not be PARSED, so 'does not verify against it' would prove nothing about trust-domain separation -- refusing rather than concluding; nothing promoted"
  if openssl verify -CAfile "$ISS_CERT" "$NEWC" >/dev/null 2>&1; then
    sfail "post-mint: the new certificate ALSO verifies against the ISSUING CA -- the two CAs are not distinct trust domains; nothing promoted"
  fi
  ok "post-mint: verifies against the CONTROLLER CA and correctly does NOT verify against the issuing CA"

  GOT_CN="$(cn_of "$NEWC")"
  [ "$GOT_CN" = "$NEW_CN" ] || sfail "post-mint: the new certificate's CN is '$GOT_CN', expected exactly '$NEW_CN' -- nothing promoted"
  NEW_SAN="$(openssl x509 -in "$NEWC" -noout -ext subjectAltName 2>/dev/null)"
  [ -n "$NEW_SAN" ] || sfail "post-mint: the new certificate has NO subjectAltName extension at all -- F8's failure mode reproduced; nothing promoted"
  GOT_DNS="$(printf '%s' "$NEW_SAN" | grep -oE 'DNS:[^,[:space:]]+' | sed 's/^DNS://')"
  GOT_NDNS="$(printf '%s\n' "$GOT_DNS" | grep -c . || true)"
  [ "$GOT_NDNS" = "2" ] || sfail "post-mint: the new certificate carries $GOT_NDNS DNS SAN(s), expected exactly 2 -- nothing promoted"
  H1=0; H2=0
  for n in $GOT_DNS; do
    if [ "$n" = "$NEW_DNS1" ]; then H1=1; fi
    if [ "$n" = "$NEW_DNS2" ]; then H2=1; fi
  done
  if [ "$H1" != 1 ] || [ "$H2" != 1 ]; then
    sfail "post-mint: the new certificate's DNS SANs are not exactly '$NEW_DNS1' and '$NEW_DNS2' -- got: $(printf '%s' "$GOT_DNS" | tr '\n' ' '); nothing promoted"
  fi
  ok "post-mint: CN and both DNS SANs are exactly the derived names"

  GOT_IPS="$(printf '%s' "$NEW_SAN" | grep -oE 'IP Address:[0-9A-Fa-f:.]+' | sed 's/^IP Address://')"
  GOT_NORM="$(printf '%s\n' "$GOT_IPS" | norm_ips)"
  GOT_NIP="$(printf '%s\n' "$GOT_NORM" | grep -c . || true)"
  EXP_NIP=1; if [ -n "$EXP_V6" ]; then EXP_NIP=2; fi
  grep -Fqx "$EXP_V4" <<<"$GOT_NORM" || sfail "post-mint: the new certificate is MISSING this DC's provider v4 VIP ($EXP_V4) -- nothing promoted"
  if [ -n "$EXP_V6" ]; then
    grep -Fqx "$EXP_V6" <<<"$GOT_NORM" || sfail "post-mint: the new certificate is MISSING this DC's provider v6 VIP ($EXP_V6) -- nothing promoted"
  fi
  # AND NOTHING ELSE. An EXTRA IP SAN is a name this certificate can speak for that nobody
  # ruled it should -- the same class of defect as a missing one, and invisible to a check
  # that only asks "is the expected address present?".
  [ "$GOT_NIP" = "$EXP_NIP" ] || sfail "post-mint: the new certificate carries $GOT_NIP IP SAN(s), expected exactly $EXP_NIP -- an extra address is a name this cert should not be able to speak for; nothing promoted"
  ok "post-mint: IP SANs are exactly this DC's provider leg(s) -- $EXP_V4${EXP_V6:+ and $EXP_V6}, and nothing else"

  # KEY USAGE + EXTENDED KEY USAGE, ASSERTED ON THE ISSUED CERTIFICATE (2026-07-30).
  # FOUND BY MUTATION TESTING, and it was the worst hole in this file: deleting BOTH the
  # keyUsage and extendedKeyUsage lines from the config builder produced a certificate with
  # NO Key Usage and NO Extended Key Usage that minted, promoted, and left the harness at
  # 38/38 PASS. Until this block, `keyUsage` appeared EXACTLY ONCE in the whole script --
  # the printf that writes it -- and in no assertion anywhere, in reissue or in verify.
  # WHY IT MATTERS OPERATIONALLY: the controller certificate is used in BOTH directions
  # against amphora agents, so losing `clientAuth` breaks the amphora->controller handshake
  # while `serverAuth` still works. That is a one-directional TLS failure at LB-create time
  # with every gate reading PASS -- the hardest possible shape to debug.
  # Asserted on the CERT, never on the config: -extfile/-extensions is what actually decides
  # the issued extension set, so a config that says the right thing proves nothing.
  NEW_KU="$(openssl x509 -in "$NEWC" -noout -ext keyUsage 2>/dev/null || true)"
  NEW_EKU="$(openssl x509 -in "$NEWC" -noout -ext extendedKeyUsage 2>/dev/null || true)"
  printf '%s' "$NEW_KU" | grep -q 'Digital Signature' \
    || sfail "post-mint: the new certificate's keyUsage does not carry Digital Signature -- nothing promoted"
  printf '%s' "$NEW_KU" | grep -q 'Key Encipherment' \
    || sfail "post-mint: the new certificate's keyUsage does not carry Key Encipherment -- nothing promoted"
  printf '%s' "$NEW_KU" | grep -q 'critical' \
    || sfail "post-mint: the new certificate's keyUsage is not marked CRITICAL -- nothing promoted"
  printf '%s' "$NEW_EKU" | grep -q 'TLS Web Client Authentication' \
    || sfail "post-mint: the new certificate has no clientAuth EKU -- amphora->controller mTLS would fail in ONE direction only; nothing promoted"
  printf '%s' "$NEW_EKU" | grep -q 'TLS Web Server Authentication' \
    || sfail "post-mint: the new certificate has no serverAuth EKU -- controller->amphora TLS would fail; nothing promoted"
  ok "post-mint: keyUsage (critical: digitalSignature, keyEncipherment) and EKU (clientAuth, serverAuth) are present ON THE ISSUED CERT"

  NEW_PUB="$(openssl x509 -in "$NEWC" -noout -pubkey 2>/dev/null | openssl pkey -pubin -pubout -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)"
  STG_KPUB="$(openssl pkey -in "$STG/controller.key" -pubout -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)"
  [ "$NEW_PUB" = "$STG_KPUB" ] || sfail "post-mint: the new certificate's public key does NOT match the staged private key -- they are not a pair; nothing promoted"
  # PROVES THE KEY ACTUALLY ROTATED. A reissue that re-certified the OLD key would satisfy
  # every name assertion above and still leave the original private key in service, which is
  # not what "fresh EC P-256 key on every mint" means.
  [ "$NEW_PUB" != "$OLD_PUB" ] || sfail "post-mint: the new certificate carries the SAME public key as the old one -- the key did NOT rotate; nothing promoted"
  ok "post-mint: cert and staged key are a pair, and the public key DIFFERS from the outgoing certificate's (the key really rotated)"

  NEW_SERIAL="$(openssl x509 -in "$NEWC" -noout -serial 2>/dev/null | sed 's/^serial=//')"
  OLD_SER_N="$(norm_hex "$OLD_SERIAL")"; NEW_SER_N="$(norm_hex "$NEW_SERIAL")"
  SRL_AFTER="$(norm_hex "$(tr -d ' \t\r\n' < "$CA_SRL")")"
  [ "$NEW_SER_N" != "$OLD_SER_N" ] || sfail "post-mint: the new certificate's serial equals the old one's ($NEW_SER_N) -- the same CA issuing a duplicate serial is exactly what the .srl gate exists to prevent; nothing promoted"
  # MEASURED, NOT ASSUMED (OpenSSL 3.0.13, 2026-07-30): with -CAserial, openssl issues the
  # cert with the INCREMENTED value and writes that same value back, so the new serial is
  # the POST-mint file content, NOT the pre-mint content. Both inequality gates below hold
  # on any sane implementation; an equality gate against the PRE-mint value would be wrong
  # on this build, so it is deliberately not shipped.
  ADV="$(python3 -c 'import sys; print("YES" if int(sys.argv[2],16) > int(sys.argv[1],16) else "NO")' "$SRL_BEFORE" "$SRL_AFTER" 2>/dev/null || true)"
  [ "$ADV" = "YES" ] || sfail "post-mint: the CA serial file did not ADVANCE ($SRL_BEFORE -> $SRL_AFTER) -- the CA's issuance state is not tracking what it issued; nothing promoted"
  ok "post-mint: serial $OLD_SER_N -> $NEW_SER_N, and the CA's serial file advanced $SRL_BEFORE -> $SRL_AFTER"

  # VALIDITY BOUNDS, both sides. `-checkend N` exits 0 when the cert will NOT expire within
  # N seconds, so 729d must pass and 731d must fail: that brackets ~730 days without
  # parsing a date format that varies by locale and openssl version.
  openssl x509 -in "$NEWC" -noout -checkend $((729 * 86400)) >/dev/null 2>&1 \
    || sfail "post-mint: the new certificate expires within 729 days -- shorter than the ruled 730; nothing promoted"
  if openssl x509 -in "$NEWC" -noout -checkend $((731 * 86400)) >/dev/null 2>&1; then
    sfail "post-mint: the new certificate does not expire within 731 days -- longer than the ruled 730; nothing promoted"
  fi
  ok "post-mint: validity brackets ~730 days (not expiring within 729d, expiring within 731d)"

  # THE BUNDLE IS BUILT IN STAGING TOO, and held to A14's invariant before it moves.
  cat "$NEWC" "$STG/controller.key" > "$STG/controller.bundle.pem" \
    || sfail "post-mint: could not assemble the bundle in staging; nothing promoted"
  chmod 600 "$STG/controller.bundle.pem" "$NEWC" "$STG/controller.key" 2>/dev/null || true
  SB_NC="$(grep -c -- '-----BEGIN CERTIFICATE-----' "$STG/controller.bundle.pem" || true)"
  SB_NK="$(grep -cE -- '-----BEGIN [A-Z ]*PRIVATE KEY-----' "$STG/controller.bundle.pem" || true)"
  { [ "$SB_NC" = "1" ] && [ "$SB_NK" = "1" ]; } \
    || sfail "post-mint: the staged bundle carries $SB_NC CERTIFICATE and $SB_NK PRIVATE KEY block(s), expected 1 and 1; nothing promoted"
  ok "post-mint: staged bundle carries exactly one CERTIFICATE block and one PRIVATE KEY block"

  # ================================================================== PROMOTION (exit 5)
  # From here on, a failure has left the workspace partly changed. mv is per file and each
  # one is checked, because "it probably worked" is not a state an operator can act on.
  PROMOTING=1
  for f in controller.key controller.cert.pem controller.bundle.pem; do
    mv -f "$STG/$f" "$W/controller/$f" \
      || pfail "promotion: mv of $f into $W/controller FAILED -- the workspace is now PARTLY updated"
    chmod 600 "$W/controller/$f" \
      || pfail "promotion: chmod 600 of $W/controller/$f FAILED -- private material may be readable"
  done
  ok "promotion: controller.key, controller.cert.pem and controller.bundle.pem are in place, all 0600"

  # ============================================================ OVERLAY SURGERY, LAST
  # python3 READS BOTH FILES ITSELF. The value is NEVER passed in argv and NEVER handed to
  # sed/awk, because the bundle contains an UNENCRYPTED private key and /proc/<pid>/cmdline
  # is world-readable -- an argument would publish the key to every local user for the
  # lifetime of the process. Only PATHS cross the boundary.
  SURG="$(python3 - "$OVERLAY" "$CON_BUNDLE" "$OVL_TMP" <<'PY' 2>&1
import base64, os, sys
ovl, bundle, tmp = sys.argv[1], sys.argv[2], sys.argv[3]
b64 = base64.b64encode(open(bundle, "rb").read()).decode("ascii")
lines = open(ovl, "r", encoding="ascii").read().splitlines(True)
# The COLON is load-bearing: 'lb-mgmt-controller-cert:' cannot match
# 'lb-mgmt-controller-cacert:', which is a different value carrying the CA certificate.
hits = [i for i, l in enumerate(lines) if l.lstrip().startswith("lb-mgmt-controller-cert:")]
if len(hits) != 1:
    print("SURGERY_FAIL expected exactly 1 lb-mgmt-controller-cert line, found %d" % len(hits))
    sys.exit(1)
i = hits[0]
old = lines[i]
indent = old[:len(old) - len(old.lstrip())]
eol = "\n" if old.endswith("\n") else ""
lines[i] = '%slb-mgmt-controller-cert: "%s"%s' % (indent, b64, eol)
# O_EXCL so a leftover temp from a crashed run is a hard error rather than a silent
# overwrite; 0600 at CREATION time, not after, so the secret is never briefly world-readable.
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
    with os.fdopen(fd, "w", encoding="ascii") as fh:
        fh.write("".join(lines))
except Exception:
    os.unlink(tmp)
    raise
os.replace(tmp, ovl)   # atomic within the directory: readers see old or new, never partial
os.chmod(ovl, 0o600)
print("SURGERY_OK")
PY
)"
  case "$SURG" in
    *SURGERY_OK*) ok "overlay surgery: lb-mgmt-controller-cert rewritten via O_EXCL temp + atomic os.replace (value never touched argv)" ;;
    *)            pfail "overlay surgery FAILED: $SURG" ;;
  esac

  # ======================================================= PROVE THE SURGERY (exit 5)
  # The pre-image comes from the ARCHIVE -- the only copy of the overlay as it was. Read
  # straight out of the tarball in memory rather than extracted to disk: extracting would
  # put a second copy of the CA key blobs on the filesystem to prove they had not changed.
  # VERDICT TOKENS ONLY. No value from either file is ever printed.
  PROOF="$(python3 - "$ARCH" "$OVERLAY" "$CON_BUNDLE" "$CA_CERT" "overlays/${SITE}-octavia-pki.yaml" <<'PY' 2>&1
import base64, hashlib, sys, tarfile
arch, ovl, bundle, cacert, member = sys.argv[1:6]
def sha(b): return hashlib.sha256(b).hexdigest()
pre = tarfile.open(arch, "r:gz").extractfile(member).read().decode("ascii").splitlines(True)
post = open(ovl, "r", encoding="ascii").read().splitlines(True)
if len(pre) != len(post):
    print("PROOF_FAIL linecount pre=%d post=%d" % (len(pre), len(post))); sys.exit(1)
changed = [i for i in range(len(pre)) if pre[i] != post[i]]
if len(changed) != 1:
    print("PROOF_FAIL changed_lines=%d expected=1" % len(changed)); sys.exit(1)
if not post[changed[0]].lstrip().startswith("lb-mgmt-controller-cert:"):
    print("PROOF_FAIL the changed line is not lb-mgmt-controller-cert"); sys.exit(1)
def kv(lines):
    d = {}
    for l in lines:
        s = l.strip()
        if s.startswith("lb-mgmt-") and ":" in s:
            k, v = s.split(":", 1)
            d[k.strip()] = v.strip().strip('"')
    return d
a, b = kv(pre), kv(post)
if set(a) != set(b):
    print("PROOF_FAIL the lb-mgmt-* key SET changed"); sys.exit(1)
for k in sorted(a):
    if k != "lb-mgmt-controller-cert" and a[k] != b[k]:
        print("PROOF_FAIL value changed for " + k); sys.exit(1)
try:
    dec = base64.b64decode(b["lb-mgmt-controller-cert"], validate=True)
except Exception:
    print("PROOF_FAIL the new value is not valid base64"); sys.exit(1)
if sha(dec) != sha(open(bundle, "rb").read()):
    print("PROOF_FAIL the new value does not decode to the promoted bundle"); sys.exit(1)
try:
    cad = base64.b64decode(b.get("lb-mgmt-controller-cacert", ""), validate=True)
except Exception:
    print("PROOF_FAIL lb-mgmt-controller-cacert is not valid base64"); sys.exit(1)
if sha(cad) != sha(open(cacert, "rb").read()):
    print("PROOF_FAIL lb-mgmt-controller-cacert no longer decodes to the controller CA cert"); sys.exit(1)
print("PROOF_OK other_values_identical=%d changed_lines=1 line_count=%d" % (len(a) - 1, len(post)))
PY
)"
  case "$PROOF" in
    PROOF_OK*) ok "surgery proof: $PROOF -- the new value decodes to the promoted bundle, and lb-mgmt-controller-cacert still decodes to this DC's controller CA" ;;
    # The overlay HAS been rewritten by this point, so exit 5's "rollback required" is the
    # honest verdict even though its usual phrasing assumes the overlay was not reached.
    *)         pfail "surgery proof FAILED: $PROOF -- the overlay HAS been rewritten and the new files ARE promoted" ;;
  esac
  PROMOTING=0

  # ============================================== FINAL: the independent gate must agree
  # A minter grading its own homework is not a gate. `verify` is re-run as a SEPARATE
  # process over the same site, and every one of its assertions must pass.
  echo
  VOUT="$(bash "$SELF" verify "$SITE" 2>&1)"; VRC=$?
  if [ "$VRC" -ne 0 ]; then
    printf '%s\n' "$VOUT" | sed 's/^/  | /'
    nfail "post-reissue verify did NOT pass (exit $VRC)"
    echo "octavia-pki reissue ($SITE): FAIL -- the mint completed and IS promoted, but the independent verify gate disagrees. This is a defect in the RESULT, not a refusal to act; roll back with:"
    echo "  restore: $RESTORE_CMD"
    exit 1
  fi
  ok "post-reissue verify: PASS -- an independent re-run of every assertion for $SITE reports 0 failures"

  echo
  echo "octavia-pki reissue ($SITE): OK"
  echo "  serial:   $OLD_SER_N -> $NEW_SER_N   (CA serial file $SRL_BEFORE -> $SRL_AFTER)"
  echo "  CN:       $NEW_CN"
  echo "  DNS SANs: $NEW_DNS1, $NEW_DNS2"
  echo "  IP SANs:  $EXP_V4${EXP_V6:+, $EXP_V6}"
  echo "  backup:   $ARCH"
  echo "  sha256:   $ARCH_SHA"
  echo "  restore:  $RESTORE_CMD"
  exit 0
}

if [ "$ACTION" = "reissue" ]; then do_reissue; fi

echo "=== octavia-pki verify: $SITE ==="

# ---------------------------------------------------------------- preconditions (REFUSE)
command -v openssl >/dev/null 2>&1 || nrefuse "openssl is not on PATH -- cannot evaluate any certificate (this is a MISSING TOOL, not a bad PKI)"
command -v python3 >/dev/null 2>&1 || nrefuse "python3 is not on PATH -- needed to normalise IPv6 literals and parse the VIP overlay"

assert_host_authority

if [ "$NREFUSE" -ne 0 ]; then
  echo "octavia-pki verify ($SITE): REFUSE -- preconditions unmet; this is NOT a pass"; exit 3
fi

# ---------------------------------------------------------------- expected values, DERIVED
derive_vips || { echo "octavia-pki verify ($SITE): REFUSE"; exit 3; }
# DC_LABEL is the CA-subject token the generator bakes. Derived the same way the runbook
# derives it, so a mismatch here means the generator ran with an unset/wrong DC_LABEL --
# which is F8's sibling defect (the runbook guards ${DC:?} but NOT ${DC_LABEL}).
derive_dc_label
echo "  (expect CA label '$DC_LABEL'; provider VIP v4=$EXP_V4 v6=${EXP_V6:-<none>})"

W="$PKI_HOME/octavia-pki/$SITE"
ISS_CERT="$W/issuing-ca/issuing-ca.cert.pem"
CON_CACERT="$W/controller-ca/controller-ca.cert.pem"
CON_CERT="$W/controller/controller.cert.pem"
OVERLAY="$REPO_ROOT/overlays/${SITE}-octavia-pki.yaml"

# ---------------------------------------------------------------- A1 workspace + file set
if [ ! -d "$W" ]; then
  nfail "A1 workspace $W does not exist -- this DC's PKI has not been generated (run Step 1.0-GEN for $SITE)"
else
  ok "A1 workspace present: ~/octavia-pki/$SITE"
  # The nine artifacts the generator is documented to produce. Absence is CONCLUSIVE.
  MISSING=()
  # `controller-ca.cert.srl` is asserted because it is CA issuance STATE, not residue: if it
  # is missing, the next `-CAcreateserial` starts a fresh sequence and the same CA can issue a
  # DUPLICATE serial. The controller cert is 2-year and D-106's FQDN work will want new SANs
  # (F9), so reissuance is scheduled, not hypothetical. Declared in creds-matrix.tsv alongside
  # this assertion -- the register and the gate must agree on what should exist.
  for rel in issuing-ca/passphrase.txt issuing-ca/issuing-ca.key.enc issuing-ca/issuing-ca.cert.pem \
             controller-ca/passphrase.txt controller-ca/controller-ca.key.enc controller-ca/controller-ca.cert.pem \
             controller-ca/controller-ca.cert.srl \
             controller/controller.key controller/controller.cert.pem controller/controller.bundle.pem; do
    [ -f "$W/$rel" ] || MISSING+=("$rel")
  done
  if [ "${#MISSING[@]}" -eq 0 ]; then ok "A2 all 10 expected artifacts present"
  else nfail "A2 missing ${#MISSING[@]} expected artifact(s): ${MISSING[*]}"; fi
fi

# ---------------------------------------------------------------- A3 modes
# Private material must be 0600. CERTS are public, so confidentiality is not the point --
# but a cert that is GROUP- or WORLD-WRITABLE is a substitutable trust anchor, which is
# the E2 finding of 2026-07-29 and the reason this assertion exists at all.
mode_of() { stat -c '%a' "$1" 2>/dev/null; }
for rel in issuing-ca/passphrase.txt issuing-ca/issuing-ca.key.enc \
           controller-ca/passphrase.txt controller-ca/controller-ca.key.enc \
           controller/controller.key controller/controller.bundle.pem; do
  f="$W/$rel"; [ -f "$f" ] || continue
  m="$(mode_of "$f")"
  if [ "$m" = "600" ]; then ok "A3 private $rel is 0600"
  else nfail "A3 private $rel is $m -- private material must be 0600"; fi
done
# The CA SERIAL is in this list, not the private list: it holds no secret, but rewriting it is
# an INTEGRITY attack -- forcing the next issuance to reuse a serial. It was missed on the first
# pass, and the miss was CAUGHT BY P5 rather than here: `verify` read 26/0 while
# `creds-matrix.py` E2 reported it 664 on both DCs. The two gates are complementary by design
# (this one checks whether a certificate is the RIGHT certificate; P5 checks the declared
# artifact set), but they must not DISAGREE about a file both can see.
for rel in issuing-ca/issuing-ca.cert.pem controller-ca/controller-ca.cert.pem \
           controller-ca/controller-ca.cert.srl controller/controller.cert.pem; do
  f="$W/$rel"; [ -f "$f" ] || continue
  m="$(mode_of "$f")"
  # group-writable = 2nd digit has the 2 bit; world-writable = 3rd digit has the 2 bit
  gw=$(( ${m:1:1} & 2 )); ww=$(( ${m:2:1} & 2 ))
  if [ "$gw" -eq 0 ] && [ "$ww" -eq 0 ]; then ok "A3 cert $rel is $m -- not group/world writable"
  else nfail "A3 cert $rel is $m -- WRITABLE by group/other: a substitutable trust anchor (E2)"; fi
done

# ---------------------------------------------------------------- A4/A5 CA identities
subject_of() { openssl x509 -in "$1" -noout -subject 2>/dev/null; }
check_subject() { # <file> <expected-substring> <label>
  local f="$1" want="$2" lbl="$3" got
  [ -f "$f" ] || { nfail "$lbl certificate absent"; return; }
  got="$(subject_of "$f")"
  if [ -z "$got" ]; then nrefuse "$lbl subject unreadable -- no conclusion drawn"; return; fi
  case "$got" in
    *"$want"*) ok "$lbl subject names this DC: contains '$want'" ;;
    *) nfail "$lbl subject does NOT contain '$want' -- got: ${got#subject=}" ;;
  esac
}
check_subject "$ISS_CERT"   "$DC_LABEL Omega Cloud Octavia Issuing CA"    "A4 issuing CA"
check_subject "$CON_CACERT" "$DC_LABEL Omega Cloud Octavia Controller CA" "A5 controller CA"

# ---------------------------------------------------------------- A6/A7 self-signatures
for pair in "A6 issuing CA:$ISS_CERT" "A7 controller CA:$CON_CACERT"; do
  lbl="${pair%%:*}"; f="${pair#*:}"
  [ -f "$f" ] || { nfail "$lbl absent"; continue; }
  if openssl verify -CAfile "$f" "$f" >/dev/null 2>&1; then ok "$lbl self-signature verifies"
  else nfail "$lbl does NOT verify against itself"; fi
done

# ---------------------------------------------------------------- A8 chain, both directions
# POSITIVE: the controller cert must verify against the CONTROLLER CA.
# NEGATIVE: it must NOT verify against the ISSUING CA. Both matter -- the two CAs are
# distinct trust domains, and a chain accidentally built against the wrong one would still
# produce a working-looking `verify OK` if only the positive case were checked.
if [ -f "$CON_CERT" ] && [ -f "$CON_CACERT" ] && [ -f "$ISS_CERT" ]; then
  if openssl verify -CAfile "$CON_CACERT" "$CON_CERT" >/dev/null 2>&1; then
    ok "A8 controller cert verifies against the CONTROLLER CA"
  else
    nfail "A8 controller cert does NOT verify against the controller CA -- chain is wrong"
  fi
  # THE NEGATIVE PROVES ITS INSTRUMENT FIRST (hardened 2026-07-30). `openssl verify -CAfile X`
  # exits non-zero both for "does not verify" and for "could not load X", so a corrupt or
  # truncated issuing-CA file would make this negative pass VACUOUSLY and print a reassuring
  # line about distinct trust domains. "Could not look" is never "nothing there".
  if ! openssl x509 -in "$ISS_CERT" -noout >/dev/null 2>&1; then
    nrefuse "A8 the issuing CA certificate could not be PARSED -- 'does not verify against it' would prove nothing about trust-domain separation; no conclusion drawn"
  elif openssl verify -CAfile "$ISS_CERT" "$CON_CERT" >/dev/null 2>&1; then
    nfail "A8 controller cert ALSO verifies against the ISSUING CA -- the two CAs are not distinct trust domains"
  else
    ok "A8 controller cert correctly does NOT verify against the issuing CA"
  fi
else
  nfail "A8 cannot evaluate the chain -- one or more certificates absent"
fi

# ---------------------------------------------------------------- A9 the SAN set (F8's gap)
# THIS IS THE ASSERTION WHOSE ABSENCE F8 RECORDS. A controller certificate with an EMPTY
# `[alt_names]` -- the exact output of the runbook's heredoc paste hazard -- passed every
# other check in the chain. openssl prints IPv6 EXPANDED AND UPPERCASE
# (`2602:F3E2:F02:11:0:0:0:57`), so a literal string compare against the overlay's
# compressed form would FAIL on a CORRECT cert. Both sides are normalised.
if [ ! -f "$CON_CERT" ]; then
  nfail "A9 controller cert absent -- SAN set not evaluated"
else
  SAN_RAW="$(openssl x509 -in "$CON_CERT" -noout -ext subjectAltName 2>/dev/null)"
  if [ -z "$SAN_RAW" ]; then
    nfail "A9 controller cert has NO subjectAltName extension at all -- this is F8's failure mode (empty [alt_names]); amphora TLS would have no valid name to match"
  else
    NDNS="$(printf '%s' "$SAN_RAW" | grep -oE 'DNS:[^,[:space:]]+' | wc -l | tr -d ' ')"
    [ "$NDNS" -eq 2 ] && ok "A9 SAN carries 2 DNS names" \
                      || nfail "A9 SAN carries $NDNS DNS name(s), expected 2"
    GOT_IPS="$(printf '%s' "$SAN_RAW" | grep -oE 'IP Address:[0-9A-Fa-f:.]+' | sed 's/^IP Address://')"
    NORM="$(printf '%s\n' "$GOT_IPS" | norm_ips)"
    printf '%s\n' "$NORM" | grep -qx "$EXP_V4" \
      && ok "A9 SAN carries this DC's provider v4 VIP ($EXP_V4)" \
      || nfail "A9 SAN is MISSING this DC's provider v4 VIP ($EXP_V4) -- got: $(printf '%s' "$NORM" | tr '\n' ' ')"
    if [ -n "$EXP_V6" ]; then
      printf '%s\n' "$NORM" | grep -qx "$EXP_V6" \
        && ok "A9 SAN carries this DC's provider v6 VIP ($EXP_V6)" \
        || nfail "A9 SAN is MISSING this DC's provider v6 VIP ($EXP_V6) -- D-109 ruling note 2026-07-29 requires it"
    else
      ok "A9 overlay declares no v6 provider leg, so no v6 SAN is expected"
    fi
  fi
fi

# ------------------------------------------------- A12 the DNS SANs, ARMED BY THE POSTURE
# F9. The controller cert's two DNS SANs are baked `dc0.vr0` literals on BOTH DCs. They are
# INERT today only because `os-public-hostname` is set NOWHERE -- bundle.yaml posture B5 is
# IP-ONLY ("the dual VIPs ARE the catalog endpoints"), and R5 refused setting it at Stage 5
# because that repeats D-019. Amphorae reach the controller by ADDRESS on o-hm0's ULA and the
# controller verifies them by UUID, so nothing resolves these names.
#
# THE POINT OF THIS CHECK IS THAT NOBODY HAS TO REMEMBER F9. It ARMS ITSELF from the very
# change that makes the SANs load-bearing: the moment `os-public-hostname` appears in the
# merged deploy input (D-106's Stage-7 work), the assertion goes live. A scheduled fix depends
# on a future session reading a note; this depends on nothing.
#
# WHAT IT ASSERTS: the FULL expected zone, not just the region. DOCFIX-205 (2026-07-30)
# replaced this check's former REFUSAL with a real assertion. It used to refuse on the DC
# label, on the grounds that D-008's shape plus D-106's `dc1.vr1`/`dc2.vr1` instantiation left
# open whether substrate `vr1-dc1` is `dc1` by token or `dc2` by position. THAT PREMISE WAS
# ALREADY DEAD: D-117 (ADOPTED 2026-07-13) names the supersession in its own Status line
# ("Supersedes ... the D-106 `dc1`/`dc2` zone labels") and rules the replacement -- "The repo's
# `dc1`/`dc2` labels are retired in favour of `dc0`/`dc1`". D-106 read as un-annotated because
# D-117's annotation half was never executed; both are fixed as of DOCFIX-205.
#
# So the expected zone is fully derivable from the site token and is NEVER typed -- the
# derivation itself now lives in derive_zone() above, because `reissue` MINTS the names this
# assertion CHECKS and the two must not be able to disagree.
#
# Deliberately NOT collapsed to D-119's single `vr1-dc0` token: D-119's bare-dcN rule targets a
# token read ALONE ("a reader who does not know which cloud the sentence is about") and here the
# next label IS the region; D-119 completes D-117 rather than reversing it, and D-117's own
# replacement labels are bare `dc0`/`dc1`. Collapsing would also amend D-008's ratified shape.
if [ -f "$CON_CERT" ]; then
  derive_zone                               # sets REGION / DC_DNS / EXP_ZONE (hoisted; see above)
  # Is the posture armed? Look for a real option KEY in the merged input, not prose.
  ARMED=0
  for f in "$REPO_ROOT/bundle.yaml" "$REPO_ROOT/overlays/${SITE}-vips.yaml" \
           "$REPO_ROOT/overlays/${SITE}-machines.yaml"; do
    [ -f "$f" ] || continue
    grep -qE '^[[:space:]]+os-public-hostname:[[:space:]]*[^[:space:]#]' "$f" && ARMED=1
  done
  DNSN="$(printf '%s' "${SAN_RAW:-}" | grep -oE 'DNS:[^,[:space:]]+' | sed 's/^DNS://')"
  if [ "$ARMED" -eq 0 ]; then
    ok "A12 DNS SANs are INERT -- os-public-hostname is set in no deploy artifact (B5 IP-only), so nothing resolves them; this assertion ARMS ITSELF when D-106 sets it"
    BAD=0
    for n in $DNSN; do
      case "$n" in *".${EXP_ZONE}") : ;; *) BAD=1 ;; esac
    done
    if [ -n "$DNSN" ] && [ "$BAD" -ne 0 ]; then
      ok "A12 (recorded, not failed) the DNS SANs are not in this DC's expected zone '$EXP_ZONE' -- harmless while inert, and F9's reissue obligation"
    fi
    # GAP FIX (2026-07-30, measured): this branch had NO POSITIVE LINE. It printed the INERT
    # banner unconditionally and, when the names were WRONG, a "(recorded, not failed)" line
    # -- so a CORRECT cert and an INCORRECT one were indistinguishable in the unarmed posture
    # except by counting assertions. That matters now: `reissue` MINTS zone-correct names and
    # the live posture is unarmed, so without this line the minter's own success could not be
    # demonstrated from `verify` output at all. The string is the SAME one the armed branch
    # prints, so acceptance is provable by ONE grep in EITHER posture.
    # `-n "$DNSN"` is load-bearing: with ZERO DNS SANs the loop above never runs, BAD stays 0,
    # and a vacuous "all of them are correct" would be printed for the WORST possible cert
    # (F8's exact output). The armed branch nfails on empty, so requiring non-empty here is
    # what makes the two branches genuinely symmetric rather than merely similar.
    if [ -n "$DNSN" ] && [ "$BAD" -eq 0 ]; then
      ok "A12 DNS SANs are all in this DC's expected zone '$EXP_ZONE'"
    fi
  else
    # Armed: the names are now load-bearing.
    BAD=0
    if [ -z "$DNSN" ]; then
      nfail "A12 os-public-hostname IS SET, so DNS SANs are load-bearing -- the controller cert carries NO DNS SAN at all; it must be reissued with names in '$EXP_ZONE'"; BAD=1
    fi
    for n in $DNSN; do
      case "$n" in
        *".${EXP_ZONE}") : ;;
        *) nfail "A12 os-public-hostname IS SET, so DNS SANs are load-bearing -- '$n' is not in this DC's expected zone '$EXP_ZONE' (D-008 shape, D-117 labels; F9); the certificate must be reissued"; BAD=1 ;;
      esac
    done
    [ "$BAD" -eq 0 ] && ok "A12 DNS SANs are all in this DC's expected zone '$EXP_ZONE'"
  fi

  # ------------------------------------------------ A13 the controller cert's SUBJECT CN
  # NEW 2026-07-30. NOTHING asserted this before: check_subject() runs on the two CAs only,
  # so the leaf's own name was unmeasured. It is not cosmetic -- 1.0-GEN.c bakes
  # `CN = octavia-controller.omega.dc0.vr0.cloud.neumatrix.local` as a LITERAL, on BOTH DCs,
  # from the same paste as the DNS SANs. A12 catches the SANs; the CN sat in the same block
  # and nothing looked at it.
  #
  # ARMED BY THE SAME POSTURE AS A12, DELIBERATELY. The CN is a DNS name serving exactly the
  # F9 role the SANs do, and it is wrong in exactly the same way on exactly the same certs.
  # Making it an UNCONDITIONAL failure would flip the live gate from PASS to FAIL for the
  # very condition A12 was designed to RECORD rather than fail -- a posture change smuggled
  # in as an assertion. Two consequences worth stating: (1) it arms itself, so D-106's
  # hostname work cannot land without this becoming load-bearing; (2) the UNCONDITIONAL CN
  # gate does exist -- it is `reissue`'s post-mint assertion, which refuses to promote a
  # certificate whose CN is not `octavia-controller.$EXP_ZONE`. Anything this script MINTS
  # is held to the exact name; anything it merely FINDS is held to the ruled posture.
  EXP_CN="octavia-controller.${EXP_ZONE}"
  GOT_CN="$(cn_of "$CON_CERT")"
  if [ -z "$GOT_CN" ]; then
    nrefuse "A13 controller cert subject CN unreadable -- no conclusion drawn"
  elif [ "$GOT_CN" = "$EXP_CN" ]; then
    ok "A13 controller cert CN is '$EXP_CN'"
  elif [ "$ARMED" -eq 0 ]; then
    ok "A13 (recorded, not failed) controller cert CN is '$GOT_CN', not '$EXP_CN' -- harmless while inert, and F9's reissue obligation"
  else
    nfail "A13 os-public-hostname IS SET, so the subject CN is load-bearing -- it is '$GOT_CN', not '$EXP_CN' (D-008 shape, D-117 labels; F9); the certificate must be reissued"
  fi
fi

# --------------------------------------------- A14 cert<->key pairing + bundle integrity
# NEW 2026-07-30. Every existing assertion reads the CERTIFICATE. None of them proves the
# controller's PRIVATE KEY belongs to it, nor that the BUNDLE Octavia is actually handed
# matches either file. That is a real hole, not a theoretical one: `reissue` rotates all
# three, and a promotion that moved two of the three would leave a chain that still
# `verify`s (A8 reads only the cert) while the amphora TLS handshake fails at runtime with
# a key/cert mismatch -- the worst kind of defect, invisible to the gate that exists.
#
# NEVER PRINTS KEY MATERIAL. The private bytes flow into `sha256sum` and nowhere else; only
# the digests are compared and only a verdict is printed. That property is asserted
# executably by the harness's no-leak case, not merely claimed here.
CON_KEY="$W/controller/controller.key"
CON_BUNDLE="$W/controller/controller.bundle.pem"
if [ -f "$CON_KEY" ] && [ -f "$CON_CERT" ]; then
  KPUB="$(openssl pkey -in "$CON_KEY" -pubout -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)"
  CPUB="$(openssl x509 -in "$CON_CERT" -noout -pubkey 2>/dev/null \
          | openssl pkey -pubin -pubout -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)"
  # An EMPTY digest is sha256 of nothing, which two unreadable files would MATCH -- that is
  # a checker that cannot fail, so it REFUSES instead of agreeing with itself.
  EMPTY_SHA="$(printf '' | sha256sum | cut -d' ' -f1)"
  if [ "$KPUB" = "$EMPTY_SHA" ] || [ "$CPUB" = "$EMPTY_SHA" ]; then
    nrefuse "A14 could not extract a public key from the controller key and/or cert -- no pairing conclusion drawn"
  elif [ "$KPUB" = "$CPUB" ]; then
    ok "A14 controller.key and controller.cert.pem carry the SAME public key (they are a pair)"
  else
    nfail "A14 controller.key does NOT match controller.cert.pem -- the certificate was issued for a DIFFERENT key; amphora TLS cannot complete"
  fi
else
  nfail "A14 cannot evaluate cert<->key pairing -- controller.key and/or controller.cert.pem absent"
fi
if [ -f "$CON_BUNDLE" ] && [ -f "$CON_CERT" ] && [ -f "$CON_KEY" ]; then
  NC="$(grep -c -- '-----BEGIN CERTIFICATE-----' "$CON_BUNDLE" 2>/dev/null || true)"
  NK="$(grep -cE -- '-----BEGIN [A-Z ]*PRIVATE KEY-----' "$CON_BUNDLE" 2>/dev/null || true)"
  if [ "$NC" = "1" ] && [ "$NK" = "1" ]; then
    ok "A14 bundle carries exactly one CERTIFICATE block and one PRIVATE KEY block"
  else
    nfail "A14 bundle carries $NC CERTIFICATE block(s) and $NK PRIVATE KEY block(s), expected 1 and 1 -- a stray extra key or a concatenated chain changes what the charm installs"
  fi
  BC="$(awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/' "$CON_BUNDLE" | sha256sum | cut -d' ' -f1)"
  FC="$(sha256sum < "$CON_CERT" | cut -d' ' -f1)"
  [ "$BC" = "$FC" ] && ok "A14 the bundle's CERTIFICATE block is byte-identical to controller.cert.pem" \
                    || nfail "A14 the bundle's CERTIFICATE block DIFFERS from controller.cert.pem -- the deployed cert is not the one every other assertion just measured"
  BK="$(awk '/-----BEGIN [A-Z ]*PRIVATE KEY-----/,/-----END [A-Z ]*PRIVATE KEY-----/' "$CON_BUNDLE" | sha256sum | cut -d' ' -f1)"
  FK="$(sha256sum < "$CON_KEY" | cut -d' ' -f1)"
  [ "$BK" = "$FK" ] && ok "A14 the bundle's PRIVATE KEY block is byte-identical to controller.key" \
                    || nfail "A14 the bundle's PRIVATE KEY block DIFFERS from controller.key -- the deployed key is not the one paired above"
else
  nfail "A14 cannot evaluate bundle integrity -- controller.bundle.pem, controller.cert.pem and/or controller.key absent"
fi

# ------------------------------------- A15 keyUsage / EKU on the controller cert (2026-07-30)
# FOUND BY MUTATION TESTING. Before this, `verify` asserted NOTHING about the issued
# extension set: a certificate with no keyUsage and no extendedKeyUsage passed every gate in
# this file. `reissue` now asserts them post-mint, but that only protects certs THIS SCRIPT
# mints -- the two live certs were minted by 1.0-GEN.c's copy-paste chain, which is exactly
# the path that produced F8's SAN-less cert. A gate that only checks its own output is not
# a gate on the estate.
# WHY THESE TWO EKUs SPECIFICALLY: the controller cert is used in BOTH directions against
# amphora agents. Losing clientAuth breaks amphora->controller only; losing serverAuth breaks
# controller->amphora only. Either is a one-directional TLS failure, which is why the set is
# asserted rather than merely its presence.
if [ -f "$CON_CERT" ]; then
  KU="$(openssl x509 -in "$CON_CERT" -noout -ext keyUsage 2>/dev/null || true)"
  EKU="$(openssl x509 -in "$CON_CERT" -noout -ext extendedKeyUsage 2>/dev/null || true)"
  KUBAD=""
  printf '%s' "$KU"  | grep -q 'Digital Signature'              || KUBAD="$KUBAD digitalSignature"
  printf '%s' "$KU"  | grep -q 'Key Encipherment'               || KUBAD="$KUBAD keyEncipherment"
  printf '%s' "$KU"  | grep -q 'critical'                       || KUBAD="$KUBAD critical-flag"
  printf '%s' "$EKU" | grep -q 'TLS Web Client Authentication'  || KUBAD="$KUBAD clientAuth"
  printf '%s' "$EKU" | grep -q 'TLS Web Server Authentication'  || KUBAD="$KUBAD serverAuth"
  if [ -z "$KU$EKU" ]; then
    nfail "A15 the controller cert carries NEITHER keyUsage NOR extendedKeyUsage -- amphora mTLS cannot be relied on in either direction"
  elif [ -n "$KUBAD" ]; then
    nfail "A15 the controller cert's usage extensions are missing:$KUBAD -- a missing EKU breaks mTLS in ONE direction only, which fails at LB-create time, not here"
  else
    ok "A15 controller cert carries keyUsage (critical: digitalSignature, keyEncipherment) and EKU (clientAuth, serverAuth)"
  fi
fi

# --------------------------------------- A16 the controller cert is not expiring (2026-07-30)
# FOUND BY MUTATION TESTING, and it was the headline hole: dropping `-days 730` makes openssl
# default to THIRTY DAYS, and a 30-day controller certificate passed every gate in this repo
# forever -- `checkend`, `notAfter` and `enddate` appeared ZERO times in the verify half.
# This is not hypothetical for the estate: the live certs came from a hand-pasted runbook
# block where the flag is one token on a long line.
# The floor is deliberately generous (30 days) rather than a 730-day bracket: verify grades
# certificates of any age, including ones legitimately part-way through their life, so it
# asserts "this will not lapse under us", not "this was minted yesterday". reissue keeps the
# tight 729/731 bracket for what IT mints.
if [ -f "$CON_CERT" ]; then
  if ! openssl x509 -in "$CON_CERT" -noout -checkend 0 >/dev/null 2>&1; then
    nfail "A16 the controller cert has ALREADY EXPIRED -- amphora TLS is broken now; reissue is required"
  elif ! openssl x509 -in "$CON_CERT" -noout -checkend $((30 * 86400)) >/dev/null 2>&1; then
    nfail "A16 the controller cert expires within 30 days -- reissue before it lapses (a short-dated cert is what a missing '-days 730' produces)"
  else
    ok "A16 controller cert is valid and not expiring within 30 days"
  fi
fi

# ---------------------------------------------------------------- A10 the deploy overlay
if [ ! -f "$OVERLAY" ]; then
  nfail "A10 deploy overlay absent at overlays/${SITE}-octavia-pki.yaml -- phase-01 hard-ABORTs without it"
else
  m="$(mode_of "$OVERLAY")"
  [ "$m" = "600" ] && ok "A10 overlay is 0600" || nfail "A10 overlay is $m -- it carries CA key blobs plus a plaintext passphrase; must be 0600"
  K="$(grep -c 'lb-mgmt-' "$OVERLAY" 2>/dev/null || true)"
  [ "$K" = "5" ] && ok "A10 overlay declares 5 lb-mgmt-* keys" || nfail "A10 overlay declares $K lb-mgmt-* key(s), expected 5"
  if LC_ALL=C grep -qP '[^\x00-\x7F]' "$OVERLAY" 2>/dev/null; then
    nfail "A10 overlay contains non-ASCII bytes"
  else ok "A10 overlay is ASCII clean"; fi
  # The F4 gate, re-asserted at verify time: this file is the only thing between a CA
  # private key and publication in a repo SEC-004 records as PUBLIC.
  if git -C "$REPO_ROOT" check-ignore -q "$OVERLAY" 2>/dev/null; then
    ok "A10 overlay is gitignored (F4)"
  else
    nfail "A10 overlay is NOT gitignored -- a CA private key is committable RIGHT NOW (F4)"
  fi

  # A17 -- THE OVERLAY MUST CARRY THE MATERIAL THIS WORKSPACE HOLDS (NEW 2026-07-30).
  # FOUND BY ADVERSARIAL REVIEW, and it is the assertion that makes every other one mean
  # something. Until now A10 graded the overlay's SHAPE -- mode, key count, ASCII, gitignore
  # -- and never its VALUES, while A1-A16 graded the workspace. Nothing joined them. Measured
  # consequence: an overlay whose embedded cert decoded to a DIFFERENT bundle than the one on
  # disk reported PASS, 0 failed. The overlay is what actually reaches the charm, so that is
  # the copy that matters; the workspace is just where it was made.
  # HOW IT HAPPENS: `reissue` promotes the workspace and rewrites the overlay as two steps
  # (exit 5 exists for the gap between them), a partial restore can extract one tar member
  # and not the other, and 1.0-GEN.d writes the overlay from a `cd`-relative path that F12
  # records as biting repeatedly. All three land here.
  # Compares DIGESTS of decoded bytes; no value is ever printed.
  if [ -f "$CON_BUNDLE" ] && [ -f "$CON_CACERT" ]; then
    OVLCMP="$(python3 - "$OVERLAY" "$CON_BUNDLE" "$CON_CACERT" <<'PY' 2>/dev/null || true
import base64, hashlib, sys, yaml
def d(b): return hashlib.sha256(b).hexdigest()
try:
    o = yaml.safe_load(open(sys.argv[1]))["applications"]["octavia"]["options"]
except Exception:
    print("UNREADABLE"); raise SystemExit(0)
out = []
for key, path, tag in (("lb-mgmt-controller-cert", sys.argv[2], "CERT"),
                       ("lb-mgmt-controller-cacert", sys.argv[3], "CACERT")):
    v = o.get(key)
    if not v:
        out.append(tag + "_EMPTY"); continue
    try:
        raw = base64.b64decode(v, validate=True)
    except Exception:
        out.append(tag + "_UNDECODABLE"); continue
    out.append(tag + ("_OK" if d(raw) == d(open(path, "rb").read()) else "_MISMATCH"))
print(" ".join(out))
PY
)"
    case "$OVLCMP" in
      "CERT_OK CACERT_OK")
        ok "A17 the overlay's controller cert and CA values decode byte-identically to this workspace's bundle and controller CA" ;;
      ""|UNREADABLE)
        nrefuse "A17 could not parse the overlay as YAML -- no conclusion drawn about whether it matches this workspace" ;;
      *CERT_MISMATCH*)
        nfail "A17 the overlay's lb-mgmt-controller-cert does NOT decode to controller.bundle.pem -- THE DEPLOYED MATERIAL IS NOT WHAT THIS WORKSPACE HOLDS. Every other assertion here graded the workspace copy; the charm gets this one ($OVLCMP)" ;;
      *CACERT_MISMATCH*)
        nfail "A17 the overlay's lb-mgmt-controller-cacert does NOT decode to this DC's controller CA -- the charm would trust the wrong CA ($OVLCMP)" ;;
      *EMPTY*)
        nfail "A17 an overlay value is EMPTY -- a truncated write leaves the key present (so A10's count still reads 5) with nothing in it ($OVLCMP)" ;;
      *)
        nfail "A17 an overlay value is not valid base64 ($OVLCMP)" ;;
    esac
  fi
fi

# ---------------------------------------------------------------- A11 cross-DC independence
# The point of the D-109 amendment (R7): no cross-DC amphora root-of-trust. Before F1 the
# generator's paths were shared, so a second DC's generation OVERWROTE the first and these
# would have been byte-identical. Only asserted when the other DC is actually present.
OTHER=""
case "$SITE" in vr1-dc0) OTHER=vr1-dc1 ;; vr1-dc1) OTHER=vr1-dc0 ;; esac
OW="$PKI_HOME/octavia-pki/$OTHER"
if [ -n "$OTHER" ] && [ -d "$OW" ]; then
  SAME=0; CMP=0
  for rel in issuing-ca/issuing-ca.key.enc controller-ca/controller-ca.key.enc controller/controller.cert.pem; do
    [ -f "$W/$rel" ] && [ -f "$OW/$rel" ] || continue
    CMP=$((CMP+1))
    cmp -s "$W/$rel" "$OW/$rel" && { SAME=$((SAME+1)); nfail "A11 $rel is IDENTICAL to $OTHER's -- the two DCs share amphora trust material, which R7 refused"; }
  done
  [ "$CMP" -gt 0 ] && [ "$SAME" -eq 0 ] && ok "A11 all $CMP compared artifacts differ from $OTHER -- per-DC independence holds"
  [ "$CMP" -eq 0 ] && ok "A11 no comparable artifact pairs present -- independence not evaluated"
else
  ok "A11 $OTHER not present on this host -- cross-DC independence not evaluated (not a pass for $OTHER)"
fi

# ---------------------------------------------------------------- verdict: three outcomes
echo
# PRECEDENCE: a CONFIRMED defect outranks an UNEVALUATED item. This ordering was originally
# the other way round, and T20 caught the consequence: once A12 arms, it always refuses on the
# unruled DC label, so a definite wrong-region SAN was being reported as "could not evaluate".
# That UNDERSTATES a known defect. Both counts are printed either way, so a refusal is never
# lost -- it just does not mask a failure.
if [ "$NFAIL" -ne 0 ]; then
  printf 'octavia-pki verify (%s): FAIL -- %d assertion(s) failed, %d passed' "$SITE" "$NFAIL" "$NOK"
  [ "$NREFUSE" -ne 0 ] && printf ' (and %s)' "1+ item(s) could NOT be evaluated -- see REFUSE above"
  echo; exit 1
fi
if [ "$NREFUSE" -ne 0 ]; then
  echo "octavia-pki verify ($SITE): REFUSE -- could not evaluate; this is NOT a pass"; exit 3
fi
echo "octavia-pki verify ($SITE): PASS -- $NOK assertion(s), 0 failed"
exit 0
