Chubb ontology · LOB extension authoring

Author your extension like the core.

A guide for LOB and App teams building their own layer on the Chubb ontology: how to add a domain in Turtle, in your own namespace, using genuine is-a, SKOS values, SHACL rules and the governed change process — grounded in the modules this repository already ships. Every example resolves against real terms in fnd/ and ins/.

Turtle · OWL 2 DL · one module per domain layer worked example: marine cargo grounded in real fnd/ + ins/ terms

The argument, up front

Thesis

An extension is not a private silo in a parallel format — it is another module of one shared ontology, authored to the same rules as the core. Write it in Turtle, mint it in your own domain-layer namespace, model genuine is-a and reach for an association, a role or a SKOS value when it is not a kind, and push every business rule into SHACL. Do that and your layer reasons, validates and promotes cleanly. Depart from it and you get a graph that looks fine and entails nonsense.

The principles below are the whole guide in miniature. Each later section applies one of them, and the marine-cargo walkthrough in §12 shows them working together as a single module.

The principles, at a glance

01

Turtle, per module

Author OWL and SHACL in Turtle, one file per concept-cluster module. JSON-LD is generated at the serving edge, never hand-authored.

02

A domain layer, not Ext-

An extension is a new layer under /ontology/ with a lowercase prefix, e.g. mrc — not an Ext-Mnemonic namespace.

03

Subclass = is-a, only

rdfs:subClassOf is subsumption, not property inheritance. If “every X is a Y” is false, use an association, a role or a value.

04

Values are SKOS

Controlled vocabularies are skos:ConceptSchemes, not OWL enum classes. Extend a scheme you don’t own with mapping properties.

05

Keys via owl:hasKey

Never type a datatype property owl:InverseFunctionalProperty — that is OWL Full. Declare identity with owl:hasKey.

06

Version in-band; govern in Jira

Version on the owl:Ontology node (versionIRI/versionInfo). Change flows through the governed request loop, not a manifest and ad-hoc CI gates.

01 — THE LAYERED MODEL

Where your extension sits

The ontology is a directed, acyclic stack of modules connected by owl:imports. Foundation (fnd/) roots everything; the insurance domain (ins/) builds on it; your extension is a new domain layer beside ins/, importing the modules it specialises. You never write into fnd/ or ins/ — you import them and point upward.

LayerOwned byExamples in this repoYou may…
fnd/ foundationIAcore, agreement; vendored SKOS / Org / Timeimport & subclass only
ins/ insuranceIApolicy, claim, coverage, businessimport & subclass only
your domain layeryour LOBmarine/cargo, cyber/policyauthor freely, in your namespace
/vocabulary/scheme ownerins/peril, your code listsmint your own schemes; map to others

The one-line rule

Mint every new term in your own module namespace, owl:imports the upper module, and connect upward with rdfs:subClassOf / rdfs:subPropertyOf — or, when it is not a kind, with an object property. Never edit a module you do not own.

02 — WHERE A CONCEPT GOES

Core, extension — and what shape it takes

Two decisions, in order. First: does this belong in core or in your extension? Second, and more often missed: is it even a class at all?

Core or extension?

It belongs in core if…

Two or more independent LOBs already need it; its semantics are stable and carry no LOB-specific rules; it is a value-chain or foundational concept (Submission → Quote → Policy → Claim). You do not add it yourself — you raise a change request to IA (§11).

It belongs in your extension if…

It is meaningful only inside your LOB; it is still being validated after reverse-engineering from a system; cross-LOB use is not yet certain; or it carries LOB-specific rules (which live in your SHACL, never as OWL axioms on a shared class).

Is it a class? The altitude question

Before you write a owl:Class, run the concept through one question: is this a genuine kind of thing — or a thing in a state, a thing playing a role, a value it holds, a part of something, or one particular thing? Almost every extension modelling error is one of those wearing the mask of a class.

If the concept is…TestModel it as
A genuine kind“every X is a Y” is true, for all membersrdfs:subClassOf the parent kind
A part / component“X is a Y” false, but “X is part of Y” truea mereology property under core:hasPart
A role an agent playsthe thing can stop being it without ceasing to existthe role-reification triangle (§03)
A value / codeit only ever labels or classifiesa skos:Concept in a scheme (§06)
A particularit names one specific thinga named individual (instance), linked by a property
An attribute it hasit is something the thing has, not isa datatype/object property, or a core:Quality/Measure

The mistake this prevents

A note on a claim, a settlement tracker, an activity roll-up — none is a kind of claim or policy. Making them rdfs:subClassOf claim:Claim / policy:Policy to “inherit properties” is a category error: a reasoner will then infer every note is a claim. See the corrected pattern in §13.

03 — THE MODELING PATTERNS

Five ways to attach a concept

Subclassing is one tool of several. Here is each pattern with the smallest real example, using terms already in core, agr, policy and coverage.

A · Genuine is-a — subclass a kind

A cargo policy really is a policy, so it subclasses policy:Policy and inherits its term, parties, coverages and policyNumber as a logical consequence — not as copied fields.

mrc:CargoPolicy a owl:Class ;
  rdfs:label "Cargo policy" ;
  rdfs:subClassOf policy:Policy ;                 # every cargo policy IS a policy — true
  skos:definition "A marine policy covering goods in transit." .

B · Association / part-of — when it is not a kind

The insured goods are a physical thing the coverage covers, not a kind of coverage. Model the goods as their own class and link with an object property that has an inverse and a place in the core:isRelatedTo hierarchy.

mrc:InsuredCargo a owl:Class ;
  rdfs:subClassOf core:PhysicalEntity .          # a physical thing, NOT a coverage/policy

mrc:coversCargo a owl:ObjectProperty ;
  rdfs:domain mrc:CargoCoverage ; rdfs:range mrc:InsuredCargo ;
  rdfs:subPropertyOf core:isRelatedTo ;
  owl:inverseOf mrc:cargoCoveredBy .

For a whole/part relationship (a coverage is part of a policy), subclass core:hasProperPart instead — exactly as coverage:hasCoverage already does.

C · Role reification — the triangle

An agent that plays a part (carrier, consignee, surveyor) is a role, not a rigid subclass of the agent. Copy the core triangle exactly: a role individual carrying core:roleProperty, a role-filler class defined by a hasValue restriction on core:hasRole, and the property the role realises. This mirrors policy:ProducerRole / policy:Producer / policy:producer.

mrc:CarrierRole a core:AgentRole ;          # (agr:PartyRole if it is a contract party)
  core:roleProperty mrc:carrier .

mrc:carrier a owl:ObjectProperty ;
  rdfs:domain mrc:Voyage ; rdfs:range core:SocialAgent ;
  rdfs:subPropertyOf core:isRelatedTo .

mrc:Carrier a owl:Class ;
  rdfs:subClassOf core:SocialAgent ;
  owl:equivalentClass [ a owl:Restriction ;
      owl:onProperty core:hasRole ; owl:hasValue mrc:CarrierRole ] .

D · Values — a SKOS concept scheme

Cargo type is a controlled list of values. Model it as a skos:ConceptScheme in /vocabulary/, reference it by a property whose range is skos:Concept, and enforce the closed set in SHACL (§06) — never as OWL enum classes.

mrc:cargoType a owl:ObjectProperty ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range skos:Concept ;
  rdfs:subPropertyOf core:hasClassifier .        # scheme & SHACL in the vocabulary + shapes files

E · Reuse a standard — before you mint

The generic parts — time, agents, organizations, provenance, quantities, codes — are already modelled. core itself builds on W3C Org, OWL-Time and SKOS. A note’s date and author are dct:/prov: concerns; money is core:MoneyAmount; a period is a time: interval. Reach for those, not a new datatype.

Two OWL 2 DL disciplines that keep your layer decidable

Keys via owl:hasKey. A natural key (an IMO number, a policy number) is declared with owl:hasKey ( … ) on the class. A datatype property must never be typed owl:InverseFunctionalProperty — that silently leaves OWL 2 DL for OWL Full. Cardinality/functional on simple properties only. Any owl:FunctionalProperty or cardinality axiom must sit on a property with no transitive sub-property. Most such rules belong in SHACL anyway (§06).

04 — NAMESPACES & IRIs

Mint them the way the core does

Every minted IRI is https, on host data.chubb.com, structured as /{root}/{layer}/{module}/{local-name}, with a trailing slash. Your extension is a new layer named for the business domain.

RuleRequirement
NS-1https on data.chubb.com. The http://…/ont/ form is deprecated.
NS-2/4Path /ontology/{layer}/{module}/ for schema; /vocabulary/… for SKOS; /resource/… for instances. Module name short, lowercase, singular.
NS-3Slash namespaces with a trailing slash. Reserve hash (#) IRIs for vendored W3C vocabularies only.
NS-5/8owl:Ontology IRI = module namespace; # baseURI, @prefix : and vann:preferredNamespaceUri all identical to it.
NS-7One preferred prefix: short, lowercase, stable, identical everywhere (mrc, not Ext-Marine).
NS-6Version IRI is {namespace}{version} (§10). The namespace itself never changes.
# A marine cargo extension — a new domain layer, lowercase prefix.
@prefix mrc: <https://data.chubb.com/ontology/marine/cargo/> .   # ✓ layer=marine, module=cargo
# NOT  http://ontology.chubb.com/ext/claim#   ✗ http, wrong host, hash, no /ontology/ root
05 — NAMING & METADATA

Conventions every term carries

ArtifactConventionExample
ClassPascalCase, domain contextmrc:CargoPolicy, mrc:Vessel
Datatype propertycamelCase noun, no hasmrc:imoNumber, mrc:packageCount
Object propertycamelCase; has/is for attribute relations, verb phrases for domain relations; always declare the inversecoverage:hasCoverage, policy:underPolicy, mrc:coversCargo
Boolean propertyis/has prefixmrc:isRefrigerated
SKOS conceptPascalCase, in its own schemecargotype:Refrigerated
Filekebab-case Turtle: {module}.ttl, {module}.shapes.ttlcargo.ttl, cargo.shapes.ttl

Note the repo does not reserve has for object properties only as an absolute rule — verb-named object properties (underPolicy, coversCargo) are idiomatic. The firm rule is the other direction: a datatype property never takes a has prefix.

Metadata every class & property must carry

PredicateRequiredNotes
rdfs:labelRequiredEnglish, from the label conventions above.
skos:definitionRequiredThe repo’s definition predicate (see any core class). State what it is, what it is not, and why it is not in core. (rdfs:comment is acceptable but skos:definition matches the repo.)
a owl:Class / owl:*PropertyRequiredAlways explicit — tools distinguish classes, properties and individuals by it.
rdfs:subClassOf / subPropertyOfRequiredA resolving parent in a module you import (or a standard). A genuine is-a (§03).
rdfs:domain / rdfs:rangeOn propertiesRemember these are inference axioms, not constraints. Domain on your class, never on a core class.
owl:hasKeyIdentity classesThe natural key. On the class, not a datatype IFP.
prov:wasAttributedTo / prov:generatedAtTimeRecommendedAuthoring provenance on the module (§09).

Version metadata is per module, not per class

Do not stamp each class with a version string. The authoritative version lives once, on the owl:Ontology node (§10). Per-term change history, when needed, goes in skos:changeNote / dct:modified — not in an overloaded owl:versionInfo.

06 — SHACL: THE BUSINESS RULES

OWL says what is possible; SHACL says what is required

OWL infers, it never rejects. Every cardinality rule, required field, format and controlled-value constraint is a SHACL shape — not an OWL axiom, which would bind all consumers globally. Group your shapes with the module in one {module}.shapes.ttl.

ComponentPredicateUse
Targetsh:targetClassThe class this shape validates.
Cardinalitysh:minCount/sh:maxCountMake a property mandatory / single-valued for this class.
Typesh:datatype/sh:classRe-assert the type at validation time — rdfs:range alone will not reject bad data.
Valuesh:in/sh:pattern/sh:minInclusivesh:in for a controlled set of SKOS concepts; sh:pattern for formats; ranges for numbers.
Messagesh:messageRequired — a failure with no message is undebuggable.
Exclude inheritedsh:maxCount 0“This class never populates that inherited property,” without touching core OWL.
Cross-class rulesh:sparqlRules spanning classes/properties an sh:property shape can’t express.

A definitional rule that should classify (“a policy with a cargo coverage is a CargoPolicy”) is the exception — that is an OWL equivalentClass, because you want the reasoner to infer it. If you are writing an axiom hoping it will reject a record, you have reached for the wrong layer.

07 — FILE ORGANIZATION

One source of truth, per artifact type

Author every artifact once, in Turtle. Organize the top level by type, and give a module the same filename stem across types so grep cargo.* finds everything. JSON-LD is generated at the edge for application consumers — never hand-authored.

ontology/marine/cargo.ttl            # OWL: the classes & properties of your module
shapes/marine/cargo.shapes.ttl      # SHACL: the business rules (not served)
vocabulary/marine/cargo-type.ttl    # SKOS: controlled-value schemes
competency/marine/cargo-cq.ttl      # competency:CompetencyQuestion individuals + SPARQL
samples/marine/cargo.ttl             # A-Box example individuals
context/marine/cargo.context.jsonld # GENERATED from cargo.ttl — do not hand-edit

Discovery, not a manifest

There is no hand-authored registry to keep in sync. The searchable index is the union of the modules themselves — a CI step merges them into one graph. If you want a per-namespace catalogue, generate it from the Turtle; never maintain it by hand in parallel.

08 — COMPETENCY QUESTIONS

Write the questions first — and keep them

An extension module is done when it can answer the questions the business asks — so write them first, and keep them as a runnable regression suite. This repo already has the meta-model: competency:CompetencyQuestion with a competency:question and an optional competency:sparqlQuery. Every proposed class or property should trace to a CQ; if none needs it, either write the CQ that justifies it, or don’t build it.

mrc:CQ-MRC-01 a competency:CompetencyQuestion ;
  competency:ontology mrc: ;
  competency:question "Which cargo policies cover refrigerated cargo carried on a given vessel?" ;
  competency:sparqlQuery """
    SELECT ?policy ?imo WHERE {
      ?policy a mrc:CargoPolicy ; coverage:hasCoverage ?cov .
      ?cov    a mrc:CargoCoverage ; mrc:coversCargo ?cargo .
      ?cargo  mrc:cargoType cargotype:Refrigerated ; mrc:carriedOn ?vessel .
      ?vessel mrc:imoNumber ?imo .
    }""" .
09 — PROVENANCE & PRIVACY

Two obligations not to skip

Provenance. Insurance is regulated, so it earns fine-grained capture. Attribute your module, and ensure the pipelines that populate it emit PROV as a byproduct of running — hand-authored provenance is never complete. Record the source-field → ontology-term lineage for every mapped value. Use PROV-O; don’t invent your own.

Privacy. Classify first — you cannot protect what you have not labelled. When an extension class or property carries personal data, tag it (e.g. with the Data Privacy Vocabulary) before exposure. As the author of new predicates, you are the person who knows which ones are sensitive.

mrc: a owl:Ontology ;
  prov:wasAttributedTo <https://data.chubb.com/resource/team/marine-lob> ;
  prov:generatedAtTime "2026-07-09"^^xsd:date .

mrc:consigneeContact a owl:DatatypeProperty ;      # a property that carries PII →
  rdfs:domain mrc:InsuredCargo ; rdfs:range xsd:string ;
  dpv:hasPersonalDataCategory dpv:Contact .           # classify it before you expose it
10 — VERSIONING

Two identities, in-band

A permanent ontology IRI names the module forever; a per-release versionIRI names this release of it. Both live on the owl:Ontology node — there is no external manifest that “owns” the version. Classify each change by its effect on consumers: adding a class or property is a minor bump requiring zero consumer changes; extensions publish on their own sprint cadence.

mrc: a owl:Ontology ;
  vann:preferredNamespacePrefix "mrc" ;
  vann:preferredNamespaceUri <https://data.chubb.com/ontology/marine/cargo/> ;
  owl:imports policy: ;
  owl:versionIRI <https://data.chubb.com/ontology/marine/cargo/0.1> ;
  owl:versionInfo "0.1" .                       # a bare version string — no team/ticket text

Once published, a term IRI is permanent. To retire one, mark owl:deprecated true and point dct:isReplacedBy at its successor; remove it only in a later major release, after a stated window. Never repurpose an identifier.

11 — GOVERNANCE & PROMOTION

How a change actually ships

You own your extension module and release it on your own clock. Changes to fnd/ or ins/ — and promotions out of your layer — go through the shared change-control loop: no silent edits, every change a tracked request. The process is orchestrated in Jira, with CI enforcing the semantic gates (the reasoner, SHACL and the competency-question regression).

Roles

Proposer, Steward, SME, Board

Anyone proposes; the steward triages and edits their modules; an SME confirms meaning; the board decides breaking, contested and cross-scheme changes. A release manager publishes.

Gates

The nine review gates

Justified by a CQ, reuse-checked, right module & altitude, pattern-sound, named per policy, reasoner-consistent, SHACL & CQ green, impact-classed, documented. Gates 6 & 7 run in CI (OWL 2 DL consistency; SHACL + CQ regression).

Promotion — when your concept graduates to core

Two LOBs independently need it

That cross-team demand — not proof-of-use in one LOB — is the signal. File a change request nominating the term for promotion.

IA generalises it

The steward strips LOB-specific content, mints a permanent core URI in the right fnd/ or ins/ module, and publishes it.

Your term is deprecated, not deleted

owl:deprecated true + dct:isReplacedBy the new core URI. Consumers migrate within the compatibility window; the old IRI resolves throughout.

12 — WORKED EXAMPLE

A marine cargo extension, end to end

The complete layer: a module, its SKOS scheme, its SHACL shapes, a competency question and a valid instance. It uses only terms that exist in the repo’s core, agr, policy and coverage modules.

ontology/marine/cargo.ttl

# baseURI: https://data.chubb.com/ontology/marine/cargo/
# imports: https://data.chubb.com/ontology/ins/policy/
# prefix: mrc
@prefix mrc:      <https://data.chubb.com/ontology/marine/cargo/> .
@prefix policy:   <https://data.chubb.com/ontology/ins/policy/> .
@prefix coverage: <https://data.chubb.com/ontology/ins/coverage/> .
@prefix core:     <https://data.chubb.com/ontology/fnd/core/> .
@prefix cargotype:<https://data.chubb.com/vocabulary/marine/cargo-type/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix vann: <http://purl.org/vocab/vann/> . @prefix dct: <http://purl.org/dc/terms/> .

mrc: a owl:Ontology ;
  dct:title "Marine Cargo Extension Ontology" ;
  vann:preferredNamespacePrefix "mrc" ;
  vann:preferredNamespaceUri <https://data.chubb.com/ontology/marine/cargo/> ;
  owl:imports policy: ;
  owl:versionIRI <https://data.chubb.com/ontology/marine/cargo/0.1> ;
  owl:versionInfo "0.1" .

## — Genuine is-a —
mrc:CargoPolicy a owl:Class ; rdfs:label "Cargo policy" ;
  rdfs:subClassOf policy:Policy ;
  skos:definition "A marine policy covering goods in transit. A kind of policy." .

mrc:CargoCoverage a owl:Class ; rdfs:label "Cargo coverage" ;
  rdfs:subClassOf coverage:Coverage ;
  skos:definition "Coverage against loss or damage to insured goods in transit." .

## — Association, NOT subclass —
mrc:InsuredCargo a owl:Class ; rdfs:label "Insured cargo" ;
  rdfs:subClassOf core:PhysicalEntity ;
  skos:definition "The physical goods insured under a cargo coverage. NOT a coverage — linked by mrc:coversCargo." .

mrc:Vessel a owl:Class ; rdfs:label "Vessel" ;
  rdfs:subClassOf core:PhysicalEntity ;
  owl:hasKey ( mrc:imoNumber ) ;                     # natural key — NOT a datatype IFP
  skos:definition "A ship transporting insured cargo, identified by its IMO number." .

mrc:Voyage a owl:Class ; rdfs:label "Voyage" ;
  rdfs:subClassOf core:Event ;
  skos:definition "A single transit of cargo from origin to destination." .

## — Role reification triangle —
mrc:CarrierRole a core:AgentRole ; rdfs:label "Carrier role" ;
  core:roleProperty mrc:carrier .
mrc:Carrier a owl:Class ; rdfs:label "Carrier" ;
  rdfs:subClassOf core:SocialAgent ;
  owl:equivalentClass [ a owl:Restriction ;
      owl:onProperty core:hasRole ; owl:hasValue mrc:CarrierRole ] .

## — Object properties (with inverses, under core:isRelatedTo) —
mrc:coversCargo a owl:ObjectProperty ; rdfs:label "covers cargo" ;
  rdfs:domain mrc:CargoCoverage ; rdfs:range mrc:InsuredCargo ;
  rdfs:subPropertyOf core:isRelatedTo ; owl:inverseOf mrc:cargoCoveredBy .
mrc:cargoCoveredBy a owl:ObjectProperty ; rdfs:label "cargo covered by" ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range mrc:CargoCoverage ;
  rdfs:subPropertyOf core:isRelatedTo ; owl:inverseOf mrc:coversCargo .
mrc:carriedOn a owl:ObjectProperty ; rdfs:label "carried on" ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range mrc:Vessel ;
  rdfs:subPropertyOf core:isRelatedTo ; owl:inverseOf mrc:carries .
mrc:carries a owl:ObjectProperty ; rdfs:label "carries" ;
  rdfs:domain mrc:Vessel ; rdfs:range mrc:InsuredCargo ;
  rdfs:subPropertyOf core:isRelatedTo ; owl:inverseOf mrc:carriedOn .
mrc:onVoyage a owl:ObjectProperty ; rdfs:label "on voyage" ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range mrc:Voyage ;
  rdfs:subPropertyOf core:participatesIn .
mrc:carrier a owl:ObjectProperty ; rdfs:label "carrier" ;
  rdfs:domain mrc:Voyage ; rdfs:range core:SocialAgent ;
  rdfs:subPropertyOf core:isRelatedTo .
mrc:cargoType a owl:ObjectProperty ; rdfs:label "cargo type" ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range skos:Concept ;
  rdfs:subPropertyOf core:hasClassifier .
mrc:hasDeclaredValue a owl:ObjectProperty ; rdfs:label "has declared value" ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range core:MoneyAmount ;   # money is a class, not a decimal
  rdfs:subPropertyOf core:hasAttribute .

## — Datatype properties —
mrc:imoNumber a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:label "IMO number" ;
  rdfs:domain mrc:Vessel ; rdfs:range xsd:string .
mrc:packageCount a owl:DatatypeProperty ; rdfs:label "package count" ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range xsd:integer .
mrc:isRefrigerated a owl:DatatypeProperty ; rdfs:label "is refrigerated" ;
  rdfs:domain mrc:InsuredCargo ; rdfs:range xsd:boolean .

vocabulary/marine/cargo-type.ttl

cargotype: a skos:ConceptScheme , owl:Ontology ; dct:title "Marine Cargo Type Scheme" ;
  vann:preferredNamespacePrefix "cargotype" ;
  owl:versionIRI <https://data.chubb.com/vocabulary/marine/cargo-type/0.1> .
cargotype:Containerized a skos:Concept ; skos:inScheme cargotype: ; skos:prefLabel "Containerized"@en ; skos:notation "CONT" .
cargotype:Bulk a skos:Concept ; skos:inScheme cargotype: ; skos:prefLabel "Bulk"@en ; skos:notation "BULK" .
cargotype:Refrigerated a skos:Concept ; skos:inScheme cargotype: ;
  skos:prefLabel "Refrigerated"@en ; skos:notation "REEF" ; skos:broader cargotype:Containerized .

Extending a scheme you don’t own

Never add a concept into another team’s scheme. Mint yours in your own /vocabulary/ namespace and link across with a SKOS mapping property: cargotype:Refrigerated skos:broadMatch enterprisePeril:ColdChain.

shapes/marine/cargo.shapes.ttl

@prefix sh: <http://www.w3.org/ns/shacl#> .  # other prefixes as in the module

mrc:CargoPolicyShape a sh:NodeShape ; sh:targetClass mrc:CargoPolicy ;
  sh:property [ sh:path policy:policyNumber ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ;
    sh:message "A cargo policy must have exactly one policy number." ] ;
  sh:property [ sh:path coverage:hasCoverage ; sh:qualifiedValueShape [ sh:class mrc:CargoCoverage ] ;
    sh:qualifiedMinCount 1 ; sh:message "A cargo policy must include at least one cargo coverage." ] .

mrc:VesselShape a sh:NodeShape ; sh:targetClass mrc:Vessel ;
  sh:property [ sh:path mrc:imoNumber ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ;
    sh:pattern "^IMO[0-9]{7}$" ; sh:message "A vessel needs one IMO number formatted IMO#######." ] .

mrc:InsuredCargoShape a sh:NodeShape ; sh:targetClass mrc:InsuredCargo ;
  sh:property [ sh:path mrc:cargoType ; sh:minCount 1 ; sh:class skos:Concept ;
    sh:in ( cargotype:Containerized cargotype:Bulk cargotype:Refrigerated ) ;
    sh:message "Insured cargo needs a type from the marine cargo-type scheme." ] .

samples/marine/cargo.ttl — a valid instance

@prefix ex: <https://data.chubb.com/resource/> .  # module prefixes as above
ex:cargo-policy-88213 a mrc:CargoPolicy ;
  policy:policyNumber "MC-88213" ; coverage:hasCoverage ex:cov-88213 .
ex:cov-88213 a mrc:CargoCoverage ; mrc:coversCargo ex:cargo-88213 .
ex:cargo-88213 a mrc:InsuredCargo ;
  mrc:cargoType cargotype:Refrigerated ; mrc:isRefrigerated true ;
  mrc:packageCount 240 ; mrc:carriedOn ex:vessel-9074729 .
ex:vessel-9074729 a mrc:Vessel ; mrc:imoNumber "IMO9074729" .
13 — COMMON MISTAKES

The traps, and the fix

The headline case — a note is not a kind of claim

✗ Don’t

Subclass to “inherit properties” when it is not an is-a.

claim:ClaimNote
  rdfs:subClassOf claim:Claim .   # every note IS a claim? no
✓ Do

Give it its own class and an object property.

claim:ClaimNote rdfs:subClassOf core:InformationEntity .
claim:hasNote a owl:ObjectProperty ;
  rdfs:domain claim:Claim ; rdfs:range claim:ClaimNote ;
  rdfs:subPropertyOf core:hasProperPart .
The trapThe fix
Author extensions as .jsonld, one file per classAuthor in Turtle, one file per module; generate JSON-LD at the edge (§07)
Namespaces named Ext-<Mnemonic>, http…#A domain layer, https…/ontology/{layer}/{module}/, lowercase prefix (§04)
Subclass a core class for “free properties”Subclass only for genuine is-a; else association / role / value (§02–03)
rdfs:domain makes a property “global”Domain is an inference axiom; scope with SHACL sh:targetClass (§05–06)
ENUM = OWL class + fixed individualsA skos:ConceptScheme; extend others with mapping properties (§06 / §12)
Single key = datatype owl:InverseFunctionalPropertyowl:hasKey on the class (datatype IFP is OWL Full) (§03)
manifest.jsonld holds the authoritative versionIn-band owl:versionIRI + owl:versionInfo (§10)
“CI Gate 3/4/8 block the merge”Nine review gates; 6 & 7 run in CI; process orchestrated in Jira (§11)
Competency questions = informal sanity checkFormal competency:CompetencyQuestion + SPARQL regression (§08)
Provenance / PII optional or absentPROV auto-emitted + lineage; classify PII before exposure (§09)

The one reflex to keep

Before you write rdfs:subClassOf, say the axiom aloud: “every X is a Y.” If that is false, you want an association, a role, or a value — not a subclass. Nearly every rule in this guide follows from that one habit.