Phase 7a: read-only verifier for the 06:00 scheduled run (AUD-115)
make verify-n8n / deploy/verify-n8n-scheduled-run.sh checks the workflow is active, reads the newest scheduled (mode=trigger) execution from the n8n database, and prints the notify rows. Exit 1 until a scheduled run exists and is green, so it distinguishes 'not yet' from 'failed'. Never runs the workflow and never sends. Document the first-run time and the re-armed monitor.
This commit is contained in:
8
Makefile
8
Makefile
@@ -19,7 +19,7 @@ GO_RUN = timeout 600 docker run --rm --network host --user "$$(id -u):$$(id -g)"
|
||||
-e HOME=/src/.gocache -e GOMODCACHE=/src/.gocache/mod -e GOCACHE=/src/.gocache/build \
|
||||
-v "$$PWD":/src -w /src $(GO_IMAGE)
|
||||
|
||||
.PHONY: help test vet build up down restart logs ps deps fmt hash-password gen-secret verify-phase1 verify-phase8 parse scan calendar notify morning-brief n8n-key n8n-import
|
||||
.PHONY: help test vet build up down restart logs ps deps fmt hash-password gen-secret verify-phase1 verify-phase8 parse scan calendar notify morning-brief n8n-key n8n-import verify-n8n
|
||||
|
||||
help:
|
||||
@echo "make test run go test ./... in golang:1.25-alpine"
|
||||
@@ -40,6 +40,7 @@ help:
|
||||
@echo "make morning-brief scan both children, then send the briefs (the n8n job)"
|
||||
@echo "make n8n-key print the restricted SSH trigger key plan (Phase 7a)"
|
||||
@echo "make n8n-import print the n8n import/publish plan (Phase 7a; APPLY=1 applies)"
|
||||
@echo "make verify-n8n prove the 06:00 scheduled run is green (Phase 7a; read-only)"
|
||||
@echo "make verify-phase1 prove /health, the login and the Caddy route"
|
||||
@echo "make verify-phase8 prove the Hub API: /capabilities, /ask, /notify (--send delivers)"
|
||||
|
||||
@@ -112,6 +113,11 @@ n8n-key:
|
||||
n8n-import:
|
||||
deploy/import-n8n-morning-brief.sh $(if $(APPLY),--apply,)
|
||||
|
||||
# Phase 7a: prove the 06:00 scheduled run itself. Read-only; exit 1 until the
|
||||
# first automatic run has happened and is green.
|
||||
verify-n8n:
|
||||
deploy/verify-n8n-scheduled-run.sh
|
||||
|
||||
# Phase 1 definition of done: /health 200, login page + session login, and the
|
||||
# Caddy route on .35.
|
||||
verify-phase1:
|
||||
|
||||
@@ -109,6 +109,9 @@ the API directly on the internal address.
|
||||
node by default, so a command node cannot activate.) It needs no
|
||||
`SCHOOL_NOTIFY_TOKEN` in n8n — the host script reads the project env file. See
|
||||
`docs/phase-7a-n8n-schedule.md` for the design and the failure drill.
|
||||
After the first 06:00, verify the scheduled run itself with the read-only
|
||||
`make verify-n8n` (`deploy/verify-n8n-scheduled-run.sh`); it exits 1 until a
|
||||
scheduled (`mode=trigger`) run exists and is green.
|
||||
|
||||
Notes:
|
||||
|
||||
|
||||
99
deploy/verify-n8n-scheduled-run.sh
Executable file
99
deploy/verify-n8n-scheduled-run.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 7a (AUD-115): prove the 06:00 n8n schedule fired and the briefs were sent.
|
||||
#
|
||||
# Read-only. It never runs the workflow and never sends anything, so it is safe to
|
||||
# call from a monitor or by hand at any time.
|
||||
#
|
||||
# Checks:
|
||||
# 1. the workflow is active in n8n,
|
||||
# 2. the newest scheduled run (mode "trigger") of schoolMorningBrf finished
|
||||
# "success" — a hand run (mode "cli") does not count,
|
||||
# 3. prints the notify rows so the morning's 8 deliveries can be read.
|
||||
#
|
||||
# Exit 0 only when 1 and 2 hold. Exit 1 with a clear "no scheduled run yet" when the
|
||||
# 06:00 trigger has not fired, so the caller can tell "not yet" from "failed".
|
||||
#
|
||||
# Usage:
|
||||
# deploy/verify-n8n-scheduled-run.sh
|
||||
#
|
||||
# Environment overrides:
|
||||
# CONTAINER n8n container name (default n8n)
|
||||
# APP_CONTAINER app container name (default compass-teams-nhs)
|
||||
# N8N_DATA_DIR n8n data dir on the host (default /home/sam/Docker/Containers/n8n_data)
|
||||
# WF_ID workflow id (default schoolMorningBrf)
|
||||
set -euo pipefail
|
||||
|
||||
CONTAINER="${CONTAINER:-n8n}"
|
||||
APP_CONTAINER="${APP_CONTAINER:-compass-teams-nhs}"
|
||||
N8N_DATA_DIR="${N8N_DATA_DIR:-/home/sam/Docker/Containers/n8n_data}"
|
||||
WF_ID="${WF_ID:-schoolMorningBrf}"
|
||||
DB="$N8N_DATA_DIR/database.sqlite"
|
||||
|
||||
fail=0
|
||||
|
||||
echo "== 1. the workflow is active in n8n =="
|
||||
if docker exec "$CONTAINER" n8n list:workflow --active=true 2>/dev/null | grep -q "$WF_ID"; then
|
||||
echo "active: $WF_ID"
|
||||
else
|
||||
echo "NOT active: $WF_ID — the 06:00 schedule will not fire" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "== 2. the newest scheduled (mode=trigger) execution =="
|
||||
if [[ ! -f "$DB" ]]; then
|
||||
echo "n8n database not found: $DB (set N8N_DATA_DIR)" >&2
|
||||
fail=1
|
||||
elif python3 - "$DB" "$WF_ID" <<'PY'
|
||||
import datetime, sqlite3, sys
|
||||
|
||||
db, wf = sys.argv[1], sys.argv[2]
|
||||
con = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=8)
|
||||
con.row_factory = sqlite3.Row
|
||||
rows = list(con.execute(
|
||||
"select id, mode, status, startedAt, stoppedAt from execution_entity "
|
||||
"where workflowId=? and mode='trigger' order by id desc limit 3", (wf,)))
|
||||
if not rows:
|
||||
print("no scheduled (mode=trigger) execution yet for", wf)
|
||||
sys.exit(1)
|
||||
|
||||
aedt = datetime.timezone(datetime.timedelta(hours=11))
|
||||
|
||||
def local(ts):
|
||||
for fmt in ("%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S"):
|
||||
try:
|
||||
dt = datetime.datetime.strptime(ts, fmt).replace(tzinfo=datetime.timezone.utc)
|
||||
return dt.astimezone(aedt).strftime("%Y-%m-%d %H:%M %Z")
|
||||
except ValueError:
|
||||
continue
|
||||
return ts
|
||||
|
||||
for r in rows:
|
||||
print(f" id={r['id']} mode={r['mode']} status={r['status']} started={local(r['startedAt'])}")
|
||||
|
||||
newest = rows[0]
|
||||
sys.exit(0 if newest["status"] == "success" else 1)
|
||||
PY
|
||||
then
|
||||
echo "the newest scheduled run finished success"
|
||||
else
|
||||
echo "the newest scheduled run is not green (or no scheduled run has happened yet)" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "== 3. notify rows (the morning's 8 deliveries) =="
|
||||
if docker exec "$APP_CONTAINER" /app/school notify status 2>/dev/null | grep -v '^gateway=' | tail -20; then
|
||||
:
|
||||
else
|
||||
echo "could not read notify status from $APP_CONTAINER" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
echo
|
||||
if [[ "$fail" -eq 0 ]]; then
|
||||
echo "OK: the 06:00 schedule is active, its newest scheduled run is green, rows above"
|
||||
else
|
||||
echo "NOT YET / FAILED: see the messages above" >&2
|
||||
fi
|
||||
exit "$fail"
|
||||
@@ -147,6 +147,25 @@ already in use"*, because the running n8n instance owns 5679. It changes only th
|
||||
|
||||
This sends the real briefs, so run it only when a real delivery is wanted.
|
||||
|
||||
## Verify the scheduled run (read-only)
|
||||
|
||||
```bash
|
||||
make verify-n8n # or: deploy/verify-n8n-scheduled-run.sh
|
||||
```
|
||||
|
||||
`deploy/verify-n8n-scheduled-run.sh` never runs the workflow and never sends
|
||||
anything. It checks that the workflow is active, reads the newest **scheduled**
|
||||
(`mode=trigger`) execution from the n8n database, and prints the notify rows. A hand
|
||||
run (`mode=cli`) does **not** satisfy it. The exit code distinguishes the states:
|
||||
|
||||
- `0` — a scheduled run exists and its newest one finished `success`.
|
||||
- `1` — the workflow is inactive, or the schedule has not fired yet, or the newest
|
||||
scheduled run failed.
|
||||
|
||||
It reads the n8n database directly (`N8N_DATA_DIR`, default
|
||||
`/home/sam/Docker/Containers/n8n_data`) because n8n 2.x stores scheduled executions
|
||||
with `mode=trigger`, which `n8n list:workflow` does not report.
|
||||
|
||||
## Applied — 2026-10-16
|
||||
|
||||
Sam accepted plan revision 2 (`257b4b0a`), and the change was applied to the shared n8n
|
||||
@@ -166,8 +185,15 @@ on `.13`. Evidence from that heartbeat:
|
||||
all `ok` with `sent_at` set.
|
||||
|
||||
The remaining check is the first automatic run: the Schedule Trigger fires at
|
||||
**06:00 Australia/Melbourne on 2026-10-17**. An issue monitor on AUD-115 wakes the
|
||||
assignee after that to record the rows.
|
||||
**06:00 Australia/Melbourne on 2026-10-17** (`2026-10-16T19:00Z`). The n8n container
|
||||
runs `TZ=Australia/Melbourne` and the workflow sets
|
||||
`settings.timezone=Australia/Melbourne`; the pre-existing `AI Calendar - Family
|
||||
Notifier` schedule stores its local 05:00/16:00/20:00 firings as 18:00/05:00/09:00
|
||||
UTC, which confirms the workflow timezone is honoured.
|
||||
|
||||
The first issue monitor fired too early and was cleared, so it was re-armed for
|
||||
**06:30 Australia/Melbourne on 2026-10-17** (`2026-10-16T19:30:00Z`) to record the
|
||||
automatic run's rows with `make verify-n8n`.
|
||||
|
||||
## Not touched
|
||||
|
||||
|
||||
Reference in New Issue
Block a user