@prefix : <https://ontology.inferal.com/modules/data-usage-processing-lineage/> .
@prefix dcls: <https://ontology.inferal.com/modules/data-classification/> .
@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#> .

:ProcessingSpecificationShape
    a sh:NodeShape ;
    rdfs:label "processing specification shape" ;
    rdfs:comment "Validates that specifications are named, declare at least one processing category, and expose at least one input or output port." ;
    sh:targetClass :ProcessingSpecification ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :declaresProcessing ;
        sh:class dpv:Processing ;
        sh:minCount 1 ;
        sh:message "A ProcessingSpecification must declare at least one DPV processing category." ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :hasInputPort
                :hasOutputPort
            ) ;
        ] ;
        sh:class :DataPort ;
        sh:minCount 1 ;
        sh:message "A ProcessingSpecification must declare at least one input or output port." ;
    ] ;
    sh:property [
        sh:path :hasExpectedClassificationImpact ;
        sh:class :ExpectedClassificationImpact ;
    ] ;
    sh:property [
        sh:path :declaresMitigationRequirement ;
        sh:class :MitigationRequirement ;
    ] ;
    sh:property [
        sh:path :specificationContext ;
        sh:class dcls:ClassificationContext ;
        sh:maxCount 1 ;
        sh:message "A ProcessingSpecification can declare at most one bundled context." ;
    ] ;
    sh:property [
        sh:path :requiresContextFacet ;
        sh:class :ContextFacet ;
    ] .

:ProcessingPlanShape
    a sh:NodeShape ;
    rdfs:label "processing plan shape" ;
    rdfs:comment "Validates that a concrete processing plan identifies at least one abstract specification and makes port-level refinement to that specification explicit." ;
    sh:targetClass :ProcessingPlan ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :implementsSpecification ;
        sh:class :ProcessingSpecification ;
        sh:minCount 1 ;
        sh:message "A ProcessingPlan must implement at least one ProcessingSpecification." ;
    ] ;
    sh:sparql [
        sh:message "Every concrete input port on a ProcessingPlan must refine an input port of an implemented ProcessingSpecification." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this ?port WHERE {
                $this :hasInputPort ?port .
                FILTER NOT EXISTS {
                    $this :implementsSpecification ?specification .
                    ?specification :hasInputPort ?abstractPort .
                    ?port :refinesPort ?abstractPort .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every concrete output port on a ProcessingPlan must refine an output port of an implemented ProcessingSpecification." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this ?port WHERE {
                $this :hasOutputPort ?port .
                FILTER NOT EXISTS {
                    $this :implementsSpecification ?specification .
                    ?specification :hasOutputPort ?abstractPort .
                    ?port :refinesPort ?abstractPort .
                }
            }
        """ ;
    ] .

:ProcessingPolicyShape
    a sh:NodeShape ;
    rdfs:label "processing policy shape" ;
    rdfs:comment "Validates category applicability and at least one explicit permission, prohibition, or requirement on processing policies." ;
    sh:targetClass :ProcessingPolicy ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :appliesToDataCategory ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:message "A ProcessingPolicy must identify at least one applicable data category." ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :permitsProcessing
                :prohibitsProcessing
                :requiresProcessing
            ) ;
        ] ;
        sh:class dpv:Processing ;
        sh:minCount 1 ;
        sh:message "A ProcessingPolicy must permit, prohibit, or require at least one processing category." ;
    ] ;
    sh:property [
        sh:path :policyContext ;
        sh:class dcls:ClassificationContext ;
        sh:maxCount 1 ;
        sh:message "A ProcessingPolicy can declare at most one bundled context." ;
    ] ;
    sh:property [
        sh:path :requiresContextFacet ;
        sh:class :ContextFacet ;
    ] .

:PermissionShape
    a sh:NodeShape ;
    rdfs:label "processing permission shape" ;
    rdfs:comment "Validates the concrete scope carried by a reified DPV permission on a processing policy." ;
    sh:targetObjectsOf dpv:hasPermission ;
    sh:class dpv:Permission ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path dpv:hasData ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:message "A processing permission must identify at least one data target." ;
    ] ;
    sh:property [
        sh:path dpv:hasProcessing ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:message "A processing permission must identify at least one processing operation." ;
    ] ;
    sh:property [
        sh:path dpv:hasActiveEntity ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:message "A processing permission must identify at least one active entity or role." ;
    ] ;
    sh:property [
        sh:path dpv:hasPurpose ;
        sh:nodeKind sh:IRI ;
        sh:message "Every processing permission purpose must be identified by an IRI." ;
    ] ;
    sh:property [
        sh:path dpv:hasContext ;
        sh:nodeKind sh:IRI ;
        sh:message "Every processing permission context must be identified by an IRI." ;
    ] ;
    sh:or (
        [
            sh:property [
                sh:path dpv:hasPurpose ;
                sh:nodeKind sh:IRI ;
                sh:minCount 1 ;
                sh:message "A processing permission must identify a purpose or context." ;
            ] ;
        ]
        [
            sh:property [
                sh:path dpv:hasContext ;
                sh:nodeKind sh:IRI ;
                sh:minCount 1 ;
                sh:message "A processing permission must identify a purpose or context." ;
            ] ;
        ]
    ) .

:ProcessingActivityShape
    a sh:NodeShape ;
    rdfs:label "processing activity shape" ;
    rdfs:comment "Validates actual processing categories, provenance inputs or outputs, realized specifications, coverage, and applied measure categories." ;
    sh:targetClass :ProcessingActivity ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :performedProcessing ;
        sh:class dpv:Processing ;
        sh:minCount 1 ;
        sh:message "A ProcessingActivity must identify at least one performed DPV processing category." ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                prov:used
                prov:generated
            ) ;
        ] ;
        sh:class prov:Entity ;
        sh:minCount 1 ;
        sh:message "A ProcessingActivity must use or generate at least one PROV entity." ;
    ] ;
    sh:property [
        sh:path :realizesSpecification ;
        sh:class :ProcessingSpecification ;
    ] ;
    sh:property [
        sh:path :processingContext ;
        sh:class dcls:ClassificationContext ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :requiresContextFacet ;
        sh:class :ContextFacet ;
    ] ;
    sh:property [
        sh:path :hasObservedClassificationImpact ;
        sh:class :ObservedClassificationImpact ;
    ] ;
    sh:property [
        sh:path :hasLineageCoverage ;
        sh:class :LineageCoverage ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ProcessingActivity must state exactly one lineage coverage value." ;
    ] ;
    sh:property [
        sh:path :lineageCoverageScope ;
        sh:class :LineageScope ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ProcessingActivity must identify exactly one explicit lineage coverage scope." ;
    ] ;
    sh:property [
        sh:path :hasLineageCoverageAssessment ;
        sh:class :LineageCoverageAssessment ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ProcessingActivity must identify exactly one provenance-bearing lineage coverage assessment." ;
    ] ;
    sh:sparql [
        sh:message "The activity's lineage coverage summary and scope must be supported by its linked assessment." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this ?assessment WHERE {
                $this :hasLineageCoverage ?coverage ;
                    :lineageCoverageScope ?scope ;
                    :hasLineageCoverageAssessment ?assessment .
                {
                    FILTER NOT EXISTS { ?assessment :assessesResource $this . }
                }
                UNION
                {
                    FILTER NOT EXISTS { ?assessment :assessedLineageCoverage ?coverage . }
                }
                UNION
                {
                    FILTER NOT EXISTS { ?assessment :assessedLineageScope ?scope . }
                }
            }
        """ ;
    ] ;
    sh:property [
        sh:path :hasDataOriginKind ;
        sh:class :DataOriginKind ;
        sh:maxCount 1 ;
        sh:message "A ProcessingActivity can state at most one data origin kind." ;
    ] ;
    sh:property [
        sh:path :appliedMeasure ;
        sh:node :MeasureCategoryShape ;
    ] ;
    sh:property [
        sh:path :measureImplementation ;
        sh:class prov:Entity ;
    ] ;
    sh:sparql [
        sh:message "A source-free activity must not bind a DataInputPort, and every used entity must be explicitly qualified through an AuxiliaryInputPort." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?input WHERE {
                $this :hasDataOriginKind :SourceFreeOrigin .
                {
                    $this prov:qualifiedUsage ?usage .
                    ?usage prov:entity ?input ;
                        prov:hadRole ?role .
                    ?role a :DataInputPort .
                }
                UNION
                {
                    $this prov:used ?input .
                    FILTER NOT EXISTS {
                        $this prov:qualifiedUsage ?usage .
                        ?usage prov:entity ?input ;
                            prov:hadRole ?role .
                        ?role a :AuxiliaryInputPort .
                    }
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A source-dependent activity must bind at least one used entity through a DataInputPort." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this WHERE {
                $this :hasDataOriginKind :SourceDependentOrigin .
                FILTER NOT EXISTS {
                    $this prov:qualifiedUsage ?usage .
                    ?usage prov:entity ?input ;
                        prov:hadRole ?role .
                    ?role a :DataInputPort .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every qualified input role on an activity that realizes a specification must be an input port owned by a realized specification." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?role WHERE {
                $this :realizesSpecification ?realized ;
                    prov:qualifiedUsage/prov:hadRole ?role .
                ?role a :InputPort .
                FILTER NOT EXISTS { ?realized :hasInputPort ?role . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every qualified output role on an activity that realizes a specification must be an output port owned by a realized specification." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?role WHERE {
                $this :realizesSpecification ?realized .
                ?output prov:qualifiedGeneration ?generation .
                ?generation prov:activity $this ;
                    prov:hadRole ?role .
                ?role a :OutputPort .
                FILTER NOT EXISTS { ?realized :hasOutputPort ?role . }
            }
        """ ;
    ] .

:TransformationActivityShape
    a sh:NodeShape ;
    rdfs:label "transformation activity shape" ;
    rdfs:comment "Requires transformations to use at least one entity and generate at least one distinct entity version." ;
    sh:targetClass :TransformationActivity ;
    sh:property [
        sh:path prov:used ;
        sh:class prov:Entity ;
        sh:minCount 1 ;
        sh:message "A TransformationActivity must use at least one input entity." ;
    ] ;
    sh:property [
        sh:path prov:generated ;
        sh:class prov:Entity ;
        sh:minCount 1 ;
        sh:message "A TransformationActivity must generate at least one output entity." ;
    ] ;
    sh:sparql [
        sh:message "A TransformationActivity must not use and generate the same fixed-aspect entity; generate a new entity version instead." ;
        sh:select """
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?entity WHERE {
                $this prov:used ?entity ;
                    prov:generated ?entity .
            }
        """ ;
    ] .

:ClassificationPropagationActivityShape
    a sh:NodeShape ;
    rdfs:label "classification propagation activity shape" ;
    rdfs:comment "Validates that classification propagation uses lineage evidence, identifies the lineage inheritance method, and generates a downstream classification record." ;
    sh:targetClass :ClassificationPropagationActivity ;
    sh:property [
        sh:path dcls:hasDetectionMethod ;
        sh:hasValue dcls:LineageInheritedDetection ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ClassificationPropagationActivity must use dcls:LineageInheritedDetection." ;
    ] ;
    sh:property [
        sh:path prov:used ;
        sh:nodeKind sh:IRI ;
        sh:minCount 2 ;
        sh:message "Classification propagation must use at least two evidence resources, normally an upstream classification and lineage or impact evidence." ;
    ] ;
    sh:property [
        sh:path prov:generated ;
        sh:qualifiedValueShape [
            sh:class dcls:DataClassification ;
        ] ;
        sh:qualifiedMinCount 1 ;
        sh:message "Classification propagation must generate a downstream DataClassification." ;
    ] .

:DataPortShape
    a sh:NodeShape ;
    rdfs:label "data port shape" ;
    rdfs:comment "Validates named, plan-scoped ports and their optional classification constraints." ;
    sh:targetClass :DataPort ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path [ sh:inversePath :hasPort ] ;
        sh:class :ProcessingSpecification ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A DataPort must belong to exactly one ProcessingSpecification." ;
    ] ;
    sh:property [
        sh:path :hasClassificationConstraint ;
        sh:class :ClassificationConstraint ;
    ] .

:InputPortShape
    a sh:NodeShape ;
    rdfs:label "input port shape" ;
    rdfs:comment "Provides explicit SHACL coverage for input ports; ownership and constraints are validated by DataPortShape." ;
    sh:targetClass :InputPort ;
    sh:nodeKind sh:IRI .

:DataInputPortShape
    a sh:NodeShape ;
    rdfs:label "data input port shape" ;
    rdfs:comment "Provides explicit SHACL coverage for data-bearing input ports; ownership is validated by DataPortShape." ;
    sh:targetClass :DataInputPort ;
    sh:nodeKind sh:IRI .

:AuxiliaryInputPortShape
    a sh:NodeShape ;
    rdfs:label "auxiliary input port shape" ;
    rdfs:comment "Provides explicit SHACL coverage for non-data auxiliary input ports; ownership is validated by DataPortShape." ;
    sh:targetClass :AuxiliaryInputPort ;
    sh:nodeKind sh:IRI .

:OutputPortShape
    a sh:NodeShape ;
    rdfs:label "output port shape" ;
    rdfs:comment "Provides explicit SHACL coverage for output ports; ownership and constraints are validated by DataPortShape." ;
    sh:targetClass :OutputPort ;
    sh:nodeKind sh:IRI .

:ClassificationConstraintShape
    a sh:NodeShape ;
    rdfs:label "classification constraint shape" ;
    rdfs:comment "Requires each classification constraint to constrain category, status, sensitivity, or structural conformance." ;
    sh:targetClass :ClassificationConstraint ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :requiresDataCategory
                :requiresClassificationStatus
                :requiresSensitivityLevel
                :requiresStructuralShape
            ) ;
        ] ;
        sh:minCount 1 ;
        sh:message "A ClassificationConstraint must constrain at least one classification or structural dimension." ;
    ] ;
    sh:property [
        sh:path :requiresDataCategory ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:property [
        sh:path :requiresClassificationStatus ;
        sh:class dcls:ClassificationStatus ;
    ] ;
    sh:property [
        sh:path :requiresSensitivityLevel ;
        sh:class dcls:SensitivityLevel ;
    ] ;
    sh:property [
        sh:path :requiresStructuralShape ;
        sh:nodeKind sh:IRI ;
    ] .

:DataSnapshotShape
    a sh:NodeShape ;
    rdfs:label "data snapshot shape" ;
    rdfs:comment "Validates that immutable data snapshots are named resources." ;
    sh:targetClass :DataSnapshot ;
    sh:nodeKind sh:IRI .

:DataElementVersionShape
    a sh:NodeShape ;
    rdfs:label "data element version shape" ;
    rdfs:comment "Requires each element version to identify exactly one source asset or snapshot." ;
    sh:targetClass :DataElementVersion ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path dcls:sourceAsset ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A DataElementVersion must identify exactly one source asset or snapshot." ;
    ] .

:LineageScopeShape
    a sh:NodeShape ;
    rdfs:label "lineage scope shape" ;
    rdfs:comment "Provides named-IRI coverage for explicit lineage coverage boundaries." ;
    sh:targetClass :LineageScope ;
    sh:nodeKind sh:IRI .

:ElementDerivationShape
    a sh:NodeShape ;
    rdfs:label "element derivation shape" ;
    rdfs:comment "Validates the input element and optional responsible activity of a qualified direct derivation." ;
    sh:targetClass :ElementDerivation ;
    sh:property [
        sh:path prov:entity ;
        sh:class :DataElementVersion ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "An ElementDerivation must identify exactly one directly contributing input element." ;
    ] ;
    sh:property [
        sh:path prov:hadActivity ;
        sh:class :ProcessingActivity ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "When an output also states unqualified direct lineage, it must include the input named by each qualified derivation." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?output ?input WHERE {
                ?output :qualifiedElementDerivation $this .
                $this prov:entity ?input .
                FILTER EXISTS { ?output :wasDirectlyDerivedFromElement ?any . }
                FILTER NOT EXISTS { ?output :wasDirectlyDerivedFromElement ?input . }
            }
        """ ;
    ] .

:ElementInfluenceShape
    a sh:NodeShape ;
    rdfs:label "element influence shape" ;
    rdfs:comment "Validates the input element and optional responsible activity of a qualified indirect influence." ;
    sh:targetClass :ElementInfluence ;
    sh:property [
        sh:path prov:entity ;
        sh:class :DataElementVersion ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "An ElementInfluence must identify exactly one indirectly influencing input element." ;
    ] ;
    sh:property [
        sh:path prov:hadActivity ;
        sh:class :ProcessingActivity ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "When an output also states unqualified indirect lineage, it must include the input named by each qualified influence." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?output ?input WHERE {
                ?output :qualifiedElementInfluence $this .
                $this prov:entity ?input .
                FILTER EXISTS { ?output :wasIndirectlyInfluencedByElement ?any . }
                FILTER NOT EXISTS { ?output :wasIndirectlyInfluencedByElement ?input . }
            }
        """ ;
    ] .

:ClassificationImpactShape
    a sh:NodeShape ;
    rdfs:label "classification impact shape" ;
    rdfs:comment "Validates a named impact kind, review marker, and at least one source or result port, category, or classification." ;
    sh:targetClass :ClassificationImpact ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :hasImpactKind ;
        sh:class :ImpactKind ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ClassificationImpact must identify exactly one impact kind." ;
    ] ;
    sh:property [
        sh:path :requiresImpactReview ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ClassificationImpact must explicitly state whether review is required." ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :sourcePort
                :resultPort
                :sourceCategory
                :resultCategory
                :sourceClassification
                :resultClassification
            ) ;
        ] ;
        sh:minCount 1 ;
        sh:message "A ClassificationImpact must identify at least one source or result port, category, or classification." ;
    ] ;
    sh:property [
        sh:path :sourceSensitivity ;
        sh:class dcls:SensitivityLevel ;
    ] ;
    sh:property [
        sh:path :resultSensitivity ;
        sh:class dcls:SensitivityLevel ;
    ] ;
    sh:property [
        sh:path :introducedRequirement ;
        sh:nodeKind sh:IRI ;
    ] ;
    sh:sparql [
        sh:message "Category suppression and unknown impacts must explicitly require review." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this WHERE {
                $this :hasImpactKind ?kind .
                FILTER(?kind IN (:CategorySuppression, :UnknownImpact))
                FILTER NOT EXISTS { $this :requiresImpactReview true . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A SensitivityEscalation impact must identify a result sensitivity." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this WHERE {
                $this :hasImpactKind :SensitivityEscalation .
                FILTER NOT EXISTS { $this :resultSensitivity ?sensitivity . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A RequirementIntroduction impact must identify the introduced requirement." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this WHERE {
                $this :hasImpactKind :RequirementIntroduction .
                FILTER NOT EXISTS { $this :introducedRequirement ?requirement . }
            }
        """ ;
    ] .

:ExpectedClassificationImpactShape
    a sh:NodeShape ;
    rdfs:label "expected classification impact shape" ;
    rdfs:comment "Requires an expected impact to be declared by exactly one processing specification and to use only that specification's ports." ;
    sh:targetClass :ExpectedClassificationImpact ;
    sh:property [
        sh:path [ sh:inversePath :hasExpectedClassificationImpact ] ;
        sh:class :ProcessingSpecification ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "An ExpectedClassificationImpact must be declared by exactly one ProcessingSpecification." ;
    ] ;
    sh:sparql [
        sh:message "Every source and result port of an expected impact must be owned by the specification that declares the impact." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this ?port WHERE {
                ?specification :hasExpectedClassificationImpact $this .
                {
                    $this :sourcePort ?port .
                    FILTER NOT EXISTS { ?specification :hasInputPort ?port . }
                }
                UNION
                {
                    $this :resultPort ?port .
                    FILTER NOT EXISTS { ?specification :hasOutputPort ?port . }
                }
            }
        """ ;
    ] .

:ObservedClassificationImpactShape
    a sh:NodeShape ;
    rdfs:label "observed classification impact shape" ;
    rdfs:comment "Requires observed impacts to reference concrete source or result classifications and the activity that generated the impact assertion." ;
    sh:targetClass :ObservedClassificationImpact ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :sourceClassification
                :resultClassification
            ) ;
        ] ;
        sh:class dcls:DataClassification ;
        sh:minCount 1 ;
        sh:message "An ObservedClassificationImpact must reference a source or result classification record." ;
    ] ;
    sh:property [
        sh:path prov:wasGeneratedBy ;
        sh:class prov:Activity ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "An ObservedClassificationImpact must identify its generating assessment or propagation activity." ;
    ] .

:ProcessingAssessmentShape
    a sh:NodeShape ;
    rdfs:label "processing assessment shape" ;
    rdfs:comment "Validates outcome, rationale, provenance, assessed resources, and optional structured confidence." ;
    sh:targetClass :ProcessingAssessment ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :assessesResource ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:message "A ProcessingAssessment must assess at least one named resource." ;
    ] ;
    sh:property [
        sh:path :hasAssessmentOutcome ;
        sh:class :AssessmentOutcome ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ProcessingAssessment must identify exactly one outcome." ;
    ] ;
    sh:property [
        sh:path :assessmentRationale ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:message "A ProcessingAssessment must provide a rationale." ;
    ] ;
    sh:property [
        sh:path prov:wasGeneratedBy ;
        sh:class prov:Activity ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ProcessingAssessment must identify exactly one generating activity." ;
    ] ;
    sh:property [
        sh:path :confidenceAssessment ;
        sh:class <https://ontology.inferal.com/modules/confidence/ConfidenceAssessment> ;
        sh:maxCount 1 ;
    ] .

:ProcessingConformanceAssessmentShape
    a sh:NodeShape ;
    rdfs:label "processing conformance assessment shape" ;
    rdfs:comment "Requires a conformance assessment to identify one activity and one specification and to use evidence for a conformant conclusion." ;
    sh:targetClass :ProcessingConformanceAssessment ;
    sh:property [
        sh:path :assessesActivity ;
        sh:class :ProcessingActivity ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ProcessingConformanceAssessment must assess exactly one ProcessingActivity." ;
    ] ;
    sh:property [
        sh:path :againstSpecification ;
        sh:class :ProcessingSpecification ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ProcessingConformanceAssessment must identify exactly one specification." ;
    ] ;
    sh:property [
        sh:path :hasAssessmentOutcome ;
        sh:class :ConformanceOutcome ;
        sh:message "A ProcessingConformanceAssessment must use a ConformanceOutcome." ;
    ] ;
    sh:sparql [
        sh:message "A conformant assessment must cite at least one evidence entity with prov:used." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this WHERE {
                $this :hasAssessmentOutcome :Conformant .
                FILTER NOT EXISTS { $this prov:used ?evidence . }
            }
        """ ;
    ] .

:ContextCompatibilityAssessmentShape
    a sh:NodeShape ;
    rdfs:label "context compatibility assessment shape" ;
    rdfs:comment "Requires a directional source and target context, an explicit set of assessed facets, one compatibility outcome, rationale, and provenance inherited from ProcessingAssessmentShape." ;
    sh:targetClass :ContextCompatibilityAssessment ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :sourceContext ;
        sh:class dcls:ClassificationContext ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ContextCompatibilityAssessment must identify exactly one source context." ;
    ] ;
    sh:property [
        sh:path :targetContext ;
        sh:class dcls:ClassificationContext ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A ContextCompatibilityAssessment must identify exactly one target context." ;
    ] ;
    sh:property [
        sh:path :assessesContextFacet ;
        sh:class :ContextFacet ;
        sh:minCount 1 ;
        sh:message "A ContextCompatibilityAssessment must name at least one assessed context facet." ;
    ] ;
    sh:property [
        sh:path :hasAssessmentOutcome ;
        sh:class :ContextCompatibilityOutcome ;
        sh:message "A ContextCompatibilityAssessment must use a ContextCompatibilityOutcome." ;
    ] ;
    sh:sparql [
        sh:message "A compatibility assessment must assess both its source and target contexts as resources." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this ?context WHERE {
                $this (:sourceContext|:targetContext) ?context .
                FILTER NOT EXISTS { $this :assessesResource ?context . }
            }
        """ ;
    ] .

:LineageCoverageAssessmentShape
    a sh:NodeShape ;
    rdfs:label "lineage coverage assessment shape" ;
    rdfs:comment "Requires one assessed resource, one coverage conclusion, explicit scope dimensions, a concrete boundary, rationale, and generating provenance." ;
    sh:targetClass :LineageCoverageAssessment ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :assessedLineageCoverage ;
        sh:class :LineageCoverage ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A LineageCoverageAssessment must identify exactly one coverage conclusion." ;
    ] ;
    sh:property [
        sh:path :assessedLineageScope ;
        sh:class :LineageScope ;
        sh:minCount 1 ;
        sh:message "A LineageCoverageAssessment must identify at least one assessed scope dimension." ;
    ] ;
    sh:property [
        sh:path :coverageBoundary ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:message "A LineageCoverageAssessment must identify at least one concrete coverage boundary." ;
    ] ;
    sh:property [
        sh:path :coverageStartTime ;
        sh:datatype xsd:dateTime ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :coverageEndTime ;
        sh:datatype xsd:dateTime ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "A lineage coverage end time must not precede its start time." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this WHERE {
                $this :coverageStartTime ?start ; :coverageEndTime ?end .
                FILTER (?end < ?start)
            }
        """ ;
    ] .

:StructuralConformanceAssessmentShape
    a sh:NodeShape ;
    rdfs:label "structural conformance assessment shape" ;
    rdfs:comment "Requires a validated resource, exact named shape, immutable shapes graph, authoritative SHACL report, conformance outcome, rationale, and validation provenance." ;
    sh:targetClass :StructuralConformanceAssessment ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :validatedResource ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A StructuralConformanceAssessment must identify exactly one validated resource." ;
    ] ;
    sh:property [
        sh:path :validationShape ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A StructuralConformanceAssessment must identify exactly one named validation shape." ;
    ] ;
    sh:property [
        sh:path :validationShapesGraph ;
        sh:class prov:Entity ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A StructuralConformanceAssessment must identify exactly one versioned shapes graph." ;
    ] ;
    sh:property [
        sh:path :validationReport ;
        sh:class sh:ValidationReport ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A StructuralConformanceAssessment must identify exactly one SHACL ValidationReport." ;
    ] ;
    sh:property [
        sh:path ( :validationReport sh:conforms ) ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "The linked SHACL ValidationReport must state exactly one sh:conforms boolean." ;
    ] ;
    sh:property [
        sh:path :validationContext ;
        sh:class dcls:ClassificationContext ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :hasAssessmentOutcome ;
        sh:class :ConformanceOutcome ;
        sh:message "A StructuralConformanceAssessment must use a ConformanceOutcome." ;
    ] ;
    sh:sparql [
        sh:message "The assessment outcome and SHACL report's sh:conforms value must agree." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX sh: <http://www.w3.org/ns/shacl#>
            SELECT $this ?report WHERE {
                $this :validationReport ?report ; :hasAssessmentOutcome ?outcome .
                {
                    ?report sh:conforms true .
                    FILTER (?outcome != :Conformant)
                }
                UNION
                {
                    ?report sh:conforms false .
                    FILTER (?outcome != :NonConformant)
                }
            }
        """ ;
    ] .

:OperationalRuleShape
    a sh:NodeShape ;
    rdfs:label "operational rule shape" ;
    rdfs:comment "Requires registered rules to identify their kind, implementation, monotonicity, and behavior under missing or conflicting evidence." ;
    sh:targetClass :OperationalRule ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :hasRuleKind ;
        sh:class :RuleKind ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :ruleImplementation ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :ruleFailureBehavior ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :ruleIsMonotonic ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] .

:ContextFacetShape
    a sh:NodeShape ;
    rdfs:label "context facet shape" ;
    rdfs:comment "Provides named-IRI coverage for context compatibility dimensions." ;
    sh:targetClass :ContextFacet ;
    sh:nodeKind sh:IRI .

:ContextCompatibilityOutcomeShape
    a sh:NodeShape ;
    rdfs:label "context compatibility outcome shape" ;
    rdfs:comment "Provides named-IRI coverage for directional compatibility conclusions." ;
    sh:targetClass :ContextCompatibilityOutcome ;
    sh:nodeKind sh:IRI .

:RuleKindShape
    a sh:NodeShape ;
    rdfs:label "rule kind shape" ;
    rdfs:comment "Provides named-IRI coverage for operational rule roles." ;
    sh:targetClass :RuleKind ;
    sh:nodeKind sh:IRI .

:MitigationRequirementShape
    a sh:NodeShape ;
    rdfs:label "mitigation requirement shape" ;
    rdfs:comment "Requires a named measure category, one scope, and an originating classification or declaring specification." ;
    sh:targetClass :MitigationRequirement ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :requiredMeasure ;
        sh:node :MeasureCategoryShape ;
        sh:minCount 1 ;
        sh:message "A MitigationRequirement must identify at least one required measure category." ;
    ] ;
    sh:property [
        sh:path :hasRequirementScope ;
        sh:class :MitigationScope ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A MitigationRequirement must identify exactly one scope." ;
    ] ;
    sh:property [
        sh:path :requirementContext ;
        sh:class dcls:ClassificationContext ;
        sh:maxCount 1 ;
        sh:message "A MitigationRequirement can identify at most one bundled context." ;
    ] ;
    sh:property [
        sh:path :requiresContextFacet ;
        sh:class :ContextFacet ;
    ] ;
    sh:property [
        sh:path [
            sh:alternativePath (
                :originatingClassification
                [ sh:inversePath :declaresMitigationRequirement ]
            ) ;
        ] ;
        sh:minCount 1 ;
        sh:message "A MitigationRequirement must originate from a classification or be declared by a specification." ;
    ] .

:MitigationAssessmentShape
    a sh:NodeShape ;
    rdfs:label "mitigation assessment shape" ;
    rdfs:comment "Requires one assessed requirement, activity, and measure; satisfied conclusions must include an explicit rationale through ProcessingAssessmentShape." ;
    sh:targetClass :MitigationAssessment ;
    sh:property [
        sh:path :assessesRequirement ;
        sh:class :MitigationRequirement ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A MitigationAssessment must assess exactly one MitigationRequirement." ;
    ] ;
    sh:property [
        sh:path :assessesActivity ;
        sh:class :ProcessingActivity ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A MitigationAssessment must assess exactly one ProcessingActivity." ;
    ] ;
    sh:property [
        sh:path :assessedMeasure ;
        sh:node :MeasureCategoryShape ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "A MitigationAssessment must identify exactly one assessed measure category." ;
    ] ;
    sh:property [
        sh:path :hasAssessmentOutcome ;
        sh:class :MitigationOutcome ;
        sh:message "A MitigationAssessment must use a MitigationOutcome." ;
    ] ;
    sh:sparql [
        sh:message "The assessed measure must be required by the assessed requirement and applied by the assessed activity." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            SELECT $this ?measure WHERE {
                $this :assessesRequirement ?requirement ;
                    :assessesActivity ?activity ;
                    :assessedMeasure ?measure .
                {
                    FILTER NOT EXISTS { ?requirement :requiredMeasure ?measure . }
                }
                UNION
                {
                    FILTER NOT EXISTS { ?activity :appliedMeasure ?measure . }
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every requirement context must match or be positively compatible with the assessed activity's processing context." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/data-usage-processing-lineage/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?requiredContext WHERE {
                $this :assessesRequirement ?requirement ;
                    :assessesActivity ?activity .
                ?requirement :requirementContext ?requiredContext .
                ?activity :processingContext ?activityContext .
                FILTER (?requiredContext != ?activityContext)
                FILTER NOT EXISTS {
                    ?compatibility a :ContextCompatibilityAssessment ;
                        :sourceContext ?requiredContext ;
                        :targetContext ?activityContext ;
                        :hasAssessmentOutcome :Compatible ;
                        prov:wasGeneratedBy ?review .
                    FILTER NOT EXISTS {
                        ?conflict a :ContextCompatibilityAssessment ;
                            :sourceContext ?requiredContext ;
                            :targetContext ?activityContext ;
                            :hasAssessmentOutcome :Incompatible .
                    }
                }
            }
        """ ;
    ] .

:MeasureCategoryShape
    a sh:NodeShape ;
    rdfs:label "measure category shape" ;
    rdfs:comment "Validates that a value denotes a DPV-compatible technical or organisational measure category class." ;
    sh:nodeKind sh:IRI ;
    sh:sparql [
        sh:message "The value must be a DPV technical or organisational measure category class." ;
        sh:select """
            PREFIX dpv: <https://w3id.org/dpv#>
            PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
            SELECT $this WHERE {
                FILTER NOT EXISTS {
                    $this rdfs:subClassOf* dpv:TechnicalOrganisationalMeasure .
                }
                FILTER NOT EXISTS {
                    $this a rdfs:Class ;
                        a ?measureCategory .
                    ?measureCategory rdfs:subClassOf* dpv:TechnicalOrganisationalMeasure .
                }
            }
        """ ;
    ] .

:ControlledValueShape
    a sh:NodeShape ;
    rdfs:label "controlled value shape" ;
    rdfs:comment "Provides basic named-IRI coverage for the vocabulary's controlled impact kinds, lineage coverage values and scopes, assessment outcomes, and mitigation scopes." ;
    sh:targetClass
        :AssessmentOutcome,
        :ConformanceOutcome,
        :ContextCompatibilityOutcome,
        :ContextFacet,
        :DataOriginKind,
        :ImpactKind,
        :LineageCoverage,
        :LineageScope,
        :MitigationOutcome,
        :MitigationScope,
        :RuleKind ;
    sh:nodeKind sh:IRI .
