Attach a check (Prüfweg)
A Prüfweg (check) is a read-only command COMPASS can run to see whether reality still matches a rule. It doesn't judge on its own — running it writes a fact, and if that fact contradicts the rule it's attached to, a deviation appears.
Create it
curl -sS -X POST "$COMPASS_URL/pruefwege" \
-H "X-Compass-Key: $COMPASS_KEY_RW" -H "Content-Type: application/json" \
-d '{"slug": "ci-timeout-grep", "typ": "grep",
"befehl": "grep -r \"timeout-minutes: 20\" .github/workflows/",
"erwartung": "mindestens ein Treffer",
"laufumgebung": "host-ro"}'
typ is one of shell|sql|http|grep|mcp. laufumgebung is one
of host-ro|wegwerf-container|backend-ro. 201 for a new slug;
calling this again with the same slug upserts titel/typ/befehl/
erwartung/laufumgebung and returns 200 — it does not touch the last
run result.
The command allow-list
befehl is checked against a fixed set of allowed prefixes before it's
accepted, and rejected outright if it contains a writing pattern — this is
what keeps a "check" from becoming a second way to change data:
Allowed prefixes:
gh api, git ls-files, git rev-list, curl -s, psql -c "SELECT,
grep, ls, cat, test -f, docker inspect,
bash scripts/ops/, python3 scripts/ops/.
Rejected if present anywhere in the command: >, rm, DELETE,
UPDATE, INSERT, -X POST, git push, docker push. Note there is no
bare push on this list — blocking it would also block read-only scripts
whose names contain the word (e.g. a pre-push-* gate script executed
via the scripts/ops/ prefix).
A command that isn't on the allow-list, or that contains one of these
patterns, is rejected with 422 before anything is stored.
Link it to a rule
Set pruefweg_slug when you write the Aussage, so GET /warum/{sache} and
the deviation list can find it:
curl -sS -X POST "$COMPASS_URL/aussagen" \
-H "X-Compass-Key: $COMPASS_KEY_RW" -H "Content-Type: application/json" \
-d '{"sache": "ci-timeout-minuten", "art": "regel",
"text": "CI-Jobs brechen nach 20 Minuten ab.",
"herkunft": "entschieden", "angelegt_von": "your-name",
"pruefweg_slug": "ci-timeout-grep"}'
Run it
curl -sS -X POST "$COMPASS_URL/pruefwege/ci-timeout-grep/run" -H "X-Compass-Key: $COMPASS_KEY_RW"
This runs with a 30-second timeout, stores letzter_lauf/letztes_ergebnis,
writes a fact (herkunft=gemessen, quelle=pruefweg:<slug>), and determines
abweichung (does the result match erwartung?). A clean run
(abweichung=false) resolves any open deviation state for this check.
In the app
Under Prüfwege (sidebar, section “Projekt”) every check is in one
place: what it checks, when it last ran, what came out, and which rules it
is attached to. A “Jetzt ausführen” button calls the same
POST /pruefwege/{slug}/run as above.
Above that is the list of unchecked rules: rules in force with no check, with an ended check, or with one that has never run. If the rule book cannot be fetched, the list says “unknown” — not “none”.
See what's deviating
curl -sS "$COMPASS_URL/abweichungen" -H "X-Compass-Key: $COMPASS_KEY_RO"
Each entry names a determined cause — never a guess:
verstoss— the check ran and genuinely disagreed with the rule.werkzeug_fehlt— the result matches a known missing-tool pattern (gh fehlt,kein git-repo,command not found,permission denied,no such file or directory,timeout nach 30s). This pattern always wins over the raw pass/fail reading — a missing tool is not a violation.unklar— the expectation text itself couldn't be evaluated as true or false from the result.
stumm=true after three consecutive werkzeug_fehlt runs of the same
check — a check that keeps reporting a missing tool stops being surfaced as
an active alert (it still appears in this list; it's dropped only from the
GET /offen count, so it never disappears silently). Nothing here clears
itself on a later green run except by explicitly resolving it — see the
three resolution endpoints (verfolgen, regel-ueberholt,
pruefweg-kaputt) in the REST reference.
Ending a check
A check that is no longer needed is ended — not deleted.
curl -sS -X POST "$COMPASS_URL/pruefwege/ci-timeout-grep/beenden" \
-H "X-Compass-Key: $COMPASS_KEY_RW" -H 'Content-Type: application/json' \
-d '{"grund": "The check now runs in CI.", "von": "your-name"}'
grund and von are required. A state without a reason is a riddle a
month later, and a check that disappears without anyone standing behind it is
the quiet way out.
From then on:
- The nightly pass no longer triggers it.
- It no longer appears under
GET /abweichungen. - The round no longer counts it — not even in the denominator.
POST .../runanswers409and names the reason.
The row stays. GET /pruefwege still returns it, with beendet_am,
beendet_grund, beendet_von and an unchanged last run. Ending it twice is
409: the first reason is the one that holds.