eyeling 1.21.2 → 1.21.4
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 +65 -0
- package/README.md +0 -1
- package/dist/browser/eyeling.browser.js +12 -1
- package/examples/arc-bridge/README.md +208 -0
- package/examples/arc-bridge/delfour/delfour.data.json +68 -0
- package/examples/arc-bridge/delfour/delfour.expected.json +88 -0
- package/examples/arc-bridge/delfour/delfour.instance.schema.json +201 -0
- package/examples/arc-bridge/delfour/delfour.model.mjs +273 -0
- package/examples/arc-bridge/delfour/delfour.spec.md +118 -0
- package/examples/arc-bridge/flandor/flandor.data.json +107 -0
- package/examples/arc-bridge/flandor/flandor.expected.json +98 -0
- package/examples/arc-bridge/flandor/flandor.instance.schema.json +285 -0
- package/examples/arc-bridge/flandor/flandor.model.mjs +303 -0
- package/examples/arc-bridge/flandor/flandor.spec.md +156 -0
- package/examples/extra/flandor.js +349 -0
- package/examples/extra/output/flandor.txt +31 -0
- package/examples/flandor.n3 +425 -0
- package/examples/output/flandor.n3 +31 -0
- package/eyeling.js +12 -1
- package/lib/builtins.js +12 -1
- package/package.json +1 -2
- package/test/api.test.js +49 -0
- package/SEMANTICS.md +0 -41
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Flandor — a macro-economic Insight Economy case for Flanders.
|
|
3
|
+
#
|
|
4
|
+
# The aha: nobody has to reveal their books for the region to coordinate.
|
|
5
|
+
#
|
|
6
|
+
# Exporters, training actors, and grid operators each keep their sensitive
|
|
7
|
+
# data local. What crosses the boundary is not the underlying evidence, but a
|
|
8
|
+
# narrow, signed, expiring insight: right now, Flanders has enough combined
|
|
9
|
+
# pressure to justify a temporary retooling response.
|
|
10
|
+
#
|
|
11
|
+
# That is the Insight Economy in action. Confidential micro-signals are
|
|
12
|
+
# transformed into a macro decision object that is:
|
|
13
|
+
# - useful enough to trigger action,
|
|
14
|
+
# - minimal enough to protect competitive and operational secrets,
|
|
15
|
+
# - governed enough to say who may use it, for what purpose, and until when.
|
|
16
|
+
#
|
|
17
|
+
# In this case, the insight says that export weakness, technical labour
|
|
18
|
+
# scarcity, and grid congestion together clear the threshold for a temporary
|
|
19
|
+
# industrial retooling package. Policymakers can act on that conclusion
|
|
20
|
+
# without gaining access to firm-level margins, vacancy lists, or grid-control
|
|
21
|
+
# details.
|
|
22
|
+
#
|
|
23
|
+
# The product being traded is therefore not raw data, and not even a general
|
|
24
|
+
# forecast, but a context-bound permissioned conclusion: a policy-grade
|
|
25
|
+
# insight for regional stabilization, with reuse for firm surveillance
|
|
26
|
+
# explicitly forbidden.
|
|
27
|
+
# ============================================================================
|
|
28
|
+
|
|
29
|
+
@prefix : <https://example.org/insight/flandor/> .
|
|
30
|
+
@prefix arc: <https://example.org/arc#> .
|
|
31
|
+
@prefix ins: <https://example.org/insight-economy#> .
|
|
32
|
+
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
|
|
33
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
34
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
35
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
36
|
+
@prefix crypto: <http://www.w3.org/2000/10/swap/crypto#> .
|
|
37
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
38
|
+
|
|
39
|
+
# -----
|
|
40
|
+
# Facts
|
|
41
|
+
# -----
|
|
42
|
+
|
|
43
|
+
:case a arc:Case ;
|
|
44
|
+
:caseName "flandor" ;
|
|
45
|
+
arc:question "Is the Flemish Economic Resilience Board allowed to use a neutral macro-economic insight for regional stabilization, and if so which package should it activate for Flanders?" ;
|
|
46
|
+
:expectedFilesWritten 6 ;
|
|
47
|
+
:requestPurpose "regional_stabilization" ;
|
|
48
|
+
:requestAction odrl:use ;
|
|
49
|
+
:hubCreatedAt "2026-04-08T07:00:00+00:00"^^xsd:dateTime ;
|
|
50
|
+
:hubExpiresAt "2026-04-08T19:00:00+00:00"^^xsd:dateTime ;
|
|
51
|
+
:boardAuthAt "2026-04-08T09:15:00+00:00"^^xsd:dateTime ;
|
|
52
|
+
:boardDutyAt "2026-04-08T18:30:00+00:00"^^xsd:dateTime ;
|
|
53
|
+
:filesWritten 6 ;
|
|
54
|
+
:auditEntries 1 .
|
|
55
|
+
|
|
56
|
+
:flanders a :Region ;
|
|
57
|
+
:regionName "Flanders" .
|
|
58
|
+
|
|
59
|
+
:signals a :SecureAggregate ;
|
|
60
|
+
:observedFirms 217 ;
|
|
61
|
+
:aggregationLevel "regional_cluster" ;
|
|
62
|
+
:containsFirmNames false ;
|
|
63
|
+
:containsPayrollRows false .
|
|
64
|
+
|
|
65
|
+
:cluster_antwerp a :IndustrialCluster ;
|
|
66
|
+
:clusterName "Antwerp chemicals" ;
|
|
67
|
+
:exportOrdersIndex 84 ;
|
|
68
|
+
:energyIntensity 92 .
|
|
69
|
+
|
|
70
|
+
:cluster_ghent a :IndustrialCluster ;
|
|
71
|
+
:clusterName "Ghent manufacturing" ;
|
|
72
|
+
:exportOrdersIndex 87 ;
|
|
73
|
+
:energyIntensity 76 .
|
|
74
|
+
|
|
75
|
+
:labourMarket a :LabourMarket ;
|
|
76
|
+
:techVacancyRateTenths 46 ;
|
|
77
|
+
:techVacancyRate 4.6 .
|
|
78
|
+
|
|
79
|
+
:grid a :GridSignal ;
|
|
80
|
+
:congestionHours 19 ;
|
|
81
|
+
:renewableCurtailmentMWh 240 .
|
|
82
|
+
|
|
83
|
+
:budget a :BudgetWindow ;
|
|
84
|
+
:maxMEUR 140 ;
|
|
85
|
+
:windowName "Q2 resilience window" .
|
|
86
|
+
|
|
87
|
+
:pkg_training_only a :PolicyPackage ;
|
|
88
|
+
:packageId "pkg:TRAIN_070" ;
|
|
89
|
+
:packageName "Flanders Skills Sprint" ;
|
|
90
|
+
:costMEUR 70 ;
|
|
91
|
+
:workerCoverage 900 ;
|
|
92
|
+
:gridReliefMW 0 ;
|
|
93
|
+
:coversSkillsStrain true .
|
|
94
|
+
|
|
95
|
+
:pkg_logistics_only a :PolicyPackage ;
|
|
96
|
+
:packageId "pkg:PORT_095" ;
|
|
97
|
+
:packageName "Schelde Trade Buffer" ;
|
|
98
|
+
:costMEUR 95 ;
|
|
99
|
+
:workerCoverage 300 ;
|
|
100
|
+
:gridReliefMW 10 ;
|
|
101
|
+
:coversExportWeakness true .
|
|
102
|
+
|
|
103
|
+
:pkg_flandor a :PolicyPackage ;
|
|
104
|
+
:packageId "pkg:RET_FLEX_120" ;
|
|
105
|
+
:packageName "Flandor Retooling Pulse" ;
|
|
106
|
+
:costMEUR 120 ;
|
|
107
|
+
:workerCoverage 1200 ;
|
|
108
|
+
:gridReliefMW 85 ;
|
|
109
|
+
:coversExportWeakness true ;
|
|
110
|
+
:coversSkillsStrain true ;
|
|
111
|
+
:coversGridStress true .
|
|
112
|
+
|
|
113
|
+
:pkg_full_corridor a :PolicyPackage ;
|
|
114
|
+
:packageId "pkg:CORRIDOR_165" ;
|
|
115
|
+
:packageName "Full Corridor Shock Shield" ;
|
|
116
|
+
:costMEUR 165 ;
|
|
117
|
+
:workerCoverage 1600 ;
|
|
118
|
+
:gridReliefMW 110 ;
|
|
119
|
+
:coversExportWeakness true ;
|
|
120
|
+
:coversSkillsStrain true ;
|
|
121
|
+
:coversGridStress true .
|
|
122
|
+
|
|
123
|
+
:macroInsight a ins:Insight ;
|
|
124
|
+
:id <https://example.org/insight/flandor> ;
|
|
125
|
+
:metric "regional_retooling_priority" ;
|
|
126
|
+
:thresholdScore 3 ;
|
|
127
|
+
:thresholdDisplay "3 active needs" ;
|
|
128
|
+
:suggestionPolicy "lowest_cost_package_covering_all_active_needs" ;
|
|
129
|
+
:scopeDevice "economic-resilience-board" ;
|
|
130
|
+
:scopeEvent "budget-prep-window" ;
|
|
131
|
+
:region "Flanders" ;
|
|
132
|
+
:createdAt "2026-04-08T07:00:00+00:00"^^xsd:dateTime ;
|
|
133
|
+
:expiresAt "2026-04-08T19:00:00+00:00"^^xsd:dateTime ;
|
|
134
|
+
:serializedLowercase """{"createdat":"2026-04-08t07:00:00+00:00","expiresat":"2026-04-08t19:00:00+00:00","id":"https://example.org/insight/flandor","metric":"regional_retooling_priority","region":"flanders","scopedevice":"economic-resilience-board","scopeevent":"budget-prep-window","suggestionpolicy":"lowest_cost_package_covering_all_active_needs","threshold":3,"type":"ins:insight"}""" .
|
|
135
|
+
|
|
136
|
+
:policy a odrl:Policy ;
|
|
137
|
+
:profile "Flandor-Insight-Policy" ;
|
|
138
|
+
odrl:permission [
|
|
139
|
+
odrl:action odrl:use ;
|
|
140
|
+
odrl:target <https://example.org/insight/flandor> ;
|
|
141
|
+
odrl:constraint [
|
|
142
|
+
odrl:leftOperand odrl:purpose ;
|
|
143
|
+
odrl:operator odrl:eq ;
|
|
144
|
+
odrl:rightOperand "regional_stabilization"
|
|
145
|
+
]
|
|
146
|
+
] ;
|
|
147
|
+
odrl:prohibition [
|
|
148
|
+
odrl:action odrl:distribute ;
|
|
149
|
+
odrl:target <https://example.org/insight/flandor> ;
|
|
150
|
+
odrl:constraint [
|
|
151
|
+
odrl:leftOperand odrl:purpose ;
|
|
152
|
+
odrl:operator odrl:eq ;
|
|
153
|
+
odrl:rightOperand "firm_surveillance"
|
|
154
|
+
]
|
|
155
|
+
] ;
|
|
156
|
+
odrl:duty [
|
|
157
|
+
odrl:action odrl:delete ;
|
|
158
|
+
odrl:constraint [
|
|
159
|
+
odrl:leftOperand odrl:dateTime ;
|
|
160
|
+
odrl:operator odrl:eq ;
|
|
161
|
+
odrl:rightOperand "2026-04-08T19:00:00+00:00"^^xsd:dateTime
|
|
162
|
+
]
|
|
163
|
+
] .
|
|
164
|
+
|
|
165
|
+
:envelope
|
|
166
|
+
:insight :macroInsight ;
|
|
167
|
+
:policy :policy ;
|
|
168
|
+
:canonicalJson """{"insight":{"createdAt":"2026-04-08T07:00:00+00:00","expiresAt":"2026-04-08T19:00:00+00:00","id":"https://example.org/insight/flandor","metric":"regional_retooling_priority","region":"Flanders","scopeDevice":"economic-resilience-board","scopeEvent":"budget-prep-window","suggestionPolicy":"lowest_cost_package_covering_all_active_needs","threshold":3,"type":"ins:Insight"},"policy":{"duty":{"action":"odrl:delete","constraint":{"leftOperand":"odrl:dateTime","operator":"odrl:eq","rightOperand":"2026-04-08T19:00:00+00:00"}},"permission":{"action":"odrl:use","constraint":{"leftOperand":"odrl:purpose","operator":"odrl:eq","rightOperand":"regional_stabilization"},"target":"https://example.org/insight/flandor"},"profile":"Flandor-Insight-Policy","prohibition":{"action":"odrl:distribute","constraint":{"leftOperand":"odrl:purpose","operator":"odrl:eq","rightOperand":"firm_surveillance"},"target":"https://example.org/insight/flandor"},"type":"odrl:Policy"}}""" .
|
|
169
|
+
|
|
170
|
+
:signature
|
|
171
|
+
:alg "HMAC-SHA256" ;
|
|
172
|
+
:keyid "demo-shared-secret" ;
|
|
173
|
+
:created "2026-04-08T07:00:00+00:00"^^xsd:dateTime ;
|
|
174
|
+
:payloadHashSHA256 "10a85e861075bef2a96c01c7f3238735f82b8f368deb62eafcedd1c4b7f7c707" ;
|
|
175
|
+
:displayPayloadHashSHA256 "718f5b17d07ab6a95503bc04a1000ddb132409f600659c03d21def81914b780b" ;
|
|
176
|
+
:signatureHMAC "955968ca99a191783bc00cba068128ccb9ff40a5e6114fda13a52c74ee27329e" ;
|
|
177
|
+
:hmacVerificationMode :trustedPrecomputedInput .
|
|
178
|
+
|
|
179
|
+
:reasonText :value "Secure aggregates from Flanders indicate simultaneous export weakness, technical labour scarcity, and grid congestion. A neutral macro insight is scoped to the economic-resilience board for one budget window only, enabling a temporary package without exposing firm-level books.\n" .
|
|
180
|
+
|
|
181
|
+
# -----
|
|
182
|
+
# Logic
|
|
183
|
+
# -----
|
|
184
|
+
|
|
185
|
+
# derive macro pressures from private but aggregated signals
|
|
186
|
+
{ ?cluster a :IndustrialCluster ; :exportOrdersIndex ?index .
|
|
187
|
+
?index math:lessThan 90 .
|
|
188
|
+
} => { :case :exportWeakness true . } .
|
|
189
|
+
|
|
190
|
+
{ :labourMarket :techVacancyRateTenths ?rate .
|
|
191
|
+
?rate math:greaterThan 39 .
|
|
192
|
+
} => { :case :skillsStrain true . } .
|
|
193
|
+
|
|
194
|
+
{ :grid :congestionHours ?hours .
|
|
195
|
+
?hours math:greaterThan 11 .
|
|
196
|
+
} => { :case :gridStress true . } .
|
|
197
|
+
|
|
198
|
+
{ :case :exportWeakness true ; :skillsStrain true ; :gridStress true . }
|
|
199
|
+
=> {
|
|
200
|
+
:case :needsRetoolingPulse true ;
|
|
201
|
+
:derivedFromNeed "regional_retooling_and_flexibility" .
|
|
202
|
+
} .
|
|
203
|
+
|
|
204
|
+
# payload hash and signature checks
|
|
205
|
+
{ :envelope :canonicalJson ?json .
|
|
206
|
+
?json crypto:sha256 ?digest .
|
|
207
|
+
:signature :payloadHashSHA256 ?digest .
|
|
208
|
+
} => { :check :payloadHashMatches true . } .
|
|
209
|
+
|
|
210
|
+
{ :signature :hmacVerificationMode :trustedPrecomputedInput . }
|
|
211
|
+
=> { :check :signatureVerifies true . } .
|
|
212
|
+
|
|
213
|
+
# minimization and scope checks
|
|
214
|
+
{ :macroInsight :serializedLowercase ?s .
|
|
215
|
+
?s string:notMatches "salary|payroll|invoice|medical|firmname" .
|
|
216
|
+
} => { :check :minimizationStripsSensitiveTerms true . } .
|
|
217
|
+
|
|
218
|
+
{ :macroInsight :scopeDevice ?device ;
|
|
219
|
+
:scopeEvent ?event ;
|
|
220
|
+
:expiresAt ?expiry .
|
|
221
|
+
} => { :check :scopeComplete true . } .
|
|
222
|
+
|
|
223
|
+
# authorization
|
|
224
|
+
{ :policy odrl:permission [
|
|
225
|
+
odrl:action odrl:use ;
|
|
226
|
+
odrl:target <https://example.org/insight/flandor> ;
|
|
227
|
+
odrl:constraint [
|
|
228
|
+
odrl:leftOperand odrl:purpose ;
|
|
229
|
+
odrl:operator odrl:eq ;
|
|
230
|
+
odrl:rightOperand "regional_stabilization"
|
|
231
|
+
]
|
|
232
|
+
] .
|
|
233
|
+
:case :boardAuthAt ?authAt .
|
|
234
|
+
:macroInsight :expiresAt ?expiresAt .
|
|
235
|
+
?authAt math:notGreaterThan ?expiresAt .
|
|
236
|
+
} => {
|
|
237
|
+
:decision :at "2026-04-08T09:15:00+00:00"^^xsd:dateTime ;
|
|
238
|
+
:outcome "Allowed" ;
|
|
239
|
+
:target <https://example.org/insight/flandor> .
|
|
240
|
+
:check :authorizationAllowed true .
|
|
241
|
+
} .
|
|
242
|
+
|
|
243
|
+
# choose the lowest-cost package that covers all active needs and fits the budget
|
|
244
|
+
{ :case :needsRetoolingPulse true .
|
|
245
|
+
:budget :maxMEUR ?max .
|
|
246
|
+
?candidate a :PolicyPackage ;
|
|
247
|
+
:costMEUR ?candidateCost ;
|
|
248
|
+
:coversExportWeakness true ;
|
|
249
|
+
:coversSkillsStrain true ;
|
|
250
|
+
:coversGridStress true .
|
|
251
|
+
?max math:notLessThan ?candidateCost .
|
|
252
|
+
1 log:notIncludes {
|
|
253
|
+
?other a :PolicyPackage ;
|
|
254
|
+
:costMEUR ?otherCost ;
|
|
255
|
+
:coversExportWeakness true ;
|
|
256
|
+
:coversSkillsStrain true ;
|
|
257
|
+
:coversGridStress true .
|
|
258
|
+
?max math:notLessThan ?otherCost .
|
|
259
|
+
?otherCost math:lessThan ?candidateCost .
|
|
260
|
+
} .
|
|
261
|
+
} => { :case :recommendedPackage ?candidate . } .
|
|
262
|
+
|
|
263
|
+
{ :case :recommendedPackage ?pkg .
|
|
264
|
+
?pkg :packageName ?name .
|
|
265
|
+
} => { :answer :recommendedPackageName ?name . } .
|
|
266
|
+
|
|
267
|
+
{ :case :recommendedPackage ?pkg .
|
|
268
|
+
?pkg :workerCoverage ?workers ;
|
|
269
|
+
:gridReliefMW ?mw .
|
|
270
|
+
} => {
|
|
271
|
+
:why :workerCoverage ?workers ;
|
|
272
|
+
:gridReliefMW ?mw .
|
|
273
|
+
} .
|
|
274
|
+
|
|
275
|
+
{ :case :recommendedPackage ?pkg .
|
|
276
|
+
?pkg :costMEUR ?cost .
|
|
277
|
+
:budget :maxMEUR ?max .
|
|
278
|
+
?max math:notLessThan ?cost .
|
|
279
|
+
} => { :check :packageWithinBudget true . } .
|
|
280
|
+
|
|
281
|
+
{ :case :recommendedPackage ?pkg .
|
|
282
|
+
?pkg :coversExportWeakness true ;
|
|
283
|
+
:coversSkillsStrain true ;
|
|
284
|
+
:coversGridStress true .
|
|
285
|
+
} => { :check :packageCoversAllNeeds true . } .
|
|
286
|
+
|
|
287
|
+
{ :case :boardDutyAt ?dutyAt .
|
|
288
|
+
:macroInsight :expiresAt ?expiresAt .
|
|
289
|
+
?dutyAt math:notGreaterThan ?expiresAt .
|
|
290
|
+
} => { :check :dutyTimingConsistent true . } .
|
|
291
|
+
|
|
292
|
+
{ :policy odrl:prohibition [
|
|
293
|
+
odrl:action odrl:distribute ;
|
|
294
|
+
odrl:constraint [ odrl:rightOperand "firm_surveillance" ]
|
|
295
|
+
] .
|
|
296
|
+
} => { :check :surveillanceReuseProhibited true . } .
|
|
297
|
+
|
|
298
|
+
{ :case :filesWritten 6 . }
|
|
299
|
+
=> { :check :filesWrittenExpected true . } .
|
|
300
|
+
|
|
301
|
+
{ :case :exportWeakness true ;
|
|
302
|
+
:skillsStrain true ;
|
|
303
|
+
:gridStress true .
|
|
304
|
+
} => {
|
|
305
|
+
:answer :activeNeedCount 3 ;
|
|
306
|
+
:activeNeedThreshold 3 .
|
|
307
|
+
:check :thresholdReached true .
|
|
308
|
+
} .
|
|
309
|
+
|
|
310
|
+
{ :case :recommendedPackage ?pkg .
|
|
311
|
+
} => { :check :lowestCostEligiblePackageChosen true . } .
|
|
312
|
+
|
|
313
|
+
{ :signature :signatureHMAC "955968ca99a191783bc00cba068128ccb9ff40a5e6114fda13a52c74ee27329e" .
|
|
314
|
+
:signature :hmacVerificationMode :trustedPrecomputedInput .
|
|
315
|
+
} => { :check :hmacMatches true . } .
|
|
316
|
+
|
|
317
|
+
{ :case :recommendedPackage ?pkg .
|
|
318
|
+
?pkg :costMEUR ?cost .
|
|
319
|
+
} => { :answer :packageCostMEUR ?cost . } .
|
|
320
|
+
|
|
321
|
+
{ :check :signatureVerifies true ;
|
|
322
|
+
:payloadHashMatches true ;
|
|
323
|
+
:minimizationStripsSensitiveTerms true ;
|
|
324
|
+
:scopeComplete true ;
|
|
325
|
+
:authorizationAllowed true ;
|
|
326
|
+
:packageWithinBudget true ;
|
|
327
|
+
:packageCoversAllNeeds true ;
|
|
328
|
+
:dutyTimingConsistent true ;
|
|
329
|
+
:surveillanceReuseProhibited true ;
|
|
330
|
+
:filesWrittenExpected true .
|
|
331
|
+
} => { :result :allChecksPass true . } .
|
|
332
|
+
|
|
333
|
+
# -------------------------------------
|
|
334
|
+
# Hard checks (Eyeling inference fuses)
|
|
335
|
+
# -------------------------------------
|
|
336
|
+
|
|
337
|
+
{ :case :filesWritten ?n .
|
|
338
|
+
?n math:notEqualTo 6 .
|
|
339
|
+
} => false .
|
|
340
|
+
|
|
341
|
+
{ :case :boardAuthAt ?authAt .
|
|
342
|
+
:macroInsight :expiresAt ?expiresAt .
|
|
343
|
+
?authAt math:greaterThan ?expiresAt .
|
|
344
|
+
} => false .
|
|
345
|
+
|
|
346
|
+
{ :case :recommendedPackage ?pkg .
|
|
347
|
+
?pkg :costMEUR ?cost .
|
|
348
|
+
:budget :maxMEUR ?max .
|
|
349
|
+
?cost math:greaterThan ?max .
|
|
350
|
+
} => false .
|
|
351
|
+
|
|
352
|
+
{ :case :recommendedPackage ?pkg .
|
|
353
|
+
?pkg :costMEUR ?cost .
|
|
354
|
+
?other a :PolicyPackage ;
|
|
355
|
+
:costMEUR ?otherCost ;
|
|
356
|
+
:coversExportWeakness true ;
|
|
357
|
+
:coversSkillsStrain true ;
|
|
358
|
+
:coversGridStress true .
|
|
359
|
+
:budget :maxMEUR ?max .
|
|
360
|
+
?max math:notLessThan ?otherCost .
|
|
361
|
+
?otherCost math:lessThan ?cost .
|
|
362
|
+
} => false .
|
|
363
|
+
|
|
364
|
+
{ :envelope :canonicalJson ?json .
|
|
365
|
+
?json crypto:sha256 ?actual .
|
|
366
|
+
:signature :payloadHashSHA256 ?expected .
|
|
367
|
+
?actual log:notEqualTo ?expected .
|
|
368
|
+
} => false .
|
|
369
|
+
|
|
370
|
+
# --------------------------------------
|
|
371
|
+
# ARC rendering through log:outputString
|
|
372
|
+
# --------------------------------------
|
|
373
|
+
|
|
374
|
+
:out01 log:outputString "=== Answer ===\n" .
|
|
375
|
+
:out02 log:outputString "Name: Flandor\n" .
|
|
376
|
+
:out03 log:outputString "Region: Flanders\n" .
|
|
377
|
+
:out04 log:outputString "Metric: regional_retooling_priority\n" .
|
|
378
|
+
{ :answer :activeNeedCount ?count ; :activeNeedThreshold ?threshold .
|
|
379
|
+
("Active need count: %s/%s\n" ?count ?threshold) string:format ?line .
|
|
380
|
+
} => { :out05 log:outputString ?line . } .
|
|
381
|
+
{ :answer :recommendedPackageName ?name .
|
|
382
|
+
("Recommended package: %s\n" ?name) string:format ?line .
|
|
383
|
+
} => { :out06 log:outputString ?line . } .
|
|
384
|
+
:out07 log:outputString "Budget cap: €140M\n" .
|
|
385
|
+
{ :answer :packageCostMEUR ?cost .
|
|
386
|
+
("Package cost: €%sM\n" ?cost) string:format ?line .
|
|
387
|
+
} => { :out08 log:outputString ?line . } .
|
|
388
|
+
{ :signature :displayPayloadHashSHA256 ?digest .
|
|
389
|
+
("Payload SHA-256: %s\n" ?digest) string:format ?line .
|
|
390
|
+
} => { :out09 log:outputString ?line . } .
|
|
391
|
+
{ :signature :signatureHMAC ?sig .
|
|
392
|
+
("Envelope HMAC-SHA-256: %s\n" ?sig) string:format ?line .
|
|
393
|
+
} => { :out10 log:outputString ?line . } .
|
|
394
|
+
|
|
395
|
+
:out11 log:outputString "\n=== Reason Why ===\n" .
|
|
396
|
+
:out12 log:outputString "Export weakness is active because at least one cluster has exportOrdersIndex < 90 (Antwerp chemicals=84, Ghent manufacturing=87).\n" .
|
|
397
|
+
:out13 log:outputString "Skills strain is active because technical vacancy rate is 4.6% (threshold > 3.9%).\n" .
|
|
398
|
+
:out14 log:outputString "Grid stress is active because congestion hours = 19 (threshold > 11).\n" .
|
|
399
|
+
:out15 log:outputString "The recommendation policy is \"lowest_cost_package_covering_all_active_needs\", so the cheapest package that covers all active needs within budget is selected.\n" .
|
|
400
|
+
:out16 log:outputString "Selected package \"Flandor Retooling Pulse\" covers export=true, skills=true, grid=true, cost=€120M.\n" .
|
|
401
|
+
:out17 log:outputString "Usage is permitted only for purpose \"regional_stabilization\" and the envelope expires at 2026-04-08T19:00:00+00:00.\n" .
|
|
402
|
+
|
|
403
|
+
:out18 log:outputString "\n=== Check ===\n" .
|
|
404
|
+
{ :check :payloadHashMatches true . }
|
|
405
|
+
=> { :out19 log:outputString "- PASS: payload hash matches\n" . } .
|
|
406
|
+
{ :check :hmacMatches true . }
|
|
407
|
+
=> { :out20 log:outputString "- PASS: hmac matches\n" . } .
|
|
408
|
+
{ :check :thresholdReached true . }
|
|
409
|
+
=> { :out21 log:outputString "- PASS: threshold reached\n" . } .
|
|
410
|
+
{ :check :scopeComplete true . }
|
|
411
|
+
=> { :out22 log:outputString "- PASS: scope complete\n" . } .
|
|
412
|
+
{ :check :minimizationStripsSensitiveTerms true . }
|
|
413
|
+
=> { :out23 log:outputString "- PASS: minimization respected\n" . } .
|
|
414
|
+
{ :check :authorizationAllowed true . }
|
|
415
|
+
=> { :out24 log:outputString "- PASS: authorized purpose allowed\n" . } .
|
|
416
|
+
{ :check :dutyTimingConsistent true . }
|
|
417
|
+
=> { :out25 log:outputString "- PASS: deletion duty still on time\n" . } .
|
|
418
|
+
{ :check :surveillanceReuseProhibited true . }
|
|
419
|
+
=> { :out26 log:outputString "- PASS: surveillance reuse prohibited\n" . } .
|
|
420
|
+
{ :check :packageWithinBudget true . }
|
|
421
|
+
=> { :out27 log:outputString "- PASS: package exists within budget\n" . } .
|
|
422
|
+
{ :check :packageCoversAllNeeds true . }
|
|
423
|
+
=> { :out28 log:outputString "- PASS: package covers all active needs\n" . } .
|
|
424
|
+
{ :check :lowestCostEligiblePackageChosen true . }
|
|
425
|
+
=> { :out29 log:outputString "- PASS: lowest-cost eligible package chosen\n" . } .
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
=== Answer ===
|
|
2
|
+
Name: Flandor
|
|
3
|
+
Region: Flanders
|
|
4
|
+
Metric: regional_retooling_priority
|
|
5
|
+
Active need count: 3/3
|
|
6
|
+
Recommended package: Flandor Retooling Pulse
|
|
7
|
+
Budget cap: €140M
|
|
8
|
+
Package cost: €120M
|
|
9
|
+
Payload SHA-256: 718f5b17d07ab6a95503bc04a1000ddb132409f600659c03d21def81914b780b
|
|
10
|
+
Envelope HMAC-SHA-256: 955968ca99a191783bc00cba068128ccb9ff40a5e6114fda13a52c74ee27329e
|
|
11
|
+
|
|
12
|
+
=== Reason Why ===
|
|
13
|
+
Export weakness is active because at least one cluster has exportOrdersIndex < 90 (Antwerp chemicals=84, Ghent manufacturing=87).
|
|
14
|
+
Skills strain is active because technical vacancy rate is 4.6% (threshold > 3.9%).
|
|
15
|
+
Grid stress is active because congestion hours = 19 (threshold > 11).
|
|
16
|
+
The recommendation policy is "lowest_cost_package_covering_all_active_needs", so the cheapest package that covers all active needs within budget is selected.
|
|
17
|
+
Selected package "Flandor Retooling Pulse" covers export=true, skills=true, grid=true, cost=€120M.
|
|
18
|
+
Usage is permitted only for purpose "regional_stabilization" and the envelope expires at 2026-04-08T19:00:00+00:00.
|
|
19
|
+
|
|
20
|
+
=== Check ===
|
|
21
|
+
- PASS: payload hash matches
|
|
22
|
+
- PASS: hmac matches
|
|
23
|
+
- PASS: threshold reached
|
|
24
|
+
- PASS: scope complete
|
|
25
|
+
- PASS: minimization respected
|
|
26
|
+
- PASS: authorized purpose allowed
|
|
27
|
+
- PASS: deletion duty still on time
|
|
28
|
+
- PASS: surveillance reuse prohibited
|
|
29
|
+
- PASS: package exists within budget
|
|
30
|
+
- PASS: package covers all active needs
|
|
31
|
+
- PASS: lowest-cost eligible package chosen
|
package/eyeling.js
CHANGED
|
@@ -1012,6 +1012,13 @@ function simpleStringFormat(fmt, args) {
|
|
|
1012
1012
|
return out;
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
1015
|
+
function termToFormatArgString(t) {
|
|
1016
|
+
const s = termToJsString(t);
|
|
1017
|
+
if (s !== null) return s;
|
|
1018
|
+
if (t instanceof Var) return null;
|
|
1019
|
+
return termToN3(t, PrefixEnv.newDefault());
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1015
1022
|
// -----------------------------------------------------------------------------
|
|
1016
1023
|
// SWAP/N3 regex compatibility helper
|
|
1017
1024
|
// -----------------------------------------------------------------------------
|
|
@@ -4182,6 +4189,10 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
4182
4189
|
|
|
4183
4190
|
// string:format
|
|
4184
4191
|
// (limited: only %s and %% are supported, anything else ⇒ builtin fails)
|
|
4192
|
+
// The format string itself must be string-castable, but placeholder arguments
|
|
4193
|
+
// are allowed to be any bound non-variable term. Plain strings/IRIs keep their
|
|
4194
|
+
// direct string value; other terms fall back to N3 rendering so formatting a
|
|
4195
|
+
// bound blank node, list, or quoted formula does not make the whole builtin fail.
|
|
4185
4196
|
if (pv === STRING_NS + 'format') {
|
|
4186
4197
|
if (!(g.s instanceof ListTerm) || g.s.elems.length < 1) return [];
|
|
4187
4198
|
const fmtStr = termToJsString(g.s.elems[0]);
|
|
@@ -4189,7 +4200,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
4189
4200
|
|
|
4190
4201
|
const args = [];
|
|
4191
4202
|
for (let i = 1; i < g.s.elems.length; i++) {
|
|
4192
|
-
const aStr =
|
|
4203
|
+
const aStr = termToFormatArgString(g.s.elems[i]);
|
|
4193
4204
|
if (aStr === null) return [];
|
|
4194
4205
|
args.push(aStr);
|
|
4195
4206
|
}
|
package/lib/builtins.js
CHANGED
|
@@ -533,6 +533,13 @@ function simpleStringFormat(fmt, args) {
|
|
|
533
533
|
return out;
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
+
function termToFormatArgString(t) {
|
|
537
|
+
const s = termToJsString(t);
|
|
538
|
+
if (s !== null) return s;
|
|
539
|
+
if (t instanceof Var) return null;
|
|
540
|
+
return termToN3(t, PrefixEnv.newDefault());
|
|
541
|
+
}
|
|
542
|
+
|
|
536
543
|
// -----------------------------------------------------------------------------
|
|
537
544
|
// SWAP/N3 regex compatibility helper
|
|
538
545
|
// -----------------------------------------------------------------------------
|
|
@@ -3703,6 +3710,10 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3703
3710
|
|
|
3704
3711
|
// string:format
|
|
3705
3712
|
// (limited: only %s and %% are supported, anything else ⇒ builtin fails)
|
|
3713
|
+
// The format string itself must be string-castable, but placeholder arguments
|
|
3714
|
+
// are allowed to be any bound non-variable term. Plain strings/IRIs keep their
|
|
3715
|
+
// direct string value; other terms fall back to N3 rendering so formatting a
|
|
3716
|
+
// bound blank node, list, or quoted formula does not make the whole builtin fail.
|
|
3706
3717
|
if (pv === STRING_NS + 'format') {
|
|
3707
3718
|
if (!(g.s instanceof ListTerm) || g.s.elems.length < 1) return [];
|
|
3708
3719
|
const fmtStr = termToJsString(g.s.elems[0]);
|
|
@@ -3710,7 +3721,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3710
3721
|
|
|
3711
3722
|
const args = [];
|
|
3712
3723
|
for (let i = 1; i < g.s.elems.length; i++) {
|
|
3713
|
-
const aStr =
|
|
3724
|
+
const aStr = termToFormatArgString(g.s.elems[i]);
|
|
3714
3725
|
if (aStr === null) return [];
|
|
3715
3726
|
args.push(aStr);
|
|
3716
3727
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyeling",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.4",
|
|
4
4
|
"description": "A minimal Notation3 (N3) reasoner in JavaScript.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"LICENSE.md",
|
|
23
23
|
"README.md",
|
|
24
24
|
"HANDBOOK.md",
|
|
25
|
-
"SEMANTICS.md",
|
|
26
25
|
"eyeling-builtins.ttl",
|
|
27
26
|
"eyeling.js",
|
|
28
27
|
"index.js",
|
package/test/api.test.js
CHANGED
|
@@ -1218,6 +1218,55 @@ _:l2 rdf:rest rdf:nil.
|
|
|
1218
1218
|
},
|
|
1219
1219
|
},
|
|
1220
1220
|
|
|
1221
|
+
{
|
|
1222
|
+
name: '51b string:format: bound blank nodes in %s placeholders render instead of failing the whole rule',
|
|
1223
|
+
opt: ['-n'],
|
|
1224
|
+
input: `@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
|
|
1225
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
|
|
1226
|
+
@prefix : <http://example.org/>.
|
|
1227
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
1228
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
1229
|
+
|
|
1230
|
+
:policy1
|
|
1231
|
+
a odrl:Agreement ;
|
|
1232
|
+
odrl:permission [
|
|
1233
|
+
odrl:target <http://example.com/asset:9898.movie> ;
|
|
1234
|
+
odrl:action odrl:play ;
|
|
1235
|
+
odrl:duty [
|
|
1236
|
+
odrl:action [
|
|
1237
|
+
rdf:value odrl:compensate ;
|
|
1238
|
+
odrl:refinement [
|
|
1239
|
+
odrl:leftOperand :payAmount ;
|
|
1240
|
+
odrl:operator odrl:eq ;
|
|
1241
|
+
odrl:rightOperand 5
|
|
1242
|
+
]
|
|
1243
|
+
]
|
|
1244
|
+
]
|
|
1245
|
+
].
|
|
1246
|
+
|
|
1247
|
+
{
|
|
1248
|
+
?P a odrl:Agreement .
|
|
1249
|
+
?P odrl:permission [
|
|
1250
|
+
odrl:target ?T ;
|
|
1251
|
+
odrl:action ?Ignore ;
|
|
1252
|
+
odrl:duty [ odrl:action ?A ]
|
|
1253
|
+
].
|
|
1254
|
+
( "%% Duty_(a,t)(action:%s)\n%% => ~Possible_(a,t)(~action:%s)\n" ?A ?A ) string:format ?Str.
|
|
1255
|
+
}
|
|
1256
|
+
=>
|
|
1257
|
+
{
|
|
1258
|
+
[] log:outputString ?Str.
|
|
1259
|
+
}.
|
|
1260
|
+
`,
|
|
1261
|
+
check(out) {
|
|
1262
|
+
const m = String(out).match(
|
|
1263
|
+
/^% Duty_\(a,t\)\(action:(_:[^)]+)\)\n% => ~Possible_\(a,t\)\(~action:(_:[^)]+)\)\n?$/,
|
|
1264
|
+
);
|
|
1265
|
+
assert.ok(m, `Expected formatted blank-node output, got: ${String(out)}`);
|
|
1266
|
+
assert.equal(m[1], m[2], 'Expected both %s placeholders to render the same blank node id');
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
|
|
1221
1270
|
{
|
|
1222
1271
|
name: '52 --ast: prints parse result as JSON array [prefixes, triples, frules, brules]',
|
|
1223
1272
|
opt: ['--ast'],
|
package/SEMANTICS.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
How does the [Eyeling HANDBOOK](https://eyereasoner.github.io/eyeling/HANDBOOK) line up with the W3C CG [Notation3 Semantics](https://w3c.github.io/N3/spec/semantics) document — and where does it intentionally diverge?
|
|
2
|
-
|
|
3
|
-
## Where Eyeling is strongly aligned
|
|
4
|
-
|
|
5
|
-
- **Core term model (IRIs, literals, variables, blank nodes, lists, quoted formulas):** The semantics spec treats N3 terms as IRIs/literals/variables plus **lists** and **graph terms**. Eyeling’s handbook describes the same internal term universe: `Iri`, `Literal`, `Var`, `Blank`, `ListTerm`, `GraphTerm`.
|
|
6
|
-
|
|
7
|
-
- **Quoted formulas need alpha-equivalence / isomorphism:** The semantics spec defines isomorphism for graphs and graph terms using renaming mappings (including special handling for nested scopes). Eyeling implements this operationally as **alpha-equivalence for `GraphTerm`**, explicitly describing “consistent renaming” as the match criterion.
|
|
8
|
-
|
|
9
|
-
- **Rules as implication (and `true` as empty formula):** The semantics spec defines log-semantics for `log:implies` and explicitly treats boolean `true`/`false` as special literals, with `true` corresponding to the empty formula. Eyeling’s parser/normalizer explicitly supports `{P} => {C}` and `{P} log:implies {C}`, and treats `true` as `{}`.
|
|
10
|
-
|
|
11
|
-
- **Lists as first-class citizens (not just RDF collections):** The semantics spec treats lists as proper N3 terms. Eyeling uses concrete `ListTerm`s and even materializes RDF `rdf:first`/`rdf:rest` chains into list terms to operate uniformly.
|
|
12
|
-
|
|
13
|
-
## Where Eyeling diverges or goes beyond the semantics doc
|
|
14
|
-
|
|
15
|
-
### 1) Blank nodes in **rule bodies**: Eyeling chooses “N3 practice” over “bnodes are existential”
|
|
16
|
-
|
|
17
|
-
The semantics doc states (for concrete syntax intuition) that **blank nodes correspond to existentially quantified variables** with **local scope**. Eyeling _intentionally_ rewrites blanks in **premises** into variables (“universally-quantified placeholders”), to avoid “existential in the body” behavior.
|
|
18
|
-
|
|
19
|
-
This is a _real semantic choice_: it matches how many people _write_ N3 rules, but it is not the same as a straightforward “bnodes are existentials everywhere” reading.
|
|
20
|
-
|
|
21
|
-
### 2) “Groundness” of quoted formulas containing variables
|
|
22
|
-
|
|
23
|
-
In the semantics spec, whether a graph term is ground depends on whether the underlying graph is closed (no free variables), and it discusses how variables can appear free when you isolate a nested graph term. Eyeling explicitly makes a pragmatic choice: **variables inside a `GraphTerm` do not make the surrounding triple non-ground** (“variables inside formulas don’t leak”).
|
|
24
|
-
|
|
25
|
-
That’s convenient for operational indexing/matching, but it doesn’t mirror the model-theoretic notion of ground graph terms one-to-one.
|
|
26
|
-
|
|
27
|
-
### 3) Eyeling implements lots of behavior the semantics doc does not yet define
|
|
28
|
-
|
|
29
|
-
The semantics report currently only gives special meaning to `log:implies` (and says LP is planned to be extended). Eyeling defines a large operational “standard library” of builtins and advanced control features (e.g., scoped querying / snapshotting). For example, it gives `log:includes`/`log:notIncludes` a two-phase snapshot semantics for determinism.
|
|
30
|
-
|
|
31
|
-
So: Eyeling is **ahead of / outside** what `semantics.html` formally specifies today.
|
|
32
|
-
|
|
33
|
-
### 4) Constraint handling via “inference fuses” (`=> false`) is operational
|
|
34
|
-
|
|
35
|
-
The semantics doc includes a notion of `false` in connection with `log:implies` constraints. Eyeling turns `{...} => false` into an _engine-level hard failure_ (exit status, message), i.e., a procedural constraint mechanism rather than just a semantic condition.
|
|
36
|
-
|
|
37
|
-
That’s useful in tooling, but it’s not something the model-theoretic semantics itself “does” (it defines truth/entailment, not process control).
|
|
38
|
-
|
|
39
|
-
### 5) Possible coverage gaps vs full N3 surface language (not strictly “semantics.html”, but relevant)
|
|
40
|
-
|
|
41
|
-
Eyeling’s handbook lists supported directives/tokens (`@prefix`, `@base`, etc.) but does not mention explicit quantifier directives like `@forAll` / `@forSome`. The semantics document leans on explicit quantification in its **abstract syntax** discussion. So Eyeling appears to support _implicit_ quantification via `?x` and blanks (plus its own rule-normalization choices), but may not implement the full explicit-quantifier surface syntax.
|