eyeling 1.5.30 → 1.5.32

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/README.md CHANGED
@@ -239,7 +239,7 @@ As soon as the premise is provable, `eyeling` exits with status code `2`.
239
239
  - **list**: `list:append` `list:first` `list:firstRest` `list:in` `list:iterate` `list:last` `list:length` `list:map` `list:member` `list:memberAt` `list:notMember` `list:remove` `list:rest` `list:reverse` `list:sort`
240
240
  - **log**: `log:collectAllIn` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:notEqualTo` `log:notIncludes` `log:skolem` `log:uri`
241
241
  - **math**: `math:absoluteValue` `math:acos` `math:asin` `math:atan` `math:cos` `math:cosh` `math:degrees` `math:difference` `math:equalTo` `math:exponentiation` `math:greaterThan` `math:integerQuotient` `math:lessThan` `math:negation` `math:notEqualTo` `math:notGreaterThan` `math:notLessThan` `math:product` `math:quotient` `math:remainder` `math:rounded` `math:sin` `math:sinh` `math:sum` `math:tan` `math:tanh`
242
- - **string**: `string:concatenation` `string:contains` `string:containsIgnoringCase` `string:endsWith` `string:equalIgnoringCase` `string:format` `string:greaterThan` `string:lessThan` `string:matches` `string:notEqualIgnoringCase` `string:notGreaterThan` `string:notLessThan` `string:notMatches` `string:replace` `string:scrape` `string:startsWith`
242
+ - **string**: `string:concatenation` `string:contains` `string:containsIgnoringCase` `string:endsWith` `string:equalIgnoringCase` `string:format` `string:greaterThan` `string:jsonPointer` `string:lessThan` `string:matches` `string:notEqualIgnoringCase` `string:notGreaterThan` `string:notLessThan` `string:notMatches` `string:replace` `string:scrape` `string:startsWith`
243
243
  - **time**: `time:localTime`
244
244
 
245
245
  ## License
@@ -0,0 +1,69 @@
1
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
2
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
3
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
4
+ @prefix ex: <http://example.org/> .
5
+
6
+ ex:doc ex:json """{
7
+ "users": [
8
+ { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
9
+ { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
10
+ ],
11
+ "policy": { "allowedDomains": ["example.org", "example.com"] }
12
+ }""" .
13
+
14
+ # Sanity check: key contains "/" so JSON Pointer must use "~1"
15
+ {
16
+ ex:doc ex:json ?J .
17
+ (?J "/users/0/profile~1name") string:jsonPointer "Ada Lovelace" .
18
+ } => {
19
+ ex:checks ex:firstUserNameOk true .
20
+ } .
21
+
22
+ # Allowed users: email domain is in policy.allowedDomains
23
+ {
24
+ ex:doc ex:json ?J .
25
+ (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
26
+ (?J "/users") string:jsonPointer ?Users .
27
+
28
+ ?Users list:iterate (?Idx ?UserJson) .
29
+ (?UserJson "/id") string:jsonPointer ?Id .
30
+ (?UserJson "/email") string:jsonPointer ?Email .
31
+ (?UserJson "/profile~1name") string:jsonPointer ?Name .
32
+
33
+ (?Email "@([^@]+)$") string:scrape ?EmailDomain .
34
+ ?Allowed list:member ?EmailDomain .
35
+
36
+ ("urn:example:user:%s" ?Id) string:format ?UriStr .
37
+ ?User log:uri ?UriStr .
38
+ } => {
39
+ ?User a ex:AllowedUser ;
40
+ ex:name ?Name ;
41
+ ex:email ?Email ;
42
+ ex:emailDomain ?EmailDomain ;
43
+ ex:userIndex ?Idx .
44
+ } .
45
+
46
+ # Blocked users: email domain is NOT in the allowlist
47
+ {
48
+ ex:doc ex:json ?J .
49
+ (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
50
+ (?J "/users") string:jsonPointer ?Users .
51
+
52
+ ?Users list:iterate (?Idx ?UserJson) .
53
+ (?UserJson "/id") string:jsonPointer ?Id .
54
+ (?UserJson "/email") string:jsonPointer ?Email .
55
+ (?UserJson "/profile~1name") string:jsonPointer ?Name .
56
+
57
+ (?Email "@([^@]+)$") string:scrape ?EmailDomain .
58
+ ?Allowed list:notMember ?EmailDomain .
59
+
60
+ ("urn:example:user:%s" ?Id) string:format ?UriStr .
61
+ ?User log:uri ?UriStr .
62
+ } => {
63
+ ?User a ex:BlockedUser ;
64
+ ex:name ?Name ;
65
+ ex:email ?Email ;
66
+ ex:emailDomain ?EmailDomain ;
67
+ ex:userIndex ?Idx .
68
+ } .
69
+
@@ -0,0 +1,800 @@
1
+ @prefix ex: <http://example.org/> .
2
+
3
+ # ----------------------------------------------------------------------
4
+ # Proof for derived triple:
5
+ # ex:checks ex:firstUserNameOk true .
6
+ # It holds because the following instance of the rule body is provable:
7
+ # ex:doc ex:json """{
8
+ # "users": [
9
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
10
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
11
+ # ],
12
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
13
+ # }""" .
14
+ # ("""{
15
+ # "users": [
16
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
17
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
18
+ # ],
19
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
20
+ # }""" "/users/0/profile~1name") string:jsonPointer "Ada Lovelace" .
21
+ # via the schematic forward rule:
22
+ # {
23
+ # ex:doc ex:json ?J .
24
+ # (?J "/users/0/profile~1name") string:jsonPointer "Ada Lovelace" .
25
+ # } => {
26
+ # ex:checks ex:firstUserNameOk true .
27
+ # } .
28
+ # with substitution (on rule variables):
29
+ # ?J = """{
30
+ # "users": [
31
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
32
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
33
+ # ],
34
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
35
+ # }"""
36
+ # Therefore the derived triple above is entailed by the rules and facts.
37
+ # ----------------------------------------------------------------------
38
+
39
+ ex:checks ex:firstUserNameOk true .
40
+
41
+ # ----------------------------------------------------------------------
42
+ # Proof for derived triple:
43
+ # <urn:example:user:u1> a ex:AllowedUser .
44
+ # It holds because the following instance of the rule body is provable:
45
+ # ex:doc ex:json """{
46
+ # "users": [
47
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
48
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
49
+ # ],
50
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
51
+ # }""" .
52
+ # ("""{
53
+ # "users": [
54
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
55
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
56
+ # ],
57
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
58
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
59
+ # ("""{
60
+ # "users": [
61
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
62
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
63
+ # ],
64
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
65
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
66
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (0 """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""") .
67
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/id") string:jsonPointer "u1" .
68
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/email") string:jsonPointer "ada@example.org" .
69
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/profile~1name") string:jsonPointer "Ada Lovelace" .
70
+ # ("ada@example.org" "@([^@]+)$") string:scrape "example.org" .
71
+ # ("example.org" "example.com") list:member "example.org" .
72
+ # ("urn:example:user:%s" "u1") string:format "urn:example:user:u1" .
73
+ # <urn:example:user:u1> log:uri "urn:example:user:u1" .
74
+ # via the schematic forward rule:
75
+ # {
76
+ # ex:doc ex:json ?J .
77
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
78
+ # (?J "/users") string:jsonPointer ?Users .
79
+ # ?Users list:iterate (?Idx ?UserJson) .
80
+ # (?UserJson "/id") string:jsonPointer ?Id .
81
+ # (?UserJson "/email") string:jsonPointer ?Email .
82
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
83
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
84
+ # ?Allowed list:member ?EmailDomain .
85
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
86
+ # ?User log:uri ?UriStr .
87
+ # } => {
88
+ # ?User a ex:AllowedUser .
89
+ # ?User ex:name ?Name .
90
+ # ?User ex:email ?Email .
91
+ # ?User ex:emailDomain ?EmailDomain .
92
+ # ?User ex:userIndex ?Idx .
93
+ # } .
94
+ # with substitution (on rule variables):
95
+ # ?Allowed = ("example.org" "example.com")
96
+ # ?Email = "ada@example.org"
97
+ # ?EmailDomain = "example.org"
98
+ # ?Id = "u1"
99
+ # ?Idx = 0
100
+ # ?J = """{
101
+ # "users": [
102
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
103
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
104
+ # ],
105
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
106
+ # }"""
107
+ # ?Name = "Ada Lovelace"
108
+ # ?UriStr = "urn:example:user:u1"
109
+ # ?User = <urn:example:user:u1>
110
+ # ?UserJson = """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}"""
111
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
112
+ # Therefore the derived triple above is entailed by the rules and facts.
113
+ # ----------------------------------------------------------------------
114
+
115
+ <urn:example:user:u1> a ex:AllowedUser .
116
+
117
+ # ----------------------------------------------------------------------
118
+ # Proof for derived triple:
119
+ # <urn:example:user:u1> ex:name "Ada Lovelace" .
120
+ # It holds because the following instance of the rule body is provable:
121
+ # ex:doc ex:json """{
122
+ # "users": [
123
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
124
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
125
+ # ],
126
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
127
+ # }""" .
128
+ # ("""{
129
+ # "users": [
130
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
131
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
132
+ # ],
133
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
134
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
135
+ # ("""{
136
+ # "users": [
137
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
138
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
139
+ # ],
140
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
141
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
142
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (0 """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""") .
143
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/id") string:jsonPointer "u1" .
144
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/email") string:jsonPointer "ada@example.org" .
145
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/profile~1name") string:jsonPointer "Ada Lovelace" .
146
+ # ("ada@example.org" "@([^@]+)$") string:scrape "example.org" .
147
+ # ("example.org" "example.com") list:member "example.org" .
148
+ # ("urn:example:user:%s" "u1") string:format "urn:example:user:u1" .
149
+ # <urn:example:user:u1> log:uri "urn:example:user:u1" .
150
+ # via the schematic forward rule:
151
+ # {
152
+ # ex:doc ex:json ?J .
153
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
154
+ # (?J "/users") string:jsonPointer ?Users .
155
+ # ?Users list:iterate (?Idx ?UserJson) .
156
+ # (?UserJson "/id") string:jsonPointer ?Id .
157
+ # (?UserJson "/email") string:jsonPointer ?Email .
158
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
159
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
160
+ # ?Allowed list:member ?EmailDomain .
161
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
162
+ # ?User log:uri ?UriStr .
163
+ # } => {
164
+ # ?User a ex:AllowedUser .
165
+ # ?User ex:name ?Name .
166
+ # ?User ex:email ?Email .
167
+ # ?User ex:emailDomain ?EmailDomain .
168
+ # ?User ex:userIndex ?Idx .
169
+ # } .
170
+ # with substitution (on rule variables):
171
+ # ?Allowed = ("example.org" "example.com")
172
+ # ?Email = "ada@example.org"
173
+ # ?EmailDomain = "example.org"
174
+ # ?Id = "u1"
175
+ # ?Idx = 0
176
+ # ?J = """{
177
+ # "users": [
178
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
179
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
180
+ # ],
181
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
182
+ # }"""
183
+ # ?Name = "Ada Lovelace"
184
+ # ?UriStr = "urn:example:user:u1"
185
+ # ?User = <urn:example:user:u1>
186
+ # ?UserJson = """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}"""
187
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
188
+ # Therefore the derived triple above is entailed by the rules and facts.
189
+ # ----------------------------------------------------------------------
190
+
191
+ <urn:example:user:u1> ex:name "Ada Lovelace" .
192
+
193
+ # ----------------------------------------------------------------------
194
+ # Proof for derived triple:
195
+ # <urn:example:user:u1> ex:email "ada@example.org" .
196
+ # It holds because the following instance of the rule body is provable:
197
+ # ex:doc ex:json """{
198
+ # "users": [
199
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
200
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
201
+ # ],
202
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
203
+ # }""" .
204
+ # ("""{
205
+ # "users": [
206
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
207
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
208
+ # ],
209
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
210
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
211
+ # ("""{
212
+ # "users": [
213
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
214
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
215
+ # ],
216
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
217
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
218
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (0 """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""") .
219
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/id") string:jsonPointer "u1" .
220
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/email") string:jsonPointer "ada@example.org" .
221
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/profile~1name") string:jsonPointer "Ada Lovelace" .
222
+ # ("ada@example.org" "@([^@]+)$") string:scrape "example.org" .
223
+ # ("example.org" "example.com") list:member "example.org" .
224
+ # ("urn:example:user:%s" "u1") string:format "urn:example:user:u1" .
225
+ # <urn:example:user:u1> log:uri "urn:example:user:u1" .
226
+ # via the schematic forward rule:
227
+ # {
228
+ # ex:doc ex:json ?J .
229
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
230
+ # (?J "/users") string:jsonPointer ?Users .
231
+ # ?Users list:iterate (?Idx ?UserJson) .
232
+ # (?UserJson "/id") string:jsonPointer ?Id .
233
+ # (?UserJson "/email") string:jsonPointer ?Email .
234
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
235
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
236
+ # ?Allowed list:member ?EmailDomain .
237
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
238
+ # ?User log:uri ?UriStr .
239
+ # } => {
240
+ # ?User a ex:AllowedUser .
241
+ # ?User ex:name ?Name .
242
+ # ?User ex:email ?Email .
243
+ # ?User ex:emailDomain ?EmailDomain .
244
+ # ?User ex:userIndex ?Idx .
245
+ # } .
246
+ # with substitution (on rule variables):
247
+ # ?Allowed = ("example.org" "example.com")
248
+ # ?Email = "ada@example.org"
249
+ # ?EmailDomain = "example.org"
250
+ # ?Id = "u1"
251
+ # ?Idx = 0
252
+ # ?J = """{
253
+ # "users": [
254
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
255
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
256
+ # ],
257
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
258
+ # }"""
259
+ # ?Name = "Ada Lovelace"
260
+ # ?UriStr = "urn:example:user:u1"
261
+ # ?User = <urn:example:user:u1>
262
+ # ?UserJson = """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}"""
263
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
264
+ # Therefore the derived triple above is entailed by the rules and facts.
265
+ # ----------------------------------------------------------------------
266
+
267
+ <urn:example:user:u1> ex:email "ada@example.org" .
268
+
269
+ # ----------------------------------------------------------------------
270
+ # Proof for derived triple:
271
+ # <urn:example:user:u1> ex:emailDomain "example.org" .
272
+ # It holds because the following instance of the rule body is provable:
273
+ # ex:doc ex:json """{
274
+ # "users": [
275
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
276
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
277
+ # ],
278
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
279
+ # }""" .
280
+ # ("""{
281
+ # "users": [
282
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
283
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
284
+ # ],
285
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
286
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
287
+ # ("""{
288
+ # "users": [
289
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
290
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
291
+ # ],
292
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
293
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
294
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (0 """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""") .
295
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/id") string:jsonPointer "u1" .
296
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/email") string:jsonPointer "ada@example.org" .
297
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/profile~1name") string:jsonPointer "Ada Lovelace" .
298
+ # ("ada@example.org" "@([^@]+)$") string:scrape "example.org" .
299
+ # ("example.org" "example.com") list:member "example.org" .
300
+ # ("urn:example:user:%s" "u1") string:format "urn:example:user:u1" .
301
+ # <urn:example:user:u1> log:uri "urn:example:user:u1" .
302
+ # via the schematic forward rule:
303
+ # {
304
+ # ex:doc ex:json ?J .
305
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
306
+ # (?J "/users") string:jsonPointer ?Users .
307
+ # ?Users list:iterate (?Idx ?UserJson) .
308
+ # (?UserJson "/id") string:jsonPointer ?Id .
309
+ # (?UserJson "/email") string:jsonPointer ?Email .
310
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
311
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
312
+ # ?Allowed list:member ?EmailDomain .
313
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
314
+ # ?User log:uri ?UriStr .
315
+ # } => {
316
+ # ?User a ex:AllowedUser .
317
+ # ?User ex:name ?Name .
318
+ # ?User ex:email ?Email .
319
+ # ?User ex:emailDomain ?EmailDomain .
320
+ # ?User ex:userIndex ?Idx .
321
+ # } .
322
+ # with substitution (on rule variables):
323
+ # ?Allowed = ("example.org" "example.com")
324
+ # ?Email = "ada@example.org"
325
+ # ?EmailDomain = "example.org"
326
+ # ?Id = "u1"
327
+ # ?Idx = 0
328
+ # ?J = """{
329
+ # "users": [
330
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
331
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
332
+ # ],
333
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
334
+ # }"""
335
+ # ?Name = "Ada Lovelace"
336
+ # ?UriStr = "urn:example:user:u1"
337
+ # ?User = <urn:example:user:u1>
338
+ # ?UserJson = """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}"""
339
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
340
+ # Therefore the derived triple above is entailed by the rules and facts.
341
+ # ----------------------------------------------------------------------
342
+
343
+ <urn:example:user:u1> ex:emailDomain "example.org" .
344
+
345
+ # ----------------------------------------------------------------------
346
+ # Proof for derived triple:
347
+ # <urn:example:user:u1> ex:userIndex 0 .
348
+ # It holds because the following instance of the rule body is provable:
349
+ # ex:doc ex:json """{
350
+ # "users": [
351
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
352
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
353
+ # ],
354
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
355
+ # }""" .
356
+ # ("""{
357
+ # "users": [
358
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
359
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
360
+ # ],
361
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
362
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
363
+ # ("""{
364
+ # "users": [
365
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
366
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
367
+ # ],
368
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
369
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
370
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (0 """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""") .
371
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/id") string:jsonPointer "u1" .
372
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/email") string:jsonPointer "ada@example.org" .
373
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" "/profile~1name") string:jsonPointer "Ada Lovelace" .
374
+ # ("ada@example.org" "@([^@]+)$") string:scrape "example.org" .
375
+ # ("example.org" "example.com") list:member "example.org" .
376
+ # ("urn:example:user:%s" "u1") string:format "urn:example:user:u1" .
377
+ # <urn:example:user:u1> log:uri "urn:example:user:u1" .
378
+ # via the schematic forward rule:
379
+ # {
380
+ # ex:doc ex:json ?J .
381
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
382
+ # (?J "/users") string:jsonPointer ?Users .
383
+ # ?Users list:iterate (?Idx ?UserJson) .
384
+ # (?UserJson "/id") string:jsonPointer ?Id .
385
+ # (?UserJson "/email") string:jsonPointer ?Email .
386
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
387
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
388
+ # ?Allowed list:member ?EmailDomain .
389
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
390
+ # ?User log:uri ?UriStr .
391
+ # } => {
392
+ # ?User a ex:AllowedUser .
393
+ # ?User ex:name ?Name .
394
+ # ?User ex:email ?Email .
395
+ # ?User ex:emailDomain ?EmailDomain .
396
+ # ?User ex:userIndex ?Idx .
397
+ # } .
398
+ # with substitution (on rule variables):
399
+ # ?Allowed = ("example.org" "example.com")
400
+ # ?Email = "ada@example.org"
401
+ # ?EmailDomain = "example.org"
402
+ # ?Id = "u1"
403
+ # ?Idx = 0
404
+ # ?J = """{
405
+ # "users": [
406
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
407
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
408
+ # ],
409
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
410
+ # }"""
411
+ # ?Name = "Ada Lovelace"
412
+ # ?UriStr = "urn:example:user:u1"
413
+ # ?User = <urn:example:user:u1>
414
+ # ?UserJson = """{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}"""
415
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
416
+ # Therefore the derived triple above is entailed by the rules and facts.
417
+ # ----------------------------------------------------------------------
418
+
419
+ <urn:example:user:u1> ex:userIndex 0 .
420
+
421
+ # ----------------------------------------------------------------------
422
+ # Proof for derived triple:
423
+ # <urn:example:user:u2> a ex:BlockedUser .
424
+ # It holds because the following instance of the rule body is provable:
425
+ # ex:doc ex:json """{
426
+ # "users": [
427
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
428
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
429
+ # ],
430
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
431
+ # }""" .
432
+ # ("""{
433
+ # "users": [
434
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
435
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
436
+ # ],
437
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
438
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
439
+ # ("""{
440
+ # "users": [
441
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
442
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
443
+ # ],
444
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
445
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
446
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (1 """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
447
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/id") string:jsonPointer "u2" .
448
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/email") string:jsonPointer "bob@evil.invalid" .
449
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/profile~1name") string:jsonPointer "Bob Mallory" .
450
+ # ("bob@evil.invalid" "@([^@]+)$") string:scrape "evil.invalid" .
451
+ # ("urn:example:user:%s" "u2") string:format "urn:example:user:u2" .
452
+ # <urn:example:user:u2> log:uri "urn:example:user:u2" .
453
+ # ("example.org" "example.com") list:notMember "evil.invalid" .
454
+ # via the schematic forward rule:
455
+ # {
456
+ # ex:doc ex:json ?J .
457
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
458
+ # (?J "/users") string:jsonPointer ?Users .
459
+ # ?Users list:iterate (?Idx ?UserJson) .
460
+ # (?UserJson "/id") string:jsonPointer ?Id .
461
+ # (?UserJson "/email") string:jsonPointer ?Email .
462
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
463
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
464
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
465
+ # ?User log:uri ?UriStr .
466
+ # ?Allowed list:notMember ?EmailDomain .
467
+ # } => {
468
+ # ?User a ex:BlockedUser .
469
+ # ?User ex:name ?Name .
470
+ # ?User ex:email ?Email .
471
+ # ?User ex:emailDomain ?EmailDomain .
472
+ # ?User ex:userIndex ?Idx .
473
+ # } .
474
+ # with substitution (on rule variables):
475
+ # ?Allowed = ("example.org" "example.com")
476
+ # ?Email = "bob@evil.invalid"
477
+ # ?EmailDomain = "evil.invalid"
478
+ # ?Id = "u2"
479
+ # ?Idx = 1
480
+ # ?J = """{
481
+ # "users": [
482
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
483
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
484
+ # ],
485
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
486
+ # }"""
487
+ # ?Name = "Bob Mallory"
488
+ # ?UriStr = "urn:example:user:u2"
489
+ # ?User = <urn:example:user:u2>
490
+ # ?UserJson = """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}"""
491
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
492
+ # Therefore the derived triple above is entailed by the rules and facts.
493
+ # ----------------------------------------------------------------------
494
+
495
+ <urn:example:user:u2> a ex:BlockedUser .
496
+
497
+ # ----------------------------------------------------------------------
498
+ # Proof for derived triple:
499
+ # <urn:example:user:u2> ex:name "Bob Mallory" .
500
+ # It holds because the following instance of the rule body is provable:
501
+ # ex:doc ex:json """{
502
+ # "users": [
503
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
504
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
505
+ # ],
506
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
507
+ # }""" .
508
+ # ("""{
509
+ # "users": [
510
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
511
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
512
+ # ],
513
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
514
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
515
+ # ("""{
516
+ # "users": [
517
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
518
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
519
+ # ],
520
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
521
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
522
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (1 """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
523
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/id") string:jsonPointer "u2" .
524
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/email") string:jsonPointer "bob@evil.invalid" .
525
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/profile~1name") string:jsonPointer "Bob Mallory" .
526
+ # ("bob@evil.invalid" "@([^@]+)$") string:scrape "evil.invalid" .
527
+ # ("urn:example:user:%s" "u2") string:format "urn:example:user:u2" .
528
+ # <urn:example:user:u2> log:uri "urn:example:user:u2" .
529
+ # ("example.org" "example.com") list:notMember "evil.invalid" .
530
+ # via the schematic forward rule:
531
+ # {
532
+ # ex:doc ex:json ?J .
533
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
534
+ # (?J "/users") string:jsonPointer ?Users .
535
+ # ?Users list:iterate (?Idx ?UserJson) .
536
+ # (?UserJson "/id") string:jsonPointer ?Id .
537
+ # (?UserJson "/email") string:jsonPointer ?Email .
538
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
539
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
540
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
541
+ # ?User log:uri ?UriStr .
542
+ # ?Allowed list:notMember ?EmailDomain .
543
+ # } => {
544
+ # ?User a ex:BlockedUser .
545
+ # ?User ex:name ?Name .
546
+ # ?User ex:email ?Email .
547
+ # ?User ex:emailDomain ?EmailDomain .
548
+ # ?User ex:userIndex ?Idx .
549
+ # } .
550
+ # with substitution (on rule variables):
551
+ # ?Allowed = ("example.org" "example.com")
552
+ # ?Email = "bob@evil.invalid"
553
+ # ?EmailDomain = "evil.invalid"
554
+ # ?Id = "u2"
555
+ # ?Idx = 1
556
+ # ?J = """{
557
+ # "users": [
558
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
559
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
560
+ # ],
561
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
562
+ # }"""
563
+ # ?Name = "Bob Mallory"
564
+ # ?UriStr = "urn:example:user:u2"
565
+ # ?User = <urn:example:user:u2>
566
+ # ?UserJson = """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}"""
567
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
568
+ # Therefore the derived triple above is entailed by the rules and facts.
569
+ # ----------------------------------------------------------------------
570
+
571
+ <urn:example:user:u2> ex:name "Bob Mallory" .
572
+
573
+ # ----------------------------------------------------------------------
574
+ # Proof for derived triple:
575
+ # <urn:example:user:u2> ex:email "bob@evil.invalid" .
576
+ # It holds because the following instance of the rule body is provable:
577
+ # ex:doc ex:json """{
578
+ # "users": [
579
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
580
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
581
+ # ],
582
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
583
+ # }""" .
584
+ # ("""{
585
+ # "users": [
586
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
587
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
588
+ # ],
589
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
590
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
591
+ # ("""{
592
+ # "users": [
593
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
594
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
595
+ # ],
596
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
597
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
598
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (1 """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
599
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/id") string:jsonPointer "u2" .
600
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/email") string:jsonPointer "bob@evil.invalid" .
601
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/profile~1name") string:jsonPointer "Bob Mallory" .
602
+ # ("bob@evil.invalid" "@([^@]+)$") string:scrape "evil.invalid" .
603
+ # ("urn:example:user:%s" "u2") string:format "urn:example:user:u2" .
604
+ # <urn:example:user:u2> log:uri "urn:example:user:u2" .
605
+ # ("example.org" "example.com") list:notMember "evil.invalid" .
606
+ # via the schematic forward rule:
607
+ # {
608
+ # ex:doc ex:json ?J .
609
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
610
+ # (?J "/users") string:jsonPointer ?Users .
611
+ # ?Users list:iterate (?Idx ?UserJson) .
612
+ # (?UserJson "/id") string:jsonPointer ?Id .
613
+ # (?UserJson "/email") string:jsonPointer ?Email .
614
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
615
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
616
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
617
+ # ?User log:uri ?UriStr .
618
+ # ?Allowed list:notMember ?EmailDomain .
619
+ # } => {
620
+ # ?User a ex:BlockedUser .
621
+ # ?User ex:name ?Name .
622
+ # ?User ex:email ?Email .
623
+ # ?User ex:emailDomain ?EmailDomain .
624
+ # ?User ex:userIndex ?Idx .
625
+ # } .
626
+ # with substitution (on rule variables):
627
+ # ?Allowed = ("example.org" "example.com")
628
+ # ?Email = "bob@evil.invalid"
629
+ # ?EmailDomain = "evil.invalid"
630
+ # ?Id = "u2"
631
+ # ?Idx = 1
632
+ # ?J = """{
633
+ # "users": [
634
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
635
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
636
+ # ],
637
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
638
+ # }"""
639
+ # ?Name = "Bob Mallory"
640
+ # ?UriStr = "urn:example:user:u2"
641
+ # ?User = <urn:example:user:u2>
642
+ # ?UserJson = """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}"""
643
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
644
+ # Therefore the derived triple above is entailed by the rules and facts.
645
+ # ----------------------------------------------------------------------
646
+
647
+ <urn:example:user:u2> ex:email "bob@evil.invalid" .
648
+
649
+ # ----------------------------------------------------------------------
650
+ # Proof for derived triple:
651
+ # <urn:example:user:u2> ex:emailDomain "evil.invalid" .
652
+ # It holds because the following instance of the rule body is provable:
653
+ # ex:doc ex:json """{
654
+ # "users": [
655
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
656
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
657
+ # ],
658
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
659
+ # }""" .
660
+ # ("""{
661
+ # "users": [
662
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
663
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
664
+ # ],
665
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
666
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
667
+ # ("""{
668
+ # "users": [
669
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
670
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
671
+ # ],
672
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
673
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
674
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (1 """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
675
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/id") string:jsonPointer "u2" .
676
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/email") string:jsonPointer "bob@evil.invalid" .
677
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/profile~1name") string:jsonPointer "Bob Mallory" .
678
+ # ("bob@evil.invalid" "@([^@]+)$") string:scrape "evil.invalid" .
679
+ # ("urn:example:user:%s" "u2") string:format "urn:example:user:u2" .
680
+ # <urn:example:user:u2> log:uri "urn:example:user:u2" .
681
+ # ("example.org" "example.com") list:notMember "evil.invalid" .
682
+ # via the schematic forward rule:
683
+ # {
684
+ # ex:doc ex:json ?J .
685
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
686
+ # (?J "/users") string:jsonPointer ?Users .
687
+ # ?Users list:iterate (?Idx ?UserJson) .
688
+ # (?UserJson "/id") string:jsonPointer ?Id .
689
+ # (?UserJson "/email") string:jsonPointer ?Email .
690
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
691
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
692
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
693
+ # ?User log:uri ?UriStr .
694
+ # ?Allowed list:notMember ?EmailDomain .
695
+ # } => {
696
+ # ?User a ex:BlockedUser .
697
+ # ?User ex:name ?Name .
698
+ # ?User ex:email ?Email .
699
+ # ?User ex:emailDomain ?EmailDomain .
700
+ # ?User ex:userIndex ?Idx .
701
+ # } .
702
+ # with substitution (on rule variables):
703
+ # ?Allowed = ("example.org" "example.com")
704
+ # ?Email = "bob@evil.invalid"
705
+ # ?EmailDomain = "evil.invalid"
706
+ # ?Id = "u2"
707
+ # ?Idx = 1
708
+ # ?J = """{
709
+ # "users": [
710
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
711
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
712
+ # ],
713
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
714
+ # }"""
715
+ # ?Name = "Bob Mallory"
716
+ # ?UriStr = "urn:example:user:u2"
717
+ # ?User = <urn:example:user:u2>
718
+ # ?UserJson = """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}"""
719
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
720
+ # Therefore the derived triple above is entailed by the rules and facts.
721
+ # ----------------------------------------------------------------------
722
+
723
+ <urn:example:user:u2> ex:emailDomain "evil.invalid" .
724
+
725
+ # ----------------------------------------------------------------------
726
+ # Proof for derived triple:
727
+ # <urn:example:user:u2> ex:userIndex 1 .
728
+ # It holds because the following instance of the rule body is provable:
729
+ # ex:doc ex:json """{
730
+ # "users": [
731
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
732
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
733
+ # ],
734
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
735
+ # }""" .
736
+ # ("""{
737
+ # "users": [
738
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
739
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
740
+ # ],
741
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
742
+ # }""" "/policy/allowedDomains") string:jsonPointer ("example.org" "example.com") .
743
+ # ("""{
744
+ # "users": [
745
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
746
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
747
+ # ],
748
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
749
+ # }""" "/users") string:jsonPointer ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
750
+ # ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") list:iterate (1 """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""") .
751
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/id") string:jsonPointer "u2" .
752
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/email") string:jsonPointer "bob@evil.invalid" .
753
+ # ("""{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""" "/profile~1name") string:jsonPointer "Bob Mallory" .
754
+ # ("bob@evil.invalid" "@([^@]+)$") string:scrape "evil.invalid" .
755
+ # ("urn:example:user:%s" "u2") string:format "urn:example:user:u2" .
756
+ # <urn:example:user:u2> log:uri "urn:example:user:u2" .
757
+ # ("example.org" "example.com") list:notMember "evil.invalid" .
758
+ # via the schematic forward rule:
759
+ # {
760
+ # ex:doc ex:json ?J .
761
+ # (?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
762
+ # (?J "/users") string:jsonPointer ?Users .
763
+ # ?Users list:iterate (?Idx ?UserJson) .
764
+ # (?UserJson "/id") string:jsonPointer ?Id .
765
+ # (?UserJson "/email") string:jsonPointer ?Email .
766
+ # (?UserJson "/profile~1name") string:jsonPointer ?Name .
767
+ # (?Email "@([^@]+)$") string:scrape ?EmailDomain .
768
+ # ("urn:example:user:%s" ?Id) string:format ?UriStr .
769
+ # ?User log:uri ?UriStr .
770
+ # ?Allowed list:notMember ?EmailDomain .
771
+ # } => {
772
+ # ?User a ex:BlockedUser .
773
+ # ?User ex:name ?Name .
774
+ # ?User ex:email ?Email .
775
+ # ?User ex:emailDomain ?EmailDomain .
776
+ # ?User ex:userIndex ?Idx .
777
+ # } .
778
+ # with substitution (on rule variables):
779
+ # ?Allowed = ("example.org" "example.com")
780
+ # ?Email = "bob@evil.invalid"
781
+ # ?EmailDomain = "evil.invalid"
782
+ # ?Id = "u2"
783
+ # ?Idx = 1
784
+ # ?J = """{
785
+ # "users": [
786
+ # { "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
787
+ # { "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
788
+ # ],
789
+ # "policy": { "allowedDomains": ["example.org", "example.com"] }
790
+ # }"""
791
+ # ?Name = "Bob Mallory"
792
+ # ?UriStr = "urn:example:user:u2"
793
+ # ?User = <urn:example:user:u2>
794
+ # ?UserJson = """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}"""
795
+ # ?Users = ("""{"id":"u1","email":"ada@example.org","profile/name":"Ada Lovelace"}""" """{"id":"u2","email":"bob@evil.invalid","profile/name":"Bob Mallory"}""")
796
+ # Therefore the derived triple above is entailed by the rules and facts.
797
+ # ----------------------------------------------------------------------
798
+
799
+ <urn:example:user:u2> ex:userIndex 1 .
800
+
package/eyeling.js CHANGED
@@ -39,6 +39,9 @@ const SKOLEM_NS = "https://eyereasoner.github.io/.well-known/genid/";
39
39
  // of the subject term in log:skolem to a Skolem IRI.
40
40
  const skolemCache = new Map();
41
41
 
42
+ // Cache for string:jsonPointer: jsonText -> { parsed: any|null, ptrCache: Map<string, Term|null> }
43
+ const jsonPointerCache = new Map();
44
+
42
45
  // Controls whether human-readable proof comments are printed.
43
46
  let proofCommentsEnabled = true;
44
47
 
@@ -676,8 +679,23 @@ class Parser {
676
679
  this.expectDot();
677
680
  backwardRules.push(this.makeRule(first, second, false));
678
681
  } else {
679
- const more = this.parsePredicateObjectList(first);
680
- this.expectDot();
682
+ let more;
683
+
684
+ if (this.peek().typ === "Dot") {
685
+ // Allow a bare blank-node property list statement, e.g. `[ a :Statement ].`
686
+ const lastTok = this.toks[this.pos - 1];
687
+ if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === "RBracket") {
688
+ more = this.pendingTriples;
689
+ this.pendingTriples = [];
690
+ this.next(); // consume '.'
691
+ } else {
692
+ throw new Error(`Unexpected '.' after term; missing predicate/object list`);
693
+ }
694
+ } else {
695
+ more = this.parsePredicateObjectList(first);
696
+ this.expectDot();
697
+ }
698
+
681
699
  // normalize explicit log:implies / log:impliedBy at top-level
682
700
  for (const tr of more) {
683
701
  if (isLogImplies(tr.p) && tr.s instanceof FormulaTerm && tr.o instanceof FormulaTerm) {
@@ -692,7 +710,6 @@ class Parser {
692
710
  }
693
711
  }
694
712
 
695
- // console.log(JSON.stringify([this.prefixes, triples, forwardRules, backwardRules], null, 2));
696
713
  return [this.prefixes, triples, forwardRules, backwardRules];
697
714
  }
698
715
 
@@ -929,6 +946,17 @@ class Parser {
929
946
  throw new Error(`Expected '.' or '}', got ${this.peek().toString()}`);
930
947
  }
931
948
  } else {
949
+ // Allow a bare blank-node property list statement inside a formula, e.g. `{ [ a :X ]. }`
950
+ if (this.peek().typ === "Dot" || this.peek().typ === "RBrace") {
951
+ const lastTok = this.toks[this.pos - 1];
952
+ if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === "RBracket") {
953
+ triples.push(...this.pendingTriples);
954
+ this.pendingTriples = [];
955
+ if (this.peek().typ === "Dot") this.next();
956
+ continue;
957
+ }
958
+ }
959
+
932
960
  triples.push(...this.parsePredicateObjectList(left));
933
961
  if (this.peek().typ === "Dot") this.next();
934
962
  else if (this.peek().typ === "RBrace") {
@@ -1794,6 +1822,195 @@ function makeStringLiteral(str) {
1794
1822
  return new Literal(JSON.stringify(str));
1795
1823
  }
1796
1824
 
1825
+ function termToJsStringDecoded(t) {
1826
+ // Like termToJsString, but for short literals it *also* interprets escapes
1827
+ // (\" \n \uXXXX …) by attempting JSON.parse on the quoted lexical form.
1828
+ if (!(t instanceof Literal)) return null;
1829
+ const [lex, _dt] = literalParts(t.value);
1830
+
1831
+ // Long strings: """ ... """ are taken verbatim.
1832
+ if (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""')) {
1833
+ return lex.slice(3, -3);
1834
+ }
1835
+
1836
+ // Short strings: try to decode escapes (this makes "{\"a\":1}" usable too).
1837
+ if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
1838
+ try { return JSON.parse(lex); } catch (e) { /* fall through */ }
1839
+ return stripQuotes(lex);
1840
+ }
1841
+
1842
+ return stripQuotes(lex);
1843
+ }
1844
+
1845
+ function _jsonPointerUnescape(seg) {
1846
+ // RFC6901: ~1 -> '/', ~0 -> '~'
1847
+ // Any other '~' escape is invalid.
1848
+ let out = "";
1849
+ for (let i = 0; i < seg.length; i++) {
1850
+ const c = seg[i];
1851
+ if (c !== "~") { out += c; continue; }
1852
+ if (i + 1 >= seg.length) return null;
1853
+ const n = seg[i + 1];
1854
+ if (n === "0") out += "~";
1855
+ else if (n === "1") out += "/";
1856
+ else return null;
1857
+ i++;
1858
+ }
1859
+ return out;
1860
+ }
1861
+
1862
+ function _jsonToTerm(v) {
1863
+ if (v === null) return makeStringLiteral("null");
1864
+ if (typeof v === "string") return makeStringLiteral(v);
1865
+ if (typeof v === "number") return new Literal(String(v));
1866
+ if (typeof v === "boolean") return new Literal(v ? "true" : "false");
1867
+ if (Array.isArray(v)) return new ListTerm(v.map(_jsonToTerm));
1868
+ if (typeof v === "object") return makeStringLiteral(JSON.stringify(v));
1869
+ return null;
1870
+ }
1871
+
1872
+ function _jsonPointerLookup(jsonText, pointer) {
1873
+ // Support URI fragment form "#/a/b" (percent-decoded) as well.
1874
+ let ptr = pointer;
1875
+ if (ptr.startsWith("#")) {
1876
+ try { ptr = decodeURIComponent(ptr.slice(1)); } catch (e) { return null; }
1877
+ }
1878
+
1879
+ // Cache per JSON document
1880
+ let entry = jsonPointerCache.get(jsonText);
1881
+ if (!entry) {
1882
+ let parsed = null;
1883
+ try { parsed = JSON.parse(jsonText); } catch (e) { parsed = null; }
1884
+ entry = { parsed, ptrCache: new Map() };
1885
+ jsonPointerCache.set(jsonText, entry);
1886
+ }
1887
+ if (entry.parsed === null) return null;
1888
+
1889
+ // Cache per pointer within this doc
1890
+ if (entry.ptrCache.has(ptr)) return entry.ptrCache.get(ptr);
1891
+
1892
+ let cur = entry.parsed;
1893
+
1894
+ if (ptr === "") {
1895
+ const t = _jsonToTerm(cur);
1896
+ entry.ptrCache.set(ptr, t);
1897
+ return t;
1898
+ }
1899
+ if (!ptr.startsWith("/")) { entry.ptrCache.set(ptr, null); return null; }
1900
+
1901
+ const parts = ptr.split("/").slice(1);
1902
+ for (const raw of parts) {
1903
+ const seg = _jsonPointerUnescape(raw);
1904
+ if (seg === null) { entry.ptrCache.set(ptr, null); return null; }
1905
+
1906
+ if (Array.isArray(cur)) {
1907
+ // JSON Pointer uses array indices as decimal strings
1908
+ if (!/^(0|[1-9]\d*)$/.test(seg)) { entry.ptrCache.set(ptr, null); return null; }
1909
+ const idx = Number(seg);
1910
+ if (!Number.isFinite(idx) || idx < 0 || idx >= cur.length) { entry.ptrCache.set(ptr, null); return null; }
1911
+ cur = cur[idx];
1912
+ continue;
1913
+ }
1914
+
1915
+ if (cur !== null && typeof cur === "object") {
1916
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) { entry.ptrCache.set(ptr, null); return null; }
1917
+ cur = cur[seg];
1918
+ continue;
1919
+ }
1920
+
1921
+ entry.ptrCache.set(ptr, null);
1922
+ return null;
1923
+ }
1924
+
1925
+ const out = _jsonToTerm(cur);
1926
+ entry.ptrCache.set(ptr, out);
1927
+ return out;
1928
+ }
1929
+
1930
+ function jsonPointerUnescape(seg) {
1931
+ // RFC6901: ~1 -> '/', ~0 -> '~'
1932
+ let out = "";
1933
+ for (let i = 0; i < seg.length; i++) {
1934
+ const c = seg[i];
1935
+ if (c !== "~") { out += c; continue; }
1936
+ if (i + 1 >= seg.length) return null;
1937
+ const n = seg[i + 1];
1938
+ if (n === "0") out += "~";
1939
+ else if (n === "1") out += "/";
1940
+ else return null;
1941
+ i++;
1942
+ }
1943
+ return out;
1944
+ }
1945
+
1946
+ function jsonToTerm(v) {
1947
+ if (v === null) return makeStringLiteral("null");
1948
+ if (typeof v === "string") return makeStringLiteral(v);
1949
+ if (typeof v === "number") return new Literal(String(v));
1950
+ if (typeof v === "boolean") return new Literal(v ? "true" : "false");
1951
+ if (Array.isArray(v)) return new ListTerm(v.map(jsonToTerm));
1952
+
1953
+ if (v && typeof v === "object") {
1954
+ // IMPORTANT: long literal so it can be parsed again via termToJsString()
1955
+ // without needing escape decoding.
1956
+ const raw = JSON.stringify(v);
1957
+ return new Literal('"""' + raw + '"""');
1958
+ }
1959
+ return null;
1960
+ }
1961
+
1962
+ function jsonPointerLookup(jsonText, pointer) {
1963
+ let ptr = pointer;
1964
+
1965
+ // Support URI fragment form "#/a/b"
1966
+ if (ptr.startsWith("#")) {
1967
+ try { ptr = decodeURIComponent(ptr.slice(1)); } catch { return null; }
1968
+ }
1969
+
1970
+ let entry = jsonPointerCache.get(jsonText);
1971
+ if (!entry) {
1972
+ let parsed = null;
1973
+ try { parsed = JSON.parse(jsonText); } catch { parsed = null; }
1974
+ entry = { parsed, ptrCache: new Map() };
1975
+ jsonPointerCache.set(jsonText, entry);
1976
+ }
1977
+ if (entry.parsed === null) return null;
1978
+
1979
+ if (entry.ptrCache.has(ptr)) return entry.ptrCache.get(ptr);
1980
+
1981
+ let cur = entry.parsed;
1982
+
1983
+ if (ptr === "") {
1984
+ const t = jsonToTerm(cur);
1985
+ entry.ptrCache.set(ptr, t);
1986
+ return t;
1987
+ }
1988
+ if (!ptr.startsWith("/")) { entry.ptrCache.set(ptr, null); return null; }
1989
+
1990
+ const parts = ptr.split("/").slice(1);
1991
+ for (const raw of parts) {
1992
+ const seg = jsonPointerUnescape(raw);
1993
+ if (seg === null) { entry.ptrCache.set(ptr, null); return null; }
1994
+
1995
+ if (Array.isArray(cur)) {
1996
+ if (!/^(0|[1-9]\d*)$/.test(seg)) { entry.ptrCache.set(ptr, null); return null; }
1997
+ const idx = Number(seg);
1998
+ if (idx < 0 || idx >= cur.length) { entry.ptrCache.set(ptr, null); return null; }
1999
+ cur = cur[idx];
2000
+ } else if (cur !== null && typeof cur === "object") {
2001
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) { entry.ptrCache.set(ptr, null); return null; }
2002
+ cur = cur[seg];
2003
+ } else {
2004
+ entry.ptrCache.set(ptr, null);
2005
+ return null;
2006
+ }
2007
+ }
2008
+
2009
+ const out = jsonToTerm(cur);
2010
+ entry.ptrCache.set(ptr, out);
2011
+ return out;
2012
+ }
2013
+
1797
2014
  // Tiny subset of sprintf: supports only %s and %%.
1798
2015
  // Good enough for most N3 string:format use cases that just splice strings.
1799
2016
  function simpleStringFormat(fmt, args) {
@@ -3345,6 +3562,21 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3345
3562
  return s2 !== null ? [s2] : [];
3346
3563
  }
3347
3564
 
3565
+ // string:jsonPointer
3566
+ // Schema: ( $jsonText $pointer ) string:jsonPointer $value
3567
+ if (g.p instanceof Iri && g.p.value === STRING_NS + "jsonPointer") {
3568
+ if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3569
+ const jsonText = termToJsString(g.s.elems[0]);
3570
+ const ptr = termToJsString(g.s.elems[1]);
3571
+ if (jsonText === null || ptr === null) return [];
3572
+
3573
+ const valTerm = jsonPointerLookup(jsonText, ptr);
3574
+ if (valTerm === null) return [];
3575
+
3576
+ const s2 = unifyTerm(g.o, valTerm, subst);
3577
+ return s2 !== null ? [s2] : [];
3578
+ }
3579
+
3348
3580
  // string:greaterThan
3349
3581
  if (g.p instanceof Iri && g.p.value === STRING_NS + "greaterThan") {
3350
3582
  const sStr = termToJsString(g.s);
@@ -4187,6 +4419,7 @@ function main() {
4187
4419
  const toks = lex(text);
4188
4420
  const parser = new Parser(toks);
4189
4421
  const [prefixes, triples, frules, brules] = parser.parseDocument();
4422
+ // console.log(JSON.stringify([prefixes, triples, frules, brules], null, 2));
4190
4423
 
4191
4424
  const facts = triples.filter(tr => isGroundTriple(tr));
4192
4425
  const derived = forwardChain(facts, frules, brules);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.5.30",
3
+ "version": "1.5.32",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [