Summary
Every JSON-LD example in examples/ references the remote context
https://www.omg.org/spec/DPROD/dprod-context.jsonld, and no example expands
into the RDF graph it is meant to denote. Expanding any of them today yields
essentially the empty graph.
The examples are written in bare terms ("type": "DataProduct", "outputPort",
"title"), while the generated context is a bare prefix map that defines none of
them and declares no value coercion. Neither half is usable as it stands.
The fix adopted below — following @VladimirAlexiev's recommendation in #93 — is
to move the examples to prefixed terms and add the missing value coercion to
the context, rather than growing the context to define every bare term. The
adoption material presents the standalone context as what gives DPROD JSON its
semantics; this issue is about making that true.
What the generator produces
spec-generator/main.py:107-119 builds the context as a bare prefix map:
jsonld_context_ontology = {
"@version": 1.1,
"dprod": ontology_namespace_iri,
"xsd": str(XSD),
"owl": str(OWL),
"dcat": str(DCAT),
"dct": str(DCTERMS),
"prov": str(PROV),
"rdfs": str(RDFS),
"rdf": str(RDF),
"sh": str(SH),
"linkedin": str(LINKEDIN)
}
and writes exactly that at spec-generator/main.py:147-150:
with open('dist/dprod-context.jsonld', mode='x', encoding='utf-8') as f:
json.dump({"@context": jsonld_context_ontology}, f, indent=4)
That same dict is also passed as the compaction context for dist/dprod.jsonld
(main.py:143), where a prefix-only context is entirely appropriate. The defect
is reusing it as the application-facing context that the examples consume;
those are two different artifacts with two different jobs.
Why the examples break
examples/dprod-example.json is representative:
{
"@context": "https://www.omg.org/spec/DPROD/dprod-context.jsonld",
"id": "https://www.ekgf.org/data/data-product/permid-data-product",
"type": "DataProduct",
"title": "PermId Data Product",
"dataProductOwner": "https://www.linkedin.com/in/olibage/",
"outputPort": { "type": "DataService", "conformsTo": "https://swagger.io/specification/" }
}
Against the generated context, three independent things go wrong:
-
id and type are undefined, so they are dropped. Every node in every
example becomes a blank node and no rdf:type is ever emitted. (The fix is
for the examples to use @id / @type, not for the context to alias them —
see the Decision section.)
-
The bare terms are undefined. DataProduct, outputPort, inputPort,
isAccessServiceOf, isDistributionOf, endpointURL, securitySchemaType,
dataProductLifecycleStatus, title, description, format, conformsTo
are all dropped. (The fix is to prefix them in the examples.)
-
No value coercion. This one is a genuine context defect and survives the
move to prefixed terms: prefix expansion gives you the predicate IRI but
never makes the value an IRI, so IRI-valued properties produce literals
rather than resources:
# actual
[] dprod:dataProductOwner "https://www.linkedin.com/in/olibage/" .
# intended
:permid-data-product dprod:dataProductOwner <https://www.linkedin.com/in/olibage/> .
Net effect: expanding any DPROD example against the published context yields
essentially the empty graph.
The full set of bare terms used across examples/ is: action, assignee,
assigner, computedOn, conformsTo, dataProductLifecycleStatus,
dataProductOwner, datasetOwner, description, endPointDescription,
endpointURL, format, geographicalCoverage, id, inputPort,
isAccessServiceOf, isDistributionOf, isMeasurementOf, label,
leftOperand, operator, outputPort, permission, refinement,
rightOperand, securitySchemaType, title, type, uid, value — plus the
types DataProduct, DataService, Dataset, Distribution. None are defined.
A second, unused context
spec-generator/globals.py:40-52 defines json_ld_context, which does alias
id/type:
json_ld_context = {
"@vocab": str(RDF),
"dprod": ontology_namespace_iri,
...
"id": "@id",
"type": "@type"
}
It is dead code — nothing in main.py references it. It would also be wrong if
wired up, because "@vocab": rdf: expands DataProduct to
rdf:DataProduct rather than dprod:DataProduct. It should be either fixed and
used, or deleted, so there is one obvious context definition rather than two.
Decision: prefixed terms, per @VladimirAlexiev's recommendation in #93
Two approaches were considered:
(A) Define every bare term in the DPROD context — keep the examples as they
are ("type": "DataProduct", "outputPort": ...) and make the context define
every one of them. Rejected.
(B) Use prefixed terms in the examples — "@type": "dprod:DataProduct",
"dprod:outputPort": ... — and keep the context small. Adopted.
@VladimirAlexiev argued for (B) in #93 from direct experience with GS1 EPCIS:
I strongly recommend to use prefixed terms like dprod:DataProduct, dprod:DataProductAgreement and NOT bare terms like DataProduct, DataProductAgreement
- We did that in EPCIS, and as a result it's harder to use EPCIS together with
other JSONLD contexts.
- The EPCIS context is over-optimized, but that was necessary since EPCIS JSON
doesn't have prefixes.
- EPCIS+CBV are used pretty much alone (not mixed with other data), but I think
DPROD will be mixed with other data on a regular basis
That last point is decisive for DPROD specifically. A data product description is
almost never a standalone document — it sits alongside DCAT catalogues, ODRL
policies, DQV measurements and organisation-specific vocabularies, several of
which already appear in our own examples. A bare-term context claims generic
names like title, format, action, target, source and value for
DPROD's own reading of them, and every consumer who merges DPROD JSON with
another context then has to fight those claims. EPCIS could afford this because
EPCIS JSON has no prefixes and is consumed alone; DPROD has neither excuse.
Approach (B) also degrades honestly. Under (A) a term nobody remembered to define
silently vanishes; under (B) an unprefixed or misprefixed term is visibly wrong.
What the context must contain
Under (B) the context stays close to what is generated today, plus two additions:
-
Value coercion. Prefix expansion alone does not make a value an IRI.
"dprod:dataProductOwner": "https://..." still yields a literal unless the
context declares the coercion:
"dprod:dataProductOwner": { "@id": "dprod:dataProductOwner", "@type": "@id" }
This must be generated, not hand-maintained. dprod-ontology.ttl already
declares 13 owl:ObjectProperty terms — dataProductOwner,
lifecycleStatus, dataProductLifecycleStatus,
informationSensitivityClassification, domain, inputPort, outputPort,
inputDataset, outputDataset, isDistributionOf, isAccessServiceOf,
protocol, securitySchemaType — so the coercion set derives mechanically
from the graph and cannot drift. owl:DatatypeProperty terms such as
purpose get no coercion, or @type from their declared range.
Coercions are also needed for the reused IRI-valued terms the examples use:
dct:conformsTo, dct:format, dcat:endpointURL,
dcat:endpointDescription. Defining these is safe — they are namespaced
names, not generic ones, so they cannot collide with another context's terms.
-
Prefixes for every vocabulary the examples draw on. Currently missing
odrl and dqv, both of which appear in examples today.
Deliberately not included: id/type aliases. Examples should use the
JSON-LD keywords @id and @type directly, which is the same argument one step
further — an alias for a keyword is a bare-term claim on the two most generic
names in the vocabulary.
What the examples must change
This is the larger half of the work and touches every file in examples/:
prefix every term, and replace id/type with @id/@type.
Doing so exposes terms that resolve to nothing in any vocabulary DPROD
references. Ignoring the ODRL terms (which need an odrl: prefix) these are:
| term |
file(s) |
note |
lifecycle |
sba-pool-rates, equity-trade |
DPROD defines dataProductLifecycleStatus / lifecycleStatus, not lifecycle |
endPointDescription |
dprod-example.json |
capital P; presumably dcat:endpointDescription |
schemaCompatiblity |
sba-pool-rates |
misspelt, and undefined even spelt correctly |
environment, sql |
sba-pool-rates |
no corresponding DPROD term |
datasetOwner |
equity-trade |
no corresponding DPROD term |
geographicalCoverage |
sba-pool-rates |
presumably dct:spatial |
computedOn, isMeasurementOf |
data-quality |
DQV terms, never prefixed |
label |
data-quality |
presumably rdfs:label |
value |
data-quality |
presumably dqv:value |
Each needs a decision: map to an existing term, add it to the DPROD ontology, or
remove it from the example. Under approach (A) these would have been quietly
absorbed into the DPROD namespace and never questioned.
No @vocab
Unchanged by the decision above, and reinforced by it. @vocab expands any
undefined term by concatenation onto a fallback namespace, so outputProt
becomes dprod:outputProt — a well-formed IRI, a real triple, no error. With
prefixed terms an undefined term is instead plainly visible as such, which is
what surfaced the table above. @vocab would have hidden every row of it.
@vocab also fixes none of the defects in this issue: it cannot express
"@type": "@id", so IRI-valued properties would still yield literals. It only
makes omissions harder to detect, and the build-time check below depends on
omissions being detectable.
Acceptance criteria
Prior reports — this is not a new finding
This defect was reported by @VladimirAlexiev on 2024-09-25 in #93 and has been
open in substance ever since. That issue states the problem exactly:
Your examples use classes like DataProduct, DataProductAgreement. But these
terms are not defined in the context, so no triples come out.
#93 was closed as completed on 2026-02-25 on the grounds that the generator now
emits a dedicated dist/dprod-context.jsonld. That closure was premature.
Splitting the context out of the ontology dump fixed the nesting problem
(#90, LOADING_REMOTE_CONTEXT_FAILED) but did not define a single term, which
was #93's actual complaint. The present issue is that unresolved remainder, and
credit for the diagnosis belongs to #93.
@VladimirAlexiev also demonstrated the consequence empirically in #92 (still
open, tracked as OMG DPROD-44):
- no DPROD terms are present whatsoever
- The second example produces no triples.
and again in #91 (still open), where the data-quality example reduces to no
NQuads at all in the JSON-LD Playground.
Related open reports of the same underlying cause:
On the design alternative
@VladimirAlexiev's prefixed-terms recommendation in #93 is no longer an open
question — it has been adopted as the approach for this issue. See the Decision
section above.
Related
#247 — two example files do not currently parse as JSON at all. That blocks the
round-trip expansion check in the acceptance criteria above, so it should land
first.
Summary
Every JSON-LD example in
examples/references the remote contexthttps://www.omg.org/spec/DPROD/dprod-context.jsonld, and no example expandsinto the RDF graph it is meant to denote. Expanding any of them today yields
essentially the empty graph.
The examples are written in bare terms (
"type": "DataProduct","outputPort","title"), while the generated context is a bare prefix map that defines none ofthem and declares no value coercion. Neither half is usable as it stands.
The fix adopted below — following @VladimirAlexiev's recommendation in #93 — is
to move the examples to prefixed terms and add the missing value coercion to
the context, rather than growing the context to define every bare term. The
adoption material presents the standalone context as what gives DPROD JSON its
semantics; this issue is about making that true.
What the generator produces
spec-generator/main.py:107-119builds the context as a bare prefix map:and writes exactly that at
spec-generator/main.py:147-150:That same dict is also passed as the compaction context for
dist/dprod.jsonld(
main.py:143), where a prefix-only context is entirely appropriate. The defectis reusing it as the application-facing context that the examples consume;
those are two different artifacts with two different jobs.
Why the examples break
examples/dprod-example.jsonis representative:{ "@context": "https://www.omg.org/spec/DPROD/dprod-context.jsonld", "id": "https://www.ekgf.org/data/data-product/permid-data-product", "type": "DataProduct", "title": "PermId Data Product", "dataProductOwner": "https://www.linkedin.com/in/olibage/", "outputPort": { "type": "DataService", "conformsTo": "https://swagger.io/specification/" } }Against the generated context, three independent things go wrong:
idandtypeare undefined, so they are dropped. Every node in everyexample becomes a blank node and no
rdf:typeis ever emitted. (The fix isfor the examples to use
@id/@type, not for the context to alias them —see the Decision section.)
The bare terms are undefined.
DataProduct,outputPort,inputPort,isAccessServiceOf,isDistributionOf,endpointURL,securitySchemaType,dataProductLifecycleStatus,title,description,format,conformsToare all dropped. (The fix is to prefix them in the examples.)
No value coercion. This one is a genuine context defect and survives the
move to prefixed terms: prefix expansion gives you the predicate IRI but
never makes the value an IRI, so IRI-valued properties produce literals
rather than resources:
Net effect: expanding any DPROD example against the published context yields
essentially the empty graph.
The full set of bare terms used across
examples/is:action,assignee,assigner,computedOn,conformsTo,dataProductLifecycleStatus,dataProductOwner,datasetOwner,description,endPointDescription,endpointURL,format,geographicalCoverage,id,inputPort,isAccessServiceOf,isDistributionOf,isMeasurementOf,label,leftOperand,operator,outputPort,permission,refinement,rightOperand,securitySchemaType,title,type,uid,value— plus thetypes
DataProduct,DataService,Dataset,Distribution. None are defined.A second, unused context
spec-generator/globals.py:40-52definesjson_ld_context, which does aliasid/type:It is dead code — nothing in
main.pyreferences it. It would also be wrong ifwired up, because
"@vocab": rdf:expandsDataProducttordf:DataProductrather thandprod:DataProduct. It should be either fixed andused, or deleted, so there is one obvious context definition rather than two.
Decision: prefixed terms, per @VladimirAlexiev's recommendation in #93
Two approaches were considered:
(A) Define every bare term in the DPROD context — keep the examples as they
are (
"type": "DataProduct","outputPort": ...) and make the context defineevery one of them. Rejected.
(B) Use prefixed terms in the examples —
"@type": "dprod:DataProduct","dprod:outputPort": ...— and keep the context small. Adopted.@VladimirAlexiev argued for (B) in #93 from direct experience with GS1 EPCIS:
That last point is decisive for DPROD specifically. A data product description is
almost never a standalone document — it sits alongside DCAT catalogues, ODRL
policies, DQV measurements and organisation-specific vocabularies, several of
which already appear in our own examples. A bare-term context claims generic
names like
title,format,action,target,sourceandvalueforDPROD's own reading of them, and every consumer who merges DPROD JSON with
another context then has to fight those claims. EPCIS could afford this because
EPCIS JSON has no prefixes and is consumed alone; DPROD has neither excuse.
Approach (B) also degrades honestly. Under (A) a term nobody remembered to define
silently vanishes; under (B) an unprefixed or misprefixed term is visibly wrong.
What the context must contain
Under (B) the context stays close to what is generated today, plus two additions:
Value coercion. Prefix expansion alone does not make a value an IRI.
"dprod:dataProductOwner": "https://..."still yields a literal unless thecontext declares the coercion:
This must be generated, not hand-maintained.
dprod-ontology.ttlalreadydeclares 13
owl:ObjectPropertyterms —dataProductOwner,lifecycleStatus,dataProductLifecycleStatus,informationSensitivityClassification,domain,inputPort,outputPort,inputDataset,outputDataset,isDistributionOf,isAccessServiceOf,protocol,securitySchemaType— so the coercion set derives mechanicallyfrom the graph and cannot drift.
owl:DatatypePropertyterms such aspurposeget no coercion, or@typefrom their declared range.Coercions are also needed for the reused IRI-valued terms the examples use:
dct:conformsTo,dct:format,dcat:endpointURL,dcat:endpointDescription. Defining these is safe — they are namespacednames, not generic ones, so they cannot collide with another context's terms.
Prefixes for every vocabulary the examples draw on. Currently missing
odrlanddqv, both of which appear in examples today.Deliberately not included:
id/typealiases. Examples should use theJSON-LD keywords
@idand@typedirectly, which is the same argument one stepfurther — an alias for a keyword is a bare-term claim on the two most generic
names in the vocabulary.
What the examples must change
This is the larger half of the work and touches every file in
examples/:prefix every term, and replace
id/typewith@id/@type.Doing so exposes terms that resolve to nothing in any vocabulary DPROD
references. Ignoring the ODRL terms (which need an
odrl:prefix) these are:lifecyclesba-pool-rates,equity-tradedataProductLifecycleStatus/lifecycleStatus, notlifecycleendPointDescriptiondprod-example.jsonP; presumablydcat:endpointDescriptionschemaCompatiblitysba-pool-ratesenvironment,sqlsba-pool-ratesdatasetOwnerequity-tradegeographicalCoveragesba-pool-ratesdct:spatialcomputedOn,isMeasurementOfdata-qualitylabeldata-qualityrdfs:labelvaluedata-qualitydqv:valueEach needs a decision: map to an existing term, add it to the DPROD ontology, or
remove it from the example. Under approach (A) these would have been quietly
absorbed into the DPROD namespace and never questioned.
No
@vocabUnchanged by the decision above, and reinforced by it.
@vocabexpands anyundefined term by concatenation onto a fallback namespace, so
outputProtbecomes
dprod:outputProt— a well-formed IRI, a real triple, no error. Withprefixed terms an undefined term is instead plainly visible as such, which is
what surfaced the table above.
@vocabwould have hidden every row of it.@vocabalso fixes none of the defects in this issue: it cannot express"@type": "@id", so IRI-valued properties would still yield literals. It onlymakes omissions harder to detect, and the build-time check below depends on
omissions being detectable.
Acceptance criteria
@id/@typerather thanid/type.DCTERMS, ODRL, DQV or an explicitly declared example namespace. The table
above is resolved rather than papered over.
dist/dprod-context.jsonlddeclares prefixes for every vocabulary theexamples use, and
"@type": "@id"on every IRI-valued property, generatedfrom the ontology's
owl:ObjectPropertydeclarations rather thanhand-listed.
examples/against the generated context producesthe intended triples — typed nodes with stable IRIs, and object properties
pointing at resources rather than literals.
so context and examples cannot drift apart again (see validate all examples by converting them to turtle/trig #92).
globals.json_ld_contextis either wired up correctly or removed.Prior reports — this is not a new finding
This defect was reported by @VladimirAlexiev on 2024-09-25 in #93 and has been
open in substance ever since. That issue states the problem exactly:
#93 was closed as completed on 2026-02-25 on the grounds that the generator now
emits a dedicated
dist/dprod-context.jsonld. That closure was premature.Splitting the context out of the ontology dump fixed the nesting problem
(#90,
LOADING_REMOTE_CONTEXT_FAILED) but did not define a single term, whichwas #93's actual complaint. The present issue is that unresolved remainder, and
credit for the diagnosis belongs to #93.
@VladimirAlexiev also demonstrated the consequence empirically in #92 (still
open, tracked as OMG DPROD-44):
and again in #91 (still open), where the data-quality example reduces to no
NQuads at all in the JSON-LD Playground.
Related open reports of the same underlying cause:
as a validation workflow. That is the same build-time check proposed in the
acceptance criteria above; validate all examples by converting them to turtle/trig #92 should be considered the canonical issue for it,
and this issue depends on it.
examples fail before the term-definition problem is even reached. Fixing this
issue without DPROD artifacts fail to resolve at their OMG NS URLs. Return 404 #232 leaves the examples broken for external consumers.
correctly fixed.
On the design alternative
@VladimirAlexiev's prefixed-terms recommendation in #93 is no longer an open
question — it has been adopted as the approach for this issue. See the Decision
section above.
Related
#247 — two example files do not currently parse as JSON at all. That blocks the
round-trip expansion check in the acceptance criteria above, so it should land
first.