Guidance for AI assistants working in this repository. Read this first; the
guides under docs/ carry the depth.
An OAuth 2.1-only authorization server in Rust. Durable domain state is in
PostgreSQL; all ephemeral state is in Redis. The crate is one library
(src/lib.rs) plus a thin binary (src/main.rs). No HTTP admin API; the CLI is
the admin surface.
| Add a... | To this module | Notes |
|---|---|---|
| Configuration key or validation rule | src/config.rs, src/config/ |
Add to the raw serde struct, then validate in the matching submodule. Update docs/CONFIGURATION.md. |
| Key-ring behavior | src/keys.rs |
Versioned ids only; never print key material. |
| Crypto primitive (hash, compare, PKCE, TOTP) | src/crypto/ |
Constant-time for secret compares. |
| PII field codec / blind index | src/crypto/field.rs |
Versioned AES-256-GCM; never log plaintext or key material. |
JOSE verification (DPoP, JAR, private_key_jwt) |
src/jose/ |
Reject fail-closed with the exact error code. |
| Postgres query or repository | src/db/ |
query!/query_as! macros; regenerate .sqlx. |
| Redis record or Lua script | src/store/ |
Keep every key under {state}; update docs/REDIS.md. |
| OAuth endpoint or grant | src/protocol/ |
Cite the RFC section in a comment. Update docs/OAUTH.md. |
| Login, consent, or device page | src/ui/ |
Keep CSP and CSRF intact. |
| Cross-cutting HTTP concern | src/middleware/ |
Mind the layer order in src/routes.rs. |
| Endpoint or layer order | src/routes.rs |
Update docs/ARCHITECTURE.md. |
| Listener or shutdown behavior | src/serve.rs, src/tls.rs |
Update docs/DEPLOYMENT.md. |
| Health or topology behavior | src/state/ |
Readiness is fail-closed. |
| CLI command or flag | src/cli.rs, src/cli/ |
Match the flag exactly in docs/OPERATIONS.md. |
| Error code or HTTP mapping | src/error.rs |
Add the variant and its status together. |
If a change spans modules, put the logic in the module that owns the behavior and call into it from the others.
rustfmt.toml).#![forbid(unsafe_code)] is on the crate. Never add unsafe.unwrap, expect, or panic in request paths. Clippy denies them.thiserror. Map a dependency failure to the right
client-safe error, do not stringify it.tokio and axum idioms: async handlers, tower layers, no blocking
work on the runtime thread.secrecy and zeroize. A secret-holding type must not print the
secret in Debug.subtle) for any secret or token compare.main.rs.StdExternalCrate.Before finishing any change, confirm:
503, never a
fail-open success and never a 500 or panic.private_key_jwt jti replay markers are written with SET NX.S256 only; plain is rejected; a missing verifier cannot
downgrade.private_key_jwt is treated as authentication only, never as a binding.Red, green, refactor. Write the failing test first and confirm it fails for the right reason.
cargo nextest run -E 'test(<behavior_name>)' # red, then green
Integration tests use the real shared Postgres and Redis from
compose.test.yaml (docker compose -f compose.test.yaml up -d: Postgres on
127.0.0.1:5433, Redis on 127.0.0.1:6380; override with
D_OAUTH_TEST_POSTGRES_URL and D_OAUTH_TEST_REDIS_URL). They get per-test
isolation from a fresh database and unique Redis key prefixes. Testcontainers
remains only for unit-test helpers under src/ and for tests/resilience.rs,
which stops and restarts containers; tests/topology.rs uses in-process
redis-test fixtures that need a redis-server binary. Prefer a real object or
an in-memory fake over a mock. Assert the observable contract. Tear the services
down with docker compose -f compose.test.yaml down -v.
Quality gates for any change:
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo nextest run --all-targets
cargo test --doc
python3 scripts/check_doc_links.py
When a query or migration is added or changed, regenerate and commit the offline cache:
cargo sqlx prepare --workspace
cargo nextest run -E 'test(admin_bootstrap)'.
Never the full suite, and never repo-wide setup.cargo nextest run --all-targets followed by cargo test --doc.Documentation is part of the change, not a follow-up. Any behavior change updates the relevant guide in the same commit:
docs/OAUTH.md.docs/SECURITY.md.docs/ARCHITECTURE.md.docs/CONFIGURATION.md.docs/DATABASE.md.docs/REDIS.md.docs/DEPLOYMENT.md.docs/OPERATIONS.md, RUNBOOK.md.A change to the deployment surface (a new listener, mount, dependency class, or
runtime setting) updates the attack-surface section in docs/SECURITY.md.
A command in a doc must match the real CLI. Then run the link check:
python3 scripts/check_doc_links.py
The CI doc-links job runs the same check, so a stale file or heading
reference fails the build.