#!/usr/bin/env bash
# tests/preflight/run-tests.sh -- offline harness for preflight.sh (DOCFIX-073).
# Builds a fixture repo whose gate scripts are stubs with controlled exits, plus
# a fakebin juju for the channel assert. Asserts ordering, aggregation (worst of
# 0/1/2), and fail-closed behavior on a missing gate. Mutates nothing outside $TMP.
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0

mkfix() { # mkfix <name> <lint_rc> <bundlechk_rc> <preflight_present:0|1> [creds_rc|"omit"]
          #       [octavia: ok|wrongzone|fail|refuse|omit|offhost|nobinding]
  # P7 (2026-07-30) is HEADEND-ONLY and FAILS CLOSED, so every fixture must model a healthy
  # headend or the gate correctly stops the deploy in cases that are testing something else.
  # Adding it to mkfix rather than to individual cases is deliberate: when P7 landed, ten
  # existing cases went red for the RIGHT reason (no octavia-pki.sh => fail closed), and
  # patching them one by one would have been the moment someone "fixed" it by demoting the
  # gate to a warning.
  local d="$TMP/$1"; mkdir -p "$d/scripts" "$d/fakebin" "$d/creds-manifests"
  local oct="${6:-ok}"
  # The headend binding P7 reads. `nobinding` exercises the refusal-to-assume path.
  if [ "$oct" != "nobinding" ]; then
    printf '# role host\nheadend fixturehost\n' > "$d/creds-manifests/host-identity"
  else
    printf '# role host\n' > "$d/creds-manifests/host-identity"
  fi
  # A FAKE hostname on PATH is what makes the host-authority branch testable at all.
  # `offhost` makes this fixture claim to be a different machine, which is the live jumphost
  # case: the PKI is elsewhere, so P7 must say NOT EVALUATED and must NOT pass silently.
  if [ "$oct" = "offhost" ]; then
    printf '#!/usr/bin/env bash\necho someotherhost\n' > "$d/fakebin/hostname"
  else
    printf '#!/usr/bin/env bash\necho fixturehost\n' > "$d/fakebin/hostname"
  fi
  chmod +x "$d/fakebin/hostname"
  # The octavia-pki stub. `ok` emits the LITERAL positive line P7 requires; `wrongzone`
  # exits 0 WITHOUT it, which is the real F9 shape -- verify passes because the names are
  # inert, while the certificate carries another DC's or another region's zone.
  case "$oct" in
    omit) : ;;
    wrongzone) printf '#!/usr/bin/env bash\necho "  ok      A12 (recorded, not failed) the DNS SANs are not in this DC\x27s expected zone"\necho "octavia-pki verify: PASS -- 37 assertion(s), 0 failed"\nexit 0\n' > "$d/scripts/octavia-pki.sh" ;;
    fail)      printf '#!/usr/bin/env bash\necho "  FAIL    A9 SAN missing this DC.s provider VIP"\nexit 1\n' > "$d/scripts/octavia-pki.sh" ;;
    refuse)    printf '#!/usr/bin/env bash\necho "  REFUSE  openssl is not on PATH"\nexit 3\n' > "$d/scripts/octavia-pki.sh" ;;
    *)         printf '#!/usr/bin/env bash\necho "  ok      A12 DNS SANs are all in this DC\x27s expected zone \x27omega.dc0.vr1.cloud.neumatrix.local\x27"\necho "octavia-pki verify: PASS -- 37 assertion(s), 0 failed"\nexit 0\n' > "$d/scripts/octavia-pki.sh" ;;
  esac
  [ "$oct" = "omit" ] || chmod +x "$d/scripts/octavia-pki.sh"
  cp "$REPO/scripts/preflight.sh" "$REPO/scripts/channel_assert.py" "$d/scripts/"
  printf 'applications:\n  keystone: {charm: keystone, channel: 2024.1/stable}\n' > "$d/bundle.yaml"
  printf '#!/usr/bin/env bash\necho "  [stub] lint"\nexit %s\n' "$2" > "$d/scripts/repo-lint.sh"
  printf '#!/usr/bin/env python3\nimport sys;print("  [stub] bundlechk");sys.exit(%s)\n' "$3" > "$d/scripts/provider-bundle-check.py"
  if [ "$4" = "1" ]; then
    # The stub REPORTS the DC it was handed. F5 (2026-07-29): preflight used to set DC
    # without exporting it, so whether P4 saw the same DC as P2 depended on the caller's
    # invocation form. The stub is how a child's view becomes observable to the harness.
    printf '#!/usr/bin/env bash\necho "  [stub] live preflight CHILD_DC=${DC:-<unset>}"\nexit 0\n' \
      > "$d/scripts/pre-flight-checks.sh"
  fi
  # P5 credential matrix (D-137 ruling 1). "omit" leaves the gate script absent so the
  # fail-closed path can be exercised.
  if [ "${5:-0}" != "omit" ]; then
    printf '#!/usr/bin/env python3\nimport sys;print("  [stub] creds-matrix");sys.exit(%s)\n' \
      "${5:-0}" > "$d/scripts/creds-matrix.py"
  fi
  # fakebin juju: publishes 2024.1/stable in the channel map
  cat > "$d/fakebin/juju" <<'FB'
#!/usr/bin/env bash
if [ "$1" = "info" ]; then
  printf 'name: %s\nchannels:\n  2024.1/stable: 778 2024-05-01\n' "$2"; exit 0
fi
exit 1
FB
  chmod +x "$d/fakebin/juju"
  # P8 substrate-drift seam (2026-08-01). A fixture with no tofu and no outer-root state
  # legitimately WARNS ("could not look"), which would turn every all-green case rc=2 --
  # so the seam supplies BOTH. $7 selects the plan shape: zerodiff | pending | observed |
  # unparsed | error. Asserting P8 needs a fake `tofu` because the real one would touch
  # the live substrate, which a harness must never do.
  # BOTH markers, because P8 evaluates only when BOTH are present (see preflight.sh:284).
  # The state file is the OWNERSHIP marker; .terraform alone is just a provider cache and
  # is NOT evidence this host owns the outer root -- T39 pins that distinction.
  mkdir -p "$d/opentofu/.terraform"
  : > "$d/opentofu/terraform.tfstate"
  cat > "$d/fakebin/tofu" <<FB
#!/usr/bin/env bash
case "\${TOFU_FIXTURE:-zerodiff}" in
  zerodiff) echo "No changes. Your infrastructure matches the configuration."; exit 0 ;;
  pending)  echo "  # module.vvr1_dc0.libvirt_domain.vm will be updated in-place"; exit 2 ;;
  observed) echo "Note: Objects have changed outside of OpenTofu"; exit 2 ;;
  unparsed) echo "something tofu has never printed before"; exit 2 ;;
  error)    echo "Error: provider configuration is invalid" >&2; exit 1 ;;
esac
FB
  chmod +x "$d/fakebin/tofu"
  # P9 seam (2026-08-02). The real check RUNS ON THE RACK and preflight normally does
  # not; the stub lets all three branches be exercised offline. $EGRESS_FIXTURE:
  # offrack (rc2 -> WARN) | broken (rc1 -> FAIL) | ok (rc0, the DEFAULT) | absent.
  # Defaults to ok for the same reason P7 does: a fixture that models an unhealthy state
  # by default drags every case testing something else, and that is the moment someone
  # "fixes" it by demoting the gate.
  mkdir -p "$d/scripts"
  cat > "$d/scripts/dc-egress-check.sh" <<'EG'
#!/usr/bin/env bash
case "${EGRESS_FIXTURE:-ok}" in
  ok)      echo "  OK     A2 edge answers"; exit 0 ;;
  broken)  echo "  FAIL   A2 edge 10.12.4.1 does NOT answer"; exit 1 ;;
  *)       echo "REFUSE: this host does not carry 10.12.4.2/22" >&2; exit 2 ;;
esac
EG
  chmod +x "$d/scripts/dc-egress-check.sh"
  echo "$d"
}
run() { # run <want_rc> <regex> <label> <dir> [DC]
  # No [DC] means a genuinely BARE invocation: `env -u DC` so an inherited DC in the
  # harness's own environment cannot make the default path look like it works.
  local out rc
  if [ -n "${5:-}" ]; then
    out="$(PATH="$4/fakebin:$PATH" DC="$5" bash "$4/scripts/preflight.sh" "$4" 2>&1)"; rc=$?
  else
    out="$(PATH="$4/fakebin:$PATH" env -u DC bash "$4/scripts/preflight.sh" "$4" 2>&1)"; rc=$?
  fi
  if [[ "$rc" == "$1" ]] && grep -qE "$2" <<<"$out"; then
    echo "  PASS  $3"; PASS=$((PASS+1))
  else
    echo "  FAIL  $3 (rc=$rc want=$1)"; echo "$out" | tail -6 | sed 's/^/        /'; FAIL=$((FAIL+1))
  fi
}

D=$(mkfix all_pass 0 0 1);   run 0 'PREFLIGHT: PASS' "T1 all gates pass -> PASS" "$D"
D=$(mkfix lint_fail 1 0 1);  run 1 'PREFLIGHT: FAIL' "T2 lint FAIL -> overall FAIL" "$D"
D=$(mkfix bundle_fail 0 1 1); run 1 'PREFLIGHT: FAIL' "T3 bundle FAIL -> overall FAIL" "$D"
D=$(mkfix warn_only 2 0 1);  run 2 'PREFLIGHT: WARN' "T4 lint WARN -> overall WARN" "$D"
D=$(mkfix missing_gate 0 0 0); run 1 'pre-flight-checks.sh missing' "T5 missing gate FAILS CLOSED" "$D"
# T6: channel pinned to a track fakebin does not publish -> FAIL
D=$(mkfix badpin 0 0 1); printf 'applications:\n  vault: {charm: vault, channel: 9.9/stable}\n' > "$D/bundle.yaml"
run 1 'does not publish' "T6 unpublished channel pin FAILS" "$D"
# T7: charmhub unreachable -> WARN (exit 2), not FAIL.
# HERMETIC: simulate failure with a FAILING fake, never by deleting the fake --
# deletion un-shadows the REAL juju on a real host (the jumphost gauntlet run
# fell through to live charmhub and returned 0). Fakes must always shadow.
D=$(mkfix offline 0 0 1)
cat > "$D/fakebin/juju" <<'FB'
#!/usr/bin/env bash
echo "ERROR cannot connect to charmhub.io: connection timed out" >&2; exit 1
FB
chmod +x "$D/fakebin/juju"
run 2 'verify .* manually' "T7 charmhub unreachable -> WARN" "$D"

# T8 -- D-137 ruling 1: the credential gate is BLOCKING. A red register must stop a
# deploy, not warn about it -- advisory was the posture that demonstrably failed.
D=$(mkfix creds_fail 0 0 1 1); run 1 'PREFLIGHT: FAIL' "T8 credential matrix FAIL -> overall FAIL" "$D"
# T9 -- the credential gate must FAIL CLOSED if its checker is missing, exactly as T5
# requires of pre-flight-checks.sh. A gate that vanishes must never read as a pass.
D=$(mkfix creds_gone 0 0 1 omit); run 1 'PREFLIGHT: FAIL' "T9 credential gate missing -> FAILS CLOSED" "$D"
# T10 -- ordering: the credential gate runs with the REAL gates, before the stage-2
# reminders block, so its verdict participates in the deploy decision.
D=$(mkfix creds_order 0 0 1 0)
out="$(PATH="$D/fakebin:$PATH" bash "$D/scripts/preflight.sh" "$D" 2>&1)"
if [[ "$(grep -n 'P5: credential matrix' <<<"$out" | cut -d: -f1)" -lt \
      "$(grep -n 'P6: stage-2 reminders' <<<"$out" | cut -d: -f1)" ]]; then
  echo "  PASS  T10 credential gate runs BEFORE the reminders block"; PASS=$((PASS+1))
else
  echo "  FAIL  T10 credential gate ordering"; FAIL=$((FAIL+1))
fi

# ---- T11-T16 -- R15(3): PREFLIGHT MUST NOT FAIL OPEN (ruled 2026-07-27) ----
# MEASURED DEFECT: note() tested only rc 1 and 2, so a sub-gate exiting 127/126/130
# or any rc>=3 left WORST untouched and preflight printed
#   "PREFLIGHT: PASS -- clear to add-model / deploy"
# Reproduced for all four before the fix. This is the gate that AUTHORISES the deploy.
run 1 'PREFLIGHT: FAIL' "T11 R15(3) sub-gate exit 127 (command not found) -> FAIL, was PASS" "$(mkfix rc127 127 0 1 0)"
run 1 'PREFLIGHT: FAIL' "T12 R15(3) sub-gate exit 126 (not executable) -> FAIL, was PASS" "$(mkfix rc126 0 126 1 0)"
run 1 'PREFLIGHT: FAIL' "T13 R15(3) sub-gate exit 3 (undefined) -> FAIL, was PASS" "$(mkfix rc3 0 0 1 3)"

# rc=2 is PER-GATE and was measured per checker rather than blanket-remapped.
# P2/P5 rc2 means "could not evaluate" -> FAIL. A gate that could not look must never
# read as a gate that found nothing.
run 1 'COULD NOT EVALUATE' "T14 R15(3) P2 rc=2 (could-not-evaluate) -> FAIL" "$(mkfix p2two 0 2 1 0)"
run 1 'COULD NOT EVALUATE' "T15 R15(3) P5 rc=2 (could-not-evaluate) -> FAIL" "$(mkfix p5two 0 0 1 2)"

# P1 rc2 is "warnings only" by its own documented contract. It must STAY a warning --
# a blanket 2->FAIL would have turned the standing L1 legacy-ASCII warn into a deploy
# blocker. This case is the regression lock on NOT over-correcting.
run 2 'PREFLIGHT: WARN' "T16 R15(3) P1 rc=2 (warnings only) stays WARN, not FAIL" "$(mkfix p1two 2 0 1 0)"

# ---- T17-T24 -- F5: ONE DC selection, EXPORTED, VALIDATED and PRINTED --------------
# MEASURED DEFECT (docs/audit/stage5-findings-20260729-successor.md F5): preflight.sh:99
# set DC="${DC:-vr1-dc0}" and never exported it, so `DC=vr1-dc1 bash scripts/preflight.sh`
# put it in the environment (children saw it) while a BARE invocation did not (children
# saw nothing and silently used their own default). Two call shapes, two behaviours.
D=$(mkfix dc_export 0 0 1 0)
run 0 'CHILD_DC=vr1-dc0' "T17 F5 BARE invocation EXPORTS the default DC to the sub-gates" "$D"
run 0 'CHILD_DC=vr1-dc1' "T18 F5 DC=vr1-dc1 reaches the sub-gates" "$D" vr1-dc1
# The verdict must say which DC it describes -- one combined verdict line over two DCs
# was the reader-facing half of F5.
run 0 'PREFLIGHT: PASS \(DC=vr1-dc1\)' "T19 F5 the verdict line names the DC" "$D" vr1-dc1
run 0 'PREFLIGHT TARGET: DC=vr1-dc1' "T20 F5 the opening header names the DC" "$D" vr1-dc1
# The `PREFLIGHT: <verdict>` token stays CONTIGUOUS -- T1-T16 above grep it verbatim and
# so do the runbooks. This pins that the DC was APPENDED, not interpolated into it.
run 2 'PREFLIGHT: WARN \(DC=vr1-dc0\)' "T21 F5 the verdict token is not split by the DC" "$(mkfix dc_warn 2 0 1 0)"
# REFUSE an unrecognised DC rather than gating an unknown target.
run 1 'not a recognised datacenter selector' "T22 F5 an unknown DC token REFUSES" "$D" no-such-dc
run 1 'RETIRED \(D-119\)' "T23 F5 a bare 'dc1' is rejected as RETIRED" "$D" dc1
# ... and refusing must happen BEFORE any gate runs: a rejected DC that still executed
# P1..P5 would have measured something and called it nothing.
OUT_BAD="$(PATH="$D/fakebin:$PATH" DC=no-such-dc bash "$D/scripts/preflight.sh" "$D" 2>&1)"
if grep -q 'P1: repo lint' <<<"$OUT_BAD"; then
  echo "  FAIL  T24 an unknown DC must refuse BEFORE any gate runs"; FAIL=$((FAIL+1))
else
  echo "  PASS  T24 an unknown DC refuses BEFORE any gate runs"; PASS=$((PASS+1))
fi

# ---- T25 -- F1: P2 assembles the PER-DC octavia-pki overlay -------------------------
# The loop hardcoded `overlays/octavia-pki.yaml`, a single unscoped path for what the
# D-109 R7 amendment rules must be per-DC CA material. Each overlay is added only when
# present, so the assertion is on the NAME P2 announces, not on a secret existing.
D=$(mkfix dc_ovl 0 0 1 0); mkdir -p "$D/overlays"
: > "$D/overlays/vr1-dc1-octavia-pki.yaml"
run 0 'overlay overlays/vr1-dc1-octavia-pki.yaml' "T25 F1 P2 picks up the PER-DC octavia-pki overlay" "$D" vr1-dc1
if PATH="$D/fakebin:$PATH" DC=vr1-dc1 bash "$D/scripts/preflight.sh" "$D" 2>&1 \
   | grep -qE 'overlay overlays/octavia-pki\.yaml'; then
  echo "  FAIL  T26 the old unscoped octavia-pki name must be gone"; FAIL=$((FAIL+1))
else
  echo "  PASS  T26 the old unscoped octavia-pki overlay name is gone"; PASS=$((PASS+1))
fi

# ===================================================================================
# T27..T33 -- P7, the Octavia amphora PKI gate (added 2026-07-30)
#
# WHY P7 EXISTS: octavia-pki.sh was invoked by NOTHING executable. P4's CHECK 0 asserted
# only that the overlay FILE EXISTS, and the octavia-pki harness runs against throwaway
# fixtures -- so "gauntlet ALL GREEN" said nothing about the live PKI, and the only real
# check ran when a human remembered to type it. That is how F9 survived: both DCs carried
# another region's names for weeks with every gate green.

# T27 -- THE CASE THIS GATE WAS BUILT FOR, and the one a naive implementation fails.
# `verify` exits 0 on a WRONG-ZONE certificate while os-public-hostname is unset, because
# A12/A13 RECORD rather than fail when the names are inert. So a P7 that only checked the
# exit code would have passed the exact defect it exists to catch. It must additionally
# require the literal positive line.
D=$(mkfix p7_wrongzone 0 0 1 0 wrongzone)
run 1 'did NOT report that the DNS SANs' "T27 verify exits 0 on a WRONG-ZONE cert -> P7 still FAILS (F9)" "$D"

# T28 -- HOST AUTHORITY. The PKI lives on the headend (D-109 (b)); anywhere else the gate
# must say NOT EVALUATED. It must NOT silently pass (that is P5's own F6 defect: probing
# the wrong host's filesystem and reporting it as fact) and must NOT hard-FAIL either,
# since running preflight from the jumphost for other purposes is legitimate.
D=$(mkfix p7_offhost 0 0 1 0 offhost)
run 2 'NOT EVALUATED on .someotherhost.' "T28 off-headend -> NOT EVALUATED (WARN), never a silent pass" "$D"

# T29 -- FAIL CLOSED, same rule T5/T9 encode for P4/P5. A gate that VANISHES must never
# read as a pass; deleting the checker must not be a way to go green.
D=$(mkfix p7_gone 0 0 1 0 omit)
run 1 'octavia-pki.sh missing' "T29 P7 checker missing -> FAILS CLOSED" "$D"

# T30 -- a real verify failure blocks the deploy.
D=$(mkfix p7_fail 0 0 1 0 fail)
run 1 'PREFLIGHT: FAIL' "T30 octavia-pki verify FAIL -> overall FAIL" "$D"

# T31 -- REFUSE (exit 3) ON THE HEADEND is a FAIL, and must be reported as an unevaluated
# PKI rather than as note()'s generic "UNEXPECTED exit code". rc 3 is octavia-pki's
# DOCUMENTED refusal code; letting it reach note()'s `*)` arm would describe a known,
# meaningful state as an unknown one.
D=$(mkfix p7_refuse 0 0 1 0 refuse)
run 1 'REFUSED .exit 3. ON THE HEADEND' "T31 REFUSE on the headend -> FAIL, named correctly" "$D"

# T32 -- no headend binding: refuse to ASSUME this host holds the PKI. Same reasoning as
# octavia-pki.sh's own binding check -- both read the SAME register file so they cannot drift.
D=$(mkfix p7_nobinding 0 0 1 0 nobinding)
run 1 "no .headend. binding" "T32 absent headend binding -> FAIL, never assumed" "$D"

# T33 -- vr0-dc0 has no Octavia amphora PKI (it is a VR1 per-DC artifact), so P7 SKIPS
# rather than inventing a failure. A gate that fires on a target it does not apply to is
# how gates get bypassed.
D=$(mkfix p7_vr0 0 0 1 0 ok)
run 0 'not applicable to vr0-dc0' "T33 vr0-dc0 -> P7 skips cleanly, run still PASSES" "$D" vr0-dc0

# --- P8 substrate drift (2026-08-01). The gate exists because preflight had ZERO tofu
# checks and a real drift had been sitting unseen. `-detailed-exitcode` returns 2 for BOTH
# a pending action and a harmless observation, so these cases pin the DISTINCTION -- a gate
# that is permanently red on benign drift gets ignored, which is the failure being avoided.
D=$(mkfix p8_pending 0 0 1 0 ok)
TOFU_FIXTURE=pending run 1 'pending substrate action' "T34 P8 pending change -> FAIL" "$D"

D=$(mkfix p8_observed 0 0 1 0 ok)
TOFU_FIXTURE=observed run 2 'refresh-only drift observed' "T35 P8 observed-only drift -> WARN, not FAIL" "$D"

# T36: an unrecognised plan shape must REFUSE, never default to a pass. This is the branch
# that would silently green if someone later "simplified" the content assertions away.
D=$(mkfix p8_unparsed 0 0 1 0 ok)
TOFU_FIXTURE=unparsed run 1 'no action or drift line parsed' "T36 P8 unparsed plan -> FAIL, never assumed clean" "$D"

D=$(mkfix p8_error 0 0 1 0 ok)
TOFU_FIXTURE=error run 1 'could not be evaluated' "T37 P8 tofu error exit -> FAIL" "$D"

# T38: no outer-root state -> WARN ("could not look" is never "nothing there").
# DELIBERATELY exercised by removing the fixture's state, NOT by removing the fake tofu from
# fakebin: the real tofu is on the system PATH, so a "closed" PATH here would fall through to
# it and touch the LIVE substrate. That is the ninth-instance trap recorded 2026-08-01 --
# when a negative depends on an absence, prove the absence rather than arranging it.
D=$(mkfix p8_nostate 0 0 1 0 ok)
rm -rf "$D/opentofu"
run 2 'no outer-root state here' "T38 P8 no substrate state -> WARN, never silent" "$D"

# T39: THE SHAPE THAT ACTUALLY BIT, and that T38 could not catch (added 2026-08-01 after a
# live false FAIL). voffice1 has NO terraform.tfstate but DOES have opentofu/.terraform,
# because D-128 has it run the INNER tofu roots and `tofu init` leaves a provider cache
# behind. T38 removes the WHOLE directory, so both markers vanish together and it can never
# distinguish them. Under the old `&&` guard this fell through to a real `tofu plan`, which
# died on a gitignored tfvar and hard-FAILED preflight on the one host the Stage-5 runbook
# designates for it. The state file is the OWNERSHIP marker; the cache is not.
D=$(mkfix p8_cache_no_state 0 0 1 0 ok)
rm -f "$D/opentofu/terraform.tfstate"          # cache REMAINS -- that is the whole point
[[ -d "$D/opentofu/.terraform" ]] || { echo "FIXTURE BROKEN: T39 needs .terraform present"; exit 1; }
run 2 'no outer-root state here' "T39 P8 provider cache but NO state -> WARN, not FAIL" "$D"

# T40..T43 -- P9 DC egress (added 2026-08-02). The gate exists because a 19-hour dc0
# egress outage was invisible to every gate in the repo; these pin that P9 cannot go
# quietly green again.
# T40: preflight run OFF the rack must WARN, not FAIL -- "not the rack" is legitimate,
# the same reasoning as P8's substrate guard. A FAIL here would make preflight
# permanently red on voffice1 and get ignored, which is the failure being prevented.
D=$(mkfix p9_offrack 0 0 1 0 ok)
EGRESS_FIXTURE=offrack run 2 'not the vr1-dc0 rack' "T40 P9 off-rack -> WARN, names the command" "$D"

# T41: run ON a rack with egress genuinely broken -> FAIL. This is the 2026-08-01 state.
D=$(mkfix p9_broken 0 0 1 0 ok)
EGRESS_FIXTURE=broken run 1 'DC egress is BROKEN' "T41 P9 broken egress -> FAIL" "$D"

# T42: healthy egress -> ok, and the overall run is not dragged down by P9.
D=$(mkfix p9_ok 0 0 1 0 ok)
EGRESS_FIXTURE=ok run 0 'DC egress verified' "T42 P9 healthy egress -> ok, overall PASS" "$D"

# T43: the checker MISSING is "could not look", never a pass. A deleted gate must not
# read as a green one -- the standing rule that a gate which vanishes must not silently
# succeed (same reasoning as P5's comment at preflight.sh:183).
D=$(mkfix p9_absent 0 0 1 0 ok); rm -f "$D/scripts/dc-egress-check.sh"
EGRESS_FIXTURE=ok run 2 'dc-egress-check.sh missing' "T43 P9 absent checker -> WARN, never a pass" "$D"

echo; echo "RESULT: PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1
