Offline Deployment
Offline deployment mode enforces a fully air-gapped runtime: every registered provider, primary route, fallback route, and default provider must target a ProviderTrustZone.LOCAL trust zone. The builder rejects any non-local provider at build time, before performing artifact verification or registry lookups.
Module: tramai-sovereign — SovereignDeploymentMode.OFFLINE
What
The offline deployment system is built on four layers:
SovereignDeploymentMode.OFFLINE— an enum value that triggers strict local-only validation during builder assemblyvalidateOfflineDeployment()— called duringSovereignTramai.Builder.build()before any registry lookup or artifact verification- Zero-egress verification — a harness that builds the runtime, invokes a loopback provider, and probes external TCP/DNS connectivity
- Evidence pack integration — zero-egress probe results can be embedded in the deployment evidence pack via
ZeroEgressEvidenceV1
What OFFLINE Enforces
When SovereignProfileConfiguration.deploymentMode == SovereignDeploymentMode.OFFLINE, the builder validates every route:
| Check | Condition | Error Code |
|---|---|---|
| Registered provider | Must be LOCAL trust zone | offline-profile-non-local-provider-rejected |
| Primary route target | Must be LOCAL trust zone | offline-profile-non-local-primary-route-rejected |
| Fallback route target | Must be LOCAL trust zone | offline-profile-non-local-fallback-rejected |
| Default provider | Must be LOCAL trust zone | offline-profile-non-local-default-provider-rejected |
What OFFLINE Does NOT Enforce
The SovereignDeploymentMode enum does not claim to enforce infrastructure-level network isolation. Production offline deployments still require:
- Firewall rules
- Container network policies (Kubernetes
NetworkPolicy) - Reverse proxy restrictions
- Sandbox or jail enforcement
- Physical air-gap controls where required
- Docker
--network=nonefor container-level isolation
Verification Order
Offline validation occurs before artifact verification and registry lookup. The builder calls validateOfflineDeployment() — if it fails, the registry is never accessed. This means a registry that throws on access is safe — offline validation catches the misconfiguration first:
// Internal builder flow:
fun build(): SovereignTramai {
// ... required-input validation ...
validateOfflineDeployment(profile) // Step 1: offline check
val verificationReceipts = verifyLocalModelArtifacts(profile, modelRegistry) // Step 2: artifact check
// ...
}
When to Use
Use offline deployment when:
- Your deployment must never egress to the public internet
- All model providers run on local machines (Ollama, vLLM, llama.cpp, etc.)
- You need build-time rejection of any non-local provider configuration
- You need zero-egress attestation — proof that the runtime cannot call external services
- You are deploying in air-gapped environments (classified networks, critical infrastructure, disconnected sites)
- You process classified data that must never leave the local trust boundary
Do not use offline deployment when:
- You use any cloud-based model provider (OpenAI, Anthropic, etc.)
- You need fallback routes to cloud providers
- Your local providers have dependencies on external services
- You need EU or global cloud trust zones
Quickstart
Kotlin
// 1. Define an offline profile
val profile = SovereignProfileConfiguration(
allowedModels = setOf("llama3.2"),
allowedProviders = setOf("ollama"),
providerZones = mapOf("ollama" to ProviderTrustZone.LOCAL),
deploymentMode = SovereignDeploymentMode.OFFLINE,
)
// 2. Build — will fail at build time if any non-LOCAL provider is configured
val tramai = SovereignTramai.builder()
.profile(profile)
.modelRegistry(localModelRegistry)
.auditStore(auditStore)
.provider(localProvider, name = "ollama", default = true)
.model("llama3.2", "ollama")
.build()
Rejected Configurations
// All of these fail at build time with specific error codes:
// Non-LOCAL registered provider — throws "offline-profile-non-local-provider-rejected"
val cloudProfile = SovereignProfileConfiguration(
allowedModels = setOf("test-model"),
allowedProviders = setOf("cloud-provider"),
providerZones = mapOf("cloud-provider" to ProviderTrustZone.GLOBAL_CLOUD),
deploymentMode = SovereignDeploymentMode.OFFLINE,
)
// Cloud fallback route — throws "offline-profile-non-local-provider-rejected"
// (the registered-provider check fires first, catching the cloud fallback
// provider as a registered non-LOCAL provider)
val fallbackProfile = SovereignProfileConfiguration(
allowedModels = setOf("test-model", "fallback-model"),
allowedProviders = setOf("local-provider", "cloud-provider"),
allowedFallbackProviders = setOf("cloud-provider"),
providerZones = mapOf(
"local-provider" to ProviderTrustZone.LOCAL,
"cloud-provider" to ProviderTrustZone.GLOBAL_CLOUD,
),
deploymentMode = SovereignDeploymentMode.OFFLINE,
)
Validation Reference
The validateOfflineDeployment() method in SovereignTramai.Builder performs these checks:
// Inside SovereignTramai.Builder.validateOfflineDeployment():
// 1. Registered providers — every registered provider must be LOCAL
for (providerName in registeredProviders) {
require(profile.providerZones.getValue(providerName) == ProviderTrustZone.LOCAL) {
"offline-profile-non-local-provider-rejected"
}
}
// 2. Primary routes — every primary route target must be LOCAL
for ((_, providerName) in primaryModelRoutes) {
require(profile.providerZones.getValue(providerName) == ProviderTrustZone.LOCAL) {
"offline-profile-non-local-primary-route-rejected"
}
}
// 3. Fallback routes — every fallback target must be LOCAL
for (fallback in fallbackRoutes) {
require(profile.providerZones.getValue(fallback.providerName) == ProviderTrustZone.LOCAL) {
"offline-profile-non-local-fallback-rejected"
}
}
// 4. Default provider — must be LOCAL if set
defaultProviderName?.let { providerName ->
require(profile.providerZones.getValue(providerName) == ProviderTrustZone.LOCAL) {
"offline-profile-non-local-default-provider-rejected"
}
}
The registered-provider loop runs first. When a cloud provider is registered as both a provider and a fallback or default provider, the offline-profile-non-local-provider-rejected error fires before the fallback or default checks can be reached.
Zero-Egress Verification Harness
The zero-egress verification harness tests that an offline deployment cannot call external services:
// The harness:
// 1. Builds the sovereign runtime in OFFLINE mode
// 2. Invokes the loopback provider (a provider that returns fixed responses)
// 3. Probes external TCP connectivity (e.g., 1.1.1.1:443)
// 4. Probes external DNS resolution (e.g., example.com)
// 5. Records all results in ZeroEgressEvidenceV1
Loopback Provider
A simple loopback provider for CI verification:
class LoopbackProvider : ModelProvider {
override suspend fun complete(request: ModelRequest): ModelResponse =
ModelResponse(content = "Loopback response for ${request.model}")
override fun providerId(): String = "loopback"
}
Verification Report
Results are captured in ZeroEgressEvidenceV1:
val zeroEgress = ZeroEgressEvidenceV1(
deploymentMode = "OFFLINE",
runtimeBuildSucceeded = true,
loopbackProviderInvocationSucceeded = true,
loopbackProviderInvocationCount = 3,
externalTcpProbeBlocked = true,
externalDnsProbeBlocked = true,
)
Docker-Based CI Harness
For CI pipelines, test offline deployment with Docker --network=none:
# .github/workflows/offline-verification.yml
steps:
- name: Build verification image
run: |
docker build -t sovereign-offline-test -f Dockerfile.offline-test .
- name: Run offline verification
run: |
docker run --network=none \
-v $PWD/build/evidence:/evidence \
sovereign-offline-test
- name: Validate evidence report
run: |
python validate_offline_report.py build/evidence/zero-egress.json
Python Report Validation
import json
with open("build/evidence/zero-egress.json") as f:
report = json.load(f)
assert report["deploymentMode"] == "OFFLINE"
assert report["runtimeBuildSucceeded"] == True
assert report["loopbackProviderInvocationSucceeded"] == True
assert report["externalTcpProbeBlocked"] == True
assert report["externalDnsProbeBlocked"] == True
print("Zero-egress verification passed")
Evidence Pack Integration
Zero-egress probe results can be embedded in the deployment evidence pack:
val tramai = SovereignTramai.builder()
.profile(profile)
.modelRegistry(registry)
.auditStore(auditStore)
.provider(loopbackProvider, name = "loopback", default = true)
.model("test-model", "loopback")
.modelArtifactVerifier(verifier)
.modelArtifactVerificationSettings(ModelArtifactVerificationSettings(enabled = true))
.build()
val pack = tramai.evidencePack(
zeroEgress = zeroEgressEvidence,
)
SovereignEvidencePackWriter.write(pack, Paths.get("evidence/sovereign-evidence.json"))
Standalone Test Reference (from tests)
The offline deployment test suite (SovereignOfflineDeploymentTest) proves these behaviors:
| Test | Expected Behavior |
|---|---|
OFFLINE with only LOCAL provider builds | SovereignTramai is created successfully |
STANDARD with LOCAL provider builds | Works in both modes |
STANDARD with EU_CLOUD provider builds | Cloud providers work in STANDARD mode |
STANDARD with GLOBAL_CLOUD provider builds | Cloud providers work in STANDARD mode |
OFFLINE with EU_CLOUD registered provider rejects | Throws IllegalArgumentException("offline-profile-non-local-provider-rejected") |
OFFLINE with GLOBAL_CLOUD registered provider rejects | Same error |
OFFLINE with local primary route and local fallback builds | Valid offline config with fallback |
OFFLINE with cloud fallback route rejects | Registered-provider check catches the cloud fallback |
OFFLINE with cloud default provider rejects | Same — registered-provider check fires first |
offline validation occurs before artifact registry lookup | A throwing registry is never called — offline check rejects first |
offline valid local route with verification enabled builds and stores receipt | verificationReceipts() is non-empty |
offline valid local route with verification disabled builds and empty receipts | verificationReceipts() is empty |
Artifact Verification in Offline Mode
When ModelArtifactVerificationSettings.enabled = true and requireDigestForLocalModels = true, the builder requires every local-model RegisteredModel to have a non-null artifactDigest. If a local model is missing its digest, the builder throws:
IllegalStateException("artifact-digest-required-for-local-model")
Artifact verification targets include both primary and fallback routes for local providers:
private fun verifyLocalModelArtifacts(...) {
val verificationTargets = buildSet {
primaryModelRoutes.forEach { (modelName, providerName) -> add(providerName to modelName) }
fallbackRoutes.forEach { route -> add(route.providerName to route.fallbackModelName) }
}
// Only LOCAL-trust-zone targets are verified
// Non-local targets are skipped
}
Limitations
- No infrastructure enforcement — the enum validates build-time configuration, not network-level isolation. Additional controls (firewall,
NetworkPolicy, physical air-gap) are required for production - Loopback provider is test-only — the loopback/
FakeProvideris for CI verification, not production inference - No continuous monitoring — zero-egress probes are run once at build/CI time; runtime monitoring of network egress is outside scope
- All-local fallback — fallback routes in offline mode must also be
LOCAL; no external failover is possible - Docker
--network=nonelimitations — Docker-level network isolation may not prevent all egress paths (e.g., mounted Unix sockets, host-network containers) - Registered-provider check is greedy — the first validation loop catches all registered non-
LOCALproviders before fallback or default checks are reached, sooffline-profile-non-local-provider-rejectedis the error you will see for most cloud provider misconfigurations
Next Steps
- Evidence Packs — include zero-egress evidence in deployment attestation
- Artifact Verification — verify local model artifacts for offline deployments
- Sovereign Mode — understand the full sovereign runtime
