AirGap architecture (sealed and offline operation)
Verified against commit
942a18727c(2026-09-26). Re-verify with:grep -rln 'AddAirGapPostureReplica\|PostureReplication' src --include=*.cs | grep -v '__Obsoleted\|Tests'andpwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/__Libraries/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.Replication.Tests/StellaOps.AirGap.Policy.Replication.Tests.csproj -Class "*AirGapPostureReplicaTests".src/is the source of truth; where this page and the code disagree, the code wins.
This page describes how Stella Ops represents and enforces sealed (air-gapped) operation at this commit. There is no AirGap service. The predecessor hosts StellaOps.AirGap.Controller (/system/airgap/{status,seal,unseal,verify}) and StellaOps.AirGap.Time (/api/v1/time/*), their airgap schema and their EF Core persistence were withdrawn under SPRINT_20260722_025 OK-5 and are frozen at src/__Obsoleted/AirGap/. Their contract is archived at docs-archive/modules/airgap-controller/.
1. Principles
- Custody is generic, interpretation is local. Platform stores posture documents verbatim and authorizes writes per state class; it never interprets them. Each consumer interprets the document through the shared
StellaOps.AirGap.Policylibrary. - Three answers, not two. A posture read is
Declared,NotDeclaredorUnreadable(AirGapPostureAvailability). Absence is not “unsealed” and a failed read is not a posture value; each caller decides what absence means for it. - Reads survive a Platform outage. Consumers replicate the
environment_statecatalog into their own database and answer posture questions from the replica. - Import is freshness-gated, never blocked outright. A verified OfflineKit carrier is the sanctioned ingress of a sealed estate.
2. Components
| Component | Path | Role |
|---|---|---|
| Environment-state custodian | src/Platform/StellaOps.Platform.WebService/Endpoints/EnvironmentStateEndpoints.cs, Services/EnvironmentStateStore.cs | PUT/GET /api/v1/platform/environment-state/{stateClass}/{scope}; registered classes airgap-seal and time-anchor; an unregistered class cannot be written; documents are capped at 256 KiB |
| Posture interpreter | src/__Libraries/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/Posture/ | AirGapSealDocument, AirGapTimeAnchorDocument, the interpreters, AirGapPostureReading, AirGapTimeAnchorProofVerifier |
| Staleness math | …/StellaOps.AirGap.Policy/Time/ | StalenessBudget, StalenessEvaluation, StalenessCalculator |
| Egress policy | …/StellaOps.AirGap.Policy/ | IEgressPolicy, EgressPolicyMode (Unsealed advisory, Sealed enforced), allow rules; bound from AirGap:Egress by AddAirGapEgressPolicy |
| Egress analyzer | …/StellaOps.AirGap.Policy.Analyzers/ | Roslyn analyzer AIRGAP001 (HttpClientUsageAnalyzer) flags raw HttpClient use that bypasses the egress policy |
| Posture replica | …/StellaOps.AirGap.Policy.Replication/AirGapPostureReplica.cs | AddAirGapPostureReplica; section Catalog:Replication:EnvironmentState (default off) |
| RFC3161 verification | src/OfflineKit/__Libraries/StellaOps.AirGap.Time.Verification/ | Rfc3161Verifier (signature, chain, OCSP/CRL revocation evidence) |
| Carrier envelope and trust material | src/OfflineKit/__Libraries/StellaOps.AirGap.Bundle/ | OfflineKitCarrierManifest/Writer/Signing, TrustProfileLoader, TrustSnapshotImporter, retained manifest models |
| Client-side verification | src/OfflineKit/__Libraries/StellaOps.AirGap.Importer/ | DsseVerifier, RekorOfflineReceiptVerifier, VersionMonotonicityChecker, FileSystemQuarantineService, OfflineVerificationPolicyLoader, Reconciliation/ |
| Enforcement face | src/Policy/StellaOps.Policy.Engine/Endpoints/ | /system/airgap/{seal,unseal,status,verify}, /system/airgap/staleness/*, /system/airgap/notifications/* |
Hosts that compose the posture replica at this commit: Platform, Policy Engine, Notify worker, OfflineKit web, Authority (Airgap/AuthorityPostureReplication.cs), Signals (Hosting/SignalsPostureReplication.cs) and the Vulnerabilities worker (VulnerabilitiesWorkerPostureRegistration.cs).
3. The posture documents
3.1 airgap-seal
AirGapSealDocument (schemaVersion 1): sealed, reason, sealedAt, policyHash, an optional embedded timeAnchor, a default stalenessBudget and optional per-content contentBudgets. stella airgap seal and stella airgap unseal write it (default scope installation, --dry-run, --force, --reason); stella airgap status reads it back.
Write authorization: policy platform.environment_state.airgap-seal.write, satisfied by envstate:airgap-seal:write or the legacy airgap:seal. Read: platform.environment_state.read, satisfied by envstate:read or the legacy airgap:status:read.
3.2 time-anchor
AirGapTimeAnchorDocument (schemaVersion 1, format rfc3161): anchorTime, source, signatureFingerprint, tokenDigest, the proof (the token itself), verifiedAt, revocationChecked and a stalenessBudget. stella airgap time-anchor declare --token <file> --trust-roots <pem> verifies the token with Rfc3161Verifier before it declares the document; stella airgap time-anchor status reads it and, with --trust-roots, re-verifies the stored proof. Write authorization: envstate:time-anchor:write only (no legacy alias).
A consumer does not trust the declared fields on their own: AirGapTimeAnchorProofVerifier re-verifies the stored proof against the public trust bundle registered for the purpose timestamp-authority (Crypto:PublicTrust:CertificateBundles:timestamp-authority). The TimeTokenFormat enum still names Roughtime, but no Roughtime verifier exists at this commit; only RFC3161 tokens are verified.
3.3 Staleness
AirGapPostureReading.EvaluateStaleness(now, contentClass) returns Unknown when no anchor is declared (deliberately distinct from Fresh), Fresh below the warning budget or for a future-dated anchor, Warning at or above warningSeconds, and Breach at or above breachSeconds. The budget is the content-class entry of contentBudgets when present, otherwise the document default (3600 s warning, 7200 s breach). See guides/staleness-and-time.md.
4. Consumers
4.1 OfflineKit import posture gate
OfflineKitImportPostureGate (src/OfflineKit/StellaOps.OfflineKit.WebService/Imports/) reads the posture replica before custody: not declared or unsealed admits, unreadable admits with a logged warning (a replica outage must not strand a sealed estate’s only supply path), sealed requires a trusted time anchor within budget and answers 409 otherwise. The time-anchor trust bundle is mounted on offlinekit-web at /app/etc/offlinekit/time-anchor-trust-roots.pem and bound through appsettings.Production.json (devops/release/bundle/config/etc/offlinekit/, devops/etc/offlinekit/). Detail: ../offlinekit/architecture.md§5.
4.2 Policy Engine enforcement face
The /system/airgap/* paths the withdrawn controller served are mapped today by Policy Engine (SealedModeEndpoints.cs, StalenessEndpoints.cs, AirGapNotificationEndpoints.cs). Its /status answers from the local replica; /seal and /unseal declare posture through Platform; /verify verifies a policy pack or risk-profile bundle and requires airgap:seal. This is Policy Engine’s contract, not the archived controller specification.
4.3 Egress
Hosts register the egress policy with AddAirGapEgressPolicy(configuration, sectionName: "AirGap") or the default section AirGap:Egress. In Sealed mode an outbound request without a matching allow rule raises AirGapEgressBlockedException. Operator-side verification that the container network is closed: guides/sealing-and-egress.md.
5. Offline content exchange
The one supported exchange envelope is the OfflineKit carrier (OfflineKitCarrierManifest, DC-18/DC-21). Export runs write carriers, stella airgap pull --run <id> downloads one and stella airgap import submits it to POST /api/offlinekit/v1/import, which verifies both DSSE envelopes against the operator trust key, applies the posture gate, takes custody and dispatches parts to their owning services. The CLI’s client-side checks (stella offline import --verify-dsse/--verify-rekor, stella verify offline) verify and admit nothing. Withdrawn dialects: the knowledge-snapshot bundle and export-archive validators (src/__Obsoleted/OfflineKit/__Libraries/StellaOps.AirGap.{Bundle,Importer}/) and the HLC job-sync bundle (…/StellaOps.AirGap.Sync/).
The release installer’s offline vulnerability-data seed (devops/release/bundle/install.sh, step 8b; SPRINT_20260925_004 DAY-7) does not use this path: it posts the seed directly to the Vulnerabilities hub’s owner route, authenticating as the stellaops-offlinekit-activator client.
6. Trust material shipped offline
- Import trust key: operator public key mounted on
offlinekit-webat/app/etc/offlinekit/import-trust.pem(OfflineKit:Import:TrustedPublicKeyPath). - Time-anchor trust: timestamp-authority PEM chain, see §4.1.
- Plugin trust roots:
PemDirectoryPluginTrustRoot(src/Plugin/StellaOps.Plugin.Host/Trust/) loads every*.pem/*.crtin its bundle directory at construction; the bundle ships with the offline kit, so rotating it requires a kit refresh and a host restart. - Trust profiles and trust snapshots for the CLI:
TrustProfileLoader,TrustSnapshotImporterinStellaOps.AirGap.Bundle.
7. Observability
StellaOps.AirGap.Importer/Telemetry/OfflineKitMetrics.cs defines meter StellaOps.AirGap.Importer (offlinekit_import_total, attestation-verify latency and related instruments). At this commit no production host outside the library references it. The controller’s airgap_seal_total / airgap_time_anchor_age_seconds instruments were withdrawn with the host. OfflineKit’s Doctor checks (doctor.offlinekit.posture.not-stored-here and others) are listed in ../offlinekit/architecture.md§2.
8. Tests
- Posture replica against real PostgreSQL:
src/__Libraries/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.Replication.Tests/. - Posture and egress interpretation:
…/StellaOps.AirGap.Policy.Tests/, analyzer:…/StellaOps.AirGap.Policy.Analyzers.Tests/. - RFC3161 verification:
src/OfflineKit/__Tests/StellaOps.AirGap.Time.Verification.Tests/. - Envelope and client-side verification:
src/OfflineKit/__Tests/StellaOps.AirGap.Bundle.Tests/,…/StellaOps.AirGap.Importer.Tests/,…/StellaOps.AirGapFederation.IntegrationTests/.
9. Design record
- Consolidation design (frozen): §6 posture map and the owner ruling A2 that left custody with Platform.
- Withdrawn source and freeze records:
src/__Obsoleted/AirGap/,src/__Obsoleted/OfflineKit/.
