Conformance matrix¶
This page tracks how each PROV-DM concept maps onto prov’s classes and factory methods, and
how well each serializer round-trips it. It is the audit artefact for Phase 3.5 of the
modernisation roadmap (roadmap step
28) and is revisited at every release as behaviour changes; a JSON-LD column is planned for
3.1.0 once the PROV-JSONLD serializer lands. For prose background on PROV-DM’s six components and
how they group in prov.model, see The PROV Data Model; this page is the detailed,
verified-against-source reference underneath that explanation.
Every cell below was checked directly against the current source: model classes against
src/prov/model/records.py, factory methods and camelCase aliases against
src/prov/model/bundle.py, PROV-N keywords against PROV_N_MAP / ADDITIONAL_N_MAP in
src/prov/constants.py, and round-trip status against the shared test matrix
(src/prov/tests/conftest.py::SHARED_TARGETS, test_statements.py, test_attributes.py).
Round-trip column key¶
JSON / XML / RDF — whether the type round-trips (
deserialize(serialize(doc)) == doc) under the sharedfmttest matrix (SHARED_TARGETS = ("model", "json", "xml", "rdf")inconftest.py). ✓ means every shared case for that concept passes cleanly; a caveat cites the tracking issue and says what still fails.PROV-N — PROV-N is output-only:
provhas no PROV-N parser (issue #122, planned for 3.2.0), so there is no PROV-N round trip to test, only the keywordget_provn()emits. Every PROV-N cell below is additionally subject to issue #223 (QualifiedName.provn_representation()does not escape PROV-N metacharacters —',),,,(,:,;,[,],=— in an identifier’s local part), which is a property of identifiers, not of any one record type; it is noted once here rather than on every row.
More caveats apply across many rows rather than to one:
#217 (RDF): 14 statement-level test cases assert two relations that share one identifier but differ only in
prov:time; PROV-O has no way to represent that (both times serialize onto the same qualified IRI), so those specific test cases are skipped for therdftarget. This is a limitation of same-identifier/differing-time relations, not of the relation types themselves —generation/usage/start/end/invalidationround-trip cleanly in the general case.#224 (XML): the PROV-XML serializer silently drops any
other_attributesentry whose value is the empty string"", regardless of record type.#225 (RDF): a Python
floatattribute (typedxsd:float) can lose precision through RDF, regardless of record type, because the RDF serializer canonicalisesxsd:floatto a short decimal form.#235 (all formats): a
Literalexplicitly typedxsd:longis silently collapsed to a plainintat assertion time, so every serializer emits it asxsd:int— the asserted datatype is lost before serialization.#244 (XML) / #249 (PROV-N) / #256 (RDF): plain Python
intvalues are always typedxsd:intwith no magnitude check, so a value outside the 32-bit range produces schema-invalid XML, a value-invalid PROV-N integer literal, and an ill-typed RDF literal, respectively.#246 (JSON): plain
int/floatattribute values are encoded with a non-string$property, violating the PROV-JSON submission’s typed-literal schema (the output still round-trips throughprovitself).#251 (PROV-N): plain Python
floatvalues are emitted as%g-formattedxsd:float— a different datatype (and, beyond 6 significant digits, a different value) than thexsd:doublethe JSON/XML/RDF serializers assert for the same document.#238 (JSON):
prov:QUALIFIED_NAME-typedLiterals are stored opaquely by the model but resolved toQualifiedNames by the JSON decoder, so such a value mutates across a JSON round trip and breaks document equality.#237 (model): every factory
time=/startTime=/endTime=parameter leaks a rawdateutilParserErroron unparseable strings (instead ofProvException) and rejects the validxsd:dateTimehour-24 lexical form.#259 (model):
Literallanguage tags compare case-sensitively, diverging from RDF 1.1’s case-insensitive language-tag value space (tags are never normalised, so values do survive round trips unchanged).
Component 1 — Entities and Activities¶
Concept (PROV-DM §) |
Model class |
Factory / alias |
PROV-N keyword |
JSON |
XML |
RDF |
|---|---|---|---|---|---|---|
Entity §5.1.1 |
|
|
✓ |
✓ |
✓ |
|
Activity §5.1.2 |
|
|
✓ |
✓ |
✓ |
|
Generation §5.1.3 |
|
|
✓ |
✓ |
✓ (#217 for the 2 same-id/differing-time cases) |
|
Usage §5.1.4 |
|
|
✓ |
✓ |
✓ (#217 for the 2 same-id/differing-time cases) |
|
Communication §5.1.5 |
|
|
✓ |
✓ |
✓ (anonymous qualified communications omit |
|
Start §5.1.6 |
|
|
✓ |
✓ |
✓ (#217 for the 4 same-id/differing-time cases) |
|
End §5.1.7 |
|
|
✓ |
✓ |
✓ (#217 for the 4 same-id/differing-time cases) |
|
Invalidation §5.1.8 |
|
|
✓ |
✓ |
✓ (#217 for the 2 same-id/differing-time cases) |
ProvEntity additionally exposes wasGeneratedBy()/wasInvalidatedBy()
and ProvActivity exposes
used()/wasInformedBy()/wasStartedBy()/wasEndedBy() as self-as-subject chaining
methods (records.py) — the table above lists the ProvBundle factories, which every relation
also has.
Component 2 — Derivations¶
Concept (PROV-DM §) |
Model class |
Factory / alias |
PROV-N keyword |
JSON |
XML |
RDF |
|---|---|---|---|---|---|---|
Derivation §5.2.1 |
|
|
✓ |
✓ |
✓ |
|
Revision §5.2.2 |
|
|
|
✓ |
✓ |
✓ |
Quotation §5.2.3 |
|
|
|
✓ |
✓ |
✓ |
Primary Source §5.2.4 |
|
|
|
✓ |
✓ |
✓ |
Revision, quotation, and primary source are PROV-DM subtypes of derivation, not separate PROV-N
records: prov implements all four with the single ProvDerivation class,
and the three subtype factories call derivation() then add the corresponding prov:type
(confirmed by inspection of bundle.py:1011-1146, and by running get_provn() on a revision()
record — it emits wasDerivedFrom(..., [prov:type='prov:Revision']), not a wasRevisionOf(...)
keyword). ADDITIONAL_N_MAP does carry a wasRevisionOf/wasQuotedFrom/hadPrimarySource
keyword mapping for contexts (such as PROV-XML) that treat these as top-level types; PROV-N
output from this library always uses the base wasDerivedFrom form.
Component 3 — Agents, Responsibility, and Influence¶
Concept (PROV-DM §) |
Model class |
Factory / alias |
PROV-N keyword |
JSON |
XML |
RDF |
|---|---|---|---|---|---|---|
Agent §5.3.1 |
|
|
✓ |
✓ |
✓ |
|
Person / Organization / SoftwareAgent §5.3.1 |
via |
none — see finding below |
|
✓ |
✓ |
✓ |
Attribution §5.3.2 |
|
|
✓ |
✓ |
✓ (anonymous qualified attributions omit |
|
Association §5.3.3 |
|
|
✓ |
✓ |
✓ |
|
Plan §5.3.3 |
via |
— |
— (plan is an ordinary entity referenced by the association’s |
✓ |
✓ |
✓ |
Delegation §5.3.4 |
|
|
✓ |
✓ |
✓ (anonymous qualified delegations sharing (delegate, activity): #226; the encode-side root cause — the qualified node omits |
|
Influence §5.3.5 |
|
|
✓ |
✓ |
✓ (anonymous qualified influences omit |
Finding: PROV-DM defines Person, Organization, and SoftwareAgent as agent subtypes, and Plan
as an entity subtype used with associations. prov has no dedicated classes or factories for the
agent subtypes — you express them with agent("ag", {PROV_TYPE: PROV["Person"]}) — while Plan
needs no special handling at all, since it is just an entity passed as the plan= argument to
association(). This is a documented, intentional design choice
(docs/explanation/prov-dm.md:111-117), not a defect; see finding log for the audit note.
Convenience factories for the three agent subtypes (together with EmptyCollection, see
Component 6) are now tracked as
#260.
Component 4 — Bundles¶
Concept (PROV-DM §) |
Model class |
Factory / alias |
PROV-N keyword |
JSON |
XML |
RDF |
|---|---|---|---|---|---|---|
Bundle constructor §5.4.1 |
|
|
✓ |
✓ |
✓ |
|
Bundle type §5.4.2 |
not implemented — see finding below |
— |
— |
— |
— |
— |
Finding: PROV-DM §5.4.1 defines bundle containment — a named, nestable set of records —
which prov fully implements via ProvDocument.bundle()/add_bundle(); only a
ProvDocument may contain named bundles (is_document()/is_bundle()
in bundle.py distinguish the two at runtime). §5.4.2 additionally lets a bundle’s identifier
denote a first-class entity of type prov:Bundle, so that provenance-of-provenance (e.g. “who
asserted this bundle”) can itself be expressed in PROV. That second half is not implemented:
the PROV_BUNDLE constant and its PROV_N_MAP["bundle"] keyword exist in constants.py but
are consumed only by dot.py (for node styling) — no serializer or
ProvBundle method ever produces a prov:Bundle-typed entity, and
get_provn()’s bundle <id> ... endBundle output is generated structurally (branching on
is_document()), not through that keyword lookup. There is currently no supported way to
attribute a bundle to an agent as a first-class PROV statement. Tracked as
#261.
Component 5 — Alternate Entities¶
Concept (PROV-DM §) |
Model class |
Factory / alias |
PROV-N keyword |
JSON |
XML |
RDF |
|---|---|---|---|---|---|---|
Specialization §5.5.1 |
|
|
✓ |
✓ |
✓ |
|
Alternate §5.5.2 |
|
|
✓ |
✓ |
✓ (the RDF triple is emitted with subject/object transposed relative to the PROV-DM argument order — symmetric-relation-safe, but third-party consumers see the arguments swapped: #258) |
|
Mention (PROV-LINKS) |
|
|
|
✓ |
✓ |
✓ |
Component 6 — Collections¶
Concept (PROV-DM §) |
Model class |
Factory / alias |
PROV-N keyword |
JSON |
XML |
RDF |
|---|---|---|---|---|---|---|
Collection §5.6 |
|
|
|
✓ |
✓ |
✓ |
EmptyCollection §5.6 |
|
none — see finding below |
|
✓ |
✓ |
✓ |
Membership §5.6 |
|
|
✓ |
✓ |
✓ |
Finding: like collections, EmptyCollection is a real PROV-DM type with a real
ADDITIONAL_N_MAP/PROV_BASE_CLS entry in constants.py, so the round-trip machinery
understands it — but there is no empty_collection() factory or empty= flag on collection()
to set the type for you; you would add prov:type: PROV["EmptyCollection"] by hand via
other_attributes. Tracked (together with the agent-subtype factories, see Component 3) as
#260.
Additional attributes¶
Five PROV-DM attributes are usable on (almost) any record and are exercised directly by the
shared attribute test matrix (test_attributes.py, ATTRIBUTE_VALUES in
attribute_values.py) and by test_statements.py’s add_label/add_locations/add_types/
add_value helpers:
Attribute |
Constant ( |
Round-trip notes |
|---|---|---|
|
|
✓ JSON/XML/RDF, including language-tagged literals and multiple values on one record. |
|
|
✓ JSON/XML/RDF across the full |
|
|
✓ JSON/XML/RDF; used throughout the qualified-relation tests (association, usage, generation, …). |
|
|
✓ JSON/XML; RDF is clean for a single value but see #77 ( |
|
|
✓ JSON/XML/RDF. |
Any attribute value (regardless of which of the five above it is) is additionally subject to
#224 (XML drops the empty string "") and
#225 (RDF can lose xsd:float precision) —
both are properties of the value’s type, not of the attribute name — as are the other
value-level caveats listed under the round-trip column key above
(#235,
#238,
#244/#249/#256,
#246,
#251,
#259).
Maintenance¶
This matrix reflects the codebase as of the Phase 3.5 conformance audit (roadmap steps 28–32,
completed 2026-07-11) and should be revisited at each release as serializers change or issues
close. Beyond the per-format round trips above, the audit also confirmed that
ProvBundle.unified() performs an identifier-keyed attribute union rather than
PROV-CONSTRAINTS merging — tracked as the umbrella
issue #253, with the full gap analysis in the
audit findings; the rework is scheduled for 3.0. Release 3.1.0 adds a
PROV-JSONLD serializer; when that lands, this page gains a JSON-LD column
alongside JSON/XML/RDF. See The PROV Data Model for the conceptual background behind
each component, and prov.model for the full class/method API reference.