Expand description
§cheers-axum — HTTP route handlers for cheers
axum 0.8 routers that wire cheers’s identity providers into a
SessionAuthority. This crate is the
HTTP-framework binding for cheers; a product (yah-platform, noisetable, …)
supplies concrete UserStore +
RefreshStore +
RevocationWriter impls (from
cheers-sqlx / cheers-redis) and a configured provider, then mounts the
returned Router at /auth.
§Modules
google(featuregoogle) —GET /auth/login/google+GET /auth/callback/googlefor Google’s standard OIDC Authorization Code + PKCE flow, plusGET /auth/google/id-token/nonce+POST /auth/google/id-tokenfor the native shape (Android Credential Manager), where the IdP hands the app a signedid_tokenand there is no code to redeem.apple(featureapple) —GET /auth/login/apple+POST /auth/callback/applefor Apple’s form-post variant with one-shot first-login name capture.passkey(featurepasskey) —POST /auth/passkey/{register,authenticate}/{start,finish}for the WebAuthn ceremonies wired to a [SessionAuthority].magic_link(featureemail) —POST /auth/magic-link/request+GET /auth/magic-link/verifyfor the email-token sign-in flow.refresh(always on) —POST /auth/refresh, the RFC 6749 §6 refresh-token grant with rotation + reuse detection overSessionAuthority::rotate.
Each module exposes:
- A state struct (e.g.
google::GoogleAuthState) bundling the provider, theSessionAuthority, an HTTP client, and the CSRF cookieconfig. - Route handlers (
login,callback) that consume that state. - A
router()constructor that mounts the handlers at the conventional paths.
§CSRF cookie binding
The OIDC flow store (OidcFlowStore)
holds in-flight flows keyed by csrf_state.secret(). To prevent a stolen
state from being usable against a victim’s session, each login handler
sets an Http-Only cookie carrying the same value; callback refuses any
request where the cookie is missing or doesn’t match the ?state= param.
Apple specifically requires SameSite=None on this cookie because Apple’s
callback is a cross-site POST — see apple for the override.
§Session response
On a successful callback, the handler returns JSON containing the freshly
minted access token, the refresh token, and the user_id — the
SessionBody shape every provider module reuses. The client caches the
access_token (SPAs: in JS heap or sessionStorage; native/CLI: OS
keyring) and hands it back as Authorization: Bearer <paseto>. See
me for the /me/sessions list+revoke routes that consume that header.
No cookies are written by these routes (per R347 the .yah.dev surface is
cookie-free); a product that wants the cookie pattern wraps the returned
Json<SessionBody> in its own middleware.
@yah:ticket(R020-F9, “Service-principal admin endpoint + Ed25519 keypair lifecycle (POST /admin/service-principals + –rotate)”)
@yah:assignee(agent:claude)
@yah:at(2026-06-04T01:36:03Z)
@yah:status(review)
@yah:phase(P3)
@yah:parent(R020)
@yah:next(“POST /admin/service-principals { kind: ‘service’, desired_id, grants: […] } authenticated by operator passkey. Allocate principal record, generate Ed25519 keypair, return secret half ONCE.”)
@yah:next(“Track pubkey in cheers for JWKS publication; the secret leaves cheers exactly once.”)
@yah:next(“Rotation: –rotate registers a fresh keypair; keep old keypair active for service_overlap_window (default 24h) before dropping from JWKS.”)
@yah:verify(“Provision a service principal, capture the returned secret, sign a token off-cheers, present it at POST /ownership — cheers verifies via JWKS without a separate fetch.”)
@yah:verify(“Rotate, observe two pubkeys in JWKS during overlap window, observe old kid drops from JWKS after window expires.”)
@yah:gotcha(“Secret half returned ONCE — cheers retains only the public key. There is no recovery path for a lost secret; rotate to issue a new one.”)
@yah:gotcha(“Yubaba uses this keypair to mint its OWN short-lived tokens (sub: svc:yubaba-. Re-exported from cheers_server::* (lib.rs L42-46). 14 new unit tests; cargo test -p cheers-server: 96 unit + 9 proptest + 2 doctest, all green. Parent relay smoke green (cheers-core 51, cheers-verify 4, cheers-axum 22). cargo check –workspace –all-features clean.”)
@yah:handoff(“AUTHORITY API: ServicePrincipalAuthority::provision(NewServicePrincipal, now) -> ProvisionedKey (allocates Principal kind=Service via Principal::try_new, generates fresh Ed25519 keypair via PasetoV4SecretMinter::generate, persists pubkey only, returns 64-byte secret ONCE). ::rotate(&PrincipalId, now) -> ProvisionedKey (refuses non-service kind / unknown id, retires the prior Active key with retire_at = now + policy.overlap_seconds, registers + returns fresh keypair). ::published_signing_keys(now) -> Vecgrants: [...] body field and writes it via whatever grant-write path lands separately. Don’t conflate — the F9 authority cleanly returns ProvisionedKey; grant population is composable at the HTTP layer.”)
@yah:handoff(“WIRE-CONTRACT COORDINATION: this work doesn’t change the W159 wire contract (claims shapes, scopes, token envelope). The JWKS shape DOES touch the kamaji consumer surface — F11 is where that coordination is needed, not here.”)
@yah:next(“Sign off F9 (this ticket) — tasks-met, awaiting human review.”)
@yah:next(“Claim R020-T17 to land the cheers-axum admin routes + operator auth.”)
@yah:next(“Claim R020-T18 to land the cheers-sqlx persistent impl + 0003 migration.”)
@yah:next(“F11 (JWKS) can now be claimed in parallel — ServicePrincipalAuthority::published_signing_keys is its consumer entry point.”)
@yah:ticket(R020-F10, “Camp bootstrap endpoint + user-delegation verification (POST /admin/camps/bootstrap)”)
@yah:assignee(agent:claude)
@yah:at(2026-06-04T01:36:11Z)
@yah:status(review)
@yah:phase(P3)
@yah:parent(R020)
@yah:next(“POST /admin/camps/bootstrap { bound_to: user:, desired_id, initial_grants: […] } authenticated as yubaba’s service principal; body carries the user U’s signed delegation (per yah/W122 QR-pair flow).”)
@yah:next(“Verify yubaba’s identity + verify the user-signed delegation; allocate camp principal with bound_to: user:; issue long-lived refresh credential.”)
@yah:next(“Retain the delegation as the auditable ‘user U authorized camp C’ record.”)
@yah:verify(“Provision a camp via the endpoint; observe bound_to set in the principal record; mint via path #2 with the returned credential succeeds.”)
@yah:verify(“Revoke U; subsequent mint via path #2 for the bound camp returns principal_revoked.”)
@yah:gotcha(“Revoking U cascades to all camps bound_to: U. Revoking C alone does NOT touch U. Bake the cascade into the principal-status update path, not into clients.”)
@yah:assumes(“yah-side QR-pair / mobile-app delegation flow (W122) is the canonical way the user signs the delegation. Cheers verifies a known signature shape — W122 must converge before this lands.”)
@arch:see(.yah/docs/working/mcp-auth-and-ownership.md)
@yah:depends_on(R020-F2)
@yah:depends_on(R020-F4)
@yah:depends_on(R020-F9)
@yah:handoff(“LANDED. NEW MODULES: (1) crates/cheers-core/src/delegation.rs — UserDelegation { bound_to, camp_id, issued_at, expires_at, user_signing_key: [u8;32], signature: [u8;64] } + DelegationError + signing_payload() (canonical bytes the W122 client signs). Wire encoding: user_signing_key + signature ride as base64url-no-pad strings. Re-exported as cheers_core::{UserDelegation, DelegationError}. base64 added as an unconditional cheers-core dep (encoding, no crypto). (2) crates/cheers-server/src/camp.rs — UserSigningKey { kid, user, public_key:[u8;32], status:Active|Revoked, created_at } + UserSigningKeyStore trait (list_active_for_user) + MemoryUserSigningKeyStore; CampBootstrapCredential { token, camp_id, created_at, expires_at, revoked }; NewCampPrincipal + ProvisionedCamp + CampPrincipalStore trait (insert/get principal + credential + delegation, revoke_camps_bound_to) + MemoryCampPrincipalStore; CampBootstrapPolicy (default 1 year credential TTL); CampAuthorityError; CampAuthority<S,K>::{ provision, revoke_user_cascade }. (3) crates/cheers-axum/src/camps.rs — CampAdminState<S,K> + CreateCampBootstrapBody + CampBootstrapResponse + POST /admin/camps/bootstrap route. Re-exports from cheers_axum::*.”)
@yah:handoff(“VERIFICATION CHAIN. CampAuthority::provision runs in fixed order — each step aborts before the next side-effect: (1) bound_to.kind == User (programmer-error gate), (2) delegation.bound_to == provision.bound_to, (3) delegation.camp_id == provision.desired_id, (4) delegation.expires_at > now (DelegationExpired), (5) lookup ACTIVE trusted keys for bound_to via UserSigningKeyStore; reject UntrustedSigningKey if delegation.user_signing_key not present (Revoked keys are filtered out by list_active_for_user), (6) Ed25519 verify_delegation_signature over signing_payload (BadSignature on miss), (7) Principal::try_new(kind=Camp, bound_to=Some(user), Active), (8) insert principal (Conflict -> AlreadyExists), insert delegation (audit retention), generate 32-byte CSPRNG credential token + insert. Pinned by 13 unit tests in camp::tests including provision_rejects_signature_from_other_keypair_in_trusted_set (smuggled-pubkey attack: trusted pubkey + forged sig caught by signature verify).”)
@yah:handoff(“CASCADE REVOKE. CampAuthority::revoke_user_cascade(user, now) -> u64 newly-revoked count. Routes through CampPrincipalStore::revoke_camps_bound_to (one sweep over principals + credentials). Memory impl flips both principal.status and credential.revoked for every match in a single mutex-held update. Rejects user.kind != User with WrongPrincipalKind (protects against accidental service/camp cascades that would silently no-op against the WHERE clause). IDEMPOTENT — second sweep returns 0. NOT YET WIRED to a user-revocation hook (no SessionAuthority MCP-awareness today); the primitive is in place for the future wiring. Same pattern OwnershipStore::revoke_by_on_behalf_of follows (F4).”)
@yah:handoff(“WIRE CONTRACT: bound_to + delegation.bound_to MUST agree at the API boundary (the duplication is on purpose — caller sees the binding at the top of the JSON without re-parsing delegation; mismatch surfaces as DelegationMismatch -> 400). Same for desired_id vs delegation.camp_id. delegation.signing_payload() is a stable byte serialization of (bound_to, camp_id, issued_at, expires_at, user_signing_key) — the canonical W122-signing target. signing_payload_starts_with_bound_to_field pins the field order so a struct reorder fails loudly.”)
@yah:handoff(“ROUTE AUTH: POST /admin/camps/bootstrap takes an MCP BEARER (yubaba’s service principal, not session bearer — different in kind from F9/T17 admin routes which take SESSION bearers for operator passkey). Required scope: Scope::CampAdmin. Verified by McpAuthState::verifier + authenticate_mcp + require_scope(Scope::CampAdmin). Session bearer presented at this route -> Unauthorized (collapses with bad sig / expired — confusing-deputy guard via the verify_mcp_at vs verify_at additional-claim-key split, pinned by bootstrap_camp_rejects_session_bearer_as_unauthorized).”)
@yah:handoff(“USER PUBKEY TRUST IS PER-USER, FROM A STORE — NOT BLINDLY ACCEPTED FROM THE BODY. The delegation carries the pubkey it signs under (so the verifier doesn’t need a kid lookup), but the AUTHORITY refuses unless that pubkey is registered in UserSigningKeyStore as Active for the named user. The enrollment flow (W122 / mobile QR-pair) populates the store; cheers-side stops at ‘lookup + verify’. The persistent UserSigningKeyStore impl + the enrollment HTTP surface are W122-side / cheers-sqlx follow-ons — file as peers when W122 lands. For now: MemoryUserSigningKeyStore + UserSigningKey::new (cross-crate constructor since the struct is #[non_exhaustive]).”)
@yah:handoff(“HTTP ERROR MAPPING in error.rs: added Frominitial_grants body field (still required to enforce composition rule (4) — but easier alongside that landing). Until then, an operator can hit POST /grants (separate path) to populate scopes for the freshly-allocated camp principal.”)
@yah:handoff(“VERIFIED GREEN: cargo test -p cheers-core (61 unit incl. 10 new delegation tests + 1 doctest); cargo test -p cheers-server (116 unit incl. 20 new camp tests, 9 proptest, 2 doctest); cargo test -p cheers-verify (4); cargo test -p cheers-axum (43 unit incl. 12 new camps tests, 7+7 integration + 5 doctest). cargo check –workspace –all-features clean. Parent relay smoke (R020) green.”)
@yah:next(“Sign off F10 — tasks-met, awaiting human review.”)
@yah:next(“When W122 (yah-side QR-pair / mobile-app delegation) converges, file the cheers-side enrollment HTTP route (POST /admin/users/
@yah:ticket(R020-F11, “JWKS extension: publish service-principal pubkeys alongside cheers signing keys”)
@yah:assignee(agent:claude)
@yah:at(2026-06-04T01:36:24Z)
@yah:status(review)
@yah:phase(P4)
@yah:parent(R020)
@yah:next(“Extend GET /.well-known/jwks.json to include service-principal pubkeys (kid keyed) alongside cheers’s signing key(s).”)
@yah:next(“Honor 24–72h overlap window per kind — both keys present during the window, only new kid mints, old kid drops after.”)
@yah:next(“Cache-Control: max-age=300; ETag for cheap conditional GET.”)
@yah:verify(“GET /.well-known/jwks.json after provisioning a service principal includes its pubkey with the expected kid.”)
@yah:verify(“After –rotate, both old and new pubkeys appear during the overlap window; old pubkey is absent after the window expires.”)
@yah:gotcha(“Kamaji matches by kid and falls back to a one-shot rate-limited (1/sec) refresh on unknown kid. Don’t change the kid format without coordinating with the kamaji refresh path.”)
@arch:see(.yah/docs/working/mcp-auth-and-ownership.md)
@yah:depends_on(R020-F9)
@yah:handoff(“LANDED crates/cheers-axum/src/jwks.rs (re-exported from cheers_axum::*): JwksState { platform_keys: Vec> } + PlatformSigningKey { kid, public_key: [u8;32] } + Jwk { kty, crv, x, kid, use } + JwkSet { keys } + router(state) mounting GET /.well-known/jwks.json. DEFAULT_JWKS_MAX_AGE_SECONDS = 300.”)
@yah:handoff(“AUTHORITATIVE SOURCES: platform_keys is a SNAPSHOT the product supplies at startup (no platform-key rotation infra today; when it lands, this list becomes its sink, the publication path stays the same). Service-principal keys flow through ServicePrincipalAuthority::published_signing_keys(now) verbatim — F9’s already-filtered output (Active + Retiring-within-window). NO new filter logic in F11.”)
@yah:handoff(“WIRE SHAPE: RFC 8037/7517 OKP+Ed25519 JWK Set. Each entry is { kty:‘OKP’, crv:‘Ed25519’, x:<base64url-no-pad 32-byte pubkey>, kid:
@yah:ticket(R020-F12, “OIDC discovery: GET /.well-known/openid-configuration”)
@yah:assignee(agent:claude)
@yah:at(2026-06-04T01:36:29Z)
@yah:status(review)
@yah:phase(P4)
@yah:parent(R020)
@yah:next(“Serve issuer, jwks_uri, token_endpoint, scopes_supported (from the Scope enum), grant_types_supported (must include urn:ietf:params:oauth:grant-type:token-exchange + passkey), subject_types_supported: [‘user’, ‘service’, ‘camp’].”)
@yah:next(“scopes_supported must be derived from the typed Scope enum so it can’t drift from what mint actually accepts.”)
@yah:verify(“GET the endpoint and validate the JSON shape against a known good fixture.”)
@yah:verify(“scopes_supported equals the full Scope enum at compile time (regression test that fails if the enum is extended but the discovery doc isn’t).”)
@arch:see(.yah/docs/working/mcp-auth-and-ownership.md)
@yah:depends_on(R020-F3)
@yah:depends_on(R020-F11)
@yah:handoff(“LANDED. NEW MODULE: crates/cheers-axum/src/discovery.rs (re-exported from cheers_axum::*): DiscoveryState { issuer } + DiscoveryState::new(issuer) + OpenIdConfiguration { issuer, jwks_uri, token_endpoint, scopes_supported, grant_types_supported, subject_types_supported } + router(state) mounting GET /.well-known/openid-configuration. Public path constants: OPENID_CONFIGURATION_PATH, JWKS_PATH, TOKEN_ENDPOINT_PATH. Public slice constants: SUBJECT_TYPES_SUPPORTED = [‘user’,‘service’,‘camp’], GRANT_TYPES_SUPPORTED = [‘urn:ietf:params:oauth:grant-type:token-exchange’,‘passkey’].”)
@yah:handoff(“ENUM-DRIVEN SCOPES (the load-bearing invariant): scopes_supported is derived from cheers_core::Scope::ALL — a new pub const on Scope listing every variant. Discovery doc CANNOT drift from what the mint path accepts; they share a single source of truth. ALL is paired with scope_all_is_exhaustive (cheers-core/src/mcp.rs:tests), which uses an exhaustive intra-crate match (Scope is #[non_exhaustive] for external users but fully matchable in cheers-core) — adding a Scope variant without listing it in ALL is either a compile error (missing match arm) or a test failure (per-arm assert).”)
@yah:handoff(“WIRE SHAPE matches the doc §Discovery example verbatim: issuer (product-supplied), jwks_uri =
@yah:ticket(R020-F14, “Audit read endpoint: GET /audit/by-on-behalf-of/<user> (W127 ‘who deployed what’)”)
@yah:status(review)
@yah:assignee(agent:bundle-anthropic-ashguard)
@yah:at(2026-08-13T20:08:34Z)
@yah:phase(P4)
@yah:parent(R020)
@yah:next(“GET /audit/by-on-behalf-of/policy binding that warns on every –all-features test build.”)
@yah:ticket(R020-T16, “Bearer/McpClaims authentication middleware in cheers-axum”)
@yah:assignee(agent:claude)
@yah:at(2026-06-04T18:26:04Z)
@yah:status(review)
@yah:phase(P2)
@yah:parent(R020)
@yah:next(“Add an McpAuthStateclaims.require_scope(Scope::OwnershipWrite)?; — native ergonomics with crate-direction intact.”)
@yah:handoff(“Added RouteError::InsufficientScope { required: Scope } variant (403 / ‘insufficient_scope’). Distinct from Unauthorized (401) by design: the principal authenticated, the call is just not authorized. Required adding cheers_core::Scope to error.rs imports.”)
@yah:handoff(“Confusing-deputy structural guard pinned by authenticate_mcp_rejects_session_token_as_unauthorized — a session-shape token (with ‘cheers’ additional claim) hitting verify_mcp_at is rejected as Unauthorized (the ‘mcp’ additional-claim key is absent). The two PASETO claim shapes can’t be confused even when both have valid Ed25519 signatures from the same key.”)
@yah:handoff(“9 new tests in mcp::tests: authenticate_mcp_verifies_valid_token_and_returns_claims; authenticate_mcp_rejects_expired_as_unauthorized; authenticate_mcp_rejects_bad_signature_as_unauthorized; authenticate_mcp_rejects_malformed_bearer; authenticate_mcp_rejects_missing_bearer; authenticate_mcp_rejects_session_token_as_unauthorized; require_scope_accepts_held_scope; require_scope_rejects_missing_scope; insufficient_scope_responds_403_with_stable_code (pins status + ‘insufficient_scope’ code + IntoResponse path).”)
@yah:handoff(“PasetoV4PublicVerifier is imported via cheers_server’s re-export (cheers_server::PasetoV4PublicVerifier) — cheers-axum’s Cargo.toml already depends on cheers-server, no new direct cheers-verify dep needed. Same pattern as me.rs’s existing EdgeVerifier import.”)
@yah:handoff(“Verified GREEN: cargo test -p cheers-axum (19 unit, up from 10 — 9 new mcp tests), cargo test -p cheers-core (51 + 1 doctest), cargo test -p cheers-server (82 + 9 proptest + 2 doctest), cargo test -p cheers-verify (4), cargo check –workspace –all-features clean.”)
@yah:next(“T17 (POST/DELETE /ownership routes) consumes this directly — OwnershipStateclaims.require_scope(Scope::OwnershipWrite)?; before calling OwnershipStore::insert/revoke_by_id. Set granted_by = claims.sub.to_string() (already in ‘svc:
@arch:see(.yah/docs/working/mcp-auth-and-ownership.md)
@yah:ticket(R020-T17, “Admin HTTP routes for service principals (POST /admin/service-principals + /rotate) + operator auth”)
@yah:assignee(agent:claude)
@yah:at(2026-06-05T02:27:04Z)
@yah:status(review)
@yah:phase(P3)
@yah:parent(R020)
@yah:next(“Add cheers-axum/src/admin.rs: AdminAuthState { edge: Arc<EdgeVerifier<V,Rd>>, authority: Arc<ServicePrincipalAuthority>, operators: Arc>, operators: Arcfn is_operator(&self, &UserId) -> bool) + CreateServicePrincipalBody + ProvisionResponse { principal, signing_key, secret_key_b64 } (Serialize+Deserialize so callers and tests can parse it) + router() mounting POST /admin/service-principals and POST /admin/service-principals/{id}/rotate. Re-exported from cheers_axum::* (lib.rs).”)
@yah:handoff(“ERROR MAPPINGS in cheers-axum/src/error.rs: added RouteError::AlreadyExists(String) -> 409 ‘already_exists’, RouteError::UnknownPrincipal(String) -> 404 ‘unknown_principal’, RouteError::NotOperator -> 403 ‘not_operator’. Plus Fromif !state.operators.is_operator(&claims.sub) { return Err(NotOperator); }. Pinned by negative test: non-operator caller gets 403 AND the principal store is empty afterwards (no side-effect leak).”)
@yah:handoff(“BASE64 IS NOW UNCONDITIONAL in cheers-axum/Cargo.toml (was optional, only on under google/apple/passkey/email features). Dropped dep:base64 from those four feature lists. Reason: admin routes base64url-encode the 64-byte secret_key in the response body — making the dep feature-gated would mean either gating /admin routes too (wrong; admin is core) or duplicating the dep. The 17KB base64 crate is already pulled by every realistic build.”)
@yah:handoff(“TESTS: 9 inline admin::tests using tower::ServiceExt::oneshot against the full Router. Covers (1) provision returns 201 + secret-once + JWKS sees the new pubkey; (2) non-operator -> 403 with no store side-effect; (3) missing bearer -> 401; (4) duplicate desired_id -> 409; (5) rotate retires old + issues fresh + both kids in JWKS during overlap; (6) rotate unknown id -> 404; (7) rotate non-operator -> 403; (8) ProvisionResponse round-trips via real provision (the structs are #[non_exhaustive]); (9) OperatorPolicy is dyn-compatible. tokio::test runtime (NOT pollster::block_on — needed for the existing tokio-tower interop).”)
@yah:handoff(“VERIFIED: cargo test -p cheers-axum admin::tests (9/9 pass), cargo test -p cheers-axum (31/31, up from 22 — 9 new admin tests), cargo test -p cheers-axum –all-features (35 unit + all integration tests pass), cargo test -p cheers-core -p cheers-server -p cheers-verify (parent relay smoke: 51 + 96 + 9 proptest + 4 + doctests, all green), cargo check –workspace –all-features clean.”)
@yah:handoff(“GRANT WIRING STILL NOT DONE (intentional, matches F9 handoff). The route body is just { desired_id }; granting scopes to the new principal is the separate composable layer the F9 handoff calls out. When GrantStore gains a put_grant trait method, extend AdminAuthState with the grants store and add a grants: Vec<Scope|BundleName> body field. Don’t reach for it from the rotate route — rotation is about key material, not authorization.”)
@yah:next(“Sign off T17 — tasks-met, awaiting human review.”)
@yah:next(“Claim R020-T18 (cheers-sqlx PgServicePrincipalStore + Turso path, see [[cheers-sqlx-backends]] memory). Note backend is Postgres + Turso (libSQL), not vanilla SQLite — verify libSQL-compat for any SQLite-only pragmas.”)
@yah:next(“Claim R020-F11 (JWKS extension) — depends on F9, can run in parallel with T18.”)
@yah:next(“Claim R020-F10 (camp bootstrap endpoint) — depends on F2/F4/F9, parallelizable.”)
@yah:relay(R514, “cheers-axum: consolidate 9 integration-test binaries to 1”)
@yah:status(review)
@yah:assignee(agent:bundle-anthropic-ashguard)
@yah:at(2026-08-13T19:07:55Z)
@yah:next(“Set autotests = false under [package] in crates/cheers-axum/Cargo.toml, add [test] name = "main" path = "tests/main.rs", and mod all 9 existing siblings from that root. Files do not move.”)
@yah:next(“Audit first: the 9 now share one address space and libtest’s thread pool. These spin real axum servers and SQLite session stores — check for a listener not bound to port 0 and for a shared on-disk DB path.”)
@yah:verify(“cargo test -p cheers-axum – –list line count identical before/after; three consecutive green runs.”)
@yah:gotcha(“mod alone does NOT reduce the binary count: cargo auto-discovers every top-level tests/*.rs as its own target even when another root declares it a module. autotests = false is the load-bearing part.”)
@yah:gotcha(“tests/common/ is a module dir, not a target — it survives the merge unchanged as an ordinary sibling module.”)
@yah:next(“Part of the parent camp’s W304 / R743 (integration-test consolidation, 146 to 70 binaries). This crate is filed here rather than as R743-T because oss/cheers is its own subcamp — .yah/camp.toml prunes it from the parent camp’s scan set, so a ticket homed here is invisible to the parent board and vice versa. Design + audit checklist: ../../.yah/docs/working/W304-integration-test-consolidation.md”)
@yah:handoff(“LANDED. cheers-axum integration-test targets 9 to 1. Cargo.toml: autotests = false under [package] plus [test] name = "main" path = "tests/main.rs". New tests/main.rs declares mod common plus the nine siblings; no test file moved. Each sibling’s mod common; became use crate::common; (dropped again in audit_basic / enrollment_basic / ownership_basic, which already spell paths as crate::common::… and warned unused). tests/common/ was already a module dir, not a target, and is unchanged.”)
@yah:verify(“cargo test -p cheers-axum –all-features – –list: 120 test names before, 120 after; set diff empty after stripping the module prefix each name now carries. Default-features: 98 to 98, also identical.”)
@yah:verify(“Three consecutive cargo test -p cheers-axum --all-features: 63 lib + 43 integration + 12 doc, 0 failed each run, zero compiler warnings. Neighbour smoke cargo test -p cheers-core -p cheers-server -p cheers-verify -p cheers-axum --all-features all green.”)
@yah:verify(“Binary count confirmed from cargo’s own output: nine Running tests/<name>.rs lines collapsed to one Running tests/main.rs.”)
@yah:gotcha(“Test names now carry a module prefix (audit_basic::foo) because the binary name no longer supplies it. Any --test <old-name> invocation is dead; the equivalent is --test main <old-name>::. One such reference existed repo-wide, R020-F13’s @yah:verify in cheers-server/src/store.rs:59 — retargeted in place, and its replacement command was run (4 passed, 24 filtered out).”)
@yah:gotcha(“Adding a test file now requires a mod line in tests/main.rs. autotests = false means nothing under tests/ is picked up implicitly any more.”)
@yah:assumes(“Merge-safety audit found nothing to fix, so no test was modified beyond the mod-to-use line: no fixed port (the OIDC tests use wiremock::MockServer::start(), ephemeral per instance; everything else drives the router via tower::Service with no listener), no on-disk DB, no env/CWD mutation, no statics. Every store in tests/common/ is in-memory.”)
@yah:cleanup(“Discovered and fixed in the same pass: cheers-sqlx + sqlx were dead dev-deps here. The Cargo.toml comment claimed me_basic backed the session stores with a real SQLite file, but R513-T8 moved that harness to cheers-test-identity and rg sqlx tests/ now finds only prose. Dropped both, which takes sqlx-macros off this crate’s dev build; Cargo.lock loses exactly those two edges.”)
@yah:cleanup(“cargo fmt -p cheers-axum -- --check disagrees with every file in the crate (import ordering, e.g. base64::Engine placement) — pre-existing toolchain style-edition drift, not from this change. Left alone rather than reformatting 26 files across a shared tree.”)
Re-exports§
pub use admin::AdminAuthState;pub use admin::CreateServicePrincipalBody;pub use admin::OperatorPolicy;pub use admin::ProvisionResponse;pub use audit::AuditIngestBody;pub use audit::AuditIngestResponse;pub use audit::AuditPageResponse;pub use audit::AuditReadQuery;pub use audit::AuditState;pub use camps::CampAdminState;pub use camps::CampBootstrapResponse;pub use camps::CreateCampBootstrapBody;pub use cookie::CsrfCookieConfig;pub use enrollment::ENROLLMENT_GRANTED_BY;pub use enrollment::EnrollNodeBody;pub use enrollment::EnrollmentState;pub use enrollment::NODE_RESOURCE_KIND as ENROLLMENT_NODE_RESOURCE_KIND;pub use enrollment::OWNS_RELATIONSHIP as ENROLLMENT_OWNS_RELATIONSHIP;pub use discovery::DiscoveryState;pub use discovery::GRANT_TYPES_SUPPORTED;pub use discovery::OPENID_CONFIGURATION_PATH;pub use discovery::OpenIdConfiguration;pub use discovery::SUBJECT_TYPES_SUPPORTED;pub use error::RouteError;pub use knock::KnockRouteError;pub use knock::KnockState;pub use knock::RateLimiter;pub use jwks::Jwk;pub use jwks::JwkSet;pub use jwks::JwksState;pub use jwks::PlatformSigningKey;pub use jwks::DEFAULT_JWKS_MAX_AGE_SECONDS;pub use mcp::authenticate_mcp;pub use mcp::verify_mcp_bearer;pub use mcp::McpAuthState;pub use mcp::McpClaimsExt;pub use me::MeAuthState;pub use me::NoSessionRecorder;pub use me::SessionDescriptor;pub use me::SessionDirectory;pub use me::SessionListEntry;pub use me::SessionRecorder;pub use ownership::CreateOwnershipBody;pub use ownership::OwnershipState;pub use refresh::router as refresh_router;pub use refresh::DirectoryBindings;pub use refresh::RefreshAuthState;pub use refresh::RefreshBody;pub use revocation_set::router as revocation_set_router;pub use revocation_set::RevocationSetBody;pub use revocation_set::REVOCATION_SET_PATH;pub use session::SessionBody;pub use tokens::authenticate_any;pub use tokens::router as tokens_router;pub use tokens::ApiTokenTrust;pub use tokens::Caller;pub use tokens::CreateTokenBody;pub use tokens::CreatedToken;pub use tokens::Credential;pub use tokens::MeTokensState;pub use tokens::RotateTokenBody;pub use tokens::TokenListEntry;
Modules§
- admin
POST /admin/service-principals+POST /admin/service-principals/{id}/rotate— the operator-only path that mints service-principal Ed25519 keypairs.- apple
- Apple Sign-In routes —
GET /auth/login/apple+POST /auth/callback/apple. - audit
- The centralized audit table’s HTTP surface —
POST /audit/ingest(write) andGET /audit/by-on-behalf-of/{user}(read). - camps
POST /admin/camps/bootstrap— the yubaba-callable path that provisions a camp principal on behalf of a user.- cookie
- CSRF cookie configuration + tiny header-level cookie helpers.
- discovery
GET /.well-known/openid-configuration— OIDC discovery document.- enrollment
POST /enrollment/node+DELETE /enrollment/node/{node_id}— the server-mediated, user-session-authenticated writer (and de-enroller) for the LAN-pair enrollment row (R593-F9, W268 §“The binding: enrollment is an ownership row”).- error
- Route-level errors. Each handler converts a typed
RouteErrorinto a JSON response with a stable code so a frontend can switch on it without string-matching messages. - Google OIDC routes — two ways in, one user row out.
- jwks
GET /.well-known/jwks.json— JSON Web Key Set publication.- knock
- Knock routes (
knock.md; R734-F4) overKnockAuthority. - magic_
link - Magic-link routes —
POST /magic-link/request+GET /magic-link/verify. - mcp
- Bearer-token authentication for MCP-call endpoints.
- me
/me/sessionsroutes — list and revoke the live sessions on the authenticated user’s account.- ownership
POST /ownership+DELETE /ownership/{id}+GET /ownership(by resource, by subject, or by principal within a resource kind,ListOwnershipQuery) — the grant door into cheers’s ownership table (D4,.yah/docs/working/product-scopes-and-authorization.md§D4). A row’s holder is a principal or a subject set (R732-F1).- passkey
- Passkey routes — registration + authentication ceremonies.
- refresh
- Refresh route —
POST /refresh(R730). - revocation_
set GET /.well-known/revocation-set.json— the issuer’s current signed revocation set (R732-F6; noisetable W235 §5.1, C6).- session
- Session JSON response shape returned by
callbackhandlers. - token_
endpoint POST /token— cheers’s one OAuth token endpoint (R731 doc §D6).- tokens
/me/tokensroutes — mint, list, revoke and rotate the authenticated user’s API tokens (PATs).