1046 lines
52 KiB
YAML
1046 lines
52 KiB
YAML
# PERNOD RICARD DATA METAMODEL — T-BOX RULEBOOK
|
|
# =============================================================================
|
|
# SINGLE SOURCE OF TRUTH. The .md and .xlsx deliverables are GENERATED from this
|
|
# file and must never be edited by hand (EV-007, EV-008).
|
|
#
|
|
# python3 generate_rulebook_md.py
|
|
# python3 generate_rulebook_xlsx.py
|
|
#
|
|
# severity : BLOCKING | MAJOR | GUIDELINE — one per rule. The rule is the
|
|
# rule; transitional tolerance belongs to a migration process,
|
|
# not to a normative document.
|
|
# control.tier : shacl | script | human (EV-012 — where the rule is enforced)
|
|
# control.executor : the script or shape that enforces it (EV-011 — no blocking
|
|
# rule without an executor)
|
|
# control.procedure: filled in by the processbook. null until then.
|
|
# =============================================================================
|
|
|
|
meta:
|
|
title: Pernod Ricard Data MetaModel — T-Box Rulebook
|
|
version: "1.1"
|
|
status: Draft for review
|
|
date: "2026-08-03"
|
|
scope: >
|
|
Governs the vocabulary of the model itself: the IRIs, labels and declarative
|
|
axioms of every class, property and enumeration individual. Does not govern
|
|
instances, which remain under the A-Box rulebook.
|
|
audience: >
|
|
Written to be read and applied directly, by a person or by a language model,
|
|
without further context. Every rule states what must hold, why, and what
|
|
enforces it.
|
|
|
|
categories:
|
|
Identifier:
|
|
title: Form of the IRI
|
|
intent: >
|
|
The IRI is identity. It is immutable in the RDF sense — changing one is a
|
|
migration, never an edit — so it must be unambiguous, searchable and free
|
|
of local convention.
|
|
Label:
|
|
title: Derivation and display
|
|
intent: >
|
|
The label is derived, not authored. Anything a reader needs that the
|
|
derivation cannot produce lives in a dedicated channel: the short label
|
|
for the spoken form, the comment for meaning.
|
|
Declaration:
|
|
title: Declarative completeness
|
|
intent: >
|
|
Nothing is left to be guessed. What a term is, what it applies to, where
|
|
it sits and where it came from are all stated, never inferred from
|
|
structure or from silence.
|
|
Evolution:
|
|
title: Change and propagation
|
|
intent: >
|
|
Every rule here is anchored on a real failure. They govern how the model
|
|
changes without breaking what depends on it.
|
|
|
|
severity_model:
|
|
BLOCKING: >
|
|
The commit does not pass. Enforced pre-commit and in continuous integration
|
|
by the named executor.
|
|
MAJOR: >
|
|
Reviewed and answered for, but not machine-enforceable. Reserved for rules
|
|
that require human judgement, per EV-011.
|
|
GUIDELINE: >
|
|
Recommended practice with no gate.
|
|
|
|
rules:
|
|
|
|
# ---------------------------------------------------------------- IDENTIFIER
|
|
|
|
- id: TN-001
|
|
category: Identifier
|
|
title: CamelCase IRI with no separator
|
|
statement: >
|
|
The local name of an IRI is strict CamelCase with no separator of any kind:
|
|
no hyphen, no underscore, no dot, no space. Initial capital for a class or
|
|
an enumeration individual; initial lower case for a property, whether
|
|
object, datatype or annotation. Consecutive capitals are admitted only where
|
|
they carry an acronym reproduced from a target class short label under
|
|
TN-011, and nowhere else.
|
|
scope: [class, object_property, datatype_property, annotation_property, individual]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A hyphen forces escaping in certain Turtle and SPARQL contexts and makes
|
|
decamelisation ambiguous: Sub-Domain has no single correct expansion.
|
|
Typographic convention is a display concern and belongs to the short label,
|
|
never to identity.
|
|
examples:
|
|
- {from: "pr:data_domain", to: "pr:DataDomain", note: no underscore}
|
|
- {from: "pr:Sub-Domain", to: "pr:DataSubDomain", note: no hyphen}
|
|
- {from: "pr:BelongsTo", to: "pr:belongsTo", note: a property starts lower case}
|
|
|
|
- id: TN-002
|
|
category: Identifier
|
|
title: No acronym in the IRI
|
|
statement: >
|
|
The local name of an IRI contains no acronym, initialism or abbreviation.
|
|
Every short form is expanded in full. One exception, and one only: a
|
|
relation reproducing the short label of the class it targets carries that
|
|
short label as it stands, acronym included, under TN-011. No other term may
|
|
carry an acronym, and no exception list is maintained.
|
|
scope: [class, object_property, datatype_property, annotation_property, individual]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: A-Box NR-004
|
|
rationale: >
|
|
An acronym in an identifier is a local convention pretending to be a name.
|
|
It is unsearchable by anyone who does not already know it, and it collides
|
|
across domains: PO is a Product Owner in one team and a Purchase Order in
|
|
another. Length is not a counter-argument — an IRI is written by tooling and
|
|
read in context, and the short form remains available through the short
|
|
label and the acronym annotation. The single exception exists because a
|
|
relation name is read on every edge of the graph and in every query, where
|
|
the spoken form is what makes it legible; reproducing the target's short
|
|
label verbatim also keeps the relation name derivable from the class rather
|
|
than invented, which a truncated form would not.
|
|
examples:
|
|
- {from: "pr:KPI", to: "pr:KeyPerformanceIndicator", note: "short form moves to pr:acronym"}
|
|
- {from: "pr:BIDataSource", to: "pr:BusinessIntelligenceDataSource", note: null}
|
|
- {from: "pr:definitionUri", to: "pr:definitionAddress", note: "URI is itself an acronym"}
|
|
- {from: "a relation targeting a class whose short label is BI Field", to: "pr:containsBIField", note: "the one exception — see TN-011"}
|
|
|
|
- id: TN-003
|
|
category: Identifier
|
|
title: Display properties are annotations
|
|
statement: >
|
|
pr:acronym and pr:shortLabel are declared owl:AnnotationProperty, never
|
|
owl:DatatypeProperty.
|
|
scope: [annotation_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A datatype property applies to an individual. Annotating a term of the
|
|
vocabulary with one takes the ontology out of OWL DL into OWL Full, which
|
|
reasoners reject and strict validators flag. An annotation property applies
|
|
to anything — class, property or individual. Nothing is lost on the control
|
|
side: SHACL validates triples and does not read OWL declarations, so every
|
|
shape targeting these properties keeps working unchanged.
|
|
examples:
|
|
- {from: "pr:shortLabel a owl:DatatypeProperty", to: "pr:shortLabel a owl:AnnotationProperty", note: null}
|
|
- {from: "pr:acronym a owl:DatatypeProperty", to: "pr:acronym a owl:AnnotationProperty", note: null}
|
|
|
|
- id: TN-004
|
|
category: Identifier
|
|
title: No digit in the IRI
|
|
statement: >
|
|
The local name of a vocabulary term contains no digit. A numeric parameter —
|
|
a window, a threshold, a version — is a value, never an identity.
|
|
scope: [class, object_property, datatype_property, annotation_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: A-Box NR-014 (deliberate opposite)
|
|
rationale: >
|
|
An instance identifier does encode numbers, and that is its purpose: it
|
|
carries a position in the domain tree. A vocabulary term encodes nothing but
|
|
its own meaning. The opposition is deliberate and is stated in the ontology
|
|
header so that no reader takes one for a violation of the other. A term
|
|
named after a thirty-day window starts lying the day the window becomes
|
|
ninety, and nothing detects it.
|
|
examples:
|
|
- {from: "pr:queryCount30d", to: "pr:queryCount", note: the window belongs to the harvesting specification}
|
|
|
|
- id: TN-005
|
|
category: Identifier
|
|
title: A class is a singular common noun
|
|
statement: >
|
|
The local name of a class is a singular noun phrase. No plural, no verb. No
|
|
type word such as Object, Entity or Item unless it names a genuine
|
|
abstraction of the model. No suffix rule is imposed on abstract classes.
|
|
scope: [class]
|
|
severity: BLOCKING
|
|
control: {tier: [script, human], executor: check_tbox_naming.py, procedure: null}
|
|
filiation: A-Box NR-008, NR-013
|
|
rationale: >
|
|
Abstractness is not inferable from a name and must not be encoded in one.
|
|
The correspondence fails in both directions: classes can be abstract without
|
|
any suffix, and classes ending in Object can be among the most heavily
|
|
instantiated in the model. Renaming a central business term to satisfy a
|
|
naming rule would trade meaning for symmetry. Abstractness is declared
|
|
instead, by TN-007.
|
|
examples:
|
|
- {from: "pr:DataDomains", to: "pr:DataDomain", note: singular}
|
|
- {from: "pr:ActorObject", to: "pr:Actor", note: no type word added for its own sake}
|
|
|
|
- id: TN-006
|
|
category: Identifier
|
|
title: Every layer root is abstract
|
|
statement: >
|
|
A class that serves as the root of a layer carries pr:isAbstract true.
|
|
scope: [class]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A non-abstract layer root allows an instance to be typed as an object of
|
|
that layer without saying which kind of object it is. That statement carries
|
|
no information and no downstream control can repair it.
|
|
examples:
|
|
- {from: "pr:PhysicalLayerObject with no declaration", to: "pr:isAbstract true", note: null}
|
|
|
|
- id: TN-007
|
|
category: Identifier
|
|
title: Every class declares whether it is abstract
|
|
statement: >
|
|
Every owl:Class carries pr:isAbstract with an explicit boolean value.
|
|
Absence of the property is a violation, not a default.
|
|
scope: [class]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Where declaration is optional, nothing distinguishes a class deliberately
|
|
left concrete from one where the question was never asked. Two situations
|
|
make the compulsion necessary. A class with subclasses may be perfectly
|
|
instantiable, and has been wrongly marked abstract by inference. And a class
|
|
used as a classification axis may have no declared subclass at all, its
|
|
members being typed by multiple typing on instances — its abstractness is
|
|
then structurally unverifiable and only the explicit declaration carries it.
|
|
examples:
|
|
- {from: "a class with no declaration", to: "pr:isAbstract true or false", note: silence is a violation}
|
|
- {from: "pr:Metric inferred abstract because it has a subclass", to: "pr:isAbstract false", note: concrete despite having a subclass}
|
|
|
|
- id: TN-008
|
|
category: Identifier
|
|
title: Abstractness is declared, never inferred
|
|
statement: >
|
|
Abstractness is read from pr:isAbstract and from nothing else. No consumer
|
|
of the model — viewer, export, script or documentation generator — may
|
|
derive it from the presence of subclasses, from the absence of instances, or
|
|
from any other structural signal.
|
|
scope: [class, consumer]
|
|
severity: BLOCKING
|
|
control: {tier: human, executor: consumer contract review, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A declaration rule holds in the source only if it also binds every consumer.
|
|
Otherwise each one re-invents its own inference and the model acquires as
|
|
many answers as it has readers. This is the same failure mode that justified
|
|
a governed short label rather than a display dictionary per viewer.
|
|
examples:
|
|
- {from: "isAbstract = term.subclasses.length > 0", to: "isAbstract = term.isAbstract === true", note: consumer contract}
|
|
|
|
- id: TN-009
|
|
category: Identifier
|
|
title: A relation begins with a verb
|
|
statement: >
|
|
The local name of an object property begins with a verb, in lower case. Both
|
|
the active form and the passive or participial form are admitted: a past
|
|
participle is a verb in first position. No relation begins with a
|
|
preposition or with a noun.
|
|
scope: [object_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A relation reads as a verb and an attribute reads as a noun. The distinction
|
|
is what lets a reader tell an edge from a field without opening the
|
|
declaration. The explicit clause on participial forms is required: read
|
|
literally, a verb-first rule would condemn a whole family of sound relations
|
|
such as computedBy, storedIn and derivedFrom.
|
|
examples:
|
|
- {from: "pr:inDatabase", to: "pr:isInDatabase", note: a preposition is not a verb}
|
|
- {from: "pr:primaryLocation", to: "pr:primarilyStoredIn", note: a noun is not a verb}
|
|
- {from: "pr:computedBy", to: "pr:computedBy", note: participial form is conforming}
|
|
|
|
- id: TN-010
|
|
category: Identifier
|
|
title: The is prefix is either a copula or a predicate
|
|
statement: >
|
|
On an owl:ObjectProperty, is acts as a COPULA — a state verb before a
|
|
preposition or adjective, whose target is a node of the graph. On a boolean
|
|
datatype or annotation property, is acts as a PREDICATE — it introduces an
|
|
adjective or participle and the value is true or false. No other use is
|
|
admitted.
|
|
scope: [object_property, datatype_property, annotation_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Conformance of a name beginning with is is never checkable on the name
|
|
alone. A control asserting that every such term is a boolean produces false
|
|
positives on every copula; the reverse control lets real problems through.
|
|
The correct check reads the declared type of the property first, then
|
|
applies the matching clause.
|
|
examples:
|
|
- {from: "pr:isInSchema", to: copula, note: object property, target is a node}
|
|
- {from: "pr:isNullable", to: predicate, note: datatype property of range xsd:boolean}
|
|
|
|
- id: TN-011
|
|
category: Identifier
|
|
title: A relation names its target by its short label
|
|
statement: >
|
|
A relation need not mention its target class at all. When it does, it uses
|
|
the target class's pr:shortLabel AS IT STANDS — not the full label, not a
|
|
truncation of the short label, and not a form invented for the occasion.
|
|
Where the short label carries an acronym, the acronym is reproduced with it:
|
|
this is the single exception admitted by TN-002, and TN-001 admits the
|
|
consecutive capitals it produces.
|
|
scope: [object_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Relation names are what is read on the edges of the graph, constantly, in
|
|
every viewer and every query. Full labels make them unreadable on an arrow.
|
|
The short label is the form the organisation actually speaks, and reusing it
|
|
makes a relation name predictable from the class it points at instead of
|
|
arbitrary. Reproducing it whole is what preserves that property: a
|
|
truncation would be a form the model invented, unpredictable from the class
|
|
and unverifiable against it. A corollary: relations do not normally carry a
|
|
short label of their own, since this rule already keeps their IRIs short.
|
|
examples:
|
|
- {from: "pr:hasDomainOwner", to: "pr:hasDomainOwner", note: "target DataDomainOwner, short label Domain Owner — conforming"}
|
|
- {from: "pr:hasDGL", to: "pr:hasGovernanceLead", note: "target short label is Governance Lead"}
|
|
- {from: "pr:inBIDataSource", to: "pr:isInBIDataSource", note: "short label BI Data Source reproduced whole, acronym included"}
|
|
- {from: "pr:containsField", to: "pr:containsBIField", note: "a truncated short label is not admitted"}
|
|
|
|
- id: TN-012
|
|
category: Identifier
|
|
title: An attribute is a noun
|
|
statement: >
|
|
The local name of a datatype property is a noun phrase. It begins with no
|
|
verb, and in particular with no has.
|
|
scope: [datatype_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A has prefix carries no information — rdfs:domain already says who has the
|
|
attribute — and it costs the reader the distinction TN-009 exists to make
|
|
visible. Under TN-018 the derived label would read as a verb phrase on a
|
|
field, so a relation and an attribute would become indistinguishable in
|
|
every display.
|
|
examples:
|
|
- {from: "pr:hasFormula", to: "pr:formula", note: null}
|
|
- {from: "pr:hasName", to: "pr:canonicalName", note: name alone is too generic to stand as an identity}
|
|
|
|
- id: TN-013
|
|
category: Identifier
|
|
title: A date attribute is a noun, not a participle
|
|
statement: >
|
|
An attribute holding a date or timestamp is named as a noun.
|
|
scope: [datatype_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
The participial form is idiomatic English but creates a standing family of
|
|
exceptions to TN-012 that must be defended at every review. The nominal form
|
|
is consistent with the other attributes of the model and needs no defence.
|
|
examples:
|
|
- {from: "pr:createdOn", to: "pr:creationDate", note: null}
|
|
- {from: "pr:lastQueriedOn", to: "pr:lastQueryDate", note: null}
|
|
|
|
- id: TN-014
|
|
category: Identifier
|
|
title: An attribute never takes a relational suffix
|
|
statement: >
|
|
A datatype property never takes a form ending in By, In, From or On. Those
|
|
forms are reserved for object properties. The only exception is the
|
|
predicative is of booleans, covered by TN-015.
|
|
scope: [datatype_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
An attribute named like a relation will be read as one. A reader
|
|
encountering a term in the shape of computedBy or ownedBy expects a node at
|
|
the other end, not a string. The tell is usually visible in the label: a
|
|
parenthetical gloss added to explain what the value actually holds is
|
|
evidence that the name itself is wrong.
|
|
examples:
|
|
- {from: "pr:definedBy holding a string", to: "pr:definitionAddress", note: "address, not location — location is taken by the storage side"}
|
|
|
|
- id: TN-015
|
|
category: Identifier
|
|
title: A boolean attribute begins with is
|
|
statement: >
|
|
A property of range xsd:boolean begins with is, followed by an adjective or
|
|
participle. This is the single explicit exception to TN-012.
|
|
scope: [datatype_property, annotation_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A bare adjective does not read as a statement, and some of them collide with
|
|
reserved words in the languages that consume the model.
|
|
examples:
|
|
- {from: "pr:nullable", to: "pr:isNullable", note: null}
|
|
- {from: "pr:abstract", to: "pr:isAbstract", note: "abstract is a reserved word in several target languages"}
|
|
|
|
- id: TN-016
|
|
category: Identifier
|
|
title: A controlled value is an individual or a literal
|
|
statement: >
|
|
A value becomes a TYPED INDIVIDUAL if it needs to be defined, owned, or
|
|
extended by a domain without modifying the model. It stays a LITERAL if it
|
|
is a closed technical state whose list is the property of governance and
|
|
must precisely not be extended. The model chooses one form per kind of value
|
|
and states the choice.
|
|
scope: [class, individual, datatype_property]
|
|
severity: BLOCKING
|
|
control: {tier: [shacl, human], executor: pr_metamodel_shapes.ttl, procedure: null}
|
|
filiation: A-Box NR-016
|
|
rationale: >
|
|
A literal cannot carry a definition, an owner or a link, and extending its
|
|
list means editing the shapes and therefore the frozen vocabulary. A typed
|
|
individual can be added by a domain without touching the model, appears as a
|
|
node in every viewer, and can be reached by following an edge. The test is
|
|
not how the value looks but who is allowed to add one.
|
|
examples:
|
|
- {from: "an activation level defined in a playbook, not in the graph", to: "literal constrained by sh:in", note: not domain-extensible}
|
|
- {from: "a kind of system a domain may need to add", to: typed individual, note: extensible and worth defining}
|
|
|
|
- id: TN-017
|
|
category: Identifier
|
|
title: Controlled literals are UPPER_SNAKE_CASE
|
|
statement: >
|
|
Controlled literal values are written UPPER_SNAKE_CASE. This is the only
|
|
place in the model where that casing is used.
|
|
scope: [datatype_property]
|
|
severity: BLOCKING
|
|
control: {tier: shacl, executor: pr_metamodel_shapes.ttl, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
The casing exists so that a value drawn from a closed list is
|
|
distinguishable from free text at a glance, in the source and in any export.
|
|
examples:
|
|
- {from: "\"Full activation\"", to: "\"FULL_ACTIVATION\"", note: null}
|
|
- {from: "\"Published\"", to: "\"PUBLISHED\"", note: null}
|
|
|
|
# --------------------------------------------------------------------- LABEL
|
|
|
|
- id: TN-018
|
|
category: Label
|
|
title: The label is derived from the IRI
|
|
statement: >
|
|
rdfs:label is obtained from the local name of the IRI by inserting a space
|
|
at every case boundary. No word added, removed, reordered or substituted.
|
|
The words of a class label keep their initial capital; the words of a
|
|
property label are entirely lower case.
|
|
scope: [class, object_property, datatype_property, annotation_property, individual]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: A-Box NR-005 (deliberate opposite)
|
|
rationale: >
|
|
A label carrying meaning the IRI does not is the symptom of a badly named
|
|
IRI or of an incomplete comment — never of a legitimate exception to the
|
|
label. Where a label has been quietly enriched to explain a term, the
|
|
explanation belongs in rdfs:comment and the short form, if the organisation
|
|
speaks one, in pr:shortLabel.
|
|
examples:
|
|
- {from: "pr:BusinessIntelligenceDataSource", to: "Business Intelligence Data Source", note: a class label keeps its capitals}
|
|
- {from: "pr:primarilyStoredIn", to: "primarily stored in", note: a property label is lower case}
|
|
- {from: "\"Ownership and Categorization Layer Object\"", to: "\"Ownership Layer Object\"", note: the added meaning moves to rdfs:comment}
|
|
|
|
- id: TN-019
|
|
category: Label
|
|
title: No acronym in the label
|
|
statement: >
|
|
The label introduces no acronym or abbreviation that the IRI does not
|
|
already carry, including in a recapitalised form that the derivation would
|
|
not produce. Where the IRI legitimately carries an acronym under TN-011, the
|
|
derived label carries it too and nothing further is added.
|
|
scope: [class, object_property, datatype_property, annotation_property, individual]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A direct corollary of TN-018, kept as a separate rule because it catches
|
|
labels whose IRI is innocent: a term correctly named in full can still carry
|
|
a hand-written label that reintroduces the short form. Stated as an
|
|
introduction rather than a prohibition, it stays a pure corollary — whatever
|
|
the IRI legitimately holds, the derivation reproduces, and nothing else.
|
|
examples:
|
|
- {from: "\"BI expression\"", to: "\"expression\"", note: the IRI was already conforming}
|
|
|
|
- id: TN-020
|
|
category: Label
|
|
title: No state in the label
|
|
statement: >
|
|
The label mentions no status, version or deprecation. State lives in an
|
|
axiom — owl:deprecated, pr:status — never in text. Deprecated and active
|
|
terms are never mixed in a single view: a consumer filters on the axiom at
|
|
query time and presents deprecated terms separately.
|
|
scope: [class, object_property, datatype_property, annotation_property, individual, consumer]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A term whose label repeats its state carries that state twice. The day one
|
|
is changed without the other, the two contradict each other and nothing
|
|
detects it. With the rule in force, the question of whether a term is
|
|
deprecated has exactly one possible answer.
|
|
examples:
|
|
- {from: "\"belongs to domain (deprecated)\"", to: "\"belongs to domain\"", note: owl:deprecated carries the state}
|
|
|
|
- id: TN-021
|
|
category: Label
|
|
title: No gloss or parenthesis in the label
|
|
statement: >
|
|
No parenthesis, qualification or example appears in the label. They belong
|
|
in rdfs:comment.
|
|
scope: [class, object_property, datatype_property, annotation_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A parenthetical gloss is almost always the symptom of an IRI that does not
|
|
say what it holds. The right response is usually to rename the term, not to
|
|
annotate the label.
|
|
examples:
|
|
- {from: "\"defined by (code URI)\"", to: "\"definition address\"", note: rename the term rather than gloss the label}
|
|
- {from: "\"query count (30 days)\"", to: "\"query count\"", note: null}
|
|
|
|
- id: TN-022
|
|
category: Label
|
|
title: The short label carries the spoken form
|
|
statement: >
|
|
A term carries pr:shortLabel whenever the form derived under TN-018 is not
|
|
what the organisation writes or says. The short label is free: acronyms,
|
|
abbreviations, hyphens, short forms.
|
|
scope: [class, individual]
|
|
severity: MAJOR
|
|
control: {tier: human, executor: review checklist, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
The short label is what makes the derivation rule bearable. Without it, a
|
|
mechanical label impoverishes every screen; with it, display becomes
|
|
explicit and centrally governed instead of being improvised by each
|
|
consumer. It is MAJOR rather than BLOCKING because judging whether a derived
|
|
form matches what people actually say is human work, and EV-011 forbids
|
|
declaring a rule blocking with no executor.
|
|
examples:
|
|
- {from: "\"Data Sub Domain Owner\"", to: "short label Sub Domain Owner", note: the word Data is dropped in the spoken form}
|
|
- {from: "\"Key Performance Indicator\"", to: "short label KPI, acronym KPI", note: the acronym channel is what makes TN-002 bearable}
|
|
|
|
- id: TN-023
|
|
category: Label
|
|
title: The label is generated, never typed
|
|
statement: >
|
|
rdfs:label is never written by hand. It is produced by the derivation
|
|
function from the IRI and stored in the source. A hand-typed label is a
|
|
violation even when its value happens to be correct.
|
|
scope: [class, object_property, datatype_property, annotation_property, individual]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Generate and store, rather than store only or compute at read time. The
|
|
label stays in the source so SPARQL and third-party tooling behave normally,
|
|
while a script regenerates it at every commit and the gate fails if a stored
|
|
label differs from the derived one. Redundancy becomes harmless because it
|
|
is checked: rather than hoping two facts stay consistent, the model makes
|
|
inconsistency detectable.
|
|
examples:
|
|
- {from: label absent, to: violation, note: null}
|
|
- {from: label differing from the derivation, to: "violation — drift", note: null}
|
|
|
|
# --------------------------------------------------------------- DECLARATION
|
|
|
|
- id: TN-024
|
|
category: Declaration
|
|
title: Every active term carries a comment
|
|
statement: >
|
|
rdfs:comment is mandatory on every active term. On a property the comment is
|
|
structured: the definition, plus the scope constraint or the confusion to be
|
|
avoided. On a class the form is free.
|
|
scope: [class, object_property, datatype_property, annotation_property]
|
|
severity: BLOCKING
|
|
control: {tier: [script, human], executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A comment that paraphrases the name prevents nothing. A good one prevents a
|
|
specific error: it says where the property may and may not be declared, or
|
|
which neighbouring term it must not be confused with. The structure is
|
|
imposed on properties because they are where ambiguity costs most — they are
|
|
asserted thousands of times by people who will not read the model. The
|
|
script checks presence; only a reviewer checks value, which is why the rule
|
|
names two tiers.
|
|
examples:
|
|
- {from: "\"the owner\"", to: "definition plus where it may be declared and what it excludes", note: null}
|
|
|
|
- id: TN-025
|
|
category: Declaration
|
|
title: Every property declares its domain and range
|
|
statement: >
|
|
rdfs:domain and rdfs:range are mandatory on every property, EXCEPT where the
|
|
property is deliberately polymorphic. A polymorphic property states so in
|
|
its comment and has its scope declared in SHACL. Silent absence is a
|
|
violation; documented absence is not.
|
|
scope: [object_property, datatype_property]
|
|
severity: BLOCKING
|
|
control: {tier: [script, human], executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A property with no domain cannot be targeted by any control. But some
|
|
properties are polymorphic by design, their scope controlled class by class
|
|
in SHACL rather than by twin properties; giving those an rdfs:domain would
|
|
trigger the RDFS retyping described in TN-028. The rule therefore separates
|
|
the two cases rather than demanding a domain everywhere.
|
|
examples:
|
|
- {from: a polymorphic property with no domain and no comment, to: violation, note: silence is indistinguishable from omission}
|
|
- {from: a polymorphic property with no domain, documented, to: conforming, note: scope declared in SHACL}
|
|
|
|
- id: TN-026
|
|
category: Declaration
|
|
title: Every term declares how it was authored
|
|
statement: >
|
|
pr:authoringMode is mandatory on every term. pr:harvestSource is mandatory
|
|
if and only if the mode is HARVESTED.
|
|
scope: [class, object_property, datatype_property, annotation_property]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Provenance decides who may edit a term and what a divergence means. A term
|
|
stating where its data comes from without stating that it is harvested, or
|
|
declaring itself harvested without naming a source, is half-declared in a
|
|
way no control can catch. Declared symmetrically, provenance also makes a
|
|
harvester specifiable from the model itself rather than from a side
|
|
document.
|
|
examples:
|
|
- {from: harvestSource present, authoringMode absent, to: "authoringMode HARVESTED", note: null}
|
|
- {from: "authoringMode HARVESTED, harvestSource absent", to: harvestSource declared, note: null}
|
|
|
|
- id: TN-027
|
|
category: Declaration
|
|
title: A concrete class has one layer and one provenance
|
|
statement: >
|
|
Every concrete class descends from exactly one layer root and from exactly
|
|
one provenance axis. Controlled-vocabulary classes are outside the layers by
|
|
nature — a named and closed exception.
|
|
scope: [class]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A class with no layer is invisible to every layer-scoped control and to
|
|
every view organised by layer; a class with two is ambiguous in both. Where
|
|
a family of classes genuinely sits outside the data layers — actors,
|
|
governance objects — the answer is an explicit root of its own, not silence.
|
|
Adding such a root by INSERTION above an existing abstraction, rather than
|
|
by renaming it, keeps the ranges that point at that abstraction readable: a
|
|
range must state the nature of its target, not its position in the model.
|
|
examples:
|
|
- {from: a class with no parent, to: attached to its layer root, note: null}
|
|
- {from: renaming an abstraction into a layer root, to: inserting the layer root above it, note: preserves the meaning of every range that points at it}
|
|
|
|
- id: TN-028
|
|
category: Declaration
|
|
title: A sub-property inherits the domain of its parent
|
|
statement: >
|
|
Declaring rdfs:subPropertyOf does not shield a property from the
|
|
rdfs:domain of its parent. Every assertion of a sub-property is also an
|
|
assertion of the parent, so the parent's domain applies and its RDFS
|
|
retyping takes effect.
|
|
scope: [object_property, datatype_property]
|
|
severity: BLOCKING
|
|
control: {tier: [script, human], executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
In RDFS a domain is not a rejecting constraint but a retyping inference.
|
|
Attaching a sub-property to a parent whose domain does not fit will silently
|
|
retype its subjects — an object of one layer quietly becomes an object of
|
|
another, entering counts, controls and traversals it was never part of.
|
|
Where two relations share a verb but not a domain, they must not be merged
|
|
and must not be linked by subPropertyOf; the unification, if one is needed,
|
|
belongs on a shared upper property with no domain of its own.
|
|
examples:
|
|
- {from: "a dashboard field asserted through a property whose domain is a warehouse column", to: "the dashboard field is inferred to BE a warehouse column", note: silent retyping}
|
|
- {from: "linking the two by rdfs:subPropertyOf", to: "no protection — the parent's domain still applies", note: null}
|
|
- {from: "a shared upper property with no domain", to: "end-to-end traversal without merging IRIs", note: the correct unification}
|
|
|
|
# ----------------------------------------------------------------- EVOLUTION
|
|
|
|
- id: EV-001
|
|
category: Evolution
|
|
title: A rename is never an edit in place
|
|
statement: >
|
|
A rename creates a new term and keeps the old one with owl:deprecated true
|
|
and dcterms:isReplacedBy. No IRI is modified in place. The deprecated stub
|
|
carries no edge: no subClassOf, no label, no comment — only its type and the
|
|
two axioms.
|
|
scope: [procedure]
|
|
severity: BLOCKING
|
|
control: {tier: human, executor: commit review, procedure: null}
|
|
filiation: A-Box LC-002
|
|
rationale: >
|
|
Nothing propagates in RDF. An IRI is a string copied into every triple, so
|
|
renaming means rewriting every triple where it appears as subject, predicate
|
|
or object — declaration, subclass axioms, domain, range, instance typing,
|
|
shape targets and paths, queries, viewers, documentation. Edges are moved,
|
|
not duplicated. The stub exists so that a consumer holding an older export
|
|
can still be told what replaced the term.
|
|
examples:
|
|
- {from: editing an IRI in place, to: new term plus deprecated stub, note: null}
|
|
|
|
- id: EV-002
|
|
category: Evolution
|
|
title: A withdrawal requires proof of non-instantiation
|
|
statement: >
|
|
A term is removed only after a count proving it was never instantiated, the
|
|
count being attached to the commit. Without proof, deprecation only.
|
|
scope: [procedure]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: count query attached to the commit, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Removal is the one irreversible operation on a vocabulary. The proof is
|
|
cheap — a single counting query — and it is what separates a safe withdrawal
|
|
from a silent data loss.
|
|
examples:
|
|
- {from: deleting a term, to: "SELECT (COUNT(?s) AS ?n) WHERE { ?s a pr:Term }", note: must return zero}
|
|
|
|
- id: EV-003
|
|
category: Evolution
|
|
title: A deletion removes every reference to the subject
|
|
statement: >
|
|
A subject removed while its IRI is still cited elsewhere is recreated by
|
|
RDFS inference. Every reference is removed in the same operation.
|
|
scope: [procedure]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: dangling-reference check in the migration script, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A graph has no foreign keys. Deleting the block that declares a subject
|
|
leaves every triple that names it intact, and a reasoner will reconstruct a
|
|
hollow node from them — present in traversals, absent from every control.
|
|
examples:
|
|
- {from: deleting a subject block, to: deleting the block and every triple naming it, note: null}
|
|
|
|
- id: EV-004
|
|
category: Evolution
|
|
title: A change is validated before it is applied
|
|
statement: >
|
|
Every modification of the model is agreed before any artifact is written.
|
|
Dry run first, apply second.
|
|
scope: [procedure]
|
|
severity: BLOCKING
|
|
control: {tier: human, executor: commit review, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
The damage from a bad change is rarely in the change itself but in
|
|
everything that silently depended on the old state. A dry run that reports
|
|
what it would touch is what makes that dependency visible while it can still
|
|
be discussed.
|
|
examples:
|
|
- {from: "migrate.py --apply as the first run", to: "migrate.py, then --apply", note: dry run is the default}
|
|
|
|
- id: EV-005
|
|
category: Evolution
|
|
title: Graph edits go through an RDF parser
|
|
statement: >
|
|
Any transformation of the graph is performed with an RDF parser. No regular
|
|
expression applied line by line to Turtle.
|
|
scope: [procedure, script]
|
|
severity: BLOCKING
|
|
control: {tier: human, executor: script review, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Turtle is a structured syntax and a line is not a unit of meaning. A literal
|
|
may contain the very characters a regex splits on, so a text-level edit that
|
|
looks correct on every example can truncate a value on the one that matters.
|
|
A parser knows the difference between a delimiter and a character inside a
|
|
string; a regex does not, and every workaround for that is a partial
|
|
reimplementation of the parser.
|
|
examples:
|
|
- {from: "re.sub on the file text", to: remove and add on parsed triples, note: null}
|
|
- {from: hand-written clause splitting, to: parser, note: a workaround is evidence the wrong tool is in use}
|
|
|
|
- id: EV-006
|
|
category: Evolution
|
|
title: Every script is idempotent
|
|
statement: >
|
|
A script may be replayed without changing the result. Its guard tests the
|
|
TARGET state, not the source state, and it counts its own result. Acceptance
|
|
test: two consecutive runs, the second reporting zero modifications.
|
|
scope: [script]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: double run in continuous integration, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A chain of transformations is only safe if any step can be replayed. A guard
|
|
that tests whether work remains to be done breaks as soon as another script
|
|
has already changed the source; a guard that tests whether the target state
|
|
exists survives it. The self-count is what turns the property into a
|
|
verifiable one.
|
|
examples:
|
|
- {from: appending unconditionally, to: appending only if the target is absent, note: guard on the target state}
|
|
|
|
- id: EV-007
|
|
category: Evolution
|
|
title: The replayable source is the start of the chain
|
|
statement: >
|
|
The replayable source is the starting file plus the ordered sequence of
|
|
scripts, never an intermediate committed state. Hand-editing a file produced
|
|
by a script is forbidden, including for a typo: the script is corrected and
|
|
the chain replayed.
|
|
scope: [procedure]
|
|
severity: BLOCKING
|
|
control: {tier: human, executor: commit review, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A committed intermediate file is a convenience, not a source. As soon as one
|
|
hand edit exists in it that no script performs, replaying the chain no
|
|
longer reproduces the committed state and nobody can say which of the two is
|
|
right. Keeping the chain authoritative is also what makes a failed migration
|
|
recoverable: restart from the last true source with a corrected script,
|
|
rather than repair a half-migrated file by hand.
|
|
examples:
|
|
- {from: fixing a typo in a generated file, to: fixing the script and replaying, note: null}
|
|
|
|
- id: EV-008
|
|
category: Evolution
|
|
title: Every artifact has a producer script
|
|
statement: >
|
|
No artifact contains hard-coded data. Every deliverable — viewer,
|
|
documentation, workbook, diagram — is regenerated from a versioned source by
|
|
a script.
|
|
scope: [script, artifact]
|
|
severity: BLOCKING
|
|
control: {tier: human, executor: artifact review, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
An artifact that cannot be regenerated becomes stale the moment the model
|
|
moves, and there is no way to tell whether it is stale or merely different.
|
|
This rulebook applies the rule to itself: the source is one file, the
|
|
document and the workbook are generated from it.
|
|
examples:
|
|
- {from: a hand-maintained workbook, to: a source file plus a generator, note: null}
|
|
|
|
- id: EV-009
|
|
category: Evolution
|
|
title: A change propagates in a single merge
|
|
statement: >
|
|
A change is propagated to the vocabulary, the instances, the shapes, the
|
|
consumers and the documentation in one branch, merged as a block. The main
|
|
line never sees a state where one has moved and another has not.
|
|
scope: [procedure]
|
|
severity: BLOCKING
|
|
control: {tier: human, executor: merge review, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Partial propagation is not a smaller version of a change, it is a different
|
|
and invalid state. Its worst form is silent: a gate that validates the
|
|
stale half and reports success. A single merge makes the intermediate state
|
|
unreachable rather than merely discouraged.
|
|
examples:
|
|
- {from: committing the vocabulary and updating the shapes later, to: one branch, one merge, one tag, note: null}
|
|
|
|
- id: EV-010
|
|
category: Evolution
|
|
title: Shapes declare the ontology version they target
|
|
statement: >
|
|
The shapes graph declares dcterms:conformsTo equal to the owl:versionIRI of
|
|
the ontology. Any divergence aborts validation immediately.
|
|
scope: [shape, script]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: run_shacl_validation.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
Validating against the wrong shapes does not fail loudly. It produces a long
|
|
list of violations that reads exactly like a regression of the model, and
|
|
the time goes into interpreting them rather than noticing the mismatch. One
|
|
declared triple and one check turn that into a single line naming the real
|
|
problem.
|
|
examples:
|
|
- {from: hundreds of violations to interpret, to: "ABORT — shapes target a different ontology version", note: null}
|
|
|
|
- id: EV-011
|
|
category: Evolution
|
|
title: No blocking rule without an executor
|
|
statement: >
|
|
A rule may be declared BLOCKING only if a named script or shape enforces it.
|
|
A blocking rule with no control is a statement of intent.
|
|
scope: [rule]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: rulebook source self-check, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
An unenforced blocking rule is worse than an absent one: it is cited as
|
|
though it held, and the gap only surfaces when something it should have
|
|
caught reaches production. Making the executor a mandatory field turns the
|
|
omission into something a script can detect on the rulebook itself.
|
|
examples:
|
|
- {from: "severity BLOCKING with no executor", to: violation, note: detected by the generators before they write anything}
|
|
|
|
- id: EV-012
|
|
category: Evolution
|
|
title: Every rule names the tier that enforces it
|
|
statement: >
|
|
Controls exist at three tiers — SHACL for the shape of the graph, scripts
|
|
for the file, the lexicon and naming, human review for semantics. Every rule
|
|
names its tier. A rule with no assigned tier is a violation.
|
|
scope: [rule]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: rulebook source self-check, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
SHACL validates a graph, not a file, and not meaning. Two IRIs standing for
|
|
the same notion produce two individually valid graphs; a duplicated block of
|
|
text produces identical triples and no complaint. Naming the tier forces the
|
|
question of what actually catches each rule, and pushes the ones SHACL
|
|
cannot see to a control that can: a normalised-label index reviewed by a
|
|
person, a file-level hash, a naming script, a review checklist.
|
|
examples:
|
|
- {from: two IRIs for one notion, to: normalised label index plus human review, note: SHACL cannot see it}
|
|
- {from: a duplicated text block, to: file-level block hashing, note: identical triples, no violation}
|
|
|
|
- id: EV-013
|
|
category: Evolution
|
|
title: No sh:pattern outside the SHACL specification
|
|
statement: >
|
|
sh:pattern uses XPath 2.0 regular expressions, which support neither
|
|
lookbehind nor lookahead. A constraint is expressed positively — what the
|
|
string must be — rather than negatively. Where that is impossible the rule
|
|
leaves SHACL and moves to the script tier under EV-012.
|
|
scope: [shape]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: run_shacl_validation.py, procedure: null}
|
|
filiation: A-Box NR-001
|
|
rationale: >
|
|
A pattern written in a richer regex dialect does not degrade gracefully: the
|
|
validator fails to compile it, and the rule that depended on it silently
|
|
stops being enforced while still being declared blocking.
|
|
examples:
|
|
- {from: "a pattern using (?<! ... )", to: "a pattern describing the whole accepted string", note: express positively}
|
|
|
|
- id: EV-014
|
|
category: Evolution
|
|
title: The term namespace is never versioned
|
|
statement: >
|
|
The namespace of terms is not versioned. The version lives in
|
|
owl:versionIRI alone.
|
|
scope: [ontology]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: check_tbox_naming.py, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A versioned term namespace changes every IRI at every release, breaking
|
|
every consumer for no benefit. It also fails silently in the other
|
|
direction: a file bound to an outdated namespace targets IRIs that no longer
|
|
exist, so every shape matches nothing and validation passes while testing
|
|
nothing at all.
|
|
examples:
|
|
- {from: "a prefix bound to a versioned namespace", to: "a prefix bound to the stable namespace", note: the version lives in owl:versionIRI}
|
|
|
|
- id: EV-015
|
|
category: Evolution
|
|
title: Every procedure that writes produces an execution log
|
|
statement: >
|
|
Any procedure that modifies the graph writes a versioned execution log,
|
|
committed alongside the change. The log records the attempt number and
|
|
timestamp, a checksum of every input consumed, the ontology version and the
|
|
version the shapes declare they target, a count per step, the full
|
|
validation report, and a difference against the previous attempt.
|
|
scope: [procedure, script]
|
|
severity: BLOCKING
|
|
control: {tier: script, executor: the procedure's own runner, procedure: null}
|
|
filiation: null
|
|
rationale: >
|
|
A counter printed to the terminal disappears with the terminal. When a
|
|
validation run fails, the question is never only what failed but whether it
|
|
is the same failure as the previous attempt: a violation that persists
|
|
unchanged after a correction says the cause lies elsewhere, while one that
|
|
moves every time says the change is being patched rather than fixed. Those
|
|
are opposite diagnoses and neither is available without a record of the
|
|
previous run. The input checksums serve the same purpose one level down,
|
|
separating a wrong model from a stale file — the ambiguity that made a past
|
|
incident expensive to explain. The log is also the natural carrier of the
|
|
proof of non-instantiation required by EV-002, which otherwise has no
|
|
defined format.
|
|
examples:
|
|
- {from: a counter printed to standard output, to: a versioned log committed with the change, note: null}
|
|
- {from: "a failed run with no record of the previous one", to: "a difference against the previous attempt", note: what makes correct-or-abandon decidable}
|
|
|
|
# ====== ANNEX: ABSTRACTNESS (TN-006, TN-007) ======
|
|
abstractness:
|
|
- {term: MetaModelObject, is_abstract: true, note: "root of the model"}
|
|
- {term: DefinedObject, is_abstract: true, note: "provenance axis"}
|
|
- {term: CapturedObject, is_abstract: true, note: "provenance axis"}
|
|
- {term: OwnershipLayerObject, is_abstract: true, note: "layer root"}
|
|
- {term: BusinessLayerObject, is_abstract: true, note: "layer root"}
|
|
- {term: LogicalLayerObject, is_abstract: true, note: "layer root"}
|
|
- {term: PhysicalLayerObject, is_abstract: true, note: "NEW — omission in v1.6"}
|
|
- {term: DeliveryLayerObject, is_abstract: true, note: "layer root"}
|
|
- {term: ConsumptionLayerObject, is_abstract: true, note: "layer root"}
|
|
- {term: GovernanceLayerObject, is_abstract: true, note: "NEW — seventh layer root"}
|
|
- {term: DataStructure, is_abstract: true, note: "harvesting types the concrete sort"}
|
|
- {term: KeyConstraint, is_abstract: true, note: "only PrimaryKey and ForeignKey instantiate"}
|
|
- {term: Actor, is_abstract: true, note: "renaming would degrade the range of ownedBy and hasPublisher"}
|
|
- {term: DataDomain, is_abstract: false, note: null}
|
|
- {term: DataSubDomain, is_abstract: false, note: null}
|
|
- {term: BusinessObject, is_abstract: false, note: "ends in Object and is heavily instantiated - refutes any suffix rule"}
|
|
- {term: BusinessConcept, is_abstract: false, note: null}
|
|
- {term: Metric, is_abstract: false, note: "concrete despite having KeyPerformanceIndicator beneath it"}
|
|
- {term: KeyPerformanceIndicator, is_abstract: false, note: null}
|
|
- {term: DataObject, is_abstract: false, note: null}
|
|
- {term: DataElement, is_abstract: false, note: null}
|
|
- {term: Database, is_abstract: false, note: null}
|
|
- {term: Schema, is_abstract: false, note: null}
|
|
- {term: Field, is_abstract: false, note: null}
|
|
- {term: System, is_abstract: false, note: null}
|
|
- {term: Transformation, is_abstract: false, note: null}
|
|
- {term: BaseTable, is_abstract: false, note: null}
|
|
- {term: ExternalTable, is_abstract: false, note: null}
|
|
- {term: View, is_abstract: false, note: null}
|
|
- {term: PrimaryKey, is_abstract: false, note: null}
|
|
- {term: ForeignKey, is_abstract: false, note: null}
|
|
- {term: DataProduct, is_abstract: false, note: null}
|
|
- {term: DataContract, is_abstract: false, note: null}
|
|
- {term: DataInterface, is_abstract: false, note: null}
|
|
- {term: BusinessIntelligenceWorkspace, is_abstract: false, note: null}
|
|
- {term: BusinessIntelligenceDataSource, is_abstract: false, note: null}
|
|
- {term: BusinessIntelligenceField, is_abstract: false, note: null}
|
|
- {term: BusinessIntelligenceReport, is_abstract: false, note: null}
|
|
- {term: DataDomainOwner, is_abstract: false, note: null}
|
|
- {term: DataSubDomainOwner, is_abstract: false, note: null}
|
|
- {term: DataProductOwner, is_abstract: false, note: null}
|
|
- {term: DataSteward, is_abstract: false, note: null}
|
|
- {term: DataGovernanceLead, is_abstract: false, note: null}
|
|
- {term: SystemType, is_abstract: false, note: "must stay concrete — it types five individuals"}
|
|
|
|
# ====== ANNEX: DISPLAY (TN-011, TN-022) ======
|
|
display:
|
|
- {iri: DataDomain, label: "Data Domain", short_label: "Domain", acronym: "DD", note: "DD is already the short form used inside A-Box identifiers under NR-014"}
|
|
- {iri: DataSubDomain, label: "Data Sub Domain", short_label: "Sub Domain", acronym: "SD", note: "no hyphen"}
|
|
- {iri: DataDomainOwner, label: "Data Domain Owner", short_label: "Domain Owner", acronym: "DDO", note: null}
|
|
- {iri: DataSubDomainOwner, label: "Data Sub Domain Owner", short_label: "Sub Domain Owner", acronym: "SDO", note: null}
|
|
- {iri: DataProductOwner, label: "Data Product Owner", short_label: "Product Owner", acronym: "PO", note: "NOT DPO — the initialism is taken by Data Protection Officer"}
|
|
- {iri: DataSteward, label: "Data Steward", short_label: "Steward", acronym: null, note: null}
|
|
- {iri: DataGovernanceLead, label: "Data Governance Lead", short_label: "Governance Lead", acronym: "DGL", note: null}
|
|
- {iri: DataElement, label: "Data Element", short_label: "Element", acronym: null, note: "drives the name of hasElement and hasGrainElement"}
|
|
- {iri: BusinessConcept, label: "Business Concept", short_label: "Concept", acronym: null, note: "drives the name of usesConcept"}
|
|
- {iri: BusinessObject, label: "Business Object", short_label: null, acronym: "BO", note: null}
|
|
- {iri: KeyPerformanceIndicator, label: "Key Performance Indicator", short_label: "KPI", acronym: "KPI", note: "the acronym channel is what makes TN-002 bearable"}
|
|
- {iri: BusinessIntelligenceWorkspace, label: "Business Intelligence Workspace", short_label: "BI Workspace", acronym: null, note: null}
|
|
- {iri: BusinessIntelligenceDataSource, label: "Business Intelligence Data Source", short_label: "BI Data Source", acronym: null, note: null}
|
|
- {iri: BusinessIntelligenceField, label: "Business Intelligence Field", short_label: "BI Field", acronym: null, note: null}
|
|
- {iri: BusinessIntelligenceReport, label: "Business Intelligence Report", short_label: "BI Report", acronym: null, note: null}
|
|
- {iri: OwnershipLayerObject, label: "Ownership Layer Object", short_label: null, acronym: null, note: "the categorisation role moves into rdfs:comment"}
|