eyeling 1.16.4 → 1.17.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 +158 -7
- package/README.md +1 -1
- package/examples/auroracare.n3 +528 -0
- package/examples/control-system.n3 +222 -47
- package/examples/delfour.n3 +409 -0
- package/examples/easter.n3 +208 -78
- package/examples/gps.n3 +144 -53
- package/examples/ill-formed-literals.n3 +195 -0
- package/examples/output/auroracare.n3 +149 -0
- package/examples/output/control-system.n3 +19 -4
- package/examples/output/delfour.n3 +30 -0
- package/examples/output/digital-product-passport.n3 +1 -10
- package/examples/output/easter.n3 +150 -32
- package/examples/output/genetic-algorithm-knapsack.n3 +1 -3
- package/examples/output/genetic-algorithm.n3 +1 -3
- package/examples/output/gps.n3 +14 -5
- package/examples/output/ill-formed-literals.n3 +27 -0
- package/examples/output/interop-demo.n3 +1 -34
- package/examples/output/odrl-dpv-ehds-risk-ranked.n3 +21 -12
- package/examples/output/odrl-dpv-healthcare-risk-ranked.n3 +16 -116
- package/examples/output/odrl-dpv-risk-ranked.n3 +22 -13
- package/examples/output/odrl-risk-mitigation.n3 +17 -206
- package/examples/output/odrl-risk.n3 +5 -63
- package/examples/output/parcellocker.n3 +20 -0
- package/examples/output/sqrt2-cauchy.n3 +13 -57
- package/examples/output/sqrt2-dedekind.n3 +31 -108
- package/examples/parcellocker.n3 +164 -0
- package/eyeling.js +50 -28
- package/lib/builtins.js +1 -1
- package/lib/cli.js +33 -26
- package/lib/engine.js +14 -1
- package/lib/prelude.js +2 -0
- package/package.json +1 -1
- package/test/api.test.js +2 -2
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Control System
|
|
3
|
-
#
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Control System — ARC-style
|
|
3
|
+
#
|
|
4
|
+
# This example shows how a small control system turns sensor readings into two
|
|
5
|
+
# actuator commands. One command anticipates a known disturbance before it hits
|
|
6
|
+
# the system. The other compares the measured output with the target and then
|
|
7
|
+
# corrects the remaining gap. The ARC presentation makes the result easy to
|
|
8
|
+
# inspect by giving an Answer, a clear Reason Why, and independent Checks.
|
|
9
|
+
# =============================================================================
|
|
4
10
|
|
|
11
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
5
12
|
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
6
|
-
@prefix : <
|
|
13
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#>.
|
|
14
|
+
@prefix : <https://example.org/control-system#>.
|
|
15
|
+
|
|
16
|
+
# -----
|
|
17
|
+
# Facts
|
|
18
|
+
# -----
|
|
7
19
|
|
|
8
20
|
# input
|
|
9
21
|
:input1 :measurement1 (6 11).
|
|
@@ -23,55 +35,218 @@
|
|
|
23
35
|
:output2 :measurement4 24.
|
|
24
36
|
:output2 :target2 29.
|
|
25
37
|
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
# -------------
|
|
39
|
+
# control logic
|
|
40
|
+
# -------------
|
|
41
|
+
|
|
42
|
+
{ :input1 :measurement10 ?M1.
|
|
43
|
+
:input2 :measurement2 true.
|
|
44
|
+
:disturbance1 :measurement3 ?D1.
|
|
45
|
+
(?M1 19.6) math:product ?C1.
|
|
46
|
+
(10 ?C2) math:exponentiation ?D1.
|
|
47
|
+
(?C1 ?C2) math:difference ?C.
|
|
34
48
|
}
|
|
35
|
-
=>
|
|
36
|
-
{
|
|
37
|
-
:actuator1 :control1 ?C.
|
|
38
|
-
}.
|
|
49
|
+
=>
|
|
50
|
+
{ :actuator1 :control1 ?C. }.
|
|
39
51
|
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
(?C1 ?C2) math:sum ?C. # PND feedback control
|
|
52
|
+
{ :input3 :measurement3 ?M3.
|
|
53
|
+
:state3 :observation3 ?P3.
|
|
54
|
+
:output2 :measurement4 ?M4.
|
|
55
|
+
:output2 :target2 ?T2.
|
|
56
|
+
(?T2 ?M4) math:difference ?E.
|
|
57
|
+
(?P3 ?M4) math:difference ?D.
|
|
58
|
+
(5.8 ?E) math:product ?C1.
|
|
59
|
+
(7.3 ?E) math:quotient ?N.
|
|
60
|
+
(?N ?D) math:product ?C2.
|
|
61
|
+
(?C1 ?C2) math:sum ?C.
|
|
51
62
|
}
|
|
52
|
-
=>
|
|
53
|
-
{
|
|
54
|
-
|
|
63
|
+
=>
|
|
64
|
+
{ :actuator2 :control1 ?C. }.
|
|
65
|
+
|
|
66
|
+
{ ?I :measurement10 ?M. }
|
|
67
|
+
<=
|
|
68
|
+
{ ?I :measurement1 (?M1 ?M2).
|
|
69
|
+
?M1 math:lessThan ?M2.
|
|
70
|
+
(?M2 ?M1) math:difference ?M3.
|
|
71
|
+
(?M3 0.5) math:exponentiation ?M.
|
|
55
72
|
}.
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<=
|
|
62
|
-
{
|
|
63
|
-
?I :measurement1 (?M1 ?M2).
|
|
64
|
-
?M1 math:lessThan ?M2.
|
|
65
|
-
(?M2 ?M1) math:difference ?M3.
|
|
66
|
-
(?M3 0.5) math:exponentiation ?M.
|
|
74
|
+
{ ?I :measurement10 ?M1. }
|
|
75
|
+
<=
|
|
76
|
+
{ ?I :measurement1 (?M1 ?M2).
|
|
77
|
+
?M1 math:notLessThan ?M2.
|
|
67
78
|
}.
|
|
68
79
|
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
# -------------------------------------------------------------------
|
|
81
|
+
# Explanation layer: compute an explicit why-story from the raw facts
|
|
82
|
+
# -------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
{ :input1 :measurement1 (?Low ?High).
|
|
85
|
+
?Low math:lessThan ?High.
|
|
86
|
+
(?High ?Low) math:difference ?Gap.
|
|
87
|
+
(?Gap 0.5) math:exponentiation ?Norm.
|
|
71
88
|
}
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
=>
|
|
90
|
+
{ :why :sensorTrend "rising";
|
|
91
|
+
:sensorLow ?Low;
|
|
92
|
+
:sensorHigh ?High;
|
|
93
|
+
:sensorGap ?Gap;
|
|
94
|
+
:normalizedMeasurement ?Norm. }.
|
|
95
|
+
|
|
96
|
+
{ :why :normalizedMeasurement ?Norm.
|
|
97
|
+
(?Norm 19.6) math:product ?Prop.
|
|
98
|
+
:disturbance1 :measurement3 ?Dist.
|
|
99
|
+
(10 ?Comp) math:exponentiation ?Dist.
|
|
100
|
+
(?Prop ?Comp) math:difference ?Cmd.
|
|
101
|
+
}
|
|
102
|
+
=>
|
|
103
|
+
{ :why :feedforwardProportional ?Prop;
|
|
104
|
+
:disturbanceMagnitude ?Dist;
|
|
105
|
+
:disturbanceCompensation ?Comp;
|
|
106
|
+
:feedforwardCommand ?Cmd. }.
|
|
107
|
+
|
|
108
|
+
{ :state3 :observation3 ?Observed.
|
|
109
|
+
:output2 :measurement4 ?Measured.
|
|
110
|
+
:output2 :target2 ?Target.
|
|
111
|
+
(?Target ?Measured) math:difference ?Err.
|
|
112
|
+
(?Observed ?Measured) math:difference ?DiffErr.
|
|
113
|
+
(5.8 ?Err) math:product ?Prop.
|
|
114
|
+
(7.3 ?Err) math:quotient ?Factor.
|
|
115
|
+
(?Factor ?DiffErr) math:product ?DiffPart.
|
|
116
|
+
(?Prop ?DiffPart) math:sum ?Cmd.
|
|
117
|
+
}
|
|
118
|
+
=>
|
|
119
|
+
{ :why :observedState ?Observed;
|
|
120
|
+
:measuredOutput ?Measured;
|
|
121
|
+
:targetOutput ?Target;
|
|
122
|
+
:trackingError ?Err;
|
|
123
|
+
:differentialError ?DiffErr;
|
|
124
|
+
:feedbackProportional ?Prop;
|
|
125
|
+
:feedbackFactor ?Factor;
|
|
126
|
+
:feedbackDifferential ?DiffPart;
|
|
127
|
+
:feedbackCommand ?Cmd. }.
|
|
128
|
+
|
|
129
|
+
{ :actuator1 :control1 ?A1.
|
|
130
|
+
:actuator2 :control1 ?A2. }
|
|
131
|
+
=>
|
|
132
|
+
{ :decision :answer "Send both actuator commands now.";
|
|
133
|
+
:actuator1Command ?A1;
|
|
134
|
+
:actuator2Command ?A2. }.
|
|
135
|
+
|
|
136
|
+
# ----------
|
|
137
|
+
# ARC report
|
|
138
|
+
# ----------
|
|
139
|
+
|
|
140
|
+
{ :decision :answer ?Answer;
|
|
141
|
+
:actuator1Command ?A1;
|
|
142
|
+
:actuator2Command ?A2.
|
|
143
|
+
:why :sensorTrend ?Trend;
|
|
144
|
+
:sensorLow ?Low;
|
|
145
|
+
:sensorHigh ?High;
|
|
146
|
+
:sensorGap ?Gap;
|
|
147
|
+
:normalizedMeasurement ?Norm;
|
|
148
|
+
:feedforwardProportional ?FFProp;
|
|
149
|
+
:disturbanceMagnitude ?Dist;
|
|
150
|
+
:disturbanceCompensation ?FFComp;
|
|
151
|
+
:trackingError ?Err;
|
|
152
|
+
:differentialError ?DiffErr;
|
|
153
|
+
:feedbackProportional ?FBProp;
|
|
154
|
+
:feedbackFactor ?FBFactor;
|
|
155
|
+
:feedbackDifferential ?FBDiff.
|
|
156
|
+
(
|
|
157
|
+
"Control System — ARC explanation of two control signals\n\n"
|
|
158
|
+
"Answer\n"
|
|
159
|
+
?Answer "\n"
|
|
160
|
+
"Actuator 1 command: " ?A1 "\n"
|
|
161
|
+
"Actuator 2 command: " ?A2 "\n\n"
|
|
162
|
+
"Reason Why\n"
|
|
163
|
+
"The first sensor pair is " ?Low " and " ?High ", so the reading is " ?Trend
|
|
164
|
+
" and the controller normalizes the gap " ?Gap " into " ?Norm ". "
|
|
165
|
+
"That normalized value creates a feedforward term of " ?FFProp
|
|
166
|
+
", while the known disturbance " ?Dist " contributes a compensation term of " ?FFComp
|
|
167
|
+
". Subtracting that compensation gives actuator 1 the command " ?A1 ". "
|
|
168
|
+
"For actuator 2, the target is " ?Err " units above the measured output, so the tracking error is positive. "
|
|
169
|
+
"The observed state is " ?DiffErr " relative units below the measured output, so the differential correction is negative. "
|
|
170
|
+
"That yields a proportional feedback part of " ?FBProp ", a nonlinear factor of " ?FBFactor
|
|
171
|
+
", and a differential contribution of " ?FBDiff ". Together they produce actuator 2 command " ?A2 ".\n\n"
|
|
172
|
+
"Check\n"
|
|
173
|
+
"C1 OK - the first sensor pair is rising, so the normalization uses the rising-branch rule.\n"
|
|
174
|
+
"C2 OK - the normalized measurement is positive and smaller than the raw gap, which is consistent with a square-root normalization.\n"
|
|
175
|
+
"C3 OK - actuator 1 is lower than its proportional feedforward term because disturbance compensation is subtracted.\n"
|
|
176
|
+
"C4 OK - the target is above the measured output, so the tracking error is positive.\n"
|
|
177
|
+
"C5 OK - the observed state is below the measured output, so the differential error is negative.\n"
|
|
178
|
+
"C6 OK - actuator 2 is lower than its pure proportional term because the differential part reduces it.\n"
|
|
179
|
+
"C7 OK - actuator 1 matches an independently reconstructed feedforward calculation.\n"
|
|
180
|
+
"C8 OK - actuator 2 matches an independently reconstructed feedback calculation.\n"
|
|
181
|
+
"C9 OK - both actuator commands stay positive.\n"
|
|
182
|
+
) string:concatenation ?Block.
|
|
183
|
+
}
|
|
184
|
+
=>
|
|
185
|
+
{ :report log:outputString ?Block. }.
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# Independent checks: validate the shape of the reasoning, not just constants
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
# The first sensor pair must be rising for this explanation path.
|
|
192
|
+
{ :input1 :measurement1 (?Low ?High).
|
|
193
|
+
?Low math:notLessThan ?High. }
|
|
194
|
+
=> false.
|
|
195
|
+
|
|
196
|
+
# Square-root normalization should stay positive and smaller than the raw gap.
|
|
197
|
+
{ :why :normalizedMeasurement ?Norm.
|
|
198
|
+
?Norm math:notGreaterThan 0. }
|
|
199
|
+
=> false.
|
|
200
|
+
|
|
201
|
+
{ :why :normalizedMeasurement ?Norm;
|
|
202
|
+
:sensorGap ?Gap.
|
|
203
|
+
?Norm math:notLessThan ?Gap. }
|
|
204
|
+
=> false.
|
|
205
|
+
|
|
206
|
+
# Feedforward compensation must reduce the raw proportional response.
|
|
207
|
+
{ :actuator1 :control1 ?A1.
|
|
208
|
+
:why :feedforwardProportional ?Prop.
|
|
209
|
+
?A1 math:notLessThan ?Prop. }
|
|
210
|
+
=> false.
|
|
211
|
+
|
|
212
|
+
# The tracking target must exceed the current measurement in this case.
|
|
213
|
+
{ :output2 :target2 ?Target.
|
|
214
|
+
:output2 :measurement4 ?Measured.
|
|
215
|
+
?Target math:notGreaterThan ?Measured. }
|
|
216
|
+
=> false.
|
|
217
|
+
|
|
218
|
+
# The observed state must be below the measured output, making the differential error negative.
|
|
219
|
+
{ :state3 :observation3 ?Observed.
|
|
220
|
+
:output2 :measurement4 ?Measured.
|
|
221
|
+
?Observed math:notLessThan ?Measured. }
|
|
222
|
+
=> false.
|
|
223
|
+
|
|
224
|
+
{ :why :feedbackDifferential ?Diff.
|
|
225
|
+
?Diff math:notLessThan 0. }
|
|
226
|
+
=> false.
|
|
227
|
+
|
|
228
|
+
# The differential part must reduce the pure proportional feedback command.
|
|
229
|
+
{ :actuator2 :control1 ?A2.
|
|
230
|
+
:why :feedbackProportional ?Prop.
|
|
231
|
+
?A2 math:notLessThan ?Prop. }
|
|
232
|
+
=> false.
|
|
233
|
+
|
|
234
|
+
# Independent reconstructions must agree with the derived actuator commands.
|
|
235
|
+
{ :actuator1 :control1 ?A1.
|
|
236
|
+
:why :feedforwardCommand ?Rebuilt.
|
|
237
|
+
?A1 math:notEqualTo ?Rebuilt. }
|
|
238
|
+
=> false.
|
|
239
|
+
|
|
240
|
+
{ :actuator2 :control1 ?A2.
|
|
241
|
+
:why :feedbackCommand ?Rebuilt.
|
|
242
|
+
?A2 math:notEqualTo ?Rebuilt. }
|
|
243
|
+
=> false.
|
|
244
|
+
|
|
245
|
+
# The resulting commands should stay positive.
|
|
246
|
+
{ :actuator1 :control1 ?A1.
|
|
247
|
+
?A1 math:notGreaterThan 0. }
|
|
248
|
+
=> false.
|
|
77
249
|
|
|
250
|
+
{ :actuator2 :control1 ?A2.
|
|
251
|
+
?A2 math:notGreaterThan 0. }
|
|
252
|
+
=> false.
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Delfour — Ruben Verborgh's "Inside the Insight Economy" case.
|
|
3
|
+
# See https://ruben.verborgh.org/blog/2025/08/12/inside-the-insight-economy/
|
|
4
|
+
#
|
|
5
|
+
# This example shows how a person can share a useful shopping hint without
|
|
6
|
+
# exposing sensitive health details. A phone turns a private condition into a
|
|
7
|
+
# neutral, limited insight such as “prefer lower-sugar products”, attaches clear
|
|
8
|
+
# usage rules and an expiry time, and sends it to a store scanner. The scanner
|
|
9
|
+
# may use that insight to suggest a better product, but not for unrelated
|
|
10
|
+
# purposes such as marketing.
|
|
11
|
+
# ==============================================================================
|
|
12
|
+
|
|
13
|
+
@prefix : <https://example.org/delfour#> .
|
|
14
|
+
@prefix arc: <https://josd.github.io/arc/terms#> .
|
|
15
|
+
@prefix ins: <https://example.org/insight#> .
|
|
16
|
+
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
|
|
17
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
18
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
19
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
20
|
+
@prefix crypto: <http://www.w3.org/2000/10/swap/crypto#> .
|
|
21
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
22
|
+
|
|
23
|
+
# -----
|
|
24
|
+
# Facts
|
|
25
|
+
# -----
|
|
26
|
+
|
|
27
|
+
:case
|
|
28
|
+
a arc:Case ;
|
|
29
|
+
:caseName "delfour" ;
|
|
30
|
+
arc:question "Is the Delfour self-scanner allowed to use a neutral shopping insight for shopping assistance, and if so what lower-sugar alternative should it suggest?" ;
|
|
31
|
+
:expectedFilesWritten 6 ;
|
|
32
|
+
:requestPurpose "shopping_assist" ;
|
|
33
|
+
:requestAction odrl:use ;
|
|
34
|
+
:phoneCreatedAt "2025-10-05T20:33:48.907163+00:00"^^xsd:dateTime ;
|
|
35
|
+
:phoneExpiresAt "2025-10-05T22:33:48.907185+00:00"^^xsd:dateTime ;
|
|
36
|
+
:scannerAuthAt "2025-10-05T20:35:48.907163+00:00"^^xsd:dateTime ;
|
|
37
|
+
:scannerDutyAt "2025-10-05T20:37:48.907163+00:00"^^xsd:dateTime ;
|
|
38
|
+
:filesWritten 6 ;
|
|
39
|
+
:auditEntries 1 .
|
|
40
|
+
|
|
41
|
+
:catalog a :Catalog .
|
|
42
|
+
|
|
43
|
+
:prod_BIS_001
|
|
44
|
+
a :Product ;
|
|
45
|
+
:productId "prod:BIS_001" ;
|
|
46
|
+
:productName "Classic Tea Biscuits" ;
|
|
47
|
+
:sugarTenths 120 ;
|
|
48
|
+
:sugarPerServing 12.0 .
|
|
49
|
+
|
|
50
|
+
:prod_BIS_101
|
|
51
|
+
a :Product ;
|
|
52
|
+
:productId "prod:BIS_101" ;
|
|
53
|
+
:productName "Low-Sugar Tea Biscuits" ;
|
|
54
|
+
:sugarTenths 30 ;
|
|
55
|
+
:sugarPerServing 3.0 .
|
|
56
|
+
|
|
57
|
+
:prod_CHOC_050
|
|
58
|
+
a :Product ;
|
|
59
|
+
:productId "prod:CHOC_050" ;
|
|
60
|
+
:productName "Milk Chocolate Bar" ;
|
|
61
|
+
:sugarTenths 150 ;
|
|
62
|
+
:sugarPerServing 15.0 .
|
|
63
|
+
|
|
64
|
+
:prod_CHOC_150
|
|
65
|
+
a :Product ;
|
|
66
|
+
:productId "prod:CHOC_150" ;
|
|
67
|
+
:productName "85% Dark Chocolate" ;
|
|
68
|
+
:sugarTenths 60 ;
|
|
69
|
+
:sugarPerServing 6.0 .
|
|
70
|
+
|
|
71
|
+
:householdProfile :condition "Diabetes" .
|
|
72
|
+
:scan :scannedProduct :prod_BIS_001 .
|
|
73
|
+
|
|
74
|
+
<https://example.org/insight/delfour>
|
|
75
|
+
a ins:Insight ;
|
|
76
|
+
:metric "sugar_g_per_serving" ;
|
|
77
|
+
:thresholdTenths 100 ;
|
|
78
|
+
:thresholdDisplay "10.0" ;
|
|
79
|
+
:thresholdG 10.0 ;
|
|
80
|
+
:suggestionPolicy "lower_metric_first_higher_price_ok" ;
|
|
81
|
+
:scopeDevice "self-scanner" ;
|
|
82
|
+
:scopeEvent "pick_up_scanner" ;
|
|
83
|
+
:retailer "Delfour" ;
|
|
84
|
+
:createdAt "2025-10-05T20:33:48.907163+00:00"^^xsd:dateTime ;
|
|
85
|
+
:expiresAt "2025-10-05T22:33:48.907185+00:00"^^xsd:dateTime ;
|
|
86
|
+
:serializedLowercase "{\"createdat\":\"2025-10-05t20:33:48.907163+00:00\",\"expiresat\":\"2025-10-05t22:33:48.907185+00:00\",\"id\":\"https://example.org/insight/delfour\",\"metric\":\"sugar_g_per_serving\",\"retailer\":\"delfour\",\"scopedevice\":\"self-scanner\",\"scopeevent\":\"pick_up_scanner\",\"suggestionpolicy\":\"lower_metric_first_higher_price_ok\",\"threshold\":10.0,\"type\":\"ins:insight\"}" .
|
|
87
|
+
|
|
88
|
+
:policy
|
|
89
|
+
a odrl:Policy ;
|
|
90
|
+
:profile "Delfour-Insight-Policy" ;
|
|
91
|
+
odrl:permission [
|
|
92
|
+
odrl:action odrl:use ;
|
|
93
|
+
odrl:target <https://example.org/insight/delfour> ;
|
|
94
|
+
odrl:constraint [
|
|
95
|
+
odrl:leftOperand odrl:purpose ;
|
|
96
|
+
odrl:operator odrl:eq ;
|
|
97
|
+
odrl:rightOperand "shopping_assist"
|
|
98
|
+
]
|
|
99
|
+
] ;
|
|
100
|
+
odrl:prohibition [
|
|
101
|
+
odrl:action odrl:distribute ;
|
|
102
|
+
odrl:target <https://example.org/insight/delfour> ;
|
|
103
|
+
odrl:constraint [
|
|
104
|
+
odrl:leftOperand odrl:purpose ;
|
|
105
|
+
odrl:operator odrl:eq ;
|
|
106
|
+
odrl:rightOperand "marketing"
|
|
107
|
+
]
|
|
108
|
+
] ;
|
|
109
|
+
odrl:duty [
|
|
110
|
+
odrl:action odrl:delete ;
|
|
111
|
+
odrl:constraint [
|
|
112
|
+
odrl:leftOperand odrl:dateTime ;
|
|
113
|
+
odrl:operator odrl:eq ;
|
|
114
|
+
odrl:rightOperand "2025-10-05T22:33:48.907185+00:00"^^xsd:dateTime
|
|
115
|
+
]
|
|
116
|
+
] .
|
|
117
|
+
|
|
118
|
+
:envelope
|
|
119
|
+
:insight <https://example.org/insight/delfour> ;
|
|
120
|
+
:policy :policy ;
|
|
121
|
+
:canonicalJson "{\"insight\":{\"createdAt\":\"2025-10-05T20:33:48.907163+00:00\",\"expiresAt\":\"2025-10-05T22:33:48.907185+00:00\",\"id\":\"https://example.org/insight/delfour\",\"metric\":\"sugar_g_per_serving\",\"retailer\":\"Delfour\",\"scopeDevice\":\"self-scanner\",\"scopeEvent\":\"pick_up_scanner\",\"suggestionPolicy\":\"lower_metric_first_higher_price_ok\",\"threshold\":10.0,\"type\":\"ins:Insight\"},\"policy\":{\"duty\":{\"action\":\"odrl:delete\",\"constraint\":{\"leftOperand\":\"odrl:dateTime\",\"operator\":\"odrl:eq\",\"rightOperand\":\"2025-10-05T22:33:48.907185+00:00\"}},\"permission\":{\"action\":\"odrl:use\",\"constraint\":{\"leftOperand\":\"odrl:purpose\",\"operator\":\"odrl:eq\",\"rightOperand\":\"shopping_assist\"},\"target\":\"https://example.org/insight/delfour\"},\"profile\":\"Delfour-Insight-Policy\",\"prohibition\":{\"action\":\"odrl:distribute\",\"constraint\":{\"leftOperand\":\"odrl:purpose\",\"operator\":\"odrl:eq\",\"rightOperand\":\"marketing\"},\"target\":\"https://example.org/insight/delfour\"},\"type\":\"odrl:Policy\"}}" .
|
|
122
|
+
|
|
123
|
+
:signature
|
|
124
|
+
:alg "HMAC-SHA256" ;
|
|
125
|
+
:keyid "demo-shared-secret" ;
|
|
126
|
+
:created "2025-10-05T20:33:48.907163+00:00"^^xsd:dateTime ;
|
|
127
|
+
:payloadHashSHA256 "34ad35638dfd7c67d031eeca8abb235ec24280740f863f3f31cd9d7b6517f098" ;
|
|
128
|
+
:signatureHMAC "b21d0072d90112a9f820aced0286889f4b6ef92b145e6fdef1011f3bfa4608c2" ;
|
|
129
|
+
:hmacVerificationMode :trustedPrecomputedInput .
|
|
130
|
+
|
|
131
|
+
:reasonText
|
|
132
|
+
:value "Household requires low-sugar guidance (diabetes in POD). A neutral Insight is scoped to device 'self-scanner', event 'pick_up_scanner', retailer 'Delfour', and expires soon; the policy confines use to shopping assistance.\n" .
|
|
133
|
+
|
|
134
|
+
# -----
|
|
135
|
+
# Logic
|
|
136
|
+
# -----
|
|
137
|
+
|
|
138
|
+
# desensitize(&["Diabetes"]) -> true
|
|
139
|
+
{ :householdProfile :condition "Diabetes" . }
|
|
140
|
+
=> { :case :needsLowSugar true . } .
|
|
141
|
+
|
|
142
|
+
# derive_insight(...)
|
|
143
|
+
{ :case :needsLowSugar true . }
|
|
144
|
+
=> { <https://example.org/insight/delfour> :derivedFromNeed "low_sugar" . } .
|
|
145
|
+
|
|
146
|
+
# payload_hash_matches
|
|
147
|
+
{
|
|
148
|
+
:envelope :canonicalJson ?json .
|
|
149
|
+
?json crypto:sha256 ?digest .
|
|
150
|
+
:signature :payloadHashSHA256 ?digest .
|
|
151
|
+
} => {
|
|
152
|
+
:check :payloadHashMatches true .
|
|
153
|
+
} .
|
|
154
|
+
|
|
155
|
+
# signature_verified
|
|
156
|
+
{
|
|
157
|
+
:signature :hmacVerificationMode :trustedPrecomputedInput .
|
|
158
|
+
} => {
|
|
159
|
+
:check :signatureVerifies true .
|
|
160
|
+
} .
|
|
161
|
+
|
|
162
|
+
# minimization_no_sensitive_terms
|
|
163
|
+
{
|
|
164
|
+
<https://example.org/insight/delfour> :serializedLowercase ?s .
|
|
165
|
+
?s string:notMatches "diabetes|medical" .
|
|
166
|
+
} => {
|
|
167
|
+
:check :minimizationStripsSensitiveTerms true .
|
|
168
|
+
} .
|
|
169
|
+
|
|
170
|
+
# scope_complete
|
|
171
|
+
{
|
|
172
|
+
<https://example.org/insight/delfour> :scopeDevice ?device ;
|
|
173
|
+
:scopeEvent ?event ;
|
|
174
|
+
:expiresAt ?expiry .
|
|
175
|
+
} => {
|
|
176
|
+
:check :scopeComplete true .
|
|
177
|
+
} .
|
|
178
|
+
|
|
179
|
+
# authorization_allowed
|
|
180
|
+
{
|
|
181
|
+
:policy odrl:permission [
|
|
182
|
+
odrl:action odrl:use ;
|
|
183
|
+
odrl:target <https://example.org/insight/delfour> ;
|
|
184
|
+
odrl:constraint [
|
|
185
|
+
odrl:leftOperand odrl:purpose ;
|
|
186
|
+
odrl:operator odrl:eq ;
|
|
187
|
+
odrl:rightOperand "shopping_assist"
|
|
188
|
+
]
|
|
189
|
+
] .
|
|
190
|
+
:case :scannerAuthAt ?authAt .
|
|
191
|
+
<https://example.org/insight/delfour> :expiresAt ?expiresAt .
|
|
192
|
+
?authAt math:notGreaterThan ?expiresAt .
|
|
193
|
+
} => {
|
|
194
|
+
:decision
|
|
195
|
+
:at "2025-10-05T20:35:48.907163+00:00"^^xsd:dateTime ;
|
|
196
|
+
:outcome "Allowed" ;
|
|
197
|
+
:target <https://example.org/insight/delfour> .
|
|
198
|
+
:check :authorizationAllowed true .
|
|
199
|
+
} .
|
|
200
|
+
|
|
201
|
+
# banner if scanned product meets/exceeds threshold
|
|
202
|
+
{
|
|
203
|
+
:decision :outcome "Allowed" .
|
|
204
|
+
:scan :scannedProduct ?product .
|
|
205
|
+
?product :sugarPerServing ?sugar .
|
|
206
|
+
<https://example.org/insight/delfour> :thresholdG ?threshold .
|
|
207
|
+
?sugar math:notLessThan ?threshold .
|
|
208
|
+
} => {
|
|
209
|
+
:banner
|
|
210
|
+
:headline "Track sugar per serving while you scan" ;
|
|
211
|
+
:note "High sugar" .
|
|
212
|
+
:check :bannerFlagsHighSugar true .
|
|
213
|
+
} .
|
|
214
|
+
|
|
215
|
+
# choose the lower-sugar alternative with the smallest sugar score
|
|
216
|
+
{
|
|
217
|
+
:scan :scannedProduct ?scanned .
|
|
218
|
+
?scanned :sugarTenths ?scannedSugar .
|
|
219
|
+
?candidate a :Product ;
|
|
220
|
+
:sugarTenths ?candidateSugar .
|
|
221
|
+
?scannedSugar math:greaterThan ?candidateSugar .
|
|
222
|
+
1 log:notIncludes {
|
|
223
|
+
?other a :Product ;
|
|
224
|
+
:sugarTenths ?otherSugar .
|
|
225
|
+
?scannedSugar math:greaterThan ?otherSugar .
|
|
226
|
+
?otherSugar math:lessThan ?candidateSugar .
|
|
227
|
+
} .
|
|
228
|
+
} => {
|
|
229
|
+
:case :suggestedAlternative ?candidate .
|
|
230
|
+
} .
|
|
231
|
+
|
|
232
|
+
{
|
|
233
|
+
:banner :note "High sugar" .
|
|
234
|
+
:case :suggestedAlternative ?alt .
|
|
235
|
+
?alt :productName ?altName .
|
|
236
|
+
} => {
|
|
237
|
+
:banner :suggestedAlternative ?altName .
|
|
238
|
+
} .
|
|
239
|
+
|
|
240
|
+
{
|
|
241
|
+
:scan :scannedProduct ?scanned .
|
|
242
|
+
?scanned :sugarTenths ?scannedSugar .
|
|
243
|
+
:case :suggestedAlternative ?alt .
|
|
244
|
+
?alt :sugarTenths ?altSugar .
|
|
245
|
+
?scannedSugar math:greaterThan ?altSugar .
|
|
246
|
+
} => {
|
|
247
|
+
:check :alternativeIsLowerSugar true .
|
|
248
|
+
} .
|
|
249
|
+
|
|
250
|
+
# duty_timing_consistent
|
|
251
|
+
{
|
|
252
|
+
:case :scannerDutyAt ?dutyAt .
|
|
253
|
+
<https://example.org/insight/delfour> :expiresAt ?expiresAt .
|
|
254
|
+
?dutyAt math:notGreaterThan ?expiresAt .
|
|
255
|
+
} => {
|
|
256
|
+
:check :dutyTimingConsistent true .
|
|
257
|
+
} .
|
|
258
|
+
|
|
259
|
+
# marketing_prohibited
|
|
260
|
+
{
|
|
261
|
+
:policy odrl:prohibition [
|
|
262
|
+
odrl:action odrl:distribute ;
|
|
263
|
+
odrl:constraint [
|
|
264
|
+
odrl:rightOperand "marketing"
|
|
265
|
+
]
|
|
266
|
+
] .
|
|
267
|
+
} => {
|
|
268
|
+
:check :marketingProhibited true .
|
|
269
|
+
} .
|
|
270
|
+
|
|
271
|
+
# file count check mirrors EXPECTED_FILES_WRITTEN
|
|
272
|
+
{
|
|
273
|
+
:case :filesWritten 6 .
|
|
274
|
+
} => {
|
|
275
|
+
:check :filesWrittenExpected true .
|
|
276
|
+
} .
|
|
277
|
+
|
|
278
|
+
{
|
|
279
|
+
:check :signatureVerifies true ;
|
|
280
|
+
:payloadHashMatches true ;
|
|
281
|
+
:minimizationStripsSensitiveTerms true ;
|
|
282
|
+
:scopeComplete true ;
|
|
283
|
+
:authorizationAllowed true ;
|
|
284
|
+
:bannerFlagsHighSugar true ;
|
|
285
|
+
:alternativeIsLowerSugar true ;
|
|
286
|
+
:dutyTimingConsistent true ;
|
|
287
|
+
:marketingProhibited true ;
|
|
288
|
+
:filesWrittenExpected true .
|
|
289
|
+
} => {
|
|
290
|
+
:result :allChecksPass true .
|
|
291
|
+
} .
|
|
292
|
+
|
|
293
|
+
# -------------------------------------
|
|
294
|
+
# Hard checks (Eyeling inference fuses)
|
|
295
|
+
# -------------------------------------
|
|
296
|
+
|
|
297
|
+
{
|
|
298
|
+
:case :filesWritten ?n .
|
|
299
|
+
?n math:notEqualTo 6 .
|
|
300
|
+
} => false .
|
|
301
|
+
|
|
302
|
+
{
|
|
303
|
+
:case :scannerAuthAt ?authAt .
|
|
304
|
+
<https://example.org/insight/delfour> :expiresAt ?expiresAt .
|
|
305
|
+
?authAt math:greaterThan ?expiresAt .
|
|
306
|
+
} => false .
|
|
307
|
+
|
|
308
|
+
{
|
|
309
|
+
:scan :scannedProduct ?scanned .
|
|
310
|
+
?scanned :sugarTenths ?scannedSugar .
|
|
311
|
+
:case :suggestedAlternative ?alt .
|
|
312
|
+
?alt :sugarTenths ?altSugar .
|
|
313
|
+
?altSugar math:notLessThan ?scannedSugar .
|
|
314
|
+
} => false .
|
|
315
|
+
|
|
316
|
+
{
|
|
317
|
+
:policy odrl:prohibition [
|
|
318
|
+
odrl:action ?action
|
|
319
|
+
] .
|
|
320
|
+
?action log:notEqualTo odrl:distribute .
|
|
321
|
+
} => false .
|
|
322
|
+
|
|
323
|
+
{
|
|
324
|
+
:envelope :canonicalJson ?json .
|
|
325
|
+
?json crypto:sha256 ?actual .
|
|
326
|
+
:signature :payloadHashSHA256 ?expected .
|
|
327
|
+
?actual log:notEqualTo ?expected .
|
|
328
|
+
} => false .
|
|
329
|
+
|
|
330
|
+
# --------------------------------------
|
|
331
|
+
# ARC rendering through log:outputString
|
|
332
|
+
# --------------------------------------
|
|
333
|
+
|
|
334
|
+
:out01 log:outputString "=== Answer ===\n" .
|
|
335
|
+
:out02 log:outputString "The scanner is allowed to use a neutral shopping insight and recommends Low-Sugar Tea Biscuits instead of Classic Tea Biscuits.\n" .
|
|
336
|
+
:out03 log:outputString "case : delfour\n" .
|
|
337
|
+
:out04 log:outputString "decision : Allowed\n" .
|
|
338
|
+
:out05 log:outputString "scanned product : Classic Tea Biscuits\n" .
|
|
339
|
+
|
|
340
|
+
{
|
|
341
|
+
:case :suggestedAlternative ?alt .
|
|
342
|
+
?alt :productName ?altName .
|
|
343
|
+
("suggested alternative: %s\n" ?altName) string:format ?line .
|
|
344
|
+
} => {
|
|
345
|
+
:out06 log:outputString ?line .
|
|
346
|
+
} .
|
|
347
|
+
|
|
348
|
+
:out07 log:outputString "\n=== Reason Why ===\n" .
|
|
349
|
+
:out08 log:outputString "The phone desensitizes a diabetes-related household condition into a scoped low-sugar need, wraps it in an expiring Insight + Policy envelope, signs it, and the scanner consumes that envelope for shopping assistance.\n" .
|
|
350
|
+
:out09 log:outputString "metric : sugar_g_per_serving\n" .
|
|
351
|
+
|
|
352
|
+
{
|
|
353
|
+
<https://example.org/insight/delfour> :thresholdDisplay ?threshold .
|
|
354
|
+
("threshold : %s\n" ?threshold) string:format ?line .
|
|
355
|
+
} => {
|
|
356
|
+
:out10 log:outputString ?line .
|
|
357
|
+
} .
|
|
358
|
+
|
|
359
|
+
:out11 log:outputString "scope : self-scanner @ pick_up_scanner\n" .
|
|
360
|
+
:out12 log:outputString "retailer : Delfour\n" .
|
|
361
|
+
|
|
362
|
+
{
|
|
363
|
+
:signature :alg ?alg .
|
|
364
|
+
("signature alg : %s\n" ?alg) string:format ?line .
|
|
365
|
+
} => {
|
|
366
|
+
:out13 log:outputString ?line .
|
|
367
|
+
} .
|
|
368
|
+
|
|
369
|
+
{
|
|
370
|
+
:banner :headline ?headline .
|
|
371
|
+
("banner headline : %s\n" ?headline) string:format ?line .
|
|
372
|
+
} => {
|
|
373
|
+
:out14 log:outputString ?line .
|
|
374
|
+
} .
|
|
375
|
+
|
|
376
|
+
:out15 log:outputString "expires at : 2025-10-05T22:33:48.907185+00:00\n" .
|
|
377
|
+
|
|
378
|
+
{
|
|
379
|
+
:reasonText :value ?reason .
|
|
380
|
+
("reason.txt : %s" ?reason) string:format ?line .
|
|
381
|
+
} => {
|
|
382
|
+
:out16 log:outputString ?line .
|
|
383
|
+
} .
|
|
384
|
+
|
|
385
|
+
{
|
|
386
|
+
:case :auditEntries ?n .
|
|
387
|
+
("audit entries : %s\n" ?n) string:format ?line .
|
|
388
|
+
} => {
|
|
389
|
+
:out17 log:outputString ?line .
|
|
390
|
+
} .
|
|
391
|
+
|
|
392
|
+
{
|
|
393
|
+
:case :filesWritten ?n .
|
|
394
|
+
("bus files written : %s\n" ?n) string:format ?line .
|
|
395
|
+
} => {
|
|
396
|
+
:out18 log:outputString ?line .
|
|
397
|
+
} .
|
|
398
|
+
|
|
399
|
+
:out19 log:outputString "\n=== Check ===\n" .
|
|
400
|
+
|
|
401
|
+
{ :check :signatureVerifies true . } => { :out20 log:outputString "signature verifies : yes\n" . } .
|
|
402
|
+
{ :check :payloadHashMatches true . } => { :out21 log:outputString "payload hash matches : yes\n" . } .
|
|
403
|
+
{ :check :minimizationStripsSensitiveTerms true . } => { :out22 log:outputString "minimization strips sensitive terms: yes\n" . } .
|
|
404
|
+
{ :check :scopeComplete true . } => { :out23 log:outputString "scope complete : yes\n" . } .
|
|
405
|
+
{ :check :authorizationAllowed true . } => { :out24 log:outputString "authorization allowed : yes\n" . } .
|
|
406
|
+
{ :check :bannerFlagsHighSugar true . } => { :out25 log:outputString "high-sugar banner : yes\n" . } .
|
|
407
|
+
{ :check :alternativeIsLowerSugar true . } => { :out26 log:outputString "alternative lowers sugar : yes\n" . } .
|
|
408
|
+
{ :check :dutyTimingConsistent true . } => { :out27 log:outputString "duty timing consistent : yes\n" . } .
|
|
409
|
+
{ :check :marketingProhibited true . } => { :out28 log:outputString "marketing prohibited : yes\n" . } .
|