Design review · rebuttal

The right tool for a code list is the one built for code lists.

Design-choices §2.2 models every code list as owl:oneOf named individuals, and limits SKOS to annotation — labels and definitions on terms. This rebuttal argues that is backwards. SKOS is the W3C standard designed for code lists; owl:oneOf is a specialist construct for a few rare cases. The default and the exception should trade places.

01 — THE DECISION, AND WHAT’S ACTUALLY CONTESTED

Agreed: values, not subclasses. Contested: how the values are represented.

design-choices §2.2 — as written

Reference-data values (status, type, peril, line-of-business codes) are owl:NamedIndividual instances of an enumeration class defined with owl:oneOf over its closed member list. SKOS is reserved for annotation vocabulary only — not for modelling code-list values as skos:Concept in a skos:ConceptScheme.

Its three supports: (1) named individuals allow OWL reasoning over enumeration membership and enable class-level disjointness; (2) SKOS concepts cannot carry OWL restrictions; (3) if ACORD cross-mapping is later needed, skos:exactMatch can be added without rearchitecting.

Half of this decision is right, and it stays. Code-list entries are values, not subclasses. §2.4’s quick test (“is this a category of things with its own structure, or a value in a controlled vocabulary?”) is exactly right, and nothing here reopens it. The disagreement is only about how to represent those values: as a closed owl:oneOf list inside the schema the reasoner works on (the T-Box), or as a SKOS concept scheme that sits next to the schema. And about the blanket rule that SKOS may only be used for annotation.

Thesis

Code lists are reference data, not schema. Model them as SKOS concept schemes and check allowed values with SHACL sh:in. Keep owl:oneOf (with owl:AllDifferent) for the rare small, fixed list whose completeness the reasoner actually needs.

02 — THE ROOT ISSUE

A code value is data, not schema

owl:oneOf is a schema construct. It defines a class by listing its complete membership, and the reasoner treats that list as a logical fact. A code list — perils, lines of business, statuses, NAICS codes — is reference data: a governed list of values that changes whenever the business needs it to. What a code list needs is labels, business codes, hierarchy, and mappings to other systems. What it does not need is logic. §2.2 ties the fastest-changing content in the model to the layer that is hardest and riskiest to change.

Make it concrete. Mid-quarter, underwriting adds Cyber to the peril list:

  • Under §2.2: adding the value means editing the published owl:oneOf axiom. That is a schema change — a new ontology version under §5.5’s own rules, which every consumer re-imports and the reasoner reprocesses. All for one new value.
  • As a concept scheme: add one skos:Concept and one IRI to a SHACL sh:in list. The schema does not change; the vocabulary has its own version number and release cycle.

The cost also grows with the size of the list. Individuals enumerated inside class definitions (“nominals” in OWL terms) are one of the known things that make reasoning slow. A three-value status list is harmless. NAICS has over 1,000 codes — a closed oneOf at that size gives the reasoner heavy work to support an inference nobody needs. SKOS concepts are ordinary instance data; the reasoner never touches them.

The project already imports SKOS (§1.1’s standard stack). Using it for the job it was designed for is the “reuse first” principle the design says it follows. It is the ban that needs a justification.

03 — THE RATIONALE, ANSWERED

Three supports, three problems

Claim 1

“Named individuals allow OWL reasoning over enumeration membership and enable class-level disjointness.”

Rebuttal

The only question a code list ever asks is: “is this value one of the allowed ones?” That is a validation question, and Appendix A’s own table already assigns it to SHACL sh:in. The disjointness point does not apply either: disjointness is a relationship between classes, and list members are individuals — the closest available statement is owl:differentFrom.

There is also a real trap in the pattern. OWL does not assume that two names refer to two different things (it has no “unique name assumption”), and §2.5 makes properties like this one functional. Feed the pattern one bad record and, instead of reporting an error, the reasoner quietly concludes that two statuses are the same thing:

# §2.5: hasPartyStatus is functional. A migration glitch supplies two values: ins:party-991 ins:hasPartyStatus ins:ActivePartyStatus , ins:MergedPartyStatus . # No owl:AllDifferent in the §2.2 pattern → the reasoner *infers* # ActivePartyStatus owl:sameAs MergedPartyStatus. Silently. Model-wide.

The same rule written as a SHACL shape fails loudly instead. sh:in fixes the allowed values; sh:maxCount fixes how many are allowed — and bad data produces a violation report, not a wrong conclusion:

# the allowed-value rule as a shape — Appendix A's own row for this job ins:PartyShape a sh:NodeShape ; sh:targetClass ins:Party ; sh:property [ sh:path ins:hasPartyStatus ; sh:maxCount 1 ; sh:in ( ins:ActivePartyStatus ins:InactivePartyStatus ins:MergedPartyStatus ) ] . # validator report for the same data — violations, not merges: # party-991 → sh:MaxCountConstraintComponent: 2 values for hasPartyStatus ins:party-992 ins:hasPartyStatus ins:PendingStatus . # unknown code # party-992 → sh:InConstraintComponent: value not in the allowed list

So to be safe, the construct chosen “for reasoning” needs an owl:AllDifferent that the pattern leaves out — while the SHACL shape answers the actual requirement directly, in the layer §4.2 assigns to data quality.

Claim 2

“SKOS concepts cannot carry OWL restrictions.”

Rebuttal

True, but it does not matter here. An owl:Restriction constrains the instances of a class. A code value like “Fire” is not a class — there are no “instances of Fire” whose properties need constraining. Named individuals cannot carry restrictions either, so both options lack a feature that no code list uses.

And when a value really does need structure, the design already has the answer. If Peril_Flood turns out to need subtypes (storm surge vs. riverine) and its own properties, §2.4’s test says: turn that one value into an owl:Class. That works the same whether the other 200 perils are named individuals or SKOS concepts — so it is no reason to put all of them in the schema.

Claim 3

“If ACORD cross-mapping becomes a requirement, skos:exactMatch can be added from individual IRIs without rearchitecting.”

Rebuttal

Notice what this support admits: when interoperability actually matters, the plan is to use SKOS. skos:exactMatch is a SKOS mapping property, defined to link skos:Concepts. Banning SKOS for modelling while depending on it for mapping is exactly the split that forces rework later.

A single predicate is also not a crosswalk. Real code-table mapping needs shades of meaning: our Peril_Windstorm may be broader than ACORD’s Hurricane — that is skos:broadMatch, not exactMatch. Consumers also need skos:notation (the actual business code) and skos:inScheme (which vocabulary the value belongs to). None of that has a natural home on a bare named individual.

The one thing the claim gets right works against it. skos:exactMatch is deliberately weaker than owl:sameAs: it links two codes without merging all their properties. That is exactly the merge hazard §1.2 warns about for identities. The design’s instincts already favour SKOS behaviour — it just will not use the vocabulary the behaviour comes from.

04 — THE SAME LIST, BOTH WAYS

Concretely

Party status per §2.2 — including the owl:AllDifferent the pattern needs but leaves out:

# §2.2 pattern: the closed list lives inside the schema (T-Box) ins:PartyStatus a owl:Class ; rdfs:subClassOf gist:Category ; owl:oneOf ( ins:ActivePartyStatus ins:InactivePartyStatus ins:MergedPartyStatus ) . [] a owl:AllDifferent ; # omitted by §2.2, required for "three distinct values" owl:distinctMembers ( ins:ActivePartyStatus ins:InactivePartyStatus ins:MergedPartyStatus ) . # adding a 4th status = editing a published class axiom = ontology release (§5.5)

The same list as a governed scheme, with validation where it belongs:

# reference data as a SKOS scheme, beside the schema ins:PartyStatusScheme a skos:ConceptScheme ; dct:title "Party Status"@en ; owl:versionInfo "2026-07" . # its own release cycle ins:ActivePartyStatus a skos:Concept ; skos:inScheme ins:PartyStatusScheme ; skos:prefLabel "Active"@en , "Actif"@fr ; # the labels §5.4 already requires skos:notation "ACT" ; # the source-system code skos:definition "Party is currently active."@en ; skos:exactMatch acord:PartyStatus_Active . # links without a sameAs merge ins:PartyShape sh:property [ # allowed-values check sh:path ins:hasPartyStatus ; sh:maxCount 1 ; sh:in ( ins:ActivePartyStatus ins:InactivePartyStatus ins:MergedPartyStatus ) ] . # adding a 4th status = one new concept; no schema release, no reasoner impact

owl:oneOf named individuals

  • Fast-changing data locked into the schema
  • Every new value edits a published axiom
  • Flat list — no hierarchy, no place for the business code
  • Can silently merge values without AllDifferent
  • Big lists (NAICS) slow the reasoner
  • Interop bolted on later, off its intended use

skos:ConceptScheme + SHACL sh:in

  • Reference data with its own release cycle
  • New values need no schema release
  • broader/narrower trees and notation built in
  • Bad data raises a loud SHACL violation
  • Plain instance data — no reasoner cost
  • Graduated *Match mappings; standard tools (VocBench, Skosmos)
05 — THE DESIGN AGAINST ITSELF

The design-choices doc already points to SKOS — everywhere but §2.2

Most of this argument comes from the design document itself:

  • Appendix A’s table already points away from oneOf for the common case. Its own rows say “validation-only allowed-value check → SHACL sh:in” and “open set that will grow → no owl:oneOf.” Perils, LOB codes, and NAICS are open, growing sets that need exactly that check — yet the “in this ontology” paragraph prescribes closed oneOf anyway.
  • §5.4 already requires SKOS on every term — trilingual prefLabel, definition, example, altLabel. The documentation the design demands is exactly what a skos:Concept carries natively. The “annotation only” rule imports SKOS and then forbids its data model.
  • §5.5’s deprecation pattern already uses skos:exactMatch to point a retired term at its replacement — SKOS mapping doing governance work inside the schema layer.
  • §1.2’s identity guidance already prefers identifiers with skos:notation over owl:sameAs, to avoid merging properties. Mapping code values with skos:*Match is the same safe-alignment idea, applied to vocabularies.
  • §2.4’s promote-to-class test already covers the rare value with real structure — which removes the only case where Claim 2 would matter.
  • Finding F7 in the companion review flags this same choice (reasoner cost, volatility, drift between projects) — and notes that the sibling Chubb ontology already publishes perils, statuses, and LOB codes as SKOS schemes. Keeping §2.2 as-is creates two Chubb vocabularies that need a translation layer to work together.
06 — THE REVISED RULE

Swap the default and the exception

This is a correction, not a purge: owl:oneOf keeps a legitimate, narrow role. The test is simple — if SHACL sh:in can meet the requirement, owl:oneOf was not needed.

Proposed §2.2 (revised)

Default: model controlled vocabularies as skos:ConceptScheme + skos:Concept (with inScheme, notation, labels, definitions). Check allowed values with SHACL sh:in; map to external code tables with the skos:*Match properties.

Exception: use owl:oneOf + named individuals with owl:AllDifferent only for a small, stable, naturally closed list whose completeness the reasoner must use — and record that reason in the term’s comment.

Promotion: a value that turns out to have real structure or subtypes becomes an owl:Class, following §2.4’s existing test — one value at a time, not the whole list.

And the ban is lifted: SKOS stays the annotation vocabulary and becomes the code-list data model — the standard tool for the standard job.

NeedUse (revised Appendix A)
Controlled vocabulary — the default caseskos:ConceptScheme + concepts, checked by sh:in
Small, stable list whose completeness the reasoner needsowl:oneOf + individuals + owl:AllDifferent
Value with its own structure or subtypesPromote that value to owl:Class (§2.4)
Cross-system code alignment (ACORD, NAICS)skos:exactMatch / closeMatch / broadMatch / narrowMatch
Opaque code string, no metadata everxsd:string + SHACL pattern

The revision keeps everything the original decision got right — values are not subclasses, closure is still available, ACORD alignment stays first-class — and puts reference data where it belongs: next to the schema, in the standard the project already imports.