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):
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.
## 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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What was addressed
claim/deliverthat refers to a request gets one Jev call with three noul questions (answers_request,states_fact_without_source,contradicts_earlier) —JEV_STRATEGIES.mdstrategies 4/6/8.Flock.ClassifyWatchmirrorsFlock.ClassifyDivergence: flock cap viareserve, CrowTimeout, budget check, and awatchflight-log event carrying the three probabilities (calibration, strategy 5).CROWS_JEV_THRESHOLD, oranswers_request≤ 1−threshold): the Sentry posts aflag(author Sentry, to owner, refers to the crow's message) naming the scores, and quarantines the crow.quarantinedmap, persisted viaLedger.Save):Quarantine/LiftQuarantine/IsQuarantined. Fail-safe — it sticks until lifted.{"message": "<author> is quarantined pending the owner's decision."}; the open-, conspiracy- and directed-response selectors skip quarantined crows.EVENTS.mdextended first:flagrow, POST 403 return, guardrail bullets for the watchdog and the quarantine shape.watchdog_test.go: six tests covering the acceptance criteria.How to test / validate
go test -race -count=1 -run 'TestSentry|TestQuarantine|TestWatchdog' .→ ok (all six pass):gofmt -l .→ empty;go vet ./...→ clean.go test -race -count=1 -timeout 180s .→ok somanycrows 7.653s.Configurations or dependencies
.envunchanged: watchdog runs whereverTYPESAFE_API_KEYis set (nil Jev = watchdog silently no-ops, same as divergence).CROWS_JEV_THRESHOLD,TokenBudgetPerItem,CrowTimeout.Closes #44
Epic: #37