diff --git a/adalbert-contracts/README.md b/adalbert-contracts/README.md new file mode 100644 index 0000000..e17e87d --- /dev/null +++ b/adalbert-contracts/README.md @@ -0,0 +1,177 @@ +# Adalbert: Data Contracts and Data-Use Policies + +**An ODRL 2.2 profile with deterministic evaluation semantics for data governance.** + +Adalbert provides a formal, standards-based approach to data contracts and data-use policies. It uses ODRL 2.2 terms for all standard constructs and adds only genuinely new extensions for lifecycle management, bilateral duties, recurrence, and structured operand resolution. + +--- + +## What This Is + +This folder contains the Adalbert data governance framework as a contribution to EKGF/dprod, complementing the `dprod-contracts` approach with: + +1. **Data contracts** — bilateral agreements between data providers and consumers (like dprod-contracts) +2. **Data-use policies** — organizational access controls, purpose restrictions, classification rules (unique to Adalbert) +3. **Formal semantics** — normative evaluation specification amenable to formal verification +4. **ODRL 2.2 compliance** — every Adalbert policy is a valid ODRL 2.2 policy + +## Key Differentiators + +| Capability | dprod-contracts | Adalbert | +|---|---|---| +| Data contracts | Yes | Yes | +| Data-use policies | No | **Yes** | +| Formal semantics | No | **Yes** (normative, verification-ready) | +| Bilateral duties | Via Promise subclass | **Via standard odrl:Duty** | +| Lifecycle states | Status enum | **Formal state machine** (Pending → Active → Fulfilled/Violated) | +| Recurrence | ICalSchedule class | **RRULE property** + deadline window | +| Operand resolution | Unspecified | **Structured paths** (agent.role, asset.classification, context.purpose) | +| Constraint vocabulary | Minimal | **33 operands** across 15 categories | +| ODRL compatibility | Custom Rule subclass | **Standard Rule types** (Permission, Duty, Prohibition) | + +--- + +## Folder Contents + +``` +adalbert-contracts/ +├── README.md # This file +├── adalbert-core.ttl # OWL ontology (ODRL profile extension) +├── adalbert-shacl.ttl # SHACL validation shapes +├── adalbert-prof.ttl # W3C DXPROF profile metadata +├── adalbert-due.ttl # Data use vocabulary (operands, actions, values) +├── examples/ +│ ├── data-contract.ttl # Bilateral contract with provider SLA + consumer duties +│ ├── data-use-policy.ttl # Role-based access control with purpose constraints +│ ├── dprod-translated.ttl # dprod-contracts examples translated to Adalbert +│ └── baseline.ttl # Comprehensive test suite (8 contracts, 2 subscriptions) +└── docs/ + ├── DATA-CONTRACTS.md # Data contract authoring guide + ├── DATA-USE-POLICIES.md # Data-use policy authoring guide + ├── SPECIFICATION.md # Technical vocabulary reference + ├── SEMANTICS.md # Formal evaluation semantics (normative) + └── DPROD-Adalbert-Comparison.md # Comparison with dprod-contracts +``` + +--- + +## Quick Start + +### Data Contract (bilateral agreement) + +```turtle +@prefix odrl: . +@prefix adalbert: . +@prefix adalbert-due: . +@prefix xsd: . + +ex:contract a adalbert:DataContract ; + odrl:profile , + ; + odrl:assigner ex:dataTeam ; + odrl:target ex:marketPrices ; + adalbert:state adalbert:Active ; + + # Provider SLA: daily delivery with 30-min window + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:deliver ; + adalbert:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; + adalbert:deadline "PT30M"^^xsd:duration + ] ; + + # Consumer: may read + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read + ] ; + + # Consumer: must report monthly + odrl:obligation [ + a odrl:Duty ; + odrl:action adalbert-due:report ; + adalbert:deadline "P30D"^^xsd:duration + ] ; + + # No redistribution + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:distribute + ] . +``` + +### Data-Use Policy (organizational access control) + +```turtle +ex:policy a odrl:Set ; + odrl:profile , + ; + odrl:target ex:employeeData ; + + # HR Analytics: read for analytics purpose only + odrl:permission [ + a odrl:Permission ; + odrl:assignee ex:hrAnalytics ; + odrl:action odrl:read ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:analytics + ] + ] ; + + # No ML training on employee data + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:use ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:processingMode ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:modelTraining + ] + ] . +``` + +--- + +## Relationship to dprod-contracts + +Adalbert and dprod-contracts are complementary approaches to the same problem space. See `docs/DPROD-Adalbert-Comparison.md` for a detailed comparison and `examples/dprod-translated.ttl` for all 8 dprod-contracts examples expressed in Adalbert. + +Key architectural difference: dprod-contracts introduces `dprod:Promise` as a new `odrl:Rule` subclass. Adalbert uses standard `odrl:Duty`, `odrl:Permission`, and `odrl:Prohibition`, making every Adalbert policy a valid ODRL 2.2 policy that any ODRL processor can partially understand. + +--- + +## SHACL Validation + +Validate any Adalbert policy against the SHACL shapes: + +```bash +shacl validate --shapes adalbert-shacl.ttl --data examples/data-contract.ttl +``` + +--- + +## Namespaces + +| Prefix | Namespace | Role | +|--------|-----------|------| +| `odrl:` | `http://www.w3.org/ns/odrl/2/` | Primary — all standard ODRL constructs | +| `adalbert:` | `https://vocabulary.bigbank/adalbert/` | Extensions only (State, deadline, recurrence, DataContract, Subscription) | +| `adalbert-due:` | `https://vocabulary.bigbank/adalbert/due/` | Data use vocabulary (operands, actions, concept values) | + +--- + +## References + +- [ODRL 2.2 Information Model](https://www.w3.org/TR/odrl-model/) +- [W3C ODRL Profile Best Practices](https://www.w3.org/community/reports/odrl/CG-FINAL-profile-bp-20240808.html) +- [W3C Market Data Profile](https://www.w3.org/2021/md-odrl-profile/v1/) +- [EKGF DPROD](https://ekgf.github.io/dprod/) + +--- + +**Version**: 0.7 | **Date**: 2026-02-16 diff --git a/adalbert-contracts/adalbert-core.ttl b/adalbert-contracts/adalbert-core.ttl new file mode 100644 index 0000000..ed1a3a2 --- /dev/null +++ b/adalbert-contracts/adalbert-core.ttl @@ -0,0 +1,306 @@ +# ============================================================================= +# Adalbert Core Ontology — ODRL 2.2 Profile Extension +# Version: 0.7 +# Date: 2026-02-04 +# ============================================================================= +# Adalbert is a proper ODRL 2.2 profile. It uses ODRL terms wherever ODRL +# defines them and only adds terms for genuinely new capabilities: +# +# - Duty lifecycle (State + state property) +# - Deadline on duties +# - Recurrence on duties (RFC 5545 RRULE) +# - Duty party roles (subject, object) +# - Data contracts and subscriptions (domain subtypes) +# - Asset/party hierarchy (partOf, memberOf) +# - Structured operand resolution (resolutionPath) +# - Runtime references (RuntimeReference, currentAgent, currentDateTime) +# - Logical negation (not) +# +# Everything else — Rule types, Policy types, Constraint types, operators, +# core properties — comes from ODRL 2.2 directly. +# ============================================================================= + +@prefix adalbert: . +@prefix rl2: . +@prefix odrl: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix dcterms: . +@prefix skos: . + +# ============================================================================= +# ONTOLOGY DECLARATION +# ============================================================================= + + a owl:Ontology ; + dcterms:title "Adalbert Core Ontology"@en ; + dcterms:description """ + Adalbert: a proper ODRL 2.2 profile for deterministic data governance. + + Uses ODRL terms for all standard constructs (Rule types, Policy types, + Constraint types, operators, core properties). Adds only: + - Explicit duty lifecycle (Pending -> Active -> Fulfilled/Violated) + - Deadline property on odrl:Duty + - Recurrence property on odrl:Duty (RFC 5545 RRULE) + - Duty party roles (subject = duty bearer, object = affected party) + - Data contract and subscription subtypes + - Asset/party hierarchy properties + - Structured operand resolution via resolutionPath + - Runtime references for dynamic right-operand values + - Logical negation (not) on LogicalConstraint + + Adalbert policies are valid ODRL 2.2 policies. Any ODRL processor can + partially understand them; Adalbert-aware processors handle the + extensions. + """@en ; + dcterms:created "2026-02-02"^^xsd:date ; + dcterms:modified "2026-02-06"^^xsd:date ; + owl:versionInfo "0.7" ; + owl:versionIRI ; + rdfs:seeAlso , + . + +# ============================================================================= +# PROFILE CONSTRAINTS (documentation of ODRL usage) +# ============================================================================= +# Adalbert fixes odrl:conflict to odrl:prohibit (declared at profile level in adalbert-prof.ttl). +# Adalbert does not use: odrl:Ticket, odrl:Request, odrl:AssetCollection, +# odrl:PartyCollection, odrl:remedy, odrl:consequence, odrl:xone. + +# ============================================================================= +# LIFECYCLE STATE (genuinely new) +# ============================================================================= + +adalbert:State a owl:Class ; + rdfs:label "State"@en ; + skos:definition "Unified lifecycle state for duties and contracts."@en ; + owl:oneOf (adalbert:Pending adalbert:Active adalbert:Fulfilled adalbert:Violated) ; + rdfs:comment """ + Four states cover both duty and contract lifecycles. + + On duties (evaluated during Eval): + - Pending: condition not yet satisfied + - Active: condition met, action required + - Fulfilled: action performed + - Violated: deadline passed without performance + + On contracts/subscriptions (administrative, not evaluated at request time): + - Pending: not yet in force + - Active: in force + - Fulfilled: obligations complete + - Violated: breached + + Unified by design: the same four transitions apply to both domains, + and a single state property avoids proliferating parallel enums. + The formal semantics (§4.1) tracks only duty state during evaluation; + contract state is managed by external administrative processes. + """@en . + +adalbert:Pending a adalbert:State ; + rdfs:label "Pending"@en ; + skos:definition "Condition not yet satisfied; not yet in force."@en . + +adalbert:Active a adalbert:State ; + rdfs:label "Active"@en ; + skos:definition "Condition satisfied, action required; in force."@en . + +adalbert:Fulfilled a adalbert:State ; + rdfs:label "Fulfilled"@en ; + skos:definition "Action performed; obligations complete."@en . + +adalbert:Violated a adalbert:State ; + rdfs:label "Violated"@en ; + skos:definition "Deadline passed without performance; breached."@en . + +adalbert:state a owl:ObjectProperty ; + rdfs:label "state"@en ; + skos:definition "Current lifecycle state."@en ; + rdfs:domain [ owl:unionOf (odrl:Duty adalbert:DataContract adalbert:Subscription) ] ; + rdfs:range adalbert:State . + +# ============================================================================= +# DEADLINE (genuinely new) +# ============================================================================= + +adalbert:deadline a owl:DatatypeProperty ; + rdfs:label "deadline"@en ; + skos:definition "Time constraint for duty fulfillment."@en ; + rdfs:domain odrl:Duty ; + rdfs:comment """ + Supports multiple forms: + - xsd:dateTime: absolute deadline (e.g., 2026-12-31T23:59:59Z) + - xsd:duration: relative to activation (e.g., P30D, PT24H) + + For duration: deadline = activationTime + duration. + No rdfs:range declared because the range is a union of datatypes; + SHACL enforces the allowed types. + """@en . + +# ============================================================================= +# RECURRENCE (genuinely new) +# ============================================================================= + +adalbert:recurrence a owl:DatatypeProperty ; + rdfs:label "recurrence"@en ; + skos:definition "RFC 5545 RRULE defining when duty instances are generated."@en ; + rdfs:domain odrl:Duty ; + rdfs:range xsd:string ; + rdfs:comment """ + An RFC 5545 RRULE string (e.g., FREQ=DAILY;BYHOUR=6;BYMINUTE=0). + Defines the schedule on which duty instances are created. + Each instance follows the standard duty lifecycle independently. + The deadline property defines the per-instance fulfillment window. + Any iCal-compliant library can parse the value. + """@en . + +# ============================================================================= +# CONTRACT CLASSES (genuinely new — domain subtypes) +# ============================================================================= + +adalbert:DataContract a owl:Class ; + rdfs:label "Data Contract"@en ; + skos:definition "Policy offer defining data access terms."@en ; + rdfs:subClassOf odrl:Offer ; + rdfs:comment """ + A DataContract is an Offer from a data provider. + It defines permissions, duties, and prohibitions for data access. + + When accepted (subscribed), becomes a Subscription (Agreement). + """@en . + +adalbert:Subscription a owl:Class ; + rdfs:label "Subscription"@en ; + skos:definition "Activated data contract binding provider and consumer."@en ; + rdfs:subClassOf odrl:Agreement ; + owl:disjointWith adalbert:DataContract ; + rdfs:comment """ + A Subscription is an activated DataContract. + - assigner: data provider (from contract) + - assignee: data consumer (subscriber) + + Bilateral: both parties may have active duties. + """@en . + +# ============================================================================= +# CONTRACT PROPERTIES (genuinely new) +# ============================================================================= + +adalbert:subscribesTo a owl:ObjectProperty ; + rdfs:label "subscribes to"@en ; + skos:definition "The contract this subscription activates."@en ; + rdfs:domain adalbert:Subscription ; + rdfs:range adalbert:DataContract . + +adalbert:effectiveDate a owl:DatatypeProperty ; + rdfs:label "effective date"@en ; + skos:definition "When the contract/subscription becomes effective."@en ; + rdfs:domain [ owl:unionOf (adalbert:DataContract adalbert:Subscription) ] ; + rdfs:range xsd:dateTime . + +adalbert:expirationDate a owl:DatatypeProperty ; + rdfs:label "expiration date"@en ; + skos:definition "When the contract/subscription expires."@en ; + rdfs:domain [ owl:unionOf (adalbert:DataContract adalbert:Subscription) ] ; + rdfs:range xsd:dateTime . + +# ============================================================================= +# HIERARCHY EXTENSIONS (genuinely new) +# ============================================================================= + +adalbert:partOf a owl:ObjectProperty, owl:TransitiveProperty ; + rdfs:label "part of"@en ; + skos:definition "Asset contained in larger asset."@en ; + rdfs:subPropertyOf odrl:partOf ; + rdfs:domain odrl:Asset ; + rdfs:range odrl:Asset ; + rdfs:comment "Transitive asset hierarchy. Bridges to ODRL via rdfs:subPropertyOf odrl:partOf — ODRL processors with RDFS reasoning can interpret Adalbert hierarchies. Transitivity and typed domains are Adalbert additions not inherited by the base property."@en . + +adalbert:memberOf a owl:ObjectProperty, owl:TransitiveProperty ; + rdfs:label "member of"@en ; + skos:definition "Party member of group/organization."@en ; + rdfs:subPropertyOf odrl:partOf ; + rdfs:domain odrl:Party ; + rdfs:range odrl:Party ; + rdfs:comment "Transitive party hierarchy. Bridges to ODRL via rdfs:subPropertyOf odrl:partOf — ODRL processors with RDFS reasoning can interpret Adalbert hierarchies. Transitivity and typed domains are Adalbert additions not inherited by the base property."@en . + +# ============================================================================= +# DUTY PARTY ROLES (genuinely new) +# ============================================================================= + +adalbert:subject a owl:ObjectProperty ; + rdfs:label "subject"@en ; + skos:definition "Party bearing the duty (must perform the action)."@en ; + rdfs:subPropertyOf odrl:assignee ; + rdfs:domain odrl:Duty ; + rdfs:range odrl:Party ; + rdfs:comment "The duty bearer. Bridges to ODRL via rdfs:subPropertyOf odrl:assignee (itself a sub-property of odrl:function). Aligns with md:subject (W3C Market Data) and rl2:subject."@en . + +adalbert:object a owl:ObjectProperty ; + rdfs:label "object"@en ; + skos:definition "Party affected by the duty action."@en ; + rdfs:subPropertyOf odrl:function ; + rdfs:domain odrl:Duty ; + rdfs:range odrl:Party ; + rdfs:comment "The party affected by or receiving the result of the duty action (e.g., who is notified, who receives the report). Bridges to ODRL via rdfs:subPropertyOf odrl:function. Generic alternative to ODRL Common Vocabulary party functions (informedParty, compensatedParty, etc.) — use those when the specific semantics fits, use adalbert:object for consistency or when no specific function applies. Aligns with md:object (W3C Market Data) and rl2:counterparty."@en . + +# ============================================================================= +# OPERAND RESOLUTION (genuinely new) +# ============================================================================= + +adalbert:resolutionPath a owl:DatatypeProperty ; + rdfs:label "resolution path"@en ; + skos:definition "Dot-separated path from canonical root to value."@en ; + rdfs:domain odrl:LeftOperand ; + rdfs:range xsd:string ; + rdfs:comment """ + Path must start with a canonical root: agent, asset, context. + Examples: agent.role, asset.classification, context.purpose. + Resolution: resolve(path, Env) -> Value + """@en . + +# ============================================================================= +# RUNTIME REFERENCES (genuinely new) +# ============================================================================= + +adalbert:RuntimeReference a owl:Class ; + rdfs:label "Runtime Reference"@en ; + skos:definition "Value resolved at evaluation time."@en . + +adalbert:currentAgent a adalbert:RuntimeReference, odrl:Party ; + rdfs:label "current agent"@en ; + skos:definition "The requesting agent."@en ; + rdfs:comment """ + Reserved for constraint identity comparisons. Runtime resolution + in right-operand position requires RL2 (rl2:rightOperandRef). + Typed as both RuntimeReference and odrl:Party for ODRL processor + compatibility. For rules applying to any requesting agent, omit + odrl:assignee (standard ODRL); do not use currentAgent as assignee. + """@en . + +adalbert:currentDateTime a odrl:LeftOperand, adalbert:RuntimeReference ; + rdfs:label "current date time"@en ; + skos:definition "Evaluation timestamp."@en ; + rdfs:seeAlso odrl:dateTime ; + rdfs:comment "Equivalent to odrl:dateTime in constraint evaluation. Dual-typed as RuntimeReference to enable resolution at evaluation time."@en . + +# ============================================================================= +# LOGICAL NEGATION (genuinely new — ODRL lacks this) +# ============================================================================= + +adalbert:not a owl:ObjectProperty ; + rdfs:label "not"@en ; + skos:definition "Logical negation on a constraint."@en ; + rdfs:domain odrl:LogicalConstraint ; + rdfs:range [ owl:unionOf (odrl:Constraint odrl:LogicalConstraint) ] ; + rdfs:comment """ + ODRL defines odrl:and and odrl:or on LogicalConstraint but lacks + negation. Adalbert adds adalbert:not following the same pattern: + _:lc a odrl:LogicalConstraint ; + adalbert:not _:c1 . + """@en . + +# ============================================================================= +# END OF ONTOLOGY +# ============================================================================= diff --git a/adalbert-contracts/adalbert-due.ttl b/adalbert-contracts/adalbert-due.ttl new file mode 100644 index 0000000..b027dd3 --- /dev/null +++ b/adalbert-contracts/adalbert-due.ttl @@ -0,0 +1,597 @@ +# ============================================================================= +# Adalbert Data Use Profile (DUE) +# Version: 0.7 +# Date: 2026-02-03 +# ============================================================================= +# Complete data governance vocabulary: operands, actions, concept values. +# +# All operands are typed as odrl:LeftOperand (not adalbert:LeftOperand). +# Actions from ODRL Common Vocabulary are used directly (odrl:use, odrl:read, +# odrl:display, odrl:distribute, odrl:delete, odrl:modify, odrl:aggregate, +# odrl:anonymize, odrl:derive). DUE-only actions use the adalbert-due: prefix. +# Action hierarchy uses odrl:includedIn. +# Operand resolution uses adalbert:resolutionPath (Adalbert extension). +# ============================================================================= + +@prefix adalbert: . +@prefix adalbert-due: . +@prefix odrl: . +@prefix skos: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix xsd: . +@prefix dcterms: . +@prefix org: . + +# ============================================================================= +# PROFILE DECLARATION +# ============================================================================= + + a owl:Ontology ; + dcterms:title "Adalbert Data Use Profile"@en ; + dcterms:description """ + Complete data governance vocabulary for Adalbert. + + Covers: + - Purpose and classification constraints + - Asset metadata (class, market, benchmark) + - Jurisdiction and residency requirements + - Retention and processing controls + - Audit and logging requirements + - Identity and access control (role, org, project) + - Environment and network constraints + - Data freshness (timeliness, delay) + - Usage modes (display, non-display, derive) + - Derivation constraints (commingled, non-substitutive) + - Consent and legal basis + - Obligation actions (reporting, delivery, notification) + + Actions from ODRL Common Vocabulary are used directly (odrl:use, + odrl:read, odrl:display, etc.). DUE adds domain-specific actions + (nonDisplay, conformTo, log, notify, report, deliver, etc.). + All operands are odrl:LeftOperand with adalbert:resolutionPath. + """@en ; + dcterms:created "2026-02-03"^^xsd:date ; + owl:versionInfo "0.7" ; + owl:imports . + +# ============================================================================= +# CONCEPT SCHEME +# ============================================================================= + +adalbert-due:scheme a skos:ConceptScheme ; + rdfs:label "Adalbert DUE Vocabulary"@en ; + skos:definition "Concept scheme for the Adalbert Data Use vocabulary."@en . + +# ============================================================================= +# PURPOSE OPERANDS +# ============================================================================= + +odrl:purpose adalbert:resolutionPath "context.purpose" ; + rdfs:comment "Purpose values are skos:Concept instances. Add skos:broader links when a purpose hierarchy is needed."@en . + +# Purpose taxonomy +adalbert-due:analytics a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "analytics"@en ; + skos:definition "General analytical use."@en . + +adalbert-due:research a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "research"@en ; + skos:definition "Research and development."@en . + +adalbert-due:compliance a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "compliance"@en ; + skos:definition "Regulatory compliance."@en . + +adalbert-due:operations a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "operations"@en ; + skos:definition "Operational use."@en . + +# ============================================================================= +# CLASSIFICATION OPERANDS +# ============================================================================= + +adalbert-due:classification a odrl:LeftOperand, skos:Concept ; + rdfs:label "classification"@en ; + skos:definition "Data classification level."@en ; + adalbert:resolutionPath "asset.classification" . + +adalbert-due:sensitivity a odrl:LeftOperand, skos:Concept ; + rdfs:label "sensitivity"@en ; + skos:definition "Data sensitivity type."@en ; + adalbert:resolutionPath "asset.sensitivity" . + +# Classification values +adalbert-due:public a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "public"@en . +adalbert-due:internal a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "internal"@en . +adalbert-due:confidential a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "confidential"@en . +adalbert-due:restricted a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "restricted"@en . + +# Sensitivity values +adalbert-due:pii a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "PII"@en ; + skos:definition "Personally Identifiable Information."@en . +adalbert-due:mnpi a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "MNPI"@en ; + skos:definition "Material Non-Public Information."@en . +adalbert-due:phi a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "PHI"@en ; + skos:definition "Protected Health Information."@en . + +# ============================================================================= +# ASSET METADATA OPERANDS +# ============================================================================= + +adalbert-due:assetClass a odrl:LeftOperand, skos:Concept ; + rdfs:label "asset class"@en ; + skos:definition "Financial instrument or data classification."@en ; + adalbert:resolutionPath "asset.class" ; + rdfs:comment "E.g., equity, fixed-income, fx, commodity, reference."@en . + +adalbert-due:market a odrl:LeftOperand, skos:Concept ; + rdfs:label "market"@en ; + skos:definition "Trading venue or market of origin."@en ; + adalbert:resolutionPath "asset.market" ; + rdfs:comment "E.g., NYSE, LSE, CME, OTC."@en . + +adalbert-due:isBenchmark a odrl:LeftOperand, skos:Concept ; + rdfs:label "is benchmark"@en ; + skos:definition "Whether asset is a benchmark or index."@en ; + adalbert:resolutionPath "asset.isBenchmark" ; + rdfs:comment "Benchmarks often have stricter usage constraints."@en . + +# ============================================================================= +# JURISDICTION OPERANDS +# ============================================================================= + +adalbert-due:jurisdiction a odrl:LeftOperand, skos:Concept ; + rdfs:label "jurisdiction"@en ; + skos:definition "Legal jurisdiction governing data use."@en ; + adalbert:resolutionPath "context.jurisdiction" . + +adalbert-due:residency a odrl:LeftOperand, skos:Concept ; + rdfs:label "residency"@en ; + skos:definition "Data residency location."@en ; + adalbert:resolutionPath "asset.residency" . + +# ============================================================================= +# TEMPORAL OPERANDS +# ============================================================================= + +adalbert-due:retentionPeriod a odrl:LeftOperand, skos:Concept ; + rdfs:label "retention period"@en ; + skos:definition "Maximum time data may be retained."@en ; + adalbert:resolutionPath "asset.retentionPeriod" . + +adalbert-due:expiry a odrl:LeftOperand, skos:Concept ; + rdfs:label "expiry"@en ; + skos:definition "Expiration date of the policy."@en ; + adalbert:resolutionPath "asset.expiry" . + +# ============================================================================= +# PROCESSING OPERANDS +# ============================================================================= + +adalbert-due:processingMode a odrl:LeftOperand, skos:Concept ; + rdfs:label "processing mode"@en ; + skos:definition "How data is processed."@en ; + adalbert:resolutionPath "context.processingMode" . + +adalbert-due:human a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "human"@en . +adalbert-due:automated a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "automated"@en . +adalbert-due:modelTraining a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "model training"@en . +adalbert-due:inference a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "inference"@en . + +# ============================================================================= +# AUDIT OPERANDS +# ============================================================================= + +adalbert-due:auditRequired a odrl:LeftOperand, skos:Concept ; + rdfs:label "audit required"@en ; + skos:definition "Whether audit logging is required."@en ; + adalbert:resolutionPath "asset.auditRequired" . + +# ============================================================================= +# IDENTITY OPERANDS +# ============================================================================= + +adalbert-due:role a odrl:LeftOperand, skos:Concept ; + rdfs:label "role"@en ; + skos:definition "Role of the requesting agent."@en ; + adalbert:resolutionPath "agent.role" . + +adalbert-due:organization a odrl:LeftOperand, skos:Concept ; + rdfs:label "organization"@en ; + skos:definition "Organization of the requesting agent."@en ; + adalbert:resolutionPath "agent.organization" ; + rdfs:comment "Hierarchy via org:subOrganizationOf."@en . + +adalbert-due:costCenter a odrl:LeftOperand, skos:Concept ; + rdfs:label "cost center"@en ; + skos:definition "Cost center of the requesting agent."@en ; + adalbert:resolutionPath "agent.costCenter" . + +adalbert-due:project a odrl:LeftOperand, skos:Concept ; + rdfs:label "project"@en ; + skos:definition "Project context of the request."@en ; + adalbert:resolutionPath "context.project" . + +adalbert-due:recipientType a odrl:LeftOperand, skos:Concept ; + rdfs:label "recipient type"@en ; + skos:definition "Type of data recipient."@en ; + adalbert:resolutionPath "agent.recipientType" ; + rdfs:comment "E.g., internal, external, professional, retail."@en . + +# Recipient type values +adalbert-due:internalRecipient a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "internal recipient"@en ; + skos:definition "Recipient within the same organization."@en . +adalbert-due:externalRecipient a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "external recipient"@en ; + skos:definition "Recipient outside the organization."@en . +adalbert-due:professional a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "professional"@en ; + skos:definition "Licensed professional user."@en . +adalbert-due:retail a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "retail"@en ; + skos:definition "Retail/individual user."@en . + +# ============================================================================= +# ENVIRONMENT OPERANDS +# ============================================================================= + +adalbert-due:environment a odrl:LeftOperand, skos:Concept ; + rdfs:label "environment"@en ; + skos:definition "Execution environment."@en ; + adalbert:resolutionPath "context.environment" . + +adalbert-due:production a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "production"@en . +adalbert-due:staging a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "staging"@en . +adalbert-due:development a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "development"@en . +adalbert-due:sandbox a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "sandbox"@en . + +adalbert-due:network a odrl:LeftOperand, skos:Concept ; + rdfs:label "network"@en ; + skos:definition "Network zone of the request."@en ; + adalbert:resolutionPath "context.network" . + +adalbert-due:internalNetwork a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "internal network"@en . +adalbert-due:externalNetwork a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "external network"@en . +adalbert-due:cloudNetwork a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "cloud network"@en . + +# ============================================================================= +# SERVICE LEVEL OPERANDS +# ============================================================================= + +adalbert-due:availability a odrl:LeftOperand, skos:Concept ; + rdfs:label "availability"@en ; + skos:definition "Service availability percentage."@en ; + adalbert:resolutionPath "context.availability" ; + rdfs:comment "Typically measured as uptime percentage over a period (e.g., 99.9%)."@en . + +adalbert-due:latency a odrl:LeftOperand, skos:Concept ; + rdfs:label "latency"@en ; + skos:definition "Response latency at a given percentile."@en ; + adalbert:resolutionPath "context.latency" ; + rdfs:comment "Measured as duration (e.g., P99 latency <= PT0.2S)."@en . + +adalbert-due:throughput a odrl:LeftOperand, skos:Concept ; + rdfs:label "throughput"@en ; + skos:definition "Request throughput capacity."@en ; + adalbert:resolutionPath "context.throughput" ; + rdfs:comment "Measured in requests per second."@en . + +# ============================================================================= +# DATA QUALITY OPERANDS +# ============================================================================= + +adalbert-due:completeness a odrl:LeftOperand, skos:Concept ; + rdfs:label "completeness"@en ; + skos:definition "Data completeness percentage."@en ; + adalbert:resolutionPath "asset.completeness" ; + rdfs:comment "Percentage of non-null required fields."@en . + +adalbert-due:accuracy a odrl:LeftOperand, skos:Concept ; + rdfs:label "accuracy"@en ; + skos:definition "Data accuracy percentage."@en ; + adalbert:resolutionPath "asset.accuracy" ; + rdfs:comment "Percentage of records passing validation rules."@en . + +# ============================================================================= +# TIMELINESS OPERANDS +# ============================================================================= + +adalbert-due:timeliness a odrl:LeftOperand, skos:Concept ; + rdfs:label "timeliness"@en ; + skos:definition "Required data freshness category."@en ; + adalbert:resolutionPath "asset.timeliness" . + +adalbert-due:delayMinutes a odrl:LeftOperand, skos:Concept ; + rdfs:label "delay minutes"@en ; + skos:definition "Maximum acceptable delay in minutes."@en ; + adalbert:resolutionPath "asset.delayMinutes" . + +# Timeliness values +adalbert-due:realtime a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "real-time"@en ; + skos:definition "Data with minimal latency (sub-second)."@en . +adalbert-due:nearRealtime a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "near real-time"@en ; + skos:definition "Data with short delay (seconds to minutes)."@en . +adalbert-due:delayed a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "delayed"@en ; + skos:definition "Data with significant delay (15+ minutes)."@en . +adalbert-due:endOfDay a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "end of day"@en ; + skos:definition "Data available after market close."@en . +adalbert-due:historical a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "historical"@en ; + skos:definition "Historical/archived data."@en . + +# ============================================================================= +# CONSENT AND LEGAL BASIS +# ============================================================================= + +adalbert-due:legalBasis a odrl:LeftOperand, skos:Concept ; + rdfs:label "legal basis"@en ; + skos:definition "Legal basis for processing."@en ; + adalbert:resolutionPath "context.legalBasis" . + +adalbert-due:consent a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "consent"@en . +adalbert-due:contract a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "contract"@en . +adalbert-due:legalObligation a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "legal obligation"@en . +adalbert-due:vitalInterest a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "vital interest"@en . +adalbert-due:publicTask a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "public task"@en . +adalbert-due:legitimateInterest a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "legitimate interest"@en . + +adalbert-due:consentId a odrl:LeftOperand, skos:Concept ; + rdfs:label "consent ID"@en ; + skos:definition "Reference to consent record."@en ; + adalbert:resolutionPath "context.consentId" . + +# ============================================================================= +# ACCESS PATTERN OPERANDS +# ============================================================================= + +adalbert-due:accessPattern a odrl:LeftOperand, skos:Concept ; + rdfs:label "access pattern"@en ; + skos:definition "Type of data access."@en ; + adalbert:resolutionPath "context.accessPattern" . + +adalbert-due:batch a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "batch"@en . +adalbert-due:streaming a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "streaming"@en . +adalbert-due:interactive a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "interactive"@en . +adalbert-due:api a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "API"@en . + +adalbert-due:volumeLimit a odrl:LeftOperand, skos:Concept ; + rdfs:label "volume limit"@en ; + skos:definition "Maximum data volume per request."@en ; + adalbert:resolutionPath "context.volumeLimit" . + +adalbert-due:rateLimit a odrl:LeftOperand, skos:Concept ; + rdfs:label "rate limit"@en ; + skos:definition "Maximum requests per time period."@en ; + adalbert:resolutionPath "context.rateLimit" . + +# ============================================================================= +# CHANNEL OPERANDS +# ============================================================================= + +adalbert-due:channel a odrl:LeftOperand, skos:Concept ; + rdfs:label "channel"@en ; + skos:definition "Communication or delivery channel."@en ; + adalbert:resolutionPath "context.channel" ; + rdfs:comment "E.g., email, chat, phone, api, sftp."@en . + +adalbert-due:serviceWindow a odrl:LeftOperand, skos:Concept ; + rdfs:label "service window"@en ; + skos:definition "Time window during which a service is available."@en ; + adalbert:resolutionPath "context.serviceWindow" ; + rdfs:comment "E.g., business-hours, 24x7, weekdays."@en . + +# ============================================================================= +# SUBSCRIPTION OPERANDS +# ============================================================================= + +adalbert-due:subscriptionTier a odrl:LeftOperand, skos:Concept ; + rdfs:label "subscription tier"@en ; + skos:definition "Access tier or subscription level."@en ; + adalbert:resolutionPath "context.subscriptionTier" ; + rdfs:comment "E.g., free, basic, premium, enterprise."@en . + +# ============================================================================= +# DERIVATION OPERANDS +# ============================================================================= + +adalbert-due:derivationType a odrl:LeftOperand, skos:Concept ; + rdfs:label "derivation type"@en ; + skos:definition "Type of derivation being performed."@en ; + adalbert:resolutionPath "context.derivationType" . + +adalbert-due:commingled a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "commingled"@en ; + skos:definition "Derived data mixed with other sources."@en . +adalbert-due:nonSubstitutive a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "non-substitutive"@en ; + skos:definition "Derived data cannot substitute for original."@en . +adalbert-due:newProduct a skos:Concept ; + skos:inScheme adalbert-due:scheme ; + rdfs:label "new product"@en ; + skos:definition "Substantially transformed into new product."@en . + +# ============================================================================= +# ODRL COMMON VOCABULARY ACTIONS (used directly) +# ============================================================================= +# These actions come from ODRL 2.2 Common Vocabulary. DUE uses them directly +# rather than redefining them. ODRL already defines their includedIn hierarchy. +# +# odrl:use — General use (top of hierarchy) +# odrl:read — Read/view (includedIn odrl:use) +# odrl:display — Display to human users (includedIn odrl:use) +# odrl:distribute — Distribute to third parties (includedIn odrl:use) +# odrl:delete — Delete the asset (includedIn odrl:use) +# odrl:modify — Modify the asset (includedIn odrl:use) +# odrl:aggregate — Aggregate with other data (includedIn odrl:use) +# odrl:anonymize — Remove identifying information (includedIn odrl:use) +# odrl:derive — Create derived data (includedIn odrl:use) + +# ============================================================================= +# DUE-SPECIFIC ACTIONS (no ODRL equivalent) +# ============================================================================= + +adalbert-due:nonDisplay a odrl:Action, skos:Concept ; + rdfs:label "non-display"@en ; + skos:definition "Use data for automated/programmatic purposes."@en ; + odrl:includedIn odrl:use ; + rdfs:comment "Algorithmic use: models, automation, calculations."@en . + +adalbert-due:conformTo a odrl:Action, skos:Concept ; + rdfs:label "conform to"@en ; + skos:definition "Conform to a schema or specification."@en ; + rdfs:seeAlso dcterms:conformsTo ; + rdfs:comment """Provider governance duty: ensure data conforms to published schema. + The duty target (odrl:target) is the schema/specification — the same resource + that dct:conformsTo would point to as a static metadata assertion. conformTo + is the operational counterpart: an ongoing obligation to maintain conformance. + No odrl:includedIn — conformTo is a duty-only action (schema/quality SLA). + The includedIn hierarchy governs permission subsumption (granting odrl:use + implicitly grants sub-actions). conformTo never appears in permissions, so + chaining it to odrl:use would create a false subsumption."""@en . + +adalbert-due:log a odrl:Action, skos:Concept ; + rdfs:label "log"@en ; + skos:definition "Log access to the asset."@en ; + odrl:includedIn odrl:inform . + +adalbert-due:notify a odrl:Action, skos:Concept ; + rdfs:label "notify"@en ; + skos:definition "Notify relevant parties."@en ; + odrl:includedIn odrl:inform . + +adalbert-due:report a odrl:Action, skos:Concept ; + rdfs:label "report"@en ; + skos:definition "Submit usage reports."@en ; + odrl:includedIn odrl:inform ; + rdfs:comment "Typically an assignee duty: report usage to data owner."@en . + +adalbert-due:deliver a odrl:Action, skos:Concept ; + rdfs:label "deliver"@en ; + skos:definition "Deliver data to consumer."@en ; + odrl:includedIn odrl:distribute ; + rdfs:comment "Typically an assigner duty in data contracts."@en . + +# ============================================================================= +# SPECIALIZED ACTIONS +# ============================================================================= + +adalbert-due:calculateIndex a odrl:Action, skos:Concept ; + rdfs:label "calculate index"@en ; + skos:definition "Use data for index/benchmark calculation."@en ; + odrl:includedIn odrl:derive . + +adalbert-due:algorithmicTrading a odrl:Action, skos:Concept ; + rdfs:label "algorithmic trading"@en ; + skos:definition "Use data for automated trading decisions."@en ; + odrl:includedIn adalbert-due:nonDisplay . + +# ============================================================================= +# DATA MANIPULATION ACTIONS +# ============================================================================= + +adalbert-due:query a odrl:Action, skos:Concept ; + rdfs:label "query"@en ; + skos:definition "Query/select data."@en ; + odrl:includedIn odrl:read . + +adalbert-due:export a odrl:Action, skos:Concept ; + rdfs:label "export"@en ; + skos:definition "Export data outside the system."@en ; + odrl:includedIn odrl:distribute . + +adalbert-due:copy a odrl:Action, skos:Concept ; + rdfs:label "copy"@en ; + skos:definition "Copy data to another location."@en ; + odrl:includedIn odrl:reproduce . + +adalbert-due:link a odrl:Action, skos:Concept ; + rdfs:label "link"@en ; + skos:definition "Link/join with other datasets."@en ; + odrl:includedIn odrl:aggregate . + +adalbert-due:profile a odrl:Action, skos:Concept ; + rdfs:label "profile"@en ; + skos:definition "Create profiles from data."@en ; + odrl:includedIn odrl:derive . + +# ============================================================================= +# END OF PROFILE +# ============================================================================= diff --git a/adalbert-contracts/adalbert-prof.ttl b/adalbert-contracts/adalbert-prof.ttl new file mode 100644 index 0000000..ca0f0aa --- /dev/null +++ b/adalbert-contracts/adalbert-prof.ttl @@ -0,0 +1,88 @@ +# ============================================================================= +# Adalbert Profile Declaration (DXPROF) +# Version: 0.7 +# Date: 2026-02-04 +# ============================================================================= +# W3C Profiles Vocabulary metadata for Adalbert. +# Adalbert is a proper ODRL 2.2 profile — it uses ODRL terms and adds +# only genuinely new extensions. +# ============================================================================= + +@prefix adalbert: . +@prefix prof: . +@prefix role: . +@prefix dct: . +@prefix odrl: . +@prefix owl: . +@prefix xsd: . + +# ============================================================================= +# BASE SPECIFICATION +# ============================================================================= + +odrl:core a dct:Standard ; + dct:title "ODRL Information Model 2.2"@en ; + dct:identifier . + +# ============================================================================= +# Adalbert CORE PROFILE +# ============================================================================= + + a prof:Profile ; + dct:title "Adalbert Core"@en ; + dct:description """ + Proper ODRL 2.2 profile for deterministic data governance. + Uses ODRL terms for standard constructs; adds lifecycle state, + deadline, recurrence, data contracts, hierarchy extensions, + operand resolution, runtime references, and logical negation. + """@en ; + prof:isProfileOf odrl:core ; + prof:hasToken "adalbert"^^xsd:token ; + odrl:conflict odrl:prohibit ; + + prof:hasResource [ + a prof:ResourceDescriptor ; + dct:title "Adalbert Specification"@en ; + prof:hasRole role:specification ; + prof:hasArtifact + ] , [ + a prof:ResourceDescriptor ; + dct:title "Adalbert Formal Semantics"@en ; + prof:hasRole role:specification ; + prof:hasArtifact + ] , [ + a prof:ResourceDescriptor ; + dct:title "Adalbert Core Ontology"@en ; + prof:hasRole role:vocabulary ; + dct:conformsTo ; + prof:hasArtifact + ] , [ + a prof:ResourceDescriptor ; + dct:title "Adalbert SHACL Shapes"@en ; + prof:hasRole role:validation ; + dct:conformsTo ; + prof:hasArtifact + ] . + +# ============================================================================= +# DATA USE PROFILE +# ============================================================================= + + a prof:Profile ; + dct:title "Adalbert Data Use (DUE)"@en ; + dct:description "Complete data governance vocabulary: operands, actions, concept values."@en ; + prof:isProfileOf ; + prof:isTransitiveProfileOf odrl:core ; + prof:hasToken "adalbert-due"^^xsd:token ; + + prof:hasResource [ + a prof:ResourceDescriptor ; + dct:title "Adalbert DUE Vocabulary"@en ; + prof:hasRole role:vocabulary ; + dct:conformsTo ; + prof:hasArtifact + ] . + +# ============================================================================= +# END +# ============================================================================= diff --git a/adalbert-contracts/adalbert-shacl.ttl b/adalbert-contracts/adalbert-shacl.ttl new file mode 100644 index 0000000..5fd4f36 --- /dev/null +++ b/adalbert-contracts/adalbert-shacl.ttl @@ -0,0 +1,552 @@ +# ============================================================================= +# Adalbert SHACL Shapes +# Version: 0.7 +# Date: 2026-02-04 +# ============================================================================= +# Validation shapes for Adalbert policies. +# All shapes target ODRL classes and use ODRL properties. +# Adalbert-specific extensions are validated where they appear on ODRL types. +# ============================================================================= + +@prefix adalbert: . +@prefix adalbert-due: . +@prefix adalbertsh: . +@prefix odrl: . +@prefix sh: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . + +# ============================================================================= +# REUSABLE PROPERTY GROUPS +# ============================================================================= + +adalbertsh:StatePropertyGroup + sh:path adalbert:state ; + sh:maxCount 1 ; + sh:in (adalbert:Pending adalbert:Active adalbert:Fulfilled adalbert:Violated) ; + sh:message "State must be Pending, Active, Fulfilled, or Violated." . + +adalbertsh:ProfilePropertyGroup + sh:path odrl:profile ; + sh:minCount 1 ; + sh:hasValue ; + sh:message "Adalbert policies must declare odrl:profile ." . + +adalbertsh:NonEmptyClausesConstraint a sh:NodeShape ; + sh:or ( + [ sh:property [ sh:path odrl:permission ; sh:minCount 1 ] ] + [ sh:property [ sh:path odrl:prohibition ; sh:minCount 1 ] ] + [ sh:property [ sh:path odrl:obligation ; sh:minCount 1 ] ] + ) ; + sh:message "Policy must have at least one permission, prohibition, or obligation." . + +# ============================================================================= +# POLICY SHAPE — profile and conflict declaration +# ============================================================================= + +adalbertsh:PolicyShape a sh:NodeShape ; + sh:targetClass odrl:Policy ; + sh:name "Policy Shape" ; + + sh:property [ + sh:path odrl:profile ; + sh:minCount 1 ; + sh:hasValue ; + sh:message "Adalbert policies must declare odrl:profile ." + ] . + +# ============================================================================= +# SET SHAPE +# ============================================================================= + +adalbertsh:SetShape a sh:NodeShape ; + sh:targetClass odrl:Set ; + sh:name "Set Shape" ; + + sh:property adalbertsh:ProfilePropertyGroup ; + + sh:node adalbertsh:NonEmptyClausesConstraint ; + + # odrl:target is optional on Sets (formal semantics: Asset?). + # When present, inherited by rules that omit their own target. + sh:property [ + sh:path odrl:target ; + sh:message "Set policy may declare a policy-level target (inherited by rules)." + ] . + +# ============================================================================= +# OFFER SHAPE +# ============================================================================= + +adalbertsh:OfferShape a sh:NodeShape ; + sh:targetClass odrl:Offer ; + sh:name "Offer Shape" ; + + sh:property adalbertsh:ProfilePropertyGroup ; + + sh:node adalbertsh:NonEmptyClausesConstraint ; + + # Offer requires assigner + sh:property [ + sh:path odrl:assigner ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Offer must have exactly one assigner." + ] . + +# ============================================================================= +# AGREEMENT SHAPE +# ============================================================================= + +adalbertsh:AgreementShape a sh:NodeShape ; + sh:targetClass odrl:Agreement ; + sh:name "Agreement Shape" ; + + sh:property adalbertsh:ProfilePropertyGroup ; + + sh:node adalbertsh:NonEmptyClausesConstraint ; + + sh:property [ + sh:path odrl:assigner ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Agreement must have exactly one assigner." + ] ; + + sh:property [ + sh:path odrl:assignee ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Agreement must have exactly one assignee." + ] . + +# ============================================================================= +# PERMISSION SHAPE +# ============================================================================= + +adalbertsh:PermissionShape a sh:NodeShape ; + sh:targetClass odrl:Permission ; + sh:name "Permission Shape" ; + + sh:property [ + sh:path odrl:action ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Permission must have exactly one action." + ] ; + + sh:property [ + sh:path odrl:target ; + sh:maxCount 1 ; + sh:message "Permission may have at most one target (inherits from policy if absent)." + ] . + +# ============================================================================= +# PROHIBITION SHAPE +# ============================================================================= + +adalbertsh:ProhibitionShape a sh:NodeShape ; + sh:targetClass odrl:Prohibition ; + sh:name "Prohibition Shape" ; + + sh:property [ + sh:path odrl:action ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Prohibition must have exactly one action." + ] ; + + sh:property [ + sh:path odrl:target ; + sh:maxCount 1 ; + sh:message "Prohibition may have at most one target (inherits from policy if absent)." + ] . + +# ============================================================================= +# DUTY SHAPE +# ============================================================================= + +adalbertsh:DutyShape a sh:NodeShape ; + sh:targetClass odrl:Duty ; + sh:name "Duty Shape" ; + + sh:property [ + sh:path odrl:action ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Duty must have exactly one action." + ] ; + + sh:property [ + sh:path adalbert:deadline ; + sh:maxCount 1 ; + sh:or ( + [ sh:datatype xsd:dateTime ] + [ sh:datatype xsd:duration ] + ) ; + sh:message "Deadline must be xsd:dateTime or xsd:duration." + ] ; + + sh:property [ + sh:path adalbert:recurrence ; + sh:maxCount 1 ; + sh:datatype xsd:string ; + sh:pattern "^FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)" ; + sh:message "Recurrence must be a valid RFC 5545 RRULE starting with FREQ=." + ] ; + + sh:property [ + sh:path adalbert:subject ; + sh:maxCount 1 ; + sh:class odrl:Party ; + sh:message "Duty subject must be a single odrl:Party." + ] ; + + sh:property [ + sh:path adalbert:object ; + sh:maxCount 1 ; + sh:class odrl:Party ; + sh:message "Duty object must be a single odrl:Party." + ] ; + + sh:property adalbertsh:StatePropertyGroup . + +# ============================================================================= +# CONSTRAINT SHAPE +# ============================================================================= + +adalbertsh:ConstraintShape a sh:NodeShape ; + sh:targetClass odrl:Constraint ; + sh:name "Constraint Shape" ; + + sh:property [ + sh:path odrl:leftOperand ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Constraint must have exactly one leftOperand." + ] ; + + sh:property [ + sh:path odrl:operator ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Constraint must have exactly one operator." + ] ; + + sh:property [ + sh:path odrl:rightOperand ; + sh:minCount 1 ; + sh:message "Constraint must have a rightOperand." + ] . + +# ============================================================================= +# LOGICAL CONSTRAINT SHAPE +# ============================================================================= + +adalbertsh:LogicalConstraintShape a sh:NodeShape ; + sh:targetClass odrl:LogicalConstraint ; + sh:name "Logical Constraint Shape" ; + + # Must have exactly one of: odrl:and, odrl:or, adalbert:not + sh:xone ( + [ sh:property [ sh:path odrl:and ; sh:minCount 1 ; sh:maxCount 1 ] ] + [ sh:property [ sh:path odrl:or ; sh:minCount 1 ; sh:maxCount 1 ] ] + [ sh:property [ sh:path adalbert:not ; sh:minCount 1 ; sh:maxCount 1 ] ] + ) ; + sh:message "LogicalConstraint must have exactly one of: odrl:and, odrl:or, or adalbert:not." . + +# ============================================================================= +# DATA CONTRACT SHAPE +# ============================================================================= + +adalbertsh:DataContractShape a sh:NodeShape ; + sh:targetClass adalbert:DataContract ; + sh:name "Data Contract Shape" ; + + sh:property adalbertsh:ProfilePropertyGroup ; + + sh:node adalbertsh:NonEmptyClausesConstraint ; + + sh:property [ + sh:path odrl:assigner ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "DataContract must have exactly one assigner." + ] ; + + sh:property adalbertsh:StatePropertyGroup ; + + sh:property [ + sh:path adalbert:effectiveDate ; + sh:maxCount 1 ; + sh:datatype xsd:dateTime + ] ; + + sh:property [ + sh:path adalbert:expirationDate ; + sh:maxCount 1 ; + sh:datatype xsd:dateTime + ] . + +# ============================================================================= +# SUBSCRIPTION SHAPE +# ============================================================================= + +adalbertsh:SubscriptionShape a sh:NodeShape ; + sh:targetClass adalbert:Subscription ; + sh:name "Subscription Shape" ; + + sh:property adalbertsh:ProfilePropertyGroup ; + + sh:node adalbertsh:NonEmptyClausesConstraint ; + + # Explicit party constraints — Subscription is subClassOf Agreement, + # but without RDFS entailment AgreementShape won't target Subscription + # instances. Duplicate the party requirements here. + sh:property [ + sh:path odrl:assigner ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Subscription must have exactly one assigner (data provider)." + ] ; + + sh:property [ + sh:path odrl:assignee ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:message "Subscription must have exactly one assignee (data consumer)." + ] ; + + sh:property [ + sh:path adalbert:subscribesTo ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:class adalbert:DataContract ; + sh:message "Subscription must reference exactly one DataContract." + ] ; + + sh:property adalbertsh:StatePropertyGroup ; + + sh:property [ + sh:path adalbert:effectiveDate ; + sh:maxCount 1 ; + sh:datatype xsd:dateTime + ] ; + + sh:property [ + sh:path adalbert:expirationDate ; + sh:maxCount 1 ; + sh:datatype xsd:dateTime + ] . + +# ============================================================================= +# LEFT OPERAND SHAPE +# ============================================================================= +# sh:minCount 0: resolutionPath is optional in the shape because ODRL's +# built-in left operands (odrl:dateTime, etc.) don't have it. Adalbert +# profile operands MUST provide it — enforced by convention and DUE. +# +# The sh:pattern validates the root prefix only. Full path grammar enforcement +# (identifier syntax, depth constraints, traversal rejection) is a runtime +# concern per Adalbert_Semantics.md §6.2, not expressible in SHACL. + +adalbertsh:LeftOperandShape a sh:NodeShape ; + sh:targetClass odrl:LeftOperand ; + sh:name "Left Operand Shape" ; + + sh:property [ + sh:path adalbert:resolutionPath ; + sh:maxCount 1 ; + sh:datatype xsd:string ; + sh:pattern "^(agent|asset|context)\\." ; + sh:message "resolutionPath must start with agent., asset., or context." + ] . + +# ============================================================================= +# DUE OPERAND SHAPE — enforce resolutionPath on all DUE operands +# ============================================================================= +# Unlike generic LeftOperandShape (which allows absent resolutionPath for ODRL +# built-in operands like odrl:dateTime), DUE operands MUST declare a path. +# +# Validation model: this shape validates the DUE vocabulary itself, not +# individual policies. The sh:targetNode list references operand IRIs +# defined in adalbert-due.ttl. To validate, load both the DUE vocabulary +# and this shapes graph: +# shacl validate --shapes adalbert-shacl.ttl --data adalbert-due.ttl +# Policy-level validation (constraint structure, cardinality) is handled +# by ConstraintShape and LeftOperandShape above. + +adalbertsh:DUEOperandShape a sh:NodeShape ; + sh:name "DUE Operand Shape" ; + sh:description "DUE operands must declare a resolution path." ; + sh:targetNode + odrl:purpose , + adalbert-due:classification , + adalbert-due:sensitivity , + adalbert-due:assetClass , + adalbert-due:market , + adalbert-due:isBenchmark , + adalbert-due:jurisdiction , + adalbert-due:residency , + adalbert-due:retentionPeriod , + adalbert-due:expiry , + adalbert-due:processingMode , + adalbert-due:auditRequired , + adalbert-due:role , + adalbert-due:organization , + adalbert-due:costCenter , + adalbert-due:project , + adalbert-due:recipientType , + adalbert-due:environment , + adalbert-due:network , + adalbert-due:timeliness , + adalbert-due:delayMinutes , + adalbert-due:legalBasis , + adalbert-due:consentId , + adalbert-due:accessPattern , + adalbert-due:volumeLimit , + adalbert-due:rateLimit , + adalbert-due:derivationType ; + + sh:property [ + sh:path adalbert:resolutionPath ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:datatype xsd:string ; + sh:pattern "^(agent|asset|context)\\." ; + sh:message "DUE operands must have exactly one resolutionPath starting with agent., asset., or context." + ] . + +# ============================================================================= +# PROFILE SHAPE — reject RL2-only and unsupported ODRL constructs +# ============================================================================= +# Each shape targets nodes that use a rejected feature and declares a +# constraint that always fails: sh:maxCount 0 on the targeted property +# (for property-based rejections) or on rdf:type (for class-based +# rejections, since any typed node has rdf:type >= 1). + +adalbertsh:RejectXoneShape a sh:NodeShape ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX odrl: + SELECT ?this WHERE { + ?this odrl:xone ?list . + } + """ + ] ; + sh:property [ + sh:path odrl:xone ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support xone operator." + ] . + +adalbertsh:RejectRemedyShape a sh:NodeShape ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX odrl: + SELECT ?this WHERE { + ?this odrl:remedy ?r . + } + """ + ] ; + sh:property [ + sh:path odrl:remedy ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:remedy. Deferred to RL2." + ] . + +adalbertsh:RejectConsequenceShape a sh:NodeShape ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX odrl: + SELECT ?this WHERE { + ?this odrl:consequence ?c . + } + """ + ] ; + sh:property [ + sh:path odrl:consequence ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:consequence. Deferred to RL2." + ] . + +adalbertsh:RejectTicketShape a sh:NodeShape ; + sh:targetClass odrl:Ticket ; + sh:property [ + sh:path rdf:type ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:Ticket." + ] . + +adalbertsh:RejectRequestShape a sh:NodeShape ; + sh:targetClass odrl:Request ; + sh:property [ + sh:path rdf:type ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:Request." + ] . + +adalbertsh:RejectAssetCollectionShape a sh:NodeShape ; + sh:targetClass odrl:AssetCollection ; + sh:property [ + sh:path rdf:type ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:AssetCollection. Use adalbert:partOf hierarchy instead." + ] . + +adalbertsh:RejectPartyCollectionShape a sh:NodeShape ; + sh:targetClass odrl:PartyCollection ; + sh:property [ + sh:path rdf:type ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:PartyCollection. Use adalbert:memberOf hierarchy instead." + ] . + +adalbertsh:RejectInheritAllowedShape a sh:NodeShape ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX odrl: + SELECT ?this WHERE { + ?this odrl:inheritAllowed ?v . + } + """ + ] ; + sh:property [ + sh:path odrl:inheritAllowed ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:inheritAllowed." + ] . + +adalbertsh:RejectInheritFromShape a sh:NodeShape ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX odrl: + SELECT ?this WHERE { + ?this odrl:inheritFrom ?p . + } + """ + ] ; + sh:property [ + sh:path odrl:inheritFrom ; + sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "Adalbert does not support odrl:inheritFrom." + ] . + +# ============================================================================= +# END OF SHAPES +# ============================================================================= diff --git a/adalbert-contracts/docs/DATA-CONTRACTS.md b/adalbert-contracts/docs/DATA-CONTRACTS.md new file mode 100644 index 0000000..d8a29dd --- /dev/null +++ b/adalbert-contracts/docs/DATA-CONTRACTS.md @@ -0,0 +1,578 @@ +# Adalbert Contracts Guide + +A guide for data platform teams using Adalbert for data contracts and subscriptions. + +--- + +## Overview + +Adalbert models internal data sharing agreements as ODRL 2.2 policies. A **DataContract** is an offer from a data provider. A **Subscription** is an activated contract binding provider and consumer. + +``` +DataContract (Offer) Subscription (Agreement) +┌──────────────────┐ ┌──────────────────────┐ +│ Provider duties │ accept │ Provider duties │ +│ Consumer rights │ ──────> │ Consumer rights │ +│ Prohibitions │ │ Consumer duties │ +│ Recurrence rules │ │ Prohibitions │ +└──────────────────┘ │ State tracking │ + └──────────────────────┘ +``` + +--- + +## Key Concepts + +### DataContract (Offer) + +An `adalbert:DataContract` is a subclass of `odrl:Offer`. It specifies: + +- **Provider** (`odrl:assigner`): the data team providing data +- **Target** (`odrl:target`): the data asset(s) covered — inherited by rules unless overridden +- **Provider duties** (`odrl:obligation` with `adalbert:subject` = provider): SLAs like delivery, notification, schema conformance +- **Consumer permissions** (`odrl:permission`): what consumers can do with the data +- **Consumer duties** (`odrl:obligation` without subject in Offer): obligations consumers accept upon subscribing +- **Prohibitions** (`odrl:prohibition`): what consumers cannot do + +### Subscription (Agreement) + +An `adalbert:Subscription` is a subclass of `odrl:Agreement`. It adds: + +- **Consumer** (`odrl:assignee`): the subscribing team +- **Effective/expiration dates**: contract period +- **State tracking**: lifecycle state on duties and the subscription itself + +### Provider Duties + +Provider duties use DUE actions: + +| Action | Description | Example | +|--------|-------------|---------| +| `adalbert-due:deliver` | Deliver data to consumers | Daily market data delivery | +| `adalbert-due:notify` | Send notifications | Schema change notification | +| `adalbert-due:conformTo` | Maintain conformance to a standard | Schema/quality SLA | + +### Consumer Duties + +| Action | Description | Example | +|--------|-------------|---------| +| `adalbert-due:report` | Submit usage reports | Monthly usage reporting | + +### Permissions + +Standard ODRL actions apply: + +| Action | Description | +|--------|-------------| +| `odrl:display` | Display data | +| `adalbert-due:nonDisplay` | Non-display (algorithmic) use | +| `odrl:derive` | Create derived products | +| `odrl:read` | Read data | + +--- + +## Step-by-Step Cookbook + +Create your first DataContract in five steps. + +### Step 1: Declare the Contract + +Every contract starts with a type, profile declaration, and provider identity. + +```turtle +@prefix odrl: . +@prefix adalbert: . +@prefix adalbert-due: . +@prefix xsd: . + +ex:contract a adalbert:DataContract ; + odrl:profile , + ; + odrl:assigner ex:dataTeam ; + odrl:target ex:marketPrices ; + adalbert:state adalbert:Active . +``` + +### Step 2: Add Provider Duties (SLAs) + +Provider duties declare what the data team commits to. The provider is identified by `adalbert:subject` on each duty. Use `adalbert:object` to identify who is affected (e.g., who receives notifications). + +Rules inherit `odrl:target` from the policy unless they target a different asset. + +```turtle + # Daily delivery by 06:30 (target inherited from policy) + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:deliver ; + adalbert:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; + adalbert:deadline "PT30M"^^xsd:duration + ] ; + + # Schema conformance (different target — not inherited) + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:conformTo ; + odrl:target ex:marketDataSchema + ] ; +``` + +### Step 3: Add Consumer Permissions + +Permissions define what subscribers can do. In an Offer, omit `odrl:assignee` — it is filled when the subscription is created. Target is inherited from the policy. + +```turtle + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:display + ] ; + + odrl:permission [ + a odrl:Permission ; + odrl:action adalbert-due:nonDisplay ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:recipientType ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:internal + ] + ] ; +``` + +### Step 4: Add Consumer Duties and Prohibitions + +Consumer duties activate upon subscription. Prohibitions apply to all subscribers. + +```turtle + # Consumer must report usage monthly (different target — not inherited) + odrl:obligation [ + a odrl:Duty ; + odrl:action adalbert-due:report ; + odrl:target ex:usageStats ; + adalbert:deadline "P30D"^^xsd:duration + ] ; + + # No external redistribution (target inherited from policy) + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:distribute + ] . +``` + +### Step 5: Create a Subscription + +When a consumer accepts the contract, create a Subscription (Agreement) referencing the contract: + +```turtle +ex:subscription a adalbert:Subscription ; + odrl:profile , + ; + adalbert:subscribesTo ex:contract ; + odrl:assigner ex:dataTeam ; + odrl:assignee ex:analyticsTeam ; + adalbert:effectiveDate "2026-02-01T00:00:00Z"^^xsd:dateTime ; + adalbert:expirationDate "2026-12-31T23:59:59Z"^^xsd:dateTime . +``` + +The subscription materializes all duties from the contract with explicit `adalbert:subject` on each. + +--- + +## Provider Duty Patterns + +### Timeliness Pattern (Delivery SLA) + +Scheduled data delivery with a fulfillment window. Target inherited from policy. + +```turtle +odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:deliver ; + adalbert:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; + adalbert:deadline "PT30M"^^xsd:duration +] . +``` + +**DCON equivalent**: `ProviderTimelinessPromise` + +### Schema Pattern (Schema Conformance) + +Provider guarantees data conforms to a published schema. Target differs from policy — specify explicitly. + +```turtle +odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:conformTo ; + odrl:target ex:marketDataSchema +] . +``` + +**DCON equivalent**: `ProviderSchemaPromise` + +### Notification Pattern (Change Notification) + +Provider must notify consumers before making changes, with a lead time expressed as a duration deadline. Use `adalbert:object` to identify who is notified. + +```turtle +odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + adalbert:object ex:consumer ; + odrl:action adalbert-due:notify ; + odrl:target ex:schemaChanges ; + adalbert:deadline "P14D"^^xsd:duration +] . +``` + +**DCON equivalent**: `ProviderChangeNotificationPromise` + +### Quality SLA Pattern + +Provider guarantees data quality via `conformTo` with a constraint. This replaces DCON's `ProviderQualityPromise`. + +```turtle +odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:conformTo ; + odrl:target ex:riskMetricsSchema ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:timeliness ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:realtime + ] +] . +``` + +The constraint can check any DUE operand — timeliness, classification, environment, etc. + +--- + +## Recurrence + +Recurring duties use `adalbert:recurrence` — an RFC 5545 RRULE string. Combined with `adalbert:deadline`, this defines a schedule and fulfillment window. + +```turtle +odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:deliver ; + adalbert:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; + adalbert:deadline "PT30M"^^xsd:duration +] . +``` + +This means: deliver market prices daily at 06:00 (target inherited from policy), with a 30-minute window to fulfill. + +### Common RRULE Patterns + +| Pattern | RRULE | +|---------|-------| +| Daily at 06:00 | `FREQ=DAILY;BYHOUR=6;BYMINUTE=0` | +| Weekly on Monday | `FREQ=WEEKLY;BYDAY=MO` | +| Monthly on the 1st | `FREQ=MONTHLY;BYMONTHDAY=1` | +| Every 15 minutes | `FREQ=MINUTELY;INTERVAL=15` | +| Hourly | `FREQ=HOURLY` | + +Each generated instance follows the standard duty lifecycle independently (Pending -> Active -> Fulfilled/Violated). + +--- + +## Common Patterns Quick Reference + +### Simple File Drop + +Read-only access, no recurrence, no consumer duties. Target inherited from policy. + +```turtle +ex:contract a adalbert:DataContract ; + odrl:profile ; + odrl:assigner ex:dataTeam ; + odrl:target ex:referenceData ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read + ] ; + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:modify + ] . +``` + +### API with SLA + +Daily delivery, schema conformance, display + non-display, monthly reporting. Target inherited from policy unless overridden. + +```turtle +ex:contract a adalbert:DataContract ; + odrl:profile , + ; + odrl:assigner ex:dataTeam ; + odrl:target ex:customerData ; + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:deliver ; + adalbert:recurrence "FREQ=DAILY;BYHOUR=7;BYMINUTE=0" ; + adalbert:deadline "PT30M"^^xsd:duration + ] ; + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:conformTo ; + odrl:target ex:customerSchema + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:display + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action adalbert-due:nonDisplay + ] ; + odrl:obligation [ + a odrl:Duty ; + odrl:action adalbert-due:report ; + odrl:target ex:usageStats ; + adalbert:deadline "P30D"^^xsd:duration + ] ; + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:distribute + ] . +``` + +### Mission-Critical Service + +High-frequency delivery with quality SLA and change notification. Target inherited from policy unless overridden. + +```turtle +ex:contract a adalbert:DataContract ; + odrl:profile , + ; + odrl:assigner ex:dataTeam ; + odrl:target ex:riskMetrics ; + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:deliver ; + adalbert:recurrence "FREQ=MINUTELY;INTERVAL=1" ; + adalbert:deadline "PT30S"^^xsd:duration + ] ; + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:conformTo ; + odrl:target ex:riskSchema ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:timeliness ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:realtime + ] + ] ; + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:notify ; + odrl:target ex:schemaChanges ; + adalbert:deadline "P14D"^^xsd:duration + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:display + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action adalbert-due:nonDisplay + ] . +``` + +### Multi-Dataset Contract + +A single contract covering multiple targets. When a policy has multiple targets, rules must specify their target explicitly — inheritance is ambiguous. + +```turtle +ex:contract a adalbert:DataContract ; + odrl:profile ; + odrl:assigner ex:dataTeam ; + odrl:target ex:marketPrices , ex:referenceData , ex:riskMetrics ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:marketPrices + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:referenceData + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:riskMetrics + ] . +``` + +--- + +## Advanced Topics + +### Target Inheritance + +`odrl:target` at the policy level is inherited by rules that don't declare their own target. This reduces redundancy: + +```turtle +ex:contract a adalbert:DataContract ; + odrl:target ex:marketPrices ; # policy-level target + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read # inherits ex:marketPrices + ] ; + odrl:obligation [ + a odrl:Duty ; + adalbert:subject ex:dataTeam ; + odrl:action adalbert-due:conformTo ; + odrl:target ex:marketDataSchema # different target — explicit + ] . +``` + +**Rules**: +- Single-target policy: rules inherit the target unless they specify a different one +- Multi-target policy: rules must specify their target (inheritance is ambiguous) +- Named duty instances (standalone): always specify their target + +### Multi-Asset Contracts + +A contract can cover multiple targets. When multiple targets exist, each rule must specify its own target (inheritance is ambiguous): + +```turtle +ex:contract odrl:target ex:asset1 , ex:asset2 ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:asset1 + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:display ; + odrl:target ex:asset2 + ] . +``` + +### Team Delegation + +Use `adalbert:memberOf` to model team hierarchies. A permission granted to a team applies to its members via hierarchy subsumption during evaluation: + +```turtle +ex:analyst a odrl:Party ; + adalbert:memberOf ex:analyticsTeam . + +ex:analyticsTeam a odrl:Party ; + adalbert:memberOf ex:tradingDivision . +``` + +### Version Chains + +Use `prov:wasRevisionOf` to link contract versions: + +```turtle +ex:contract-v2 a adalbert:DataContract ; + prov:wasRevisionOf ex:contract-v1 . + +ex:contract-v3 a adalbert:DataContract ; + prov:wasRevisionOf ex:contract-v2 . +``` + +Subscriptions reference the specific contract version they activate: + +```turtle +ex:subscription adalbert:subscribesTo ex:contract-v2 . +``` + +### Expiration + +Contracts and subscriptions can have explicit expiration dates: + +```turtle +ex:subscription a adalbert:Subscription ; + adalbert:effectiveDate "2026-01-15T00:00:00Z"^^xsd:dateTime ; + adalbert:expirationDate "2026-12-31T23:59:59Z"^^xsd:dateTime . +``` + +--- + +## Lifecycle + +Duties and contracts share four states: + +``` + condition true +Pending ──────────────> Active + | | + action done | | deadline passed + v v + Fulfilled Violated +``` + +- **Pending**: condition not yet met / not yet in force +- **Active**: condition met, action required / in force +- **Fulfilled**: action performed / obligations complete +- **Violated**: deadline passed / breached + +--- + +## Versioning + +Contracts can be versioned using `prov:wasRevisionOf`: + +```turtle +ex:contract-v2 a adalbert:DataContract ; + prov:wasRevisionOf ex:contract-v1 . +``` + +Subscriptions reference the contract they activate via `adalbert:subscribesTo`: + +```turtle +ex:subscription adalbert:subscribesTo ex:contract-v2 . +``` + +--- + +## Complete Example + +See [examples/data-contract.ttl](../examples/data-contract.ttl) for a full working contract with bilateral duties, recurrence, schema conformance, and subscription. + +For comprehensive test data covering all patterns, see [examples/baseline.ttl](../examples/baseline.ttl). + +--- + +## DCON Migration + +If migrating from DCON, see [adalbert-term-mapping.md](adalbert-term-mapping.md) for complete property equivalents and [comparisons/comparison-dcon.md](comparisons/comparison-dcon.md) for the supersession analysis. + +--- + +## Validation Checklist + +1. Every policy declares `odrl:profile ` and `` +2. Conflict strategy (`odrl:conflict odrl:prohibit`) is inherited from the profile — do not repeat per-policy +3. DataContract has `odrl:assigner` (provider) +4. Subscription has both `odrl:assigner` and `odrl:assignee` +5. Subscription has `adalbert:subscribesTo` referencing a DataContract +6. Each duty has exactly one `odrl:action` +7. Each permission and prohibition has exactly one `odrl:action` and one `odrl:target` +8. Provider duties have `adalbert:subject` set to the provider +9. Deadlines use `xsd:dateTime` or `xsd:duration` +10. Recurrence uses a valid RFC 5545 RRULE starting with `FREQ=` +11. Constraints have `leftOperand`, `operator`, and `rightOperand` +12. LogicalConstraints use exactly one of `odrl:and`, `odrl:or`, or `adalbert:not` +13. Validate against SHACL shapes: + +```bash +shacl validate --shapes ontology/adalbert-shacl.ttl --data my-contract.ttl +``` diff --git a/adalbert-contracts/docs/DATA-USE-POLICIES.md b/adalbert-contracts/docs/DATA-USE-POLICIES.md new file mode 100644 index 0000000..57493d2 --- /dev/null +++ b/adalbert-contracts/docs/DATA-USE-POLICIES.md @@ -0,0 +1,551 @@ +# Adalbert Policy Writers Guide + +A guide for data stewards writing data use policies with Adalbert. + +--- + +## 1. When to Use Policies vs Contracts + +Adalbert supports two document types for different purposes: + +| Type | ODRL Base | Use Case | Parties | +|------|-----------|----------|---------| +| **Policy** (`odrl:Set`) | Set | Organizational rules, access controls | None (applies to anyone matching constraints) | +| **Offer / Contract** (`adalbert:DataContract`) | Offer | Bilateral agreements between teams | Provider (assigner) required | +| **Subscription** (`adalbert:Subscription`) | Agreement | Activated contract | Both parties required | + +Use **policies** (`odrl:Set`) when: +- Writing organizational data governance rules +- Defining access controls by role, purpose, or classification +- Setting restrictions that apply universally (no specific consumer) +- Encoding regulatory requirements (GDPR, data residency) + +Use **contracts** when two specific teams need a bilateral agreement with SLAs. See [contracts-guide.md](contracts-guide.md) for contract authoring. + +--- + +## 2. Quick Start + +A minimal data use policy: + +```turtle +@prefix odrl: . +@prefix adalbert: . +@prefix adalbert-due: . +@prefix xsd: . + +ex:policy a odrl:Set ; + odrl:profile , + ; + odrl:target ex:customerData ; + + odrl:permission [ + a odrl:Permission ; + odrl:assignee ex:analyticsTeam ; + odrl:action odrl:read ; + odrl:target ex:customerData ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:analytics + ] + ] ; + + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:distribute ; + odrl:target ex:customerData + ] . +``` + +This policy says: the analytics team may read customer data for analytics purposes, and no one may distribute it. + +--- + +## 3. Permission Patterns + +### Purpose-Restricted Permission + +Grant access only for a specific purpose. + +```turtle +odrl:permission [ + a odrl:Permission ; + odrl:assignee ex:complianceTeam ; + odrl:action odrl:read ; + odrl:target ex:transactionData ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:compliance + ] +] . +``` + +### Classification-Based Permission + +Grant access based on the data's classification level. + +```turtle +odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:classification ; + odrl:operator odrl:isAnyOf ; + odrl:rightOperand (adalbert-due:public adalbert-due:internal) + ] +] . +``` + +### Role-Based Permission + +Grant access based on the requesting agent's role. + +```turtle +odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:role ; + odrl:operator odrl:eq ; + odrl:rightOperand "data-analyst" + ] +] . +``` + +### Time-Limited Permission + +Grant access only before a certain date. + +```turtle +odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert:currentDateTime ; + odrl:operator odrl:lt ; + odrl:rightOperand "2026-12-31T23:59:59Z"^^xsd:dateTime + ] +] . +``` + +--- + +## 4. Prohibition Patterns + +### No External Distribution + +```turtle +odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:distribute ; + odrl:target ex:data +] . +``` + +### No PII Processing + +Prohibit use of data classified as PII for certain processing modes. + +```turtle +odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:use ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:LogicalConstraint ; + odrl:and ( + [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:sensitivity ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:pii + ] + [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:processingMode ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:modelTraining + ] + ) + ] +] . +``` + +### No Commingling + +Prohibit mixing data with other sources. + +```turtle +odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:derive ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:derivationType ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:commingled + ] +] . +``` + +--- + +## 5. Constraint Patterns + +Constraints restrict when rules apply. An `odrl:Constraint` has three parts: + +| Part | Property | Description | +|------|----------|-------------| +| Left operand | `odrl:leftOperand` | What to check (from DUE vocabulary) | +| Operator | `odrl:operator` | How to compare (`eq`, `neq`, `lt`, `gt`, `lteq`, `gteq`, `isAnyOf`, `isNoneOf`, `isAllOf`) | +| Right operand | `odrl:rightOperand` | Expected value(s) | + +### Purpose Constraint + +```turtle +odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:analytics +] . +``` + +### Classification Constraint + +```turtle +odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:classification ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:confidential +] . +``` + +### Jurisdiction Constraint + +```turtle +odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:jurisdiction ; + odrl:operator odrl:isAnyOf ; + odrl:rightOperand ("US" "UK" "EU") +] . +``` + +### Retention Constraint + +```turtle +odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:retentionPeriod ; + odrl:operator odrl:lteq ; + odrl:rightOperand "P7Y"^^xsd:duration +] . +``` + +### Environment Constraint + +```turtle +odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:environment ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:production +] . +``` + +--- + +## 6. DUE Operands Quick Reference + +All operands are `odrl:LeftOperand` with `adalbert:resolutionPath`. + +### Agent Operands (resolved from requesting agent) + +| Operand | Resolution Path | Values / Type | +|---------|----------------|---------------| +| `adalbert-due:role` | `agent.role` | String | +| `adalbert-due:organization` | `agent.organization` | IRI | +| `adalbert-due:costCenter` | `agent.costCenter` | String | +| `adalbert-due:recipientType` | `agent.recipientType` | `internal`, etc. | + +### Asset Operands (resolved from target asset) + +| Operand | Resolution Path | Values / Type | +|---------|----------------|---------------| +| `adalbert-due:classification` | `asset.classification` | `public`, `internal`, `confidential`, `restricted` | +| `adalbert-due:sensitivity` | `asset.sensitivity` | `pii`, `mnpi`, `phi` | +| `adalbert-due:assetClass` | `asset.class` | String (equity, fx, etc.) | +| `adalbert-due:market` | `asset.market` | String (NYSE, LSE, etc.) | +| `adalbert-due:isBenchmark` | `asset.isBenchmark` | Boolean | +| `adalbert-due:residency` | `asset.residency` | String (country) | +| `adalbert-due:retentionPeriod` | `asset.retentionPeriod` | `xsd:duration` | +| `adalbert-due:expiry` | `asset.expiry` | `xsd:dateTime` | +| `adalbert-due:timeliness` | `asset.timeliness` | `realtime`, `nearRealtime`, `delayed`, `endOfDay`, `historical` | +| `adalbert-due:delayMinutes` | `asset.delayMinutes` | Integer | +| `adalbert-due:auditRequired` | `asset.auditRequired` | Boolean | + +### Context Operands (resolved from request context) + +| Operand | Resolution Path | Values / Type | +|---------|----------------|---------------| +| `odrl:purpose` | `context.purpose` | `analytics`, `research`, `compliance`, `operations` | +| `adalbert-due:jurisdiction` | `context.jurisdiction` | String (country) | +| `adalbert-due:environment` | `context.environment` | `production`, `staging`, `development`, `sandbox` | +| `adalbert-due:network` | `context.network` | `internalNetwork`, `externalNetwork`, `cloudNetwork` | +| `adalbert-due:processingMode` | `context.processingMode` | `human`, `automated`, `modelTraining`, `inference` | +| `adalbert-due:legalBasis` | `context.legalBasis` | `consent`, `contract`, `legalObligation`, `vitalInterest`, `publicTask`, `legitimateInterest` | +| `adalbert-due:consentId` | `context.consentId` | String | +| `adalbert-due:accessPattern` | `context.accessPattern` | `batch`, `streaming`, `interactive`, `api` | +| `adalbert-due:volumeLimit` | `context.volumeLimit` | Integer | +| `adalbert-due:rateLimit` | `context.rateLimit` | Integer | +| `adalbert-due:derivationType` | `context.derivationType` | `commingled`, `nonSubstitutive`, `newProduct` | +| `adalbert-due:project` | `context.project` | String | + +--- + +## 7. DUE Actions Quick Reference + +### ODRL Common Vocabulary (used directly) + +| Action | Definition | Hierarchy | +|--------|------------|-----------| +| `odrl:use` | General use | Top | +| `odrl:read` | Read/view | `includedIn use` | +| `odrl:display` | Display to users | `includedIn use` | +| `odrl:distribute` | Distribute to third parties | `includedIn use` | +| `odrl:delete` | Delete the asset | `includedIn use` | +| `odrl:modify` | Modify the asset | `includedIn use` | +| `odrl:aggregate` | Aggregate with other data | `includedIn use` | +| `odrl:anonymize` | Remove identifying info | `includedIn use` | +| `odrl:derive` | Create derived data | `includedIn use` | + +### DUE-Specific Actions + +| Action | Definition | Parent | +|--------|------------|--------| +| `adalbert-due:nonDisplay` | Automated/programmatic use | `use` | +| `adalbert-due:conformTo` | Conform to schema/spec | None | +| `adalbert-due:log` | Log access | `inform` | +| `adalbert-due:notify` | Notify parties | `inform` | +| `adalbert-due:report` | Submit reports | `inform` | +| `adalbert-due:deliver` | Deliver data | `distribute` | +| `adalbert-due:query` | Query/select | `read` | +| `adalbert-due:export` | Export outside system | `distribute` | +| `adalbert-due:copy` | Copy to another location | `reproduce` | +| `adalbert-due:link` | Link/join datasets | `aggregate` | +| `adalbert-due:profile` | Create profiles | `derive` | +| `adalbert-due:calculateIndex` | Index calculation | `derive` | +| `adalbert-due:algorithmicTrading` | Automated trading | `nonDisplay` | + +Action hierarchy uses `odrl:includedIn`. A permission on `odrl:use` implicitly permits all actions below it. + +--- + +## 8. Combining Constraints + +### Logical AND + +All conditions must be true. Use `odrl:LogicalConstraint` with `odrl:and`: + +```turtle +odrl:constraint [ + a odrl:LogicalConstraint ; + odrl:and ( + [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:compliance + ] + [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:legalBasis ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:legitimateInterest + ] + ) +] . +``` + +### Logical OR + +At least one condition must be true. Use `odrl:or`: + +```turtle +odrl:constraint [ + a odrl:LogicalConstraint ; + odrl:or ( + [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:compliance + ] + [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:operations + ] + ) +] . +``` + +### Logical NOT + +Negate a constraint. Use `adalbert:not`: + +```turtle +odrl:constraint [ + a odrl:LogicalConstraint ; + adalbert:not [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:environment ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:production + ] +] . +``` + +This means: the constraint is satisfied when the environment is **not** production. + +A `LogicalConstraint` must have exactly one of `odrl:and`, `odrl:or`, or `adalbert:not`. + +--- + +## 9. Common Policy Patterns + +### Internal Analytics Only + +```turtle +ex:policy a odrl:Set ; + odrl:profile , + ; + odrl:target ex:data ; + + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:LogicalConstraint ; + odrl:and ( + [ + a odrl:Constraint ; + odrl:leftOperand odrl:purpose ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:analytics + ] + [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:recipientType ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:internal + ] + ) + ] + ] ; + + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:distribute ; + odrl:target ex:data + ] . +``` + +### Jurisdiction-Restricted + +```turtle +odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:jurisdiction ; + odrl:operator odrl:isAnyOf ; + odrl:rightOperand ("US" "UK") + ] +] . +``` + +### Retention-Limited + +```turtle +odrl:obligation [ + a odrl:Duty ; + odrl:action odrl:delete ; + odrl:target ex:data ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:retentionPeriod ; + odrl:operator odrl:gt ; + odrl:rightOperand "P5Y"^^xsd:duration + ] +] . +``` + +### Audit-Required + +```turtle +odrl:obligation [ + a odrl:Duty ; + odrl:action adalbert-due:log ; + odrl:target ex:accessLog ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand adalbert-due:classification ; + odrl:operator odrl:eq ; + odrl:rightOperand adalbert-due:confidential + ] ; + adalbert:deadline "PT1H"^^xsd:duration +] . +``` + +--- + +## 10. Policy Review Checklist + +1. Every policy declares `odrl:profile ` and `` +2. Conflict strategy (`odrl:conflict odrl:prohibit`) is inherited from the profile — do not repeat per-policy +3. Policy has at least one `odrl:target` +4. Each permission and prohibition has exactly one `odrl:action` and one effective `odrl:target` — either declared on the rule or inherited from the policy-level target (rule-level `odrl:target` may be omitted when a policy-level target exists) +5. Each duty has exactly one `odrl:action` +6. Each constraint has `leftOperand`, `operator`, and `rightOperand` +7. LogicalConstraints use exactly one of `odrl:and`, `odrl:or`, or `adalbert:not` +8. Resolution paths match canonical roots (`agent.`, `asset.`, `context.`) +9. Prohibitions cover the intended restrictions (prohibition overrides permission) +10. Duties have appropriate deadlines where time-sensitive +11. Classification and sensitivity values match the DUE vocabulary +12. Validate against SHACL shapes: + +```bash +shacl validate --shapes ontology/adalbert-shacl.ttl --data my-policy.ttl +``` + +--- + +## Further Reading + +- [adalbert-overview.md](adalbert-overview.md) — What is Adalbert? +- [adalbert-specification.md](adalbert-specification.md) — Technical vocabulary reference +- [adalbert-term-mapping.md](adalbert-term-mapping.md) — Business term -> property mapping +- [examples/data-use-policy.ttl](../examples/data-use-policy.ttl) — Complete working policy example +- [examples/baseline.ttl](../examples/baseline.ttl) — Comprehensive test data + +--- + +**Version**: 0.7 | **Date**: 2026-02-04 diff --git a/adalbert-contracts/docs/DPROD-Adalbert-Comparison.md b/adalbert-contracts/docs/DPROD-Adalbert-Comparison.md new file mode 100644 index 0000000..aea0938 --- /dev/null +++ b/adalbert-contracts/docs/DPROD-Adalbert-Comparison.md @@ -0,0 +1,207 @@ +# DPROD-Contracts vs Adalbert: Comparison Report + +A comparison of the two ODRL-based data contract approaches contributed to EKGF/dprod. + +--- + +## Overview + +Both `dprod-contracts` and `adalbert-contracts` extend ODRL 2.2 for data governance. They share the same goal — formalizing bilateral data agreements between providers and consumers — but differ in scope, architecture, and formality. + +| Dimension | dprod-contracts | adalbert-contracts | +|---|---|---| +| **Scope** | Data contracts only | Data contracts + data-use policies | +| **ODRL relationship** | Custom Rule subclass (Promise) | Proper ODRL 2.2 profile (Duty, Permission, Prohibition) | +| **Formal semantics** | No | Yes (normative, verification-ready) | +| **Lifecycle** | 4 statuses (Pending, Active, Expired, Cancelled) | 4 states (Pending, Active, Fulfilled, Violated) with formal transitions | +| **Bilateral duties** | Via ProviderPromise / ConsumerPromise | Via `adalbert:subject` / `adalbert:object` on standard `odrl:Duty` | +| **Recurrence** | `dprod:ICalSchedule` class + `dprod:icalRule` | `adalbert:recurrence` property (direct RRULE string on Duty) | +| **Validation** | SHACL shapes | SHACL shapes | +| **W3C Profile declaration** | ODRL Profile in ontology | Separate W3C DXPROF declaration | + +--- + +## Architectural Differences + +### 1. Promise vs Duty + +**dprod-contracts** introduces `dprod:Promise` as a new `odrl:Rule` subclass, disjoint with `odrl:Duty`, `odrl:Permission`, and `odrl:Prohibition`. This creates a parallel class hierarchy: + +``` +odrl:Rule +├── odrl:Permission +├── odrl:Prohibition +├── odrl:Duty +└── dprod:Promise ← new Rule type + ├── dprod:ProviderPromise + │ ├── dprod:ProviderTimelinessPromise + │ ├── dprod:ProviderSchemaPromise + │ ├── dprod:ProviderServiceLevelPromise + │ └── ... + └── dprod:ConsumerPromise +``` + +**Adalbert** uses standard `odrl:Duty` for all obligations, distinguishing provider from consumer duties via `adalbert:subject`: + +``` +odrl:Rule (unchanged) +├── odrl:Permission +├── odrl:Prohibition +└── odrl:Duty + ├── with adalbert:subject = provider → provider duty + └── with adalbert:subject = consumer → consumer duty +``` + +**Implication**: Adalbert policies are valid ODRL 2.2 — any ODRL processor can partially understand them. dprod-contracts requires Promise-aware processing. + +### 2. Data-Use Policies + +**dprod-contracts** focuses exclusively on bilateral contracts between named parties. + +**Adalbert** also supports `odrl:Set` policies — organizational rules that apply to anyone matching constraints: + +- Role-based access control +- Purpose restrictions +- Classification-based permissions +- Environment constraints +- Jurisdiction requirements +- Retention limits + +This is a key differentiator. Real-world data governance requires both contracts (bilateral) and policies (organizational). + +### 3. Formal Semantics + +**dprod-contracts** defines classes and properties but not evaluation behavior. + +**Adalbert** includes a normative formal semantics document defining: + +- `Eval : Request × PolicySet × State → Decision × DutySet` (total function) +- Deterministic conflict resolution (Prohibition > Permission) +- Duty lifecycle state machine (Pending → Active → Fulfilled/Violated) +- Operand resolution from canonical roots (agent, asset, context) +- Recurrence expansion via RFC 5545 RRULE + +This makes Adalbert amenable to formal verification (Dafny, Why3, Coq). + +### 4. Recurrence + +**dprod-contracts**: +```turtle +dprod:hasSchedule [ + a dprod:ICalSchedule ; + dprod:icalRule "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" +] . +``` + +**Adalbert**: +```turtle +adalbert:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; +adalbert:deadline "PT30M"^^xsd:duration . +``` + +Adalbert is more concise (property on Duty vs. nested class) and adds `deadline` for the per-instance fulfillment window. + +### 5. Operand Resolution + +**dprod-contracts** does not define how constraint operands resolve. + +**Adalbert** defines `adalbert:resolutionPath` — dot-separated paths from canonical roots: + +``` +agent.role, agent.organization, agent.costCenter +asset.classification, asset.market, asset.isBenchmark +context.purpose, context.environment, context.legalBasis +``` + +This enables deterministic constraint evaluation across implementations. + +--- + +## Mapping Table + +### Classes + +| dprod-contracts | Adalbert | Notes | +|---|---|---| +| `dprod:DataContract` | `adalbert:DataContract` | Both subclass `odrl:Agreement` / `odrl:Offer` | +| `dprod:DataOffer` | `adalbert:DataContract` | Adalbert DataContract is an Offer; Subscription is the Agreement | +| `dprod:Promise` | `odrl:Duty` | Standard ODRL type | +| `dprod:ProviderPromise` | `odrl:Duty` + `adalbert:subject` | Subject identifies provider | +| `dprod:ConsumerPromise` | `odrl:Permission` / `odrl:Prohibition` / `odrl:Duty` | Split by semantics | +| `dprod:ProviderTimelinessPromise` | `odrl:Duty` + `adalbert-due:deliver` + `adalbert:recurrence` | | +| `dprod:ProviderSchemaPromise` | `odrl:Duty` + `adalbert-due:conformTo` | | +| `dprod:ProviderServiceLevelPromise` | `odrl:Duty` + `adalbert-due:conformTo` + constraints | | +| `dprod:ServiceLevelTarget` | `odrl:Constraint` | Expressed as constraint on duty | +| `dprod:ICalSchedule` | `adalbert:recurrence` (string property) | Simpler, same RRULE syntax | + +### Properties + +| dprod-contracts | Adalbert | Notes | +|---|---|---| +| `dprod:contractStatus` | `adalbert:state` | Similar lifecycle, different value names | +| `dprod:providerPromise` | `odrl:obligation` | Standard ODRL property | +| `dprod:consumerPromise` | `odrl:permission` / `odrl:obligation` / `odrl:prohibition` | Split by semantics | +| `dprod:hasSchedule` / `dprod:icalRule` | `adalbert:recurrence` | Direct RRULE on Duty | +| `dprod:hasEffectivePeriod` | `adalbert:effectiveDate` + `adalbert:expirationDate` | | +| `dprod:noticePeriod` | `adalbert:deadline` | Duration on notification duty | +| `dprod:supersedes` | `prov:wasRevisionOf` | Standard provenance | +| `dprod:hasServiceLevelTarget` | `odrl:constraint` on duty | | +| `dprod:hasPricing` | Not in scope | Adalbert focuses on rights, not pricing | + +### Actions + +| dprod-contracts | Adalbert | Notes | +|---|---|---| +| `dprod:deliverOnSchedule` | `adalbert-due:deliver` | `includedIn odrl:distribute` | +| `dprod:maintainSchema` | `adalbert-due:conformTo` | Duty-only action | +| `dprod:maintainQuality` | `adalbert-due:conformTo` + constraint | Quality as constrained conformance | +| `dprod:notifyChange` | `adalbert-due:notify` | `includedIn odrl:inform` | +| `dprod:notifyTermination` | `adalbert-due:notify` + `adalbert:deadline` | Deadline = notice period | +| `dprod:meetServiceLevel` | `adalbert-due:conformTo` + constraints | SLA as constrained conformance | +| `dprod:provideSupport` | `adalbert-due:notify` | Best fit | +| `dprod:reportUsage` | `adalbert-due:report` | `includedIn odrl:inform` | +| `dprod:query` | `adalbert-due:query` | `includedIn odrl:read` | +| `dprod:access` | `odrl:use` | Standard ODRL | +| `dprod:deriveInsights` | `odrl:derive` | Standard ODRL | +| `dprod:aggregate` | `odrl:aggregate` | Standard ODRL | +| `dprod:anonymize` | `odrl:anonymize` | Standard ODRL | +| `dprod:shareInternally` | `odrl:distribute` + constraint | Internal-only via constraint | +| `dprod:restrictPurpose` | `odrl:Prohibition` on `odrl:distribute` | Prohibition, not action | +| `dprod:deleteOnExpiry` | `odrl:Duty` with `odrl:delete` | Standard ODRL action | + +### Status Values + +| dprod-contracts | Adalbert | Notes | +|---|---|---| +| `dprod:ContractStatusPending` | `adalbert:Pending` | Same semantics | +| `dprod:ContractStatusActive` | `adalbert:Active` | Same semantics | +| `dprod:ContractStatusExpired` | `adalbert:Fulfilled` | Adalbert: natural completion = Fulfilled | +| `dprod:ContractStatusCancelled` | `adalbert:Violated` | Adalbert: early termination = Violated | + +--- + +## What Adalbert Adds Beyond dprod-contracts + +1. **Data-use policies** (`odrl:Set`) — organizational rules, access controls, not just bilateral contracts +2. **Formal semantics** — total evaluation function, deterministic conflict resolution, verifiable +3. **Duty lifecycle** — formal state machine with Pending → Active → Fulfilled/Violated transitions +4. **Structured operand resolution** — `resolutionPath` from canonical roots (agent, asset, context) +5. **Rich constraint vocabulary** — 33 operands across purpose, classification, jurisdiction, environment, timeliness, SLA metrics, data quality, channels, subscription tiers, legal basis, etc. +6. **Logical negation** — `adalbert:not` (ODRL lacks this) +7. **Target inheritance** — policy-level `odrl:target` inherited by rules +8. **Party hierarchies** — `adalbert:partOf` (assets) and `adalbert:memberOf` (parties) with transitivity + +## What dprod-contracts Adds Beyond Adalbert + +1. **Pricing** — `dprod:hasPricing` with `schema:PriceSpecification` (marketplace scenarios). Adalbert intentionally does not address pricing, focusing on rights and obligations. +2. **Promise class hierarchy** — richer typing for different commitment types (may aid discovery) + +--- + +## Translated Examples + +See `examples/dprod-translated.ttl` for all 8 dprod-contracts examples translated to Adalbert, demonstrating that Adalbert can express the same contracts while adding lifecycle tracking, deterministic evaluation, and structured constraints. + +--- + +**Version**: 0.7 | **Date**: 2026-02-16 diff --git a/adalbert-contracts/docs/SEMANTICS.md b/adalbert-contracts/docs/SEMANTICS.md new file mode 100644 index 0000000..da297b4 --- /dev/null +++ b/adalbert-contracts/docs/SEMANTICS.md @@ -0,0 +1,1127 @@ +--- +title: "Adalbert Formal Semantics" +subtitle: "Deterministic Policy Evaluation for Data Governance" +version: "0.7" +status: "Draft" +date: 2026-02-03 +abstract: | + Adalbert is a proper ODRL 2.2 profile with deterministic, total evaluation + semantics and bilateral agreement support. This document specifies the formal + semantics in a style amenable to mechanization in Dafny, Why3, or similar + verification frameworks. +--- + +## 1. Introduction + +Adalbert addresses semantic gaps in ODRL 2.2: + +1. **Duty Ambiguity**: ODRL conflates pre-conditions with post-obligations +2. **Unilateral Agreements**: ODRL evaluates only from assignee perspective +3. **Undefined States**: ODRL permits evaluation to be "undefined" + +Adalbert provides: + +- Explicit duty lifecycle with state machine semantics +- Bilateral agreement evaluation (grantor and grantee duties) +- Total evaluation functions (always terminate with defined result) +- Clear separation of Condition (pre-requisite) from Duty (obligation) +- Path-based operand resolution with formal grammar and security constraints + +### 1.1 Scope and Runtime Boundary + +This specification defines **evaluation semantics** — the contract a conformant engine +must satisfy. The `State` parameter in `Eval` is an opaque input provided by the +runtime environment. Adalbert specifies what decision and state transitions *should* +result from evaluation, but does not define: + +- How `State` is persisted or managed between evaluations +- Event-driven triggers for duty activation or deadline enforcement +- Protocols for requirement fulfillment claims or re-evaluation + +These operational concerns are out of scope for a declarative policy profile. +Implementations requiring runtime state management, event processing, and enforcement +protocols should consult RL2, which extends Adalbert's evaluation semantics with a +complete operational protocol layer. + +### 1.2 Notation + +- `×` for Cartesian product +- `→` for function types +- `∪` for union +- `∈` for set membership +- `⊥` for undefined/bottom value +- `⟦e⟧` for denotation of expression `e` +- `Γ ⊢ e : τ` for typing judgement ("in context Γ, expression e has type τ") + +### 1.2 Document Status + +This document is **normative** for Adalbert implementations. + +--- + +## 2. Type System + +The type system ensures well-formed policies. + +### 2.1 Typing Judgements + +We use: + +``` +Γ ⊢ e : τ +``` + +Where Γ is a typing context mapping identifiers to types. + +### 2.2 Types + +``` +τ ::= Agent | Action | Asset | Condition | Time | Duration | Boolean | Value | Norm | State | Policy +``` + +### 2.3 Key Typing Rules + +Permission: + +``` +Γ ⊢ a : Agent Γ ⊢ x : Action Γ ⊢ s : Asset Γ ⊢ c : Condition +--------------------------------------------------------------------------- + Γ ⊢ Permission(a, x, s, c) : Norm +``` + +Duty: + +``` +Γ ⊢ a : Agent Γ ⊢ x : Action Γ ⊢ s : Asset +Γ ⊢ c : Condition Γ ⊢ dl : Deadline Γ ⊢ r : Recurrence +-------------------------------------------------------------------------- + Γ ⊢ Duty(a, x, s, c, dl, r) : Norm +``` + +Prohibition: + +``` +Γ ⊢ a : Agent Γ ⊢ x : Action Γ ⊢ s : Asset Γ ⊢ c : Condition +--------------------------------------------------------------------------- + Γ ⊢ Prohibition(a, x, s, c) : Norm +``` + +AtomicConstraint: + +``` +Γ ⊢ left : LeftOperand Γ ⊢ op : ComparisonOperator Γ ⊢ right : Value +------------------------------------------------------------------------------- + Γ ⊢ AtomicConstraint(left, op, right) : Condition +``` + +Logical connectives follow standard typing rules for Boolean-valued expressions. + +--- + +## 3. Abstract Syntax + +We define Adalbert's abstract syntax using a typed algebraic grammar. + +### 3.1 Syntactic Domains + +| Domain | Symbol | Description | +|--------|--------|-------------| +| Agents | **A** | Set of agent identifiers | +| Actions | **X** | Set of action identifiers | +| Assets | **S** | Set of asset identifiers | +| Values | **V** | Set of atomic values (strings, numbers, URIs) | +| Time | **T** | Time domain (ISO 8601 instants) | +| Duration | **D** | Duration domain (ISO 8601 durations) | + +### 3.2 Norms + +``` +Norm ::= Permission(subject: Agent, action: Action, asset: Asset, condition: Condition?) + | Duty(subject: Agent, action: Action, asset: Asset, + object: Agent?, condition: Condition?, deadline: Deadline?, recurrence: Recurrence?) + | Prohibition(subject: Agent, action: Action, asset: Asset, condition: Condition?) + +Deadline ::= AbsoluteDeadline(time: Time) + | RelativeDeadline(duration: Duration) + +Recurrence ::= RRule(rule: String) +``` + +**Notes**: + +- `Permission` corresponds to `odrl:Permission`; `Prohibition` to `odrl:Prohibition`; `Duty` to `odrl:Duty`. +- The formal `subject` parameter maps to `adalbert:subject` on duties (rdfs:subPropertyOf `odrl:assignee`) and to `odrl:assignee` on permissions/prohibitions. +- The formal `object` parameter (duties only) maps to `adalbert:object` — the party affected by the duty action (e.g., who is notified). Not used in norm matching. +- `AbsoluteDeadline`: Fixed point in time (e.g., 2026-12-31T23:59:59Z) +- `RelativeDeadline`: Duration from activation (e.g., P30D, PT24H) + +**Abstract-to-RDF Name Mapping**: + +| Abstract Syntax | RDF Encoding | Notes | +|---|---|---| +| `Permission` | `odrl:Permission` | | +| `Duty` | `odrl:Duty` | | +| `Prohibition` | `odrl:Prohibition` | | +| `subject` (Permission/Prohibition) | `odrl:assignee` | Party to whom the norm applies | +| `subject` (Duty) | `adalbert:subject` | Party bearing the duty (rdfs:subPropertyOf odrl:assignee) | +| `object` (Duty) | `adalbert:object` | Party affected by the duty action (metadata, not used in matching) | +| `grantor` | `odrl:assigner` | Party granting rights | +| `grantee` | `odrl:assignee` | Party receiving rights (policy-level) | +| `condition` | `odrl:constraint` | | +| `Set` | `odrl:Set` | | +| `Offer` | `odrl:Offer` | `DataContract` is a subtype | +| `Agreement` | `odrl:Agreement` | `Subscription` is a subtype | +| `lte` | `odrl:lteq` | | +| `gte` | `odrl:gteq` | | +| `recurrence` | `adalbert:recurrence` | RFC 5545 RRULE string | + +### 3.3 Conditions + +``` +Condition ::= AtomicConstraint(leftOperand: LeftOperand, + operator: ComparisonOperator, + rightOperand: Value) + | And(operands: Condition+) + | Or(operands: Condition+) + | Not(operand: Condition) + +ComparisonOperator ::= eq | neq | lt | lte | gt | gte | isAnyOf | isNoneOf + +RuntimeRef ::= currentAgent | currentDateTime +``` + +**Notes**: + +- `leftOperand` is drawn from profile-defined operands with `resolutionPath`, or dual-typed `RuntimeReference` operands (e.g., `currentDateTime`) +- Dynamic value resolution on the left side uses `LeftOperand` with `adalbert:resolutionPath` or dual-typed `RuntimeReference` operands resolved via `resolveRuntime` +- Right operands are literal values. Identity binding via runtime references in right-operand position is deferred to RL2 (`rl2:rightOperandRef`) + +### 3.4 Policies + +``` +Policy ::= Set(target: Asset?, clauses: Norm+, condition: Condition?) + | Offer(grantor: Agent, grantee: Agent?, target: Asset?, clauses: Norm+, condition: Condition?) + | Agreement(grantor: Agent, grantee: Agent, target: Asset?, clauses: Norm+, condition: Condition?) +``` + +**Notes**: + +- `Set`: Unilateral declaration, no parties. Maps to `odrl:Set` in the RDF encoding. +- `Offer`: Proposal from grantor, grantee optional (open offer). Maps to `odrl:Offer`. `DataContract` is a subtype of `Offer`. +- `Agreement`: Bilateral binding, both parties identified. Maps to `odrl:Agreement`. `Subscription` is a subtype of `Agreement`. +- The formal `grantor` parameter maps to `odrl:assigner` (the party granting rights) in the RDF encoding. +- The formal `grantee` parameter maps to `odrl:assignee` (the party receiving rights) in the RDF encoding. + +### 3.5 Requests + +``` +Request ::= Request(agent: Agent, action: Action, asset: Asset, context: Context) + +Context ::= Map +``` + +**Note**: Context keys correspond to the second segment of resolution paths (e.g., the path `context.purpose` resolves by looking up `"purpose"` in `Context`). + +--- + +## 4. Semantic Domains + +### 4.1 State + +``` +Σ = { + clock : Time, + state : Duty → State, + activatedAt : Duty → Time?, // When duty became Active + performed : Set<(Agent, Action, Asset, Time)> +} + +State ::= Pending | Active | Fulfilled | Violated +``` + +**Notes**: + +- `State` is a unified lifecycle enum shared by duties, contracts, and subscriptions. The formal semantics tracks duty state during evaluation; contract and subscription state is administrative (not evaluated at request time). +- Terminal states (`Fulfilled`, `Violated`) are permanent — see §9.4. + +**Initial state** Σ₀: + +``` +Σ₀ = { + clock = currentSystemTime, + state = λd. Pending, + activatedAt = λd. ⊥, + performed = ∅ +} +``` + +**State Update Notation**: We use `Σ[f ↦ v]` to denote state update: + +``` +Σ[state(d) ↦ Active] = + (Σ.clock, Σ.state[d ↦ Active], Σ.activatedAt, Σ.performed) + +Σ[state(d) ↦ Active, activatedAt(d) ↦ t] = + (Σ.clock, Σ.state[d ↦ Active], Σ.activatedAt[d ↦ t], Σ.performed) +``` + +### 4.2 Environment + +``` +Env = { + agent : Agent, // Canonical root: agent + action : Action, + asset : Asset, // Canonical root: asset + context : Context, // Canonical root: context + Σ : Σ +} +``` + +The three canonical roots (`agent`, `asset`, `context`) are the entry points for `deref` path resolution (see §6.3). + +**Environment Construction**: Given a Request `R = (a, x, s, ctx)` and state Σ: + +``` +buildEnv(R, Σ) = { + agent = R.agent, + action = R.action, + asset = R.asset, + context = R.context, + Σ = Σ +} +``` + +### 4.3 Decision + +``` +Decision ::= Permit | Deny | NotApplicable +``` + +### 4.4 Evaluation Result + +``` +Result = { + decision : Decision, + grantorDuties : Set, // Duties on the grantor (data provider) + granteeDuties : Set, // Duties on the grantee (data consumer) + violations : Set, + explanation : Explanation +} +``` + +--- + +## 5. Duty Lifecycle + +### 5.1 State Diagram + +``` + condition becomes true + ┌─────────────────────────────────────┐ + │ ▼ + ┌───────┐ ┌────────┐ + │Pending│ │ Active │ + └───────┘ └────────┘ + │ │ + action performed │ │ deadline exceeded + ▼ ▼ + ┌──────────┐ ┌─────────┐ + │Fulfilled │ │Violated │ + └──────────┘ └─────────┘ +``` + +### 5.2 Transition Rules + +**Rule D-ACTIVATE** (Pending → Active): + +``` +Σ.state(duty) = Pending +duty.condition = ⊥ ∨ ⟦duty.condition⟧(Env) = true +───────────────────────────────────────────────── +Σ' = Σ[state(duty) ↦ Active, activatedAt(duty) ↦ Σ.clock] +``` + +Activation is **condition-driven**: when the duty's condition first evaluates to true (or the duty has no condition), the duty becomes active. + +**Rule D-FULFILL** (Active → Fulfilled): + +``` +Σ.state(duty) = Active +performed(duty.subject, duty.action, duty.asset, Σ) = true +effectiveDeadline(duty, Σ) ≥ Σ.clock ∨ effectiveDeadline(duty, Σ) = ∞ +──────────────────────────────────────────────────────────────────────── +Σ' = Σ[state(duty) ↦ Fulfilled] +``` + +Fulfillment is **event-driven**: when a performed action matches the duty's required action (including narrower actions via `includedIn` subsumption), the duty is fulfilled. + +**Rule D-VIOLATE** (Active → Violated): + +``` +Σ.state(duty) = Active +Σ.clock > effectiveDeadline(duty, Σ) +performed(duty.subject, duty.action, duty.asset, Σ) = false +──────────────────────────────────────────────────────────── +Σ' = Σ[state(duty) ↦ Violated] +``` + +Violation is **time-driven**: when the deadline passes without fulfillment, the duty is violated. + +**Algorithmic form** (for implementation): + +``` +updateDutyStates(duties, Env, Σ) = + foldl(updateOneDuty(Env), Σ, duties) + +updateOneDuty(Env)(Σ, d) = + case Σ.state(d) of + Pending → if d.condition = ⊥ ∨ ⟦d.condition⟧(Env) + then Σ[state(d) ↦ Active, activatedAt(d) ↦ Σ.clock] + else Σ + Active → if performed(d.subject, d.action, d.asset, Σ) + then Σ[state(d) ↦ Fulfilled] + else if Σ.clock > effectiveDeadline(d, Σ) + then Σ[state(d) ↦ Violated] + else Σ + _ → Σ -- Fulfilled/Violated are terminal +``` + +### 5.3 Effective Deadline + +``` +effectiveDeadline(duty, Σ) = + case duty.deadline of + ⊥ → ∞ + AbsoluteDeadline(t) → t + RelativeDeadline(d) → Σ.activatedAt(duty) + d +``` + +### 5.4 Match Semantics + +Action and asset matching support hierarchy subsumption: + +``` +x₁ matches x₂ ⟺ x₁ = x₂ ∨ x₁ includedIn⁺ x₂ +s₁ matches s₂ ⟺ s₁ = s₂ ∨ s₁ partOf⁺ s₂ +``` + +Where `includedIn⁺` and `partOf⁺` are the transitive closures of `odrl:includedIn` and `adalbert:partOf` respectively. + +Agent matching supports hierarchy subsumption: + +``` +a₁ matches a₂ ⟺ a₁ = a₂ ∨ a₁ memberOf⁺ a₂ +``` + +Where `memberOf⁺` is the transitive closure of `adalbert:memberOf`. + +The subsumption-aware performed check is: + +``` +performed(a, x, s, Σ) := + ∃(a', x', s', t) ∈ Σ.performed : + a' = a ∧ (x' = x ∨ x' includedIn⁺ x) ∧ s' matches s +``` + +`Σ.performed` records exact actions as they occur. `performed()` is the query-time subsumption check used in all fulfillment and violation rules. This is a bounded graph traversal over the `odrl:includedIn` hierarchy. + +### 5.5 Recurrence Semantics + +A duty with a `recurrence` field defines a recurring obligation. The recurrence value is an RFC 5545 RRULE string (e.g., `FREQ=DAILY;BYHOUR=6;BYMINUTE=0`). + +**Instance Generation**: + +``` +expand : Recurrence × Time → Set