@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 ex: <https://example.com/duplo/> .
@prefix pd: <https://w3id.org/dpv/pd#> .
@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#> .

# This graph intentionally contains several independent processing-lineage patterns. It
# demonstrates abstract specifications, concrete plans, runtime provenance,
# classification propagation, policy modality, mitigation evidence, direct and
# indirect element lineage, source-free generation, version replacement,
# streaming windows, partial lineage, and non-conformance.

# Shared agents, contexts, structural shapes, and implementation entities

ex:relay-worker
    a prov:SoftwareAgent ;
    rdfs:label "Relay worker" .

ex:privacy-validator
    a prov:SoftwareAgent ;
    rdfs:label "Privacy validation service" .

ex:privacy-analyst
    a prov:Person ;
    rdfs:label "Privacy analyst" .

ex:analytics-context
    a dcls:ClassificationContext ;
    rdfs:label "Restricted analytics context" .

ex:export-context
    a dcls:ClassificationContext ;
    rdfs:label "External export context" .

ex:StringElementShape
    a sh:NodeShape ;
    rdfs:label "String element structural shape" ;
    sh:property [
        sh:path dcls:path ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
    ] .

ex:structural-shapes-v1
    a prov:Entity ;
    rdfs:label "Structural shapes graph version 1" ;
    prov:value "sha256:example-structural-shapes-v1" .

ex:hmac-sha256-config-v3
    a prov:Entity ;
    rdfs:label "HMAC-SHA-256 configuration version 3" ;
    rdfs:comment "A public configuration identifier. Secret key material is deliberately not embedded in the provenance graph." .

ex:aes-gcm-config-v2
    a prov:Entity ;
    rdfs:label "AES-GCM configuration version 2" .

# 1. Abstract pseudonymisation contract over categories

ex:pseudonymise-identifier-specification
    a :ProcessingSpecification ;
    rdfs:label "Pseudonymise identifier specification" ;
    rdfs:comment "Accepts confirmed personal data and projects pseudonymised personal data. The projection is an expectation rather than an assertion about any concrete output." ;
    :declaresProcessing dpv:Pseudonymise ;
    :specificationContext ex:analytics-context ;
    :hasInputPort ex:pseudonymisation-identified-input ;
    :hasOutputPort ex:pseudonymisation-pseudonymous-output ;
    :hasExpectedClassificationImpact ex:pseudonymisation-expected-impact ;
    :declaresMitigationRequirement ex:pseudonymisation-output-requirement .

ex:pseudonymisation-identified-input
    a :DataInputPort ;
    rdfs:label "identified personal-data input" ;
    :hasClassificationConstraint ex:confirmed-personal-data-constraint .

ex:confirmed-personal-data-constraint
    a :ClassificationConstraint ;
    rdfs:label "confirmed personal-data constraint" ;
    :requiresDataCategory dpv:PersonalData ;
    :requiresClassificationStatus dcls:Confirmed ;
    :requiresStructuralShape ex:StringElementShape .

ex:pseudonymisation-pseudonymous-output
    a :OutputPort ;
    rdfs:label "pseudonymous output" ;
    :hasClassificationConstraint ex:pseudonymised-output-constraint .

ex:pseudonymised-output-constraint
    a :ClassificationConstraint ;
    rdfs:label "pseudonymised output constraint" ;
    :requiresDataCategory dpv:PseudonymisedData ;
    :requiresClassificationStatus dcls:Confirmed .

ex:pseudonymisation-expected-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "expected pseudonymisation classification impact" ;
    :sourcePort ex:pseudonymisation-identified-input ;
    :resultPort ex:pseudonymisation-pseudonymous-output ;
    :sourceCategory dpv:PersonalData ;
    :resultCategory dpv:PseudonymisedData ;
    :hasImpactKind :CategoryReplacement ;
    :requiresImpactReview true ;
    :impactRationale "Pseudonymisation replaces identifying representation categories with a pseudonymised-data category but does not remove personal-data status." .

ex:pseudonymisation-output-requirement
    a :MitigationRequirement ;
    rdfs:label "pseudonymisation output-state requirement" ;
    :requiredMeasure dpv:Pseudonymisation ;
    :hasRequirementScope :OutputState ;
    :requirementContext ex:analytics-context .

# A concrete plan repeats its deployable ports instead of treating the abstract
# ports as execution-global variables. This keeps port scope explicit.

ex:email-tokenisation-plan-v3
    a :ProcessingPlan ;
    rdfs:label "Email tokenisation plan version 3" ;
    :implementsSpecification ex:pseudonymise-identifier-specification ;
    :declaresProcessing dpv:Pseudonymise ;
    :specificationContext ex:analytics-context ;
    :hasInputPort ex:email-tokenisation-input-v3 ;
    :hasOutputPort ex:email-tokenisation-output-v3 ;
    :hasExpectedClassificationImpact ex:email-tokenisation-expected-impact-v3 ;
    :declaresMitigationRequirement ex:email-tokenisation-output-requirement-v3 ;
    prov:wasRevisionOf ex:email-tokenisation-plan-v2 .

ex:email-tokenisation-plan-v2
    a prov:Plan ;
    rdfs:label "Email tokenisation plan version 2" .

ex:email-tokenisation-input-v3
    a :DataInputPort ;
    rdfs:label "email input for tokenisation plan version 3" ;
    :refinesPort ex:pseudonymisation-identified-input ;
    :hasClassificationConstraint ex:confirmed-email-constraint .

ex:confirmed-email-constraint
    a :ClassificationConstraint ;
    rdfs:label "confirmed email-address constraint" ;
    :requiresDataCategory pd:EmailAddress ;
    :requiresClassificationStatus dcls:Confirmed ;
    :requiresStructuralShape ex:StringElementShape .

ex:email-tokenisation-output-v3
    a :OutputPort ;
    rdfs:label "token output for tokenisation plan version 3" ;
    :refinesPort ex:pseudonymisation-pseudonymous-output ;
    :hasClassificationConstraint ex:pseudonymised-output-constraint-v3 .

ex:pseudonymised-output-constraint-v3
    a :ClassificationConstraint ;
    rdfs:label "confirmed pseudonymised output constraint version 3" ;
    :requiresDataCategory dpv:PseudonymisedData ;
    :requiresClassificationStatus dcls:Confirmed .

ex:email-tokenisation-expected-impact-v3
    a :ExpectedClassificationImpact ;
    rdfs:label "email tokenisation expected impact version 3" ;
    :sourcePort ex:email-tokenisation-input-v3 ;
    :resultPort ex:email-tokenisation-output-v3 ;
    :sourceCategory pd:EmailAddress ;
    :resultCategory dpv:PseudonymisedData ;
    :hasImpactKind :CategoryReplacement ;
    :requiresImpactReview true ;
    :impactRationale "The HMAC token no longer has email-address syntax, while remaining pseudonymised personal data with upstream email lineage." .

ex:email-tokenisation-output-requirement-v3
    a :MitigationRequirement ;
    rdfs:label "email tokenisation output requirement version 3" ;
    :requiredMeasure dpv:Pseudonymisation ;
    :hasRequirementScope :OutputState ;
    :requirementContext ex:analytics-context .

# 2. Concrete pseudonymisation execution and element lineage

ex:customers-v17
    a :DataSnapshot ;
    rdfs:label "Customers table snapshot version 17" .

ex:customer-tokens-v4
    a :DataSnapshot ;
    rdfs:label "Customer token table snapshot version 4" ;
    prov:wasDerivedFrom ex:customers-v17 ;
    prov:wasGeneratedBy ex:email-tokenisation-run-42 .

ex:customer-email-v17
    a :DataElementVersion ;
    rdfs:label "Customer email element version 17" ;
    dcls:sourceAsset ex:customers-v17 ;
    dcls:path "customers.email" .

ex:customer-token-v4
    a :DataElementVersion ;
    rdfs:label "Customer token element version 4" ;
    dcls:sourceAsset ex:customer-tokens-v4 ;
    dcls:path "customer_tokens.customer_token" ;
    :wasDirectlyDerivedFromElement ex:customer-email-v17 ;
    :qualifiedElementDerivation ex:customer-token-email-derivation ;
    prov:wasGeneratedBy ex:email-tokenisation-run-42 ;
    prov:qualifiedGeneration ex:email-tokenisation-token-generation .

ex:customer-token-email-derivation
    a :ElementDerivation ;
    rdfs:label "Customer token from email derivation" ;
    prov:entity ex:customer-email-v17 ;
    prov:hadActivity ex:email-tokenisation-run-42 ;
    prov:hadUsage ex:email-tokenisation-email-usage ;
    prov:hadGeneration ex:email-tokenisation-token-generation .

ex:email-tokenisation-run-42
    a :TransformationActivity ;
    rdfs:label "Email tokenisation run 42" ;
    :performedProcessing dpv:Pseudonymise ;
    :realizesSpecification ex:email-tokenisation-plan-v3 ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :ElementDependencyLineageScope ;
    :hasLineageCoverageAssessment ex:email-tokenisation-run-42-coverage-assessment ;
    :hasDataOriginKind :SourceDependentOrigin ;
    :appliedMeasure dpv:Pseudonymisation ;
    :measureImplementation ex:hmac-sha256-config-v3 ;
    :hasObservedClassificationImpact ex:email-tokenisation-observed-impact ;
    prov:used ex:customers-v17, ex:customer-email-v17, ex:hmac-sha256-config-v3 ;
    prov:generated ex:customer-tokens-v4, ex:customer-token-v4 ;
    prov:qualifiedUsage ex:email-tokenisation-email-usage ;
    prov:qualifiedAssociation ex:email-tokenisation-association ;
    prov:startedAtTime "2026-07-10T17:00:00Z"^^xsd:dateTime ;
    prov:endedAtTime "2026-07-10T17:00:03Z"^^xsd:dateTime .

ex:email-tokenisation-email-usage
    a prov:Usage ;
    prov:entity ex:customer-email-v17 ;
    prov:hadRole ex:email-tokenisation-input-v3 .

ex:email-tokenisation-token-generation
    a prov:Generation ;
    prov:activity ex:email-tokenisation-run-42 ;
    prov:hadRole ex:email-tokenisation-output-v3 .

ex:email-tokenisation-association
    a prov:Association ;
    prov:agent ex:relay-worker ;
    prov:hadPlan ex:email-tokenisation-plan-v3 .

# The source and output classifications are separate records. The output is not
# classified as an email address merely because its source was one.

ex:customer-email-classification
    a dcls:DataClassification ;
    rdfs:label "Customer email classification" ;
    dcls:classifiesElement ex:customer-email-v17 ;
    dcls:personalDataCategory pd:EmailAddress ;
    dcls:hasSensitivityLevel dcls:ModerateSensitivity ;
    dcls:hasClassificationStatus dcls:Confirmed ;
    dcls:hasClassificationContext ex:analytics-context ;
    dcls:requiresReview false ;
    dcls:requiresMeasure dpv:Pseudonymisation ;
    :hasMitigationRequirement ex:email-classification-pseudonymisation-requirement .

ex:email-classification-pseudonymisation-requirement
    a :MitigationRequirement ;
    rdfs:label "Email classification pseudonymisation requirement" ;
    :originatingClassification ex:customer-email-classification ;
    :requiredMeasure dpv:Pseudonymisation ;
    :hasRequirementScope :OutputState ;
    :requirementContext ex:analytics-context .

ex:customer-token-classification
    a dcls:DataClassification ;
    rdfs:label "Customer token classification" ;
    dcls:classifiesElement ex:customer-token-v4 ;
    dcls:personalDataCategory dpv:PseudonymisedData ;
    dcls:hasSensitivityLevel dcls:ModerateSensitivity ;
    dcls:wasClassifiedBy ex:email-classification-propagation ;
    dcls:hasClassificationStatus dcls:Confirmed ;
    dcls:hasClassificationContext ex:analytics-context ;
    dcls:requiresReview false ;
    dcls:requiresMeasure dpv:AccessControlMethod ;
    prov:wasDerivedFrom ex:customer-email-classification .

ex:email-classification-propagation
    a :ClassificationPropagationActivity ;
    rdfs:label "Email token classification propagation" ;
    dcls:hasDetectionMethod dcls:LineageInheritedDetection ;
    prov:used
        ex:customer-email-classification,
        ex:email-tokenisation-run-42,
        ex:email-tokenisation-expected-impact-v3 ;
    prov:generated
        ex:customer-token-classification,
        ex:email-tokenisation-observed-impact ;
    prov:wasAssociatedWith ex:privacy-validator .

ex:email-tokenisation-observed-impact
    a :ObservedClassificationImpact ;
    rdfs:label "Observed email tokenisation impact" ;
    :sourcePort ex:email-tokenisation-input-v3 ;
    :resultPort ex:email-tokenisation-output-v3 ;
    :sourceClassification ex:customer-email-classification ;
    :resultClassification ex:customer-token-classification ;
    :sourceCategory pd:EmailAddress ;
    :resultCategory dpv:PseudonymisedData ;
    :hasImpactKind :CategoryReplacement ;
    :requiresImpactReview false ;
    :impactRationale "The generated HMAC token conforms to the configured pseudonymised-output contract and remains personal data." ;
    prov:wasGeneratedBy ex:email-classification-propagation .

ex:tokenisation-mitigation-review
    a prov:Activity ;
    rdfs:label "Tokenisation mitigation review" ;
    prov:used ex:email-tokenisation-run-42, ex:hmac-sha256-config-v3 ;
    prov:generated ex:tokenisation-mitigation-assessment ;
    prov:wasAssociatedWith ex:privacy-analyst .

ex:tokenisation-mitigation-assessment
    a :MitigationAssessment ;
    rdfs:label "Tokenisation mitigation assessment" ;
    :assessesResource ex:email-tokenisation-run-42 ;
    :assessesActivity ex:email-tokenisation-run-42 ;
    :assessesRequirement ex:email-classification-pseudonymisation-requirement ;
    :assessedMeasure dpv:Pseudonymisation ;
    :hasAssessmentOutcome :Satisfied ;
    :assessmentRationale "The reviewed run used the approved HMAC configuration and produced the expected pseudonymous output representation. Key custody is assessed separately." ;
    prov:used ex:email-tokenisation-run-42, ex:hmac-sha256-config-v3 ;
    prov:wasGeneratedBy ex:tokenisation-mitigation-review .

ex:tokenisation-conformance-validation
    a prov:Activity ;
    rdfs:label "Tokenisation conformance validation" ;
    prov:used ex:email-tokenisation-run-42, ex:pseudonymise-identifier-specification ;
    prov:generated ex:tokenisation-conformance-assessment ;
    prov:wasAssociatedWith ex:privacy-validator .

ex:tokenisation-conformance-assessment
    a :ProcessingConformanceAssessment ;
    rdfs:label "Tokenisation conformance assessment" ;
    :assessesResource ex:email-tokenisation-run-42 ;
    :assessesActivity ex:email-tokenisation-run-42 ;
    :againstSpecification ex:pseudonymise-identifier-specification ;
    :hasAssessmentOutcome :Conformant ;
    :assessmentRationale "The bound input has a confirmed email classification and the generated output has a reviewed pseudonymised-data classification." ;
    prov:used
        ex:customer-email-classification,
        ex:customer-token-classification,
        ex:customer-email-structural-assessment,
        ex:email-tokenisation-run-42 ;
    prov:wasGeneratedBy ex:tokenisation-conformance-validation .

ex:customer-email-structural-validation
    a prov:Activity ;
    rdfs:label "Customer email structural validation" ;
    prov:used ex:customer-email-v17, ex:structural-shapes-v1 ;
    prov:generated ex:customer-email-validation-report, ex:customer-email-structural-assessment ;
    prov:wasAssociatedWith ex:privacy-validator .

ex:customer-email-validation-report
    a sh:ValidationReport ;
    sh:conforms true .

ex:customer-email-structural-assessment
    a :StructuralConformanceAssessment ;
    :assessesResource ex:customer-email-v17 ;
    :validatedResource ex:customer-email-v17 ;
    :validationShape ex:StringElementShape ;
    :validationShapesGraph ex:structural-shapes-v1 ;
    :validationReport ex:customer-email-validation-report ;
    :validationContext ex:analytics-context ;
    :hasAssessmentOutcome :Conformant ;
    :assessmentRationale "The exact input element conforms to StringElementShape under structural shapes graph version 1 in the analytics context." ;
    prov:used ex:customer-email-validation-report, ex:structural-shapes-v1 ;
    prov:wasGeneratedBy ex:customer-email-structural-validation .

ex:analytics-to-export-compatibility-review
    a prov:Activity ;
    rdfs:label "Analytics-to-export context compatibility review" ;
    prov:used ex:analytics-context, ex:export-context ;
    prov:generated ex:analytics-to-export-compatibility .

ex:analytics-to-export-compatibility
    a :ContextCompatibilityAssessment ;
    :assessesResource ex:analytics-context, ex:export-context ;
    :sourceContext ex:analytics-context ;
    :targetContext ex:export-context ;
    :assessesContextFacet :ControllerContextFacet, :PurposeContextFacet ;
    :hasAssessmentOutcome :Compatible ;
    :assessmentRationale "The approved export is controlled by the same accountable party and is limited to the reviewed analytics purpose; jurisdiction and legal-basis compatibility are outside this assessment." ;
    prov:used ex:analytics-context, ex:export-context ;
    prov:wasGeneratedBy ex:analytics-to-export-compatibility-review .

# 3. Identity copy: category preservation

ex:identity-copy-specification
    a :ProcessingSpecification ;
    rdfs:label "Identity copy specification" ;
    :declaresProcessing dpv:Copy ;
    :hasInputPort ex:copy-input ;
    :hasOutputPort ex:copy-output ;
    :hasExpectedClassificationImpact ex:copy-preservation-impact .

ex:copy-input a :DataInputPort ; rdfs:label "copy input" .
ex:copy-output a :OutputPort ; rdfs:label "copy output" .

ex:copy-preservation-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "copy category preservation" ;
    :sourcePort ex:copy-input ;
    :resultPort ex:copy-output ;
    :hasImpactKind :CategoryPreservation ;
    :requiresImpactReview false ;
    :impactRationale "A byte-preserving copy retains all applicable source classifications and requirements." .

ex:customer-email-copy-v1
    a :DataElementVersion ;
    rdfs:label "Customer email copy version 1" ;
    dcls:sourceAsset ex:customer-export-v1 ;
    dcls:path "email" ;
    :wasDirectlyDerivedFromElement ex:customer-email-v17 .

ex:customer-export-v1 a :DataSnapshot ; rdfs:label "Customer export version 1" .

ex:identity-copy-run
    a :TransformationActivity ;
    rdfs:label "Identity copy run" ;
    :performedProcessing dpv:Copy ;
    :realizesSpecification ex:identity-copy-specification ;
    :processingContext ex:export-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :ElementDependencyLineageScope ;
    :hasLineageCoverageAssessment ex:identity-copy-run-coverage-assessment ;
    prov:used ex:customer-email-v17 ;
    prov:generated ex:customer-export-v1, ex:customer-email-copy-v1 .

# 4. Encryption: classification preservation plus a protection state

ex:encrypted-export-specification
    a :ProcessingSpecification ;
    rdfs:label "Encrypted export specification" ;
    :declaresProcessing dpv:Transform ;
    :hasInputPort ex:encryption-input ;
    :hasOutputPort ex:encryption-output ;
    :hasExpectedClassificationImpact ex:encryption-preservation-impact ;
    :declaresMitigationRequirement ex:encrypted-output-requirement .

ex:encryption-input a :DataInputPort ; rdfs:label "encryption input" .
ex:encryption-output a :OutputPort ; rdfs:label "encrypted output" .

ex:encryption-preservation-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "encryption category preservation" ;
    :sourcePort ex:encryption-input ;
    :resultPort ex:encryption-output ;
    :hasImpactKind :CategoryPreservation ;
    :requiresImpactReview false ;
    :impactRationale "Encryption changes exposure and representation but does not remove the underlying data classification." .

ex:encrypted-output-requirement
    a :MitigationRequirement ;
    rdfs:label "encrypted output requirement" ;
    :requiredMeasure dpv:Encryption ;
    :hasRequirementScope :OutputState .

ex:customer-export-encrypted-v1 a :DataSnapshot ; rdfs:label "Encrypted customer export version 1" .

ex:encryption-run
    a :TransformationActivity ;
    rdfs:label "Customer export encryption run" ;
    :performedProcessing dpv:Transform ;
    :realizesSpecification ex:encrypted-export-specification ;
    :processingContext ex:export-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :ElementDependencyLineageScope ;
    :hasLineageCoverageAssessment ex:encryption-run-coverage-assessment ;
    :appliedMeasure dpv:Encryption ;
    :measureImplementation ex:aes-gcm-config-v2 ;
    prov:used ex:customer-export-v1, ex:aes-gcm-config-v2 ;
    prov:generated ex:customer-export-encrypted-v1 .

# 5. Aggregation: the output is a reviewable derived classification

ex:city-aggregation-specification
    a :ProcessingSpecification ;
    rdfs:label "City aggregation specification" ;
    :declaresProcessing dpv:Aggregate ;
    :hasInputPort ex:city-aggregation-input ;
    :hasOutputPort ex:city-count-output ;
    :hasExpectedClassificationImpact ex:aggregation-unknown-impact .

ex:city-aggregation-input
    a :DataInputPort ;
    rdfs:label "city personal-data input" ;
    :hasClassificationConstraint ex:confirmed-city-constraint .

ex:confirmed-city-constraint
    a :ClassificationConstraint ;
    rdfs:label "confirmed city constraint" ;
    :requiresDataCategory pd:City ;
    :requiresClassificationStatus dcls:Confirmed .

ex:city-count-output a :OutputPort ; rdfs:label "city count output" .

ex:aggregation-unknown-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "aggregation impact requiring review" ;
    :sourcePort ex:city-aggregation-input ;
    :resultPort ex:city-count-output ;
    :sourceCategory pd:City ;
    :hasImpactKind :UnknownImpact ;
    :requiresImpactReview true ;
    :impactRationale "Aggregation can reduce identifiability, but group size, sparsity, auxiliary data, and release context determine the result classification." .

ex:customer-city-v17
    a :DataElementVersion ;
    rdfs:label "Customer city element version 17" ;
    dcls:sourceAsset ex:customers-v17 ;
    dcls:path "customers.city" .

ex:customer-city-classification
    a dcls:DataClassification ;
    rdfs:label "Customer city classification" ;
    dcls:classifiesElement ex:customer-city-v17 ;
    dcls:personalDataCategory pd:City ;
    dcls:hasSensitivityLevel dcls:ModerateSensitivity ;
    dcls:hasClassificationStatus dcls:Confirmed ;
    dcls:hasClassificationContext ex:analytics-context ;
    dcls:requiresReview false .

ex:city-counts-v1 a :DataSnapshot ; rdfs:label "City counts version 1" .

ex:city-count-v1
    a :DataElementVersion ;
    rdfs:label "City count element version 1" ;
    dcls:sourceAsset ex:city-counts-v1 ;
    dcls:path "city_counts.count" ;
    :wasDirectlyDerivedFromElement ex:customer-city-v17 .

ex:city-aggregation-run
    a :TransformationActivity ;
    rdfs:label "City aggregation run" ;
    :performedProcessing dpv:Aggregate ;
    :realizesSpecification ex:city-aggregation-specification ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :ElementDependencyLineageScope ;
    :hasLineageCoverageAssessment ex:city-aggregation-run-coverage-assessment ;
    prov:used ex:customers-v17, ex:customer-city-v17 ;
    prov:generated ex:city-counts-v1, ex:city-count-v1 .

ex:city-count-candidate-classification
    a dcls:DataClassification ;
    rdfs:label "City count candidate classification" ;
    dcls:classifiesElement ex:city-count-v1 ;
    dcls:candidateDataCategory dpv:DerivedData ;
    dcls:hasSensitivityLevel dcls:ModerateSensitivity ;
    dcls:hasClassificationStatus dcls:Candidate ;
    dcls:hasClassificationContext ex:analytics-context ;
    dcls:requiresReview true ;
    prov:wasDerivedFrom ex:customer-city-classification .

# 6. Join and indirect influence can introduce linkability and sensitivity

ex:profile-join-specification
    a :ProcessingSpecification ;
    rdfs:label "Profile join specification" ;
    :declaresProcessing dpv:Combine ;
    :hasInputPort ex:profile-join-left, ex:profile-join-key, ex:profile-join-right ;
    :hasOutputPort ex:profile-join-output ;
    :hasExpectedClassificationImpact ex:profile-introduction-impact, ex:profile-sensitivity-impact .

ex:profile-join-left a :DataInputPort ; rdfs:label "profile join left input" .
ex:profile-join-key a :DataInputPort ; rdfs:label "profile join key control input" .
ex:profile-join-right a :DataInputPort ; rdfs:label "profile join right input" .
ex:profile-join-output a :OutputPort ; rdfs:label "joined profile output" .

ex:profile-introduction-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "profile category introduction impact" ;
    :sourcePort ex:profile-join-left, ex:profile-join-right ;
    :resultPort ex:profile-join-output ;
    :resultCategory dpv:DerivedPersonalData ;
    :hasImpactKind :CategoryIntroduction ;
    :requiresImpactReview true ;
    :impactRationale "Combining independently classified datasets can create a derived personal profile not present as a category on either input alone." .

ex:profile-sensitivity-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "profile sensitivity escalation impact" ;
    :sourcePort ex:profile-join-key ;
    :resultPort ex:profile-join-output ;
    :sourceSensitivity dcls:ModerateSensitivity ;
    :resultSensitivity dcls:HighSensitivity ;
    :hasImpactKind :SensitivityEscalation ;
    :requiresImpactReview true ;
    :impactRationale "An identifier used only as a join key can increase linkability even when the key is not projected into the output." .

ex:purchases-v8 a :DataSnapshot ; rdfs:label "Purchases snapshot version 8" .
ex:profiles-v2 a :DataSnapshot ; rdfs:label "Joined profiles version 2" .

ex:purchase-account-id-v8
    a :DataElementVersion ;
    rdfs:label "Purchase account identifier version 8" ;
    dcls:sourceAsset ex:purchases-v8 ;
    dcls:path "purchases.account_id" .

ex:purchase-behaviour-v8
    a :DataElementVersion ;
    rdfs:label "Purchase browsing behaviour version 8" ;
    dcls:sourceAsset ex:purchases-v8 ;
    dcls:path "purchases.browsing_behaviour" .

ex:profile-behaviour-v2
    a :DataElementVersion ;
    rdfs:label "Joined profile behaviour version 2" ;
    dcls:sourceAsset ex:profiles-v2 ;
    dcls:path "profiles.behaviour" ;
    :wasDirectlyDerivedFromElement ex:purchase-behaviour-v8 ;
    :wasIndirectlyInfluencedByElement ex:customer-token-v4, ex:purchase-account-id-v8 ;
    :qualifiedElementInfluence ex:profile-token-join-influence .

ex:profile-token-join-influence
    a :ElementInfluence ;
    rdfs:label "Profile token join influence" ;
    prov:entity ex:customer-token-v4 ;
    prov:hadActivity ex:profile-join-run .

ex:profile-join-run
    a :TransformationActivity ;
    rdfs:label "Profile join run" ;
    :performedProcessing dpv:Combine ;
    :realizesSpecification ex:profile-join-specification ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :ElementDependencyLineageScope ;
    :hasLineageCoverageAssessment ex:profile-join-run-coverage-assessment ;
    prov:used
        ex:customer-tokens-v4,
        ex:purchases-v8,
        ex:customer-token-v4,
        ex:purchase-account-id-v8,
        ex:purchase-behaviour-v8 ;
    prov:generated ex:profiles-v2, ex:profile-behaviour-v2 .

# 7. Element elision: absence is declared by a complete plan, not guessed from
# a missing triple. The redacted output has no corresponding note element.

ex:note-elision-specification
    a :ProcessingSpecification ;
    rdfs:label "Free-text note elision specification" ;
    :declaresProcessing dpv:Remove ;
    :hasInputPort ex:note-elision-input ;
    :hasOutputPort ex:redacted-dataset-output ;
    :hasExpectedClassificationImpact ex:note-suppression-impact .

ex:note-elision-input a :DataInputPort ; rdfs:label "free-text note input" .
ex:redacted-dataset-output a :OutputPort ; rdfs:label "redacted dataset output" .

ex:note-suppression-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "note category suppression impact" ;
    :sourcePort ex:note-elision-input ;
    :sourceCategory dpv:PersonalData ;
    :hasImpactKind :CategorySuppression ;
    :requiresImpactReview true ;
    :impactRationale "The plan omits the entire note element. A complete mapping is required before absence can support a suppression claim." .

ex:customer-notes-v3 a :DataSnapshot ; rdfs:label "Customer notes snapshot version 3" .
ex:customer-redacted-v1 a :DataSnapshot ; rdfs:label "Redacted customer snapshot version 1" .

ex:customer-note-v3
    a :DataElementVersion ;
    rdfs:label "Customer note element version 3" ;
    dcls:sourceAsset ex:customer-notes-v3 ;
    dcls:path "customers.note" .

ex:note-elision-run
    a :TransformationActivity ;
    rdfs:label "Customer note elision run" ;
    :performedProcessing dpv:Remove ;
    :realizesSpecification ex:note-elision-specification ;
    :processingContext ex:export-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :ElementDependencyLineageScope ;
    :hasLineageCoverageAssessment ex:note-elision-run-coverage-assessment ;
    :appliedMeasure dpv:DataRedaction ;
    prov:used ex:customer-notes-v3, ex:customer-note-v3 ;
    prov:generated ex:customer-redacted-v1 .

# 8. Source-free generation is a positive assertion. It is not inferred merely
# because no prov:used triple was collected.

ex:random-id-specification
    a :ProcessingSpecification ;
    rdfs:label "Random identifier generation specification" ;
    :declaresProcessing dpv:Generate ;
    :hasInputPort ex:random-generator-configuration-input ;
    :hasOutputPort ex:random-id-output .

ex:random-generator-configuration-input
    a :AuxiliaryInputPort ;
    rdfs:label "random generator configuration input" .

ex:random-id-output a :OutputPort ; rdfs:label "random identifier output" .
ex:random-identifiers-v1 a :DataSnapshot ; rdfs:label "Random identifiers version 1" .
ex:random-generator-config-v1 a prov:Entity ; rdfs:label "Random generator configuration version 1" .

ex:random-id-run
    a :ProcessingActivity ;
    rdfs:label "Random identifier generation run" ;
    :performedProcessing dpv:Generate ;
    :realizesSpecification ex:random-id-specification ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :ExecutionBoundaryScope ;
    :hasLineageCoverageAssessment ex:random-id-run-coverage-assessment ;
    :hasDataOriginKind :SourceFreeOrigin ;
    prov:used ex:random-generator-config-v1 ;
    prov:generated ex:random-identifiers-v1 ;
    prov:qualifiedUsage ex:random-generator-configuration-usage .

ex:random-generator-configuration-usage
    a prov:Usage ;
    prov:entity ex:random-generator-config-v1 ;
    prov:hadRole ex:random-generator-configuration-input .

# 9. In-place updates are represented as successive entity versions.

ex:normalisation-specification
    a :ProcessingSpecification ;
    rdfs:label "Email normalisation specification" ;
    :declaresProcessing dpv:Transform ;
    :hasInputPort ex:normalisation-input ;
    :hasOutputPort ex:normalisation-output ;
    :hasExpectedClassificationImpact ex:normalisation-preservation-impact .

ex:normalisation-input a :DataInputPort ; rdfs:label "normalisation input" .
ex:normalisation-output a :OutputPort ; rdfs:label "normalisation output" .

ex:normalisation-preservation-impact
    a :ExpectedClassificationImpact ;
    rdfs:label "normalisation category preservation" ;
    :sourcePort ex:normalisation-input ;
    :resultPort ex:normalisation-output ;
    :hasImpactKind :CategoryPreservation ;
    :requiresImpactReview false ;
    :impactRationale "Case and whitespace normalisation preserve email-address classification." .

ex:customers-v18
    a :DataSnapshot ;
    rdfs:label "Customers table snapshot version 18" ;
    prov:wasRevisionOf ex:customers-v17 ;
    prov:wasGeneratedBy ex:normalisation-run .

ex:normalisation-run
    a :TransformationActivity ;
    rdfs:label "Email normalisation run" ;
    :performedProcessing dpv:Transform ;
    :realizesSpecification ex:normalisation-specification ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :AssetLineageScope ;
    :hasLineageCoverageAssessment ex:normalisation-run-coverage-assessment ;
    prov:used ex:customers-v17 ;
    prov:generated ex:customers-v18 ;
    prov:invalidated ex:customers-v17 .

# 10. Stateful stream processing uses explicit windows as snapshots.

ex:stream-window-specification
    a :ProcessingSpecification ;
    rdfs:label "Five-minute event window specification" ;
    :declaresProcessing dpv:Aggregate ;
    :hasInputPort ex:event-stream-input ;
    :hasOutputPort ex:event-window-output .

ex:event-stream-input a :DataInputPort ; rdfs:label "event stream input" .
ex:event-window-output a :OutputPort ; rdfs:label "event window output" .

ex:event-stream-offsets-1000-1099 a :DataSnapshot ; rdfs:label "Event stream offsets 1000 through 1099" .
ex:event-window-1700 a :DataSnapshot ; rdfs:label "Event window ending at 17:00" .

ex:stream-window-run
    a :TransformationActivity ;
    rdfs:label "Five-minute stream window run" ;
    :performedProcessing dpv:Aggregate ;
    :realizesSpecification ex:stream-window-specification ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :CompleteLineage ;
    :lineageCoverageScope :AssetLineageScope ;
    :hasLineageCoverageAssessment ex:stream-window-run-coverage-assessment ;
    prov:used ex:event-stream-offsets-1000-1099 ;
    prov:generated ex:event-window-1700 .

# 11. Partial cross-organisation lineage identifies the known boundary explicitly.

ex:partner-feed-v5 a :DataSnapshot ; rdfs:label "Partner feed version 5" .
ex:partner-derived-v1 a :DataSnapshot ; rdfs:label "Partner-derived dataset version 1" .

ex:partner-processing-run
    a :ProcessingActivity ;
    rdfs:label "Partner feed processing run" ;
    :performedProcessing dpv:Transform ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :PartialLineage ;
    :lineageCoverageScope :CrossSystemBoundaryScope ;
    :hasLineageCoverageAssessment ex:partner-processing-run-coverage-assessment ;
    prov:used ex:partner-feed-v5 ;
    prov:generated ex:partner-derived-v1 .

# 12. An abstract policy over categories remains normative. The policy query can
# later compare these rules with performedProcessing claims.

ex:restricted-personal-data-export-policy
    a :ProcessingPolicy ;
    rdfs:label "Restricted personal-data export policy" ;
    :appliesToDataCategory dpv:PersonalData ;
    :permitsProcessing dpv:Pseudonymise ;
    :prohibitsProcessing dpv:Transfer ;
    :requiresProcessing dpv:Transform ;
    :policyContext ex:export-context .

# 13. Non-conformance is an assessment, not the absence of conformance.

ex:unreviewed-email-classification
    a dcls:DataClassification ;
    rdfs:label "Unreviewed email candidate" ;
    dcls:classifiesElement ex:customer-email-v17 ;
    dcls:candidatePersonalDataCategory pd:EmailAddress ;
    dcls:hasSensitivityLevel dcls:ModerateSensitivity ;
    dcls:hasClassificationStatus dcls:Candidate ;
    dcls:hasClassificationContext ex:analytics-context ;
    dcls:requiresReview true .

ex:nonconforming-tokenisation-run
    a :TransformationActivity ;
    rdfs:label "Non-conforming tokenisation run" ;
    :performedProcessing dpv:Pseudonymise ;
    :realizesSpecification ex:pseudonymise-identifier-specification ;
    :processingContext ex:analytics-context ;
    :hasLineageCoverage :PartialLineage ;
    :lineageCoverageScope :ElementDependencyLineageScope ;
    :hasLineageCoverageAssessment ex:nonconforming-tokenisation-run-coverage-assessment ;
    prov:used ex:customer-email-v17 ;
    prov:generated ex:nonconforming-token-output .

ex:nonconforming-token-output a :DataSnapshot ; rdfs:label "Non-conforming token output" .

ex:nonconformance-validation
    a prov:Activity ;
    rdfs:label "Non-conformance validation activity" ;
    prov:used ex:nonconforming-tokenisation-run, ex:unreviewed-email-classification ;
    prov:generated ex:nonconformance-assessment ;
    prov:wasAssociatedWith ex:privacy-validator .

ex:nonconformance-assessment
    a :ProcessingConformanceAssessment ;
    rdfs:label "Non-conformance assessment" ;
    :assessesResource ex:nonconforming-tokenisation-run ;
    :assessesActivity ex:nonconforming-tokenisation-run ;
    :againstSpecification ex:pseudonymise-identifier-specification ;
    :hasAssessmentOutcome :NonConformant ;
    :assessmentRationale "The only available email classification is a candidate, while the specification requires a confirmed classification." ;
    prov:used ex:nonconforming-tokenisation-run, ex:unreviewed-email-classification ;
    prov:wasGeneratedBy ex:nonconformance-validation .

# 14. Coverage summaries are projections from evidence-bearing assessments.

ex:lineage-coverage-evaluation
    a prov:Activity ;
    rdfs:label "Lineage coverage evaluation" ;
    prov:wasAssociatedWith ex:privacy-validator ;
    prov:generated
        ex:email-tokenisation-run-42-coverage-assessment,
        ex:identity-copy-run-coverage-assessment,
        ex:encryption-run-coverage-assessment,
        ex:city-aggregation-run-coverage-assessment,
        ex:profile-join-run-coverage-assessment,
        ex:note-elision-run-coverage-assessment,
        ex:random-id-run-coverage-assessment,
        ex:normalisation-run-coverage-assessment,
        ex:stream-window-run-coverage-assessment,
        ex:partner-processing-run-coverage-assessment,
        ex:nonconforming-tokenisation-run-coverage-assessment .

ex:email-tokenisation-run-42-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:email-tokenisation-run-42 ;
    :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :ElementDependencyLineageScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:email-tokenisation-run-42 ;
    :coverageStartTime "2026-07-10T17:00:00Z"^^xsd:dateTime ;
    :coverageEndTime "2026-07-10T17:00:03Z"^^xsd:dateTime ;
    :assessmentRationale "The recorder captured all direct and indirect element dependencies within run 42." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:identity-copy-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:identity-copy-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :ElementDependencyLineageScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:identity-copy-run ;
    :assessmentRationale "The copy has one represented source element and one represented result element." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:encryption-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:encryption-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :ElementDependencyLineageScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:encryption-run ;
    :assessmentRationale "All input, configuration, and output dependencies within the encryption execution are recorded." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:city-aggregation-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:city-aggregation-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :ElementDependencyLineageScope, :DirectDependencyScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:city-aggregation-run ;
    :assessmentRationale "Every value-contributing source element in the bounded aggregation execution is represented." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:profile-join-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:profile-join-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :ElementDependencyLineageScope, :DirectDependencyScope, :IndirectDependencyScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:profile-join-run ;
    :assessmentRationale "The join assessment covers both value derivations and indirect join-key influences." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:note-elision-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:note-elision-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :ElementDependencyLineageScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:note-elision-run ;
    :assessmentRationale "The bounded mapping records the removed input and the complete output projection." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:random-id-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:random-id-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :ExecutionBoundaryScope ;
    :coverageBoundary ex:random-id-run ;
    :assessmentRationale "The run is positively source-free and all auxiliary dependencies are qualified." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:normalisation-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:normalisation-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :AssetLineageScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:normalisation-run ;
    :assessmentRationale "The old and new snapshot versions and the invalidation boundary are represented." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:stream-window-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:stream-window-run ; :assessedLineageCoverage :CompleteLineage ;
    :assessedLineageScope :AssetLineageScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:stream-window-run, ex:event-stream-offsets-1000-1099 ;
    :assessmentRationale "The assessment is complete only for the explicitly identified stream-offset window." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:partner-processing-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:partner-processing-run ; :assessedLineageCoverage :PartialLineage ;
    :assessedLineageScope :AssetLineageScope, :CrossSystemBoundaryScope ;
    :coverageBoundary ex:partner-processing-run, ex:partner-feed-v5 ;
    :assessmentRationale "The local consumer boundary is recorded, but upstream partner transformations are unavailable." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .

ex:nonconforming-tokenisation-run-coverage-assessment
    a :LineageCoverageAssessment ;
    :assessesResource ex:nonconforming-tokenisation-run ; :assessedLineageCoverage :PartialLineage ;
    :assessedLineageScope :ElementDependencyLineageScope, :ExecutionBoundaryScope ;
    :coverageBoundary ex:nonconforming-tokenisation-run ;
    :assessmentRationale "The run lacks a complete qualified port and element mapping." ;
    prov:wasGeneratedBy ex:lineage-coverage-evaluation .
