Design review · rebuttal

owl:cardinality doesn’t count your data — it declares a truth.

Design-choices §2.7 puts every cardinality rule into the class definition as an owl:Restriction, doubles it with owl:FunctionalProperty, and has §4.2 mirror it again in SHACL. This rebuttal makes one narrow claim: cardinality is a data-quality rule and belongs in SHACL by default. An OWL restriction earns its place only when it states a logical truth about the domain that the reasoner uses to conclude something new.

01 — THE DECISION, AND WHAT’S ACTUALLY CONTESTED

Agreed: constraints should be explicit. Contested: which layer counts.

design-choices §2.7 — as written

Cardinality and quantifier restrictions are declared on classes as blank-node owl:Restriction entries in rdfs:subClassOf. Mandatory single-valued properties are also typed owl:FunctionalProperty (“double-enforcement”). All cardinality must be declared when the class is first authored, not added retroactively.

Its supports: (1) restrictions make the meaning of “well-formed class instance” machine-readable and “reasoner-enforceable”; (2) double-enforcement adds a property-level guarantee on top; (3) declaring cardinality at creation time “prevents accumulation of under-constrained terms.”

The goals are right, and they stay. Constraints should be explicit and machine-readable, not tribal knowledge. “A party must have exactly one party number” should be written down where tools can check it. The dispute is only about the layer: §2.7 writes the rule as an OWL axiom — a statement the reasoner treats as a fact about the world — when what the rule wants to do is count values in records and complain. Counting and complaining is SHACL’s job, and the design’s own §4.2 says so.

Thesis

Cardinality is a data-quality rule: express it as SHACL sh:minCount/sh:maxCount by default. Write an owl:Restriction only when the cardinality is a logical truth of the domain and the reasoner uses it to infer something — classification, not counting.

02 — THE ROOT ISSUE

An open-world axiom cannot do closed-world counting

OWL reasons under the open-world assumption: missing information is treated as unknown, not as absent. That single fact breaks both halves of the cardinality-as-restriction plan:

  • The “mandatory” half never fires. owl:minCardinality 1 on partyNumber means “every party has at least one party number — somewhere, stated or not.” A party record with no party number is not an error to the reasoner; it assumes the number exists and simply wasn’t written down. The completeness check the modeler wanted silently does nothing.
  • The “at most one” half fires the wrong way. Give a party two party numbers and owl:cardinality 1 does not reject either. For object values the reasoner concludes the two values are the same thing. For literals like "P-1" and "P-2", which cannot be equal, it concludes the ontology is inconsistent — and an inconsistent ontology poisons every inference, model-wide (finding F3, rated Critical).

So the restriction either stays silent or takes the whole model down. Neither behaviour is what “a well-formed party has exactly one party number” meant. SHACL, which counts under a closed-world reading and reports per record, is the construct that means it.

§2.7 wants a gate. It installed a fact. Facts don’t stop bad data — they absorb it.

03 — THE RATIONALE, ANSWERED

Three supports, three problems

Claim 1

“Restrictions make the semantic meaning of ‘well-formed class instance’ machine-readable and reasoner-enforceable.”

Rebuttal

“Well-formed” is a validation idea, and “reasoner-enforceable” is the wrong verb. A reasoner does not enforce; it concludes. Watch both failure modes on real integration data:

# §2.7 pattern: cardinality as a class axiom ins:Party a owl:Class ; rdfs:subClassOf [ a owl:Restriction ; owl:onProperty ins:partyNumber ; owl:cardinality 1 ] . ins:party-303 a ins:Party . # no party number at all # → open world: the reasoner assumes the number exists, unstated. Silence. ins:party-404 ins:partyNumber "P-1" , "P-2" . # two source keys # → "P-1" and "P-2" cannot be equal → the ontology is INCONSISTENT

The same rule as a SHACL shape does exactly what the sentence meant — it counts, and it complains per record:

# "a well-formed party has exactly one party number" — said properly ins:PartyShape a sh:NodeShape ; sh:targetClass ins:Party ; sh:property [ sh:path ins:partyNumber ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A party must carry exactly one party number." ] . # validator output — two records, two clear errors: # party-303 → sh:minCount: missing partyNumber # party-404 → sh:maxCount: 2 values for partyNumber

Machine-readable? Both are. Only one of them checks anything.

Claim 2

“Mandatory single-valued properties are also typed owl:FunctionalProperty at the property level — double-enforcement.”

Rebuttal

Two axioms in the wrong layer are not twice the enforcement — they are twice the hazard. FunctionalProperty has the same semantics as cardinality 1, applied globally: a second value is not rejected, it is inferred equal to the first. §2.5 states this correctly in its own text (“FunctionalProperty does not reject a second value; it infers the two values are identical”) — and §2.7 then relies on it as if it validated. Finding F3 rates exactly this combination Critical: the first party that arrives with two source keys does not produce a report; it takes down consistency checking for the entire graph.

“Double-enforcement” also doubles the maintenance problem. §4.2 requires a hand-written SHACL shape mirroring the same rule — so each cardinality now lives in three places (restriction, property axiom, shape), all maintained by hand. Finding F14 calls the result: two sources of truth, guaranteed drift. One authoritative encoding — the shape — would do the actual job with nothing to keep in sync.

Claim 3

“All cardinality must be declared when the class is first authored — this prevents accumulation of under-constrained terms.”

Rebuttal

This rule freezes day-one guesses into permanent logic. Cardinality knowledge evolves — especially in integration, where “exactly one” routinely turns out to mean “one per source system.” Under §5.5’s own rules, published axioms are effectively permanent once consumers depend on them. A wrong cardinality 1 guessed at creation is expensive to retract; a SHACL shape can be tightened, loosened, or downgraded to a warning (sh:severity) at any time, because shapes are versioned data-quality rules, not schema.

The risk is also asymmetric, and the rule points the wrong way. An under-constrained OWL class is harmless — the reasoner just infers less. An over-constrained one turns ordinary dirty data into model-wide inconsistency. If anything should be declared early and generously, it is the shape; axioms should be added late, one by one, when the logical need is proven.

04 — COUNTING VS. REASONING, BOTH WAYS

Concretely — and where a restriction is genuinely right

The default case: cardinality as a shape, with the constraint stated once, checked closed-world, and free to evolve:

# the data-quality rule — one source of truth, per-record reports ins:PartyShape a sh:NodeShape ; sh:targetClass ins:Party ; sh:property [ sh:path ins:partyNumber ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; sh:severity sh:Violation ; # or sh:Warning while sources are cleaned sh:message "A party must carry exactly one party number." ] .

The exception — the logical-axiom viewpoint. Here the restriction states a truth of the domain, and the reasoner uses it to classify. This is inference creating knowledge, not counting values:

# a defined class: the restriction drives classification ins:InsuredParty a owl:Class ; owl:equivalentClass [ a owl:Restriction ; owl:onProperty ins:playsRole ; owl:someValuesFrom ins:InsuredRole ] ; rdfs:comment "Inference intended: any party with an insured role is automatically classified as InsuredParty." . # data: ins:acme-corp ins:playsRole ins:insured-role-p001 . # → the reasoner CONCLUDES: acme-corp a ins:InsuredParty — new knowledge, # no analyst typed it. That is what OWL restrictions are for.

owl cardinality as the data rule

  • Missing mandatory value → silence (open world)
  • Two literals → the whole model goes inconsistent
  • Two objects → silently inferred to be the same
  • Day-one guesses frozen into permanent axioms (§5.5)
  • Same rule kept in three places by hand (F14)
  • No messages, no severity levels, no per-record reports

SHACL counts; OWL classifies

  • Missing value → minCount violation, named record
  • Duplicates → maxCount violation with a message
  • Rules evolve freely; sh:severity for rollout
  • One source of truth — nothing to drift
  • Restrictions kept where they infer (defined classes)
  • §4.2’s split — OWL reasons, SHACL validates — honoured
05 — THE DESIGN AGAINST ITSELF

The document states the split — §2.7 doesn’t live by it

As elsewhere in this series, most of the argument comes from the design document itself:

  • §2.7’s own preamble says it: restrictions “are OWL axioms (inference), not SHACL shapes (validation).” The rationale then calls them “reasoner-enforceable” and reads them as the definition of a well-formed instance — validation words for an inference construct.
  • §2.5 already teaches the exact semantics:FunctionalProperty does not reject a second value; it infers the two values are identical.” §2.7 quotes the same property as “double-enforcement” one section later.
  • §4.1/§4.2 draw the line correctly — OWL owns ontological truth, SHACL owns data quality, “a cardinality restriction in OWL and a sh:minCount in SHACL do different jobs.” §2.7 uses the OWL one to do the SHACL job.
  • §4.2’s mirror requirement creates the drift finding: every cardinality is hand-copied between OWL and SHACL with no generation or sync check — F14’s “two sources of truth, guaranteed drift.”
  • F3 (Critical) flags the concrete failure: Functional + cardinality 1 on identifiers means the first party arriving with two source keys collapses consistency checking for the whole graph, instead of producing one violation report.
  • The review’s own synthesis names the root cause: “the design reaches for an OWL axiom where it wants a data constraint.” §2.7 is the clearest instance of it.
06 — THE REVISED RULE

Shapes count; axioms classify

This is a correction, not a purge: OWL restrictions keep a legitimate, narrow role. The test is simple — if failing the constraint should produce a violation report, it is a shape. If satisfying it should let the reasoner conclude something new, it is an axiom.

Proposed §2.7 (revised)

Default: cardinality and value-type rules are SHACL: sh:minCount / sh:maxCount / sh:datatype / sh:class per class, with sh:message and sh:severity. Shapes may be added or tightened at any time — they are versioned data-quality rules, not schema.

Exception (the logical-axiom viewpoint): write an owl:Restriction only when it states a necessary truth of the domain and the reasoner uses it — typically someValuesFrom / hasValue in a defined class (owl:equivalentClass) that drives classification. Record the intended inference in the term’s comment.

Drop double-enforcement: never pair FunctionalProperty with cardinality 1 as a data check, and keep both off identifier properties fed by source systems (F3). Identifier uniqueness across records is a SHACL(-SPARQL) check.

One source of truth: where a rule genuinely must exist in both layers, generate one encoding from the other or add a CI check that they agree (F14). Manual mirroring at scale is not a plan.

NeedUse
“Must have at least one” — mandatory propertysh:minCount 1
“At most one” — single-valued propertysh:maxCount 1
Value must be of a typesh:datatype / sh:class on the path
Identifier unique across all recordsSHACL-SPARQL constraint — not InverseFunctional
Automatic classification the reasoner should performowl:equivalentClass + someValuesFrom / hasValue
A necessary truth the reasoner exploitsowl:Restriction — with the intended inference documented

The revision keeps everything §2.7 wanted — explicit, machine-readable constraints declared early — and gets both behaviours right: bad data produces named, fixable violation reports, and the reasoner spends its effort on classification the team actually asked for.