Skip to content

feat(rdf): preserve declared annotation terms and OWL roles - #33

Open
jdsika wants to merge 1 commit into
fix/string-derived-anyuri-types-stay-literalsfrom
feat/owlgen-iri-valued-metadata
Open

jdsika wants to merge 1 commit into
fix/string-derived-anyuri-types-stay-literalsfrom
feat/owlgen-iri-valued-metadata

Conversation

@jdsika

@jdsika jdsika commented Oct 2, 2026 •

Copy link
Copy Markdown

2026-10-14 — stacked on #32; one rule changed. An annotation value now gets the RDF term its declared range gives a slot value in the other generators:

  • nodeidentifier → a node (unchanged; the DCMI example in docs(rdf): declare DCMI annotation values and OWL roles #36 uses it);
  • curie → a string literal, no longer an IRI node. Its datatype is xsd:string; OWL types curie slots as xsd:string datatype properties, SHACL expects xsd:string literals, and the JSON-LD context and runtime dumper write literals;
  • uri / uriorcurie → an xsd:anyURI literal by default (unchanged; the linkml:types default), and an IRI with gen-owl --xsd-anyuri-as-iri, as for slot values. String-derived xsd:anyURI types stay literals. This uses fix(rdf): preserve string-derived anyURI literals across generators #32's is_xsd_anyuri_range, hence the stacking.

Measurements behind this (per-range RDF terms and SHACL verdicts on main and #32) are summarized in #32's description. Tests: test_curie_range_is_a_string_literal and test_owl_anyuri_value_follows_xsd_anyuri_as_iri fail on the previous head and pass now. Full compiler suite on the rebased head: 12,638 passed.

Declared scalar metadata needs both the correct RDF term and the correct OWL property role. Previously, IRI-valued annotations could lose their identity, metadata slots could be emitted as ordinary data/object properties, and explicit annotation-property declarations still appeared in logical restrictions. An OWL consumer could therefore lose ontology annotations or load invalid OWL.

This change uses existing LinkML declarations: instantiates supplies metadata slots; their scalar ranges select RDF values; implements: [owl:AnnotationProperty] selects the OWL projection. For example, an annotation slot with range: nodeidentifier and that implements mapping emits a node-valued annotation, while range: string preserves literal text, including URL-looking text. Arbitrary custom predicates use the same mechanism.

Contract and standards

  • OWL and SHACL share the declared-range resolver, including imported/inherited metaclasses, local/CURIE/full-IRI tags, language tags, blank nodes and typed literals. Unsupported scalar representations and conflicting metadata declarations fail explicitly.
  • The OWL generator completes the established annotation-property mapping from #2091, implemented by merged #2095. Genuine annotation emissions receive declarations. Built-in annotation-property output IRIs have their specification-defined AP role; explicit incompatible roles fail. An AP cannot also be used as an object/data property, including available imported definitions and expression-only slots whose precise data role is inferred later. Contradictory explicit roles fail.
  • Ordinary metadata-class validation constraints remain in the schema for other consumers. Their AP projection omits OWL logical restrictions; explicit logical AP uses such as class unique_keys, inverses, characteristics and rule conditions fail. LinkML identifier/singular-key flags retain their identity, requiredness and inlining meaning in other consumers; they do not themselves request an OWL hasKey axiom, and AP projection does not enforce their uniqueness in OWL. Legal annotation subproperties and named-IRI domain/range axioms are retained without claiming logical validation of annotation values.
  • Reserved RDF/RDFS/XSD/OWL namespaces follow OWL 2 section 2.4, with the nine built-in annotation-property exceptions. Structural predicates such as imports/versionIRI cannot be passed as annotations.
  • OWL annotation values admit IRIs, anonymous individuals and literals. OWL 2 DL typing constraints require annotation roles to agree with declarations, and OWL RDF parsing uses these roles to recover annotations.
  • LinkML metamodel refinement supplies the existing extension mechanism. DCMI rangeIncludes supplies recommendations rather than an IRI-coercion rule; RDF Schema resources include literals.

Logical uses are recorded as they are generated. Unknown legacy metadata receives an AP declaration only after all logical intent has been collected, so early and late metadata behave consistently. Anonymous and nested rule conditions resolve the owning class attribute, including when it shadows a global slot. Synthetic type-object value properties are registered on every generation, including reuse and preconfigured IRIs.

No new flag or metamodel field is needed. OWL projection is a real choice already expressed by implements: annotation properties deliberately lack OWL logical constraint semantics, while normal data properties retain their existing projection. No DCMI allowlist, value URL heuristic or vocabulary lookup is introduced. Untyped metadata retains the existing fallback, including skipping unknown CURIE prefixes. Untyped annotations and native metadata on already declared logical predicates retain their legacy RDF projection; this is necessary for models such as Biolink and for importing the metamodel itself. It does not silently convert an existing logical property into an AP or establish DL conformance for that legacy projection. Declared annotation/AP requests retain strict collision checks.

Cross-generator behavior and limits

Real consumers check generated OWL, SHACL, JSON Schema, ShEx, Python and schema RDF/JSON-LD. Shape annotations do not become instance constraints. An AP declaration does not erase the metadata class's instance validation in other formats. JSON strings do not distinguish RDF nodes from literals, and generated Python retains its existing coercion behavior.

The published generic YAML example is included directly in the documentation and compiled in tests. With metaclasses disabled, all four native-URI/type-object combinations load, save and reload in OWLAPI 5.5.0 with zero OWL 2 DL profile violations and retained ontology annotations. Class, slot, type, enum and permissible-value annotation assertions also survive the independent consumer.

This is scalar RDF serialization, not complete metadata-extension validation. Structured agent descriptions, native-field RDF export mappings, existing datatype-alias and type-object class/datatype profile defects, and unrelated inferred object/datatype ambiguities remain separate work. LinkML identifiers and singular keys remain distinct from explicit OWL key axioms. Native license remains authoritative and string-valued; native contributors retains its existing identifier representation. This PR does not establish full OWL 2 DL conformance for every schema or the default metaclass projection, and does not claim a completed downstream migration.

Related community requests: #3249, #3543, and #3546. The structured part of linkml#3543 remains outside this change.

Validation

  • Affected declaration/role and existing OWL suites: 325 passed, 3 skipped, including 14 required OWLAPI cases and real cross-generator positive/negative consumer checks. Independent adversarial closeout passed 45 selected regressions; all five reproduced findings are resolved without weakening existing assertions.
  • Full compiler suite: 12,552 passed, 2,028 skipped, 12 xfailed, with required OWLAPI consumers executed on the final source.
  • Runtime suite: 1,824 passed, 323 skipped, 2 xfailed.
  • Complete slow suite including Biolink and metamodel imports: 66 passed, 9 skipped, 3 xfailed. Slow OWL metamodel and CLI snapshot checks passed. Every changed RDF snapshot was audited: only AP declarations were added; all remaining triples are isomorphic.
  • Strict Sphinx documentation, configured pre-commit hooks, Ruff, lock consistency, both packages' wheel/sdist builds, and an isolated installed-wheel smoke test of the documented node/literal/AP output passed.
  • A dedicated CI job prepares pinned OWLAPI dependencies once and requires the consumer tests to execute; the ordinary matrix can omit that external dependency without silently passing the dedicated gate.

All five adversarial findings of the 2026-10-12 review are resolved. CI: see the checks on the current head (including the required OWLAPI consumer job).

Merge order

Stack: #32 → #33 → #36. This is the prerequisite for PR #36, which demonstrates DCMI metadata using this generic implementation. Merge #32, then #33 into main first after review and green final-head CI. Then rebase #36 onto the resulting main commit, retarget its base to main, preserve its single feature commit and rerun CI for its resulting head. A squash merge changes the prerequisite SHA; do not assume the pre-merge stacked head remains valid. Do not merge #36 into this feature branch as a substitute for merging it into main. No merge is performed by this review.

@jdsika jdsika self-assigned this Oct 2, 2026
@jdsika
jdsika force-pushed the feat/owlgen-iri-valued-metadata branch from d674fbc to 6ac1208 Compare October 2, 2026 15:03
@jdsika
jdsika marked this pull request as draft October 5, 2026 10:57
@jdsika jdsika changed the title feat(gen-owl): emit IRI-valued metadata as IRIs feat(rdf): honor declared annotation ranges in OWL and SHACL Oct 5, 2026
@jdsika
jdsika force-pushed the feat/owlgen-iri-valued-metadata branch from 6ac1208 to 4cf65e0 Compare October 5, 2026 11:13
@jdsika
jdsika marked this pull request as ready for review October 5, 2026 11:25
@jdsika
jdsika force-pushed the feat/owlgen-iri-valued-metadata branch from 4cf65e0 to 8f65be9 Compare October 12, 2026 14:24
@jdsika jdsika changed the title feat(rdf): honor declared annotation ranges in OWL and SHACL feat(rdf): preserve declared annotation terms and OWL roles Oct 12, 2026
@jdsika
jdsika force-pushed the feat/owlgen-iri-valued-metadata branch 2 times, most recently from 1e6c491 to 134f7db Compare October 12, 2026 16:14
Resolve declared scalar annotation ranges through imported and inherited
metaclasses in OWL and SHACL. A value gets the RDF term its declared range
gives a slot value: nodeidentifier yields a node, other types a literal of
their datatype (curie included), and gen-owl --xsd-anyuri-as-iri turns uri
and uriorcurie values into IRIs as it does for slot values. Nothing is
coerced from vocabulary names or URL-like text.

Complete the existing implements annotation-property mapping. Declare emitted
metadata predicates, omit AP logical restrictions, and reject AP/data-role
collisions, reserved structural predicates, and unsupported logical AP uses.
Preserve existing ordinary property projection and native metadata fields.

Exercise direct generated artifacts and real OWLAPI round-trips, SHACL,
JSON Schema, ShEx, Python, RDF and JSON-LD consumers. Test the documentation
example and add a required OWLAPI CI gate. Update RDF snapshots only for
annotation-property declarations and document remaining support boundaries.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika
jdsika force-pushed the feat/owlgen-iri-valued-metadata branch from 134f7db to 6120dbf Compare October 14, 2026 06:44
@jdsika
jdsika changed the base branch from main to fix/string-derived-anyuri-types-stay-literals October 14, 2026 06:44
jdsika added a commit that referenced this pull request Oct 14, 2026
Review update replacing the earlier declared-annotation implementation
with the reviewed #33/#36: annotation values get the RDF term their
declared range gives slot values (curie stays a literal; gen-owl
--xsd-anyuri-as-iri makes uri/uriorcurie values IRIs), emitted metadata
predicates are declared as annotation properties, role collisions fail,
and the DCMI example and OWLAPI consumer gate are included. The
integration test declares its metadata slot an annotation property, as
the reviewed contract requires. Snapshots are regenerated; they differ
from the previous ones only by the annotation-property declarations.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
jdsika added a commit that referenced this pull request Oct 14, 2026
Review update replacing the earlier declared-annotation implementation
with the reviewed #33/#36: annotation values get the RDF term their
declared range gives slot values (curie stays a literal; gen-owl
--xsd-anyuri-as-iri makes uri/uriorcurie values IRIs), emitted metadata
predicates are declared as annotation properties, role collisions fail,
and the DCMI example and OWLAPI consumer gate are included. The
integration test declares its metadata slot an annotation property, as
the reviewed contract requires. Snapshots are regenerated; they differ
from the previous ones only by the annotation-property declarations.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant