TLPT Evidence Pack v1 Contract
Contract ID: CONTRACT-TLPT-EVIDENCE-PACK-V1-031 Version: tlpt-evidence-pack.v1 Status: Draft Owner: EvidenceLocker Guild Last updated: 2026-05-01
Purpose
TLPT Evidence Pack v1 is the signed EvidenceLocker capsule manifest for DORA threat-led penetration testing evidence. The pack is white-team/evidence-side only: it binds the approved TLPT scope, the frozen Replay baseline, the controls inventory, runtime instrumentation snapshots, post-test attestations, and white-team sign-off. It does not run offensive tooling.
This contract is for engineers and white-team auditors assembling or verifying the final DORA TLPT evidence capsule. The pack is the terminal artifact of the TLPT evidence chain: scope → baseline → evidence pack.
Export Center Assurance Profile
ExportCenter advertises the dora.tlpt-evidence-pack assurance profile for discovery and readiness. Signed export readiness remains fail-closed on dora-tlpt-evidence-pack-scope-missing, dora-tlpt-evidence-pack-white-team-signoff-missing, and dora-tlpt-evidence-pack-schema-mapping-missing until the TLPT scope, baseline/evidence refs, and white-team sign-off are supplied by the owning EvidenceLocker/TLPT workflow.
POST /v1/exports/assurance/profiles/dora.tlpt-evidence-pack/runs is registered on the shared Assurance produce surface. The request body supplies tlptEvidencePack, using the same CreateTlptEvidencePackRequest shape as EvidenceLocker. While readiness is blocked and no request-scoped TLPT pack is supplied, ExportCenter returns HTTP 409 assurance_export_readiness_blocked. When the request supplies the TLPT scope, baseline, controls inventory, runtime instrumentation, post-test attestations, and sign-off, ExportCenter delegates assembly/signing to EvidenceLocker POST /api/v1/evidence/capsules/tlpt-packs and persists the returned sealed TlptEvidencePackResponse JSON as the Assurance run artifact. ExportCenter does not assemble or sign TLPT packs locally.
Schema Pinning
- Schema version is pinned to
tlpt-evidence-pack.v1. - DSSE payload type:
application/vnd.stellaops.tlpt-evidence-pack+json. - EU signing payload registry id:
tlpt-evidence-pack-v1(docs/contracts/eu-signing-payload-registry-v1.md). - Implementation:
src/EvidenceLocker/StellaOps.EvidenceLocker/Capsules/— contract records inTlptEvidencePackContracts.cs; assembly logic inCapsuleService.AssembleTlptEvidencePackAsync; HTTP endpoint insrc/EvidenceLocker/StellaOps.EvidenceLocker/Api/Capsules/CapsuleEndpoints.cs. - Adjacent tests:
src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Tests/CapsulePipelineTests.csandsrc/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Tests/CapsuleEndpointsTests.cs. - Upstream scope contract:
docs/contracts/tlpt-scope-v1.md. - Upstream baseline contract:
docs/contracts/tlpt-baseline-v1.md. - Breaking changes require a new schema version; consumers must not auto-follow future TLPT evidence pack schemas.
Request Shape
The assembly request (CreateTlptEvidencePackRequest) carries only the caller-supplied inputs. The server derives identifiers, timestamps, generator, retention, and expectedSignerKeyId. Required request fields:
scope: see manifestscopebelow.baseline: see manifestbaselinebelow.controlsInventory[]: at least one artifact reference (empty/null arrays are rejected with400 invalid_tlpt_pack_request).runtimeInstrumentation.preTestSnapshots[]andruntimeInstrumentation.postTestSnapshots[]: each must contain at least one artifact reference.postTestAttestations[]: at least one artifact reference.signOff: see manifestsignOffbelow.auditWindow: optional; when present,tomust be greater than or equal tofrom(otherwise400 invalid_tlpt_pack_request).
The request does not accept capsuleId, packId, tenantId, createdAt, generator, retention, or expectedSignerKeyId; those are computed during assembly.
Manifest Shape
TlptEvidencePackManifest top-level fields:
apiVersion:stella.ops/v1.kind:TlptEvidencePackManifest.schemaVersion:tlpt-evidence-pack.v1.capsuleId: deterministic UUID. It is the first 16 bytes of the pack fingerprint (the SHA-256 over tenant id plus normalized pack inputs) rendered as aD-format GUID. It is not an RFC 4122 versioned UUID.packId: deterministictlpt-pack-<16 hex>identifier, the first 16 hex characters of the same pack fingerprint.capsuleIdandpackIdtherefore derive from one shared fingerprint.tenantId: tenant whose TLPT evidence is packaged (the resolved tenant UUID from the request context, inDformat).createdAt: UTC pack assembly timestamp (normalized to second precision).generator:stellaops-evidence-locker/tlpt-evidence-pack/v1.scope:scopeId(optional),scopeHash,documentRef, anddocumentHashfor thetlpt-scope.v1document.baseline:baselineHash,documentRef, anddocumentHashfor thetlpt-baseline.v1manifest.controlsInventory[]: signed or hash-addressed control inventory artifacts.runtimeInstrumentation.preTestSnapshots[]: runtime snapshots captured before the TLPT activity.runtimeInstrumentation.postTestSnapshots[]: runtime snapshots captured after the TLPT activity.postTestAttestations[]: white-team/customer post-test attestation artifacts.signOff:approvedByRef(opaque approver ref),approvedAt(approval timestamp),approvalRef, andattestationHash.retention: EvidenceLocker retention metadata withpolicyId,retentionYears,retainUntil, andreason. Default ispolicyIddora-tlpt-evidence-default,retentionYears10,retainUntil=createdAt + 10 years, andreasonDORA TLPT evidence retention default: 10 years..auditWindow: optionalfrom/toUTC slice for large packs.expectedSignerKeyId: key id advertised by the configured signer (ICapsuleSigner.KeyId) before signing; null when the signer exposes no key id.
Response Shape
The endpoint returns TlptEvidencePackResponse (HTTP 201 Created, Location: /api/v1/evidence/capsules/{capsuleId}) with:
capsuleId,packId,tenantId,contentHash,createdAt,sealedAt.signingStatus:signedorunsigned.signerKeyId: signer key id when available, else null.regionTag: data-residency region resolved for the write; defaults tounspecified.manifest: the fullTlptEvidencePackManifestabove.dsseEnvelope: the DSSE envelope JSON when signed, else null.
Artifact references use this shape:
{
"artifactType": "runtime-signals",
"artifactRef": "capsule-input://tlpt/runtime/pre-signals.json",
"artifactHash": "sha256:5555555555555555555555555555555555555555555555555555555555555555",
"capturedAt": "2026-05-01T09:00:00Z",
"description": "optional short description"
}
Determinism Rules
- JSON is canonicalized by the EvidenceLocker capsule canonicalizer (
CapsuleManifestCanonicalizer): object properties sorted lexicographically (ordinal), stable array ordering, UTF-8, no insignificant whitespace. - Input digest fields —
scopeHash,baselineHash, scope/baseline document hashes,signOff.attestationHash, and each artifactartifactHash— must use the lowercasesha256:<64 hex>form (validated as exactlysha256:+ 64 lowercase hex chars; otherwise400 invalid_tlpt_pack_request). - The manifest
contentHash(and the auditpackHash) is the bare lowercase hex SHA-256 of the canonical manifest bytes, with nosha256:prefix. - Artifact
artifactTypevalues are normalized to lowercase; required text fields are trimmed; optional empty text becomes null. - Artifact arrays are de-duplicated (by
artifactType/artifactHash/artifactRef) and ordered byartifactType, thenartifactHash, thenartifactRefusing ordinal comparison. - Timestamps are normalized to UTC second precision.
- With the same tenant, normalized inputs, and clock, the same
capsuleId,packId, andcontentHashare produced.
Signing And Verification
TLPT pack assembly is a single-step seal boundary: unlike Decision Capsules (which are created unsealed, then sealed by a separate POST .../seal call), AssembleTlptEvidencePackAsync canonicalizes, signs, and persists the manifest in one operation. There is no separate seal endpoint for TLPT packs.
The pack is signed through ICapsuleSigner.SignPayloadAsync using the TLPT DSSE payload type. Protected assembly fails closed (InvalidOperationException, surfaced as HTTP 500) when no durable signer is configured unless the host enables AllowUnsignedCapsules for a local test harness, in which case the pack is persisted with signingStatus = unsigned.
After signing, the sealed capsule is projected into the verdict ledger via TryProjectSealedCapsuleAsync, so the sealing -> verdict_ledger count forcing function holds for TLPT packs identically to Decision Capsules.
Verification recomputes the canonical manifest hash and verifies the DSSE envelope against the payload type selected by manifest kind. Existing Decision Capsule verification remains on application/vnd.stellaops.decision-capsule+json.
The assembly endpoint POST /api/v1/evidence/capsules/tlpt-packs requires the evidence:create scope (StellaOpsResourceServerPolicies.EvidenceCreate) and runs under the /api/v1/evidence/capsules tenant-scoped group; data-residency write-region enforcement applies before assembly.
Retention
Default retention is 10 years. The manifest retention block records policyId dora-tlpt-evidence-default, retentionYears 10, retainUntil (createdAt plus 10 years), and a human-readable reason. The default intentionally exceeds the 3-year TLPT cycle and leaves audit lookback room across multiple cycles. These values are fixed constants in CapsuleService.AssembleTlptEvidencePackAsync — the request cannot override them. Tenant- or regulator-specific overrides are not part of this capsule slice; if added later, they must remain explicit in the manifest and audit trail.
Offline And Replay Expectations
- Pack assembly does not fetch external data.
- The scope document must already carry the
scopeHashfromtlpt-scope.v1. - The baseline document must already carry the Replay Core
baselineHashfromtlpt-baseline.v1. - Offline verification needs only the capsule manifest, DSSE envelope, signer public key material, and referenced artifact hashes.
- CLI command wiring is owned by
SPRINT_20260430_133_CLI_tlpt_commands.mdand must call this EvidenceLocker contract instead of recomputing pack hashes.
Audit Trail Status
The capsule manifest and repository record contain the pack content hash, expected signer key id, signing status, sealed timestamp, and retention window. The service API records the same values in the audit trail through POST /api/v1/evidence/capsules/tlpt-packs. The emitted audit event has module evidence (AuditModules.Evidence) and action assemble_tlpt_pack (AuditActions.Evidence.AssembleTlptPack), with resource type tlpt_evidence_pack.
Required audit details:
packId,packHash, andcontentHash.packHashandcontentHashcarry the same value (the canonical manifest content hash).capsuleIdandledgerId.ledgerIdis set to the same value ascapsuleId(the capsule record id); there is no separate decision-capsule ledger row id for TLPT packs.signerKeyId,expectedSignerKeyId, andsigningStatus.sealedAt.retentionPolicyId,retentionYears, andretainUntil.schemaVersion.
The audit event id and timestamp are non-deterministic by design (opaque observability fields); they are not derived from canonical content.
Audit emission is additive to the existing local CLI pack behavior; local CLI pack creation continues to use the capsule service directly and remains offline-friendly.
