eyeling 1.12.14 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/HANDBOOK.md CHANGED
@@ -761,6 +761,8 @@ A predicate is treated as builtin if:
761
761
 
762
762
  Super restricted mode exists to let you treat all other predicates as ordinary facts/rules without any built-in evaluation.
763
763
 
764
+ **Note on `log:query`:** Eyeling also recognizes a special _top-level_ directive of the form `{...} log:query {...}.` to **select which results to print**. This is **not** a builtin predicate (it is not evaluated as part of goal solving); it is handled by the parser/CLI/output layer. See §11.3.5 below and Chapter 13 for details.
765
+
764
766
  ### 11.2 Built-ins return multiple solutions
765
767
 
766
768
  Every builtin returns a list of substitution _deltas_.
@@ -1353,6 +1355,47 @@ As _builtins_, `log:implies` and `log:impliedBy` let you **inspect the currently
1353
1355
 
1354
1356
  Each enumerated rule is standardized apart (fresh variable names) before unification so you can safely query over it.
1355
1357
 
1358
+ ### Top-level directive: `log:query` (output selection)
1359
+
1360
+ **Shape (top level only):**
1361
+
1362
+ ```n3
1363
+ { ...premise... } log:query { ...conclusion... }.
1364
+ ```
1365
+
1366
+ `log:query` is best understood as an **output projection**, not as a rule and not as a normal builtin:
1367
+
1368
+ - Eyeling still computes the saturated forward closure (facts + rules, including backward-rule proofs where needed).
1369
+ - It then proves the **premise formula** as a goal (as if it were fed to `log:includes` in the global scope).
1370
+ - For every solution, it instantiates the **conclusion formula** and collects the resulting triples.
1371
+ - The final output is the **set of unique ground triples** from those instantiated conclusions.
1372
+
1373
+ This is “forward-rule-like” in spirit (premise ⇒ conclusion), but the instantiated conclusion triples are **not added back into the fact store**; they are just what Eyeling prints.
1374
+
1375
+ **Important details:**
1376
+
1377
+ - Only **top-level** `{...} log:query {...}.` directives are recognized. Inside quoted formulas (or inside rule bodies/heads) it is just an ordinary triple.
1378
+ - Query-mode output depends on the saturated closure, so it cannot be streamed; `--stream` has no effect when any `log:query` directives are present.
1379
+ - If you want _logical_ querying inside a rule/proof, use `log:includes` (and optionally `log:conclusion`) instead.
1380
+
1381
+ **Example (project a result set):**
1382
+
1383
+ ```n3
1384
+ @prefix : <urn:ex:>.
1385
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
1386
+
1387
+ { :a :p ?x } => { :a :q ?x }.
1388
+ :a :p :b.
1389
+
1390
+ { :a :q ?x } log:query { :result :x ?x }.
1391
+ ```
1392
+
1393
+ Output (only):
1394
+
1395
+ ```n3
1396
+ :result :x :b .
1397
+ ```
1398
+
1356
1399
  ### Scoped proof inside formulas: `log:includes` and friends
1357
1400
 
1358
1401
  #### `log:includes`
@@ -1809,6 +1852,14 @@ See also: [Chapter 14 — Entry points: CLI, bundle exports, and npm API](#ch14)
1809
1852
 
1810
1853
  By default, Eyeling prints **newly derived forward facts** (the heads of fired `=>` rules), serialized as N3. It does **not** reprint your input facts.
1811
1854
 
1855
+ If the input contains one or more **top-level** `log:query` directives:
1856
+
1857
+ ```n3
1858
+ { ...premise... } log:query { ...conclusion... }.
1859
+ ```
1860
+
1861
+ Eyeling still computes the saturated forward closure, but it prints only the **unique instantiated conclusion triples** of those `log:query` directives (instead of all newly derived facts). This is useful when you want a forward-rule-like projection of results.
1862
+
1812
1863
  For proof/explanation output and output modes, see:
1813
1864
 
1814
1865
  - [Chapter 13 — Printing, proofs, and the user-facing output](#ch13)
@@ -1835,6 +1886,8 @@ Options:
1835
1886
  -v, --version Print version and exit.
1836
1887
  ```
1837
1888
 
1889
+ Note: when `log:query` directives are present, Eyeling cannot stream output (the selected results depend on the saturated closure), so `--stream` has no effect in that mode.
1890
+
1838
1891
  See also:
1839
1892
 
1840
1893
  - [Chapter 13 — Printing, proofs, and the user-facing output](#ch13)
@@ -2031,7 +2084,7 @@ If you don’t want “stop the world”, derive a `:Violation` fact instead, an
2031
2084
  The most robust way to keep LLM-generated logic plausible is to make it live under tests:
2032
2085
 
2033
2086
  - Keep tiny **fixtures** (facts) alongside the rules.
2034
- - Run Eyeling to produce the **derived closure** (Eyeling can emit only newly derived forward facts, and can optionally include compact proof comments).
2087
+ - Run Eyeling to produce the **derived closure** (Eyeling emits only newly derived forward facts by default, can optionally include compact proof comments, and can also use `log:query` directives to project a specific result set).
2035
2088
  - Compare against an expected output (“golden file”) in CI.
2036
2089
 
2037
2090
  This turns rule edits into a normal change-management loop: diffs are explicit, reviewable, and reproducible.
package/README.md CHANGED
@@ -4,7 +4,8 @@ A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
4
4
 
5
5
  - Single self-contained bundle (`eyeling.js`), no external runtime deps
6
6
  - Forward (`=>`) + backward (`<=`) chaining over Horn-style rules
7
- - Outputs only **newly derived** forward facts (optionally with compact proof comments)
7
+ - Outputs only **newly derived** forward facts by default (optionally with compact proof comments)
8
+ - If the input contains one or more top-level `{ ... } log:query { ... }.` directives, the output becomes the **unique instantiated conclusion triples** of those queries (a forward-rule-like projection)
8
9
  - Works in Node.js and fully client-side (browser/worker)
9
10
 
10
11
  ## Links
@@ -45,6 +46,16 @@ See all options:
45
46
  npx eyeling --help
46
47
  ```
47
48
 
49
+ ### log:query output selection
50
+
51
+ If your input contains one or more **top-level** directives of the form:
52
+
53
+ ```n3
54
+ { ?x a :Human. } log:query { ?x a :Mortal. }.
55
+ ```
56
+
57
+ Eyeling will still compute the saturated forward closure, but it will **print only** the **unique instantiated conclusion triples** of those `log:query` directives (instead of printing all newly derived forward facts).
58
+
48
59
  ### JavaScript API
49
60
 
50
61
  CommonJS:
@@ -79,6 +90,9 @@ const { closureN3 } = eyeling.reasonStream(input, {
79
90
  proof: false,
80
91
  onDerived: ({ triple }) => console.log(triple),
81
92
  });
93
+
94
+ // With log:query directives present, closureN3 contains the query-selected triples.
95
+ // The return value also includes `queryMode`, `queryTriples`, and `queryDerived`.
82
96
  ```
83
97
 
84
98
  > Note: the npm `reason()` helper shells out to the bundled `eyeling.js` CLI for simplicity and robustness.
@@ -1,8 +1,8 @@
1
1
  @prefix : <http://example.com/> .
2
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
2
3
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
3
- @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
4
4
 
5
5
  :a :name "Alice" .
6
- :t rdf:reifies { :a :name "Alice" . } .
6
+ :t log:nameOf { :a :name "Alice" . } .
7
7
  :t :statedBy :bob .
8
8
  :t :recorded "2021-07-07"^^xsd:date .
@@ -1,15 +1,15 @@
1
- @prefix rdfg: <http://www.w3.org/2009/rdfg#> .
2
1
  @prefix : <http://example.org/#> .
3
2
  @prefix foaf: <http://xmlns.com/foaf/0.1/> .
4
3
  @prefix sec: <https://w3id.org/security#> .
5
4
  @prefix skolem: <https://eyereasoner.github.io/.well-known/genid/5649fff4-464d-5969-88ed-956a6b5f0d90#> .
5
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
6
6
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
7
7
 
8
- skolem:g0 rdfg:isGraph {
8
+ skolem:g0 log:nameOf {
9
9
  :Bob foaf:name "Bob" .
10
10
  } .
11
11
 
12
- skolem:g1 rdfg:isGraph {
12
+ skolem:g1 log:nameOf {
13
13
  skolem:g0 sec:proof _:dataSignature .
14
14
  _:signature1 a sec:DataIntegrityProof .
15
15
  _:signature1 sec:cryptosuite "ecdsa-rdfc-2019" .
@@ -22,7 +22,7 @@ skolem:g1 rdfg:isGraph {
22
22
  _:signature1 sec:validUntil "2025-04-03T00:00:00.000Z"^^xsd:dateTime .
23
23
  } .
24
24
 
25
- _:g3 rdfg:isGraph {
25
+ _:g3 log:nameOf {
26
26
  skolem:g1 sec:proof _:signature2 .
27
27
  _:signature2 a sec:DataIntegrityProof .
28
28
  _:signature2 sec:cryptosuite "ecdsa-rdfc-2019" .
@@ -0,0 +1,479 @@
1
+ # ===========================================================================================
2
+ # ODRL + DPV risk assessment with ranked, explainable output.
3
+ #
4
+ # What this file does
5
+ # - Models an agreement as an ODRL policy (odrl:Policy) containing permissions,
6
+ # prohibitions, duties, and constraints. ODRL is expressive enough to encode the
7
+ # normative “may/must/must-not” structure of TOS clauses as RDF.
8
+ # - Links each ODRL rule to a clause resource (:Clause) to keep human-readable text
9
+ # while preserving machine-readable structure for reasoning.
10
+ # - Uses N3 rules with log:includes / log:notIncludes to detect missing safeguards
11
+ # (e.g., missing notice constraints, missing inform duties, missing consent constraints).
12
+ # - Generates DPV risks (dpv:Risk) and classifies them using DPV-RISK concepts:
13
+ # risk:hasRiskSource, dpv:hasConsequence, dpv:hasImpact, dpv:hasSeverity, dpv:hasRiskLevel.
14
+ # - Produces mitigations as dpv:RiskMitigationMeasure resources and attaches them
15
+ # to risks with dpv:isMitigatedByMeasure.
16
+ # - Computes a numeric score for each risk and prints a ranked, explainable report.
17
+ #
18
+ # Ranking / output
19
+ # - Output strings are emitted as log:outputString triples.
20
+ # - When running with Eyeling “strings” mode (-r), strings are printed in deterministic
21
+ # order based on their subject key. The program encodes ranking via an “inverse score”
22
+ # key (e.g., 1000 - score) so higher-risk items appear first.
23
+ #
24
+ # References
25
+ # - N3 spec: https://w3c.github.io/N3/spec/
26
+ # - Eyeling builtins: https://eyereasoner.github.io/eyeling/HANDBOOK#ch11
27
+ # - ODRL vocab: https://www.w3.org/TR/odrl-vocab/
28
+ # - DPV risk module: https://w3c.github.io/dpv/2.0/dpv/modules/risk.html
29
+ # - DPV-RISK: https://w3id.org/dpv/risk
30
+ # ===========================================================================================
31
+
32
+ @prefix : <https://example.org/odrl-dpv-risk-ranked#> .
33
+ @prefix odrl: <http://www.w3.org/ns/odrl/2/> .
34
+ @prefix dpv: <https://w3id.org/dpv#> .
35
+ @prefix risk: <https://w3id.org/dpv/risk#> .
36
+ @prefix dct: <http://purl.org/dc/terms/> .
37
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
38
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
39
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
40
+ @prefix string:<http://www.w3.org/2000/10/swap/string#> .
41
+ @prefix tosl: <https://example.org/tosl-profile#> .
42
+
43
+ # ---------------------------
44
+ # 1) Consumer profile (needs)
45
+ # ---------------------------
46
+
47
+ :ConsumerExample a :ConsumerProfile ;
48
+ dct:title "Example consumer profile" ;
49
+ :hasNeed :Need_DataCannotBeRemoved,
50
+ :Need_ChangeOnlyWithPriorNotice,
51
+ :Need_NoSharingWithoutConsent,
52
+ :Need_DataPortability .
53
+
54
+ :Need_DataCannotBeRemoved a :Need ;
55
+ :importance "20"^^xsd:integer ;
56
+ dct:description "Provider must not remove the consumer account/data." .
57
+
58
+ :Need_ChangeOnlyWithPriorNotice a :Need ;
59
+ :importance "15"^^xsd:integer ;
60
+ :minNoticeDays "14"^^xsd:integer ;
61
+ dct:description "Agreement may change only with prior notice (>= 14 days)." .
62
+
63
+ :Need_NoSharingWithoutConsent a :Need ;
64
+ :importance "12"^^xsd:integer ;
65
+ dct:description "No data sharing without explicit consent." .
66
+
67
+ :Need_DataPortability a :Need ;
68
+ :importance "10"^^xsd:integer ;
69
+ dct:description "Consumer must be able to export their data." .
70
+
71
+ # ------------------------------------------
72
+ # 2) Agreement (ODRL policy graph + clauses)
73
+ # ------------------------------------------
74
+
75
+ :Agreement1 a :Agreement ;
76
+ dct:title "Example Agreement" ;
77
+ :policyGraph {
78
+ :Policy1 a odrl:Policy ;
79
+ odrl:permission :PermDeleteAccount,
80
+ :PermChangeTerms,
81
+ :PermShareData ;
82
+ odrl:prohibition :ProhibitExportData .
83
+
84
+ # Clause C1: remove account/data without safeguards
85
+ :PermDeleteAccount a odrl:Permission ;
86
+ odrl:assigner :Provider ;
87
+ odrl:assignee odrl:consumer ;
88
+ odrl:action tosl:removeAccount ;
89
+ odrl:target :UserAccount ;
90
+ :clause :ClauseC1 .
91
+
92
+ # Clause C2: change terms with noticeDays >= 3 (may be too short for the consumer)
93
+ :PermChangeTerms a odrl:Permission ;
94
+ odrl:assigner :Provider ;
95
+ odrl:assignee odrl:consumer ;
96
+ odrl:action tosl:changeTerms ;
97
+ odrl:target :AgreementText ;
98
+ odrl:duty [
99
+ a odrl:Duty ;
100
+ odrl:action odrl:inform ;
101
+ odrl:constraint [
102
+ a odrl:Constraint ;
103
+ odrl:leftOperand tosl:noticeDays ;
104
+ odrl:operator odrl:gteq ;
105
+ odrl:rightOperand "3"^^xsd:integer
106
+ ]
107
+ ] ;
108
+ :clause :ClauseC2 .
109
+
110
+ # Clause C3: share data without explicit consent safeguard
111
+ :PermShareData a odrl:Permission ;
112
+ odrl:assigner :Provider ;
113
+ odrl:assignee odrl:consumer ;
114
+ odrl:action tosl:shareData ;
115
+ odrl:target :UserData ;
116
+ :clause :ClauseC3 .
117
+
118
+ # Clause C4: prohibit export (hurts portability)
119
+ :ProhibitExportData a odrl:Prohibition ;
120
+ odrl:assigner :Provider ;
121
+ odrl:assignee odrl:consumer ;
122
+ odrl:action tosl:exportData ;
123
+ odrl:target :UserData ;
124
+ :clause :ClauseC4 .
125
+ } .
126
+
127
+ :ClauseC1 a :Clause ; :clauseId "C1" ; :text "Provider may remove the user account (and associated data) at its discretion." .
128
+ :ClauseC2 a :Clause ; :clauseId "C2" ; :text "Provider may change terms by informing users at least 3 days in advance." .
129
+ :ClauseC3 a :Clause ; :clauseId "C3" ; :text "Provider may share user data with partners for business purposes." .
130
+ :ClauseC4 a :Clause ; :clauseId "C4" ; :text "Users are not permitted to export their data." .
131
+
132
+ :ProcessContext1 a dpv:Process ;
133
+ dct:source :Agreement1 ;
134
+ dct:title "Service operation under Agreement1" .
135
+
136
+ # ------------------------------------------------------------------------------------
137
+ # 3) Risk rules (ODRL -> DPV/DPV-RISK) + mitigations
138
+ # IMPORTANT: only match ODRL structure inside log:includes; clause text is outside.
139
+ # ------------------------------------------------------------------------------------
140
+
141
+ # R1: remove account/data WITHOUT notice constraint AND WITHOUT inform duty
142
+ {
143
+ :Agreement1 :policyGraph ?G .
144
+ :ConsumerExample :hasNeed :Need_DataCannotBeRemoved .
145
+ :Need_DataCannotBeRemoved :importance ?w .
146
+
147
+ ?G log:includes {
148
+ :PermDeleteAccount a odrl:Permission ;
149
+ odrl:action tosl:removeAccount ;
150
+ :clause ?clause .
151
+ }.
152
+
153
+ ?G log:notIncludes {
154
+ :PermDeleteAccount odrl:constraint [
155
+ odrl:leftOperand tosl:noticeDays
156
+ ] .
157
+ }.
158
+
159
+ ?G log:notIncludes {
160
+ :PermDeleteAccount odrl:duty [
161
+ odrl:action odrl:inform
162
+ ] .
163
+ }.
164
+
165
+ ?clause :clauseId ?cid ; :text ?txt .
166
+
167
+ (90 ?w) math:sum ?raw .
168
+ ( :Agreement1 :ConsumerExample :DeleteAccountNoSafeguards ?cid ) log:skolem ?risk .
169
+ ( :Agreement1 :ConsumerExample :DeleteAccountNoSafeguardsSource ?cid ) log:skolem ?src .
170
+
171
+ ( "Risk: account/data removal is permitted without notice safeguards (no notice constraint and no duty to inform). Clause %s: %s"
172
+ ?cid ?txt ) string:format ?why .
173
+ }
174
+ =>
175
+ {
176
+ ?src a risk:RiskSource, risk:LegalComplianceRisk ;
177
+ dct:source :PermDeleteAccount ;
178
+ dct:description "Account removal permitted without notice safeguards." .
179
+
180
+ ?risk a dpv:Risk, risk:UnwantedDataDeletion, risk:DataUnavailable, risk:DataErasureError, risk:DataLoss ;
181
+ dct:source :PermDeleteAccount ;
182
+ risk:hasRiskSource ?src ;
183
+ dpv:hasConsequence risk:DataLoss, risk:DataUnavailable, risk:CustomerConfidenceLoss ;
184
+ dpv:hasImpact risk:FinancialLoss, risk:NonMaterialDamage ;
185
+ :aboutClause ?clause ;
186
+ :scoreRaw ?raw ;
187
+ :violatesNeed :Need_DataCannotBeRemoved ;
188
+ dct:description ?why .
189
+
190
+ :ProcessContext1 dpv:hasRisk ?risk .
191
+
192
+ ( ?risk :M1 ) log:skolem ?m1 .
193
+ ( ?risk :M2 ) log:skolem ?m2 .
194
+
195
+ ?m1 a dpv:RiskMitigationMeasure ;
196
+ dct:description "Add a notice constraint (minimum noticeDays) before account removal." ;
197
+ dpv:mitigatesRisk ?risk ;
198
+ :suggestAdd {
199
+ :PermDeleteAccount odrl:constraint [
200
+ a odrl:Constraint ;
201
+ odrl:leftOperand tosl:noticeDays ;
202
+ odrl:operator odrl:gteq ;
203
+ odrl:rightOperand "14"^^xsd:integer
204
+ ] .
205
+ } .
206
+
207
+ ?m2 a dpv:RiskMitigationMeasure ;
208
+ dct:description "Add a duty to inform the consumer prior to account removal." ;
209
+ dpv:mitigatesRisk ?risk ;
210
+ :suggestAdd {
211
+ :PermDeleteAccount odrl:duty [
212
+ a odrl:Duty ;
213
+ odrl:action odrl:inform
214
+ ] .
215
+ } .
216
+
217
+ ?risk dpv:isMitigatedByMeasure ?m1, ?m2 .
218
+ } .
219
+
220
+ # R2: change terms noticeDays is below consumer requirement
221
+ {
222
+ :Agreement1 :policyGraph ?G .
223
+ :ConsumerExample :hasNeed :Need_ChangeOnlyWithPriorNotice .
224
+ :Need_ChangeOnlyWithPriorNotice :importance ?w ; :minNoticeDays ?req .
225
+
226
+ ?G log:includes {
227
+ :PermChangeTerms a odrl:Permission ;
228
+ odrl:action tosl:changeTerms ;
229
+ :clause ?clause ;
230
+ odrl:duty [
231
+ odrl:action odrl:inform ;
232
+ odrl:constraint [
233
+ odrl:leftOperand tosl:noticeDays ;
234
+ odrl:rightOperand ?days
235
+ ]
236
+ ] .
237
+ }.
238
+
239
+ ?days math:lessThan ?req .
240
+ ?clause :clauseId ?cid ; :text ?txt .
241
+
242
+ (70 ?w) math:sum ?raw .
243
+ ( :Agreement1 :ConsumerExample :NoticeTooShort ?cid ) log:skolem ?risk .
244
+ ( :Agreement1 :ConsumerExample :NoticeTooShortSource ?cid ) log:skolem ?src .
245
+
246
+ ( "Risk: terms may change with notice (%s days) below consumer requirement (%s days). Clause %s: %s"
247
+ ?days ?req ?cid ?txt ) string:format ?why .
248
+ }
249
+ =>
250
+ {
251
+ ?src a risk:RiskSource, risk:PolicyRisk ;
252
+ dct:source :PermChangeTerms ;
253
+ dct:description "Notice for changing terms is shorter than consumer requirement." .
254
+
255
+ ?risk a dpv:Risk, risk:PolicyRisk, risk:CustomerConfidenceLoss ;
256
+ dct:source :PermChangeTerms ;
257
+ risk:hasRiskSource ?src ;
258
+ dpv:hasConsequence risk:CustomerConfidenceLoss ;
259
+ dpv:hasImpact risk:NonMaterialDamage ;
260
+ :aboutClause ?clause ;
261
+ :scoreRaw ?raw ;
262
+ :violatesNeed :Need_ChangeOnlyWithPriorNotice ;
263
+ dct:description ?why .
264
+
265
+ :ProcessContext1 dpv:hasRisk ?risk .
266
+
267
+ ( ?risk :M1 ) log:skolem ?m1 .
268
+ ?m1 a dpv:RiskMitigationMeasure ;
269
+ dct:description "Increase minimum noticeDays in the inform duty to meet the consumer requirement." ;
270
+ dpv:mitigatesRisk ?risk ;
271
+ :suggestAdd {
272
+ :PermChangeTerms odrl:duty [
273
+ a odrl:Duty ;
274
+ odrl:action odrl:inform ;
275
+ odrl:constraint [
276
+ a odrl:Constraint ;
277
+ odrl:leftOperand tosl:noticeDays ;
278
+ odrl:operator odrl:gteq ;
279
+ odrl:rightOperand "14"^^xsd:integer
280
+ ]
281
+ ] .
282
+ } .
283
+
284
+ ?risk dpv:isMitigatedByMeasure ?m1 .
285
+ } .
286
+
287
+ # R3: share data WITHOUT explicit consent constraint
288
+ {
289
+ :Agreement1 :policyGraph ?G .
290
+ :ConsumerExample :hasNeed :Need_NoSharingWithoutConsent .
291
+ :Need_NoSharingWithoutConsent :importance ?w .
292
+
293
+ ?G log:includes {
294
+ :PermShareData a odrl:Permission ;
295
+ odrl:action tosl:shareData ;
296
+ :clause ?clause .
297
+ }.
298
+
299
+ ?G log:notIncludes {
300
+ :PermShareData odrl:constraint [
301
+ odrl:leftOperand tosl:consent ;
302
+ odrl:operator odrl:eq ;
303
+ odrl:rightOperand true
304
+ ] .
305
+ }.
306
+
307
+ ?clause :clauseId ?cid ; :text ?txt .
308
+
309
+ (85 ?w) math:sum ?raw .
310
+ ( :Agreement1 :ConsumerExample :ShareNoConsent ?cid ) log:skolem ?risk .
311
+ ( :Agreement1 :ConsumerExample :ShareNoConsentSource ?cid ) log:skolem ?src .
312
+
313
+ ( "Risk: user data sharing is permitted without an explicit consent constraint. Clause %s: %s"
314
+ ?cid ?txt ) string:format ?why .
315
+ }
316
+ =>
317
+ {
318
+ ?src a risk:RiskSource, risk:PolicyRisk ;
319
+ dct:source :PermShareData ;
320
+ dct:description "Data sharing permitted without explicit consent constraint." .
321
+
322
+ ?risk a dpv:Risk, risk:UnwantedDisclosureData, risk:CustomerConfidenceLoss ;
323
+ dct:source :PermShareData ;
324
+ risk:hasRiskSource ?src ;
325
+ dpv:hasConsequence risk:CustomerConfidenceLoss ;
326
+ dpv:hasImpact risk:NonMaterialDamage, risk:FinancialLoss ;
327
+ :aboutClause ?clause ;
328
+ :scoreRaw ?raw ;
329
+ :violatesNeed :Need_NoSharingWithoutConsent ;
330
+ dct:description ?why .
331
+
332
+ :ProcessContext1 dpv:hasRisk ?risk .
333
+
334
+ ( ?risk :M1 ) log:skolem ?m1 .
335
+ ?m1 a dpv:RiskMitigationMeasure ;
336
+ dct:description "Add an explicit consent constraint before data sharing." ;
337
+ dpv:mitigatesRisk ?risk ;
338
+ :suggestAdd {
339
+ :PermShareData odrl:constraint [
340
+ a odrl:Constraint ;
341
+ odrl:leftOperand tosl:consent ;
342
+ odrl:operator odrl:eq ;
343
+ odrl:rightOperand true
344
+ ] .
345
+ } .
346
+
347
+ ?risk dpv:isMitigatedByMeasure ?m1 .
348
+ } .
349
+
350
+ # R4: prohibit export (no portability)
351
+ {
352
+ :Agreement1 :policyGraph ?G .
353
+ :ConsumerExample :hasNeed :Need_DataPortability .
354
+ :Need_DataPortability :importance ?w .
355
+
356
+ ?G log:includes {
357
+ :ProhibitExportData a odrl:Prohibition ;
358
+ odrl:action tosl:exportData ;
359
+ :clause ?clause .
360
+ }.
361
+
362
+ ?clause :clauseId ?cid ; :text ?txt .
363
+
364
+ (60 ?w) math:sum ?raw .
365
+ ( :Agreement1 :ConsumerExample :NoPortability ?cid ) log:skolem ?risk .
366
+ ( :Agreement1 :ConsumerExample :NoPortabilitySource ?cid ) log:skolem ?src .
367
+
368
+ ( "Risk: portability is restricted because exporting user data is prohibited. Clause %s: %s"
369
+ ?cid ?txt ) string:format ?why .
370
+ }
371
+ =>
372
+ {
373
+ ?src a risk:RiskSource, risk:PolicyRisk ;
374
+ dct:source :ProhibitExportData ;
375
+ dct:description "Data export is prohibited, reducing portability." .
376
+
377
+ ?risk a dpv:Risk, risk:PolicyRisk, risk:CustomerConfidenceLoss ;
378
+ dct:source :ProhibitExportData ;
379
+ risk:hasRiskSource ?src ;
380
+ dpv:hasConsequence risk:CustomerConfidenceLoss ;
381
+ dpv:hasImpact risk:NonMaterialDamage ;
382
+ :aboutClause ?clause ;
383
+ :scoreRaw ?raw ;
384
+ :violatesNeed :Need_DataPortability ;
385
+ dct:description ?why .
386
+
387
+ :ProcessContext1 dpv:hasRisk ?risk .
388
+
389
+ ( ?risk :M1 ) log:skolem ?m1 .
390
+ ?m1 a dpv:RiskMitigationMeasure ;
391
+ dct:description "Add a permission allowing data export (or remove the prohibition) to support portability." ;
392
+ dpv:mitigatesRisk ?risk ;
393
+ :suggestAdd {
394
+ :Policy1 odrl:permission [
395
+ a odrl:Permission ;
396
+ odrl:assigner :Provider ;
397
+ odrl:assignee odrl:consumer ;
398
+ odrl:action tosl:exportData ;
399
+ odrl:target :UserData
400
+ ] .
401
+ } .
402
+
403
+ ?risk dpv:isMitigatedByMeasure ?m1 .
404
+ } .
405
+
406
+ # ------------------------------------------------
407
+ # 4) Score normalization + DPV-RISK severity/level
408
+ # ------------------------------------------------
409
+
410
+ { ?r a dpv:Risk ; :scoreRaw ?raw . ?raw math:greaterThan "100"^^xsd:integer . }
411
+ => { ?r :score "100"^^xsd:integer . } .
412
+
413
+ { ?r a dpv:Risk ; :scoreRaw ?raw . "100"^^xsd:integer math:notLessThan ?raw . }
414
+ => { ?r :score ?raw . } .
415
+
416
+ { ?r a dpv:Risk ; :score ?s . ?s math:greaterThan "79"^^xsd:integer . }
417
+ => { ?r dpv:hasSeverity risk:HighSeverity ; dpv:hasRiskLevel risk:HighRisk . } .
418
+
419
+ { ?r a dpv:Risk ; :score ?s . ?s math:lessThan "80"^^xsd:integer . ?s math:greaterThan "49"^^xsd:integer . }
420
+ => { ?r dpv:hasSeverity risk:ModerateSeverity ; dpv:hasRiskLevel risk:ModerateRisk . } .
421
+
422
+ { ?r a dpv:Risk ; :score ?s . ?s math:lessThan "50"^^xsd:integer . }
423
+ => { ?r dpv:hasSeverity risk:LowSeverity ; dpv:hasRiskLevel risk:LowRisk . } .
424
+
425
+ # ------------------------------------------------------------------------------
426
+ # 5) Ranked explainable output (Eyeling -r prints these in key order)
427
+ # ------------------------------------------------------------------------------
428
+
429
+ # Header
430
+ {
431
+ :Agreement1 dct:title ?alabel .
432
+ :ConsumerExample dct:title ?plabel .
433
+ ( "\n=== Ranked DPV Risk Report ===\nAgreement: %s\nProfile: %s\n\n"
434
+ ?alabel ?plabel ) string:format ?hdr .
435
+ }
436
+ =>
437
+ {
438
+ ( :Agreement1 :ConsumerExample 0 ) log:outputString ?hdr .
439
+ } .
440
+
441
+ # Risk lines (key includes inverse score = 1000 - score)
442
+ {
443
+ ?r a dpv:Risk ;
444
+ :score ?score ;
445
+ dpv:hasRiskLevel ?lvl ;
446
+ dpv:hasSeverity ?sev ;
447
+ :aboutClause ?clause ;
448
+ dct:description ?why .
449
+ ?clause :clauseId ?cid .
450
+
451
+ ( "1000"^^xsd:integer ?score ) math:difference ?inv .
452
+
453
+ ( "score=%s (%s, %s) clause %s\n %s\n\n"
454
+ ?score ?lvl ?sev ?cid ?why ) string:format ?line .
455
+ }
456
+ =>
457
+ {
458
+ ( :Agreement1 :ConsumerExample 1 ?inv ?r ) log:outputString ?line .
459
+ } .
460
+
461
+ # Mitigation lines (same ordering as their risk)
462
+ {
463
+ ?r a dpv:Risk ;
464
+ :score ?score ;
465
+ dpv:isMitigatedByMeasure ?m ;
466
+ :aboutClause ?clause .
467
+ ?clause :clauseId ?cid .
468
+ ?m dct:description ?md .
469
+
470
+ ( "1000"^^xsd:integer ?score ) math:difference ?inv .
471
+
472
+ ( " - mitigation for clause %s: %s\n"
473
+ ?cid ?md ) string:format ?mline .
474
+ }
475
+ =>
476
+ {
477
+ ( :Agreement1 :ConsumerExample 2 ?inv ?r ) log:outputString ?mline .
478
+ } .
479
+