@prefix rc: <https://ontology.inferal.com/modules/classification-review-context/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix mod: <https://w3id.org/mod#> .
@prefix vann: <http://purl.org/vocab/vann/> .
@prefix pubacc: <https://ontology.inferal.com/modules/publication-access/> .
@prefix dcls: <https://ontology.inferal.com/modules/data-classification/> .
rc:CurrentContextShape a sh:NodeShape; sh:targetClass rc:CurrentContext; sh:nodeKind sh:IRI .
rc:HistoricalReviewShape a sh:NodeShape; sh:targetClass rc:HistoricalReview; sh:nodeKind sh:IRI .
rc:ReviewOfferShape a sh:NodeShape; sh:targetClass rc:ReviewOffer; sh:nodeKind sh:IRI .
rc:ReviewAssessmentShape a sh:NodeShape; sh:targetClass rc:ReviewAssessment; sh:nodeKind sh:IRI .
rc:ApprovedOfferBindingShape a sh:NodeShape; sh:targetClass rc:ApprovedOfferBinding; sh:nodeKind sh:IRI .
rc:CurrentContextShape sh:property [ sh:path rc:provider; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string ];
 sh:property [ sh:path rc:predicate; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:semanticDigest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ];
 sh:property [ sh:path rc:programDigest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ];
 sh:property [ sh:path rc:candidateCategory; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:candidateSensitivity; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:contextSupported; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:boolean ];
 sh:property [ sh:path rc:scope; sh:minCount 1; sh:maxCount 1; sh:hasValue rc:RetainedSchemaClassificationOnly ] .
rc:ReviewOfferShape sh:property [ sh:path rc:offerDigest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ];
 sh:property [ sh:path rc:provider; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string ];
 sh:property [ sh:path rc:predicate; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:reviewedSemanticDigest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ];
 sh:property [ sh:path rc:reviewedProgramDigest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ];
 sh:property [ sh:path rc:decision; sh:minCount 1; sh:maxCount 1; sh:in (rc:Confirm rc:Reject) ];
 sh:property [ sh:path rc:category; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:sensitivity; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:reviewerName; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string ];
 sh:property [ sh:path rc:reviewedAt; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:dateTime ];
 sh:property [ sh:path rc:approvalMatched; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:boolean ];
 sh:property [ sh:path rc:supersedes; sh:maxCount 1; sh:nodeKind sh:IRI ] .
rc:ReviewAssessmentShape sh:property [ sh:path rc:context; sh:minCount 1; sh:maxCount 1; sh:class rc:CurrentContext ];
 sh:property [ sh:path rc:standing; sh:minCount 1; sh:maxCount 1; sh:in (rc:NoApplicableReview rc:HistoricalUnanchoredReview rc:CurrentAnchoredConfirmation rc:CurrentAnchoredRejection rc:ContextDriftRequiresReview rc:ConflictingReviewOffers rc:CanonicalContextUnqualified) ];
 sh:property [ sh:path rc:classificationStatus; sh:minCount 1; sh:maxCount 1; sh:in (dcls:Candidate dcls:Confirmed dcls:Rejected) ];
 sh:property [ sh:path rc:requiresReview; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:boolean ];
 sh:property [ sh:path rc:category; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:sensitivity; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:authorizationStanding; sh:minCount 1; sh:maxCount 1; sh:hasValue rc:Unassessed ];
 sh:property [ sh:path rc:ownershipStanding; sh:minCount 1; sh:maxCount 1; sh:hasValue rc:Unassessed ];
 sh:property [ sh:path rc:consentStanding; sh:minCount 1; sh:maxCount 1; sh:hasValue rc:Unassessed ];
 sh:property [ sh:path rc:payloadStanding; sh:minCount 1; sh:maxCount 1; sh:hasValue rc:Unassessed ] .

# A current schema review is a single exact standing/status/review tuple.
rc:ReviewAssessmentShape sh:or (
 [ sh:property [ sh:path rc:standing; sh:hasValue rc:CurrentAnchoredConfirmation ];
   sh:property [ sh:path rc:classificationStatus; sh:hasValue dcls:Confirmed ];
   sh:property [ sh:path rc:requiresReview; sh:hasValue false ] ]
 [ sh:property [ sh:path rc:standing; sh:hasValue rc:CurrentAnchoredRejection ];
   sh:property [ sh:path rc:classificationStatus; sh:hasValue dcls:Rejected ];
   sh:property [ sh:path rc:requiresReview; sh:hasValue false ] ]
 [ sh:property [ sh:path rc:standing; sh:in (rc:NoApplicableReview rc:HistoricalUnanchoredReview rc:ContextDriftRequiresReview rc:ConflictingReviewOffers rc:CanonicalContextUnqualified) ];
   sh:property [ sh:path rc:classificationStatus; sh:hasValue dcls:Candidate ];
   sh:property [ sh:path rc:requiresReview; sh:hasValue true ] ]
) .
rc:ReviewOfferShape sh:property [ sh:path rc:scope; sh:minCount 1; sh:maxCount 1; sh:hasValue rc:RetainedSchemaClassificationOnly ];
 sh:sparql [ a sh:SPARQLConstraint;
  sh:message "Supersession targets an existing offer in the same provider, field and purpose, and cannot form a cycle.";
  sh:select """
PREFIX rc:<https://ontology.inferal.com/modules/classification-review-context/>
SELECT $this WHERE {
 $this rc:supersedes ?target .
 FILTER($this = ?target || EXISTS { ?target rc:supersedes+ $this } || NOT EXISTS {
   $this rc:provider ?provider; rc:predicate ?predicate; rc:scope ?scope .
   ?target a rc:ReviewOffer; rc:provider ?provider; rc:predicate ?predicate; rc:scope ?scope .
 })
}
""" ] .
rc:HistoricalReviewShape sh:property [ sh:path rc:provider; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string ];
 sh:property [ sh:path rc:predicate; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
 sh:property [ sh:path rc:decision; sh:minCount 1; sh:maxCount 1; sh:in (rc:Confirm rc:Reject) ];
 sh:property [ sh:path rc:reviewerName; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string ];
 sh:property [ sh:path rc:reviewedAt; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:dateTime ] .
rc:ApprovedOfferBindingShape sh:property [ sh:path rc:offer; sh:minCount 1; sh:maxCount 1; sh:class rc:ReviewOffer ];
 sh:property [ sh:path rc:offerDigest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ];
 sh:property [ sh:path rc:programDigest; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ] .

rc:CurrentContextShape sh:property [ sh:path rc:canonicalContext; sh:maxCount 1; sh:datatype xsd:string ];
 sh:property [ sh:path rc:sourceFrameDigest; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ] .
rc:HistoricalReviewShape sh:property [ sh:path rc:retainedRecord; sh:maxCount 1; sh:datatype xsd:string ] .
rc:ReviewOfferShape sh:property [ sh:path rc:retainedRecord; sh:maxCount 1; sh:datatype xsd:string ];
 sh:property [ sh:path rc:supersedesDigest; sh:maxCount 1; sh:datatype xsd:string; sh:pattern "^[0-9a-f]{64}$" ];
 sh:sparql [ a sh:SPARQLConstraint;
 sh:message "A supersession edge includes the exact immutable target digest, and a digest cannot exist without its edge.";
 sh:select """
PREFIX rc:<https://ontology.inferal.com/modules/classification-review-context/>
SELECT $this WHERE {
 { $this rc:supersedes ?target . FILTER NOT EXISTS { $this rc:supersedesDigest ?digest . ?target rc:offerDigest ?digest } }
 UNION { $this rc:supersedesDigest ?digest . FILTER NOT EXISTS { $this rc:supersedes ?target } }
}
""" ] .
