TramAI

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-sovereignSovereignDeploymentMode.OFFLINE


What

The offline deployment system is built on four layers:

  1. SovereignDeploymentMode.OFFLINE — an enum value that triggers strict local-only validation during builder assembly
  2. validateOfflineDeployment() — called during SovereignTramai.Builder.build() before any registry lookup or artifact verification
  3. Zero-egress verification — a harness that builds the runtime, invokes a loopback provider, and probes external TCP/DNS connectivity
  4. 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:

CheckConditionError Code
Registered providerMust be LOCAL trust zoneoffline-profile-non-local-provider-rejected
Primary route targetMust be LOCAL trust zoneoffline-profile-non-local-primary-route-rejected
Fallback route targetMust be LOCAL trust zoneoffline-profile-non-local-fallback-rejected
Default providerMust be LOCAL trust zoneoffline-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=none for 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:

TestExpected Behavior
OFFLINE with only LOCAL provider buildsSovereignTramai is created successfully
STANDARD with LOCAL provider buildsWorks in both modes
STANDARD with EU_CLOUD provider buildsCloud providers work in STANDARD mode
STANDARD with GLOBAL_CLOUD provider buildsCloud providers work in STANDARD mode
OFFLINE with EU_CLOUD registered provider rejectsThrows IllegalArgumentException("offline-profile-non-local-provider-rejected")
OFFLINE with GLOBAL_CLOUD registered provider rejectsSame error
OFFLINE with local primary route and local fallback buildsValid offline config with fallback
OFFLINE with cloud fallback route rejectsRegistered-provider check catches the cloud fallback
OFFLINE with cloud default provider rejectsSame — registered-provider check fires first
offline validation occurs before artifact registry lookupA throwing registry is never called — offline check rejects first
offline valid local route with verification enabled builds and stores receiptverificationReceipts() is non-empty
offline valid local route with verification disabled builds and empty receiptsverificationReceipts() 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/FakeProvider is 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=none limitations — 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-LOCAL providers before fallback or default checks are reached, so offline-profile-non-local-provider-rejected is the error you will see for most cloud provider misconfigurations

Next Steps