From dd13f632c81019dfd3cc1bc5e8f6899e45cc0f74 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 2 Aug 2026 16:23:56 +0200 Subject: [PATCH] ci: a failed run sends its own alarm (R-168, probe P5) P5 measured: a failed run produces NO mail, NO notification row and NO log line from Gitea. A red tick in a web UI nobody watches is exactly the defect R-29 filed, rebuilt one layer up, so the run alarms itself on the project's existing transactional path (Resend, the same one the hub uses) and prints the provider's accepted id, making 'it was sent' an observable rather than an assumption. The key is a user-level Gitea Actions secret created out-of-band; it is in no committed file. The recipient is the operator address the hub already uses and is not a secret. This push is deliberately made while main is still carrying the Scenario B breakage, so the resulting run fails and demonstrates the alarm end to end. --- .gitea/workflows/gates.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.gitea/workflows/gates.yml b/.gitea/workflows/gates.yml index 1ef297a..1ded8bf 100644 --- a/.gitea/workflows/gates.yml +++ b/.gitea/workflows/gates.yml @@ -37,3 +37,41 @@ jobs: # already reliably run by a person or none of CI's business. The exit code IS the result: # no `|| true`, no pipe that could swallow it. run: python3 scripts/repo_gates.py --fast + + - name: Alarm on failure + # THE POINT OF THE WHOLE THING. Probe P5 measured that a failed run produces NO mail, NO + # notification row and NO log line from Gitea itself — a red tick in a web UI nobody watches + # is exactly the shape R-29 filed against. So the run sends its own alarm, on the project's + # existing transactional path (Resend, the same one the hub uses), and it prints the + # provider's accepted id so "it was sent" is an observable rather than an assumption. + if: failure() + env: + RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} + run: | + python3 - > payload.json <<'PY' + import json, os + repo = os.environ.get("GITHUB_REPOSITORY", "?") + sha = os.environ.get("GITHUB_SHA", "?") + run = os.environ.get("GITHUB_RUN_NUMBER", "?") + srv = os.environ.get("GITHUB_SERVER_URL", "https://gitea.dooplex.hu") + print(json.dumps({ + "from": "Felhom CI ", + "to": ["admin@felhom.eu"], + "subject": "[felhom CI] gates FAILED in %s" % repo, + "text": ( + "The gate entry point exited non-zero.\n\n" + "Repository : %s\n" + "Commit : %s\n" + "Run : %s/%s/actions/runs/%s\n\n" + "The failing gate names itself in the run log.\n\n" + "If the local pre-push hook was GREEN for this commit, then CI and the hook\n" + "disagree - that is a finding about the gates themselves, not about CI, and it\n" + "outranks whatever the push was for.\n" + ) % (repo, sha, srv, repo, run), + })) + PY + curl -sS --fail-with-body -X POST https://api.resend.com/emails \ + -H "Authorization: Bearer $RESEND_API_KEY" \ + -H "Content-Type: application/json" \ + --data @payload.json > resend-response.json + python3 -c "import json;print('RESEND-ACCEPTED id=%s' % json.load(open('resend-response.json'))['id'])"