The kill switch had no operator surface. streamrevoke.Store.List() existed and
was called from nowhere, so an admin could only terminate one session by id or
revoke a user's streams as a side effect of editing their account — with no way
to see what was revoked, choose a TTL, or undo a mistake. Because expiry is
deliberately monotonic, a wrong 24h kill was irreversible.
- GET/POST/DELETE /api/v1/admin/streams/revocations, admin-only, additive.
- Explicit wire-to-internal kind mapping: the wire accepts "session" (and
"sess"), the store key is "sess". Passing the wire string straight into a Key
would create a revocation IsRevoked never consults.
- Validation: non-empty bounded session ids; canonical positive user ids
(strconv.Itoa round-trip, so "01" is rejected — the cache key is the
canonical form); ttl_seconds bounded to 30d so the duration cannot overflow;
bounded reason and request body. DELETE of an absent key is idempotent.
- Store.Unrevoke, guarded by a bounded in-memory tombstone: the tombstone is
installed and the local entry dropped BEFORE the slow durable/Redis deletes,
so a concurrent poll reconcile cannot re-apply the row it just read. A newer
Revoke on the same key clears the tombstone, so an unrevoke never suppresses
a later legitimate kill. Tombstones age out with the kill they replaced.
- maintain() takes the same operation lock as Revoke/Unrevoke around its
durable block, closing the window where a poll tick could re-Upsert a row
Unrevoke had just deleted — invisible until a restart resurrected the kill.
- Durable self-heal now compares expiry, not mere presence, so a failed Revoke
mirror leaves a stale shorter row that the next tick repairs.
- A failed unrevoke publish fails safe: other processes keep the kill until it
expires. Propagation failures surface as warnings rather than weakening the
local result.
Unrevoking an over-cap victim is legal but the async enforcer will re-revoke it
on its next pass while the user is still over cap; that is documented at the
endpoint.
Part of the stream monitoring & kill-switch epic.