Skip to content

Tier 3 inference rules and SPARQL are pinned to the 0.6.x UNTP vocabulary: 0.7.0 credentials yield zero products #712

Description

@halleluiaman-ux

Package: @uncefact/untp-test-suite v0.1.0 (monorepo uncefact/tests-untp @ d09f538, tag reference-implementation-v0.3.0)
Tier: 3 (RDF / graph validation, trust-chain & claim verification)
Severity: Tier 3 graph verification produces a false negative for every UNTP v0.7.0 credential, regardless of whether matching DCC/DIA credentials are supplied.

Summary

The Tier 3 N3 inference rules and the SPARQL queries in rdf-utils.ts are written
against the 0.6.x UNTP vocabulary namespaces and the 0.6.x data-model shape. When a
v0.7.0 credential is converted to RDF (via its unified context), its triples use the
new https://vocabulary.uncefact.org/untp/... namespace and the restructured 0.7.0 data
model. The query patterns therefore bind nothing, listAllProducts returns [], and the
verification test fails with:

Verifications
  ✖ should verify all product claims and issuer trust chains
    Should find products to verify: expected [] not to be empty

RDF validity (the tag:rdf sub-tests) passes — the credentials convert to valid RDF;
only the inference/verification layer is incompatible.

Reproduction (minimal)

npx untp-test --tag tier3 --trust-did=did:web:example.org ./dpp-0.7.0.json

with any 0.7.0 DPP (unified context https://vocabulary.uncefact.org/untp/0.7.0/context/).

Observed: tag:rdf PASS, inference "succeeded", but listAllProducts[]
"expected [] not to be empty".

Root cause — namespace AND data-model drift, not just a prefix swap

src/untp-test/rdf-utils.ts (listAllProducts ~L36–66; second query ~L171–186;
getUnattestedIssuersForProduct ~L262+) and the N3 rules under untp-tests/inference
(*-infer-*.n3) use:

PREFIX dpp:  <https://test.uncefact.org/vocabulary/untp/dpp/0/>
PREFIX untp: <https://test.uncefact.org/vocabulary/untp/core/0/>
...
?credential a dpp:DigitalProductPassport .
?credential vc:credentialSubject ?subject .
?subject  untp:product           ?product .          # (A)
?product  schemaorg:name         ?productName .
?subject  untp:conformityClaim   ?claim .            # (B)
?claim    untp:assessmentCriteria ?criterion .       # (C)

What a 0.7.0 DPP actually emits as N-quads (real Reeco example, abridged):

<.../dpp/{id}> <https://www.w3.org/2018/credentials#credentialSubject> <.../product/X> .
<.../product/X> a <https://vocabulary.uncefact.org/untp/Product> .
<.../product/X> <https://vocabulary.uncefact.org/untp/performanceClaim> <.../claim/Y> .
<.../claim/Y>   <https://vocabulary.uncefact.org/untp/conformityTopic>  <.../conformity-topic/Z> .
<.../claim/Y>   <https://vocabulary.uncefact.org/untp/referenceCriteria> <.../scheme/W> .

Differences:

Query assumption (0.6.x) 0.7.0 reality
ns https://test.uncefact.org/vocabulary/untp/{core,dpp}/0/ ns https://vocabulary.uncefact.org/untp/
(A) credentialSubjectuntp:product → product node no intermediate predicatecredentialSubject is the untp:Product node
(B) untp:conformityClaim untp:performanceClaim
(C) untp:assessmentCriteria untp:referenceCriteria
dpp:DigitalProductPassport untp:DigitalProductPassport (under the unified ns)
untp:conformityTopic untp:conformityTopic (name kept, ns changed)

So pattern (A) fails first (no untp:product triple), which alone empties the result set;
(B)/(C)/the namespaces compound it. vc:credentialSubject is the only pattern that still
holds.

Suggested fix (direction)

This needs the inference rules and the SPARQL re-modelled to 0.7.0, not just new
prefixes. Concretely:

  1. Namespaces — introduce a 0.7.0 vocabulary prefix and use it across the N3 rules and
    rdf-utils.ts:

    - PREFIX dpp:  <https://test.uncefact.org/vocabulary/untp/dpp/0/>
    - PREFIX untp: <https://test.uncefact.org/vocabulary/untp/core/0/>
    + PREFIX untp: <https://vocabulary.uncefact.org/untp/>

    and ?credential a dpp:DigitalProductPassport?credential a untp:DigitalProductPassport.

  2. Flatten the product stepcredentialSubject is the product in 0.7.0:

    - ?credential vc:credentialSubject ?subject .
    - ?subject untp:product ?product .
    - ?product schemaorg:name ?productName .
    + ?credential vc:credentialSubject ?product .
    + ?product a untp:Product .
    + ?product schemaorg:name ?productName .
  3. Rename predicates:

    - ?subject untp:conformityClaim   ?claim .
    - ?claim   untp:assessmentCriteria ?criterion .
    + ?product untp:performanceClaim  ?claim .
    + ?claim   untp:referenceCriteria ?criterion .

    (keep untp:conformityTopic, now under the new ns). The DCC-side patterns
    (dcc:assessment, dcc:assessedProduct, untp:conformance, …) need the same
    0.7.0 review.

  4. Given the 0.6.x ↔ 0.7.0 divergence, prefer version-aware rule/query sets (select by
    the credential's UNTP version) over editing in place, so 0.6.x credentials keep working.

Not yet validated end-to-end against a full 0.7.0 trust graph (no DCC/DIA on hand). The
property/namespace mapping above is taken directly from the RDF expansion of real 0.7.0
DPPs; the DCC/DIA-side remap should be confirmed once 0.7.0 conformity/anchor samples are
available.

Implementer-side note (Reeco, 2026-06-11)

Verified independently against real UNTP v0.7.0 DPPs (issuer did:web:ia.reeco.eco): after
adding a Reeco extension @context as the third array element, the credentials pass Tier 1
(valid JSON-LD + W3C VC schema) and pass the official UNTP v0.7.0 schema (Tier 2 diagnostic,
via the suite's own createAjvInstance + validateJsonAgainstSchema). The only remaining
failures are strictly suite-side — this issue — i.e. they are not a property of the
credentials under test.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions