refactor(rustd): delete the hand-rolled parsers Zig had no library for
Indy asked what `GateRef` actually was. It was me carrying a Zig-ism: `approval_gate_async.zig` packs the reference into `"action_id|deadline_ms"` and splits it back by hand, because Zig has no serializer and a pipe-delimited pair is the cheapest thing to write. Nothing else reads that key — one writer, one reader, both on the lease path — so the format was never a contract, only the shape a hand-rolled encoder happened to produce. It is a serde type now. The separator, the split, the "what if an id contains a pipe" question and the two functions that answered it are gone, and `#[serde(try_from)]` runs the domain validation on EVERY read instead of the ones a caller remembered. Three more of the same shape, found by looking rather than by being asked: **The stored vocabularies.** `Answer` and `Status` were `match` arms over string literals — which is precisely what `runner::policy`'s own `parse_wire` doc argues against: a hand-written match is a second copy of every variant's name, and the failure it causes (a row one release writes that the next cannot read) has no failing test behind it. Both are `#[serde(rename)]` declarations now. `parse_wire` itself was a crate-private helper with one consumer and is now `afd_core::spelling::from_spelling` with three. **The URL parser.** `base_url_guard.zig` scans for `://`, finds the first `/?#`, takes the last `@`, and looks for a `:` unless the authority opens with `[`. I ported that by hand and justified it on wire parity — a justification that turned out to be wrong when checked: the runner compares allowlist entries with `std.ascii.eqlIgnoreCase` at all three of its matching sites, so normalisation is harmless. It is `url::Url` now, already in the lock, and the win compounds: `Host` comes out TYPED, so `ssrf` classifies an `Ipv4Addr` directly with no string round trip and its bracket-stripping and zone-id handling disappear along with the parse. Three verdicts change, and every one is the parser agreeing with what an HTTP client would actually dial — the only property this guard is for: - `https:///just/a/path` dials `just`, where the Zig called it malformed and protected nothing. The SSRF check runs on that host, so `https:///169.254.169.254` is refused exactly as the two-slash spelling is. - `https://256.1.1.1/v1` is refused. The Zig's `parseIpv4` failed, concluded "not a literal", and passed it through as a NAME — the unsafe direction. - A schemeless host reports `InvalidScheme` rather than the parser's "relative URL", which is the diagnosis an operator can act on. Two files crossed the length cap on the way and were split at real seams, not at line counts: `gate/pending.rs` into the reference and the vocabularies it resolves through, and `config/gates.rs` into approval rules and anomaly rules — an approval rule asks a HUMAN and an anomaly rule asks nobody, which is why `approval_gate_anomaly.zig` is its own module upstream too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
K
Kishore Kumar committed
3fa990ec63d6402dc6169cbb487b762edd2fb70c
Parent: c84e126