Skip to content

Seven-Layer Validation

dppvalidator uses a seven-layer validation architecture (plus a Layer 0 detection phase) to ensure Digital Product Passports are structurally correct, type-safe, semantically meaningful, cryptographically verifiable, and supply-chain traceable. The canonical layer + error-code table is pinned by ADR 0006.

Architecture

flowchart TD
    subgraph Input
        A[/"📄 Input Data (JSON)"/]
    end

    subgraph Layer0["Layer 0: Detection"]
        A0["Auto-detect family + version<br/>from $schema, @context, type"]
    end

    subgraph Layer1["Layer 1: Schema"]
        B["JSON Schema Draft 2020-12<br/>Required fields, types, formats"]
    end

    subgraph Layer2["Layer 2: Model"]
        C["Pydantic v2 Models<br/>Type coercion, URL validation"]
    end

    subgraph Layer3["Layer 3: JSON-LD"]
        C2["PyLD Expansion<br/>Context resolution, term validation"]
    end

    subgraph Layer4["Layer 4: Semantic"]
        D["Business rules<br/>Date logic, GTIN checksums, sums"]
    end

    subgraph Layer5["Layer 5: Vocabulary"]
        D2["External code lists<br/>ISO 3166, UNECE Rec 20/46, HS"]
    end

    subgraph Layer6["Layer 6: Plugin"]
        D3["Per-pack rules<br/>e.g. textile TXT, CIRPASS CQ, tyre TYR"]
    end

    subgraph Layer7["Layer 7: Signature (reserved)"]
        E["VC signature verification<br/>DID resolution, Ed25519/ECDSA"]
    end

    subgraph Output
        F[/"✅ ValidationResult<br/>.valid | .errors | .signature_valid"/]
    end

    A --> A0
    A0 --> B
    B -->|"SCH001-SCH099"| C
    C -->|"MDL001-MDL099"| C2
    C2 -->|"JLD001-JLD099"| D
    D -->|"SEM001-SEM099"| D2
    D2 -->|"VOC001-VOC099"| D3
    D3 -->|"plugin-specific"| E
    E -->|"reserved"| F

    style Layer0 fill:#fce4ec,stroke:#c2185b
    style Layer1 fill:#e3f2fd,stroke:#1976d2
    style Layer2 fill:#fff3e0,stroke:#f57c00
    style Layer3 fill:#e0f7fa,stroke:#0097a7
    style Layer4 fill:#e8f5e9,stroke:#388e3c
    style Layer5 fill:#dcedc8,stroke:#558b2f
    style Layer6 fill:#ede7f6,stroke:#5e35b1
    style Layer7 fill:#fff8e1,stroke:#ffa000
    style Output fill:#f3e5f5,stroke:#7b1fa2

Layer 0: Schema Detection

Automatically detects the DPP schema version from the input document.

Detection priority:

  1. $schema URL pattern (e.g., untp-dpp-schema-0.6.1.json or …/v0.7.0/.../DigitalProductPassport.json)
  2. @context URLs:
  3. Legacy (0.6.x): https://test.uncefact.org/vocabulary/untp/dpp/X.Y.Z/
  4. Modern (0.7.0+): https://vocabulary.uncefact.org/untp/X.Y.Z/context/
  5. type array presence → default version
  6. Fallback to dppvalidator.schemas.registry.DEFAULT_SCHEMA_VERSION (currently 0.7.0)
Python
from dppvalidator import ValidationEngine

# Auto-detection (default)
engine = ValidationEngine()

# Pin v0.7.0 explicitly (matches the current default; the flag is optional).
engine = ValidationEngine(schema_version="0.7.0")

# Pin v0.6.1 (legacy) explicitly. A v0.7.0 payload through this engine
# fails fast with VER001 (version mismatch).
engine = ValidationEngine(schema_version="0.6.1")

The full version-handling story (detection internals, default-version constant, adding a new UNTP version) lives in UNTP DPP versions.

Per-version layer dispatch

Layers 1–3 below dispatch through version-keyed tables — the engine selects the right model / rule set / link paths for the detected version. The dispatch is centralised in three tables:

Table Module Layer it powers
_MODEL_BY_VERSION validators/model.py Model layer (Layer 2)
ALL_RULES_BY_VERSION validators/rules/__init__.py Semantic layer (Layer 4)
LINK_PATHS_BY_VERSION validators/deep.py Deep validator (separate from layers 1–5)

Plugin authors can opt into version-aware dispatch by setting applies_to_versions = ("0.7.0",) on their rule class — see the plugin development guide.

Layer 1: Schema Validation

Validates JSON structure against the UNTP DPP JSON Schema.

What it checks:

  • Required fields are present
  • Field types match schema (string, number, array, etc.)
  • String formats (URI, date-time, email)
  • Enum values
  • Array constraints (minItems, maxItems)
  • Object constraints (additionalProperties)

Error codes: SCH001 - SCH099

Layer 2: Model Validation

Validates data against Pydantic models with stricter type checking.

What it checks:

  • Python type constraints
  • URL validation (HttpUrl)
  • Date/datetime parsing
  • Custom field validators
  • Model-level validators (cross-field)

Error codes: MDL001 - MDL099

Layer 3: JSON-LD Semantic Validation

Validates JSON-LD semantics using PyLD expansion algorithm.

What it checks:

  • @context is present and valid
  • All terms resolve during expansion (no undefined terms)
  • Custom terms use proper namespacing
  • Context URLs are reachable

Error codes: JLD001 - JLD099

Python
from dppvalidator import ValidationEngine

# Enable JSON-LD validation
engine = ValidationEngine(validate_jsonld=True)

# Or via layers
engine = ValidationEngine(layers=["schema", "model", "jsonld"])

Layer 4: Semantic Validation

Validates business rules over the parsed Pydantic shape.

What it checks:

  • Mass-fraction sums (composition arrays sum to 100 %)
  • Date relationships (validFrom < validUntil)
  • GTIN checksums (GS1 standard)
  • Cross-reference consistency
  • Hazardous-material safety claims
  • Item-level serial-number rules

Error codes: SEM001 - SEM099

Layer 5: Vocabulary Validation

Validates external code-list references against the bundled controlled-vocabulary tables.

What it checks:

  • ISO 3166-1 alpha-2 country codes
  • UN/CEFACT Rec 20 unit codes
  • UNECE Rec 46 material codes
  • HS codes for product classification

Error codes: VOC001 - VOC099

Layer 6: Plugin Validation

Runs version-aware rules registered by entry-point plugins (dppvalidator.plugins). Every active pack contributes its own prefix; coverage across packs is enumerated by the plugin registry at runtime.

What it checks:

  • Pack-specific business rules — e.g. textile MVP 2025-12-04 (TXT*), CIRPASS-2 quality (CQ*), tyres GDSO Birth/Collection/Retread (TYR*).

Error codes: per-plugin (TXT001TXT099, CQ001CQ099, TYR001TYR099, …).

Layer 7: Signature Verification (reserved)

Verifies Verifiable Credential signatures and DID resolution.

What it checks:

  • DID resolution (did:web, did:key)
  • Signature verification (Ed25519, ES256, ES384)
  • Proof types (Ed25519Signature2020, DataIntegrityProof, JsonWebSignature2020)

Error codes: Reserved. The SIG001SIG099 range is held for future structured signature-verification codes; today the verifier surfaces untyped error strings via VerificationResult.errors.

Python
from dppvalidator import ValidationEngine

# Enable signature verification
engine = ValidationEngine(verify_signatures=True)
result = engine.validate(dpp_data)

# Check verification status
if result.signature_valid:
    print(f"Signed by: {result.issuer_did}")

Deep Validation

For supply chain traceability, use async deep validation to crawl linked documents.

Python
from dppvalidator import ValidationEngine

engine = ValidationEngine()

# Validate with supply chain traversal
result = await engine.validate_deep(
    dpp_data,
    max_depth=3,
    follow_links=["credentialSubject.traceabilityEvents"],
    timeout=30.0,
)

print(f"Total documents: {result.total_documents}")
print(f"All valid: {result.valid}")

Selecting Layers

Python
from dppvalidator import ValidationEngine

# All layers (default)
engine = ValidationEngine()

# Schema only
engine = ValidationEngine(layers=["schema"])

# Model + Semantic (skip schema)
engine = ValidationEngine(layers=["model", "semantic"])

# Full validation with JSON-LD and signatures
engine = ValidationEngine(
    validate_jsonld=True,
    verify_signatures=True,
)

Performance

Benchmark results (1000 iterations, Apple Silicon):

Layer Mean Time Throughput
Model (minimal) 0.012ms 84,387 ops/sec
Model (full) 0.016ms 63,945 ops/sec
Semantic 0.005ms 200,889 ops/sec
Full (Model+Sem) 0.022ms 45,735 ops/sec
Engine Creation 0.001ms 1,524,868 ops/sec

Run benchmarks: uv run python -m benchmarks.run_benchmarks --all

JSON-LD and signature verification depend on network latency (cached after first request).

Next Steps