SIGN IN SIGN UP

feat(rustd): port §4 auth as two crates — the decision, and the provider

`bearer_or_api_key` is named after its own defect, but the if/else chain is
the symptom. The disease is that tenant_api_key.zig, cli_credential.zig and
runner_bearer.zig are the same procedure written three times — hash, look up,
check liveness, resolve capability, build a principal — with every difference
buried in a hand-written body. That is why looksWellFormed guards exactly one
of the three and nobody noticed.

So the differences become data and the class set becomes total:

- credential.rs   a prefix TABLE, asserted PREFIX-FREE at compile time. agt_t
                  and agt_r differ in one byte and the Zig chain holds that
                  property by authoring order; here a careless agt_ class
                  fails the build instead of shadowing its neighbour.
- plane.rs        the runner/tenant boundary as data. Zig enforces it by which
                  middleware got mounted; Plane::admits is a total table and
                  test_planes_partition_the_catalogue proves every class
                  belongs to exactly one plane.
- authenticate.rs ONE procedure driven by a HashedClass const per class.
                  Dispatch is an exhaustive match, so a new credential class
                  fails the BUILD until it is wired — the guarantee scope.rs
                  already gets from wire()/bit().
- error.rs        each refusal owns its registry code AND its client-visible
                  sentence, pinned byte-for-byte against the Zig constants.

The crate split makes Zig's `make test-auth` portability wall a fact rustc
checks: afd_auth lists no runtime, no socket and no datastore, so it cannot
name them. afd_identity holds the only sockets in the auth path — JWKS
fetch/cache/RS256 verify, and the live capability resolver.

core_api-develop's lib-auth supplies the vocabulary (FlowDelegate's
opens_door/subject_is_present is kind/authenticate, and its lib-auth/api-auth
split is this split). Its FlowBuilder does not: a Vec<Box<dyn FlowDelegate>>
scanned by equality resolves an unregistered door to None, i.e. a 401 at run
time. M-DI-HIERARCHY puts generics above dyn Trait for the same reason.

RS256 verifies through ring, which rustls already selects as its provider in
this binary — so it is a direct dependency on a crate that was always linked,
at the layer whose API takes raw (n, e). rustls only exposes RSA against a DER
SubjectPublicKeyInfo, and a JWKS has no certificate.

Dimensions 4.1-4.6 all done. Divergences recorded in the spec: the RSA floor
rises to 2048 (behaviour, Indy's call, made safe by refusing at parse so
prime() turns it into a boot refusal); one procedure replaces three (shape);
the prefix table is compile-time prefix-free (shape); the plane boundary is
data (shape). The spec's "all hash compares timing-safe" is corrected — there
is no constant-time compare in that path, it is safe by input entropy.

afd_auth 510/510 and afd_identity 524/524 lines under lcov. Note that
--summary-only under-reports a generic crate: it counts the rlib's
un-monomorphized Registry<p,p,p> copies, which cannot execute. Recorded
beside the tracing/log finding it resembles.

§5's Dimension 5.5 now names afd_state for the three Postgres
CredentialDirectory impls, which is the crate this spec already planned for
"the auth-consumed lookups". §7 must call JwksVerifier::prime() at boot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
K
Kishore Kumar committed
81aae2eb16e32d721b0e6ca4cf38e51250ad4ae4
Parent: 872977d