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.
@@ -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/DPV-RISK 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
+