Raise alerts: failed or paused prints, and AI detections

Nothing subscribed to channel:print_alerts and nothing watched a printer
fail, so an operator away from the screen found out when they came back to
a ruined plate. Alerts are now rows in printer_alerts, listed at
GET /alerts (open=true for what still needs a person) and cleared with
POST /alerts/:id/acknowledge, which is operator-only and keeps the first
acknowledger on the record.

Two sources. Telemetry: a change into error is a failed print if the printer
was printing or paused, otherwise a printer error; a print the printer
paused itself (how an AMS fault looks) is announced too. Transitions, not
levels - a printer sitting in error is one alert, not one per frame - and a
connection's first sight of a printer raises nothing, so a gateway that
reconnects does not re-announce known faults. AI: the worker's Valkey
publications are stored when confidence is at least ALERT_MIN_CONFIDENCE
(default 0.6); the worker deliberately does not threshold, so the policy
lives here. A bad message costs one message, not the listener.

e2e-authz.sh gains the scoping/ack cases; new e2e-alerts.sh drives a stand-in
gateway over the real /ws/edge/v1 and the real Valkey channel (checked to
fail when the derivation is broken).
This commit is contained in:
2026-10-29 14:56:24 +00:00
parent 6a2457315e
commit 847f7cb686
17 changed files with 1526 additions and 2 deletions
+22
View File
@@ -305,6 +305,28 @@ CODE="$(req POST /auth/edge-token - "{\"farmId\":\"$FARM_A\",\"edgeProxyToken\":
expect 401 POST /auth/edge-token - "{\"farmId\":\"$FARM_A\",\"edgeProxyToken\":\"not-the-token\"}" \
-- "a wrong proxy token is refused"
# --- 7b. alerts ---------------------------------------------------------
echo "==> alerts are scoped, and acknowledging is an operator's decision"
ALERT_A="$(new_uuid)"
psql_q "INSERT INTO printer_alerts (id, farm_id, printer_id, kind, message) VALUES ('$ALERT_A', '$FARM_A', '$PRINTER_A', 'print_failed', 'e2e: the print failed')" >/dev/null
expect 200 GET "/alerts?open=true" "$VIEWER_A" -- "a viewer lists open alerts"
body_has "$ALERT_A"
expect 200 GET "/alerts?open=true" "$OUTSIDER" -- "the outsider lists open alerts"
body_lacks "$ALERT_A"
ok "the outsider's alert list does not contain farm A's alert"
expect 404 POST "/alerts/$ALERT_A/acknowledge" "$OUTSIDER" -- "the outsider cannot acknowledge another org's alert"
expect 403 POST "/alerts/$ALERT_A/acknowledge" "$VIEWER_A" -- "a viewer cannot acknowledge"
expect 200 POST "/alerts/$ALERT_A/acknowledge" "$OPERATOR_A" -- "an operator acknowledges"
WHO="$(json 'd["acknowledgedBy"]')"
[ "$WHO" = "$OPERATOR_A_ID" ] && ok "acknowledgedBy came from the token" || bad "acknowledgedBy is '$WHO'"
expect 200 POST "/alerts/$ALERT_A/acknowledge" "$OWNER_A" -- "acknowledging again is harmless"
WHO2="$(json 'd["acknowledgedBy"]')"
[ "$WHO2" = "$OPERATOR_A_ID" ] && ok "the first acknowledger stays on the record" || bad "second ack overwrote the record: '$WHO2'"
expect 200 GET "/alerts?open=true" "$VIEWER_A" -- "an acknowledged alert leaves the open list"
body_lacks "$ALERT_A"
expect 401 GET /alerts - -- "GET /alerts rejects anonymous"
# --- 8. the guards that stop an org becoming unadministrable --------------
echo "==> an organisation cannot be left without an owner"