eyeling 1.12.13 → 1.12.15
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/examples/annotation.n3 +2 -2
- package/examples/context-association.n3 +4 -4
- package/examples/odrl-dpv-risk-ranked.n3 +479 -0
- package/examples/odrl-risk-mitigation.n3 +870 -0
- package/examples/output/odrl-dpv-risk-ranked.n3 +95 -0
- package/examples/output/odrl-risk-mitigation.n3 +207 -0
- package/examples/reifies.n3 +2 -1
- package/package.json +1 -1
- package/tools/n3gen.js +80 -27
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
# =====================================================================================
|
|
2
|
+
# Notation3 (N3) + ODRL Risk Assessment + Mitigation Demo
|
|
3
|
+
#
|
|
4
|
+
# Purpose
|
|
5
|
+
# - Represent an agreement as an ODRL policy graph (odrl:Policy with
|
|
6
|
+
# odrl:permission / odrl:prohibition / odrl:duty / odrl:constraint).
|
|
7
|
+
# - Detect potentially unfair or risky terms using N3 rules (antecedent => consequent).
|
|
8
|
+
# - Infer risk triples (:Risk instances) with:
|
|
9
|
+
# * :score / :severity
|
|
10
|
+
# * :aboutClause / :issue
|
|
11
|
+
# * :explanation (human-readable justification)
|
|
12
|
+
# - Attach mitigations (:Mitigation instances) with:
|
|
13
|
+
# * :fixText (human-readable fix)
|
|
14
|
+
# * :suggestAdd { ... } (quoted N3 graph showing suggested additions)
|
|
15
|
+
# - Rank risks highest->lowest and print an explainable report via log:outputString.
|
|
16
|
+
# - Print a mitigation plan (highest risk first).
|
|
17
|
+
#
|
|
18
|
+
# Data Shape
|
|
19
|
+
# :Agreement
|
|
20
|
+
# :policyGraph { ...ODRL triples... } # quoted formula (N3 graph term)
|
|
21
|
+
#
|
|
22
|
+
# Rules
|
|
23
|
+
# - Use log:includes / log:notIncludes to pattern-match inside the quoted policyGraph.
|
|
24
|
+
# - Generate stable IRIs with log:skolem.
|
|
25
|
+
# - Compute scores using math:* builtins; format strings with string:format.
|
|
26
|
+
#
|
|
27
|
+
# Ranking / Reporting
|
|
28
|
+
# - Collect (score risk) or (score risk fixText) tuples with log:collectAllIn.
|
|
29
|
+
# - Sort with list:sort, reverse with list:reverse, iterate with list:iterate.
|
|
30
|
+
# - Emit lines with log:outputString (run Eyeling in strings-output mode).
|
|
31
|
+
#
|
|
32
|
+
# Notes
|
|
33
|
+
# - Heuristic compliance/risk checker for demo purposes (not legal advice).
|
|
34
|
+
# - Extend by adding more unfair-term rules, more needs, and more mitigations.
|
|
35
|
+
#
|
|
36
|
+
# References
|
|
37
|
+
# - N3 specification: https://w3c.github.io/N3/spec/
|
|
38
|
+
# - Eyeling builtins handbook: https://eyereasoner.github.io/eyeling/HANDBOOK#ch11
|
|
39
|
+
# - ODRL model/vocabulary: https://www.w3.org/TR/odrl-model/
|
|
40
|
+
# =====================================================================================
|
|
41
|
+
|
|
42
|
+
@prefix : <https://example.org/odrl-mitigation-demo#>.
|
|
43
|
+
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
|
|
44
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
|
|
45
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
|
|
46
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
47
|
+
@prefix list: <http://www.w3.org/2000/10/swap/list#>.
|
|
48
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
49
|
+
@prefix string:<http://www.w3.org/2000/10/swap/string#>.
|
|
50
|
+
|
|
51
|
+
# -------------------------------------------------------------
|
|
52
|
+
# 1) Consumer profile (needs) — tweak these to drive assessment
|
|
53
|
+
# -------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
:ConsumerCarol a :ConsumerProfile;
|
|
56
|
+
:label "Carol (example consumer)";
|
|
57
|
+
:hasNeed :Need_ChangeOnlyWithPriorNotice,
|
|
58
|
+
:Need_ReminderBeforeAutoRenew,
|
|
59
|
+
:Need_MinLiabilityCap,
|
|
60
|
+
:Need_DataPortability,
|
|
61
|
+
:Need_DataNotRemoved,
|
|
62
|
+
:Need_RefundRequired,
|
|
63
|
+
:Need_NoTrackingWithoutOptIn.
|
|
64
|
+
|
|
65
|
+
:Need_ChangeOnlyWithPriorNotice a :Need;
|
|
66
|
+
:label "Agreement may change only with prior notice";
|
|
67
|
+
:importance 15;
|
|
68
|
+
:minNoticeDays 14.
|
|
69
|
+
|
|
70
|
+
:Need_ReminderBeforeAutoRenew a :Need;
|
|
71
|
+
:label "Auto-renew only with prior reminder";
|
|
72
|
+
:importance 10;
|
|
73
|
+
:minReminderDays 7.
|
|
74
|
+
|
|
75
|
+
:Need_MinLiabilityCap a :Need;
|
|
76
|
+
:label "Liability cap must not be too low";
|
|
77
|
+
:importance 8;
|
|
78
|
+
:minLiabilityCapEuro 200.
|
|
79
|
+
|
|
80
|
+
:Need_DataPortability a :Need;
|
|
81
|
+
:label "Must be able to export my data";
|
|
82
|
+
:importance 8.
|
|
83
|
+
|
|
84
|
+
:Need_DataNotRemoved a :Need;
|
|
85
|
+
:label "Provider must not remove my data";
|
|
86
|
+
:importance 12.
|
|
87
|
+
|
|
88
|
+
:Need_RefundRequired a :Need;
|
|
89
|
+
:label "Fees should be refundable (at least within a cooling-off window)";
|
|
90
|
+
:importance 9;
|
|
91
|
+
:minCoolingOffDays 14.
|
|
92
|
+
|
|
93
|
+
:Need_NoTrackingWithoutOptIn a :Need;
|
|
94
|
+
:label "No tracking unless I opt in";
|
|
95
|
+
:importance 9.
|
|
96
|
+
|
|
97
|
+
# -------------------------------------------------------------
|
|
98
|
+
# 2) Agreement model in ODRL (quoted formula as the “document”)
|
|
99
|
+
# -------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
:Agreement3 a :Agreement;
|
|
102
|
+
:label "Example Platform Agreement (with fixes)";
|
|
103
|
+
:policyGraph {
|
|
104
|
+
:Policy3 a odrl:Policy;
|
|
105
|
+
odrl:permission :PermChangeTerms,
|
|
106
|
+
:PermAutoRenew,
|
|
107
|
+
:PermChargeFee,
|
|
108
|
+
:PermTrackUser,
|
|
109
|
+
:PermDeleteUserData;
|
|
110
|
+
odrl:prohibition :ProhibitExportData.
|
|
111
|
+
|
|
112
|
+
# Clause D1: change terms WITH notice duty, but notice is only 3 days (too short vs need 14)
|
|
113
|
+
:PermChangeTerms a odrl:Permission;
|
|
114
|
+
odrl:action :changeTerms;
|
|
115
|
+
odrl:target :AgreementText;
|
|
116
|
+
odrl:duty :DutyNotify;
|
|
117
|
+
:clause :ClauseD1.
|
|
118
|
+
|
|
119
|
+
:DutyNotify a odrl:Duty;
|
|
120
|
+
odrl:action :notifyPriorNotice;
|
|
121
|
+
odrl:constraint :C_noticeDays.
|
|
122
|
+
|
|
123
|
+
:C_noticeDays a odrl:Constraint;
|
|
124
|
+
odrl:leftOperand :noticeDays;
|
|
125
|
+
odrl:operator odrl:gteq;
|
|
126
|
+
odrl:rightOperand 3.
|
|
127
|
+
|
|
128
|
+
:ClauseD1 a :Clause;
|
|
129
|
+
:clauseId "D1";
|
|
130
|
+
:text "We may change these terms with notice. Notice may be as short as 3 days.".
|
|
131
|
+
|
|
132
|
+
# Clause D2: auto-renewal, but NO reminder duty
|
|
133
|
+
:PermAutoRenew a odrl:Permission;
|
|
134
|
+
odrl:action :autoRenewSubscription;
|
|
135
|
+
odrl:target :Subscription;
|
|
136
|
+
:clause :ClauseD2.
|
|
137
|
+
|
|
138
|
+
:ClauseD2 a :Clause;
|
|
139
|
+
:clauseId "D2";
|
|
140
|
+
:text "Your subscription renews automatically unless you cancel.".
|
|
141
|
+
|
|
142
|
+
# Clause D3: fees are non-refundable
|
|
143
|
+
:PermChargeFee a odrl:Permission;
|
|
144
|
+
odrl:action :chargeFee;
|
|
145
|
+
odrl:target :SubscriptionFee;
|
|
146
|
+
odrl:constraint :C_nonRefundable;
|
|
147
|
+
:clause :ClauseD3.
|
|
148
|
+
|
|
149
|
+
:C_nonRefundable a odrl:Constraint;
|
|
150
|
+
odrl:leftOperand :refundAllowed;
|
|
151
|
+
odrl:operator odrl:eq;
|
|
152
|
+
odrl:rightOperand false.
|
|
153
|
+
|
|
154
|
+
:ClauseD3 a :Clause;
|
|
155
|
+
:clauseId "D3";
|
|
156
|
+
:text "All fees are non-refundable.".
|
|
157
|
+
|
|
158
|
+
# Clause D4: provider may track user, without opt-in
|
|
159
|
+
:PermTrackUser a odrl:Permission;
|
|
160
|
+
odrl:action :trackUser;
|
|
161
|
+
odrl:target :User;
|
|
162
|
+
:clause :ClauseD4.
|
|
163
|
+
|
|
164
|
+
:ClauseD4 a :Clause;
|
|
165
|
+
:clauseId "D4";
|
|
166
|
+
:text "We may track your activity to improve services.".
|
|
167
|
+
|
|
168
|
+
# Clause D5: prohibits exporting data (no portability)
|
|
169
|
+
:ProhibitExportData a odrl:Prohibition;
|
|
170
|
+
odrl:action :exportData;
|
|
171
|
+
odrl:target :UserData;
|
|
172
|
+
:clause :ClauseD5.
|
|
173
|
+
|
|
174
|
+
:ClauseD5 a :Clause;
|
|
175
|
+
:clauseId "D5";
|
|
176
|
+
:text "You may not export or download your data from the service.".
|
|
177
|
+
|
|
178
|
+
# Clause D6: provider may delete user data
|
|
179
|
+
:PermDeleteUserData a odrl:Permission;
|
|
180
|
+
odrl:action :deleteUserData;
|
|
181
|
+
odrl:target :UserData;
|
|
182
|
+
:clause :ClauseD6.
|
|
183
|
+
|
|
184
|
+
:ClauseD6 a :Clause;
|
|
185
|
+
:clauseId "D6";
|
|
186
|
+
:text "We may delete your data at our discretion.".
|
|
187
|
+
|
|
188
|
+
# Clause D7: very low liability cap (20 EUR)
|
|
189
|
+
:PermAutoRenew odrl:constraint :C_liabilityCap.
|
|
190
|
+
:C_liabilityCap a odrl:Constraint;
|
|
191
|
+
odrl:leftOperand :liabilityCapEuro;
|
|
192
|
+
odrl:operator odrl:eq;
|
|
193
|
+
odrl:rightOperand 20.
|
|
194
|
+
}.
|
|
195
|
+
|
|
196
|
+
# ------------------------------------------------------------------
|
|
197
|
+
# 3) Risk inference rules (unfair-term heuristics + needs-aware)
|
|
198
|
+
#
|
|
199
|
+
# Output model:
|
|
200
|
+
# ?risk a :Risk ;
|
|
201
|
+
# :aboutAgreement, :forProfile, :aboutClause, :issue, :rawScore,
|
|
202
|
+
# :score, :severity, :explanation, ...
|
|
203
|
+
# ------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
# Helper: deterministic risk IRIs
|
|
206
|
+
# (groundTerm) log:skolem ?iri
|
|
207
|
+
|
|
208
|
+
# ---------------------------------------------------------
|
|
209
|
+
# R1 — Notice period too short for changes to the agreement
|
|
210
|
+
# ---------------------------------------------------------
|
|
211
|
+
{
|
|
212
|
+
?profile a :ConsumerProfile; :hasNeed :Need_ChangeOnlyWithPriorNotice.
|
|
213
|
+
:Need_ChangeOnlyWithPriorNotice :importance ?w; :minNoticeDays ?minDays.
|
|
214
|
+
|
|
215
|
+
?agreement a :Agreement; :policyGraph ?G.
|
|
216
|
+
?G log:includes {
|
|
217
|
+
?P a odrl:Policy.
|
|
218
|
+
?P odrl:permission ?perm.
|
|
219
|
+
?perm odrl:action :changeTerms.
|
|
220
|
+
?perm :clause ?clause.
|
|
221
|
+
?clause :clauseId ?cid; :text ?txt.
|
|
222
|
+
|
|
223
|
+
?perm odrl:duty ?d.
|
|
224
|
+
?d odrl:action :notifyPriorNotice.
|
|
225
|
+
?d odrl:constraint ?c.
|
|
226
|
+
?c odrl:leftOperand :noticeDays;
|
|
227
|
+
odrl:operator odrl:gteq;
|
|
228
|
+
odrl:rightOperand ?days.
|
|
229
|
+
}.
|
|
230
|
+
|
|
231
|
+
?days math:lessThan ?minDays.
|
|
232
|
+
|
|
233
|
+
(70 ?w) math:sum ?raw.
|
|
234
|
+
( ?agreement ?profile :NoticeTooShort ?clause ) log:skolem ?risk.
|
|
235
|
+
|
|
236
|
+
( "This clause is risky because the notice period (%s days) is below the consumer requirement (%s days). Clause %s: %s"
|
|
237
|
+
?days ?minDays ?cid ?txt
|
|
238
|
+
) string:format ?why.
|
|
239
|
+
}
|
|
240
|
+
=>
|
|
241
|
+
{
|
|
242
|
+
?risk a :Risk;
|
|
243
|
+
:aboutAgreement ?agreement;
|
|
244
|
+
:forProfile ?profile;
|
|
245
|
+
:aboutClause ?clause;
|
|
246
|
+
:clauseId ?cid;
|
|
247
|
+
:issue :NoticeTooShort;
|
|
248
|
+
:baseScore 70;
|
|
249
|
+
:needWeight ?w;
|
|
250
|
+
:rawScore ?raw;
|
|
251
|
+
:title "Notice period too short";
|
|
252
|
+
:explanation ?why;
|
|
253
|
+
:violatesNeed :Need_ChangeOnlyWithPriorNotice.
|
|
254
|
+
}.
|
|
255
|
+
|
|
256
|
+
# ---------------------------------------
|
|
257
|
+
# R2 — Auto-renewal without reminder duty
|
|
258
|
+
# ---------------------------------------
|
|
259
|
+
{
|
|
260
|
+
?profile a :ConsumerProfile; :hasNeed :Need_ReminderBeforeAutoRenew.
|
|
261
|
+
:Need_ReminderBeforeAutoRenew :importance ?w; :minReminderDays ?minDays.
|
|
262
|
+
|
|
263
|
+
?agreement a :Agreement; :policyGraph ?G.
|
|
264
|
+
?G log:includes {
|
|
265
|
+
?P a odrl:Policy.
|
|
266
|
+
?P odrl:permission ?perm.
|
|
267
|
+
?perm odrl:action :autoRenewSubscription.
|
|
268
|
+
?perm :clause ?clause.
|
|
269
|
+
?clause :clauseId ?cid; :text ?txt.
|
|
270
|
+
}.
|
|
271
|
+
|
|
272
|
+
?G log:notIncludes {
|
|
273
|
+
?perm odrl:duty ?d.
|
|
274
|
+
?d odrl:action :sendRenewalReminder.
|
|
275
|
+
}.
|
|
276
|
+
|
|
277
|
+
(75 ?w) math:sum ?raw.
|
|
278
|
+
( ?agreement ?profile :AutoRenewNoReminder ?clause ) log:skolem ?risk.
|
|
279
|
+
|
|
280
|
+
( "This clause is risky because it allows auto-renewal without a reminder. Consumer needs at least %s days reminder. Clause %s: %s"
|
|
281
|
+
?minDays ?cid ?txt
|
|
282
|
+
) string:format ?why.
|
|
283
|
+
}
|
|
284
|
+
=>
|
|
285
|
+
{
|
|
286
|
+
?risk a :Risk;
|
|
287
|
+
:aboutAgreement ?agreement;
|
|
288
|
+
:forProfile ?profile;
|
|
289
|
+
:aboutClause ?clause;
|
|
290
|
+
:clauseId ?cid;
|
|
291
|
+
:issue :AutoRenewNoReminder;
|
|
292
|
+
:baseScore 75;
|
|
293
|
+
:needWeight ?w;
|
|
294
|
+
:rawScore ?raw;
|
|
295
|
+
:title "Auto-renewal without reminder";
|
|
296
|
+
:explanation ?why;
|
|
297
|
+
:violatesNeed :Need_ReminderBeforeAutoRenew.
|
|
298
|
+
}.
|
|
299
|
+
|
|
300
|
+
# ------------------------
|
|
301
|
+
# R3 — Non-refundable fees
|
|
302
|
+
# ------------------------
|
|
303
|
+
{
|
|
304
|
+
?profile a :ConsumerProfile; :hasNeed :Need_RefundRequired.
|
|
305
|
+
:Need_RefundRequired :importance ?w; :minCoolingOffDays ?minDays.
|
|
306
|
+
|
|
307
|
+
?agreement a :Agreement; :policyGraph ?G.
|
|
308
|
+
?G log:includes {
|
|
309
|
+
?P a odrl:Policy.
|
|
310
|
+
?P odrl:permission ?perm.
|
|
311
|
+
?perm odrl:action :chargeFee.
|
|
312
|
+
?perm odrl:constraint ?c.
|
|
313
|
+
?c odrl:leftOperand :refundAllowed;
|
|
314
|
+
odrl:operator odrl:eq;
|
|
315
|
+
odrl:rightOperand false.
|
|
316
|
+
?perm :clause ?clause.
|
|
317
|
+
?clause :clauseId ?cid; :text ?txt.
|
|
318
|
+
}.
|
|
319
|
+
|
|
320
|
+
(70 ?w) math:sum ?raw.
|
|
321
|
+
( ?agreement ?profile :NonRefundableFee ?clause ) log:skolem ?risk.
|
|
322
|
+
|
|
323
|
+
( "This clause is risky because it declares fees non-refundable, conflicting with a refund/cooling-off expectation (>= %s days). Clause %s: %s"
|
|
324
|
+
?minDays ?cid ?txt
|
|
325
|
+
) string:format ?why.
|
|
326
|
+
}
|
|
327
|
+
=>
|
|
328
|
+
{
|
|
329
|
+
?risk a :Risk;
|
|
330
|
+
:aboutAgreement ?agreement;
|
|
331
|
+
:forProfile ?profile;
|
|
332
|
+
:aboutClause ?clause;
|
|
333
|
+
:clauseId ?cid;
|
|
334
|
+
:issue :NonRefundableFee;
|
|
335
|
+
:baseScore 70;
|
|
336
|
+
:needWeight ?w;
|
|
337
|
+
:rawScore ?raw;
|
|
338
|
+
:title "Non-refundable fees";
|
|
339
|
+
:explanation ?why;
|
|
340
|
+
:violatesNeed :Need_RefundRequired.
|
|
341
|
+
}.
|
|
342
|
+
|
|
343
|
+
# ------------------------------------
|
|
344
|
+
# R4 — Tracking without opt-in consent
|
|
345
|
+
# ------------------------------------
|
|
346
|
+
{
|
|
347
|
+
?profile a :ConsumerProfile; :hasNeed :Need_NoTrackingWithoutOptIn.
|
|
348
|
+
:Need_NoTrackingWithoutOptIn :importance ?w.
|
|
349
|
+
|
|
350
|
+
?agreement a :Agreement; :policyGraph ?G.
|
|
351
|
+
?G log:includes {
|
|
352
|
+
?P a odrl:Policy.
|
|
353
|
+
?P odrl:permission ?perm.
|
|
354
|
+
?perm odrl:action :trackUser.
|
|
355
|
+
?perm :clause ?clause.
|
|
356
|
+
?clause :clauseId ?cid; :text ?txt.
|
|
357
|
+
}.
|
|
358
|
+
|
|
359
|
+
?G log:notIncludes {
|
|
360
|
+
?perm odrl:constraint ?c.
|
|
361
|
+
?c odrl:leftOperand :optInConsent;
|
|
362
|
+
odrl:operator odrl:eq;
|
|
363
|
+
odrl:rightOperand true.
|
|
364
|
+
}.
|
|
365
|
+
|
|
366
|
+
(80 ?w) math:sum ?raw.
|
|
367
|
+
( ?agreement ?profile :TrackingNoOptIn ?clause ) log:skolem ?risk.
|
|
368
|
+
|
|
369
|
+
( "This clause is risky because it permits tracking without explicit opt-in consent. Clause %s: %s"
|
|
370
|
+
?cid ?txt
|
|
371
|
+
) string:format ?why.
|
|
372
|
+
}
|
|
373
|
+
=>
|
|
374
|
+
{
|
|
375
|
+
?risk a :Risk;
|
|
376
|
+
:aboutAgreement ?agreement;
|
|
377
|
+
:forProfile ?profile;
|
|
378
|
+
:aboutClause ?clause;
|
|
379
|
+
:clauseId ?cid;
|
|
380
|
+
:issue :TrackingNoOptIn;
|
|
381
|
+
:baseScore 80;
|
|
382
|
+
:needWeight ?w;
|
|
383
|
+
:rawScore ?raw;
|
|
384
|
+
:title "Tracking without opt-in";
|
|
385
|
+
:explanation ?why;
|
|
386
|
+
:violatesNeed :Need_NoTrackingWithoutOptIn.
|
|
387
|
+
}.
|
|
388
|
+
|
|
389
|
+
# ------------------------------------------------
|
|
390
|
+
# R5 — Prohibition of data export (no portability)
|
|
391
|
+
# ------------------------------------------------
|
|
392
|
+
{
|
|
393
|
+
?profile a :ConsumerProfile; :hasNeed :Need_DataPortability.
|
|
394
|
+
:Need_DataPortability :importance ?w.
|
|
395
|
+
|
|
396
|
+
?agreement a :Agreement; :policyGraph ?G.
|
|
397
|
+
?G log:includes {
|
|
398
|
+
?P a odrl:Policy.
|
|
399
|
+
?P odrl:prohibition ?proh.
|
|
400
|
+
?proh odrl:action :exportData.
|
|
401
|
+
?proh :clause ?clause.
|
|
402
|
+
?clause :clauseId ?cid; :text ?txt.
|
|
403
|
+
}.
|
|
404
|
+
|
|
405
|
+
(85 ?w) math:sum ?raw.
|
|
406
|
+
( ?agreement ?profile :NoDataExport ?clause ) log:skolem ?risk.
|
|
407
|
+
|
|
408
|
+
( "This clause is risky because it prohibits exporting data, undermining portability. Clause %s: %s"
|
|
409
|
+
?cid ?txt
|
|
410
|
+
) string:format ?why.
|
|
411
|
+
}
|
|
412
|
+
=>
|
|
413
|
+
{
|
|
414
|
+
?risk a :Risk;
|
|
415
|
+
:aboutAgreement ?agreement;
|
|
416
|
+
:forProfile ?profile;
|
|
417
|
+
:aboutClause ?clause;
|
|
418
|
+
:clauseId ?cid;
|
|
419
|
+
:issue :NoDataExport;
|
|
420
|
+
:baseScore 85;
|
|
421
|
+
:needWeight ?w;
|
|
422
|
+
:rawScore ?raw;
|
|
423
|
+
:title "No data export / portability";
|
|
424
|
+
:explanation ?why;
|
|
425
|
+
:violatesNeed :Need_DataPortability.
|
|
426
|
+
}.
|
|
427
|
+
|
|
428
|
+
# ----------------------------------
|
|
429
|
+
# R6 — Provider may delete user data
|
|
430
|
+
# ----------------------------------
|
|
431
|
+
{
|
|
432
|
+
?profile a :ConsumerProfile; :hasNeed :Need_DataNotRemoved.
|
|
433
|
+
:Need_DataNotRemoved :importance ?w.
|
|
434
|
+
|
|
435
|
+
?agreement a :Agreement; :policyGraph ?G.
|
|
436
|
+
?G log:includes {
|
|
437
|
+
?P a odrl:Policy.
|
|
438
|
+
?P odrl:permission ?perm.
|
|
439
|
+
?perm odrl:action :deleteUserData.
|
|
440
|
+
?perm :clause ?clause.
|
|
441
|
+
?clause :clauseId ?cid; :text ?txt.
|
|
442
|
+
}.
|
|
443
|
+
|
|
444
|
+
(90 ?w) math:sum ?raw.
|
|
445
|
+
( ?agreement ?profile :ProviderMayDeleteData ?clause ) log:skolem ?risk.
|
|
446
|
+
|
|
447
|
+
( "This clause is risky because it allows the provider to delete the consumer’s data. Clause %s: %s"
|
|
448
|
+
?cid ?txt
|
|
449
|
+
) string:format ?why.
|
|
450
|
+
}
|
|
451
|
+
=>
|
|
452
|
+
{
|
|
453
|
+
?risk a :Risk;
|
|
454
|
+
:aboutAgreement ?agreement;
|
|
455
|
+
:forProfile ?profile;
|
|
456
|
+
:aboutClause ?clause;
|
|
457
|
+
:clauseId ?cid;
|
|
458
|
+
:issue :ProviderMayDeleteData;
|
|
459
|
+
:baseScore 90;
|
|
460
|
+
:needWeight ?w;
|
|
461
|
+
:rawScore ?raw;
|
|
462
|
+
:title "Provider can delete user data";
|
|
463
|
+
:explanation ?why;
|
|
464
|
+
:violatesNeed :Need_DataNotRemoved.
|
|
465
|
+
}.
|
|
466
|
+
|
|
467
|
+
# --------------------------
|
|
468
|
+
# R7 — Liability cap too low
|
|
469
|
+
# --------------------------
|
|
470
|
+
{
|
|
471
|
+
?profile a :ConsumerProfile; :hasNeed :Need_MinLiabilityCap.
|
|
472
|
+
:Need_MinLiabilityCap :importance ?w; :minLiabilityCapEuro ?minCap.
|
|
473
|
+
|
|
474
|
+
?agreement a :Agreement; :policyGraph ?G.
|
|
475
|
+
?G log:includes {
|
|
476
|
+
?P a odrl:Policy.
|
|
477
|
+
?P odrl:permission ?perm.
|
|
478
|
+
?perm odrl:action :autoRenewSubscription.
|
|
479
|
+
?perm odrl:constraint ?c.
|
|
480
|
+
?c odrl:leftOperand :liabilityCapEuro;
|
|
481
|
+
odrl:operator odrl:eq;
|
|
482
|
+
odrl:rightOperand ?cap.
|
|
483
|
+
?perm :clause ?clause.
|
|
484
|
+
?clause :clauseId ?cid; :text ?txt.
|
|
485
|
+
}.
|
|
486
|
+
|
|
487
|
+
?cap math:lessThan ?minCap.
|
|
488
|
+
|
|
489
|
+
(65 ?w) math:sum ?raw.
|
|
490
|
+
( ?agreement ?profile :LiabilityCapTooLow ?clause ) log:skolem ?risk.
|
|
491
|
+
|
|
492
|
+
( "This clause is risky because the liability cap (%s EUR) is below the consumer minimum (%s EUR). Clause %s: %s"
|
|
493
|
+
?cap ?minCap ?cid ?txt
|
|
494
|
+
) string:format ?why.
|
|
495
|
+
}
|
|
496
|
+
=>
|
|
497
|
+
{
|
|
498
|
+
?risk a :Risk;
|
|
499
|
+
:aboutAgreement ?agreement;
|
|
500
|
+
:forProfile ?profile;
|
|
501
|
+
:aboutClause ?clause;
|
|
502
|
+
:clauseId ?cid;
|
|
503
|
+
:issue :LiabilityCapTooLow;
|
|
504
|
+
:baseScore 65;
|
|
505
|
+
:needWeight ?w;
|
|
506
|
+
:rawScore ?raw;
|
|
507
|
+
:title "Liability cap too low";
|
|
508
|
+
:explanation ?why;
|
|
509
|
+
:violatesNeed :Need_MinLiabilityCap.
|
|
510
|
+
}.
|
|
511
|
+
|
|
512
|
+
# --------------------------------------------------------
|
|
513
|
+
# 4) Mitigation inference rules (attach recommended fixes)
|
|
514
|
+
# --------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
# Fix for :NoticeTooShort — raise notice days to meet requirement
|
|
517
|
+
{
|
|
518
|
+
?risk a :Risk;
|
|
519
|
+
:issue :NoticeTooShort;
|
|
520
|
+
:aboutAgreement ?agreement;
|
|
521
|
+
:forProfile ?profile.
|
|
522
|
+
|
|
523
|
+
:Need_ChangeOnlyWithPriorNotice :minNoticeDays ?minDays.
|
|
524
|
+
|
|
525
|
+
?agreement :policyGraph ?G.
|
|
526
|
+
?G log:includes {
|
|
527
|
+
?perm odrl:action :changeTerms.
|
|
528
|
+
}.
|
|
529
|
+
|
|
530
|
+
( ?risk :Mitigation ) log:skolem ?fix.
|
|
531
|
+
|
|
532
|
+
( "Suggested fix: ensure prior-notice duty specifies noticeDays >= %s."
|
|
533
|
+
?minDays
|
|
534
|
+
) string:format ?fixText.
|
|
535
|
+
}
|
|
536
|
+
=>
|
|
537
|
+
{
|
|
538
|
+
?risk :hasMitigation ?fix.
|
|
539
|
+
?fix a :Mitigation;
|
|
540
|
+
:forRisk ?risk;
|
|
541
|
+
:fixText ?fixText;
|
|
542
|
+
:suggestAdd {
|
|
543
|
+
?perm odrl:duty [
|
|
544
|
+
a odrl:Duty;
|
|
545
|
+
odrl:action :notifyPriorNotice;
|
|
546
|
+
odrl:constraint [
|
|
547
|
+
a odrl:Constraint;
|
|
548
|
+
odrl:leftOperand :noticeDays;
|
|
549
|
+
odrl:operator odrl:gteq;
|
|
550
|
+
odrl:rightOperand ?minDays
|
|
551
|
+
]
|
|
552
|
+
].
|
|
553
|
+
}.
|
|
554
|
+
}.
|
|
555
|
+
|
|
556
|
+
# Fix for :AutoRenewNoReminder — add renewal reminder duty
|
|
557
|
+
{
|
|
558
|
+
?risk a :Risk;
|
|
559
|
+
:issue :AutoRenewNoReminder;
|
|
560
|
+
:aboutAgreement ?agreement;
|
|
561
|
+
:forProfile ?profile.
|
|
562
|
+
|
|
563
|
+
:Need_ReminderBeforeAutoRenew :minReminderDays ?minDays.
|
|
564
|
+
|
|
565
|
+
?agreement :policyGraph ?G.
|
|
566
|
+
?G log:includes { ?perm odrl:action :autoRenewSubscription. }.
|
|
567
|
+
|
|
568
|
+
( ?risk :Mitigation ) log:skolem ?fix.
|
|
569
|
+
|
|
570
|
+
( "Suggested fix: add a reminder duty for auto-renewal with reminderDays >= %s."
|
|
571
|
+
?minDays
|
|
572
|
+
) string:format ?fixText.
|
|
573
|
+
}
|
|
574
|
+
=>
|
|
575
|
+
{
|
|
576
|
+
?risk :hasMitigation ?fix.
|
|
577
|
+
?fix a :Mitigation;
|
|
578
|
+
:forRisk ?risk;
|
|
579
|
+
:fixText ?fixText;
|
|
580
|
+
:suggestAdd {
|
|
581
|
+
?perm odrl:duty [
|
|
582
|
+
a odrl:Duty;
|
|
583
|
+
odrl:action :sendRenewalReminder;
|
|
584
|
+
odrl:constraint [
|
|
585
|
+
a odrl:Constraint;
|
|
586
|
+
odrl:leftOperand :reminderDays;
|
|
587
|
+
odrl:operator odrl:gteq;
|
|
588
|
+
odrl:rightOperand ?minDays
|
|
589
|
+
]
|
|
590
|
+
].
|
|
591
|
+
}.
|
|
592
|
+
}.
|
|
593
|
+
|
|
594
|
+
# Fix for :NonRefundableFee — allow refund (or add cooling-off window)
|
|
595
|
+
{
|
|
596
|
+
?risk a :Risk;
|
|
597
|
+
:issue :NonRefundableFee;
|
|
598
|
+
:aboutAgreement ?agreement;
|
|
599
|
+
:forProfile ?profile.
|
|
600
|
+
|
|
601
|
+
:Need_RefundRequired :minCoolingOffDays ?minDays.
|
|
602
|
+
|
|
603
|
+
?agreement :policyGraph ?G.
|
|
604
|
+
?G log:includes { ?perm odrl:action :chargeFee. }.
|
|
605
|
+
|
|
606
|
+
( ?risk :Mitigation ) log:skolem ?fix.
|
|
607
|
+
|
|
608
|
+
( "Suggested fix: allow refunds (e.g., refundAllowed=true) or define a cooling-off period >= %s days."
|
|
609
|
+
?minDays
|
|
610
|
+
) string:format ?fixText.
|
|
611
|
+
}
|
|
612
|
+
=>
|
|
613
|
+
{
|
|
614
|
+
?risk :hasMitigation ?fix.
|
|
615
|
+
?fix a :Mitigation;
|
|
616
|
+
:forRisk ?risk;
|
|
617
|
+
:fixText ?fixText;
|
|
618
|
+
:suggestAdd {
|
|
619
|
+
?perm odrl:constraint [
|
|
620
|
+
a odrl:Constraint;
|
|
621
|
+
odrl:leftOperand :refundAllowed;
|
|
622
|
+
odrl:operator odrl:eq;
|
|
623
|
+
odrl:rightOperand true
|
|
624
|
+
].
|
|
625
|
+
?perm odrl:constraint [
|
|
626
|
+
a odrl:Constraint;
|
|
627
|
+
odrl:leftOperand :coolingOffDays;
|
|
628
|
+
odrl:operator odrl:gteq;
|
|
629
|
+
odrl:rightOperand ?minDays
|
|
630
|
+
].
|
|
631
|
+
}.
|
|
632
|
+
}.
|
|
633
|
+
|
|
634
|
+
# Fix for :TrackingNoOptIn — require explicit opt-in consent constraint
|
|
635
|
+
{
|
|
636
|
+
?risk a :Risk;
|
|
637
|
+
:issue :TrackingNoOptIn;
|
|
638
|
+
:aboutAgreement ?agreement;
|
|
639
|
+
:forProfile ?profile.
|
|
640
|
+
|
|
641
|
+
?agreement :policyGraph ?G.
|
|
642
|
+
?G log:includes { ?perm odrl:action :trackUser. }.
|
|
643
|
+
|
|
644
|
+
( ?risk :Mitigation ) log:skolem ?fix.
|
|
645
|
+
|
|
646
|
+
"Suggested fix: require opt-in consent for tracking (optInConsent=true)." log:equalTo ?fixText.
|
|
647
|
+
}
|
|
648
|
+
=>
|
|
649
|
+
{
|
|
650
|
+
?risk :hasMitigation ?fix.
|
|
651
|
+
?fix a :Mitigation;
|
|
652
|
+
:forRisk ?risk;
|
|
653
|
+
:fixText ?fixText;
|
|
654
|
+
:suggestAdd {
|
|
655
|
+
?perm odrl:constraint [
|
|
656
|
+
a odrl:Constraint;
|
|
657
|
+
odrl:leftOperand :optInConsent;
|
|
658
|
+
odrl:operator odrl:eq;
|
|
659
|
+
odrl:rightOperand true
|
|
660
|
+
].
|
|
661
|
+
}.
|
|
662
|
+
}.
|
|
663
|
+
|
|
664
|
+
# Fix for :NoDataExport — permit data export / portability
|
|
665
|
+
{
|
|
666
|
+
?risk a :Risk;
|
|
667
|
+
:issue :NoDataExport;
|
|
668
|
+
:aboutAgreement ?agreement;
|
|
669
|
+
:forProfile ?profile.
|
|
670
|
+
|
|
671
|
+
?agreement :policyGraph ?G.
|
|
672
|
+
|
|
673
|
+
( ?risk :Mitigation ) log:skolem ?fix.
|
|
674
|
+
|
|
675
|
+
"Suggested fix: add a permission to export/download user data (data portability)." log:equalTo ?fixText.
|
|
676
|
+
}
|
|
677
|
+
=>
|
|
678
|
+
{
|
|
679
|
+
?risk :hasMitigation ?fix.
|
|
680
|
+
?fix a :Mitigation;
|
|
681
|
+
:forRisk ?risk;
|
|
682
|
+
:fixText ?fixText;
|
|
683
|
+
:suggestAdd {
|
|
684
|
+
:Policy3 odrl:permission [
|
|
685
|
+
a odrl:Permission;
|
|
686
|
+
odrl:action :exportData;
|
|
687
|
+
odrl:target :UserData
|
|
688
|
+
].
|
|
689
|
+
}.
|
|
690
|
+
}.
|
|
691
|
+
|
|
692
|
+
# Fix for :ProviderMayDeleteData — prohibit deletion or constrain to lawful/consumer-requested cases
|
|
693
|
+
{
|
|
694
|
+
?risk a :Risk;
|
|
695
|
+
:issue :ProviderMayDeleteData;
|
|
696
|
+
:aboutAgreement ?agreement;
|
|
697
|
+
:forProfile ?profile.
|
|
698
|
+
|
|
699
|
+
?agreement :policyGraph ?G.
|
|
700
|
+
?G log:includes { ?perm odrl:action :deleteUserData. }.
|
|
701
|
+
|
|
702
|
+
( ?risk :Mitigation ) log:skolem ?fix.
|
|
703
|
+
|
|
704
|
+
"Suggested fix: remove provider discretion to delete data; allow deletion only on consumer request or legal obligation." log:equalTo ?fixText.
|
|
705
|
+
}
|
|
706
|
+
=>
|
|
707
|
+
{
|
|
708
|
+
?risk :hasMitigation ?fix.
|
|
709
|
+
?fix a :Mitigation;
|
|
710
|
+
:forRisk ?risk;
|
|
711
|
+
:fixText ?fixText;
|
|
712
|
+
:suggestAdd {
|
|
713
|
+
?perm odrl:constraint [
|
|
714
|
+
a odrl:Constraint;
|
|
715
|
+
odrl:leftOperand :deletionGround;
|
|
716
|
+
odrl:operator odrl:isAnyOf;
|
|
717
|
+
odrl:rightOperand ( :consumerRequest :legalObligation )
|
|
718
|
+
].
|
|
719
|
+
}.
|
|
720
|
+
}.
|
|
721
|
+
|
|
722
|
+
# Fix for :LiabilityCapTooLow — raise cap to meet minimum
|
|
723
|
+
{
|
|
724
|
+
?risk a :Risk;
|
|
725
|
+
:issue :LiabilityCapTooLow;
|
|
726
|
+
:aboutAgreement ?agreement;
|
|
727
|
+
:forProfile ?profile.
|
|
728
|
+
|
|
729
|
+
:Need_MinLiabilityCap :minLiabilityCapEuro ?minCap.
|
|
730
|
+
|
|
731
|
+
?agreement :policyGraph ?G.
|
|
732
|
+
?G log:includes { ?perm odrl:action :autoRenewSubscription. }.
|
|
733
|
+
|
|
734
|
+
( ?risk :Mitigation ) log:skolem ?fix.
|
|
735
|
+
|
|
736
|
+
( "Suggested fix: raise liabilityCapEuro so it is >= %s EUR (or remove the cap where inappropriate)."
|
|
737
|
+
?minCap
|
|
738
|
+
) string:format ?fixText.
|
|
739
|
+
}
|
|
740
|
+
=>
|
|
741
|
+
{
|
|
742
|
+
?risk :hasMitigation ?fix.
|
|
743
|
+
?fix a :Mitigation;
|
|
744
|
+
:forRisk ?risk;
|
|
745
|
+
:fixText ?fixText;
|
|
746
|
+
:suggestAdd {
|
|
747
|
+
?perm odrl:constraint [
|
|
748
|
+
a odrl:Constraint;
|
|
749
|
+
odrl:leftOperand :liabilityCapEuro;
|
|
750
|
+
odrl:operator odrl:gteq;
|
|
751
|
+
odrl:rightOperand ?minCap
|
|
752
|
+
].
|
|
753
|
+
}.
|
|
754
|
+
}.
|
|
755
|
+
|
|
756
|
+
# ---------------------------------------------------
|
|
757
|
+
# 5) Normalize score (clamp at 100) + severity labels
|
|
758
|
+
# ---------------------------------------------------
|
|
759
|
+
|
|
760
|
+
{ ?r a :Risk; :rawScore ?raw. ?raw math:greaterThan 100. } => { ?r :score 100. }.
|
|
761
|
+
{ ?r a :Risk; :rawScore ?raw. 100 math:greaterThan ?raw. } => { ?r :score ?raw. }.
|
|
762
|
+
{ ?r a :Risk; :rawScore ?raw. ?raw math:equalTo 100. } => { ?r :score 100. }.
|
|
763
|
+
|
|
764
|
+
{ ?r a :Risk; :score ?s. ?s math:notLessThan 80. } => { ?r :severity :High. }.
|
|
765
|
+
{ ?r a :Risk; :score ?s. ?s math:lessThan 80. ?s math:notLessThan 50. } => { ?r :severity :Medium. }.
|
|
766
|
+
{ ?r a :Risk; :score ?s. ?s math:lessThan 50. } => { ?r :severity :Low. }.
|
|
767
|
+
|
|
768
|
+
# ----------------------------------------------------------------------------
|
|
769
|
+
# 6) Ranked risk list (highest->lowest) + explainable report + mitigation plan
|
|
770
|
+
# ----------------------------------------------------------------------------
|
|
771
|
+
|
|
772
|
+
# Header line
|
|
773
|
+
{
|
|
774
|
+
?agreement a :Agreement; :label ?alabel.
|
|
775
|
+
?profile a :ConsumerProfile; :label ?plabel.
|
|
776
|
+
( "\n=== Risk report for %s (profile: %s) ===\n" ?alabel ?plabel ) string:format ?hdr.
|
|
777
|
+
}
|
|
778
|
+
=>
|
|
779
|
+
{
|
|
780
|
+
( ?agreement ?profile 0 ) log:outputString ?hdr.
|
|
781
|
+
}.
|
|
782
|
+
|
|
783
|
+
# Produce ranked risk lines
|
|
784
|
+
{
|
|
785
|
+
?agreement a :Agreement.
|
|
786
|
+
?profile a :ConsumerProfile.
|
|
787
|
+
|
|
788
|
+
( ( ?score ?risk )
|
|
789
|
+
{ ?risk a :Risk;
|
|
790
|
+
:aboutAgreement ?agreement;
|
|
791
|
+
:forProfile ?profile;
|
|
792
|
+
:score ?score. }
|
|
793
|
+
?pairs
|
|
794
|
+
) log:collectAllIn 1.
|
|
795
|
+
|
|
796
|
+
?pairs list:sort ?sortedAsc.
|
|
797
|
+
?sortedAsc list:reverse ?sortedDesc.
|
|
798
|
+
|
|
799
|
+
?sortedDesc list:iterate ( ?i ?pair ).
|
|
800
|
+
( ?i 1 ) math:sum ?rank.
|
|
801
|
+
|
|
802
|
+
( ?pair 0 ) list:memberAt ?score.
|
|
803
|
+
( ?pair 1 ) list:memberAt ?risk.
|
|
804
|
+
|
|
805
|
+
?risk :severity ?sev;
|
|
806
|
+
:clauseId ?cid;
|
|
807
|
+
:title ?title;
|
|
808
|
+
:explanation ?why.
|
|
809
|
+
|
|
810
|
+
( "%s) score=%s (%s), clause %s — %s. %s\n"
|
|
811
|
+
?rank ?score ?sev ?cid ?title ?why
|
|
812
|
+
) string:format ?line.
|
|
813
|
+
}
|
|
814
|
+
=>
|
|
815
|
+
{
|
|
816
|
+
( ?agreement ?profile ?rank ) log:outputString ?line.
|
|
817
|
+
}.
|
|
818
|
+
|
|
819
|
+
# Mitigation header
|
|
820
|
+
{
|
|
821
|
+
?agreement a :Agreement.
|
|
822
|
+
?profile a :ConsumerProfile.
|
|
823
|
+
"\n--- Suggested mitigations (highest risk first) ---\n" log:equalTo ?hdr2.
|
|
824
|
+
}
|
|
825
|
+
=>
|
|
826
|
+
{
|
|
827
|
+
( ?agreement ?profile 999 ) log:outputString ?hdr2.
|
|
828
|
+
}.
|
|
829
|
+
|
|
830
|
+
# Produce ranked mitigation lines (aligned with risk ranking by score)
|
|
831
|
+
{
|
|
832
|
+
?agreement a :Agreement.
|
|
833
|
+
?profile a :ConsumerProfile.
|
|
834
|
+
|
|
835
|
+
( ( ?score ?risk ?fixText )
|
|
836
|
+
{
|
|
837
|
+
?risk a :Risk;
|
|
838
|
+
:aboutAgreement ?agreement;
|
|
839
|
+
:forProfile ?profile;
|
|
840
|
+
:score ?score;
|
|
841
|
+
:clauseId ?cid;
|
|
842
|
+
:title ?title;
|
|
843
|
+
:hasMitigation ?fix.
|
|
844
|
+
?fix :fixText ?fixText.
|
|
845
|
+
}
|
|
846
|
+
?triples
|
|
847
|
+
) log:collectAllIn 1.
|
|
848
|
+
|
|
849
|
+
?triples list:sort ?sortedAsc.
|
|
850
|
+
?sortedAsc list:reverse ?sortedDesc.
|
|
851
|
+
|
|
852
|
+
?sortedDesc list:iterate ( ?i ?t ).
|
|
853
|
+
( ?i 1 ) math:sum ?rank.
|
|
854
|
+
|
|
855
|
+
( ?t 0 ) list:memberAt ?score.
|
|
856
|
+
( ?t 1 ) list:memberAt ?risk.
|
|
857
|
+
( ?t 2 ) list:memberAt ?fixText.
|
|
858
|
+
|
|
859
|
+
?risk :clauseId ?cid;
|
|
860
|
+
:title ?title.
|
|
861
|
+
|
|
862
|
+
( "%s) clause %s — %s (score=%s). %s\n"
|
|
863
|
+
?rank ?cid ?title ?score ?fixText
|
|
864
|
+
) string:format ?line.
|
|
865
|
+
}
|
|
866
|
+
=>
|
|
867
|
+
{
|
|
868
|
+
( ?agreement ?profile (1000 ?rank) ) log:outputString ?line.
|
|
869
|
+
}.
|
|
870
|
+
|