ijima_core/revocation.rs
1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Token revocation — the kill-switch for issued grant tokens.
5//!
6//! Schubert `GrantToken`s are stateless Ed25519 signatures: valid until
7//! the heat death of the key. For a long-lived multi-principal deployment
8//! that is unacceptable — a leaked bearer must be killable *now*, not at
9//! the next issuer-key rotation.
10//!
11//! Ijima's answer is a store-backed **revocation list**: the daemon keeps
12//! an in-memory hash set (hydrated from the store at boot, appended via
13//! the admin route) and rejects any bearer whose SHA-256 hash is a
14//! member — checked right after the cryptographic verify, so a revoked
15//! token is exactly as dead as a bad-signature token.
16//!
17//! **Why a hash, not the token?** Revocation entries may be inspected by
18//! operators or synced to satellites; storing raw bearers would leak
19//! live credentials into logs/backups. The SHA-256 of the bearer leaks
20//! nothing usable.
21//!
22//! **Why not expiry?** Token-carried expiry belongs in Schubert
23//! (`GrantToken` fields + verify-time check, requested for Schubert 0.5).
24//! Revocation and expiry are complementary: expiry handles routine
25//! deprovisioning; revocation handles incidents. See
26//! `docs/adr/token-revocation.md`.
27
28#[cfg(feature = "serde")]
29use serde::{Deserialize, Serialize};
30
31/// A revoked grant token, identified by the SHA-256 hex of its bearer
32/// string.
33#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35pub struct TokenRevocation {
36 /// SHA-256 hex digest of the revoked bearer token (primary key).
37 pub token_hash: String,
38 /// When the revocation was recorded (unix seconds).
39 pub revoked_at_unix: u64,
40 /// Operator note — e.g. `"leaked in CI log"` (optional).
41 pub reason: Option<String>,
42}