SIGN IN SIGN UP

feat(rustd): a parked event resolves on a later poll, not on a waiting thread

The approval gate's read side: the reference an event is parked under,
the decision behind it, and the counter that stops a runaway.

`GateRef` parses to a `Uuid7` rather than to a bounded byte buffer. The
Zig carries `[36]u8` plus a length and validates only that length, which
admits any thirty-six bytes — including spellings
`core.fleet_approval_gates` could never have written. Parsing to the
identifier type means a reference that exists names something that could
be a row, and the read after it needs no second check.

`evaluate` is pure and takes the answer as a parameter, so the ordering
that matters is provable with no datastore: an ANSWER OUTRANKS A LAPSED
DEADLINE. A reviewer approving at the last second and a sweeper that has
not run yet must not race to opposite outcomes, and the answer is the
one a human actually gave. The deadline boundary is exclusive, matching
`now_ms > deadline_ms`, and the test says so at all three points.

`Status` keeps its five arms and `Answer` has two. The three refusing
statuses are three different incidents to an operator reading the row —
a reviewer said no, nobody answered, the daemon stopped it — and one
fact to the question "may this event run". `Status::answer` is the one
place they collapse, and `None` is `Pending` alone, which is what lets
the durable read hand back an `Option` instead of a terminality check
every caller has to remember.

The mirror-then-durable fallback comes across intact, including its log
line: it fires only in the window a best-effort mirror write missed, so
it is the metric that says the write side has a gap, and it is silent
when it does not.

`afd_fleet` still names no Redis command. The three primitives this
needed — get, set-with-expiry, increment-in-window — landed in
`afd_redis::kv` as GENERIC operations rather than gate-shaped ones,
because the key shapes belong beside the sweeper and resolver that also
read them, and the queue crate should not learn the approval gate's
vocabulary. Every other Redis shape in this daemon already arrives
through a typed surface; these are the same thing.

The counter script stays one EVAL. Two commands leave a window where a
crash strands a fresh key with no expiry, after which every later call
sees a count above one, skips the expiry branch forever, and accumulates
without bound until it auto-kills the fleet for traffic spread over
days.

The anomaly check fails OPEN where the gate fails CLOSED, and it answers
a verdict rather than a `Result` — the one place in this crate that is
right, because there is no caller decision left to make. Handing back an
`Err` would offer a choice whose only correct answer the module already
knows.

Not in this commit: parking an event, the card's detail, and pausing a
fleet. Those are writes; this is everything a poll READS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
K
Kishore Kumar committed
2c732397e3e64209cd4634fdd584d856e495cbb2
Parent: e4ae4a7