Skip to content

chore: refresh ENVITED-X integration with reviewed generator features - #14

Draft
jdsika wants to merge 32 commits into
mainfrom
feat/envited-x-pipeline
Draft

jdsika wants to merge 32 commits into
mainfrom
feat/envited-x-pipeline

Conversation

@jdsika

@jdsika jdsika commented May 7, 2026 •

Copy link
Copy Markdown

Purpose

Feature integration branch consumed by downstream ENVITED-X projects. Refreshed on upstream main at 68e7e3d6467d040bccad88216769cd404da0ee41 with the current fork feature heads below. This PR remains draft and is not an upstream submission.

Head: d75f78248bd5530bfa76ef28b283ba37f98d153c. The branch contains 23 feature commits plus one integration reconciliation commit above that upstream base. The six existing commits of #24 are preserved; other included features contribute one commit each. GitHub shows 32 commits against the fork base: these 24 integration commits plus 8 newer upstream commits. Every new integration commit is signed and preserves human authorship, including Rayene Messaoud's rule features.

Included feature heads

Fork PR Source head Feature
#24 084aaa948 feat(rdf): add opt-in diff-stable blank-node labels
#27 dc9f3ea7b docs(owl): document deterministic serialization and --diff-stable
#25 2208d4f0a test(rdf): record the canonicalizer gaps against the extracted library
#26 38f1f9bd0 refactor(rdf): delegate canonicalization to the diffable-rdf library
#19 1e506c200 feat(gen-shacl): translate presence-implies-value rules to SHACL-SPARQL
#20 c89faac06 feat(gen-shacl): add a compositional fallback for rule-to-SPARQL conversion
#23 6a78f262c docs(shacl): document rule-to-SHACL-SPARQL constraint generation
#35 fbc5a66fa test(gen-shacl): cover nested range_expression postconditions
#13 fdee56fd4 fix(shaclgen): emit sh:pattern for pattern constraints inside any_of
#29 4a9d4754d feat(generators): translate class-level boolean expressions alike to SHACL and JSON Schema
#16 8fac49060 feat(jsonschemagen): emit propertyNames from inlined-dict key slot constraints
#4 0342283c1 fix(generators): add --normalize-prefixes flag for well-known prefix names
#31 733544202 feat(gen-owl): honor instantiates on emitted schema resources
#33 4cf65e06a feat(rdf): honor declared annotation ranges in OWL and SHACL
#32 3e1b4ef88 fix(rdf): preserve string-derived anyURI literals across generators
#30 46c2ab1aa fix(generators): enforce range_expression on each value alike in SHACL and JSON Schema
#34 23553b9e8 feat(generators): enforce has_member alike in SHACL and JSON Schema
#36 f2e5389dd docs(rdf): model DCMI agent annotations with declared ranges

#25 and repaired #26 are now included. #26's source head 38f1f9bd0 passed 19 fork checks and 22 upstream checks before integration. Its feature commit is bde215e2a; its existing merge of upstream main is unnecessary on this newer upstream base.

The old #30 inlined-as-node option is replaced by the reviewed generic range_expression implementation. #33 uses declared annotation ranges. #36 contributes its current documentation/tests for that model, replacing the old DCMI property allowlist; #36 remains draft, as requested. Including its current content here does not mark its review resolved.

Integration reconciliation

  • Retained both independently added test/documentation sections and the newer upstream identifier and relative-schema-path fixes.
  • Combined any_of pattern constraints with nested range expressions on the same branch.
  • Preserved both instantiates membership and declared annotation values on OWL type resources.
  • Consolidated identical SHACL helpers shared by class expressions and rule conversion.
  • Added 14 real-consumer interaction cases covering the above constraints and metadata in both RDF label modes; none use mocks.
  • Applied repository formatting, addressing the previous chore: refresh ENVITED-X integration with reviewed generator features #14 quality-check failure. Corrected the inherited claim that diff-stable labels always produce minimal diffs.

Validation

  • Focused generator suites: 1,629 passed, 4 skipped, 3 xfailed; 264 subtests passed.
  • New integration consumer cases: 14 passed.
  • Full runtime suite: 1,878 passed, 323 skipped, 2 xfailed.
  • Repository pre-commit hooks passed, including the new tests.
  • Dependency audit with CI's uv version (0.11.21): no known vulnerabilities or adverse project statuses. The multidict 6.9.1 security update from refactor(rdf): delegate canonicalization to the diffable-rdf library #26 is included.
  • Full compiler suite including slow/Biolink tests: 13,670 passed, 1,960 skipped, 15 xfailed; 264 subtests passed. Notebooks and Kroki excluded locally.
  • Strict Sphinx HTML build, exact-version lockfile check, and both packages' wheel/sdist builds passed.
  • Exact-head remote CI is green: 19 successful checks on d75f78248bd5530bfa76ef28b283ba37f98d153c, including all seven platform jobs, four slow-test jobs, both notebook jobs, Rust generation, documentation, lint, audit, and package build. No pending or failed checks. Docker is intentionally skipped on the fork; the retained limitation below still applies.

Retained limitation

#4 is unchanged apart from integration with current upstream. Its prefixmaps Git dependency remains the known blocker for the Docker build that installs a wheel in an image without Git. The fork's Docker workflow is skipped; a green fork run does not establish that this blocker is resolved. No workaround or dependency-policy change is included.

No feature PR has been merged, and no new upstream PR has been opened. Standards references and feature-specific design limitations remain documented in the individual PRs and generator documentation.

@jdsika
jdsika force-pushed the feat/envited-x-pipeline branch 9 times, most recently from a705c35 to 3d3a52a Compare May 12, 2026 16:35
@jdsika
jdsika force-pushed the feat/envited-x-pipeline branch 3 times, most recently from cab84ad to b2b3dba Compare June 10, 2026 19:42
@rmessaou
rmessaou force-pushed the feat/envited-x-pipeline branch from 97e73d0 to ef7ad85 Compare July 8, 2026 09:02
@jdsika jdsika self-assigned this Jul 11, 2026
@jdsika
jdsika force-pushed the feat/envited-x-pipeline branch from 6aa7702 to 233adc3 Compare September 11, 2026 14:23
@jdsika jdsika changed the title chore: consolidated feature branch for ENVITED-X pipeline chore: feature integration branch for the ENVITED-X pipeline Sep 11, 2026
@jdsika
jdsika force-pushed the feat/envited-x-pipeline branch from 233adc3 to b8a388e Compare September 11, 2026 14:44
"Feature request" and "Bug" GitHub issue templates had duplicated
frontmatters. This patch removes the duplicates.

Signed-off-by: Silvano Cirujano Cuesta <[email protected]>
matentzn and others added 6 commits October 7, 2026 17:40
fix(github-templates): clean-up duplicated frontmatter
…1) (linkml#3973)

* fix(projectgen): fix substring matching; check top keys/dirs are str before setattr

* fix(projectgen): merge generator_args with cli_args; test empty config file

* feat(generator): shared config-file overlay for golang/java generators
noelmcloughlin and others added 25 commits October 8, 2026 11:11
…ml#4076)

* fix(javagen): report a missing --template-file as a usage error

Signed-off-by: noelmcloughlin <[email protected]>

* docs(javagen): --config-file reads the whole generator_args.java section

Signed-off-by: noelmcloughlin <[email protected]>

---------

Signed-off-by: noelmcloughlin <[email protected]>
RDFC-1.0 canonicalization already makes RDF output deterministic:
isomorphic graphs always serialize identically. It does not make output
diffable. Blank nodes are numbered `c14nN` in a single global order, so
inserting one class can renumber every blank node after it and rewrite
most of the file. A one-line semantic change lands as a whole-file diff,
which makes generated OWL/SHACL hard to review and noisy to keep under
version control.

Add a `diff_stable` argument to `canonicalize_rdf_graph()` and a
`--diff-stable/--no-diff-stable` flag to the four RDF generators. When
enabled, blank-node labels are derived from each node's own neighbourhood
via Weisfeiler-Lehman refinement, so an edit relabels only the blank
nodes it actually touches.

Measured churn on a real schema (add one class, count changed lines):

    generator   default   --diff-stable
    owlgen         2091              17
    shexgen         796              50
    shaclgen        291              13
    rdfgen          115              25

Output stays deterministic and isomorphic either way; only the choice of
label changes. Off by default, because enabling it relabels existing
output.

The refinement itself lives in `diffable-rdf`, whose only dependencies
(rdflib, pyoxigraph) are already linkml-runtime dependencies at higher
versions, so this adds no new transitive dependencies.
…-opping

Bump the floor to diffable-rdf 0.3.0 and add the missing uv.lock entry: the
dependency was declared in pyproject.toml but never locked, so "uv lock --check"
and the "uv sync --frozen" anti-malware gate would both have failed CI.

0.3.0 also fixes two defects in the Weisfeiler-Lehman labelling this feature
relies on. Disconnected blank-node components now converge independently, so an
edit in one region no longer relabels an unrelated one. And the suffix used to
tell structurally indistinguishable nodes apart was assigned in c14nN *text*
order, so c14n10 sorted between c14n1 and c14n2 -- adding a tenth tied blank
node relabelled eight of the nine already there, the exact opposite of what this
labelling is for.

Separately, diff_stable=True was silently ignored whenever pyoxigraph refused
the graph and canonicalize_rdf_graph degraded to rdflib. Weisfeiler-Lehman
refinement consumes canonical pyoxigraph quads, and that path exists precisely
because there are none, so the argument could not be honoured -- but the caller
was never told. "shaclgen --include-annotations --diff-stable" reaches it, via
the literal predicate an annotation tag without a ':' produces, and returned
output byte-identical to --no-diff-stable. It now warns, with a regression test
asserting the warning and the byte-identical output that makes silence
misleading.
0.4.0 carries graph.base through the library's rdflib fallback, verifying
that every absolute IRI of the source survives a re-read rather than
dropping the directive outright, and adds a diff_stable parameter to
canonicalize_rdf_graph.

The lock entry is written by hand because the workspace sets
exclude-newer = "7 days", which filters any release younger than that from
resolution; 0.3.0 was pinned the same way for the same reason, and both
become resolvable normally on 2026-09-18. uv lock --check and
uv sync --all-groups both accept the entry.

https://github.com/ASCS-eV/diffable-rdf/releases/tag/v0.4.0
Registers a diffable_rdf pytest marker and applies it to the six
diff_stable tests (including the parametrized per-generator test), so
an external CI job can run 'pytest -m diffable_rdf' against a local
diffable-rdf checkout without pulling in the rest of the suite.

Follow-up to a review comment on this PR requesting canary tests from
linkml running in diffable-rdf's own CI, modeled on the numpydantic
project's tests-linkml.yml workflow.
Expose runtime and LinkML extras with a lazy import and an installation hint.
Move usage guidance into the collaboration guide and shorten generator help.

Assert designed RDF edits, source preservation, generator output and CLI flags.
Check ordinary and extra wheel installs in the existing package build job.
Addresses the second review round.

Remove the installed-wheel checks and the CI step that drove them. They
were a one-off apparatus for something the test suite already has a
mechanism for, and part of it only asserted that uv builds wheel metadata
correctly from pyproject.toml rather than testing anything about linkml.

Test the missing extra with a new mock_missing_import fixture built on the
existing MockImportErrorFinder, so the next optional dependency needs no
fixture of its own. Guard the import the way bigquerygen already does:
find_spec raises under that fixture, so the install hint never surfaced.

Drop the default-value test, restore the pre-existing sort test, and cut
the CLI matrix to the single case that shows the flag reaching the
serializer.

Record the convention in AGENTS.md, point MockImportErrorFinder at the
general fixture, and link the guide from the diff_stable parameter.
gen-owl canonicalizes its output with RDFC-1.0 before serializing, and the
determinism work adds a --diff-stable option on top of it, but neither is
mentioned anywhere in the generator documentation. Describe what is guaranteed
without passing any flag, why RDFC-1.0's sequential blank-node numbering can
still produce noisy diffs across schema edits, and what --diff-stable changes.

Also record the behaviour users meet in practice but cannot discover from
--help: that the same option exists on gen-rdf, gen-shacl and gen-shex, and
that graphs which are not standard RDF -- literal predicates from SHACL
annotation mode, relative IRIs such as the metamodel's bibo:status <testing> --
take an rdflib fallback that stays reproducible across processes, warns, and
deliberately does not honour --diff-stable.
Asserts each correctness property against both linkml's copy and
diffable_rdf, marking whichever implementation does not hold it as a
strict xfail, so the file is a ratchet in both directions.

Nine gaps run one way, two of them silent data corruption. One ran the
other way -- the library dropped @base on the degraded path -- and that
was the last property blocking delegation. diffable-rdf 0.4.0 fixed it,
so that case now passes on both sides and carries no mark.
The implementation was extracted into diffable-rdf at the maintainers'
request in linkml#3295, but linkml kept its own copy and the two
drifted. This deletes the copy and calls the library, which is what the
extraction was for.

Nine correctness fixes come with it, each already asserted in
test_rdf_canonicalize_defects.py and each previously a strict xfail on
the linkml side:

- a base ending in # no longer rewrites every IRI that merely shares its
  prefix (silent corruption: output parsed, meaning changed)
- a shared rdf:List tail is no longer duplicated (9 triples in, 11 out)
- N-Triples refuses a relative IRI instead of writing a file its own
  parser rejects
- literals containing U+2028, U+2029, U+0085 and the other separators
  str.splitlines() treats as line breaks survive the line sort
- degraded RDF/XML, degraded Turtle and json-ld are byte-identical
  across processes
- every format ends with exactly one newline
- a Dataset is refused rather than silently flattened

Behaviour changes for callers: nt output for a graph containing a
relative IRI now raises ValueError rather than writing an unparseable
file, and json-ld is canonicalized rather than handed to rdflib, so it
no longer warns. All four RDF generators produce byte-identical output.

The library reports degradation through logging; linkml reports it
through warnings so it is visible without logging configuration.
_DegradedPathWarnings bridges the two, and the tests pin the properties
that makes load-bearing: the warning is attributed to the caller's line,
the library's logger is left as it was found, a caller who configured
logging still receives the record, and warnings survive an exception.

Signed-off-by: jdsika <[email protected]>
The rules-to-SHACL-SPARQL converter recognised two named patterns.  This
adds the presence-implies-value pattern: a precondition asserting
`value_presence: PRESENT` on one slot, and a postcondition constraining
another slot with `equals_string` or `equals_string_in`.  It reads as
"if the guard slot is present, the target slot must be present and hold
one of the allowed values", and generalises the boolean guard to
arbitrary enum values.  The boolean guard becomes the case
`equals_string: "true"` of this pattern on an xsd:boolean-typed flag.

`equals_string` / `equals_string_in` compare strings.  The metamodel
defines them for slots of range string; enums and types with datatype
xsd:string are treated alike, and a slot without a range takes the
schema's default_range.  On an xsd:boolean-typed slot each value must be
a lexical form of xsd:boolean (XML Schema 1.1 Part 2 section 3.3.2.2:
true, false, 1, 0) and denotes that boolean.  A rule applying them to
any other range is skipped with a warning: its RDF values are typed
literals or IRIs that equal no string.  Built-in type names resolve
without importing linkml:types, as in the main slot loop; the built-in
name of `curie` was misspelt there and is corrected.

Values are compared with `=`, spelled out as the disjunction SPARQL 1.1
section 17.4.1.9 defines `IN` to be, so the RDF 1.1-identical plain and
xsd:string literal forms both match and booleans compare by value;
rdflib evaluates `IN` and triple-pattern constants by term identity.
COALESCE turns the type error that RDFterm-equal raises for
incomparable literals into false, so a spec-conformant engine reports
such a value instead of dropping the row.  Queries are DISTINCT and
project ?path (and ?value where there is one), so each result names the
property and the offending value.  A value containing a backslash
followed by "u" or "U" is split with CONCAT, since codepoint escapes are
replaced before a query is parsed.

The exclusive-value pattern accepts the precondition
`has_member: {equals_string: V}`, which states "V is one of the values".
A bare `equals_string: V` is read the same way, as before, now with a
warning on a multivalued slot: the specification applies a slot
constraint to all members of a collection.  The pattern gets the same
range check and value comparison, and its maximum_cardinality > 1 form
tests HAVING on the COUNT aggregate: expressions projected by SELECT are
not visible to HAVING (SPARQL 1.1 section 18.2.4.2), so
`HAVING (?count > N)` never held and the constraint never fired.

A rule applies to all members of its class, so a class shape now
carries the rules of its ancestors and mixins, as the JSON Schema
generator applies them.  Each rule is translated in the inheriting
class's context, where slot_usage may refine the slots it references.
Classes sharing a class_uri share one shape, which carries each rule
once.  With `open_world: true` "the postconditions may be omitted in
instance data", so an absent target is no violation.

A rule whose conditions carry any operator beyond what a pattern
translates, or name an unknown or identifier slot, is skipped rather
than partially translated.  Each problem with a rule is logged once as a
warning naming the declaring class, the rule's position and the class
shapes it affects.  The metadata fields the operator accounting ignores
are derived from the metamodel.  Slot resolution goes through induced
slots, so slot_usage overrides, slot_uri overrides and alias-form keys
resolve to the IRI sh:path emits.

Behaviour changes for existing schemas: the boolean guard rejects a
string "true" on an xsd:boolean flag and skips a flag whose type has
another datatype IRI (main compared with str()); exclusive-value rules
with maximum_cardinality > 1 now fire; inherited rules are now enforced
on subclass shapes; problems with rules are logged at WARNING instead of
DEBUG; rule results carry sh:resultPath and sh:value; sh:message follows
the rule's in_language or default_language.

Co-authored-by: jdsika <[email protected]>
…ersion

A rule that no named pattern matches is now composed from the operators
it uses: each precondition becomes SPARQL filters on $this, and the
single postcondition slot the union of the ways to violate it, binding
the offending value to ?value (SHACL sections 5.3.1 and 5.3.2).

A condition may use value_presence, required, equals_string,
equals_string_in, minimum_value, maximum_value, a range_expression on
the slot's class (nested to any depth) and has_member.  The semantics
follow the JSON Schema generator's if/then:

- presence: value_presence decides, then required, then a default: a
  precondition requires its slot, a postcondition unless the rule is
  open_world, an inner condition of a nested expression never;
- value operators and range_expression apply to every value of the slot
  (05validation.md: "all members"), has_member to some value;
- equals_string(_in) only on string, enum and boolean slots (the named
  patterns' range check), bounds only on the numeric datatypes of SPARQL
  1.1 section 17.1, guarded with isNumeric, and only finite numbers.

Any other operator, a rule with empty pre- or postconditions or several
postcondition slots, skips the rule with one warning, so a rule is never
partially translated.  A rule skipped part-way no longer reports the
notes of the abandoned translation.

Co-authored-by: jdsika <[email protected]>
The SHACL generator translates LinkML rules into sh:sparql constraints,
but its documentation did not mention them.

Describe the two named patterns and the composed translation with the
operators it supports, the semantics shared with the JSON Schema
generator (presence, every value versus has_member, value comparison,
numeric bounds), inheritance, open_world, --no-emit-rules and the skip
warnings, with an example generated by the generator.  Replace the
dangling "See above for implementation status" in the rules section of
advanced.md with links to the JSON Schema and SHACL generator pages.
The composed rule translation handles a range_expression in a
postcondition with the same code as in a precondition, but its tests
exercised nested postconditions only in a closed world.  Pin the cases
specific to nested postconditions:

- open_world: an omitted or empty wrapper satisfies the rule, and a
  present non-matching value violates it;
- closed world: an empty wrapper satisfies the inner value condition,
  and every member of a multivalued wrapper must satisfy it;
- an enum value with a meaning compares as that IRI, resolved on the
  range class;
- a condition on the range class's identifier skips the rule.

Signed-off-by: jdsika <[email protected]>
The SHACL generator translated any_of branches by dispatching
solely on `any.range` (class, type, enum, or simple datatype).
If a branch specified `pattern:` — either alone or combined
with a range — the constraint was silently dropped, producing
an empty blank node `[ ]` (trivially satisfied) instead of the
intended `[ sh:pattern "..." ]`.

This is a problem for schemas that use pattern alternatives in
`any_of`, such as the SPDX license field where valid values are
either members of a fixed enum (SPDX identifiers), IRIs, or
custom identifiers matching the LicenseRef- pattern defined in
SPDX Specification v2.3 Annex D (ABNF: license-ref =
["DocumentRef-"(idstring)":"]"LicenseRef-"(idstring)).

The fix adds a single check after the range dispatch:

    if any.pattern:
        g.add((range_list[-1], SH.pattern, Literal(any.pattern)))

This correctly handles:
- Pattern-only branches (no range): node gets only sh:pattern
- Range + pattern branches: node gets both sh:datatype and sh:pattern
- Range-only branches (no pattern): unchanged behaviour

The test suite now includes a dedicated schema exercising all
three cases, with assertions on both the generated RDF triples
and pyshacl validation of conforming/non-conforming data.

Signed-off-by: Carlo van Driesten <[email protected]>
…SHACL and JSON Schema

gen-shacl dropped class-level any_of, all_of, exactly_one_of and none_of,
so a class stating "a code or a name is required" generated shapes that
accepted everything.  gen-json-schema translated them, but only for the
declaring class, and read an absent slot differently depending on where
an expression was nested.

Both generators now give class expressions one semantics, shared in
linkml.generators.common.class_expression:

- A class expression constrains every instance of its class, so a class
  carries the expressions of its ancestors and mixins, translated in its
  own context.
- The specification does not say what a slot condition means for an
  absent slot.  As an SQL CHECK constraint (ISO/IEC 9075) reads a
  condition on null, a condition that doesn't decide whether its slot
  may be absent is unknown for an absent slot.  An instance is invalid
  only when an expression is definitely false, and exactly_one_of holds
  when exactly one member is definitely true.  The operators compose,
  and gen-json-schema no longer rejects an instance meeting only the
  first member of an exactly_one_of while accepting one meeting none.

gen-shacl emits sh:or, sh:and, sh:xone and sh:not (the operators'
exact_mappings, SHACL 4.6) over anonymous member shapes, each in its
"not false" form or, under sh:not, its "definitely true" form, where
such a condition also has sh:minCount 1.  Conditions translate required,
value_presence, the cardinalities, minimum/maximum_value, pattern,
equals_string(_in), equals_number and range; a parameter SHACL allows
once per shape that one condition needs twice moves into an sh:and
member.  An operator whose members use anything else is skipped whole,
with one warning naming the shapes it is missing from.

gen-json-schema builds the same forms with anyOf, allOf, oneOf and not,
resolves conditions on the induced slot, and also enforces cardinalities
(minItems / maxItems) and range in class-level conditions.
value_presence: ABSENT on several slots of one expression now requires
each to be absent rather than not all of them present, in rules too.

The slot loop's range dispatch and sh:path computation move into
_add_range and _slot_iri; output for schemas without class expressions
is unchanged.  The compliance tests test_class_any_of and
test_class_any_of_with_required run for SHACL.

Signed-off-by: jdsika <[email protected]>
…nstraints

For an inlined-as-dict slot whose range class has an identifier/key slot, render the
key slot's string-applicable constraints onto JSON Schema propertyNames (draft-06+)
instead of dropping them. In the inlined-dict form the mapping key is the identifier
value, so the key slot's constraints constrain the keys. JSON object keys are always
strings, so only pattern, enum (equals_string_in) and a string const (equals_string)
are emitted; numeric minimum/maximum, numeric const (equals_number) and allOf are
excluded -- a numeric const would otherwise reject every key. structured_pattern is
honored when materialize_patterns is enabled, consistent with value patterns.
Backward compatible: emitted only when a string-applicable key constraint applies.

Signed-off-by: Carlo van Driesten <[email protected]>

Includes the documentation for the emitted propertyNames constraint.
… names

Add an opt-in --normalize-prefixes flag to OWL, SHACL, and JSON-LD
Context generators that normalises non-standard prefix aliases to
well-known names from a static prefix map (derived from rdflib 7.x
defaults, cross-checked against prefix.cc consensus).

Key design decisions:
- Static frozen map (MappingProxyType) instead of runtime
  Graph().namespaces() lookup eliminates rdflib version dependency
- Both http://schema.org/ and https://schema.org/ map to 'schema'
- Shared normalize_graph_prefixes() helper used by OWL and SHACL
- Two-phase graph normalisation: Phase 1 normalises schema-declared
  prefixes, Phase 2 cleans up runtime-injected bindings
- Collision detection: skip with warning when standard prefix name
  is already user-declared for a different namespace
- Phase 2 guard prevents overwriting HTTPS bindings with HTTP variants

The flag defaults to off, preserving existing behaviour.

Includes the documentation for the flag in docs/generators/owl.rst.
Translate explicit instantiates declarations to rdf:type on the emitted
schema resource through one shared helper. Apply the mapping consistently
to schemas, classes, slots, types, enums, and non-literal permissible values.
Literal permissible values cannot be RDF subjects and produce a warning.

Membership types the schema element itself, not its ordinary data instances,
and does not become subclass inheritance. Existing enum representation and
OWL class/individual punning remain intact. No additional flag is needed
because the schema already declares the relationship.

Cover CURIEs, absolute and minted IRIs, literal values, representation modes,
and the distinction between gen-rdf metamodel serialization and gen-owl
ontology assertions. Document the LinkML and OWL specification references.

Signed-off-by: jdsika <[email protected]>
Resolve annotation slots through the metaclasses declared by instantiates,
including imported and inherited definitions. Use their scalar ranges to
preserve the distinction between RDF nodes and literals in both generators.

Replace vocabulary-name coercion with a shared declaration-driven resolver.
rdfs:Resource includes literals, and URI-looking strings alone do not define
an annotation's intended RDF term. Reuse URI validation, reject ambiguous or
unsupported declared representations, and preserve undeclared metadata.
Emit explicit annotations on local OWL types through the same path.

Add functional cross-generator contracts and document explicit modelling,
standards, supported metadata owners and serialization boundaries.

Signed-off-by: jdsika <[email protected]>
A string-derived type with uri: xsd:anyURI holds URI-reference data,
including relative paths. Preserve its typed literal in JSON-LD contexts,
SHACL, ShEx and runtime RDF serialization, matching OWL datatype mode.

Resolve type ancestry and the effective expanded datatype together so
inherited types, prefix aliases and explicit datatype overrides agree.
Legacy generators inspect a copy of their resolved schema instead of
reopening source files. Preserve the existing JSON-LD/OWL IRI option and
keep the runtime independent of a new compiler/runtime shared API.

Exercise actual JSON-LD expansion, Python-to-RDF serialization, SHACL and
ShEx validation, including negative RDF-term substitutions. Test mixed
class/literal JSON-LD and SHACL ranges; general ShEx any_of remains a
documented pre-existing limitation outside this correction.

Signed-off-by: jdsika <[email protected]>
…L and JSON Schema

gen-shacl ignored a slot's range_expression, and gen-json-schema read it
with the two-valued builder of rules, which ignores cardinalities, the
range of a condition, and the slot_usage and aliases of the value class.

Both generators now read it as a class-level expression: each value must
satisfy it as an instance of the range class satisfies a class-level
expression. In SHACL it is an sh:node next to the range's sh:class. On a
class alternative of any_of it applies to that alternative, and in a
slot condition it takes the form of the condition. SHACL skips an
expression it cannot express with a warning, as it skips a class-level
expression.

gen-json-schema applied the value constraints of a slot inlined as a
dict, and the conditions of class expressions on it, to the dict instead
of to each value.

Signed-off-by: jdsika <[email protected]>
has_member asks for at least one value of a multivalued slot satisfying
an expression. gen-shacl ignored it. gen-json-schema translated it as
contains, which an absent slot passes but an empty one fails, applied
only the value operators of the expression, and ignored it in
class-level expressions.

Both generators now read a member as one value, which must satisfy the
value operators, range and range_expression of the expression. A slot
without a value has no member, so has_member decides presence. In SHACL
it is an sh:qualifiedValueShape with sh:qualifiedMinCount 1; in JSON
Schema, contains on a required slot, or some value of a dict inlined by
key. In a slot condition the member takes the form of the condition.
SHACL skips a has_member it cannot express with a warning.

Signed-off-by: jdsika <[email protected]>
Use the generic metaclass annotation mechanism for creator, contributor,
publisher and rights-holder values. Document DCMI rangeIncludes guidance
and the distinction between node identifiers and textual identifiers.

Add OWL/SHACL contracts for declared nodes, literal URL text, contributor
metadata parity and preservation of undeclared OWL annotation values.
No additional compiler allowlist or generator option is needed.

Signed-off-by: jdsika <[email protected]>
…actions

Share identical SHACL helpers from the rule and class-expression features. Preserve combined pattern/range constraints and type metadata through real generated artifacts, with both RDF label modes. Correct the inherited diff-stability guarantee and normalize integrated test formatting.

Signed-off-by: jdsika <[email protected]>
@jdsika
jdsika force-pushed the feat/envited-x-pipeline branch from e3081ef to d75f782 Compare October 8, 2026 14:35
@jdsika jdsika changed the title chore: feature integration branch for the ENVITED-X pipeline chore: refresh ENVITED-X integration with reviewed generator features Oct 8, 2026
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.

6 participants