@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix schema: <http://schema.org/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<https://deannaburt.com/#shape-ArticleCompleteness> a sh:NodeShape ;
    rdf:value "Warning, not Violation, and deliberately: Google's Article rich result wants all three, but a missing image is the operator's copy to fix, not a bug in this tool. Measured against the fixture matrix before choosing the severity — 0 of 3 Articles there carry an image, so gating on it would fail CI on correct output." ;
    sh:message "An Article should carry a headline, a publication date and an image." ;
    sh:property <https://deannaburt.com/#shape-ArticleCompleteness-datePublished>,
        <https://deannaburt.com/#shape-ArticleCompleteness-headline>,
        <https://deannaburt.com/#shape-ArticleCompleteness-image> ;
    sh:severity sh:Warning ;
    sh:targetClass schema:Article .

<https://deannaburt.com/#shape-BreadcrumbPositions> a sh:NodeShape ;
    rdf:value "ListItem is the most numerous class in a typical export (41 of 637 triples in the fixture) and a position that is missing or doubled silently reorders a breadcrumb trail in search results." ;
    sh:message "Every breadcrumb entry needs exactly one position and one name." ;
    sh:property <https://deannaburt.com/#shape-BreadcrumbPositions-position> ;
    sh:severity sh:Violation ;
    sh:targetClass schema:ListItem .

<https://deannaburt.com/#shape-EntityUriSpace> a sh:NodeShape ;
    rdf:value "The one shape whose content is entity-specific, which is why the file is emitted per entity rather than once for the engine. A subject outside the entity's base means the export is asserting facts under someone else's namespace. The trailing slash is part of the rule, not decoration: `identifiers.py` mints every id as `{base}/#kind-slug`, and when this shape first ran, five call sites in layer2 and transforms were hand-rolling `{base}#…` instead. Both forms sit inside the namespace but they are distinct IRIs, so a node minted through both paths would never have merged. Those sites now go through the helpers; this pins it." ;
    sh:message "Nodes this export describes must be identified under the entity's own base." ;
    sh:pattern "^https://deannaburt\\.com/" ;
    sh:severity sh:Warning ;
    sh:targetSubjectsOf rdf:type .

<https://deannaburt.com/#shape-IriIdentity> a sh:NodeShape ;
    rdf:value "BHE-DES-RDF-001 §8.5. A blank node gets a random label on every run, so an otherwise-identical export produces byte-different Turtle and the manifest's sha256 stops meaning anything. Phase 2 minted @ids for all 26 shapes that used to be blank; this is the shape that keeps them minted." ;
    sh:message "Every typed node must carry an IRI @id, never a blank node." ;
    sh:nodeKind sh:IRI ;
    sh:severity sh:Violation ;
    sh:targetSubjectsOf rdf:type .

<https://deannaburt.com/#shape-OfferCatalogBackReference> a sh:NodeShape ;
    rdf:value "The defect §3.4 opens with. The old _deduplicate_graph kept whichever same-@id node had more keys, so layer2's deliberate partial {@id, @type, hasOfferCatalog} Organization node lost to the richer Layer-1 one and no ProductsServices page ever shipped the back-reference. Phase 0.5 fixed it; this shape is what notices if it regresses." ;
    sh:message "An OfferCatalog must be referenced by something's hasOfferCatalog." ;
    sh:property <https://deannaburt.com/#shape-OfferCatalogBackReference-inv-hasOfferCatalog> ;
    sh:severity sh:Violation ;
    sh:targetClass schema:OfferCatalog .

<https://deannaburt.com/#shape-ProductIdentity> a sh:NodeShape ;
    rdf:value "Phase 1 wired the product model to the generator for the first time; before it, no product entered through the UI reached the JSON-LD at all. Warning rather than Violation because most small-business catalogues have no GTIN and many services have no price — the fixture's own Product has neither." ;
    sh:message "A Product should carry an offer and a stable identifier." ;
    sh:property <https://deannaburt.com/#shape-ProductIdentity-offers>,
        <https://deannaburt.com/#shape-ProductIdentity-sku> ;
    sh:severity sh:Warning ;
    sh:targetClass schema:Product .

<https://deannaburt.com/#shape-SingularHeadline> a sh:NodeShape ;
    rdf:value "The same collision detector aimed where it bit hardest. Academic article titles are the routine case of titles over 60 characters, so Articles are where truncated slugs collided." ;
    sh:message "An Article may carry only one headline and one publication date." ;
    sh:property <https://deannaburt.com/#shape-SingularHeadline-datePublished>,
        <https://deannaburt.com/#shape-SingularHeadline-headline> ;
    sh:severity sh:Violation ;
    sh:targetClass schema:Article .

<https://deannaburt.com/#shape-SingularName> a sh:NodeShape ;
    rdf:value "The observable symptom of the §3.4 @id-collision defect. Two truncated slugs colliding made dedup merge two unrelated things into one node, and a merged node carries both names. Phase 0.6 disambiguates the slugs; a second name here means it stopped working. Cheapest collision detector available — `name` is on almost every node." ;
    sh:message "A node may carry only one name." ;
    sh:property <https://deannaburt.com/#shape-SingularName-name> ;
    sh:severity sh:Violation ;
    sh:targetSubjectsOf schema:name .

<https://deannaburt.com/#shape-ArticleCompleteness-datePublished> a sh:PropertyShape ;
    sh:message "Article has no publication date." ;
    sh:minCount 1 ;
    sh:path schema:datePublished ;
    sh:severity sh:Warning .

<https://deannaburt.com/#shape-ArticleCompleteness-headline> a sh:PropertyShape ;
    sh:message "Article has no headline." ;
    sh:minCount 1 ;
    sh:path schema:headline ;
    sh:severity sh:Warning .

<https://deannaburt.com/#shape-ArticleCompleteness-image> a sh:PropertyShape ;
    sh:message "Article has no image." ;
    sh:minCount 1 ;
    sh:path schema:image ;
    sh:severity sh:Warning .

<https://deannaburt.com/#shape-BreadcrumbPositions-position> a sh:PropertyShape ;
    sh:datatype xsd:integer ;
    sh:maxCount 1 ;
    sh:message "ListItem has no single integer position." ;
    sh:minCount 1 ;
    sh:path schema:position ;
    sh:severity sh:Violation .

<https://deannaburt.com/#shape-OfferCatalogBackReference-inv-hasOfferCatalog> a sh:PropertyShape ;
    sh:message "OfferCatalog is orphaned — nothing points at it." ;
    sh:minCount 1 ;
    sh:path [ sh:inversePath schema:hasOfferCatalog ] ;
    sh:severity sh:Violation .

<https://deannaburt.com/#shape-ProductIdentity-offers> a sh:PropertyShape ;
    sh:message "Product has no Offer." ;
    sh:minCount 1 ;
    sh:path schema:offers ;
    sh:severity sh:Warning .

<https://deannaburt.com/#shape-ProductIdentity-sku> a sh:PropertyShape ;
    sh:message "Product has no sku (gtin/mpn/productID are equally acceptable)." ;
    sh:minCount 1 ;
    sh:path schema:sku ;
    sh:severity sh:Warning .

<https://deannaburt.com/#shape-SingularHeadline-datePublished> a sh:PropertyShape ;
    sh:maxCount 1 ;
    sh:message "Two publication dates on one Article." ;
    sh:path schema:datePublished ;
    sh:severity sh:Violation .

<https://deannaburt.com/#shape-SingularHeadline-headline> a sh:PropertyShape ;
    sh:maxCount 1 ;
    sh:message "Two headlines on one Article." ;
    sh:path schema:headline ;
    sh:severity sh:Violation .

<https://deannaburt.com/#shape-SingularName-name> a sh:PropertyShape ;
    sh:maxCount 1 ;
    sh:message "Two names on one node: an @id collision merged two things." ;
    sh:path schema:name ;
    sh:severity sh:Violation .

