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:
$schemaURL pattern (e.g.,untp-dpp-schema-0.6.1.jsonor…/v0.7.0/.../DigitalProductPassport.json)@contextURLs:- Legacy (0.6.x):
https://test.uncefact.org/vocabulary/untp/dpp/X.Y.Z/ - Modern (0.7.0+):
https://vocabulary.uncefact.org/untp/X.Y.Z/context/ typearray presence → default version- Fallback to
dppvalidator.schemas.registry.DEFAULT_SCHEMA_VERSION(currently0.7.0)
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:
@contextis present and valid- All terms resolve during expansion (no undefined terms)
- Custom terms use proper namespacing
- Context URLs are reachable
Error codes: JLD001 - JLD099
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 (TXT001–TXT099, CQ001–CQ099,
TYR001–TYR099, …).
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 SIG001–SIG099 range is held for
future structured signature-verification codes; today the verifier
surfaces untyped error strings via VerificationResult.errors.
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.
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¶
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¶
- Validation Guide — Using the validation engine
- API Reference — ValidationEngine API