Draft Specification
This companion specification is under active development. The concepts and requirements defined here may change based on implementation feedback. Use in production at your own risk.
Profiles & Semantic Validation
Define what artifacts a pack should contain, validate their structure, and express compliance requirements as machine-readable contracts.
The core Evidence Pack spec defines how to package evidence (structure, integrity, signatures). This companion spec defines what evidence to include and how to validate it semantically. Profiles declare requirements; types define artifact schemas; overlays add constraints.
Introduction
A structurally valid Evidence Pack tells you nothing about whether it contains the right evidence. A pack could pass all integrity checks but be missing a required SOC 2 report, or include a pentest summary without the full findings. Profiles solve this by declaring what a pack must contain.
Relationship to Core Spec
This spec builds on the reserved fields defined in Evidence Pack v1.0:
profile- References the profile this pack claims to conform tooverlays- Additional constraints applied on top of the profileprofile_lock- Pinned digests for reproducible validation
Artifact Semantic Type
For semantic validation to work, artifacts must declare their semantic type. This companion spec defines a new optional artifact field:
| Field | Type | Required | Description |
|---|---|---|---|
semantic_type |
String | No | Type identifier (e.g., evidencepack/soc2-report@v1) |
An artifact matches a profile requirement when artifact.semantic_type == requirement.type. The field is named semantic_type (not type) to distinguish it from the core spec's type field ("embedded"/"reference").
Core Concepts
Types
Reusable building blocks. A type defines the schema and semantics for a single artifact category (e.g., "SOC 2 Type II Report").
Profiles
Named contracts. A profile declares what types a pack must include and any constraints on those artifacts.
Overlays
Additive constraints. An overlay modifies a profile for specific contexts (e.g., HIPAA requirements on top of SOC 2).
How They Compose
// A pack declares its profile, overlays, and locks digests for reproducibility
{
"spec_version": "1.0",
"profile": "evidencepack/soc2-basic@v1",
"overlays": ["evidencepack/hipaa-overlay@v1"],
"profile_lock": [
{ "id": "evidencepack/soc2-basic@v1", "digest": "sha256:abc123..." },
{ "id": "evidencepack/hipaa-overlay@v1", "digest": "sha256:def456..." },
{ "id": "evidencepack/soc2-report@v1", "digest": "sha256:789def..." }
],
"artifacts": [
{
"type": "embedded",
"path": "artifacts/soc2-2025.pdf",
"digest": "sha256:...",
"semantic_type": "evidencepack/soc2-report@v1",
"collected_at": "2025-03-15T10:00:00Z",
"metadata": { "auditor": "Big4 LLP" }
}
]
}
Validation proceeds in layers:
- Resolve the profile and overlays (fetch or use cached, verify digests if locked)
- Merge overlays onto the base profile to produce an effective profile
- Validate each artifact against its declared type schema
- Check that all required types are present with valid artifacts
- Report findings (pass, fail, or warning for each requirement)
Types
A type is a reusable definition for a category of evidence artifact. It specifies what the artifact should contain, how to validate it, and what metadata to expect.
Type Definition
{
"id": "evidencepack/soc2-report@v1",
"name": "SOC 2 Type II Report",
"description": "Annual SOC 2 Type II attestation report from an accredited auditor",
"content_types": ["application/pdf"],
"metadata_schema": {
"type": "object",
"properties": {
"report_period_start": { "type": "string", "format": "date" },
"report_period_end": { "type": "string", "format": "date" },
"auditor": { "type": "string" },
"trust_services_criteria": {
"type": "array",
"items": { "enum": ["security", "availability", "processing_integrity", "confidentiality", "privacy"] }
}
},
"required": ["report_period_start", "report_period_end", "auditor"]
},
"controls": ["SOC2-CC1.1", "SOC2-CC1.2"]
}
Type Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
String | Yes | Unique identifier in namespace/name@version format |
name |
String | Yes | Human-readable name |
description |
String | No | Extended description of what this type represents |
content_types |
Array | No | Allowed MIME types for artifacts of this type |
metadata_schema |
Object | No | JSON Schema for validating artifact.metadata (not file contents) |
controls |
Array | No | Control framework mappings this type addresses |
examples |
Array | No | Conformance examples for collector testing (see below) |
Type Examples
Types MAY include an examples array for collector conformance testing. Each example provides a sample input (mocked API response) and expected output (valid artifact), enabling automated verification that collector implementations produce correct output.
{
"id": "evidencepack/vcs-config@v1",
"name": "Version Control Configuration",
"examples": [
{
"name": "github-org-mixed",
"description": "GitHub organization with mixed branch protection",
"input_url": "https://registry.evidencepack.org/examples/vcs-config/github-org/input.json",
"output_url": "https://registry.evidencepack.org/examples/vcs-config/github-org/output.json"
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Short identifier for this example (lowercase, alphanumeric, hyphens) |
description |
String | No | Human-readable description of what scenario this example represents |
input_url |
String | Yes | URL to fetch sample API response (collector input) |
output_url |
String | Yes | URL to fetch expected artifact output |
Conformance testing: Collectors are conformant for a type when they produce output matching the expected output for all examples. The test procedure is:
- Fetch the example input from
input_url - Run the collector with the input
- Validate the collector's output against the type's
metadata_schema - Compare the collector's output to the expected output from
output_url
Resolution & Registry
Types, profiles, and overlays are resolved from configured registries or local cache. A registry is an HTTPS endpoint with these URL patterns:
# Registry URL structure GET https://registry.example.com/types/{namespace}/{name}@v{N} GET https://registry.example.com/profiles/{namespace}/{name}@v{N} GET https://registry.example.com/overlays/{namespace}/{name}@v{N} # Example: fetch a type definition GET https://registry.evidencepack.org/types/evidencepack/soc2-report@v1 # Response (same structure for types, profiles, overlays) { "id": "evidencepack/soc2-report@v1", "digest": "sha256:abc123...", "definition": { ... } }
The digest is the SHA-256 of the JCS-canonicalized definition bytes. Validators should compute the digest locally and verify it matches the registry-provided value.
No central registry required
- Registries are optional. Organizations may self-host or mirror registries for their own types and profiles.
- Offline verification. Once a profile is resolved, its digest is recorded in
profile_lock. Subsequent validation can proceed without network access. - Profiles are optional. Core pack validation (structural integrity, digest verification, signature checks) does not require profile resolution.
Profiles
A profile is a named contract that declares what a pack must contain. Profiles reference types and specify requirements (required vs optional, cardinality, freshness).
Profile Structure
{
"id": "evidencepack/soc2-basic@v1",
"name": "SOC 2 Basic",
"description": "Minimum evidence for a SOC 2 security review",
"requirements": [
{
"type": "evidencepack/soc2-report@v1",
"required": true,
"cardinality": { "min": 1, "max": 1 },
"freshness": { "max_age_days": 365 }
},
{
"type": "evidencepack/pentest-report@v1",
"required": true,
"cardinality": { "min": 1 },
"freshness": { "max_age_days": 365 }
},
{
"type": "evidencepack/security-policy@v1",
"required": false
}
]
}
Requirement Fields
| Field | Type | Description |
|---|---|---|
type |
String | Type identifier this requirement references |
required |
Boolean | Whether the pack must include this type (default: true) |
cardinality |
Object | min and max count of artifacts |
freshness |
Object | max_age_days from artifact's collected_at |
Profile Resolution
When validating a pack, the validator must resolve its declared profile:
- Parse the
profilefield from the manifest - If
profile_lockcontains a matchingid, verify the fetched profile's digest matches - Fetch the profile from a configured registry or local cache
- If digest verification fails, reject validation (do not substitute)
Overlays
Overlays are additive constraints that modify a base profile. They're useful for expressing cross-cutting requirements (like HIPAA) that apply on top of existing profiles.
Overlay Structure
{
"id": "evidencepack/hipaa-overlay@v1",
"name": "HIPAA Overlay",
"description": "Additional requirements for HIPAA compliance",
"add_requirements": [
{
"type": "evidencepack/baa-agreement@v1",
"required": true
},
{
"type": "evidencepack/hipaa-risk-assessment@v1",
"required": true,
"freshness": { "max_age_days": 365 }
}
],
"modify_requirements": [
{
"type": "evidencepack/pentest-report@v1",
"freshness": { "max_age_days": 180 }
}
]
}
Merge Semantics
Overlays are applied in declared order. For each overlay:
add_requirements-Append new requirements to the effective profile. If the type already exists, emitDUPLICATE_REQUIREMENT_TYPEerror.modify_requirements-Update existing requirements (matched bytype). Targets the effective profile at application time, so overlay B can modify a requirement added by overlay A.- Modifications are shallow-merged: specified fields override, unspecified fields are preserved.
- If an overlay modifies a type not in the effective profile, emit
OVERLAY_TARGET_NOT_FOUNDerror.
required in overlays: Overlays must not set the required field in modify_requirements. To change the minimum artifact count, set cardinality.min explicitly. Overlays may set required in add_requirements.
Semantic Validation
Semantic validation checks whether a pack satisfies its declared profile. This runs after structural validation (core spec Level 1) succeeds.
Validation Procedure
Semantic validation proceeds in eight steps:
- Build lock index -Index
profile_lockentries byid. EmitDUPLICATE_LOCK_ENTRYon duplicates. - Resolve profile -Resolve and verify digest against lock. Emit
PROFILE_NOT_FOUNDorDIGEST_MISMATCH. - Resolve and merge overlays -For each overlay in order, resolve, verify lock, and merge. Emit
OVERLAY_TARGET_NOT_FOUNDif modifying missing type. - Resolve types -For each type in effective profile, resolve and verify lock.
- Check unpinned dependencies -Emit
UNPINNED_DEPENDENCYwarning for dependencies not inprofile_lock. - Index artifacts -Group artifacts by
semantic_type. EmitUNTYPED_ARTIFACTwarning for missing type. - Check requirements -For each requirement: check cardinality, freshness,
content_type, andmetadata. - Check unknown artifacts -Emit
UNKNOWN_ARTIFACTwarning for typed artifacts not matching any requirement.
// Simplified pseudocode (see spec for full details) function validateSemantic(pack): findings = [] // 1. Build lock index from profile_lock lockedDigests = indexByIdCheckingDuplicates(pack.profile_lock) // 2-4. Resolve profile, overlays, types (verify against locks) effectiveProfile = resolveLocked(pack.profile, lockedDigests) for overlayId in pack.overlays: effectiveProfile = merge(effectiveProfile, resolveLocked(overlayId)) // 6. Index artifacts by semantic_type artifactsByType = {} for artifact in pack.artifacts: if artifact.semantic_type: artifactsByType[artifact.semantic_type].append(artifact) else: findings.add(warning, UNTYPED_ARTIFACT, artifact.path) // 7. Check each requirement for req in effectiveProfile.requirements: artifacts = artifactsByType[req.type] or [] minCount = req.cardinality?.min ?? (1 if req.required else 0) // Cardinality if minCount > 0 and len(artifacts) == 0: findings.add(error, MISSING_REQUIRED, req.type) // Freshness (uses collected_at, must be UTC Z format) if req.freshness?.max_age_days: for artifact in artifacts: if not artifact.collected_at: findings.add(error, MISSING_COLLECTED_AT) else if not validTimestamp(artifact.collected_at): findings.add(error, INVALID_COLLECTED_AT) else if daysSince(artifact.collected_at) > req.freshness.max_age_days: findings.add(error, STALE_ARTIFACT) return findings
Findings Format
Validation produces a list of findings. Each finding has a severity, message, and location:
{
"findings": [
{
"severity": "error",
"code": "MISSING_REQUIRED",
"message": "Missing required artifact of type evidencepack/soc2-report@v1",
"requirement": "evidencepack/soc2-report@v1"
},
{
"severity": "error",
"code": "STALE_ARTIFACT",
"message": "Artifact is 400 days old, max allowed is 365",
"path": "artifacts/pentest-2024.pdf",
"requirement": "evidencepack/pentest-report@v1"
},
{
"severity": "warning",
"code": "UNKNOWN_ARTIFACT",
"message": "Artifact not matched to any profile requirement",
"path": "artifacts/misc/notes.txt"
}
],
"summary": {
"errors": 2,
"warnings": 1,
"passed": false
}
}
Finding Codes
| Code | Severity | Description |
|---|---|---|
MISSING_REQUIRED |
error | A required type has no matching artifacts |
CARDINALITY_VIOLATION |
error | Too few or too many artifacts for a type |
STALE_ARTIFACT |
error | Artifact exceeds freshness requirement |
MISSING_COLLECTED_AT |
error | Artifact missing collected_at when freshness required |
INVALID_COLLECTED_AT |
error | Artifact collected_at present but not in required format |
SCHEMA_VIOLATION |
error | Artifact metadata fails type's metadata_schema |
MISSING_METADATA |
error | Artifact missing metadata when type specifies metadata_schema |
CONTENT_TYPE_MISMATCH |
error | Artifact content_type not in type's content_types |
MISSING_CONTENT_TYPE |
error | Artifact missing content_type when type specifies content_types |
PROFILE_NOT_FOUND |
error | Could not resolve declared profile |
OVERLAY_NOT_FOUND |
error | Could not resolve declared overlay |
TYPE_NOT_FOUND |
error | Could not resolve type referenced by requirement |
OVERLAY_TARGET_NOT_FOUND |
error | Overlay modifies a type not present in effective profile |
DUPLICATE_REQUIREMENT_TYPE |
error | Profile or overlay adds requirement for a type that already exists |
DIGEST_MISMATCH |
error | Resolved digest doesn't match profile_lock entry |
DUPLICATE_LOCK_ENTRY |
error | Same identifier appears multiple times in profile_lock |
UNPINNED_DEPENDENCY |
warning | Dependency not pinned in profile_lock |
UNTYPED_ARTIFACT |
warning | Artifact has no semantic_type field |
INVALID_SEMANTIC_TYPE |
warning | Artifact's semantic_type doesn't match identifier format |
UNKNOWN_ARTIFACT |
warning | Typed artifact's semantic_type not in any requirement |
Reference Profiles
Reference types, profiles, and overlays are published under the evidencepack/ namespace. Use these as starting points or reference implementations.
10
Types
3
Profiles
2
Overlays
evidencepack/soc2-basic@v1
Minimum evidence for a SOC 2 security review. Requires SOC 2 Type II report and penetration test summary.
evidencepack/cloud-security@v1
Technical security evidence for cloud-native organizations. Requires cloud config, IAM, VCS, and IdP evidence.
evidencepack/hipaa-overlay@v1
HIPAA-specific overlay. Adds BAA agreement and tightens freshness requirements.
Test Vectors
Use these test vectors to verify your semantic validator implementation. Test vector files will be published in test-vectors/profiles/.
collected_at when freshness is required.collected_at (e.g., offset instead of Z).profile_lock that doesn't match the resolved profile.profile_lock (missing type entries).semantic_type field.CLI Support
The Go SDK includes --semantic flag for profile validation with pinned digests.
Initiated by
Locktivity
We built Evidence Packs in the open because portable, verifiable assurance is a problem bigger than any one vendor.