← All postsAgile Ontology Engineering · Part 3

Versioning an Ontology as a Public API: Persistent IRIs, Semantic Releases, and the Maintenance Loop

Ontology EngineeringSemantic WebCI/CDStandardsVersioning

An ontology nobody uses can change freely. The moment it becomes the shared vocabulary for three ETL pipelines, a SHACL validation service, and an LLM agent’s tool layer, every edit is a potential breaking release for systems you may not control and cannot always test against.

This is the part of ontology engineering that catches teams off guard. The modeling phase feels creative and collaborative. The versioning phase feels like bureaucracy. But the discipline of semantic versioning applied to ontologies is not overhead: it is the mechanism that lets a shared vocabulary evolve without coordination failures. Without it, someone renames a property for clarity on a Tuesday, and by Friday three dashboards, a compliance report, and a GraphRAG prompt template have failed silently, and the incident post-mortem traces it to a single well-intentioned rename.

The cure is not to freeze the ontology. Cautious, unchanging ontologies fossilize into technical debt. The cure is the discipline software engineering already invented: semantic versioning, persistent identifiers, and automated breaking-change detection.

A well-published ontology is a promise. The IRI is the address. The version is the contract. Both need to be permanent and machine-readable for the ecosystem that depends on them to function.

MAJOR.MINOR.PATCH for OWL

Software engineers reach for semantic versioning instinctively. The intuition maps directly to ontology changes once you understand what counts as a breaking change in a semantic model.

MAJOR Breaking change. Removes a class, renames a property, changes a declared domain or range, splits or merges classes. Any consumer using the changed term will break without explicit migration work. MINOR Additive, backward-compatible change. New class, new property, new SHACL shape, new CQ coverage, new equivalence mapping. Consumers need not change: they simply gain new vocabulary to optionally adopt. PATCH Fixes and editorial changes with no functional effect. Corrected label, fixed typo in a comment, tightened a SHACL pattern without altering the shape's semantics, added a missing rdfs:seeAlso annotation.

What Counts as a Breaking Change

The MAJOR category requires careful thought because breaking changes in an ontology are subtler than breaking changes in a software API.

Removing a class or property is obviously breaking: any SPARQL query, SHACL shape, or ETL mapping referencing the removed term fails immediately.

Renaming an IRI is always a MAJOR change, even if it looks cosmetic. Renaming proc:supplierCode to proc:vendorCode breaks every SPARQL query that references the old IRI, every SHACL shape using sh:path proc:supplierCode, and every tool description in an LLM agent’s prompt template. The IRI is the identifier: its string value matters to every consumer.

Changing a declared domain or range is breaking because OWL reasoners use domain and range axioms for inference. Narrowing a range from xsd:string to a specific class changes what instances the reasoner will accept as valid, which can cause previously-valid data to fail consistency checks.

Weakening a SHACL constraint can be breaking in a different direction. If a sh:maxCount 1 shape is relaxed to sh:maxCount 5, consumers that assumed cardinality-1 data may fail when they encounter multiple values they were not designed to handle.

Tightening a SHACL constraint (adding sh:minCount 1 to a previously optional property) is MAJOR for data producers but MINOR for consumers. This asymmetry is worth acknowledging in your release notes.

MAJOR
Remove a class or property
Rename any IRI term
Change domain or range
Split or merge classes
MINOR
New class or property
New SHACL shape
New CQ coverage
Add owl:equivalentClass
PATCH
Fix label or comment typo
Tighten sh:pattern (editorial)
Add missing rdfs:seeAlso
Add owl:deprecated annotation
Use Bubastis in CI to classify every change automatically. Fail the gate if a MAJOR change targets a MINOR release branch.
⚠️
Renaming is always MAJOR, even when it looks cosmetic Renaming proc:supplierCode to proc:vendorCode seems harmless. Every SPARQL query, every SHACL shape, and every GraphRAG tool description referencing the old IRI breaks silently. The correct approach: mark the old term with owl:deprecated true, add a rdfs:comment pointing to the replacement, and keep the old term live for one full major version before removing it.

Persistent Identifiers: The Two-IRI Pattern

Every ontology published for external consumption needs two distinct IRIs, each serving a different purpose. Confusing them is one of the most common publishing mistakes.

The ontologyIRI: Your Permanent Address

The ontologyIRI is the IRI you declare in owl:Ontology. It is the address you publish in documentation, papers, and cross-references. It must never change: not when you move hosting providers, not when you restructure your namespace, not when you rename the project. It is a commitment to the world.

Because it must be permanent but the content it points to changes with each release, the ontologyIRI is not a direct file URL. It is a persistent identifier that resolves via HTTP redirect to the current release. The standard mechanism is an HTTP 303 (See Other) redirect: a request to https://w3id.org/proc/ redirects to the hosted Turtle file for the current version.

The versionIRI: An Immutable Snapshot

Each release gets a versionIRI that is an immutable snapshot: the content at that address never changes. A consumer that needs to pin to a specific release uses the versionIRI directly. A consumer that always wants the latest follows the ontologyIRI redirect.

owl:ontologyIRI
https://w3id.org/proc/
Permanent · never changes · bookmark this
HTTP 303 redirect to current release
owl:versionIRI (current)
https://w3id.org/proc/2.1.0
Immutable snapshot · never edit after release
<div class="iri-item" style="background: var(--surface); border-color: var(--border);">
  <div class="iri-label">owl:versionIRI (previous)</div>
  <div class="iri-value">https://w3id.org/proc/2.0.0</div>
  <div class="pipeline-sub" style="margin-top: 0.4rem">Still resolves · consumers pinned here are safe</div>
</div>
The ontologyIRI is the stable address you publish. The versionIRI is the immutable snapshot. Consumers that need stability pin the versionIRI; tooling that always wants latest follows the 303 redirect.

Declaring Both in Turtle

<https://w3id.org/proc/>
    a owl:Ontology ;
    owl:versionIRI <https://w3id.org/proc/2.1.0> ;
    owl:versionInfo "2.1.0" ;
    dcterms:modified "2026-07-19"^^xsd:date ;
    dcterms:title "Procurement Ontology" ;
    dcterms:creator <https://orcid.org/0000-0000-0000-0001> ;
    owl:priorVersion <https://w3id.org/proc/2.0.0> ;
    owl:backwardCompatibleWith <https://w3id.org/proc/2.0.0> .

The owl:backwardCompatibleWith annotation asserts that MINOR and PATCH releases are compatible with the named prior version. MAJOR releases omit this annotation, signaling to consumers that migration is required.

Setting Up w3id.org

w3id.org is a free, community-maintained persistent IRI service operated by the W3C Permanent Identifier Community Group. Obtaining a persistent IRI there is a GitHub pull request:

  1. Fork the w3id.org repository on GitHub.
  2. Create a subdirectory for your namespace, e.g. proc/.
  3. Add a .htaccess file that redirects requests to your hosting URL:
Options -MultiViews
Header set Access-Control-Allow-Origin *
Header set Content-Type text/turtle

RewriteEngine on
RewriteRule ^$ https://raw.githubusercontent.com/your-org/proc-ontology/main/releases/latest/ontology.ttl [R=303,L]
RewriteRule ^([0-9]+\.[0-9]+\.[0-9]+)$ https://raw.githubusercontent.com/your-org/proc-ontology/main/releases/$1/ontology.ttl [R=303,L]
  1. Submit the pull request. Reviews are typically done within a few days.

The redirect rules serve two patterns: https://w3id.org/proc/ redirects to the latest release, and https://w3id.org/proc/2.1.0 redirects to the specific release snapshot. If you later move hosting from GitHub to your own server, you update the .htaccess redirect: the w3id.org IRI stays permanent forever.


Automated Breaking-Change Detection

Manual changelog entries are unreliable. Developers forget to flag breaking changes, underestimate their impact, or misclassify MAJOR changes as MINOR. Two tools automate this and belong in your CI pipeline.

Bubastis: Axiom-Level OWL Diff

Bubastis computes the symmetric difference between two OWL ontologies at the axiom level. It produces a machine-readable XML diff listing added and removed axioms, and classifies changes by type. In CI, you use it to check whether a PR targeting a MINOR release branch contains any MAJOR-severity axiom removals:

bubastis \
  -ontology1 releases/2.0.0/ontology.ttl \
  -ontology2 ontology.ttl \
  -ignoreAnnotations true \
  -output bubastis-diff.xml

# Fail the gate if any axioms were removed
if grep -q "Removed Axiom" bubastis-diff.xml; then
  echo "ERROR: Breaking change (removed axiom) detected on a MINOR release branch."
  cat bubastis-diff.xml
  exit 1
fi

The -ignoreAnnotations true flag tells Bubastis to skip annotation property changes (labels, comments) in the diff, because annotation changes are always PATCH and would create noise in a breaking-change check.

ROBOT diff: Human-Readable Changelog

ROBOT’s diff command produces a Markdown table of changes between two ontology versions. It is less precise than Bubastis for automated CI classification, but produces excellent human-readable changelogs:

robot diff \
  --left releases/2.0.0/ontology.ttl \
  --right ontology.ttl \
  --output CHANGELOG-draft.md \
  --labels true

With --labels true, the output includes rdfs:label values alongside IRIs, making the changelog readable by stakeholders who do not know the IRIs by memory.

In practice, run both on every PR targeting a release branch: Bubastis for the automated gate, ROBOT diff for the human-readable review comment that gets posted to the PR.

The Combined CI Step

- name: Detect breaking changes
  run: |
    PREV_RELEASE=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "v0.0.0")
    PREV_VERSION=${PREV_RELEASE#v}

    if [ -f "releases/${PREV_VERSION}/ontology.ttl" ]; then
      # Human-readable diff for PR review
      robot diff \
        --left releases/${PREV_VERSION}/ontology.ttl \
        --right ontology.ttl \
        --output CHANGELOG-draft.md \
        --labels true

      # Machine-readable classification
      bubastis \
        -ontology1 releases/${PREV_VERSION}/ontology.ttl \
        -ontology2 ontology.ttl \
        -ignoreAnnotations true \
        -output bubastis-diff.xml

      # Fail on breaking change targeting a MINOR branch
      if [[ "${{ github.base_ref }}" != release/v[0-9]*.0.0 ]]; then
        if grep -q "Removed Axiom" bubastis-diff.xml; then
          echo "MAJOR change detected on a non-major release branch."
          exit 1
        fi
      fi
    fi

The Full Release Pipeline

Pull Request
SHACL gate
unit shapes
Merge to main
SPARQL-SHACL
Bubastis diff
git tag vX.Y.Z
ROBOT diff
CQ acceptance
GitHub Release
ontology.ttl artifact
CHANGELOG auto-gen
The release train. Each stage has a hard exit criterion. Consumers are notified via GitHub Release webhook or a dependency pinning bot.

Managing Deprecations Properly

Deprecated terms should live for exactly one major version after they are marked deprecated, then be removed in the next MAJOR release. The lifecycle has three steps:

Mark deprecated in the current release: add owl:deprecated true and a rdfs:comment or schema:supersededBy pointing to the replacement.

proc:supplierCode
    owl:deprecated true ;
    rdfs:comment "Deprecated in 2.1.0. Use proc:vendorCode instead." ;
    schema:supersededBy proc:vendorCode .

Announce the removal in release notes with a migration guide. Give consumers the full major version cycle to migrate: typically three to six months.

Remove in the next MAJOR release. Once removed, update the version IRI to a new MAJOR version and remove the deprecated term from the file entirely.

Automate deprecation tracking Add a CI step that counts owl:deprecated true terms whose dcterms:modified date is older than 180 days, and posts a summary to your team Slack channel. Deprecation without a deadline is legacy debt accumulation. The automated reminder forces the conversation before the next MAJOR release.

The Maintenance Loop: Closing Back to the ORSD

Releasing an ontology version is not the end of the lifecycle: it is the beginning of the feedback loop that feeds the next sprint’s ORSD revision. The telemetry produced by a live ontology in production is the richest possible input for the next iteration of requirements.

Reading the Signals

SHACL violation rate increase after a release usually means one of two things: something changed upstream that the ontology was not designed to handle, or a new data source is being ingested that has different structural assumptions. In either case, the correct response is to open a new sprint with a revised ORSD that addresses the new source of violations - not to patch the SHACL shapes in isolation.

Consumer query latency increase after adding new classes or property chains often indicates that the OWL reasoning cost has grown. Profile the reasoner, identify the expensive axioms, and decide whether to refactor them into a less expressive profile or to move reasoning to query time. This is an NFR violation and belongs in the ORSD §5.

New stakeholder questions that the ontology cannot answer are new Competency Questions. When demand planning starts asking “can we query which suppliers have a backup commodity alternative?” and the current model cannot express that relationship, that is a CQ for the next sprint’s ORSD. The question came from production: record it as a functional requirement before modeling the answer.

IRI collisions when federating with another ontology module are namespace architecture problems. They surface during integration and require a coordinated PATCH release to standardize. Catching them early (via namespace audits in CI) is far cheaper than finding them when two systems try to merge their graphs.

Deprecated term removal deadlines approaching are the clearest signal: they appear in the automated deprecation report and trigger the migration planning conversation with consumers. A MAJOR release without migrated consumers creates hard failures; the advance notice exists precisely to prevent this.

🔁 A practical maintenance schedule

On every merge to main: Run the full SHACL gate (gates ① through ④). Review any new warnings that did not exist before. A new warning is a data quality trend, not just a noise artifact.

Weekly: Review the SHACL violation dashboard. Are any violation counts trending up? Is the false positive rate stable? Are there shapes that consistently fire on valid data?

Monthly: Review open deprecations. Are there terms marked deprecated more than 90 days ago that consumers still reference? If so, reach out proactively.

Each sprint planning: Review CQs that are currently failing or newly unanswerable. Each one becomes a candidate sprint story. Prioritize with stakeholders using the same process as Part 1.

Before each MAJOR release: Audit the full term surface area. Remove all deprecated terms that have passed their end-of-life date. Run Bubastis against the previous MAJOR version to confirm the change classification. Draft the migration guide before tagging the release.


Putting the Three Parts Together

This series has covered the complete lifecycle of an ontology as an engineering artifact, from the first conversation with stakeholders to a versioned release with automated quality gates.

Part 1 Write the ORSD with stakeholders. Enumerate Competency Questions with FR links. Build the UML conceptual model with domain experts. Apply ODP reuse hierarchy. Encode in OWL using the translation rules. The specification phase that makes everything downstream deterministic. Part 2 Build the SHACL validation layer in three tiers: unit shapes per PR, SPARQL-SHACL on merge, CQ acceptance on release. Wire to GitHub Actions. Validation is the execution of the specification, not a separate quality activity. Part 3 Apply semantic versioning to OWL changes. Publish with persistent IRIs via w3id.org. Detect breaking changes automatically with Bubastis and ROBOT. Release on a predictable train. Read production telemetry to feed the next ORSD sprint.

The methodology is a closed loop, not a one-shot process. Each release produces new information - violations, unanswered CQs, consumer feedback, latency metrics - that feeds the next iteration of requirements. Ontology engineering practiced this way is indistinguishable from any mature software engineering discipline: specify, model, validate, release, observe, iterate. The vocabulary is different. The discipline is the same.

Tooling references: Bubastis (OWL axiom diff); ROBOT (OWL toolchain, including diff and changelog generation); w3id.org (W3C Permanent Identifier Community Group); OWL 2 specification §3.1 (versionIRI semantics); Semantic Versioning 2.0.0 (the software versioning standard this methodology adapts).