From b5b5ab680d707fba05de5a15adf0ae77713e16d0 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:03:08 +0800 Subject: [PATCH] ops: scheduled live-domain monitor for the deployed product (PT-17) Nothing exercised the deployed product after release, so a broken domain, dead health probe, or lost route could only be discovered by a user. Probe https://psychiatry.tools every six hours with plain unauthenticated GETs: the app shell renders, /api/health reports ok in live (non-demo) mode, the key public routes answer 200 (following the /applications -> /tools canonicalisation either side of that landing), and the www alias resolves. No secrets, no third-party actions, and a repository variable (LIVE_DOMAIN_URL) can repoint it. A red run is the alert; deeper answer-SLO probing stays in the secret-gated ops digest. Co-Authored-By: Claude Fable 5 --- .github/workflows/live-domain-monitor.yml | 85 +++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .github/workflows/live-domain-monitor.yml diff --git a/.github/workflows/live-domain-monitor.yml b/.github/workflows/live-domain-monitor.yml new file mode 100644 index 000000000..bb530ed48 --- /dev/null +++ b/.github/workflows/live-domain-monitor.yml @@ -0,0 +1,85 @@ +name: Live domain monitor + +# PT-17 (2026-07-13 external audit): nothing exercised the deployed product +# after release, so a broken domain, dead health probe, or lost route could +# only be discovered by a user. This monitor probes the public domain on a +# schedule with plain unauthenticated GETs — no secrets, no providers beyond +# what any anonymous visitor triggers, and no third-party actions. +# +# A red run is the alert. Deeper answer-SLO probing stays in the ops-digest +# workflow (dispatch-only, secret-gated); this one must stay cheap enough to +# never be worth disabling. + +on: + schedule: + - cron: "23 */6 * * *" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: live-domain-monitor + cancel-in-progress: false + +jobs: + probe: + name: Probe public domain + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + # Repository variable overrides the default (e.g. a staging cutover). + LIVE_DOMAIN_URL: ${{ vars.LIVE_DOMAIN_URL || 'https://psychiatry.tools' }} + steps: + - name: Root renders the app shell + run: | + set -euo pipefail + body="$(curl -sSL --fail --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "$LIVE_DOMAIN_URL/")" + echo "$body" | grep -q "Clinical Guide" || { + echo "::error::Root responded but does not render the Clinical Guide shell" + exit 1 + } + + - name: Health endpoint reports ok in live mode + run: | + set -euo pipefail + health="$(curl -sSL --fail --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "$LIVE_DOMAIN_URL/api/health")" + echo "$health" + echo "$health" | grep -q '"status":"ok"' || { + echo "::error::/api/health did not report status ok" + exit 1 + } + echo "$health" | grep -q '"demoMode":false' || { + echo "::error::/api/health reports demo mode — live configuration lost" + exit 1 + } + + - name: Key routes stay reachable + run: | + set -euo pipefail + # -L follows the /applications -> /tools canonicalisation either side + # of that redirect landing, so this list survives route renames. + for route in /privacy /services /forms /differentials /applications; do + code="$(curl -sSL -o /dev/null -w '%{http_code}' --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "$LIVE_DOMAIN_URL$route")" + echo "$route -> $code" + if [ "$code" != "200" ]; then + echo "::error::$route returned $code" + exit 1 + fi + done + + - name: WWW and TLS answer + run: | + set -euo pipefail + # Only meaningful for the canonical production domain; an overridden + # LIVE_DOMAIN_URL (e.g. staging) has no www alias to assert. + if [ "$LIVE_DOMAIN_URL" != "https://psychiatry.tools" ]; then + echo "LIVE_DOMAIN_URL overridden; skipping www alias check" + exit 0 + fi + code="$(curl -sSL -o /dev/null -w '%{http_code}' --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "https://www.psychiatry.tools/")" + echo "www -> $code" + if [ "$code" != "200" ]; then + echo "::error::www.psychiatry.tools returned $code" + exit 1 + fi