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

rebuts design-choices §2.7 · analysis F3 · F14
Cardinality is a data-quality rule. Default: SHACL sh:minCount / sh:maxCount per class — counted closed-world, reported per record. Exception: an owl:Restriction only when it states a logical truth the reasoner uses to infer something new (classification), with the intended inference documented.

Scope of the dispute

Agreed — keepConstraints should be explicit, machine-readable, and declared early — "exactly one party number" belongs where tools can check it. §4.2's OWL-reasons / SHACL-validates split is right.
Contested — changeWriting every cardinality as a class axiom (owl:Restriction), doubling it with FunctionalProperty, and freezing all of it at class-creation time.
The tellFailing should produce a violation report → it's a shape. Satisfying should let the reasoner conclude something new → it's an axiom.

§2.7's three supports, answered

§2.7 saysCounterRemember it by
"Restrictions make 'well-formed class instance' machine-readable and reasoner-enforceable." A reasoner doesn't enforce; it concludes. OWL is open-world: a missing mandatory value is "unknown," never an error — minCardinality silently passes. Two values aren't rejected — objects get merged, and two different literals make the whole model inconsistent (see trap ↓). "Well-formed" is a closed-world idea; SHACL is the construct that means it. The restriction either stays silent or takes the model down — it never files a report.
"Mandatory single-valued properties are also typed FunctionalProperty — double-enforcement." Twice the axioms, twice the hazard, zero validation. §2.5 itself says Functional "does not reject a second value; it infers the two values are identical." F3 (Critical): one party with two source keys collapses consistency checking for the whole graph. Plus §4.2's hand-mirrored shape = the same rule in three places (F14: guaranteed drift). Two axioms saying the same wrong thing don't make it a check.
"All cardinality must be declared at class creation — prevents under-constrained terms." This freezes day-one guesses into permanent logic. "Exactly one" often turns out to mean "one per source system," and published axioms are effectively permanent (§5.5). The risk is asymmetric: an under-constrained class just infers less; an over-constrained one turns dirty data into model-wide inconsistency. Declare the shape early (adjustable, has sh:severity); add axioms late, when the inference need is proven. Under-constrained OWL is harmless. Over-constrained OWL is poison.

The trap in the §2.7 pattern (F3)

# cardinality as a class axiom + FunctionalProperty ins:Party rdfs:subClassOf [ a owl:Restriction ; owl:onProperty ins:partyNumber ; owl:cardinality 1 ] . ins:party-303 a ins:Party . # no number at all # → open world: "it exists, just unstated." Silence. ins:party-404 ins:partyNumber "P-1" , "P-2" . # → literals can't be equal → whole model INCONSISTENT

The "mandatory" half never fires; the "at most one" half fires model-wide. Neither tells you which record is wrong.

The default pattern — copy this

# the data rule: counted closed-world, reported per record ins:PartyShape a sh:NodeShape ; sh:targetClass ins:Party ; sh:property [ sh:path ins:partyNumber ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "Exactly one party number required." ] . # validator output: # party-303 → sh:minCount: missing partyNumber # party-404 → sh:maxCount: 2 values for partyNumber

The exception — a restriction that earns its axiom

# defined class: the restriction drives classification ins:InsuredParty owl:equivalentClass [ a owl:Restriction ; owl:onProperty ins:playsRole ; owl:someValuesFrom ins:InsuredRole ] . ins:acme-corp ins:playsRole ins:insured-role-p001 . # → reasoner CONCLUDES: acme-corp a ins:InsuredParty # new knowledge, no analyst typed it — that's what OWL is for

Inference creating knowledge, not counting values. Document the intended inference in the term's comment.

Decision rule (proposed §2.7)

NeedUse
"Must have at least one" — mandatorysh:minCount 1
"At most one" — single-valuedsh:maxCount 1
Value must be of a typesh:datatype / sh:class
Identifier unique across all recordsSHACL-SPARQL — not InverseFunctional
Automatic classificationequivalentClass + someValuesFrom
Necessary truth the reasoner usesowl:Restriction — inference documented

The design doc already agrees — everywhere but the decision line

  • §2.7's own preamble — restrictions "are OWL axioms (inference), not SHACL shapes (validation)." The rationale then calls them "reasoner-enforceable" — a validation word for an inference construct.
  • §2.5 — states the exact semantics: "Functional does not reject a second value; it infers the two values are identical." §2.7 calls the same property "double-enforcement" one section later.
  • §4.1/§4.2 — draw the line correctly: OWL owns ontological truth, SHACL owns data quality, and the two cardinality encodings "do different jobs." §2.7 uses the OWL one to do the SHACL job.
  • F3 (Critical) — one party with two source keys doesn't produce a report; it collapses consistency checking for the whole graph.
  • F14 (Gap) — §4.2's hand-mirrored shapes = two sources of truth, guaranteed drift. Generate one encoding from the other, or CI-check that they agree.
  • The review's synthesis — "the design reaches for an OWL axiom where it wants a data constraint." §2.7 is the clearest case.

Bottom line

Shapes count; axioms classify. Every cardinality rule moves to SHACL — with messages, severity levels, and per-record reports — and owl:Restriction survives as the documented exception for logical truths the reasoner actually uses (defined classes, automatic classification). Everything §2.7 wanted — explicit, machine-readable constraints declared early — is kept; they just finally check something.

Condensed from the full rebuttal · rebuts Ontology Design Choices §2.7 · companion findings F3, F14 · siblings: domain/range, code-list cheatsheets. Standards: OWL 2 DL, SHACL, RDFS.