Epic: #37. Depends on: Epic C (#25 divergence path as the Jev pattern). No code dependency on E1/E2.
Goal
The Sentry watches crow messages with Jev (strategy 8): for each crow claim or deliver that refers to a request, run one SystemOne call with answers_request and states_fact_without_source (noul) plus contradicts_earlier (noul, state = the message + earlier delivers). Verdicts at or above the high band (threshold, reuse CROWS_JEV_THRESHOLD) trigger a breach; a harness guardrail hit (e.g. posting while the crow's level forbids unprompted speech) also counts. On breach: append a flag message (author Sentry) and mark the crow quarantined in the crow registry (crows.json, E1; for persona crows add a runtime quarantine set — persisted alongside the ledger). Enforcement: quarantined crows cannot claim, deliver, or speak unprompted; their POSTs get 403 with a message naming the quarantine. Bounds: at most one flag per crow per referent (existing-flag check like NudgeFor in nestlog.go); checks never run on owner messages; every verdict logged with probabilities (strategy 5 calibration).
Acceptance criteria — falsifiable tests
TestSentryQuarantinesFabrication (watchdog_test.go) — Given fake Jev answering states_fact_without_source noul 0.9 and a Hugin deliver that refers to a request, when the deliver posts, then a Sentry flag message exists (refers_to the deliver) and Hugin's next claim POST returns 403. Red now: no watchdog path.
TestSentryBelowBandSilent — same fake at noul 0.4: no flag, no quarantine, deliver stands.
TestSentryOneFlagPerExpectation — two more flagged delivers on the same referent: exactly one flag message exists (later breaches logged, not posted).
TestSentryNeverSeesOwner — an owner message with the same fabricated content posts no flag (check is skipped for author owner).
TestQuarantineSilencesUnprompted — with Hugin quarantined and an open (unaddressed) request, respondOpenRequest/invite paths skip Hugin entirely (scripted brain never receives a Hugin call; Munin may still answer).
TestWatchdogGoesThroughFlock — verdict call respects Flock.reserve (saturated flock + short ctx → no flag, message unaffected).
EVENTS.md first: flag row already exists (lines 46/88) — extend with the quarantine side-effect note and the 403 response shape.
Test command
go test -race -count=1 -run 'TestSentry|TestQuarantine|TestWatchdog' .
Blockers / unknowns
Quarantine for persona crows (not in the dynamic registry) needs a storage decision: ledger-adjacent quarantine.json vs a field on the ledger record — pick the ledger (it already persists per-crow state) and note the correction in this issue if the plan changes.
Epic: #37. Depends on: Epic C (#25 divergence path as the Jev pattern). No code dependency on E1/E2.
## Goal
The Sentry watches crow messages with Jev (strategy 8): for each crow `claim` or `deliver` that refers to a request, run one SystemOne call with `answers_request` and `states_fact_without_source` (noul) plus `contradicts_earlier` (noul, state = the message + earlier delivers). Verdicts at or above the high band (`threshold`, reuse `CROWS_JEV_THRESHOLD`) trigger a breach; a harness guardrail hit (e.g. posting while the crow's level forbids unprompted speech) also counts. On breach: append a `flag` message (author Sentry) and mark the crow quarantined in the crow registry (`crows.json`, E1; for persona crows add a runtime quarantine set — persisted alongside the ledger). Enforcement: quarantined crows cannot claim, deliver, or speak unprompted; their POSTs get 403 with a message naming the quarantine. Bounds: at most one flag per crow per referent (existing-flag check like `NudgeFor` in `nestlog.go`); checks never run on owner messages; every verdict logged with probabilities (strategy 5 calibration).
## Acceptance criteria — falsifiable tests
1. `TestSentryQuarantinesFabrication` (`watchdog_test.go`) — Given fake Jev answering `states_fact_without_source` noul 0.9 and a Hugin deliver that refers to a request, when the deliver posts, then a Sentry `flag` message exists (`refers_to` the deliver) and Hugin's next claim POST returns 403. Red now: no watchdog path.
2. `TestSentryBelowBandSilent` — same fake at noul 0.4: no flag, no quarantine, deliver stands.
3. `TestSentryOneFlagPerExpectation` — two more flagged delivers on the same referent: exactly one flag message exists (later breaches logged, not posted).
4. `TestSentryNeverSeesOwner` — an owner message with the same fabricated content posts no flag (check is skipped for author owner).
5. `TestQuarantineSilencesUnprompted` — with Hugin quarantined and an open (unaddressed) request, `respondOpenRequest`/invite paths skip Hugin entirely (scripted brain never receives a Hugin call; Munin may still answer).
6. `TestWatchdogGoesThroughFlock` — verdict call respects `Flock.reserve` (saturated flock + short ctx → no flag, message unaffected).
7. EVENTS.md first: `flag` row already exists (lines 46/88) — extend with the quarantine side-effect note and the 403 response shape.
## Test command
`go test -race -count=1 -run 'TestSentry|TestQuarantine|TestWatchdog' .`
## Blockers / unknowns
Quarantine for persona crows (not in the dynamic registry) needs a storage decision: ledger-adjacent `quarantine.json` vs a field on the ledger record — pick the ledger (it already persists per-crow state) and note the correction in this issue if the plan changes.
Implemented in PR #50. Two minimal test adaptations from the written criteria:
Criterion 6 (watchdog through Flock): literally Flock.sem = make(chan struct{}, 0) with a live server ctx cannot work — the POST path itself tags through Flock.ClassifyKind → reserve, so a saturated flock hangs the crow's POST forever and it never returns 201. Adapted: saturated sem and an ended server ctx, so every wrapper call fails fast (kind falls back to the poster's header, POST returns 201) while the watchdog can never obtain a slot → no flag ever appears. Still proves the verdict goes through the Flock wrapper: a direct client call would have flagged.
Criterion 3 (one flag per expectation): once the first flag lands, the crow is quarantined, so its next deliver POST is turned away at the door (403) and cannot reach the watchdog via the API. The test asserts both: the 403 on the re-post, and true dedup by calling checkWatchdog directly on a second already-in-log Hugin deliver for the same referent — still exactly one flag.
All six acceptance tests pass; full suite green with -race. ask_owner escalation (reinstate/retire) deliberately left for #45.
Implemented in PR #50. Two minimal test adaptations from the written criteria:
1. **Criterion 6 (watchdog through Flock):** literally `Flock.sem = make(chan struct{}, 0)` with a live server ctx cannot work — the POST path itself tags through `Flock.ClassifyKind` → `reserve`, so a saturated flock hangs the crow's POST forever and it never returns 201. Adapted: saturated sem *and* an ended server ctx, so every wrapper call fails fast (kind falls back to the poster's header, POST returns 201) while the watchdog can never obtain a slot → no flag ever appears. Still proves the verdict goes through the Flock wrapper: a direct client call would have flagged.
2. **Criterion 3 (one flag per expectation):** once the first flag lands, the crow is quarantined, so its next deliver POST is turned away at the door (403) and cannot reach the watchdog via the API. The test asserts both: the 403 on the re-post, and true dedup by calling `checkWatchdog` directly on a second already-in-log Hugin deliver for the same referent — still exactly one flag.
All six acceptance tests pass; full suite green with `-race`. ask_owner escalation (reinstate/retire) deliberately left for #45.
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.
Epic: #37. Depends on: Epic C (#25 divergence path as the Jev pattern). No code dependency on E1/E2.
Goal
The Sentry watches crow messages with Jev (strategy 8): for each crow
claimordeliverthat refers to a request, run one SystemOne call withanswers_requestandstates_fact_without_source(noul) pluscontradicts_earlier(noul, state = the message + earlier delivers). Verdicts at or above the high band (threshold, reuseCROWS_JEV_THRESHOLD) trigger a breach; a harness guardrail hit (e.g. posting while the crow's level forbids unprompted speech) also counts. On breach: append aflagmessage (author Sentry) and mark the crow quarantined in the crow registry (crows.json, E1; for persona crows add a runtime quarantine set — persisted alongside the ledger). Enforcement: quarantined crows cannot claim, deliver, or speak unprompted; their POSTs get 403 with a message naming the quarantine. Bounds: at most one flag per crow per referent (existing-flag check likeNudgeForinnestlog.go); checks never run on owner messages; every verdict logged with probabilities (strategy 5 calibration).Acceptance criteria — falsifiable tests
TestSentryQuarantinesFabrication(watchdog_test.go) — Given fake Jev answeringstates_fact_without_sourcenoul 0.9 and a Hugin deliver that refers to a request, when the deliver posts, then a Sentryflagmessage exists (refers_tothe deliver) and Hugin's next claim POST returns 403. Red now: no watchdog path.TestSentryBelowBandSilent— same fake at noul 0.4: no flag, no quarantine, deliver stands.TestSentryOneFlagPerExpectation— two more flagged delivers on the same referent: exactly one flag message exists (later breaches logged, not posted).TestSentryNeverSeesOwner— an owner message with the same fabricated content posts no flag (check is skipped for author owner).TestQuarantineSilencesUnprompted— with Hugin quarantined and an open (unaddressed) request,respondOpenRequest/invite paths skip Hugin entirely (scripted brain never receives a Hugin call; Munin may still answer).TestWatchdogGoesThroughFlock— verdict call respectsFlock.reserve(saturated flock + short ctx → no flag, message unaffected).flagrow already exists (lines 46/88) — extend with the quarantine side-effect note and the 403 response shape.Test command
go test -race -count=1 -run 'TestSentry|TestQuarantine|TestWatchdog' .Blockers / unknowns
Quarantine for persona crows (not in the dynamic registry) needs a storage decision: ledger-adjacent
quarantine.jsonvs a field on the ledger record — pick the ledger (it already persists per-crow state) and note the correction in this issue if the plan changes.Implemented in PR #50. Two minimal test adaptations from the written criteria:
Criterion 6 (watchdog through Flock): literally
Flock.sem = make(chan struct{}, 0)with a live server ctx cannot work — the POST path itself tags throughFlock.ClassifyKind→reserve, so a saturated flock hangs the crow's POST forever and it never returns 201. Adapted: saturated sem and an ended server ctx, so every wrapper call fails fast (kind falls back to the poster's header, POST returns 201) while the watchdog can never obtain a slot → no flag ever appears. Still proves the verdict goes through the Flock wrapper: a direct client call would have flagged.Criterion 3 (one flag per expectation): once the first flag lands, the crow is quarantined, so its next deliver POST is turned away at the door (403) and cannot reach the watchdog via the API. The test asserts both: the 403 on the re-post, and true dedup by calling
checkWatchdogdirectly on a second already-in-log Hugin deliver for the same referent — still exactly one flag.All six acceptance tests pass; full suite green with
-race. ask_owner escalation (reinstate/retire) deliberately left for #45.