@prefix ae: <https://ontology.inferal.com/modules/artifact-evolution/> .
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

ae:GovernedArtifactShape
    a sh:NodeShape ;
    rdfs:label "governed artifact shape"@en ;
    rdfs:comment "Validates the optional current-version designation and reciprocal artifact-version links."@en ;
    sh:targetClass ae:GovernedArtifact ;
    sh:property [
        sh:path ae:currentArtifactVersion ;
        sh:class ae:ArtifactVersion ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "Every hasArtifactVersion or currentArtifactVersion value must reciprocally identify this governed artifact with versionOfArtifact."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this ?version WHERE {
                $this (ae:hasArtifactVersion|ae:currentArtifactVersion) ?version .
                FILTER NOT EXISTS { ?version ae:versionOfArtifact $this . }
            }
        """ ;
    ] .

ae:ArtifactVersionShape
    a sh:NodeShape ;
    rdfs:label "artifact version shape"@en ;
    rdfs:comment "Requires every artifact version to specialize exactly one governed artifact and optionally carry one comparable version identifier."@en ;
    sh:targetClass ae:ArtifactVersion ;
    sh:property [
        sh:path ae:versionOfArtifact ;
        sh:class ae:GovernedArtifact ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path ae:versionIdentifier ;
        sh:maxCount 1 ;
    ] .

ae:EvolutionSignalShape
    a sh:NodeShape ;
    rdfs:label "evolution signal shape"@en ;
    rdfs:comment "Requires a signal to concern one artifact and have one signal kind."@en ;
    sh:targetClass ae:EvolutionSignal ;
    sh:property [ sh:path ae:concernsArtifact ; sh:class ae:GovernedArtifact ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:hasSignalKind ; sh:class ae:SignalKind ; sh:minCount 1 ; sh:maxCount 1 ] .

ae:ChangeProposalShape
    a sh:NodeShape ;
    rdfs:label "change proposal shape"@en ;
    rdfs:comment "Requires a proposal to concern one artifact from one matching baseline and to contain at least one operation supported by evidence."@en ;
    sh:targetClass ae:ChangeProposal ;
    sh:property [ sh:path ae:concernsArtifact ; sh:class ae:GovernedArtifact ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:baselineVersion ; sh:class ae:ArtifactVersion ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:hasOperation ; sh:class ae:ChangeOperation ; sh:minCount 1 ] ;
    sh:property [ sh:path ae:supportedBy ; sh:class prov:Entity ; sh:minCount 1 ] ;
    sh:sparql [
        sh:message "A proposal baseline must be a version of the artifact concerned by the proposal."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this ?artifact ?baseline WHERE {
                $this ae:concernsArtifact ?artifact ; ae:baselineVersion ?baseline .
                FILTER NOT EXISTS { ?baseline ae:versionOfArtifact ?artifact . }
            }
        """ ;
    ] .

ae:ChangeOperationShape
    a sh:NodeShape ;
    rdfs:label "change operation shape"@en ;
    rdfs:comment "Requires one change kind and at least one affected resource or exact statement target."@en ;
    sh:targetClass ae:ChangeOperation ;
    sh:property [ sh:path ae:hasChangeKind ; sh:class ae:ChangeKind ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:sparql [
        sh:message "A change operation must affect at least one resource or exact statement target."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this WHERE {
                FILTER NOT EXISTS { $this ae:affectsResource ?resource . }
                FILTER NOT EXISTS { $this ae:affectsStatementTarget ?target . }
            }
        """ ;
    ] .

ae:ChangeDecisionShape
    a sh:NodeShape ;
    rdfs:label "change decision shape"@en ;
    rdfs:comment "Requires a decision to address one proposal, state one outcome, identify an authority, and carry generation provenance and time."@en ;
    sh:targetClass ae:ChangeDecision ;
    sh:property [ sh:path ae:decidesProposal ; sh:class ae:ChangeProposal ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:hasDecisionOutcome ; sh:class ae:DecisionOutcome ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path prov:wasAttributedTo ; sh:class prov:Agent ; sh:minCount 1 ] ;
    sh:property [ sh:path prov:wasGeneratedBy ; sh:class ae:ChangeReview ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path prov:generatedAtTime ; sh:datatype xsd:dateTimeStamp ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:sparql [
        sh:message "A superseding decision must decide the same proposal as the decision it supersedes."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this ?earlier WHERE {
                $this ae:decidesProposal ?proposal ; ae:supersedesDecision ?earlier .
                FILTER NOT EXISTS { ?earlier ae:decidesProposal ?proposal . }
            }
        """ ;
    ] .

ae:ImpactAssessmentShape
    a sh:NodeShape ;
    rdfs:label "impact assessment shape"@en ;
    rdfs:comment "Requires an impact assessment to identify one subject and supporting evidence, with at most one compatibility conclusion."@en ;
    sh:targetClass ae:ImpactAssessment ;
    sh:property [ sh:path ae:assessmentSubject ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:supportedBy ; sh:class prov:Entity ; sh:minCount 1 ] ;
    sh:property [ sh:path ae:hasCompatibilityImpact ; sh:class ae:CompatibilityImpact ; sh:maxCount 1 ] .

ae:ChangeReviewShape
    a sh:NodeShape ;
    rdfs:label "change review shape"@en ;
    rdfs:comment "Requires a change review to use a proposal and identify at least one associated authority."@en ;
    sh:targetClass ae:ChangeReview ;
    sh:property [
        sh:path prov:used ;
        sh:qualifiedValueShape [ sh:class ae:ChangeProposal ] ;
        sh:qualifiedMinCount 1 ;
    ] ;
    sh:property [ sh:path prov:wasAssociatedWith ; sh:class prov:Agent ; sh:minCount 1 ] .

ae:ChangeRealizationShape
    a sh:NodeShape ;
    rdfs:label "change realization shape"@en ;
    rdfs:comment "Requires a realization to generate one artifact version and to identify at least one proposal or operation it implements."@en ;
    sh:targetClass ae:ChangeRealization ;
    sh:property [ sh:path ae:generatedArtifactVersion ; sh:class ae:ArtifactVersion ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:sparql [
        sh:message "A change realization must identify at least one proposal or specific operation it realizes."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this WHERE {
                FILTER NOT EXISTS { $this ae:realizesProposal ?proposal . }
                FILTER NOT EXISTS { $this ae:implementsOperation ?operation . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every generated version must belong to the artifact concerned by each realized proposal."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this ?proposal ?artifact ?version WHERE {
                $this ae:realizesProposal ?proposal ; ae:generatedArtifactVersion ?version .
                ?proposal ae:concernsArtifact ?artifact .
                FILTER NOT EXISTS { ?version ae:versionOfArtifact ?artifact . }
            }
        """ ;
    ] .

ae:ArtifactComparisonShape
    a sh:NodeShape ;
    rdfs:label "artifact comparison shape"@en ;
    rdfs:comment "Requires one distinct baseline/result pair, one declared comparison regime and coverage status, and one generated change set agreeing with the comparison endpoints."@en ;
    sh:targetClass ae:ArtifactComparison ;
    sh:property [ sh:path ae:baselineVersion ; sh:class ae:ArtifactVersion ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:resultVersion ; sh:class ae:ArtifactVersion ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:usesComparisonRegime ; sh:class ae:ComparisonRegime ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:coverageStatus ; sh:class ae:ComparisonCoverage ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [
        sh:path ae:coverageDescription ;
        sh:nodeKind sh:Literal ;
        sh:pattern "\\S" ;
    ] ;
    sh:property [ sh:path ae:generatedChangeSet ; sh:class ae:ChangeSet ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:sparql [
        sh:message "An artifact comparison must compare distinct versions of the same governed artifact."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this ?baseline ?result WHERE {
                $this ae:baselineVersion ?baseline ; ae:resultVersion ?result .
                FILTER(
                    ?baseline = ?result ||
                    NOT EXISTS {
                        ?baseline ae:versionOfArtifact ?artifact .
                        ?result ae:versionOfArtifact ?artifact .
                    }
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "The generated change set must state the same baseline and result versions as its comparison activity."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this ?changeSet WHERE {
                $this ae:baselineVersion ?baseline ; ae:resultVersion ?result ; ae:generatedChangeSet ?changeSet .
                FILTER NOT EXISTS { ?changeSet ae:baselineVersion ?baseline ; ae:resultVersion ?result . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "The generated change set must identify this artifact comparison as its generating activity."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?changeSet WHERE {
                $this ae:generatedChangeSet ?changeSet .
                FILTER NOT EXISTS { ?changeSet prov:wasGeneratedBy $this . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A partial or indeterminate comparison must describe the actual coverage limit or unresolved condition."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            PREFIX dcterms: <http://purl.org/dc/terms/>
            PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
            SELECT $this ?coverage WHERE {
                $this ae:coverageStatus ?coverage .
                ?coverage skos:broader* ?baseCoverage .
                FILTER(?baseCoverage IN (ae:PartialCoverage, ae:IndeterminateCoverage))
                FILTER NOT EXISTS { $this ae:coverageDescription ?description . }
            }
        """ ;
    ] .

ae:ComparisonRegimeShape
    a sh:NodeShape ;
    rdfs:label "comparison regime shape"@en ;
    rdfs:comment "Requires every comparison regime to describe the graph scope, identity treatment, and processing semantics it governs."@en ;
    sh:targetClass ae:ComparisonRegime ;
    sh:property [
        sh:path dcterms:description ;
        sh:nodeKind sh:Literal ;
        sh:minCount 1 ;
        sh:minLength 1 ;
        sh:pattern "\\S" ;
    ] .

ae:ChangeSetShape
    a sh:NodeShape ;
    rdfs:label "change set shape"@en ;
    rdfs:comment "Requires a change set to describe one artifact, one distinct version pair, any observed realized changes, and one generating comparison that points back to the change set."@en ;
    sh:targetClass ae:ChangeSet ;
    sh:property [ sh:path ae:concernsArtifact ; sh:class ae:GovernedArtifact ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:baselineVersion ; sh:class ae:ArtifactVersion ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:resultVersion ; sh:class ae:ArtifactVersion ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path ae:hasChange ; sh:class ae:RealizedChange ] ;
    sh:property [ sh:path prov:wasGeneratedBy ; sh:class ae:ArtifactComparison ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:sparql [
        sh:message "A change set must compare distinct versions of its concernsArtifact."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this ?artifact ?baseline ?result WHERE {
                $this ae:concernsArtifact ?artifact ; ae:baselineVersion ?baseline ; ae:resultVersion ?result .
                FILTER(
                    ?baseline = ?result ||
                    NOT EXISTS { ?baseline ae:versionOfArtifact ?artifact . } ||
                    NOT EXISTS { ?result ae:versionOfArtifact ?artifact . }
                )
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A change set's generating comparison must identify this change set as its generated output."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?comparison WHERE {
                $this prov:wasGeneratedBy ?comparison .
                FILTER NOT EXISTS { ?comparison ae:generatedChangeSet $this . }
            }
        """ ;
    ] .

ae:RealizedChangeShape
    a sh:NodeShape ;
    rdfs:label "realized change shape"@en ;
    rdfs:comment "Requires a realized change to have one kind, an affected target, and membership in at least one change set."@en ;
    sh:targetClass ae:RealizedChange ;
    sh:property [ sh:path ae:hasChangeKind ; sh:class ae:ChangeKind ; sh:minCount 1 ; sh:maxCount 1 ] ;
    sh:property [ sh:path [ sh:inversePath ae:hasChange ] ; sh:class ae:ChangeSet ; sh:minCount 1 ] ;
    sh:sparql [
        sh:message "A realized change must affect at least one resource or exact statement target."@en ;
        sh:select """
            PREFIX ae: <https://ontology.inferal.com/modules/artifact-evolution/>
            SELECT $this WHERE {
                FILTER NOT EXISTS { $this ae:affectsResource ?resource . }
                FILTER NOT EXISTS { $this ae:affectsStatementTarget ?target . }
            }
        """ ;
    ] .

ae:SignalKindShape
    a sh:NodeShape ;
    rdfs:label "signal kind shape"@en ;
    rdfs:comment "Requires every signal-kind concept to have a preferred label and concept scheme."@en ;
    sh:targetClass ae:SignalKind ;
    sh:property [ sh:path skos:prefLabel ; sh:minCount 1 ] ;
    sh:property [ sh:path skos:inScheme ; sh:class skos:ConceptScheme ; sh:minCount 1 ] .

ae:ChangeKindShape
    a sh:NodeShape ;
    rdfs:label "change kind shape"@en ;
    rdfs:comment "Requires every change-kind concept to have a preferred label and concept scheme."@en ;
    sh:targetClass ae:ChangeKind ;
    sh:property [ sh:path skos:prefLabel ; sh:minCount 1 ] ;
    sh:property [ sh:path skos:inScheme ; sh:class skos:ConceptScheme ; sh:minCount 1 ] .

ae:DecisionOutcomeShape
    a sh:NodeShape ;
    rdfs:label "decision outcome shape"@en ;
    rdfs:comment "Requires every decision-outcome concept to have a preferred label and concept scheme."@en ;
    sh:targetClass ae:DecisionOutcome ;
    sh:property [ sh:path skos:prefLabel ; sh:minCount 1 ] ;
    sh:property [ sh:path skos:inScheme ; sh:class skos:ConceptScheme ; sh:minCount 1 ] .

ae:CompatibilityImpactShape
    a sh:NodeShape ;
    rdfs:label "compatibility impact shape"@en ;
    rdfs:comment "Requires every compatibility-impact concept to have a preferred label and concept scheme."@en ;
    sh:targetClass ae:CompatibilityImpact ;
    sh:property [ sh:path skos:prefLabel ; sh:minCount 1 ] ;
    sh:property [ sh:path skos:inScheme ; sh:class skos:ConceptScheme ; sh:minCount 1 ] .

ae:ComparisonCoverageShape
    a sh:NodeShape ;
    rdfs:label "comparison coverage shape"@en ;
    rdfs:comment "Requires every comparison-coverage concept to have a preferred label and concept scheme."@en ;
    sh:targetClass ae:ComparisonCoverage ;
    sh:property [ sh:path skos:prefLabel ; sh:minCount 1 ] ;
    sh:property [ sh:path skos:inScheme ; sh:class skos:ConceptScheme ; sh:minCount 1 ] .
