comment #25
### Design, and the two alternatives it beat

**Three legs, no new registration.** PostToolUse (matcher stays `Write` — the fastest leg for the common instrument), PreToolUse (mid-turn, catches every other instrument), Stop (backstop for a drop written by the turn's final tool call). All three already fire and already carry a cwd, so this costs no plugin republish and no extra per-tool-call hook spawn. Widening PostToolUse to `*` was rejected for exactly those two costs — the matcher lives in the plugin's static `hooks.json`.

**Rejected: the io funnel's `COMMUNE` kind.** It is the *right* predicate, and `api.md` names this exact construct ("wake-marker-class constructs over commune and IO events") as its motivating case. But it fires when **ingest completes** — >15s later, operator-measured — by which time no hook is running. It is also authenticated (`--session-id` or a capability `--token`) and its payload is capped at the 16KB class with `truncated`/`digest_seq`, which our communes routinely exceed.

**Rejected: a `[service]` ResidentService.** The only process alive between turns, but `manifest.schema.json` `$defs/Service` states it has "no perch, no identity, and no address" — so it cannot make the authenticated call. Filed as `spt-bs-releases#275`, priced **not blocking** because of this fix. deployah is porting the missing `[service]` docs as `spt-bs-releases#274` / v0.67.1.

### The hazard this introduces

The trigger stops being an **event** (one Write call, observed once by construction) and becomes a **state** (a file that persists until core ingests it, >15s and many tool calls later). Every state-shaped trigger re-fires unless something remembers. Without a stamp the detector would arm on every hook for the whole ingest window — a second wake-arm self-send, a rewritten clearing latch, and ultimately two post-clear sequences, which *blend* rather than fail loudly (the v0.14.1 boundary-rename scar).

`state/commune-seen/<eid>.stamp` records the **last examined** identity (`<mtime_ms>:<len>`), not the last armed one — a commune with no wake marker is the common case, and recording only arms would re-read a 20–100KB file on every tool call for 15 seconds while never arming. Both halves get a test, including one asserting the file is stat'd and **not** read on the repeat passes: a detector that is correct but re-reads is a regression nobody would notice. `REQ-HAZARD-COMMUNE-DROP-REARM`.

Found while reading the call sites: our own `>>commune<<` shortcut writes the drop and arms in the same breath, so it must stamp at that moment too — otherwise the new detector sees a fresh drop on the next hook and arms a second time. The shortcut is the one path that can double-fire *through* the fix rather than around it.

### The assumption, stated so it can be falsified

This holds only while core's ingest is **slower than a hook cycle** — that is what guarantees the drop is still on disk when the next hook looks. Sub-second ingest would let a Bash-written drop vanish unseen, and the funnel/service path becomes necessary. Deliberately not a hazard requirement: it is a property of core's timing, not of our code, so it cannot be unit-tested and lives as a call-site comment naming its fallback.

### Risk being watched

Arming from `Stop` interacts with the quiet-window falsifier (`mark_clearing_stop_seen`, `hook.rs:3647`): get the ordering wrong and we either strand a latch or falsify one we just armed on the next hook. `REQ-HAZARD-QUIET-LATCH-STRANDED`'s tests get re-run against the new leg rather than assumed to still hold.
