@prefix : <https://ontology.inferal.com/modules/data-classification/> .
@prefix conf: <https://ontology.inferal.com/modules/confidence/> .
@prefix dpv: <https://w3id.org/dpv#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

:DataElementShape
    a sh:NodeShape ;
    rdfs:label "data element shape" ;
    rdfs:comment "Validates source, path, and predicate metadata for data elements used in data classifications." ;
    sh:targetClass :DataElement ;
    sh:property [
        sh:path :sourceAsset ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
    ] ;
    sh:property [
        sh:path :path ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :dataPredicate ;
        sh:nodeKind sh:IRI ;
        sh:maxCount 1 ;
    ] .

:ClassificationContextShape
    a sh:NodeShape ;
    rdfs:label "classification context shape" ;
    rdfs:comment "Validates that classification context resources are named IRIs." ;
    sh:targetClass :ClassificationContext ;
    sh:nodeKind sh:IRI .

:DataPolicyShape
    a sh:NodeShape ;
    rdfs:label "data policy shape" ;
    rdfs:comment "Validates that data policy resources are named IRIs." ;
    sh:targetClass :DataPolicy ;
    sh:nodeKind sh:IRI .

:DetectionMethodShape
    a sh:NodeShape ;
    rdfs:label "detection method shape" ;
    rdfs:comment "Validates that detection method resources are named IRIs." ;
    sh:targetClass :DetectionMethod ;
    sh:nodeKind sh:IRI .

:ClassificationRuleFieldPartShape
    a sh:NodeShape ;
    rdfs:label "classification rule field part shape" ;
    rdfs:comment "Validates that classification rule field part resources are named IRIs." ;
    sh:targetClass :ClassificationRuleFieldPart ;
    sh:nodeKind sh:IRI .

:ClassificationStatusShape
    a sh:NodeShape ;
    rdfs:label "classification status shape" ;
    rdfs:comment "Validates that classification status resources are named IRIs." ;
    sh:targetClass :ClassificationStatus ;
    sh:nodeKind sh:IRI .

:SensitivityLevelShape
    a sh:NodeShape ;
    rdfs:label "sensitivity level shape" ;
    rdfs:comment "Validates that sensitivity level resources are named IRIs." ;
    sh:targetClass :SensitivityLevel ;
    sh:nodeKind sh:IRI .

:MitigationMeasureShape
    a sh:NodeShape ;
    rdfs:label "mitigation measure shape" ;
    rdfs:comment "Validates that mitigation measure category resources are named IRIs." ;
    sh:targetClass :MitigationMeasure ;
    sh:nodeKind sh:IRI .

:MitigationMeasureCategoryCoverageShape
    a sh:NodeShape ;
    rdfs:label "mitigation measure category coverage shape" ;
    rdfs:comment "Documents SHACL coverage for built-in mitigation measure category classes. These classes are used as class-valued dcls:requiresMeasure values; DataClassificationShape checks the property that references them." ;
    sh:targetClass
        :AttributeGroupingObfuscation,
        :ElementElision,
        :IdentityLinkagePseudonymisation,
        :ValueElision .

:DataClassificationShape
    a sh:NodeShape ;
    rdfs:label "data classification shape" ;
    rdfs:comment "Shape for classification targets, category assertions, review status, sensitivity, confidence, and policy control links on data classifications." ;
    sh:targetClass :DataClassification ;
    sh:property [
        sh:path :classifiesElement ;
        sh:class :DataElement ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A DataClassification must classify exactly one DataElement." ;
    ] ;
    sh:property [
        sh:path :classifiedByRule ;
        sh:class :ClassificationRule ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :dataCategory
                :personalDataCategory
                :candidateDataCategory
                :candidatePersonalDataCategory
            ) ;
        ] ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:message "A DataClassification must name at least one asserted or candidate data category." ;
    ] ;
    sh:property [
        sh:path :personalDataCategory ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :candidateDataCategory ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :candidatePersonalDataCategory ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :hasSensitivityLevel ;
        sh:class :SensitivityLevel ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :wasClassifiedBy
                :detectedBy
                :reviewedBy
            ) ;
        ] ;
        sh:class prov:Activity ;
    ] ;
    sh:property [
        sh:path :confidence ;
        sh:datatype xsd:decimal ;
        sh:minInclusive 0 ;
        sh:maxInclusive 1 ;
        sh:maxCount 1 ;
        sh:message "Confidence must be a decimal value between 0 and 1 inclusive." ;
    ] ;
    sh:property [
        sh:path :confidenceAssessment ;
        sh:class conf:ConfidenceAssessment ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :hasClassificationStatus ;
        sh:class :ClassificationStatus ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :hasClassificationContext ;
        sh:class :ClassificationContext ;
    ] ;
    sh:property [
        sh:path :requiresReview ;
        sh:datatype xsd:boolean ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :requiresMeasure ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:sparql [
        sh:message "requiresMeasure values must be technical or organisational measure category classes." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-classification/>
            PREFIX dpv: <https://w3id.org/dpv#>
            PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
            SELECT $this ?measure WHERE {
                $this :requiresMeasure ?measure .
                FILTER NOT EXISTS {
                    ?measure rdfs:subClassOf* dpv:TechnicalOrganisationalMeasure .
                }
                FILTER NOT EXISTS {
                    ?measure a rdfs:Class ;
                        a ?measureCategory .
                    ?measureCategory rdfs:subClassOf* dpv:TechnicalOrganisationalMeasure .
                }
            }
        """ ;
    ] ;
    sh:property [
        sh:path :requiresLegalBasis ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :allowedProcessing ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :prohibitedProcessing ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :triggersPolicy ;
        sh:class :DataPolicy ;
    ] ;
    sh:sparql [
        sh:message "A Candidate classification must explicitly require review." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-classification/>
            SELECT $this WHERE {
                $this :hasClassificationStatus :Candidate .
                FILTER NOT EXISTS { $this :requiresReview true . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Candidate and Rejected classifications must use candidateDataCategory or candidatePersonalDataCategory instead of asserted dataCategory or personalDataCategory." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-classification/>
            SELECT $this WHERE {
                $this :hasClassificationStatus ?status .
                FILTER(?status IN (:Candidate, :Rejected))
                {
                    $this :dataCategory ?category .
                }
                UNION
                {
                    $this :personalDataCategory ?category .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Confirmed classifications must use asserted dataCategory or personalDataCategory." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-classification/>
            SELECT $this WHERE {
                $this :hasClassificationStatus :Confirmed .
                FILTER NOT EXISTS {
                    {
                        $this :dataCategory ?category .
                    }
                    UNION
                    {
                        $this :personalDataCategory ?category .
                    }
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A RestrictedSensitivity classification must require review and at least one required measure." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-classification/>
            SELECT $this WHERE {
                $this :hasSensitivityLevel :RestrictedSensitivity .
                {
                    FILTER NOT EXISTS { $this :requiresReview true . }
                }
                UNION
                {
                    FILTER NOT EXISTS { $this :requiresMeasure ?measure . }
                }
            }
        """ ;
    ] .

:DetectionActivityShape
    a sh:NodeShape ;
    rdfs:label "detection activity shape" ;
    rdfs:comment "Shape for detection activities: their detection method link and optional PROV start and end times. Timestamps are optional but, when present, must be single xsd:dateTime values so audit chronology stays comparable." ;
    sh:targetClass :DetectionActivity ;
    sh:property [
        sh:path :hasDetectionMethod ;
        sh:class :DetectionMethod ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A DetectionActivity must identify exactly one hasDetectionMethod." ;
    ] ;
    sh:property [
        sh:path prov:startedAtTime ;
        sh:datatype xsd:dateTime ;
        sh:maxCount 1 ;
        sh:message "A DetectionActivity start time, when present, must be a single xsd:dateTime." ;
    ] ;
    sh:property [
        sh:path prov:endedAtTime ;
        sh:datatype xsd:dateTime ;
        sh:maxCount 1 ;
        sh:message "A DetectionActivity end time, when present, must be a single xsd:dateTime." ;
    ] .

:ClassificationRuleSetShape
    a sh:NodeShape ;
    rdfs:label "classification rule set shape" ;
    rdfs:comment "Validates that classification rule sets contain at least one classification rule." ;
    sh:targetClass :ClassificationRuleSet ;
    sh:property [
        sh:path :hasClassificationRule ;
        sh:class :ClassificationRule ;
        sh:minCount 1 ;
        sh:message "A ClassificationRuleSet must identify at least one ClassificationRule." ;
    ] .

:ClassificationRuleShape
    a sh:NodeShape ;
    rdfs:label "classification rule shape" ;
    rdfs:comment "Validates rule field parts, match or exclusion patterns, ordering, assigned categories, status, sensitivity, confidence, and review requirement values." ;
    sh:targetClass :ClassificationRule ;
    sh:property [
        sh:path :matchesFieldPart ;
        sh:class :ClassificationRuleFieldPart ;
        sh:minCount 1 ;
        sh:message "A ClassificationRule must identify at least one inspected field part." ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :matchesPattern
                :excludesPattern
            ) ;
        ] ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:message "A ClassificationRule must provide at least one match or exclusion pattern." ;
    ] ;
    sh:property [
        sh:path :ruleOrder ;
        sh:datatype xsd:integer ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :ruleRationale ;
        sh:datatype xsd:string ;
    ] ;
    sh:property [
        sh:path :assignsDataCategory ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :assignsPersonalDataCategory ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :assignsClassificationStatus ;
        sh:class :ClassificationStatus ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :assignsSensitivityLevel ;
        sh:class :SensitivityLevel ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :assignsConfidence ;
        sh:datatype xsd:decimal ;
        sh:minInclusive 0 ;
        sh:maxInclusive 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :assignsReviewRequirement ;
        sh:datatype xsd:boolean ;
        sh:maxCount 1 ;
    ] .
