Architecture Decision Record (direction, not a now-task). Records where write/admin capability belongs in the stack, so future work does not push it into the verify/decision layers.
Decision
If and when write / administrative capability is needed, it lives in a separate repository auth.admin — a pluggable, optional management API (UI optional) for identity and permission administration. auth.provider stays verify-only; auth.policy-verifier stays decision-only. Neither gains user-CRUD / grant-authoring / lifecycle write.
Motivation
The verify-only boundary (see auth.provider #296 / #297 / #301, and the adapter-surface epic auth.provider #305) deliberately keeps identity lifecycle and permission authoring out of the authentication/decision layers. Those write concerns still need a home. Bolting them onto the provider or the verifier would break the separation of concerns that the whole stack is built on. A dedicated write plane keeps each component single-responsibility and independently deployable.
This mirrors the established split we already resemble (Ory): identity management (Kratos) / OAuth (Hydra) / permission management (Keto) / enforcement (Oathkeeper), each exposing an admin API on a separate trust boundary from its public API.
Resulting stack (write plane added)
| Plane |
Repo |
Responsibility |
| Administration / write (new, future) |
auth.admin |
Identity CRUD, credential/password writes, email/phone verification-state transitions, account state machine, device registration + device→user linking, anonymous→registered upgrade, MFA enrollment management, and permission authoring (role / scope / ABAC-attribute grant management). UI optional (admin console + end-user self-service signup/reset/verify screens). |
| Authentication & token issuance |
auth.provider |
verify-only + issuance + issuer/subject/claims binding |
| Authorization decision |
auth.policy-verifier |
decision-only (evaluates; does not own grants) |
| Authorization enforcement |
protobuf.interceptors |
gRPC/protobuf interceptors |
| Stores |
operator-owned |
written by auth.admin, read by auth.provider (identity) and auth.policy-verifier (grants/attributes) |
Design guardrails (keep the stack coherent)
- Separate trust boundary. The admin API is privileged (creates users, grants permissions). It MUST NOT share the public surface of the provider's token endpoint — internal-only, strong caller auth (mTLS / service credential), fully audited. (Do not repeat the
0.0.0.0 + no-caller-auth mistake — see auth.policy-verifier #108.)
- Shared Store contract, not shared code.
auth.admin writes the same schema that auth.provider's UserRepository and auth.policy-verifier's collectors read. The integration point is the Store schema, not a library dependency — so repos stay independently deployable (consistent with the stack's no-cross-repo-atomic-commit constraint).
auth.admin is itself pluggable / optional. Operators may adopt it wholesale or replace it with their own IAM/admin system. Same philosophy as the Store adapters (auth.provider #305).
- Authoring here, decision in the verifier. "Who has which role/scope/attribute" is a write owned by
auth.admin; "allow/deny" is evaluation owned by auth.policy-verifier. Separate administer from decide.
- UI belongs to this plane. Admin console and end-user self-service (signup / reset / verify screens) live here (or are built by the operator against this API) — resolving the provider's intentional "no UI" gap without polluting the token layer.
What this resolves from the review + follow-up
- Signup, password-reset write, email-verification state, device registration, device→user linking, anonymous→registered upgrade →
auth.admin + Stores.
- Permission/grant authoring →
auth.admin; auth.policy-verifier stays decision-only.
- "No UI" →
auth.admin (optional).
auth.provider retains only: verify, issuance, device-login entry point (auth.provider #301), post-credential-change revocation hook (auth.provider #296 sliver), and Store-owned state → claim surfacing (auth.provider #297 sliver).
Status
Direction recorded. No implementation implied yet. Revisit when write/admin capability is actually scheduled; at that point, spin up auth.admin and expand this into a component spec.
Decision
If and when write / administrative capability is needed, it lives in a separate repository
auth.admin— a pluggable, optional management API (UI optional) for identity and permission administration.auth.providerstays verify-only;auth.policy-verifierstays decision-only. Neither gains user-CRUD / grant-authoring / lifecycle write.Motivation
The verify-only boundary (see auth.provider #296 / #297 / #301, and the adapter-surface epic auth.provider #305) deliberately keeps identity lifecycle and permission authoring out of the authentication/decision layers. Those write concerns still need a home. Bolting them onto the provider or the verifier would break the separation of concerns that the whole stack is built on. A dedicated write plane keeps each component single-responsibility and independently deployable.
This mirrors the established split we already resemble (Ory): identity management (Kratos) / OAuth (Hydra) / permission management (Keto) / enforcement (Oathkeeper), each exposing an admin API on a separate trust boundary from its public API.
Resulting stack (write plane added)
auth.adminauth.providerauth.policy-verifierprotobuf.interceptorsauth.admin, read byauth.provider(identity) andauth.policy-verifier(grants/attributes)Design guardrails (keep the stack coherent)
0.0.0.0+ no-caller-auth mistake — see auth.policy-verifier #108.)auth.adminwrites the same schema thatauth.provider'sUserRepositoryandauth.policy-verifier's collectors read. The integration point is the Store schema, not a library dependency — so repos stay independently deployable (consistent with the stack'sno-cross-repo-atomic-commitconstraint).auth.adminis itself pluggable / optional. Operators may adopt it wholesale or replace it with their own IAM/admin system. Same philosophy as the Store adapters (auth.provider #305).auth.admin; "allow/deny" is evaluation owned byauth.policy-verifier. Separate administer from decide.What this resolves from the review + follow-up
auth.admin+ Stores.auth.admin;auth.policy-verifierstays decision-only.auth.admin(optional).auth.providerretains only: verify, issuance, device-login entry point (auth.provider #301), post-credential-change revocation hook (auth.provider #296 sliver), and Store-owned state → claim surfacing (auth.provider #297 sliver).Status
Direction recorded. No implementation implied yet. Revisit when write/admin capability is actually scheduled; at that point, spin up
auth.adminand expand this into a component spec.