gleamunison/trust
Trust lifecycle for signers: rotation windows, expiry, revocation.
This module owns ONE decision: does this evidence count, right now? A signature is necessary but not sufficient — the key that made it must be inside its validity window, must not be revoked, and the attestation itself must not be older than the policy allows. evidence.gleam owns the record and the HMAC; believing it is this module’s job, and nobody else’s.
Types
pub type KeyState {
Active(since: Int, until: Int)
Revoked
}
Constructors
-
Active(since: Int, until: Int)Usable from
sinceuntiluntil(exclusive). A rotation overlap is two Active entries: old withuntil = T, new withsince = T - overlap. -
RevokedPermanently untrusted, regardless of timestamps.
How evidence is judged. One type, two variants, and the production constructor can only ever produce the first.
pub type TrustPolicy {
RequireKeys(keys: List(TrustedKey), max_age_secs: Int)
FixturesOnly
}
Constructors
-
RequireKeys(keys: List(TrustedKey), max_age_secs: Int)Production: a key whose state admits the record’s timestamp, plus a maximum attestation age in seconds (0 = no limit).
-
FixturesOnlyFixtures only: an in-memory store holding unsigned records. Never produced by production_policy, so no CLI path can reach it.
pub type TrustRefusal {
UnknownSigner(id: String)
KeyRevoked(id: String)
KeyNotYetValid(id: String, since: Int)
KeyExpired(id: String, until: Int)
SignatureMismatch(id: String)
UnsignedRecord
AttestationTooOld(kind: String, ts: Int, max_age_secs: Int)
EvidenceReadFailed
NoRecord(kind: String)
NoPassingVerdict(kind: String, verdict: String)
}
Constructors
-
UnknownSigner(id: String) -
KeyRevoked(id: String) -
KeyNotYetValid(id: String, since: Int) -
KeyExpired(id: String, until: Int) -
SignatureMismatch(id: String) -
UnsignedRecord -
AttestationTooOld(kind: String, ts: Int, max_age_secs: Int) -
EvidenceReadFailed -
NoRecord(kind: String) -
NoPassingVerdict(kind: String, verdict: String)
pub type TrustedKey {
TrustedKey(signer: evidence.Signer, state: KeyState)
}
Constructors
-
TrustedKey(signer: evidence.Signer, state: KeyState)
Values
pub fn active(
signer: evidence.Signer,
since: Int,
until: Int,
) -> TrustedKey
pub fn admit(
policy: TrustPolicy,
record: evidence.Evidence,
) -> Result(evidence.Evidence, TrustRefusal)
Full admission at the current time.
pub fn admits(
adapter: storage.StorageAdapter,
subject: String,
kind: evidence.Kind,
policy: TrustPolicy,
) -> Bool
Bool form for callers that only branch on the answer. The gate paths use
verified so they can report the refusal.
pub fn describe(policy: TrustPolicy) -> String
What the policy actually enforces, for a report an operator can read.
pub fn policy(keys: List(TrustedKey)) -> TrustPolicy
pub fn production_policy() -> Result(TrustPolicy, String)
The production policy: the env signer plus the window, revocation flag and attestation age it is bound to. Absent variables mean no limit, so an existing store keeps verifying; setting them is how a key is rotated or retired without touching a single evidence record.
pub fn refusal_name(refusal: TrustRefusal) -> String
A stable machine-readable name. Exit codes tell a script what happened; this tells a human why, and both come from the same decision.
pub fn revoked(signer: evidence.Signer) -> TrustedKey
pub fn verified(
adapter: storage.StorageAdapter,
subject: String,
kind: evidence.Kind,
policy: TrustPolicy,
) -> Result(Nil, TrustRefusal)
Is there evidence of this kind for this subject that the policy admits? Returns the refusal rather than a bare false: “expired key” and “never attested” are different facts and an operator acts on them differently.
pub fn verified_at(
adapter: storage.StorageAdapter,
subject: String,
kind: evidence.Kind,
policy: TrustPolicy,
now: Int,
) -> Result(Nil, TrustRefusal)
pub fn verified_in(
snapshot: evidence.Snapshot,
subject: String,
kind: evidence.Kind,
policy: TrustPolicy,
) -> Result(Nil, TrustRefusal)