E3: Sentry watchdog detection + quarantine (#44) #50

Merged
jacisjake merged 2 commits from task/44-sentry-quarantine into main 2026-10-28 15:00:42 +00:00
jacisjake commented 2026-10-28 14:55:36 +00:00 (Migrated from github.com)

What was addressed

  • Sentry watchdog (E3): every crow claim/deliver that refers to a request gets one Jev call with three noul questions (answers_request, states_fact_without_source, contradicts_earlier) — JEV_STRATEGIES.md strategies 4/6/8.
  • Flock.ClassifyWatch mirrors Flock.ClassifyDivergence: flock cap via reserve, CrowTimeout, budget check, and a watch flight-log event carrying the three probabilities (calibration, strategy 5).
  • Above-band breach (any score ≥ CROWS_JEV_THRESHOLD, or answers_request ≤ 1−threshold): the Sentry posts a flag (author Sentry, to owner, refers to the crow's message) naming the scores, and quarantines the crow.
  • Quarantine lives on the ledger (quarantined map, persisted via Ledger.Save): Quarantine/LiftQuarantine/IsQuarantined. Fail-safe — it sticks until lifted.
  • Enforcement: a quarantined crow's POSTs get 403 {"message": "<author> is quarantined pending the owner's decision."}; the open-, conspiracy- and directed-response selectors skip quarantined crows.
  • Bounds: at most one flag per crow per referent; owner messages are never inspected; no deletion paths — the Sentry never hard-kills.
  • EVENTS.md extended first: flag row, POST 403 return, guardrail bullets for the watchdog and the quarantine shape.
  • watchdog_test.go: six tests covering the acceptance criteria.
  • ask_owner escalation (reinstate/retire the crow) deliberately lands in #45, not here.

How to test / validate

  • go test -race -count=1 -run 'TestSentry|TestQuarantine|TestWatchdog' . → ok (all six pass):
--- PASS: TestSentryQuarantinesFabrication (0.02s)
--- PASS: TestSentryBelowBandSilent (0.81s)
--- PASS: TestSentryOneFlagPerExpectation (0.02s)
--- PASS: TestSentryNeverSeesOwner (0.41s)
--- PASS: TestQuarantineSilencesUnprompted (0.01s)
--- PASS: TestWatchdogGoesThroughFlock (0.41s)
ok  	somanycrows	2.689s
  • Red state (before the feature commit) failed as expected — the API did not exist:
./watchdog_test.go:113:17: s.p.Ledger.IsQuarantined undefined (type *Ledger has no field or method IsQuarantined)
./watchdog_test.go:138:16: s.p.Ledger.IsQuarantined undefined (type *Ledger has no field or method IsQuarantined)
./watchdog_test.go:181:4: s.checkWatchdog undefined (type *Server has no field or method checkWatchdog)
./watchdog_test.go:208:13: s.p.Ledger.Quarantine undefined (type *Ledger has no field or method Quarantine)
FAIL	somanycrows [build failed]
  • gofmt -l . → empty; go vet ./... → clean.
  • Full suite: go test -race -count=1 -timeout 180s . → ok somanycrows 7.653s.

Configurations or dependencies

  • .env unchanged: watchdog runs wherever TYPESAFE_API_KEY is set (nil Jev = watchdog silently no-ops, same as divergence).
  • No new guardrails or env vars; reuses CROWS_JEV_THRESHOLD, TokenBudgetPerItem, CrowTimeout.

Closes #44
Epic: #37

## What was addressed - Sentry watchdog (E3): every crow `claim`/`deliver` that refers to a request gets one Jev call with three noul questions (`answers_request`, `states_fact_without_source`, `contradicts_earlier`) — `JEV_STRATEGIES.md` strategies 4/6/8. - `Flock.ClassifyWatch` mirrors `Flock.ClassifyDivergence`: flock cap via `reserve`, CrowTimeout, budget check, and a `watch` flight-log event carrying the three probabilities (calibration, strategy 5). - Above-band breach (any score ≥ `CROWS_JEV_THRESHOLD`, or `answers_request` ≤ 1−threshold): the Sentry posts a `flag` (author Sentry, to owner, refers to the crow's message) naming the scores, and quarantines the crow. - Quarantine lives on the ledger (`quarantined` map, persisted via `Ledger.Save`): `Quarantine`/`LiftQuarantine`/`IsQuarantined`. Fail-safe — it sticks until lifted. - Enforcement: a quarantined crow's POSTs get 403 `{"message": "<author> is quarantined pending the owner's decision."}`; the open-, conspiracy- and directed-response selectors skip quarantined crows. - Bounds: at most one flag per crow per referent; owner messages are never inspected; no deletion paths — the Sentry never hard-kills. - `EVENTS.md` extended first: `flag` row, POST 403 return, guardrail bullets for the watchdog and the quarantine shape. - `watchdog_test.go`: six tests covering the acceptance criteria. - ask_owner escalation (reinstate/retire the crow) deliberately lands in #45, not here. ## How to test / validate - [ ] `go test -race -count=1 -run 'TestSentry|TestQuarantine|TestWatchdog' .` → ok (all six pass): ``` --- PASS: TestSentryQuarantinesFabrication (0.02s) --- PASS: TestSentryBelowBandSilent (0.81s) --- PASS: TestSentryOneFlagPerExpectation (0.02s) --- PASS: TestSentryNeverSeesOwner (0.41s) --- PASS: TestQuarantineSilencesUnprompted (0.01s) --- PASS: TestWatchdogGoesThroughFlock (0.41s) ok somanycrows 2.689s ``` - [ ] Red state (before the feature commit) failed as expected — the API did not exist: ``` ./watchdog_test.go:113:17: s.p.Ledger.IsQuarantined undefined (type *Ledger has no field or method IsQuarantined) ./watchdog_test.go:138:16: s.p.Ledger.IsQuarantined undefined (type *Ledger has no field or method IsQuarantined) ./watchdog_test.go:181:4: s.checkWatchdog undefined (type *Server has no field or method checkWatchdog) ./watchdog_test.go:208:13: s.p.Ledger.Quarantine undefined (type *Ledger has no field or method Quarantine) FAIL somanycrows [build failed] ``` - [ ] `gofmt -l .` → empty; `go vet ./...` → clean. - [ ] Full suite: `go test -race -count=1 -timeout 180s .` → `ok somanycrows 7.653s`. ## Configurations or dependencies - [ ] `.env` unchanged: watchdog runs wherever `TYPESAFE_API_KEY` is set (nil Jev = watchdog silently no-ops, same as divergence). - [ ] No new guardrails or env vars; reuses `CROWS_JEV_THRESHOLD`, `TokenBudgetPerItem`, `CrowTimeout`. Closes #44 Epic: #37
Sign in to join this conversation.