From 526972b70aca16ff90c15f9cd752287e0d7eff6f Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:23:25 +0100 Subject: [PATCH 1/3] Add Annotation/Note shape (W3C Web Annotation Vocabulary) Adds a SHACL shape for oa:Annotation covering the common note, bookmark, comment, and highlight cases. Grounded in the W3C Web Annotation Vocabulary (Recommendation, 2017). Co-Authored-By: Claude Opus 4.7 (1M context) --- shapes/annotation.ttl | 106 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 shapes/annotation.ttl diff --git a/shapes/annotation.ttl b/shapes/annotation.ttl new file mode 100644 index 0000000..cbe1428 --- /dev/null +++ b/shapes/annotation.ttl @@ -0,0 +1,106 @@ +@prefix dc: . +@prefix dct: . +@prefix oa: . +@prefix prov: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix vs: . +@prefix xsd: . + +@prefix annotation_shape: . + +annotation_shape:AnnotationShape + a sh:NodeShape ; + sh:targetClass oa:Annotation ; + sh:name "Annotation Shape" ; + sh:description "SHACL shape describing a Web Annotation covering common note, bookmark, comment, and highlight cases." ; + dct:created "2026-04-23"^^xsd:date ; + vs:term_status "testing" ; + dc:source ; + prov:wasDerivedFrom ; + dct:references ; # references the oa:Annotation class from the W3C Web Annotation Vocabulary, which is the expected type of an annotation node, together with the oa:hasBody, oa:hasTarget, oa:bodyValue, and oa:motivatedBy properties used to relate an annotation to its body, target, textual body content, and motivation respectively. + dct:references ; # references the W3C Web Annotation Data Model recommendation which defines the conceptual model used by the Web Annotation Vocabulary. + sh:codeIdentifier "Annotation"; + + # Type: an annotation should be typed as oa:Annotation. + sh:property [ + sh:path rdf:type ; + sh:hasValue oa:Annotation ; + sh:name "Type" ; + sh:description "The RDF type of the annotation, which must include oa:Annotation." ; + sh:codeIdentifier "type"; + ] ; + + # Inline textual body (for simple comment / note cases). + sh:property [ + sh:path oa:bodyValue ; + sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:name "Body Value" ; + sh:description "Textual content of the annotation body, used for plain-text comments or notes (Web Annotation Vocabulary, oa:bodyValue)." ; + sh:codeIdentifier "bodyValue"; + ] ; + + # External or embedded body resource(s) (for richer body cases). + sh:property [ + sh:path oa:hasBody ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:name "Body" ; + sh:description "A resource that provides the content of the annotation (Web Annotation Vocabulary, oa:hasBody). May be repeated when an annotation has multiple bodies." ; + sh:codeIdentifier "hasBody"; + ] ; + + # Target(s) the annotation is about. Required for an annotation to be meaningful. + sh:property [ + sh:path oa:hasTarget ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:minCount 1 ; + sh:name "Target" ; + sh:description "The resource (or specific resource) that the annotation is about (Web Annotation Vocabulary, oa:hasTarget)." ; + sh:codeIdentifier "hasTarget"; + ] ; + + # Motivation. Constrained to the common note / bookmark / highlight / comment values. + sh:property [ + sh:path oa:motivatedBy ; + sh:nodeKind sh:IRI ; + sh:in ( + oa:bookmarking + oa:commenting + oa:highlighting + ) ; + sh:name "Motivation" ; + sh:description "Why the annotation was created. Limited here to the common note-taking subset: bookmarking, commenting, and highlighting (Web Annotation Vocabulary, oa:Motivation)." ; + sh:codeIdentifier "motivatedBy"; + ] ; + + # Creator (typically a WebID for Solid use). + sh:property [ + sh:path dct:creator ; + sh:nodeKind sh:IRI ; + sh:maxCount 1 ; + sh:name "Creator" ; + sh:description "The agent that created the annotation, typically referenced as a WebID." ; + sh:codeIdentifier "creator"; + ] ; + + # Created date. + sh:property [ + sh:path dct:created ; + sh:datatype xsd:dateTime ; + sh:maxCount 1 ; + sh:name "Created" ; + sh:description "The date and time at which the annotation was created." ; + sh:codeIdentifier "created"; + ] ; + + # Last modified date. + sh:property [ + sh:path dct:modified ; + sh:datatype xsd:dateTime ; + sh:maxCount 1 ; + sh:name "Modified" ; + sh:description "The date and time at which the annotation was last modified." ; + sh:codeIdentifier "modified"; + ] . From 7fb6a6e4e7ca80720191cd4b4d90050ea0deafc3 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:25:22 +0100 Subject: [PATCH 2/3] Loosen oa:motivatedBy and dct:creator constraints per roborev - Drop sh:in restriction on oa:motivatedBy so the shape does not reject conforming oa:Annotation instances that use motivations outside the common note-taking subset (e.g., oa:tagging, oa:replying). - Permit dct:creator as either a WebID IRI or an embedded agent description, matching the W3C Web Annotation Data Model. Co-Authored-By: Claude Opus 4.7 (1M context) --- shapes/annotation.ttl | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/shapes/annotation.ttl b/shapes/annotation.ttl index cbe1428..10713fb 100644 --- a/shapes/annotation.ttl +++ b/shapes/annotation.ttl @@ -61,27 +61,28 @@ annotation_shape:AnnotationShape sh:codeIdentifier "hasTarget"; ] ; - # Motivation. Constrained to the common note / bookmark / highlight / comment values. + # Motivation. Left open to the full Web Annotation motivation set so that + # the shape does not reject conforming annotations using motivations + # outside the common note-taking subset (e.g., oa:tagging, oa:replying). + # Apps focused on notes/bookmarks/highlights typically use oa:bookmarking, + # oa:commenting, or oa:highlighting. sh:property [ sh:path oa:motivatedBy ; sh:nodeKind sh:IRI ; - sh:in ( - oa:bookmarking - oa:commenting - oa:highlighting - ) ; sh:name "Motivation" ; - sh:description "Why the annotation was created. Limited here to the common note-taking subset: bookmarking, commenting, and highlighting (Web Annotation Vocabulary, oa:Motivation)." ; + sh:description "Why the annotation was created (Web Annotation Vocabulary, oa:Motivation). Common note-taking values are oa:bookmarking, oa:commenting, and oa:highlighting." ; sh:codeIdentifier "motivatedBy"; ] ; - # Creator (typically a WebID for Solid use). + # Creator. Permits a WebID IRI or an embedded agent description, matching + # the Web Annotation Data Model which allows the creator to be referenced + # or described inline. sh:property [ sh:path dct:creator ; - sh:nodeKind sh:IRI ; + sh:nodeKind sh:BlankNodeOrIRI ; sh:maxCount 1 ; sh:name "Creator" ; - sh:description "The agent that created the annotation, typically referenced as a WebID." ; + sh:description "The agent that created the annotation. Typically a WebID IRI in Solid, but the Web Annotation Data Model also permits an embedded agent description." ; sh:codeIdentifier "creator"; ] ; From 337677552deda72b7ad9c454686a07789d0bc391 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:27:28 +0100 Subject: [PATCH 3/3] Document body mutual-exclusion and drop creator maxCount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Note in oa:bodyValue and oa:hasBody descriptions that the Web Annotation Data Model (§3.2.4) says an annotation SHOULD NOT have both. The shape stays permissive (interoperability first) but flags the constraint to consumers. - Drop sh:maxCount 1 on dct:creator since the Web Annotation Data Model permits multiple creators (co-creation). Co-Authored-By: Claude Opus 4.7 (1M context) --- shapes/annotation.ttl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/shapes/annotation.ttl b/shapes/annotation.ttl index 10713fb..d3958cb 100644 --- a/shapes/annotation.ttl +++ b/shapes/annotation.ttl @@ -33,12 +33,16 @@ annotation_shape:AnnotationShape ] ; # Inline textual body (for simple comment / note cases). + # Per the Web Annotation Data Model (§3.2.4), an annotation SHOULD NOT + # have both oa:bodyValue and oa:hasBody. This shape does not enforce + # the exclusion (favouring interoperability) but consumers SHOULD + # populate only one of the two. sh:property [ sh:path oa:bodyValue ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Body Value" ; - sh:description "Textual content of the annotation body, used for plain-text comments or notes (Web Annotation Vocabulary, oa:bodyValue)." ; + sh:description "Textual content of the annotation body, used for plain-text comments or notes (Web Annotation Vocabulary, oa:bodyValue). SHOULD NOT be combined with oa:hasBody on the same annotation." ; sh:codeIdentifier "bodyValue"; ] ; @@ -47,7 +51,7 @@ annotation_shape:AnnotationShape sh:path oa:hasBody ; sh:nodeKind sh:BlankNodeOrIRI ; sh:name "Body" ; - sh:description "A resource that provides the content of the annotation (Web Annotation Vocabulary, oa:hasBody). May be repeated when an annotation has multiple bodies." ; + sh:description "A resource that provides the content of the annotation (Web Annotation Vocabulary, oa:hasBody). May be repeated when an annotation has multiple bodies. SHOULD NOT be combined with oa:bodyValue on the same annotation." ; sh:codeIdentifier "hasBody"; ] ; @@ -76,13 +80,13 @@ annotation_shape:AnnotationShape # Creator. Permits a WebID IRI or an embedded agent description, matching # the Web Annotation Data Model which allows the creator to be referenced - # or described inline. + # or described inline. Cardinality is left open as the data model permits + # multiple creators (co-creation). sh:property [ sh:path dct:creator ; sh:nodeKind sh:BlankNodeOrIRI ; - sh:maxCount 1 ; sh:name "Creator" ; - sh:description "The agent that created the annotation. Typically a WebID IRI in Solid, but the Web Annotation Data Model also permits an embedded agent description." ; + sh:description "The agent that created the annotation. Typically a WebID IRI in Solid, but the Web Annotation Data Model also permits an embedded agent description and multiple creators." ; sh:codeIdentifier "creator"; ] ;