rdfs:comment"Applies to everything governance asserts. Observed objects are deliberately exempt: see ObservedObjectShape.";
sh:property[
sh:pathpr:hasIdentifier;sh:datatypexsd:string;
sh:minCount1;sh:maxCount1;
sh:severitysh:Violation;
sh:message"DQ-001 -- every curated object carries exactly one identifier. NOTE: uniqueness across the graph is NOT tested here; SHACL checks cardinality only. The reconciliation job tests uniqueness."];
sh:property[
sh:pathpr:hasName;sh:datatypexsd:string;
sh:minCount1;sh:maxCount1;sh:minLength3;
sh:severitysh:Violation;
sh:message"Every curated object carries exactly one canonical name."];
sh:message"LC-001 -- status must be one of DRAFT, UNDER_REVIEW, PUBLISHED, DEPRECATED, RETIRED."];
sh:property[
sh:pathpr:owningDomain;sh:maxCount1;
sh:severitysh:Violation;
sh:message"OW-001 -- at most one owning Data Domain. On a Data Element this may be re-declared to differ from the parent chain: that marks a borrowed element."];
sh:property[
sh:pathpr:hasVersion;sh:maxCount1;
sh:severitysh:Warning;
sh:message"A published object should carry a version."].
prs:PublishedShapeash:NodeShape;
sh:targetClasspr:CuratedObject;
rdfs:comment"Extra demands that only bite once an object claims to be PUBLISHED. Keeping these off the base shape is what lets a domain be documented progressively.";
sh:sparql[
sh:severitysh:Violation;
sh:message"OW-007 -- an object flagged TO_ARBITRATE cannot be PUBLISHED. Resolve at the Cross-Domain Council first.";
rdfs:comment "No identifier, no name, no owner, no status, no version. A harvested column is attributed through the Data Element mapped to it, never asserted directly. What it must carry is enough to be re-found on the next run." ;
sh:message "A Field carries DATABASE.SCHEMA.RELATION.COLUMN. A bare column name is ambiguous as soon as it exists in two tables, so the FQN is the real join key against INFORMATION_SCHEMA." ] ;
sh:message "BR-004 -- a Business Object is about exactly one Business Concept. Replaces the old 3..10 minimum, which was satisfied in v0.6 by attaching unrelated concepts. If the subject cannot be named, the object is not scoped." ] ;
sh:message "A Business Object carries no definition of its own: meaning belongs to the Business Concept named by aboutConcept. Two definitions for one notion are two definitions free to diverge. MIGRATION: move the text to that Concept." ] .
rdfs:comment "Note what is NOT here: no requirement that a Concept be realized by any Data Element or measured by any Metric. The business layer is allowed to run ahead of the data. Coverage is a dashboard metric, not a rule." ;
sh:message "BR-003 -- the presence of a formula is what makes something a Metric. A Concept with a formula is a misclassified Metric." ] ;
sh:property [
sh:path pr:hasSource ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Business Concept has no source of truth. Its authority is its owning domain and its definition belongs in the glossary. Naming a system here would ask which of the systems that USE the concept defines it -- a question with no answer." ] .
sh:message "A Metric states every unit its variants are expressed in. NOT capped at one: Sell Out Volume is computed in 9L and in L, Sell Out Value in EUR, USD and local currency. The single-unit rule belongs on the Data Element, where it holds -- see DataElementShape." ] ;
sh:message "A Metric measures at least one Business Concept -- this is how a measure element reaches business meaning." ] ;
sh:property [
sh:path pr:hasGranularity ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Grain moved off the Metric in v1.1. A Metric is a calculation rule and has no rows; grain belongs to the Data Object, stated with hasGrainElement and checkable against the real primary key." ] ;
sh:property [
sh:path pr:physicalName ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Metric must not carry a physical name -- that is a layer leak. Under BR-013 the physical name belongs on the Data Element." ] .
# The source of truth of a Metric is asserted by exception, then checked.
# "Where can I get this metric" is a first-order question and the derivation
# chain will have gaps before harvesting, so the assertion stands on its own --
# but it must agree with the elements that implement the metric, or the two
# will drift the day an element moves.
prs:MetricSourceShape a sh:NodeShape ;
sh:targetClass pr:Metric ;
sh:sparql [
sh:severity sh:Violation ;
sh:message "A Metric's source of truth must match the source of at least one element that computes it. If they disagree, either the metric points at the wrong system or an element has moved." ;
# BR-013, finally executable. v0.1.1 declared it BLOCKING with no shape.
prs:MotherMetricShape a sh:NodeShape ;
sh:targetClass pr:Metric ;
sh:sparql [
sh:severity sh:Warning ;
sh:message "BR-013 -- a PUBLISHED Metric should be implemented by at least one Data Element through computedBy. A metric no element computes is a definition with nothing behind it." ;
sh:message "BR-006 -- a Data Object represents exactly one Business Object. This is the pivot: the single junction between governance and technology." ] ;
sh:property [
sh:path pr:hasElement ; sh:class pr:DataElement ;
sh:minCount 1 ;
sh:severity sh:Violation ;
sh:message "A Data Object holds at least one Data Element." ] ;
sh:message "Stewardship is declared on the Business Object and inherited through represents. Declaring it here allows two answers to the same question." ] ;
sh:property [
sh:path pr:hasSource ; sh:maxCount 1 ;
sh:not [ sh:pattern "/" ] ;
sh:severity sh:Warning ;
sh:message "hasSource names the SYSTEM of origin, nothing else. A value like 'SODH Gold / F_SELL_OUT' packs a system and a physical relation into one string -- the physical relation belongs in physicalName." ] .
sh:message "A Data Element carries exactly one unit. This is the counterpart of the Metric rule: the mother metric lists the units its variants use, each variant states the single unit it is actually expressed in. An element with two units is two elements." ] ;
sh:message "A Data Element represents at most one Business Concept. If a column carries two business notions, it should almost certainly be split -- so the case where a steward cannot choose is exactly the one worth looking at." ] ;
sh:property [
sh:path pr:monitoredBy ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Stewardship is inherited from the Business Object, not declared here." ] .
# The core rule of v1.1.
prs:DataElementMeaningShape a sh:NodeShape ;
sh:targetClass pr:DataElement ;
rdfs:comment "A Data Element reaches business meaning by exactly one of two routes: it represents a Business Concept (dimensional), or a Metric is computedBy it (measure). sh:xone rejects zero routes and two alike. A measure element must NOT also represent a Concept: the chain Metric -> measures -> Concept already carries the meaning, and carries more, since it names which metric it travels through." ;
sh:message "A Data Element must reach business meaning by exactly one route: represents a Business Concept (dimensional), or be computedBy a Metric (measure). Never both, never neither. A curated column with no business meaning is debt, not an acceptable state." .
sh:message "A Data Product is operated by exactly one Sub-Domain. It spans several Business Objects and cannot inherit accountability, so it declares its own -- it is a consumable, and there must be no ambiguity about who runs it." ] ;
sh:message "A Data Product should name exactly one Product Owner. A warning rather than a block: the SODH owner is genuinely not appointed, and a blocking rule that cannot be satisfied truthfully is how BR-004 produced padding." ] ;
sh:message "A Data Product packages at least one Data Object. The Business Objects it makes available are derived from these, which is why no direct link to a Business Object is needed." ] ;
sh:property [
sh:path pr:governedBy ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "A Data Product should be governed by a Data Contract." ] ;
sh:property [
sh:path pr:exposes ; sh:class pr:DataInterface ;
sh:severity sh:Info ;
sh:message "The channels through which consumers actually get the data." ] .
prs:DataInterfaceShape a sh:NodeShape ;
sh:targetClass pr:DataInterface ;
sh:property [
sh:path pr:servedBy ; sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "An interface should name what physically backs it. Unlike the old Data Asset test, this is verifiable: a grant, a share or an endpoint either exists or it does not." ] .
rdfs:comment "realizesMetric is a transitional control: asserted directly, with no rule written against the lineage chain. Making the assertion conditional on intermediate mappings existing would create confusion and discourage people from filling it in at all." ;
sh:property [
sh:path pr:realizesMetric ; sh:class pr:Metric ;
sh:maxCount 1 ;
sh:severity sh:Info ;
sh:message "Which certified Metric this BI field implements. Comparing expressions across fields realizing one Metric is the harmonisation measure." ] .
sh:message "NR-001 -- a canonical name must not end in a physical suffix. Written as sh:not[sh:pattern] rather than a lookbehind: sh:pattern is normatively XPath 2.0 regex, which has no lookbehind, so the v0.1.1 form only worked under pySHACL." ] ;
sh:property [
sh:path pr:hasName ;
sh:not [ sh:pattern "^[a-z_]+$" ] ;
sh:severity sh:Warning ;
sh:message "NR-002 -- a canonical name is written for humans, not as a snake_case identifier." ] .