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 since until until (exclusive). A rotation overlap is two Active entries: old with until = T, new with since = T - overlap.

  • Revoked

    Permanently 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).

  • FixturesOnly

    Fixtures 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

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 keys_admit(
  keys: List(TrustedKey),
  signer_id: String,
  ts: Int,
) -> Result(TrustedKey, TrustRefusal)

The pure key-state decision: does a signer’s key, in its current state, admit a timestamp? No signature, no freshness — just the window. Public for downstream consumers deciding on bare key state (replica-author admission); the production path composes this inside admit on Evidence.

pub const never: Int

No expiry: an Active key with until = never is valid until revoked.

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)
✨ Search Document