Versioning an Ontology as a Public API: Persistent IRIs, Semantic Releases, and the Maintenance Loop
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.
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.
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.
<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>
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:
- Fork the w3id.org repository on GitHub.
- Create a subdirectory for your namespace, e.g.
proc/. - Add a
.htaccessfile 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]
- 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
unit shapes
Bubastis diff
CQ acceptance
CHANGELOG auto-gen
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.
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.
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.