# gatekeep
> The soul selects her own society,
> Then shuts the door;
>
> — Emily Dickinson, "Exclusion" (1890)
`gatekeep` is a code-first authorization engine for Rust. Policies are ordinary
Rust values, a pure deterministic core evaluates them, and every decision
carries the reasons that produced it.
It is the sibling of [`keepsake`](../keepsake-rs): keepsake keeps relation
lifecycle state — entitlements, holds, sanctions, risk flags, gates — and
gatekeep decides what those facts permit. The two compose but stay independent crates.
## Where it fits
Use gatekeep for an in-process authorization boundary authored in Rust, by the
team that owns the application. Policies are composable combinators over a
frozen set of facts, evaluated synchronously with no IO, so a decision replays
exactly from its inputs.
It is not a policy DSL, an authentication or session layer, or a network policy
service; those stay with the application or with crates built for them. Because
each policy is reified as inspectable data, gatekeep can serialize, hash, diff,
and explain a decision. It can also answer "which resources can this principal
reach?", not just "may this principal reach this one?".
## Usage
```rust
use gatekeep::{
condition, evaluate, policy, DecisiveClause, Effect, Fact, GatekeepResult, KnownFacts,
Lattice, ReasonCode, StaticFactId,
};
// Outcome grade: how much of a record the caller may read.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
enum ReadAccess {
Redacted,
Full,
}
impl Lattice for ReadAccess {
fn meet(&self, other: &Self) -> Self { (*self).min(*other) }
fn join(&self, other: &Self) -> Self { (*self).max(*other) }
fn top() -> Self { Self::Full }
fn bottom() -> Self { Self::Redacted }
}
// A fact the application resolves before evaluation.
struct CaseOwner;
impl Fact for CaseOwner {
const ID: StaticFactId = StaticFactId::new("case_owner");
}
fn read_access() -> GatekeepResult<()> {
// "The case owner may read the full record."
let owner_full_read = policy::grant(ReadAccess::Full, condition::has::<CaseOwner>())
.try_labeled("owner_full_read")?
.try_reason("not_case_owner")?;
// The owner is permitted, with the granted grade carried on the effect.
let permitted = evaluate(&owner_full_read, &KnownFacts::new().with_present::<CaseOwner>());
assert_eq!(permitted.effect, Effect::Permit(ReadAccess::Full));
// A non-owner is denied, and the decision explains itself instead of
// returning a bare "no": the facts that were missing and a stable reason
// code your UI or audit log can map to a message.
let denied = evaluate(&owner_full_read, &KnownFacts::new());
assert_eq!(denied.effect, Effect::Deny);
if let DecisiveClause::Deny { reason, unsatisfied, .. } = &denied.trace.decisive {
assert_eq!(reason.as_ref().map(ReasonCode::as_str), Some("not_case_owner"));
assert_eq!(unsatisfied.len(), 1); // the missing case_owner fact
}
Ok(())
}
```
Partial evaluation reuses the same policy value with `PartialFacts`: mark
request-known facts as present or absent, leave resource-level facts unknown,
then lower the residual policy in an application-owned adapter. For Postgres list
queries, `gatekeep-sqlx` maps residual facts to trusted row predicates and
appends a lowered filter and grade projection to a `sqlx::QueryBuilder`.
See [`docs/SPEC.md`](docs/SPEC.md) for the lowering walkthrough, or the
[`axum-authorized-list`](examples/axum-authorized-list) and
[`axum-keepsake-authorized-list`](examples/axum-keepsake-authorized-list)
examples for request facts resolved in-process and from keepsake.
## Why it exists
The Rust authz ecosystem leans on external DSLs. A policy DSL is worth its
overhead across many services and for non-engineer authors; for a single Rust
service it mostly adds cost: a second language, the domain re-encoded as entities
and attributes, and typos that fail at runtime instead of compile time. gatekeep
keeps policies in Rust and reifies them as data, so they stay analyzable.
## License
Licensed under either of:
- Apache License, Version 2.0
- MIT license
at your option.