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.

Draft Companion to Evidence Pack Spec v1.0

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.

Design Note
Why separate specs? The core spec focuses on structural correctness and cryptographic integrity. These are stable, well-understood problems. Semantic validation is domain-specific and evolving. By keeping them separate, the core spec can remain stable while profiles iterate based on real-world usage.

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 to
  • overlays - Additional constraints applied on top of the profile
  • profile_lock - Pinned digests for reproducible validation
Core spec validators ignore these fields. A Level 1 validator will accept a pack regardless of whether it satisfies its declared profile. Semantic validation requires a profile-aware validator implementing this companion spec.

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:

  1. Resolve the profile and overlays (fetch or use cached, verify digests if locked)
  2. Merge overlays onto the base profile to produce an effective profile
  3. Validate each artifact against its declared type schema
  4. Check that all required types are present with valid artifacts
  5. 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:

  1. Fetch the example input from input_url
  2. Run the collector with the input
  3. Validate the collector's output against the type's metadata_schema
  4. 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:

  1. Parse the profile field from the manifest
  2. If profile_lock contains a matching id, verify the fetched profile's digest matches
  3. Fetch the profile from a configured registry or local cache
  4. If digest verification fails, reject validation (do not substitute)
Immutability requirement: Per R-028e in the core spec, validators MUST NOT silently substitute a different profile version. If resolution fails, validation must fail explicitly.

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, emit DUPLICATE_REQUIREMENT_TYPE error.
  • modify_requirements -Update existing requirements (matched by type). 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_FOUND error.
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.
Design Note
Why additive only? Overlays cannot remove requirements from a base profile. This ensures that applying an overlay never weakens the overall requirements. If you need a weaker profile, create a new base profile rather than trying to subtract from an existing one.

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:

  1. Build lock index -Index profile_lock entries by id. Emit DUPLICATE_LOCK_ENTRY on duplicates.
  2. Resolve profile -Resolve and verify digest against lock. Emit PROFILE_NOT_FOUND or DIGEST_MISMATCH.
  3. Resolve and merge overlays -For each overlay in order, resolve, verify lock, and merge. Emit OVERLAY_TARGET_NOT_FOUND if modifying missing type.
  4. Resolve types -For each type in effective profile, resolve and verify lock.
  5. Check unpinned dependencies -Emit UNPINNED_DEPENDENCY warning for dependencies not in profile_lock.
  6. Index artifacts -Group artifacts by semantic_type. Emit UNTYPED_ARTIFACT warning for missing type.
  7. Check requirements -For each requirement: check cardinality, freshness, content_type, and metadata.
  8. Check unknown artifacts -Emit UNKNOWN_ARTIFACT warning 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 = indexByIdChecking­Duplicates(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.

Profile

evidencepack/cloud-security@v1

Technical security evidence for cloud-native organizations. Requires cloud config, IAM, VCS, and IdP evidence.

Profile

evidencepack/hipaa-overlay@v1

HIPAA-specific overlay. Adds BAA agreement and tightens freshness requirements.

Overlay
Browse all types, profiles, and overlays

Test Vectors

Use these test vectors to verify your semantic validator implementation. Test vector files will be published in test-vectors/profiles/.

valid-soc2-basic.zip
A pack that satisfies evidencepack/soc2-basic@v1. Should pass with no errors.
PASS
missing-pentest.zip
A pack declaring soc2-basic profile but missing the required pentest report.
MISSING_REQUIRED
stale-soc2.zip
A pack with a SOC 2 report older than 365 days.
STALE_ARTIFACT
missing-collected-at.zip
A pack with artifact missing collected_at when freshness is required.
MISSING_COLLECTED_AT
invalid-collected-at.zip
A pack with malformed collected_at (e.g., offset instead of Z).
INVALID_COLLECTED_AT
digest-mismatch.zip
A pack with profile_lock that doesn't match the resolved profile.
DIGEST_MISMATCH
unpinned-type.zip
A pack with incomplete profile_lock (missing type entries).
UNPINNED_DEPENDENCY
overlay-target-missing.zip
An overlay that modifies a type not in the base profile.
OVERLAY_TARGET_NOT_FOUND
hipaa-overlay-valid.zip
A pack with soc2-basic + hipaa-overlay that satisfies both.
PASS
untyped-artifact.zip
A pack with artifact missing semantic_type field.
UNTYPED_ARTIFACT

CLI Support

The Go SDK includes --semantic flag for profile validation with pinned digests.

Go SDK
Locktivity

Initiated by

Locktivity

We built Evidence Packs in the open because portable, verifiable assurance is a problem bigger than any one vendor.