Evidence Packs
Evidence packs capture the security posture of a sovereign TramAI deployment as a deterministic, safe-for-auditors JSON artifact. They contain no secrets, tokens, prompts, stack traces, or filesystem paths — every identifier is sanitized through EvidenceSafeString before being written.
Module: tramai-sovereign — SovereignEvidencePackV1 DTO, SovereignEvidencePackWriter, and SovereignEvidencePackGenerator.
What
An evidence pack is a SovereignEvidencePackV1 DTO that summarizes:
- Deployment identity — deployment mode (
STANDARDorOFFLINE), allowed models (sorted), allowed providers (sorted), trust zone mappings - Artifact verification — settings snapshot, per-artifact receipts (manifest digest, artifact count, total size)
- Zero-egress evidence — optional subsection from offline deployment testing (loopback invocations, TCP/DNS probes)
- Audit chain — optional validation of the hash-chained audit event integrity
- Supply chain — optional SBOM linkage (CycloneDX format with SHA-256 digest)
- Release bundle — optional build artifact evidence with Maven coordinates and digests for every JAR
- CI/CD attestation — optional build provenance from a CI pipeline (GitHub Actions)
Deterministic JSON
The SovereignEvidencePackWriter produces JSON with:
- Stable field ordering matching the data class declaration order
- Full JSON control-character escaping (all chars < 0x20 as
\uXXXX) - No external JSON library dependency
- Parent directory auto-creation
Same input produces identical output every time — suitable for hashing, signing, or comparing in CI.
Safety Guarantees
All string values are validated through EvidenceSafeString.sanitize():
- Rejects identifiers containing path prefixes (
/tmp/,/home/,/Users/) - Rejects identifiers containing secrets-adjacent terms (
token,secret,password,prompt,rawRequest,rawResponse,stacktrace) - Rejects control characters
- Rejects Windows drive paths
SHA-256 digest values (sha256:<hex>) bypass string sanitization and are validated with strict regex instead.
When to Use
Use evidence packs when:
- You need audit-grade documentation of your TramAI deployment's security posture
- You need CI attestation — generate and upload evidence packs as CI artifacts with provenance
- You need enterprise security review artifacts that auditors can inspect without access to the runtime
- You need air-gap validation evidence — prove that an offline deployment has zero egress
- You need a verifiable deployment snapshot — hash the pack and sign it
Do not use evidence packs for runtime monitoring or live debugging — they are static snapshots.
Quickstart
Basic Generation
// 1. Build your sovereign runtime
val tramai = SovereignTramai.builder()
.profile(profile)
.modelRegistry(registry)
.auditStore(auditStore)
.provider(ollamaProvider, name = "ollama", default = true)
.model("llama3.2", "ollama")
.modelArtifactVerifier(verifier)
.modelArtifactVerificationSettings(ModelArtifactVerificationSettings(enabled = true))
.build()
// 2. Generate an evidence pack
val pack = tramai.evidencePack()
// 3. Write to file
SovereignEvidencePackWriter.write(pack, Paths.get("evidence/sovereign-evidence.json"))
With All Optional Sub-Sections
val pack = tramai.evidencePack(
zeroEgress = ZeroEgressEvidenceV1(
deploymentMode = "OFFLINE",
runtimeBuildSucceeded = true,
loopbackProviderInvocationSucceeded = true,
loopbackProviderInvocationCount = 3,
externalTcpProbeBlocked = true,
externalDnsProbeBlocked = true,
),
auditChain = AuditChainEvidenceV1(
isValid = true,
totalEvents = 42,
),
supplyChain = SupplyChainEvidenceV1(
schemaVersion = 1,
sbomFormat = "CycloneDX",
sbomSpecVersion = "1.6",
sbomFileName = "bom.json",
sbomSha256 = "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd",
generatedBy = "CycloneDX Gradle Plugin 3.2.4",
),
releaseBundle = ReleaseBundleEvidenceV1(
schemaVersion = 1,
buildTool = "Gradle",
javaVersion = "21",
gradleVersion = "8.12",
artifacts = listOf(
ReleaseArtifactEvidenceV1(
groupId = "dev.tramai",
artifactId = "tramai-core",
version = "0.3.0",
classifier = null,
extension = "jar",
fileName = "tramai-core-0.3.0.jar",
sha256 = "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd",
sizeBytes = 245760,
),
),
),
attestation = AttestationEvidenceV1(
schemaVersion = 1,
provider = "GitHub Artifact Attestations",
workflowName = "deploy",
workflowRunId = "1234567890",
repository = "my-org/my-repo",
commitSha = "abc123def456abc123def456abc123def456abc1",
attestedSubjects = listOf(
AttestedSubjectV1(
fileName = "sovereign-evidence.json",
sha256 = "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd",
attestationType = "build-provenance",
),
),
),
)
Evidence Pack Schema (V1)
SovereignEvidencePackV1
| Field | Type | Description |
|---|---|---|
schemaVersion | Int | Schema version (currently 1) |
deploymentMode | String | "STANDARD" or "OFFLINE" |
allowedModels | List<String> | Allowed model names (sorted) |
allowedProviders | List<String> | Allowed provider names (sorted) |
providerZones | Map<String, String> | Trust zone per provider |
artifactVerificationSettings | Map<String, Any?> | Verification settings snapshot (enabled, requireDigestForLocalModels) |
artifacts | List<ArtifactEvidenceV1> | Per-artifact verification receipts |
zeroEgress | ZeroEgressEvidenceV1? | Zero-egress probe results |
auditChain | AuditChainEvidenceV1? | Audit chain validation |
supplyChain | SupplyChainEvidenceV1? | SBOM linkage |
releaseBundle | ReleaseBundleEvidenceV1? | Build artifact evidence |
attestation | AttestationEvidenceV1? | CI/CD attestation |
generatedAt | String | ISO-8601 generation timestamp |
ArtifactEvidenceV1
| Field | Type | Description |
|---|---|---|
registryEntryId | String | Registry entry identifier |
manifestDigest | String | SHA-256 of the manifest |
modelName | String | Model name |
verifiedAt | String | ISO-8601 verification timestamp |
artifactCount | Int | Number of verified artifact files |
totalSizeBytes | Long | Total verified size |
ZeroEgressEvidenceV1
| Field | Type | Description |
|---|---|---|
deploymentMode | String | Deployment mode under test |
runtimeBuildSucceeded | Boolean | Runtime built successfully |
loopbackProviderInvocationSucceeded | Boolean | Loopback provider returned valid response |
loopbackProviderInvocationCount | Int | Number of loopback invocations |
externalTcpProbeBlocked | Boolean | External TCP connect to 1.1.1.1:443 blocked |
externalDnsProbeBlocked | Boolean | External DNS resolution of example.com blocked |
AuditChainEvidenceV1
| Field | Type | Description |
|---|---|---|
isValid | Boolean | Audit chain integrity validated |
totalEvents | Int | Total events in the chain |
SupplyChainEvidenceV1
| Field | Type | Description |
|---|---|---|
schemaVersion | Int | Schema version (must be 1) |
sbomFormat | String | SBOM format (e.g., "CycloneDX") |
sbomSpecVersion | String | Format spec version (e.g., "1.6") |
sbomFileName | String | SBOM file name (no path separators) |
sbomSha256 | String | sha256:<hex> digest — validated against ^sha256:[a-fA-F0-9]{64}$ |
generatedBy | String | SBOM generator identifier |
AttestationEvidenceV1
| Field | Type | Description |
|---|---|---|
schemaVersion | Int | Schema version (must be 1) |
provider | String | Attestation provider (e.g., "GitHub Artifact Attestations") |
workflowName | String | CI workflow name |
workflowRunId | String | Numeric workflow run ID — validated against ^[0-9]+$ |
repository | String | owner/repo format — validated against ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ |
commitSha | String | 40-character hex commit SHA — validated against ^[a-fA-F0-9]{40}$ |
attestedSubjects | List<AttestedSubjectV1> | Attested file digests (must not be empty) |
AttestedSubjectV1
| Field | Type | Description |
|---|---|---|
fileName | String | File name (no path separators) |
sha256 | String | sha256:<hex> digest |
attestationType | String | "build-provenance" or "sbom" only |
ReleaseBundleEvidenceV1
| Field | Type | Description |
|---|---|---|
schemaVersion | Int | Schema version (must be 1) |
buildTool | String | Build tool identifier (e.g., "Gradle") |
javaVersion | String | Java runtime version |
gradleVersion | String | Gradle version |
artifacts | List<ReleaseArtifactEvidenceV1> | Release artifacts (must not be empty) |
ReleaseArtifactEvidenceV1
| Field | Type | Description |
|---|---|---|
groupId | String | Maven group ID (e.g., "dev.tramai") |
artifactId | String | Maven artifact ID (e.g., "tramai-core") |
version | String | Artifact version (e.g., "0.3.0") |
classifier | String? | Optional Maven classifier |
extension | String | File extension (e.g., "jar") |
fileName | String | File name (no path separators) |
sha256 | String | sha256:<hex> digest — validated against ^sha256:[a-fA-F0-9]{64}$ |
sizeBytes | Long | File size in bytes (must be >= 0) |
Writer
The SovereignEvidencePackWriter produces deterministic JSON output:
SovereignEvidencePackWriter.write(pack, Paths.get("evidence/pack.json"))
Output format:
{
"schemaVersion": 1,
"deploymentMode": "OFFLINE",
"allowedModels": [
"llama3.2"
],
"allowedProviders": [
"ollama"
],
"providerZones": {
"ollama": "LOCAL"
},
"artifactVerificationSettings": {
"enabled": true,
"requireDigestForLocalModels": true
},
"artifacts": [
{
"registryEntryId": "ollama-llama3.2",
"manifestDigest": "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd",
"modelName": "llama3.2",
"verifiedAt": "2025-06-14T12:00:00Z",
"artifactCount": 3,
"totalSizeBytes": 1234567890
}
],
"zeroEgress": null,
"auditChain": null,
"supplyChain": null,
"releaseBundle": null,
"attestation": null,
"generatedAt": "2025-06-14T12:00:00Z"
}
Generator Validation
The SovereignEvidencePackGenerator.generate() method performs validation and sanitization:
- EvidenceSafeString sanitization — all model names, provider names, zone keys, registry entry IDs, and workflow names are sanitized (rejects paths, secrets-adjacent terms, control characters)
- Supply chain validation —
sbomSha256must match^sha256:[a-fA-F0-9]{64}$,schemaVersionmust be 1,sbomFileNamemust not contain path separators - Attestation validation —
workflowRunIdmust be numeric,repositorymust matchowner/repo,commitShamust be 40 hex chars,attestedSubjectsmust not be empty,attestationTypemust be"build-provenance"or"sbom" - Release bundle validation —
sha256for each artifact must match^sha256:[a-fA-F0-9]{64}$,sizeBytesmust be >= 0, artifacts list must not be empty - Deterministic sorting —
allowedModelsandallowedProvidersare sorted;providerZoneskeys are sorted alphabetically in the writer
All validation failures use fixed safe-code error messages:
"evidence-unsafe-identifier""evidence-unsafe-digest-format""evidence-unsafe-control-character""evidence-unsupported-supply-chain-schema-version""evidence-unsupported-attestation-schema-version""evidence-unsafe-attestation-workflow-run-id""evidence-unsafe-attestation-commit-sha""evidence-unsafe-attestation-repository""evidence-unsafe-attestation-subjects""evidence-unsupported-attestation-type""evidence-unsupported-release-bundle-schema-version""evidence-unsafe-release-artifacts""evidence-unsafe-artifact-size"
CI Workflow Integration
Evidence packs are designed for CI artifact upload with attestation:
# .github/workflows/deploy.yml
steps:
- name: Generate evidence pack
run: ./gradlew sovereignEvidencePack
- name: Sign the evidence pack
run: |
openssl dgst -sha256 -sign private-key.pem \
-out build/evidence/sovereign-evidence.json.sig \
build/evidence/sovereign-evidence.json
- name: Upload evidence
uses: actions/upload-artifact@v4
with:
name: sovereign-evidence
path: build/evidence/
Extension Pattern
To add a new evidence sub-section:
- Define the DTO — a new data class like
ZeroEgressEvidenceV1with documented properties and@paramKDoc - Add to SovereignEvidencePackV1 — add an optional field (
val newSection: NewSectionV1? = null) - Update the writer — add a
serializeNewSection()method and wire it intoserialize()matching the data class declaration order - Update the generator — add sanitization and validation in
SovereignEvidencePackGenerator.generate()
Limitations
- Snapshot-only — evidence packs capture a point-in-time view; they are not updated automatically
- No secrets — by design, evidence packs cannot contain secrets, tokens, or credentials (
EvidenceSafeStringactively rejects them) - No runtime data — no prompts, responses, or model outputs are included
- Manual sub-sections —
zeroEgress,auditChain,supplyChain,releaseBundle, andattestationmust be constructed manually and passed toevidencePack() - Schema version 1 — the current schema is v1; breaking changes will increment
schemaVersion
Next Steps
- Offline Deployment — generate zero-egress evidence for air-gapped deployments
- Artifact Verification — verification receipts feed into evidence packs
- Sovereign Mode — understanding the sovereign runtime
