feat(state): the three credential directories, and the two answers that must not merge
Dimension 5.5. A new crate, `afd_state`, named for `src/agentsfleetd/state/` where the Zig daemon keeps its statements. Integration goes 31 → 43 tests, all against live Postgres. ## Where these had to live `src/auth/` may not import `src/db/` and `make test-auth` greps to keep it that way; `afd_auth` reaches the same wall by construction, since it does not list `sqlx` and so cannot name it. Zig's concrete lookups therefore sit in `cmd/api_key_lookup.zig`, `cmd/cli_credential_lookup.zig` and `cmd/serve_runner_lookup.zig` — three files, three `LookupFn` pointers, three `Ctx` structs each holding the same pool. Here it is one type holding one pool and one `resolve` that dispatches on the class the caller already has in hand. The plumbing was three times as wide to say the same thing. ## The distinction the trait exists for `Ok(None)` and `Err(Unavailable)` must never collapse. A digest matching nothing is an authentication REJECTION; a Postgres blip is not. The runner client counts rejections toward a self-termination ceiling and resets that counter on transport-class failures, so reporting an outage as a rejection walks a healthy fleet's runners to shutdown one at a time. Two tests hold that line from opposite sides — a statement Postgres refuses (`DROP TABLE` under a live pool) and a datastore that is gone entirely (`DROP DATABASE ... WITH (FORCE)` under one) — and both must answer unavailable. ## A corrupt row is unavailable, not unknown A third case the trait has no obvious answer for. `Ok(None)` would tell a runner its token is unknown, which it counts; `Unavailable` is also simply true, and it pages the right person. Both paths log at `error`, because that is the only signal the row exists at all. Reachable in a test, which took reading the schema: `ck_runners_id_uuidv7` checks the version nibble and nothing else, so a UUID with an RFC-4122-invalid variant is a value Postgres stores and `Uuid7::parse` refuses. `oidc_subject` has no non-empty constraint, so a blank subject is seedable too. ## `#[non_exhaustive]` made `CredentialKind`'s own doc comment false The comment promises "the build fails until all three exist". The attribute three lines below it forces every crate OUTSIDE `afd_auth` to write a `_` arm — and the crate that most needs exhaustiveness is outside by construction. A new credential class would have fallen into a catch-all and resolved to nothing, silently. It buys downstream semver stability for crates that have no downstream. Removed; four others remain in `afd_auth` and want the same look. ## Rows read through `FromRow`, not column at a time Reading columns one by one gives every read its own error arm, and those arms are unreachable in a correct build — dead code this workspace measures. A hand-written `FromRow` per class folds all of it into the one failure sqlx already reports, so the lookup has a single error path and it is one a test can reach by cutting the connection. Written out rather than derived, for the same reason the problem+json envelope builds a `Map`. ## Also fixed `test_entries_match_the_zig_registry` had no Zig spelling recorded for 429, so the first entry to answer that status broke it. The registry test now knows `.too_many_requests` and `.not_found`. `Entropy`'s `Default` was the workspace's only uncovered line. Covered by an assertion worth having on its own: the default source is the operating system's, not the mock behind `test-util` — a `Default` reaching for that would make every minted id predictable in any build with the feature on.
K
Kishore Kumar committed
66bea03a4ba9c4774c3174e2afd52ab8a3db681d
Parent: 6f70680