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