diff --git a/dprod-contracts/README.md b/dprod-contracts/README.md new file mode 100644 index 0000000..e6e9c78 --- /dev/null +++ b/dprod-contracts/README.md @@ -0,0 +1,159 @@ +# DPROD Data Contracts: Deterministic Policy Language for Data Governance + +**An ODRL 2.2 profile with deterministic evaluation semantics for data governance.** + +--- + +## What This Is + +DPROD Contracts is an ODRL 2.2 profile. It uses ODRL terms for all standard constructs (Permission, Duty, Prohibition, Agreement, etc.) and only adds extensions where ODRL 2.2 leaves behavior undefined: + +1. **Explicit lifecycle**: Pending -> Active -> Fulfilled/Violated (unified for duties and contracts) +2. **Bilateral agreements**: Both assigner and assignee may have duties +3. **Deterministic evaluation**: Total functions, no undefined states +4. **Formal verification target**: Amenable to Dafny, Why3, Coq +5. **Structured operand resolution**: SPARQL-style `dprod:path` property paths +6. **Recurring duties**: `recurrence` via RFC 5545 RRULE for scheduled obligations + +DPROD Contracts is **specification-first**. The semantics document defines what any conformant implementation must do. Every DPROD policy is a valid ODRL 2.2 policy. + +--- + +## Quick Start + +See [examples/](examples/) for complete working policies: + +- [data-contract.ttl](examples/data-contract.ttl) -- DataOffer, DataContract, bilateral duties +- [data-use-policy.ttl](examples/data-use-policy.ttl) -- Role-based access, purpose constraints + +--- + +## What DPROD Contracts Adds to ODRL 2.2 + +| Extension | ODRL 2.2 | What DPROD Adds | +|-----------|----------|-----------------| +| Duty lifecycle | Undefined | Pending -> Active -> Fulfilled/Violated | +| Bilateral duties | Unilateral (assignee only) | Assigner duties + assignee duties | +| Conflict resolution | Configurable | Fixed: Prohibition > Permission | +| Evaluation order | Undefined | Deterministic left-to-right | +| Operand resolution | Implicit | Explicit `dprod:path` property paths (+ `dprod:select` for complex resolution) | +| Recurring duties | -- | `recurrence` via RFC 5545 RRULE with per-instance `deadline` | +| Contract types | -- | `DataOffer` (subclass of Offer), `DataContract` (subclass of Agreement) | + +**Note**: DPROD Contracts is an ODRL profile, not a parallel vocabulary. Standard ODRL processors can parse DPROD policies; DPROD-aware processors additionally enforce lifecycle, bilateral duties, and deterministic evaluation. + +--- + +## Design Principles + +### 1. Specification Precedes Implementation + +The [formal semantics](docs/formal-semantics.md) is the normative reference. + +### 2. Total Functions + +Every evaluation terminates with a defined result: + +``` +Eval : Request x PolicySet x State -> Decision x DutySet +``` + +### 3. Bilateral Agreements + +Agreements return duties for **both** parties: + +``` +Result = { + decision: Permit | Deny | NotApplicable, + assignerDuties: Set, // Provider obligations (SLAs) + assigneeDuties: Set, // Consumer obligations + violations: Set +} +``` + +### 4. Unified Lifecycle State + +Duties and contracts share four states: + +``` + condition true +Pending ──────────────> Active + │ │ + action done │ │ deadline passed + ▼ ▼ + Fulfilled Violated +``` + +An `odrl:Duty` progresses through `dprod:State` values. A `dprod:DataOffer` shares the same state machine. + +### 5. Structured Operand Resolution + +Operands resolve via `dprod:path` -- SPARQL-style property paths from the evaluation context: + +```turtle +# Context-rooted (single-step): direct property on request +ex:environment dprod:path ex:environment . + +# Asset-rooted (two-step): via odrl:target +ex:timeliness dprod:path (odrl:target ex:timeliness) . + +# Agent-rooted (two-step): via odrl:assignee +ex:recipientType dprod:select "SELECT ?v WHERE { $request odrl:assignee/ex:recipientType ?v }" . +``` + +--- + +## Repository Structure + +``` +dprod-contracts/ +├── dprod-contracts.ttl # Core ontology (ODRL profile extension) +├── dprod-contracts-shapes.ttl # SHACL validation shapes +├── dprod-contracts-prof.ttl # DXPROF profile declaration +├── examples/ +│ ├── data-contract.ttl # Complete contract example (with recurrence) +│ ├── data-use-policy.ttl # Access control example +│ └── baseline.ttl # Comprehensive test data (8 offers, 2 contracts) +└── docs/ + ├── overview.md # What is DPROD Contracts? (start here) + ├── specification.md # Technical vocabulary reference + ├── term-mapping.md # Business term -> property mapping + ├── formal-semantics.md # Formal semantics (normative) + ├── contracts-guide.md # Data contract authoring guide + └── policy-writers-guide.md # Data use policy authoring guide +``` + + +--- + +## Namespaces + +| Prefix | Namespace | Role | +|--------|-----------|------| +| `odrl:` | `http://www.w3.org/ns/odrl/2/` | Primary -- all standard constructs | +| `dprod:` | `https://www.omg.org/spec/DPROD/` | Extensions (State, deadline, recurrence, DataOffer, DataContract, path, select, hierarchy) + domain-specific actions, operands, and concept values | + +--- + +## Conformance + +An implementation conforms to DPROD Contracts if: + +1. It accepts policies that validate against `dprod-contracts-shapes.ttl` +2. It uses `odrl:Permission`, `odrl:Duty`, `odrl:Prohibition`, `odrl:Agreement` for standard constructs +3. Its evaluation function produces identical results for identical inputs +4. All functions are total (no undefined behavior) +5. State transitions match the operational semantics +6. Agreement evaluation returns duties for both assigner and assignee + +--- + +## References + +- [ODRL 2.2 Information Model](https://www.w3.org/TR/odrl-model/) +- [W3C Market Data Profile](https://www.w3.org/2021/md-odrl-profile/v1/) +- [W3C ODRL Profile Best Practices](https://www.w3.org/community/reports/odrl/CG-FINAL-profile-bp-20240808.html) + +--- + +**Version**: 0.7 | **Date**: 2026-02-04 diff --git a/dprod-contracts/docs/changes-plan.md b/dprod-contracts/docs/changes-plan.md new file mode 100644 index 0000000..3c993f5 --- /dev/null +++ b/dprod-contracts/docs/changes-plan.md @@ -0,0 +1,368 @@ +# Plan: Apply outstanding suggestions from Matthias + +Working plan for the DPROD-contracts review items not yet applied on `add-dprod-contracts`. +Source emails (oldest -> newest): `19d08a1d` 2026-03-19, `19d92a2a` 2026-04-15, `19da58f8` 2026-04-19, `19db5fe8` 2026-04-22, Stephen 2026-05-14 (DataDuty consistency / DataOffer target / acceptsOffer basket model). + +Buckets, in suggested execution order: +1. Quick textual fixes (low risk, no semantics) +2. SHACL shape improvements (mechanical) +3. DCON remnant cleanup (documentation) +4. Recurrence redesign (one large change; spec already drafted in `recurrence-redesign.md`) +5. Ontology design decisions +6. Deferred / needs more info + +A "verify" step is included for each item so the work can be checked without re-reading the email. + +--- + +## Triage notes (2026-05-10) + +Cross-cutting status pass over all items in this plan. Verified line numbers and existence of each cited file/string before classifying. Where the original plan had an incorrect premise or imprecise line number, that is flagged here and corrected in-line under the item. + +### Done already +- **1.1** — marked PASS in-line. +- **1.4** — marked Done in-line. +- **3.3** — done 2026-05-10; "+ DCON migration" trimmed from `dprod-contracts/README.md:120`; `rg` for DCON across both READMEs returns nothing. +- **3.1** — done 2026-05-10; DCON Migration section + redundant `---` removed from `overview.md`, table row + inventory cell trimmed; `rg` for DCON in overview.md returns nothing. +- **3.2** — done 2026-05-10; both `**DCON equivalent**` lines removed, the "replaces DCON's ProviderQualityPromise" sentence trimmed, and the `## DCON Migration` section + redundant `---` removed from `contracts-guide.md`; `rg` confirms zero hits. +- **2.1** — done 2026-05-10; `sh:message "…must be a single xsd:dateTime value."` added to all four date property shapes (DataOffer + DataContract × effectiveDate + expirationDate). Wording covers both `sh:maxCount` and `sh:datatype` violations. +- **2.2** — done 2026-05-10; all five Reject* shapes (`Xone`, `Remedy`, `Consequence`, `InheritAllowed`, `InheritFrom`) swapped from `sh:SPARQLTarget` SPARQL queries to declarative `sh:targetSubjectOf`. SHACL-Core only, no SHACL-SPARQL dependency. Validation outcomes unchanged. +- **1.5** — done 2026-05-10; Option A executed across three files. `rdfs:comment` rewritten on `dprod:currentDateTime`; `formal-semantics.md` §6.2 gets a Normalisation paragraph + `let ref' = …` line in `resolveRuntime`; `specification.md:163` parenthetical updated. Optional info-shape step skipped. +- **5.3** — done 2026-05-10; Option A (keep `subPropertyOf` + document deliberate domain narrowing). Both `dprod:partOf` and `dprod:memberOf` `rdfs:comment` blocks extended. +- **5.2** — done 2026-05-14; hybrid of option (a). Renamed `dprod:subject` → `dprod:subjectOfDuty` and `dprod:object` → `dprod:objectOfDuty`; both now `rdfs:subPropertyOf odrl:function` (symmetric, mirrors the W3C Market Data ODRL Profile's duty-scoped `md:subject`/`md:object`). Propagated across `dprod-contracts.ttl`, `dprod-contracts-shapes.ttl`, all three example TTLs, and all docs (spec, guides, formal-semantics, term-mapping, og.md). All TTL files re-parsed cleanly with rdflib. +- **3.4** — done 2026-05-14; no action. The plan's premise conflated two distinct things: `dct:conformsTo` is the *predicate* ("the data conforms to schema X" — a static fact, already in core DPROD) and `ex:conformTo` is the *ODRL action* in the concept scheme used inside Duty patterns ("the provider must conform to X by date Y" — a deontic obligation). They are not redundant ways of saying the same thing; they encode different commitments. Current usage is correct as-is — nothing to rewrite. +- **5.1** — done 2026-05-14. Confirmed all contracts terms are minted under the core `dprod:` namespace (they always were — the prefix was already `dprod:`). Cleaned up the confusing `@base <…/contracts/>` directive in `dprod-contracts.ttl` (no relative IRIs depended on it) and re-pointed every `rdfs:isDefinedBy` from `<…/contracts/>` to `dprod:` so the terms self-declare as part of core DPROD. The artifact-level `owl:Ontology` IRI at `<…/contracts/>` is kept because `dprod-contracts-prof.ttl` and `dprod-contracts-shapes.ttl` reference it as a `prof:hasArtifact` / `owl:imports` target. All three TTL files re-parse cleanly with rdflib. +- **5.6** — done 2026-05-10; Option D (do nothing). Plan's premise was wrong: nothing in DPROD-contracts is typed `dct:Standard` (that label sits correctly on `odrl:core`). Current `prof:Profile` + `owl:Ontology` typing kept; `voaf:Vocabulary` not added. + +### Superseded by group decision (no longer a quick textual fix) +- **1.3** — group agreed to remove `owl:oneOf` and split `dprod:State` into Contract-state and Duty-state subclasses. This is now a Bucket-5 design task: needs a naming choice and a list of which evaluation algorithms branch on which subclass. +- Rename to life cycle state so a :dutyLifeCycleState and a :dataContractLifeCycleState. +### Quick wins — low risk, no decision needed (verified) + + +### Needs decision +- **5.4** — `dprod:path` OWL typing. Affects 2.3 and Bucket 6.2. +- **5.5** — `dprod:select`: implement, mark non-normative, or delete. +- **1.3 follow-on** — naming and algorithm-branching for the Contract-state / Duty-state split. +- **5.7** — consistency: introduce `dprod:DataDuty` subtype of `odrl:Duty`? (Stephen, 2026-05-14) +- **5.8** — `odrl:target` on DataOffer: drop to exactly 1? (Stephen, 2026-05-14; couples with 5.9) +- **5.9** — `dprod:acceptsOffer` on DataContract: allow 1..* (basket model)? (Stephen, 2026-05-14; couples with 5.8) + +### Could decide unilaterally if you want to dispatch +- **2.3** — SHACL shape for `dprod:path` value structure: mechanical if you keep it SHACL-Core. Soft-blocks on 5.4 only if 5.4 changes how `dprod:path` is OWL-typed. + +### Large but spec'd — schedule, do not re-decide +- **Bucket 4 (recurrence redesign)** — `recurrence-redesign.md` exists (213 lines) and contains the design. Execute as one PR. The former item 1.2 (line-34 header rewording) is now item 4.6 and travels with this PR. + +### Deferred — needs external input +- **6.1** — fetch `fixme.md` Gmail attachment for message `19d7952e` before triaging. +- **6.2** — downstream of decision 5.4. + +### Suggested execution order (refines the existing one at the bottom) +1. **Now (independent commits, all verified):** 1.5 (apply the three Option-A edits), 2.1, 2.2, 2.4, 3.1, 3.2, 3.3. (Item 1.2 is no longer standalone — folded into Bucket 4 as item 4.6.) +2. **Then:** 2.3 if you want to dispatch it unilaterally. +3. **Surface to group in one message:** 5.4, 5.5, 5.7, 5.8+5.9 (treat as one bundle — they are tightly coupled), plus the 1.3 follow-on. +4. **Fetch attachment** for 6.1. +5. **Bucket 4** as a dedicated PR. +6. **Bucket 5 → Bucket 6.2** after decisions land. + +--- + +## Bucket 1 — Quick textual fixes + +### 1.1 README: "SHACL-style" -> "SPARQL-style" property paths +- Source: 19db5fe8 item A +- Why: README claims SHACL-style paths, but the operand resolution path grammar in `dprod:path` is SPARQL property-path syntax. +- Where: `README.md` (root) — Matthias cited lines 15 and 91, but root README is short and does not contain those strings; the actual occurrence is `dprod-contracts/dprod-contracts.ttl:38` and likely in one of the contracts docs. Search-and-replace across `dprod-contracts/`. +- Steps: + 1. `rg -n "SHACL-style" dprod-contracts/` to locate every occurrence. + 2. Replace "SHACL-style property paths" -> "SPARQL-style property paths" wherever it describes `dprod:path`. + 3. Update `dprod-contracts.ttl:38` (header description bullet) and any docs hits. +- Verify: `rg -n "SHACL-style" dprod-contracts/` returns nothing. +- PASS (re-swept 2026-05-28: six remaining occurrences across `dprod-contracts.ttl`, `README.md`, `docs/specification.md`, `docs/formal-semantics.md` replaced; only `changes-plan.md` itself still mentions the historical phrase to describe this task). + +### 1.2 Ontology header: recurrence description (line 34) +- MOVED (2026-05-10) to item 4.6 in Bucket 4. The rewording only makes sense once the property definition at `dprod-contracts.ttl:175-188` has been updated, so it travels with the recurrence redesign rather than as a standalone textual fix. + +### 1.3 Closed-world note on `dprod:State owl:oneOf` +- Source: 19d08a1d item #6 +- Why: `owl:oneOf` is a closed-world enumeration — extenders cannot add new states. Matthias asked us to acknowledge that this is deliberate so reviewers do not treat it as a bug. +- Where: `dprod-contracts/dprod-contracts.ttl:64-85`. +- Steps: + 1. Append a paragraph to the existing `rdfs:comment` on `dprod:State`: "These four states are intentionally closed (`owl:oneOf`); profiles MUST NOT introduce new states because evaluation algorithms branch on them." +- Verify: text appears in `:State` comment; ontology still parses. +- NOTE: ignore: removed owl:oneOf - Group agreed that this should be open. Follow on action: The group has decided that we will split the two-state into two separate properties: (Contract state and Duty state +They should subclass from the DPROD enumeration) + +### 1.4 `examples/odcs.md` reference to non-existent `odcs-translated.ttl` +- Source: 19d08a1d item #17 +- Why: Doc points readers at a file that doesn't exist; the actual example is `odcs.ttl`. +- Where: `dprod-contracts/examples/odcs.md:243`. +- Steps: + 1. Change `odcs-translated.ttl` -> `odcs.ttl` in that line and any other occurrences. +- Verify: `rg -n "odcs-translated" dprod-contracts/` returns nothing; the cited file exists. +- NOTE: Done + +### 1.5 Clarify `dprod:currentDateTime` vs `odrl:dateTime` +- Source: 19d08a1d item #15 +- Why: Today the comment only says "Equivalent to odrl:dateTime" + `rdfs:seeAlso odrl:dateTime`. ODRL implementers will not know which to use, or whether they alias. +- Where: `dprod-contracts/dprod-contracts.ttl:322-330`. +- Steps: + 1. Extend `rdfs:comment` to specify: ODRL evaluators use `odrl:dateTime`; DPROD evaluators MUST treat `dprod:currentDateTime` as the canonical operand and resolve `odrl:dateTime` to the same value at evaluation time. + 2. Add `owl:sameAs odrl:dateTime` only if you actually want OWL inference; otherwise keep `rdfs:seeAlso` and document the equivalence in the formal semantics. +- Verify: `formal-semantics.md` mentions both names and states which is canonical; ontology still parses. +- DECISION (2026-05-10): **Option A** — `dprod:currentDateTime` is canonical; document the rule, no OWL `sameAs`. Concrete edits: + 1. `dprod-contracts.ttl:321-329` — rewrite `rdfs:comment` to: "Canonical DPROD operand for the evaluation timestamp. Dual-typed as `dprod:RuntimeReference` so `resolveRuntime` binds it to the evaluator's clock. ODRL's `odrl:dateTime` is the upstream equivalent; DPROD evaluators MUST canonicalise `odrl:dateTime` to `dprod:currentDateTime` before resolution. Keep `rdfs:seeAlso`; do NOT assert `owl:sameAs` (DPROD evaluation does not require an OWL reasoner)." + 2. `formal-semantics.md` §6.2 (around line 614/622) — add a one-line normalization step before the `resolveRuntime` switch: "if `op = odrl:dateTime` then `op ← currentDateTime`". Cross-reference the ontology comment. + 3. `specification.md:163` — change parenthetical from "(mapped to `odrl:dateTime`)" to "(canonical form; `odrl:dateTime` normalises to this)". + 4. (Optional, deferred) SHACL `sh:Info`-severity shape on `odrl:Constraint` flagging `odrl:leftOperand odrl:dateTime` with message "use `dprod:currentDateTime` for DPROD contracts". Skip for now to keep the change small. +- Reasoning: formal-semantics already canonicalises on `currentDateTime` (line 622: `currentDateTime → Env.Σ.clock`); examples already use it; option B (`owl:sameAs`) would unilaterally extend the ODRL namespace and bind DPROD evaluation to an OWL reasoner that implementers don't otherwise need; option C (drop `currentDateTime`) loses the dual-typing trick that triggers `resolveRuntime`. +- DONE (2026-05-10): three edits applied. (1) Rewrote `rdfs:comment` on `dprod:currentDateTime` at `dprod-contracts.ttl:325` as a triple-quoted multi-line literal that names the operand as canonical, spells out the evaluator canonicalisation rule, and explains why equivalence sits on `rdfs:seeAlso` instead of `owl:sameAs`. (2) Added a "Normalisation" paragraph plus a `let ref' = if ref = odrl:dateTime then currentDateTime else ref` line to `resolveRuntime` in `formal-semantics.md` §6.2. (3) Updated `specification.md:163` parenthetical to "(canonical form; `odrl:dateTime` normalises to this)". Optional step 4 (SHACL info-shape) intentionally skipped. +--- + +## Bucket 2 — SHACL shape improvements + +### 2.1 Add `sh:message` to date shapes +- Source: 19db5fe8 item C +- Why: Validators that report violations on missing/wrong `effectiveDate`/`expirationDate` will surface `sh:datatype` errors with no human message — bad UX. +- Where: `dprod-contracts/dprod-contracts-shapes.ttl` — `effectiveDate` shapes at lines 291 (DataOffer) and 331 (DataContract); `expirationDate` shapes at lines 296 (DataOffer) and 336 (DataContract). +- Steps: + 1. Add `sh:message "effectiveDate, if present, must be xsd:dateTime."` to each `effectiveDate` property shape. + 2. Add `sh:message "expirationDate, if present, must be xsd:dateTime."` to each `expirationDate` property shape. +- Verify: `rg -n "effectiveDate|expirationDate" dprod-contracts/dprod-contracts-shapes.ttl -A 4` shows `sh:message` under each occurrence. +- DONE (2026-05-10): added `sh:message` to all four shapes (DataOffer + DataContract × effectiveDate + expirationDate) at lines 294, 300, 336, 342. Used unified wording "effectiveDate/expirationDate, if present, must be a single xsd:dateTime value." so the message is correct for both `sh:datatype` and `sh:maxCount` violations (the property shape's single message applies to both constraints). Note: line numbers shifted by 3 vs the pre-edit range (291/296/331/336 → 291/297/333/339 for the `sh:path` lines). + +### 2.2 Replace `sh:SPARQLTarget` with `sh:targetSubjectOf` in Reject* shapes +- Source: 19db5fe8 item D +- Why: Five Reject* shapes use `sh:SPARQLTarget` to find the subjects of `odrl:xone`/`odrl:remedy`/`odrl:consequence`/`odrl:inheritAllowed`/`odrl:inheritFrom`. `sh:targetSubjectOf` does exactly this declaratively, without SHACL-SPARQL extension. +- Where: `dprod-contracts/dprod-contracts-shapes.ttl:373, 390, 407, 472, 489`. +- Steps: + 1. For each shape (`RejectXoneShape`, `RejectRemedyShape`, `RejectConsequenceShape`, `RejectInheritAllowedShape`, `RejectInheritFromShape`) replace the `sh:target [ a sh:SPARQLTarget ; sh:select "..." ]` block with `sh:targetSubjectOf `. + 2. Drop the `sh:property [ sh:path ; sh:maxCount 0 ; ... ]` block in favour of the existing `sh:message`/`sh:severity`. With `sh:targetSubjectOf` the violation message can sit on the NodeShape directly OR be moved to a `sh:property` with `sh:path ; sh:maxCount 0` — keep whichever style matches the Ticket/Request shapes for consistency. + 3. Confirm Ticket/Request/AssetCollection/PartyCollection shapes (which use `sh:targetClass` + `sh:sparql`) stay as-is — they are class-targeted, not predicate-targeted. +- Verify: `rg -n "sh:SPARQLTarget" dprod-contracts/` returns no hits; SHACL validation suite (if any) still passes; conformance examples flag the same violations as before. +- DONE (2026-05-10): all five Reject* shapes refactored. The 7-line `sh:target [ a sh:SPARQLTarget; sh:select ... ]` block in each was replaced with a single `sh:targetSubjectOf ` line; the existing `sh:property [ sh:path ; sh:maxCount 0; sh:severity; sh:message ]` block was left untouched. Verified: `sh:SPARQLTarget` no longer appears in any source file (only in this plan's descriptive text); five new `sh:targetSubjectOf` lines at 376, 387, 398, 457, 468. Ignored the plan's "match Ticket/Request style" hint per triage — those shapes solve a different problem (rejecting class membership via `sh:sparql` constraint, not predicate presence). + +### 2.3 SHACL shape validating `dprod:path` value structure +- Source: 19d08a1d item #10 +- Why: `dprod:path` is "either a single property IRI or an RDF list of property IRIs". Today only cardinality is enforced (`LeftOperandShape` at `:346-356`). A malformed value (e.g. a literal) silently breaks evaluation. +- Where: `dprod-contracts/dprod-contracts-shapes.ttl:346-363`. +- Steps: + 1. Inside `LeftOperandShape`, extend the `sh:path dprod:path` property shape with `sh:or ( [ sh:nodeKind sh:IRI ] [ sh:node dprod-shapes:RdfListOfIris ] )`. + 2. Add a new shape `dprod-shapes:RdfListOfIris` that walks `rdf:rest*/rdf:first` and asserts each element is `sh:nodeKind sh:IRI`. (SHACL-Core idiom: `sh:property [ sh:path ( [ sh:zeroOrMorePath rdf:rest ] rdf:first ) ; sh:nodeKind sh:IRI ] .`) +- Verify: a `LeftOperand` whose `dprod:path` is a literal triggers a violation; existing examples in `dprod-contracts/examples/` still validate. + +### 2.4 Reject-shape for per-policy `odrl:conflict odrl:prohibit` override +- Source: 19d08a1d item #13 +- Why: DPROD spec fixes the conflict policy to "Prohibition > Permission". Per-policy `odrl:conflict` would silently change semantics. Should be rejected by SHACL. +- Where: `dprod-contracts/dprod-contracts-shapes.ttl` (new shape, after the existing Reject* block). +- Steps: + 1. Add `dprod-shapes:RejectPolicyConflictShape` with `sh:targetSubjectOf odrl:conflict` and a `sh:property [ sh:path odrl:conflict ; sh:maxCount 0 ; sh:severity sh:Violation ; sh:message "DPROD contracts fix conflict resolution to Prohibition > Permission; per-policy odrl:conflict is not allowed." ]`. +- Verify: A test policy with `odrl:conflict odrl:prohibit` triggers a violation; existing examples without it still pass. + +--- + +## Bucket 3 — DCON remnant cleanup + +`term-mapping.md` already had its DCON content removed (commit f51b0b3). The remaining files still reference DCON or link to the now-empty section in term-mapping. + +### 3.1 Remove "DCON Migration" section from `overview.md` +- Source: 19d92a2a, 19db5fe8 items G/I +- Where: `dprod-contracts/docs/overview.md:211-220`. +- Steps: + 1. Delete the "## DCON Migration" heading (line 211) and its bullet list (through line 220). + 2. Delete the "Migrating from DCON" row in the role table at `:233`. + 3. Update the inventory at `:242` ("Business term -> property mapping + DCON migration") to drop the trailing "+ DCON migration". +- Verify: `rg -n "DCON" dprod-contracts/docs/overview.md` returns nothing. +- DONE (2026-05-10): deleted the section + the trailing `---` to avoid two adjacent separators; removed the table row; trimmed the inventory cell. `rg` confirms zero DCON hits in overview.md. + +### 3.2 Drop DCON references from `contracts-guide.md` +- Source: 19db5fe8 item I (agent cited lines 211, 228, 232, 550-552). +- Where: `dprod-contracts/docs/contracts-guide.md`. +- Steps: + 1. `rg -n "DCON|dcon:" dprod-contracts/docs/contracts-guide.md` to enumerate. + 2. For each hit, decide: (a) delete the DCON-specific paragraph, or (b) rewrite without DCON. Most are likely "if migrating from DCON, ..." asides — delete. + 3. If a "Further reading" link points to `term-mapping.md#dcon-migration`, drop the anchor or remove the link. +- Verify: `rg -n "DCON|dcon:" dprod-contracts/docs/contracts-guide.md` returns nothing. +- DONE (2026-05-10): removed both `**DCON equivalent**` lines (after Schema and Notification patterns); trimmed the "This replaces DCON's `ProviderQualityPromise`." sentence from the Quality SLA paragraph; removed the `## DCON Migration` section + redundant `---` near the end. `rg` confirms zero hits. + +### 3.3 Drop DCON entry from root README ToC +- Source: 19db5fe8 item I (agent cited `README.md:120`). +- Where: `README.md`. (Note: the root README we read is only 21 lines. The line-120 reference was likely in `dprod-contracts/README.md` — re-check there.) +- Steps: + 1. `rg -n "DCON|dcon:" README.md dprod-contracts/README.md`. + 2. Remove ToC entries / cross-references to the deleted DCON Migration section. +- Verify: `rg -n "DCON|dcon:" README.md dprod-contracts/README.md` returns nothing. +- DONE (2026-05-10): trimmed "+ DCON migration" from `dprod-contracts/README.md:120`. Root `README.md` had no DCON refs. Verify `rg` returns no hits — confirmed. + +### 3.4 Standardize on `dprod:conformsTo`, drop `ex:conformTo` action pattern +- Source: 19db5fe8 item H +- Why: Two ways to express the same thing — `dprod:conformsTo` (a property) vs an `odrl:action ex:conformTo` duty pattern. Keep one. +- Where: `dprod-contracts/docs/contracts-guide.md:54, 116, 206, 232, 238, 323, 365, 435` (and any examples). +- Steps: + 1. Confirm `dprod:conformsTo` is defined in `dprod-contracts.ttl` with appropriate domain/range; if not, define it as the canonical form. + 2. Walk each cited line; for each "duty with `odrl:action ex:conformTo`" idiom, rewrite to use `dprod:conformsTo` directly on the asset/contract. If the duty wraps additional metadata (deadline, recurrence) keep the duty but switch its action — discuss with author before mass-rewrite. + 3. Update `examples/` files that demonstrate the action pattern. +- Verify: `rg -n "ex:conformTo|conformTo" dprod-contracts/` shows only `dprod:conformsTo` occurrences (or none) in normative docs/examples. +- Risk: this changes example semantics — pause after step 1 and confirm the direction with group before doing the bulk rewrite. +- CORRECTION (2026-05-10): The plan's premise is wrong. `dprod:conformsTo` does **not** exist anywhere. What actually exists: + - `dct:conformsTo` — used by core DPROD (`ontology/dprod/dprod-shapes.ttl:97,112,293` define `Distribution-conformsTo`/`Dataset-conformsTo` with `sh:path dct:conformsTo`); also in `dprod-contracts-prof.ttl:39,45` and the recurrence-spec design. + - `ex:conformTo` — example-namespace `odrl:Action`, used in Duty patterns in `examples/baseline.ttl`, `examples/odcs.ttl`, `contracts-guide.md`, `term-mapping.md`, `policy-writers-guide.md`. +- Reframed decision space (needs group): + - **(a) Replace `ex:conformTo` Duty pattern with direct `dct:conformsTo` triples on the asset.** Loses the deontic framing — "the data conforms to X" becomes a static fact, not an enforceable obligation. Probably wrong for SLA/quality use where deadlines/constraints attach to the duty. + - **(b) Promote `ex:conformTo` to a real DPROD action `dprod:conformTo`** (typed as `odrl:Action`). Same Duty pattern, stable IRI, out of the example namespace. Minimal semantic change. + - **(c) Hybrid (recommended starting point):** keep `dct:conformsTo` for static "this is the schema we conform to" facts (already in core DPROD), AND mint `dprod:conformTo` as an `odrl:Action` for the Duty pattern. They coexist because they mean different things — fact vs obligation. +- Until decision is made, **do not** bulk-rewrite. The current `ex:conformTo` usages are functionally fine but live in the example namespace. + +--- + +## Bucket 4 — Recurrence redesign (large) + +The spec is already drafted in `dprod-contracts/docs/recurrence-redesign.md`. This section operationalises that spec. + +Source: 19da58f8 (full proposal in recurrence-redesign.md). + +### 4.1 Add `dprod:RecurrenceSpec` class +- Where: `dprod-contracts.ttl`, after `dprod:RuntimeReference` block. +- Steps: + 1. Copy the class definition from `recurrence-redesign.md` lines 9-51 into the ontology. + 2. Add `prov:Plan` superclass if you want recurrence specs to participate in PROV (open question — see 5.x). +- Verify: ontology parses; `RecurrenceSpec` resolves under `https://www.omg.org/spec/DPROD/`. + +### 4.2 Add `dprod:RRuleScheme` and `dprod:CrontabScheme` named individuals +- Where: same file, after the new class. +- Steps: + 1. Copy individual definitions from `recurrence-redesign.md` lines 61-89. + 2. Sanity-check the `rdfs:seeAlso` URLs still resolve (RFC 5545 §3.3.10, opengroup crontab spec). +- Verify: each is `a dprod:RecurrenceSpec` and has `rdfs:seeAlso`. + +### 4.3 Replace `dprod:recurrence` definition +- Where: `dprod-contracts.ttl:176-189` (current `owl:DatatypeProperty` form). +- Steps: + 1. Delete the current `dprod:recurrence` block. + 2. Insert the replacement block from `recurrence-redesign.md:133-193` (`owl:ObjectProperty` with `rdfs:range dprod:RecurrenceSpec`). +- Verify: ontology parses; range is `dprod:RecurrenceSpec`. + +### 4.4 Update SHACL shapes for recurrence +- Where: `dprod-contracts-shapes.ttl` (whichever shape currently constrains `dprod:recurrence`; if none, add one). +- Steps: + 1. Replace any `sh:datatype xsd:string` constraint with `sh:or ( [ sh:nodeKind sh:IRI ] [ sh:node dprod-shapes:RecurrenceSpecShape ] )`. + 2. Define `RecurrenceSpecShape`: requires exactly one `dct:conformsTo` (range `dprod:RecurrenceSpec`), exactly one `rdf:value` (xsd:string), nothing else. +- Verify: examples with both styles validate; a string literal directly under `dprod:recurrence` fails. + +### 4.5 Update examples +- Where: `dprod-contracts/examples/*.ttl` and `dprod-contracts/examples/odcs.ttl` in particular (uncommitted exploratory edits already there — see `git status`). +- Steps: + 1. Reconcile the uncommitted `odcs.ttl` edits with the canonical pattern from `recurrence-redesign.md`. The existing edits use names like `dprod:IcalRecurrence` / `dprod:CronRecurrence` and `dc:conformsTo` — these need to become `dprod:RRuleScheme` / `dprod:CrontabScheme` and `dct:conformsTo`. + 2. Add at least one expression-based and one vocabulary-based recurrence example. +- Verify: examples parse; SHACL validation passes; the chosen names match the ontology. + +### 4.6 Update header bullet at `dprod-contracts.ttl:34` +- Source: 19da58f8 (also referenced in `recurrence-redesign.md` section "Also update the ontology header description (line 34)"). +- Why: Header still says "RFC 5545 RRULE" but the redesign permits crontab and frequency URIs too. Apply this last in the Bucket-4 PR so the description matches the property definition that 4.3 introduces. +- Where: `dprod-contracts/dprod-contracts.ttl:34`. +- Steps: + 1. Replace `- Recurrence property on odrl:Duty (RFC 5545 RRULE)` with `- Recurrence property on odrl:Duty (iCal RRULE, crontab, or frequency URI)`. +- Verify: line 34 reads the new wording; ontology still parses (`riot --validate dprod-contracts/dprod-contracts.ttl`). +- (Originally tracked as item 1.2 under Bucket 1; folded in here on 2026-05-10 because it only makes sense once the property definition has changed.) + +### 4.7 Update prose docs +- Where: `dprod-contracts/docs/overview.md`, `contracts-guide.md`, `formal-semantics.md`, `specification.md`. +- Steps: + 1. Find existing recurrence prose: `rg -n "recurrence|RRULE|RFC 5545" dprod-contracts/docs/`. + 2. Reframe as "either a structured `RecurrenceSpec` (RRULE/crontab/...) or a frequency URI". +- Verify: docs no longer claim "must be an RRULE string". + +### 4.8 Delete `dprod-contracts/docs/recurrence-redesign.md` once 4.1-4.7 are merged. +- Verify: `git status` clean. + +--- + +## Bucket 5 — Ontology design decisions + +### 5.2 `dprod:subject subPropertyOf odrl:assignee` — wrong inference? +- Source: 19d08a1d items #2/#3, 19db5fe8 item B +- Where: `dprod-contracts.ttl:241-263`. +- Issue: Today `dprod:subject` -> `odrl:assignee` and `dprod:object` -> `odrl:function`. Matthias argues `odrl:assignee` is the wrong super-property — the duty bearer in ODRL is the *assignee* of an action, but DPROD's `subject` is "the party who must perform" which fits `odrl:function`'s "party affected" semantics inversely. Matthias also flags that the rename to clearer names (`bearerParty`/`affectedParty` or `assignerParty`/`assigneeParty`) would resolve the confusion entirely. +- Decision: **(a)** Rename `subject`/`object` to descriptive names AND fix the super-property choice, or **(b)** Keep names, just swap super-properties, or **(c)** Leave alone and document the intentional choice. +- If (a): coordinated rename across ontology + shapes + docs + examples — non-trivial. +- DONE (2026-05-14): Hybrid of option (a). Renamed `dprod:subject` → `dprod:subjectOfDuty` and `dprod:object` → `dprod:objectOfDuty` (the `OfDuty` suffix makes the duty-only scope explicit and matches the SHACL domain). Super-property changed: both are now `rdfs:subPropertyOf odrl:function` (symmetric; the previously-asserted `odrl:assignee` parent on `subject` produced the inference Matthias flagged). The W3C Market Data ODRL Profile sets the precedent — `md:subject`/`md:object` are stand-alone duty-scoped properties with no `subPropertyOf` to `odrl:assignee`; we keep a single bridge to the abstract `odrl:function` umbrella so ODRL processors still see *some* role link. Propagated across shapes, all three example TTLs, and all docs (spec, guides, formal-semantics, term-mapping, og.md). `rdfs:label` left as "subject"/"object" for human-readable display. + +### 5.3 `dprod:memberOf subPropertyOf odrl:partOf` +- Source: 19d08a1d item #4 +- Where: `dprod-contracts.ttl:230-240`. +- Issue: ODRL's `partOf` is generic (party or asset). Sub-classing for parties only is fine if the domain restriction is enforced, which it is (`rdfs:domain odrl:Party`). Matthias flagged this as "debatable" — the inference is correct, just narrow. Probably keep. +- Decision: keep, drop, or replace with `skos:broader` style? Default: keep + add an `rdfs:comment` explaining the deliberate domain restriction. +- DONE (2026-05-10): Option A executed. Kept both `subPropertyOf odrl:partOf` axioms; extended `rdfs:comment` on both `dprod:partOf` and `dprod:memberOf` to explain that domain/range are deliberately narrowed (Asset/Asset and Party/Party respectively) and that DPROD does not produce mixed party-and-asset hierarchies. Door left open: revisit Option B (drop the subPropertyOf) is cheap if future feedback shows inferred `odrl:partOf` triples cause trouble in real deployments. + +### 5.4 `dprod:path` lacks OWL type and `rdfs:range` +- Source: 19d08a1d item #5 +- Where: `dprod-contracts.ttl:266-277`. +- Issue: declared `a rdf:Property` (not `owl:ObjectProperty` or `owl:DatatypeProperty`) and no `rdfs:range`. Tooling that loads the ontology as OWL DL will treat it as untyped. Range is technically `rdfs:Resource | rdf:List` — awkward for OWL DL. +- Decision: **(a)** Type as `owl:ObjectProperty` with `rdfs:range [ owl:unionOf ( rdfs:Resource rdf:List ) ]` (works in OWL Full), **(b)** Leave as `rdf:Property` and document why, or **(c)** Split into two properties (`dprod:pathSimple` -> IRI, `dprod:pathSequence` -> rdf:List). Most ontologies in the wild pick (b) and add a SHACL shape (covered in 2.3). + +### 5.5 `dprod:select` — wire in or remove +- Source: 19d08a1d item #12 +- Where: `dprod-contracts.ttl:279-290`; check `formal-semantics.md` for usage. +- Issue: Defined but the resolve algorithm ignores it. Either dead code or unfinished feature. +- Decision: **(a)** Implement in formal semantics (a SPARQL evaluation step that runs when `dprod:select` is present), or **(b)** Mark as non-normative ("for future use"), or **(c)** Delete entirely. + +### 5.6 Profile decl: `dct:Standard` vs `voaf:Vocabulary` +- Source: 19d08a1d item #16 +- Where: `dprod-contracts/dprod-contracts-prof.ttl:16`. +- Issue: Upstream ODRL profile typings often use `voaf:Vocabulary`; DPROD-contracts uses `dct:Standard`. Mostly cosmetic; affects how vocabulary catalogs (LOV, etc.) display the profile. +- Decision: keep `dct:Standard`, add `voaf:Vocabulary`, or replace. +- CORRECTION (2026-05-10): the plan's premise is partly wrong. Nothing in DPROD-contracts is typed `dct:Standard`. The `dct:Standard` at `dprod-contracts-prof.ttl:16` is on `odrl:core` (the upstream ODRL spec), which is the correct primitive for "this is the reference specification we extend". The three actual root resources are: + - `odrl:core` → `dct:Standard` (correct as-is; the upstream spec) + - `` (DPROD profile) → `prof:Profile` in `dprod-contracts-prof.ttl:24-25` + - `` (contracts ontology) → `owl:Ontology` in `dprod-contracts.ttl:23-24` + Reframed, the real question is whether to *add* `voaf:Vocabulary` (for LOV discoverability) to the contracts ontology root, the profile node, or neither. +- DONE (2026-05-10): **Option D — do nothing.** Current `prof:Profile` + `owl:Ontology` typing is correct and DXPROF-compliant. `voaf:Vocabulary` would only matter for LOV-style auto-discovery, which isn't a priority. Revisit cheap if LOV indexing becomes a goal. + +### 5.7 Consistency: introduce `dprod:DataDuty` as a subtype of `odrl:Duty`? +- Source: Stephen (email, 2026-05-14) — comment #1. +- Where: `dprod-contracts.ttl` — `dprod:DataOffer` (`rdfs:subClassOf odrl:Offer`) and `dprod:DataContract` (`rdfs:subClassOf odrl:Agreement`) are confirmed subclasses, but no `dprod:DataDuty` exists; the duty-specific properties (`dprod:subjectOfDuty`, `dprod:objectOfDuty`, `dprod:deadline`, `dprod:recurrence`, `dprod:state`) hang directly off `odrl:Duty`. +- Issue: Inconsistent. The Offer/Agreement extensions are scoped to a DPROD subclass; the Duty extension is *not*, so it widens the meaning of any `odrl:Duty` that happens to share a graph with DPROD. Stephen argues a `dprod:DataDuty` subclass would mirror the Offer/Contract pattern and contain the lifecycle/deadline/recurrence semantics inside DPROD-land. +- Decision: **(a)** Mint `dprod:DataDuty rdfs:subClassOf odrl:Duty` and re-domain the duty-only properties (`subjectOfDuty`, `objectOfDuty`, `deadline`, `recurrence`, `state` restricted to duty case) to `dprod:DataDuty`; update SHACL `DutyShape` to target `dprod:DataDuty` rather than `odrl:Duty`. Coordinated change across ontology + shapes + examples + docs. Or **(b)** Document the asymmetry as deliberate (duty extensions are *cross-cutting* and apply to any ODRL duty in a DPROD-profile graph, by design). Or **(c)** Run both: add `dprod:DataDuty` as an annotation-only subclass with no shape change. +- Risk: Option (a) is non-trivial because `dprod:state` has a union domain `(odrl:Duty | dprod:DataOffer | dprod:DataContract)` — re-domaining the duty branch means split state semantics or leave the union as-is. + +### 5.8 `odrl:target` cardinality on `dprod:DataOffer` — drop from 1..* to 1? +- Source: Stephen (email, 2026-05-14) — comment #2. +- Where: `dprod-contracts-shapes.ttl` — `dprod-shapes:SetShape:99-102` allows unbounded `odrl:target` at the policy level (inherited by `OfferShape` and `DataOfferShape`); `dprod-shapes:DataOfferShape:275-302` adds no further `odrl:target` constraint. +- Issue: Current shape permits multiple targets per `dprod:DataOffer`. This forces inline duties to re-declare `odrl:target` to disambiguate which dataset each obligation refers to. Stephen argues for a tighter model: exactly one `odrl:target` per offer, and bundle multiple offers into a contract instead (see 5.9). Outcome: simpler offers, cleaner duty scoping, easier reuse. +- Decision: **(a)** Constrain `dprod-shapes:DataOfferShape` to `sh:minCount 1; sh:maxCount 1` on `odrl:target` and remove the per-duty `odrl:target` workaround in examples. Or **(b)** Keep 1..* and document when authors should split into multiple offers. Or **(c)** Leave as 0..* (matches ODRL Set inheritance) and treat single-target as a convention. +- Coupling: tightly linked to 5.9 — if 5.9 lets a contract accept multiple offers, 5.8 (single target per offer) becomes the natural composition unit. + +### 5.9 `dprod:acceptsOffer` cardinality on `dprod:DataContract` — 1 vs 1..*? +- Source: Stephen (email, 2026-05-14) — comment #3 ("shopping basket" model). +- Where: `dprod-contracts.ttl:192-198` (`dprod:acceptsOffer` property) and `dprod-contracts-shapes.ttl:324-330` (`sh:minCount 1; sh:maxCount 1; sh:class dprod:DataOffer`). +- Issue: Today a `dprod:DataContract` references exactly one `dprod:DataOffer`. Stephen's reference-data use case has 100s of distributions from one provider, of which any subscriber takes a different subset — so the 1:1 model either forces 80 near-duplicate contracts, or one contract referencing a 100-distribution offer with extra metadata about which 80 obligations apply. Stephen proposes: pair (5.8) "single-target offer" with (5.9) `dprod:acceptsOffer` cardinality `1..*` so a contract is a *basket* of accepted offers. Bonus: lets provider-duties and consumer-duties be authored as separate offers and composed in the contract, instead of stuffed into one generic provider offer. +- Decision: **(a)** Change `dprod-shapes:DataContractShape` `dprod:acceptsOffer` to `sh:minCount 1` only (drop `sh:maxCount`), accepting the basket model. Or **(b)** Keep `1`, document the workaround (one mega-offer + selective obligation refs). Or **(c)** Add a sibling property `dprod:acceptsOffers` (plural, basket) and keep `acceptsOffer` as the single-offer shorthand. +- Coupling: depends on 5.8. Whatever is decided here, the formal-semantics resolve algorithm (norm matching across the contract's offers) needs an explicit rule. + +--- + +## Bucket 6 — Deferred / needs more info + +### 6.1 `fixme.md` attachment from email 19d7952e (2026-04-10) +- Email body says only "Maybe useful?" — actual feedback in attached `fixme.md`. We have not retrieved the attachment. +- Action: download the attachment via `mcp__google_workspace__get_gmail_attachment_content` for message `19d7952e55251e29` and re-evaluate. + +### 6.2 Path-traversal sandboxing/grammar (Adalbert's `deref`) +- Source: 19d08a1d "hardening" note. +- Issue: An earlier prototype by Adalbert constrained `dprod:path` with a formal grammar that excluded back-references and circular paths. The current ontology dropped it. Whether to restore is partly a security question (untrusted policy authors) and partly a complexity question. +- Action: revisit after Bucket 5 decisions are made; this is downstream of 5.4. + +--- + +## Suggested execution order + +1. Bucket 1 items 1.1, 1.3, 1.4, 1.5 — small, independent commits. (1.2 moved to 4.6.) +2. Bucket 2 items 2.1, 2.2 — independent commits. +3. Bucket 3 — one commit per file (overview, contracts-guide, README), then 3.4 separately because it's larger. +4. Bucket 5 design decisions — surface as discussion points (Slack / call). Block Bucket 4 only if 5.4 changes how `dprod:path` is typed (it doesn't really; orthogonal). +5. Bucket 4 — single feature branch / PR; large diff, easy to review as one unit. +6. Bucket 5 changes after decisions are taken. +7. Bucket 6 once attachments are read. + +After each bucket: `riot --validate` on every `.ttl`, run any SHACL conformance suite under `dprod-contracts/examples/`. diff --git a/dprod-contracts/docs/contracts-guide.md b/dprod-contracts/docs/contracts-guide.md new file mode 100644 index 0000000..1584b25 --- /dev/null +++ b/dprod-contracts/docs/contracts-guide.md @@ -0,0 +1,564 @@ +# DPROD Contracts Guide + +A guide for data platform teams using DPROD for data contracts and subscriptions. + +--- + +## Overview + +DPROD models internal data sharing agreements as ODRL 2.2 policies. A **DataOffer** is an offer from a data provider. A **DataContract** is an activated offer binding provider and consumer. + +``` +DataOffer (Offer) DataContract (Agreement) ++--------------------------+ +--------------------------+ +| Provider duties | | Provider duties | +| Consumer rights | | Consumer rights | +| Prohibitions | | Consumer duties | +| Recurrence rules | | Prohibitions | ++--------------------------+ | State tracking | + accept +--------------------------+ + --------> +``` + +--- + +## Key Concepts + +### DataOffer (Offer) + +A `dprod:DataOffer` 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 `dprod:subjectOfDuty` = 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 + +### DataContract (Agreement) + +A `dprod:DataContract` 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 DPROD actions: + +| Action | Description | Example | +|--------|-------------|---------| +| `ex:deliver` | Deliver data to consumers | Daily market data delivery | +| `ex:notify` | Send notifications | Schema change notification | +| `ex:conformTo` | Maintain conformance to a standard | Schema/quality SLA | + +### Consumer Duties + +| Action | Description | Example | +|--------|-------------|---------| +| `ex:report` | Submit usage reports | Monthly usage reporting | + +### Permissions + +Standard ODRL actions apply: + +| Action | Description | +|--------|-------------| +| `odrl:display` | Display data | +| `ex:nonDisplay` | Non-display (algorithmic) use | +| `odrl:derive` | Create derived products | +| `odrl:read` | Read data | + +--- + +## Step-by-Step Cookbook + +Create your first DataOffer in five steps. + +### Step 1: Declare the Contract + +Every contract starts with a type, profile declaration, and provider identity. + +```turtle +@prefix odrl: . +@prefix dprod: . +@prefix ex: . +@prefix xsd: . + +ex:contract a dprod:DataOffer ; + odrl:profile ; + odrl:assigner ex:dataTeam ; + odrl:target ex:marketPrices ; + dprod:state dprod:Active . +``` + +### Step 2: Add Provider Duties (SLAs) + +Provider duties declare what the data team commits to. The provider is identified by `dprod:subjectOfDuty` on each duty. Use `dprod:objectOfDuty` 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 ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:deliver ; + dprod:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; + dprod:deadline "PT30M"^^xsd:duration + ] ; + + # Schema conformance (different target -- not inherited) + odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex: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 ex:nonDisplay ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand ex:recipientType ; + odrl:operator odrl:eq ; + odrl:rightOperand ex: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 ex:report ; + odrl:target ex:usageStats ; + dprod:deadline "P30D"^^xsd:duration + ] ; + + # No external redistribution (target inherited from policy) + odrl:prohibition [ + a odrl:Prohibition ; + odrl:action odrl:distribute + ] . +``` + +### Step 5: Create a DataContract + +When a consumer accepts the offer, create a DataContract (Agreement) referencing the offer: + +```turtle +ex:subscription a dprod:DataContract ; + odrl:profile ; + dprod:acceptsOffer ex:contract ; + odrl:assigner ex:dataTeam ; + odrl:assignee ex:analyticsTeam ; + dprod:effectiveDate "2026-02-01T00:00:00Z"^^xsd:dateTime ; + dprod:expirationDate "2026-12-31T23:59:59Z"^^xsd:dateTime . +``` + +The contract materializes all duties from the offer with explicit `dprod:subjectOfDuty` 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 ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:deliver ; + dprod:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; + dprod:deadline "PT30M"^^xsd:duration +] . +``` + + +### Schema Pattern (Schema Conformance) + +Provider guarantees data conforms to a published schema. Target differs from policy -- specify explicitly. + +```turtle +odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:conformTo ; + odrl:target ex:marketDataSchema +] . +``` + +### Notification Pattern (Change Notification) + +Provider must notify consumers before making changes, with a lead time expressed as a duration deadline. Use `dprod:objectOfDuty` to identify who is notified. + +```turtle +odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + dprod:objectOfDuty ex:consumer ; + odrl:action ex:notify ; + odrl:target ex:schemaChanges ; + dprod:deadline "P14D"^^xsd:duration +] . +``` + +### Quality SLA Pattern + +Provider guarantees data quality via `conformTo` with a constraint. + +```turtle +odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:conformTo ; + odrl:target ex:riskMetricsSchema ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand ex:timeliness ; + odrl:operator odrl:eq ; + odrl:rightOperand ex:realtime + ] +] . +``` + +The constraint can check any DPROD operand -- timeliness, classification, environment, etc. + +--- + +## Recurrence + +Recurring duties use `dprod:recurrence` -- an RFC 5545 RRULE string. Combined with `dprod:deadline`, this defines a schedule and fulfillment window. + +```turtle +odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:deliver ; + dprod:recurrence "FREQ=DAILY;BYHOUR=6;BYMINUTE=0" ; + dprod: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 dprod:DataOffer ; + 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 dprod:DataOffer ; + odrl:profile ; + odrl:assigner ex:dataTeam ; + odrl:target ex:customerData ; + odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:deliver ; + dprod:recurrence "FREQ=DAILY;BYHOUR=7;BYMINUTE=0" ; + dprod:deadline "PT30M"^^xsd:duration + ] ; + odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:conformTo ; + odrl:target ex:customerSchema + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:display + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action ex:nonDisplay + ] ; + odrl:obligation [ + a odrl:Duty ; + odrl:action ex:report ; + odrl:target ex:usageStats ; + dprod: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 dprod:DataOffer ; + odrl:profile ; + odrl:assigner ex:dataTeam ; + odrl:target ex:riskMetrics ; + odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:deliver ; + dprod:recurrence "FREQ=MINUTELY;INTERVAL=1" ; + dprod:deadline "PT30S"^^xsd:duration + ] ; + odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:conformTo ; + odrl:target ex:riskSchema ; + odrl:constraint [ + a odrl:Constraint ; + odrl:leftOperand ex:timeliness ; + odrl:operator odrl:eq ; + odrl:rightOperand ex:realtime + ] + ] ; + odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex:notify ; + odrl:target ex:schemaChanges ; + dprod:deadline "P14D"^^xsd:duration + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:display + ] ; + odrl:permission [ + a odrl:Permission ; + odrl:action ex: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 dprod:DataOffer ; + 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 dprod:DataOffer ; + odrl:target ex:marketPrices ; # policy-level target + odrl:permission [ + a odrl:Permission ; + odrl:action odrl:read # inherits ex:marketPrices + ] ; + odrl:obligation [ + a odrl:Duty ; + dprod:subjectOfDuty ex:dataTeam ; + odrl:action ex: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 `dprod: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 ; + dprod:memberOf ex:analyticsTeam . + +ex:analyticsTeam a odrl:Party ; + dprod:memberOf ex:tradingDivision . +``` + +### Version Chains + +Use `prov:wasRevisionOf` to link contract versions: + +```turtle +ex:contract-v2 a dprod:DataOffer ; + prov:wasRevisionOf ex:contract-v1 . + +ex:contract-v3 a dprod:DataOffer ; + prov:wasRevisionOf ex:contract-v2 . +``` + +DataContracts reference the specific offer version they activate: + +```turtle +ex:subscription dprod:acceptsOffer ex:contract-v2 . +``` + +### Expiration + +Contracts and subscriptions can have explicit expiration dates: + +```turtle +ex:subscription a dprod:DataContract ; + dprod:effectiveDate "2026-01-15T00:00:00Z"^^xsd:dateTime ; + dprod: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 dprod:DataOffer ; + prov:wasRevisionOf ex:contract-v1 . +``` + +DataContracts reference the offer they activate via `dprod:acceptsOffer`: + +```turtle +ex:subscription dprod:acceptsOffer 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). + +--- + +## Validation Checklist + +1. Every policy declares `odrl:profile ` +2. Conflict strategy (`odrl:conflict odrl:prohibit`) is inherited from the profile -- do not repeat per-policy +3. DataOffer has `odrl:assigner` (provider) +4. DataContract has both `odrl:assigner` and `odrl:assignee` +5. DataContract has `dprod:acceptsOffer` referencing a DataOffer +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 `dprod:subjectOfDuty` 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 `dprod:not` +13. Validate against SHACL shapes: + +```bash +shacl validate --shapes dprod-contracts-shapes.ttl --data my-contract.ttl +``` diff --git a/dprod-contracts/docs/formal-semantics.md b/dprod-contracts/docs/formal-semantics.md new file mode 100644 index 0000000..803c463 --- /dev/null +++ b/dprod-contracts/docs/formal-semantics.md @@ -0,0 +1,1115 @@ +--- +title: "DPROD Contracts Formal Semantics" +subtitle: "Deterministic Policy Evaluation for Data Governance" +version: "0.7" +status: "Draft" +date: 2026-02-03 +abstract: | + DPROD Contracts 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 + +DPROD Contracts 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" + +DPROD Contracts 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) +- Property-path-based operand resolution with SPARQL-style traversal semantics + +### 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. DPROD Contracts 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 DPROD Contracts'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 DPROD Contracts 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 DPROD Contracts'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 `dprod:subjectOfDuty` on duties (rdfs:subPropertyOf `odrl:function`) and to `odrl:assignee` on permissions/prohibitions. +- The formal `object` parameter (duties only) maps to `dprod:objectOfDuty` — 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) | `dprod:subjectOfDuty` | Party bearing the duty (rdfs:subPropertyOf odrl:function) | +| `object` (Duty) | `dprod:objectOfDuty` | 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` | `DataOffer` is a subtype | +| `Agreement` | `odrl:Agreement` | `DataContract` is a subtype | +| `lte` | `odrl:lteq` | | +| `gte` | `odrl:gteq` | | +| `recurrence` | `dprod: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 `dprod:path`, or dual-typed `RuntimeReference` operands (e.g., `currentDateTime`) +- Dynamic value resolution on the left side uses `LeftOperand` with `dprod:path` (and optionally `dprod:select`) 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`. `DataOffer` is a subtype of `Offer`. +- `Agreement`: Bilateral binding, both parties identified. Maps to `odrl:Agreement`. `DataContract` 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 properties correspond to the leaf of `dprod:path` declarations (e.g., `dprod:path odrl:purpose` resolves `?request odrl:purpose ?value`). + +--- + +## 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 evaluation context is the entry point for `dprod:path` property path traversal (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 `dprod:partOf` respectively. + +Agent matching supports hierarchy subsumption: + +``` +a₁ matches a₂ ⟺ a₁ = a₂ ∨ a₁ memberOf⁺ a₂ +``` + +Where `memberOf⁺` is the transitive closure of `dprod: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