Air-Gapped Time Anchors and Staleness Budgets

Audience: operators of sealed estates and engineers consuming posture.

Verified against commit 942a18727c (2026-09-26): src/__Libraries/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/Posture/, src/OfflineKit/__Libraries/StellaOps.AirGap.Time.Verification/, src/Cli/StellaOps.Cli/Commands/CommandHandlers.AirGap.TimeAnchor.cs.

A sealed estate cannot ask an external clock what time it is. Stella Ops instead records a trusted time anchor, an RFC3161 timestamp token verified against a timestamp-authority trust chain, and measures the age of that anchor against staleness budgets.

1. Where the anchor lives

The anchor is an environment-state document of class time-anchor held by Platform (PUT/GET /api/v1/platform/environment-state/time-anchor/{scope}). The document (AirGapTimeAnchorDocument, schemaVersion 1, format rfc3161) carries anchorTime, source, signatureFingerprint, tokenDigest, the token itself as proof, verifiedAt, revocationChecked and a stalenessBudget. A seal document (airgap-seal) can also carry an embedded timeAnchor together with a default budget and per-content contentBudgets.

Writing a time-anchor document requires envstate:time-anchor:write; there is no legacy alias.

2. Declaring and inspecting the anchor

# Verify the token locally, then declare it (default scope: installation)
stella airgap time-anchor declare --token anchor.tsr --trust-roots tsa-chain.pem [--source <label>] [--dry-run] [--json]

# Read the declared anchor; with --trust-roots, re-verify the stored proof
stella airgap time-anchor status [--trust-roots tsa-chain.pem] [--output json]

Verification uses Rfc3161Verifier: the CMS signature, the signer chain against the supplied roots, and revocation evidence (OCSP responses or CRLs) where provided. The TimeTokenFormat enum still names Roughtime, but no Roughtime verifier exists at this commit.

Consumers never trust the declared fields alone. AirGapTimeAnchorProofVerifier re-verifies the stored proof against the public trust bundle registered for the purpose timestamp-authority (Crypto:PublicTrust:CertificateBundles:timestamp-authority). On offlinekit-web that bundle is the PEM chain mounted at /app/etc/offlinekit/time-anchor-trust-roots.pem and bound through appsettings.Production.json.

3. Staleness evaluation

AirGapPostureReading.EvaluateStaleness(now, contentClass):

VerdictCondition
Unknownposture not declared or unreadable, or no anchor declared; deliberately not Fresh
Freshanchor age below warningSeconds, or the anchor is future-dated
Warningage at or above warningSeconds and below breachSeconds
Breachage at or above breachSeconds

The budget is the contentBudgets[contentClass] entry when present, otherwise the document’s default stalenessBudget (3600 s warning, 7200 s breach when not declared).

4. Where staleness is enforced

5. Operator notes