Runs the service criteria of dpp-criteria against listed Digital Product Passport (DPP) services and reports "N of M automated checks passed" per service and run.
Results are results of automated checks only. They are no certification and do not establish a presumption of conformity. dpp-validator is part of a research project and shares what we currently know; the list of services makes no claim to be complete and is not a legal assessment.
Passport criteria (a published passport behind a product identifier) are checked by dpplint; this repository covers the behaviour of the DPP service and its API.
- Check types
tlsandhttpfor all criteria withtarget: serviceandmethod: automated. automated-authandself-declaredcriteria are listed asskippedwith the reason and areason_code; passport criteria are listed undernot_run.- Features a service lists under
not_implementedgiveskippedwithreason_code: not_implemented; features it does not declare at all givenot_applicable. - Two kinds of entries in
services/of dpp-criteria (entry.source): entries maintained by the operator are run in full every day; entries from our research are run read-only (only GET, HEAD and OPTIONS) once a week, or not at all ifcheck.modeisnone.--read-onlymakes any run read-only; criteria with other methods areskippedwithreason_code: not_sent. - Open software without a public instance (
check.mode: test-instance-weekly) is run in full once a week on a short-lived test instance of our own, started with the current release and discarded after the run (test-instances/). The results name the software version; certificate, TLS and HTTP version checks and the criteria the entry lists undertest_instance.not_applicableareskippedwithreason_code: not_applicable, because they would describe our test environment. - The example passports of an entry (
example_passports) are checked with the API of dpplint in the same run as the service: daily for operator entries, weekly for research entries, also when the service itself is not checked. - Run with GitHub Actions (
.github/workflows/daily.yml, every day at about 04:17 UTC, the weekly checks on Mondays; started by hand with the inputweeklyfor both): the JSON results are kept on the branchresults(runs/<date>/,latest/, dpplint results underlinter/), and the result pages andavailable-services.jsonare published on GitHub Pages: https://ownyourdata.github.io/dpp-validator/. - Requests carry the User-Agent
dpp-validator/<version> (+https://github.com/OwnYourData/dpp-validator; office@ownyourdata.eu)and go one after the other. - Not yet: rating over several days (
history, DPP-ID-002).
The criteria are not copied into this repository. They are read at run time
from a dpp-criteria checkout at a fixed commit; the Docker image contains the
commit pinned in DPP_CRITERIA_REF, and every result names
the commit it was produced with. Criteria and service entries are checked
against the JSON Schemas of the same checkout.
CRITERIA-FORMAT.md
defines the check types; the comments at the top of each file under
lib/dpp_validator/checks/ say how this runner applies them. Gaps in the
format are resolved by pull requests to dpp-criteria, not in the runner.
Per criterion the result is passed, warning (passed, with a remark),
failed or skipped (with a reason and a reason_code: not_applicable,
not_implemented, no_evidence, needs_credentials, not_sent,
unreachable or not_evaluated, see "Results" in CRITERIA-FORMAT.md). Only
criteria with status: active count in "N of M"; passed and warning count
as passed, skipped is not counted. Criteria with status: proposed are
reported separately as "proposed, not counted".
./build.sh
docker run --rm oydeu/dpp-validator:latest test
docker run --rm -v "$PWD/results:/app/results" oydeu/dpp-validator:latest run --service ownyourdata-dpp-servicerun checks one entry: in full for an operator entry, read-only for an entry
from our research, in full on a running test instance with
--test-instance <info file> (see test-instances/);
an entry with check.mode: none is refused. cycle does what the scheduled
workflow does for one cadence, with the dpplint checks of the example
passports; --test-instances <dir> gives the info files of the started test
instances:
docker run --rm -v "$PWD/out:/app/out" oydeu/dpp-validator:latest cycle --cadence daily --output out
docker run --rm -v "$PWD/out:/app/out" oydeu/dpp-validator:latest cycle --cadence weekly --output outservices --cadence daily|weekly lists the entries whose service is run with
that cadence, services --test-instances those checked on our own test
instance.
A service that is not listed is checked from a local entry file, read-only:
docker run --rm -v "$PWD/local:/app/local" oydeu/dpp-validator:latest run --service local/service.yaml --output local/result.json --read-onlydocker run --rm -v "$PWD/out:/app/out" -v "$PWD/site:/app/site" oydeu/dpp-validator:latest site --results out --output site --this-service ownyourdata-dpp-service
writes the result pages from the entries of the dpp-criteria checkout and the
JSON results, as the workflow does: site/index.html (overview),
site/services/<id>.html (one page per entry), site/results/,
site/linter/ and site/available-services.json.
./build.sh <ref> builds with another dpp-criteria commit, branch or tag
(resolved to its commit). The JSON result is written to
results/<service id>.json; --output - prints it instead.
Without Docker (Ruby 3.1 or later, a dpp-criteria checkout next to this one):
bundle install
DPP_CRITERIA_DIR=../dpp-criteria bundle exec rake test
bin/dpp-validator run --criteria ../dpp-criteria --service ownyourdata-dpp-serviceThe checks need a direct connection to the service: TLS and HTTP version checks are meaningless behind a proxy that terminates TLS.
{
"validator": { "name": "dpp-validator", "version": "0.1.0" },
"notice": "Results of automated checks only. ...",
"service": { "id": "...", "name": "...", "operator": "...", "api_base": "...", "features": [], "not_implemented": [] },
"run_at": "2026-09-28T12:00:00Z",
"mode": "full",
"dpp_criteria": { "repository": "https://github.com/OwnYourData/dpp-criteria", "commit": "<full commit>" },
"summary": {
"text": "N of M automated checks passed", "passed": 0, "failed": 0, "warnings": 0, "skipped": 0,
"skipped_by_reason": { "not_applicable": 0 },
"proposed_not_counted": { "text": "...", "passed": 0, "failed": 0, "warnings": 0, "skipped": 0, "skipped_by_reason": {} }
},
"criteria": [
{ "id": "DPP-API-013", "version": 1, "status": "proposed", "title": "...",
"description_url": "https://github.com/OwnYourData/dpp-criteria/blob/<commit>/criteria/README.md#dpp-api-013", "level": "MUST",
"target": "service", "method": "automated", "check_type": "http", "result": "passed",
"messages": [], "details": ["step 1 (GET /dpps/{dppId}): HTTP 200 over HTTP/2"], "counted": false }
],
"not_run": [ { "id": "DPP-DAT-014", "target": "passport", "reason": "..." } ]
}messages holds failures and warnings (severity error or warning),
reason and reason_code the reason for skipped, details what the runner
observed. mode is full, read-only or test-instance; a run on a test
instance has test_instance (software, version, release, images,
started_at, setup, test_passport, test_data) and no api_base in
service. skipped_by_reason counts the
skipped criteria per reason_code (only codes that occur).
description_url links to the readable description of the criterion in
criteria/README.md of the dpp-criteria commit used; it is absent if that
commit has no such page.
cycle writes linter/<id>.json for every entry with example passports:
{
"service_id": "ownyourdata-dpp-service",
"checked_at": "2026-10-18T04:20:00Z",
"dpplint": "https://dpplint.ownyourdata.eu",
"passports": [
{ "product_id": "https://...", "label": "reference passport", "lint_url": "https://dpplint.ownyourdata.eu/?productId=...",
"status": "checked", "dpp_criteria": "<commit>",
"summary": { "text": "5 of 5 automated checks passed", "passed": 5, "failed": 0, "total": 5, "warnings": 1, "skipped": 1, "proposed_failed": 0 },
"criteria": [ { "id": "DPP-ID-016", "title": "...", "status": "active", "result": "warning" } ] }
]
}status is checked or error (with error, e.g. when dpplint cannot be
reached).
The list of all entries with their latest results, for pages that show it
(e.g. the start page of a DPP service). Entries come in this order: the entry
named with --this-service, then operator entries, then entries from our
research, each by name.
{
"generated_at": "2026-10-18T04:25:00Z",
"notice": "Results of automated checks only, from a research project ...",
"counting": "passed: active criteria with result passed or warning; ...",
"services": [
{
"id": "ownyourdata-dpp-service", "name": "OwnYourData DPP Service", "url": "https://dpp-service.ownyourdata.eu/",
"provider": { "name": "OwnYourData", "country": "AT" },
"this_service": true,
"entry": { "source": "operator", "as_of": "2026-10-18" },
"licence": { "text": "Apache-2.0", "spdx": ["Apache-2.0"] },
"linter": { "passports": [
{ "product_id": "https://...", "label": "reference passport", "lint_url": "https://dpplint.ownyourdata.eu/?productId=...",
"summary_text": "5 of 5 automated checks passed", "passed": 5, "total": 5, "failed": 0, "warnings": 1,
"checked_at": "2026-10-18", "note": null } ] },
"validator": { "status": "checked", "mode": "full", "cadence": "daily",
"details_url": "https://ownyourdata.github.io/dpp-validator/services/ownyourdata-dpp-service.html",
"summary_text": "15 of 15 automated checks passed", "passed": 15, "total": 15, "failed": 0,
"warnings": 0, "proposed_failed": 1, "checked_at": "2026-10-18" }
}
]
}Counting, for linter and validator alike: passed are the active
criteria with result passed or warning, failed those with result
failed, total is passed + failed, and warnings says how many of the
passed ones have a warning (they are included in passed). Skipped criteria
are not counted. summary_text is the text of dpplint or dpp-validator; a page
shows these numbers as "5 of 5 passed · 1 warning".
linter.passportsis empty, withlinter.note"no public example passport", if the entry lists none. A passport not checked yet hasnote: "first check pending", one whose check failed the error innote; both without numbers.validator.statusischecked,pending(no run yet) or, for an entry withcheck.mode: none, itscheck.reason:needs_credentials,no_public_instanceorno_en18222_paths.modeisfull,read-onlyortest-instance(in full on our own short-lived test instance; thentest_instancegivessoftware,versionandreleaseof the last run),cadencedailyorweekly.
lib/dpp_validator/checks/– one class per check type, registered inchecks.rbtest-instances/– setups of our own short-lived test instances, one directory per entrylib/dpp_validator/transport/– HTTP/1.x and HTTP/2 over TLS with ALPN, TLS version probeslib/dpp_validator/from_dpplint/–EcmaRegexp,IRegexp,HeaderAssertion, copied unchanged from dpplint (source commit in each file header), with their tests undertest/from_dpplint/test/– Minitest; local HTTPS test servers with their own CA, HTTP/1.1 and HTTP/2assets/oyd-mark.svg– the OwnYourData mark next to the title of the result pages, also their iconconfig/funding.yml– the funding notice in the footer of the result pages and in this README, the one place where its wording is kept
http-2 (MIT), janeway-jsonpath
(MIT; match()/search() and comparisons with a bare @ are adapted in
json_path.rb), json_schemer (MIT).
The DPP Service Validator is part of PACE-DPP – Promoting and Accelerating a Digital Product Passport based Data-Service Ecosystem. This Lighthouse Project has been made possible by financial contributions from the Austrian Federal Ministry of Innovation, Mobility and Infrastructure (BMIMI, formerly BMK), supported by the Austrian Research Promotion Agency (FFG) under grant number 917177, as well as from the German Federal Ministry of Research, Technology and Space (BMFTR, formerly BMWK), supported by the German Research Promotion Agency (DLR-PT).
Apache License 2.0 – see LICENSE.