Skip to content

M7-20 Alerts into the instance and to the outside

No Date Ruling Reason
D-M7-20-0 2026-10-20 Base int/m1-wave50 at d200587, stacked on feat/M2-05-runs-state-machine (20d1a89) and feat/M2-27-webhooks-incoming (a0d1de4), branch feat/M7-20-alert-routing in its own worktree; the branch is not merged locally and not pushed, the PR lane ports it. The base names the chapters in German; text this ticket adds is English, also inside German chapters. The brief relies on no core rule and no ruling of origin/main beyond D-W-100 and D-W-125, which are planning rulings and are not ported. Rulings stand in a section of the ticket like the earlier tickets on this base. The launcher's instructions take precedence over the brief's landing step; merged names win (D-W-100).
D-M7-20-1 2026-10-20 The setup tool is the POSIX script infra/compose/alert-setup.sh (not a Python tool under tools/): alert-setup.sh [--secrets-dir DIR] [--internal-base URL] channel\|external\|heartbeat reads one URL from standard input (not echoed on a terminal), writes infra/compose/secrets/alertmanager_<route> atomically and prints only <created\|updated\|kept> <name> sha256:<12 hex>. For channel it keeps the path /api/v1/hooks/<id>/<token> and replaces the origin by http://api:8081; the other two URLs are written unchanged. It never creates the channel, the app or the webhook: they need a person's step-up. The release bundle ships infra/compose but not tools/, and an instance host needs only sh and Docker; the fingerprint form of secrets-init (16 5.3 item 5) lets the owner compare both outputs; requirement 13 allows no public path, and the URL createIncomingWebhook returns names server.public_url.
D-M7-20-2 2026-10-20 File modes: the owner's source file is 0600 in a directory 0700 (what secrets-init reads as root); the copies in the subdirectory alertmanager of the volume secrets keep the third-party rule of 16 5.3 item 4 (root, directory 0755, files 0444), because the image prom/alertmanager runs as nobody. Brief Design note 1 asks for 0600; requirement 15 allows the mode the secrets layout needs for a third-party service, and that layout is already fixed.
D-M7-20-3 2026-10-20 Alertmanager starts through the guard infra/observability/alertmanager/start.sh (entrypoint /bin/sh /etc/alertmanager/start.sh /bin/alertmanager), which reads every url_file of the configuration and exits 1 while one is missing, unreadable or blank, naming the files and the fix, never a value. Checked 2026-10-20 with prom/alertmanager:v0.34.1: a missing url_file does not stop the start, the receiver fails only when it sends (requirement 16).
D-M7-20-4 2026-10-20 The end-to-end test is make alert-e2e (tools/observability/alert_e2e.py, outside make check, row in docs/quality/gates.md), on the smoke stack of M1-73 with the overlay infra/compose/test/compose.alert-e2e.yaml. The test rules (infra/compose/test/alert-e2e/rules.yaml, one page and one ticket alert on the gauge alert_path_e2e_firing of the stub) are loaded only by the overlay's Prometheus, whose configuration the runner derives from the real one with 5 s intervals, the extra rule file and the scrape job of the stub; Alertmanager runs with the real configuration and its timers. The probe (bothy-smoke alerts) sets up channel, app and webhook as the owner with step-up and removes the flag file after the firing messages, so the rules resolve; the stub (bothy-smoke alert-stub) records the external notifications. Names, levels and summaries come from the rule file only. The brief's Design note 2; the Go probe already speaks DPoP and the step-up with the device key (M1-73), so no second client is built; a flag file is the one control both the probe and the stub reach without a published port.
D-M7-20-5 2026-10-20 Failure class behind the blocked alert path: the contract closed the payload of a foreign sender (IncomingWebhookPost and AlertmanagerAlert with additionalProperties: false, checked against hand-written fixtures), so every member Alertmanager sends beyond the documented payload (v0.34.1: routeLabels, notification_reason) gave 400 validation_failed with unknown_member, which Alertmanager treats as final and drops. Fixed at one place: incoming.Classify gets every top-level member name; an Alertmanager payload ignores members it does not name (at the top and, through the open AlertmanagerAlert, in each alert), a native body stays closed and gives 400 validation_failed with one unknown_member per foreign member in name order. IncomingWebhookPost is additionalProperties: true so the generated type carries the foreign members to Classify; both schemas are listed in freeFormSchemas of the closed-schema check of api/routes/gen. Proven by a seeded model test of Classify over all member sets and a seeded HTTP test that posts every fixture with random foreign members; the two payloads recorded from Alertmanager v0.34.1 are golden fixtures. The payload is the sender's, not a request of a Bothy client, so the reason of R-API-04 (an older server must not swallow a client's intent) does not apply, while a refusal loses the alert; listing the two members would break again with the next Alertmanager version. Steering ruling of 2026-10-20 (class fix of M7-20).
D-M7-20-6 2026-10-22 Port to main: the four own commits of feat/M7-20-alert-routing (after the merges of M2-05 and M2-27) are brought onto origin/main as one change on feat/M7-20-alert-routing-main; M2-05 and M2-27 are already on main. The spec links name the English chapters (05-actions.md, 08-identity.md, 11-extensions.md, 16-operations.md), the German text this ticket touched in alerting.md and configuration.md is taken in the English form of main, the changelog entries and these rulings are fragments, and the generated clients are generated again. D-M7-20-0 describes the base the ticket was built on. main has the English spec and the fragment rule (PL/24); the behaviour is unchanged.