eyeling 1.8.2 → 1.8.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/README.md CHANGED
@@ -21,7 +21,7 @@ Try it here:
21
21
  - Load an N3 program from a URL (in the "Load N3 from URL" box or as ?url=...).
22
22
  - Share a link with the program encoded in the URL fragment (`#...`).
23
23
 
24
- - Streaming demo
24
+ - Streaming demo tab
25
25
  - Browse a Wikidata entity, load its facts, and see Eyeling’s **deductive closure appear incrementally** as triples are derived.
26
26
  - Edit **N3 rules live** and re-run to watch how different inference rules change what gets derived.
27
27
  - Demo **CORS-safe dynamic fetching**: derived “fetch requests” can trigger extra facts (e.g., Wikiquote extracts) that are injected and re-reasoned.
@@ -0,0 +1,182 @@
1
+ # =============================================================================
2
+ # AIR-style policy reasoning with a paraconsistent conflict and deterministic
3
+ # conflict resolution.
4
+ #
5
+ # Scenario:
6
+ # - Jos has Flu; Flu is a Disease -> Jos is Sick.
7
+ # - Doctor says Jos can do ProgrammingWork.
8
+ # - Policy: Sick + Work => deny Work (general).
9
+ # - Policy: Sick + Work + Office => deny Work (avoid infecting colleagues).
10
+ # - Resolution: If conflicted:
11
+ # * Work => Deny
12
+ # * Home+ProgrammingWork => Permit
13
+ # * otherwise => Undecided
14
+ #
15
+ # Technique:
16
+ # - Keep facts+rules in a quoted KB formula, compute closure with log:conclusion.
17
+ # - Use log:includes / log:notIncludes for scoped checks (SNAF).
18
+ # =============================================================================
19
+
20
+ @prefix : <http://example.org/#>.
21
+ @prefix air: <http://dig.csail.mit.edu/TAMI/2007/amord/air#>.
22
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
23
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
24
+
25
+ # -----------------------------------------------------------------------------
26
+ # 1) KB formula
27
+ # -----------------------------------------------------------------------------
28
+
29
+ :kb :hasFormula {
30
+
31
+ # Facts
32
+ :Jos a :Person ; :has :Flu .
33
+ :Flu :typeOf :Disease.
34
+
35
+ :Home a :Location.
36
+ :Office a :Location.
37
+
38
+ :ProgrammingWork rdfs:subClassOf :Work.
39
+
40
+ # Requests
41
+ :Request_Jos_Prog_Home a :Request ;
42
+ :agent :Jos ;
43
+ :job :ProgrammingWork ;
44
+ :location :Home .
45
+
46
+ :Request_Jos_Prog_Office a :Request ;
47
+ :agent :Jos ;
48
+ :job :ProgrammingWork ;
49
+ :location :Office .
50
+
51
+ # Doctor said (quoted)
52
+ :Doctor :says { :Jos :canDo :ProgrammingWork. }.
53
+
54
+ # Lift quoted statement
55
+ { ?doc :says ?G.
56
+ ?G log:includes { ?x :canDo ?job. }.
57
+ } => { ?x :canDo ?job. }.
58
+
59
+ # Flu -> Disease
60
+ { ?x :has ?a. ?a :typeOf ?b. } => { ?x :has ?b. }.
61
+
62
+ # Disease -> Sick (context)
63
+ { ?x :has :Disease. } => { ?x :healthStatus :Sick. }.
64
+
65
+ # Minimal subclass closure helper
66
+ { ?a rdfs:subClassOf ?b. } => { ?a :isKindOf ?b. }.
67
+ { ?a :isKindOf ?b. ?b :isKindOf ?c. } => { ?a :isKindOf ?c. }.
68
+
69
+ # canDo propagates to supertypes
70
+ { ?x :canDo ?job. ?job rdfs:subClassOf ?super. } => { ?x :canDo ?super. }.
71
+
72
+ # ----------------------------------------------------------
73
+ # AIR-annotated rules (documentation) + executable counterparts
74
+ # ----------------------------------------------------------
75
+
76
+ # Doctor: if canDo requested job => Permit
77
+ :R_DoctorPermit a air:Rule;
78
+ air:if { ?req a :Request; :agent ?x; :job ?job. ?x :canDo ?job. };
79
+ air:then { ?req :decision :Permit. }.
80
+
81
+ { ?req a :Request; :agent ?x; :job ?job. ?x :canDo ?job. }
82
+ => { ?req :decision :Permit. }.
83
+
84
+ # Health: Sick + Work => Deny
85
+ :R_SickNoWork a air:Rule;
86
+ air:if { ?req a :Request; :agent ?x; :job ?job. ?x :healthStatus :Sick. ?job :isKindOf :Work. };
87
+ air:then { ?req :decision :Deny. }.
88
+
89
+ { ?req a :Request; :agent ?x; :job ?job. ?x :healthStatus :Sick. ?job :isKindOf :Work. }
90
+ => { ?req :decision :Deny. }.
91
+
92
+ # Health: Sick + Work + Office => Deny (avoid infecting colleagues)
93
+ :R_SickWork a air:Rule;
94
+ air:if { ?req a :Request; :agent ?x; :job ?job; :location :Office.
95
+ ?x :healthStatus :Sick. ?job :isKindOf :Office. };
96
+ air:then { ?req :decision :Deny. ?x :avoidInfectingColleagues true. }.
97
+
98
+ { ?req a :Request; :agent ?x; :job ?job; :location :Office.
99
+ ?x :healthStatus :Sick. ?job :isKindOf :Office. }
100
+ => { ?req :decision :Deny. ?x :avoidInfectingColleagues true. }.
101
+
102
+ # Resolution hint: Sick + ProgrammingWork at Home => Permit
103
+ :R_SickProgHome a air:Rule;
104
+ air:if { ?req a :Request; :agent ?x; :job :ProgrammingWork; :location :Home. ?x :healthStatus :Sick. };
105
+ air:then { ?req :decision :Permit. ?x :remoteWorkRecommended true. }.
106
+
107
+ { ?req a :Request; :agent ?x; :job :ProgrammingWork; :location :Home. ?x :healthStatus :Sick. }
108
+ => { ?req :decision :Permit. ?x :remoteWorkRecommended true. }.
109
+ }.
110
+
111
+ # -----------------------------------------------------------------------------
112
+ # 2) Lift request facts out of the closure (small, deterministic output)
113
+ # -----------------------------------------------------------------------------
114
+
115
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
116
+ ?C log:includes { ?req a :Request; :agent ?who; :job ?job; :location ?loc. }.
117
+ } => { ?req :agent ?who; :job ?job; :location ?loc. }.
118
+
119
+ # -----------------------------------------------------------------------------
120
+ # 3) decisionStatus via scoped checks on closure
121
+ # -----------------------------------------------------------------------------
122
+
123
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
124
+ ?C log:includes { ?req :decision :Permit. }.
125
+ ?C log:includes { ?req :decision :Deny. }.
126
+ } => { ?req :decisionStatus :BothPermitDeny. }.
127
+
128
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
129
+ ?C log:includes { ?req :decision :Permit. }.
130
+ ?C log:notIncludes { ?req :decision :Deny. }.
131
+ } => { ?req :decisionStatus :PermitOnly. }.
132
+
133
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
134
+ ?C log:includes { ?req :decision :Deny. }.
135
+ ?C log:notIncludes { ?req :decision :Permit. }.
136
+ } => { ?req :decisionStatus :DenyOnly. }.
137
+
138
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
139
+ ?C log:notIncludes { ?req :decision :Permit. }.
140
+ ?C log:notIncludes { ?req :decision :Deny. }.
141
+ } => { ?req :decisionStatus :Neither. }.
142
+
143
+ # -----------------------------------------------------------------------------
144
+ # 4) effectiveDecision (mutually exclusive; no extra Undecided)
145
+ # -----------------------------------------------------------------------------
146
+
147
+ # Non-conflicts
148
+ { ?req :decisionStatus :PermitOnly. } => { ?req :effectiveDecision :Permit. }.
149
+ { ?req :decisionStatus :DenyOnly. } => { ?req :effectiveDecision :Deny. }.
150
+ { ?req :decisionStatus :Neither. } => { ?req :effectiveDecision :NotSpecified. }.
151
+
152
+ # Conflict + Work => Deny (+ flag in same rule)
153
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
154
+ ?C log:includes { ?req :decision :Permit. }.
155
+ ?C log:includes { ?req :decision :Deny. }.
156
+ ?C log:includes { ?req :location :Office }.
157
+ } => { ?req :effectiveDecision :Deny. }.
158
+
159
+ # Conflict + Home + ProgrammingWork => Permit (+ flag)
160
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
161
+ ?C log:includes { ?req :decision :Permit. }.
162
+ ?C log:includes { ?req :decision :Deny. }.
163
+ ?C log:includes { ?req :location :Home. }.
164
+ ?C log:includes { ?req :job :ProgrammingWork. }.
165
+ } => { ?req :effectiveDecision :Permit. }.
166
+
167
+ # Conflict that matches neither resolver => Undecided (+ flag)
168
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
169
+ ?C log:includes { ?req :decision :Permit. }.
170
+ ?C log:includes { ?req :decision :Deny. }.
171
+ ?C log:notIncludes { ?req :location :Office. }.
172
+ ?C log:notIncludes { ?req :location :Home. ?req :job :ProgrammingWork. }.
173
+ } => { ?req :effectiveDecision :Undecided. }.
174
+
175
+ # -----------------------------------------------------------------------------
176
+ # 5) Summary: if Home permitted + Work denied => RemoteWorkOnly
177
+ # -----------------------------------------------------------------------------
178
+
179
+ { ?rH :agent ?who; :location :Home; :effectiveDecision :Permit.
180
+ ?rC :agent ?who; :location :Office; :effectiveDecision :Deny.
181
+ } => { ?who :workDecision :RemoteWorkOnly. }.
182
+
@@ -0,0 +1,379 @@
1
+ # =============================================================================
2
+ # This example illustrates “eventual interoperability”:
3
+ # 1) Start with rich, internally coherent source semantics (my:*).
4
+ # 2) Add interoperability later as explicit, reusable alignments when a
5
+ # concrete interaction/business case exists.
6
+ # 3) Alignments can be versioned and swapped without changing the source.
7
+ # 4) Same source can be aligned one-to-many: different partners, different
8
+ # target shapes.
9
+ #
10
+ # The file models a dataset owner publishing a roster using their own rich
11
+ # vocabulary (my:) without forcing early agreement on a shared schema.
12
+ # When two concrete reuse needs appear (exporting to Partner A and Partner B),
13
+ # each integration is handled by introducing an interaction pattern:
14
+ # alignment artefact (mappings) + an expected output shape.
15
+ # The reasoner selects the active alignment for each partner, applies
16
+ # class/property/inverse-property mappings to produce each partner’s expected
17
+ # shape, and then emits “ready” roster views (:PartnerA_Roster, :PartnerB_Roster).
18
+ # In other words: local semantics first, interoperability later, and one dataset
19
+ # can be aligned to multiple consumers without changing the producer’s model.
20
+ #
21
+ # SHACL note:
22
+ # SHACL NodeShapes describe each partner’s expected output shape and act as a
23
+ # consumer-facing “contract” for the aligned view (they can also be validated).
24
+ #
25
+ # Inspired by: https://pietercolpaert.be/interoperability/2026/01/08/eventual-interoperability
26
+ # =============================================================================
27
+
28
+ @prefix my: <http://example.org/my#>.
29
+ @prefix a: <http://example.org/partnerA#>.
30
+ @prefix b: <http://example.org/partnerB#>.
31
+ @prefix align: <http://example.org/align#>.
32
+ @prefix : <http://example.org/demo#>.
33
+
34
+ @prefix list: <http://www.w3.org/2000/10/swap/list#>.
35
+ @prefix math: <http://www.w3.org/2000/10/swap/math#>.
36
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
37
+ @prefix owl: <http://www.w3.org/2002/07/owl#>.
38
+ @prefix sh: <http://www.w3.org/ns/shacl#>.
39
+
40
+ # ----------------------------------------------------------
41
+ # 1) Producer data (rich, local semantics)
42
+ # ----------------------------------------------------------
43
+
44
+ my:Course_KG a my:Course ;
45
+ my:courseCode "KG-101" ;
46
+ my:title "Knowledge Graphs" .
47
+
48
+ my:Student_A a my:Student ;
49
+ my:givenName "Ava" ;
50
+ my:familyName "Ng" ;
51
+ my:displayName "Ava Ng" ;
52
+ my:studentID "S123" ;
53
+ my:enrolledInCourse my:Course_KG ;
54
+ my:enrollmentRole my:TeachingAssistant .
55
+
56
+ my:Student_B a my:Student ;
57
+ my:givenName "Bram" ;
58
+ my:familyName "Peeters" ;
59
+ my:displayName "Bram Peeters" ;
60
+ my:studentID "S124" ;
61
+ my:enrolledInCourse my:Course_KG ;
62
+ my:enrollmentRole my:Student .
63
+
64
+ # ----------------------------------------------------------
65
+ # 2) Interaction patterns (business cases)
66
+ # ----------------------------------------------------------
67
+
68
+ :RosterExportToPartnerA a :InteractionPattern ;
69
+ :enabled true ;
70
+ :usesAlignmentFamily :AlignFamily_PartnerA ;
71
+ # SHACL shapes used by this interaction pattern:
72
+ :usesShape :AStudentShape, :ACourseShape .
73
+
74
+ :RosterExportToPartnerB a :InteractionPattern ;
75
+ :enabled true ;
76
+ :usesAlignmentFamily :AlignFamily_PartnerB ;
77
+ # SHACL shapes used by this interaction pattern:
78
+ :usesShape :BLearnerShape, :BModuleShape .
79
+
80
+ # ----------------------------------------------------------
81
+ # 3) SHACL shapes (descriptions of what partners expect)
82
+ # ----------------------------------------------------------
83
+
84
+ # Partner A expects:
85
+ # - Students have identifier, givenName, familyName
86
+ # - Courses have at least one hasStudent, and those values are Students
87
+
88
+ :AStudentShape a sh:NodeShape ;
89
+ sh:targetClass a:Student ;
90
+ sh:property [
91
+ a sh:PropertyShape ;
92
+ sh:path a:identifier ;
93
+ sh:minCount 1
94
+ ] ;
95
+ sh:property [
96
+ a sh:PropertyShape ;
97
+ sh:path a:givenName ;
98
+ sh:minCount 1
99
+ ] ;
100
+ sh:property [
101
+ a sh:PropertyShape ;
102
+ sh:path a:familyName ;
103
+ sh:minCount 1
104
+ ] .
105
+
106
+ :ACourseShape a sh:NodeShape ;
107
+ sh:targetClass a:Course ;
108
+ sh:property [
109
+ a sh:PropertyShape ;
110
+ sh:path a:hasStudent ;
111
+ sh:minCount 1 ;
112
+ sh:class a:Student
113
+ ] .
114
+
115
+ # Partner B expects:
116
+ # - Learners have learnerId and fullName
117
+ # - Modules have moduleCode and at least one hasLearner (values are Learners)
118
+
119
+ :BLearnerShape a sh:NodeShape ;
120
+ sh:targetClass b:Learner ;
121
+ sh:property [
122
+ a sh:PropertyShape ;
123
+ sh:path b:learnerId ;
124
+ sh:minCount 1
125
+ ] ;
126
+ sh:property [
127
+ a sh:PropertyShape ;
128
+ sh:path b:fullName ;
129
+ sh:minCount 1
130
+ ] .
131
+
132
+ :BModuleShape a sh:NodeShape ;
133
+ sh:targetClass b:Module ;
134
+ sh:property [
135
+ a sh:PropertyShape ;
136
+ sh:path b:moduleCode ;
137
+ sh:minCount 1
138
+ ] ;
139
+ sh:property [
140
+ a sh:PropertyShape ;
141
+ sh:path b:hasLearner ;
142
+ sh:minCount 1 ;
143
+ sh:class b:Learner
144
+ ] .
145
+
146
+ # ----------------------------------------------------------
147
+ # 4) Alignment artefacts (kept separate from producer model)
148
+ # Demonstrates incremental/versioned alignments.
149
+ # ----------------------------------------------------------
150
+
151
+ :Align_A_v1 a :Alignment ; :active false ; :hasFormula {
152
+ # Minimal mapping: only identifier and enrollment link
153
+ my:studentID owl:equivalentProperty a:identifier .
154
+ my:enrolledInCourse owl:inverseOf a:hasStudent .
155
+ my:Student owl:equivalentClass a:Student .
156
+ my:Course owl:equivalentClass a:Course .
157
+ }.
158
+
159
+ :Align_A_v2 a :Alignment ; :active true ; :hasFormula {
160
+ # More complete mapping (later, when the business case is clear)
161
+ my:studentID owl:equivalentProperty a:identifier .
162
+ my:givenName owl:equivalentProperty a:givenName .
163
+ my:familyName owl:equivalentProperty a:familyName .
164
+ my:enrolledInCourse owl:inverseOf a:hasStudent .
165
+ my:Student owl:equivalentClass a:Student .
166
+ my:Course owl:equivalentClass a:Course .
167
+ }.
168
+
169
+ :AlignFamily_PartnerA :hasMember :Align_A_v1, :Align_A_v2.
170
+
171
+ :Align_B_v1 a :Alignment ; :active true ; :hasFormula {
172
+ # Partner B wants a different shape
173
+ my:Student owl:equivalentClass b:Learner .
174
+ my:studentID owl:equivalentProperty b:learnerId .
175
+ my:displayName owl:equivalentProperty b:fullName .
176
+ my:enrolledInCourse owl:inverseOf b:hasLearner .
177
+ my:Course owl:equivalentClass b:Module .
178
+ my:courseCode owl:equivalentProperty b:moduleCode .
179
+ }.
180
+
181
+ :AlignFamily_PartnerB :hasMember :Align_B_v1.
182
+
183
+ # ----------------------------------------------------------
184
+ # 5) Generic “eventual interoperability” engine
185
+ # - pick the active alignment for the interaction pattern
186
+ # - apply vocabulary-level alignments
187
+ # ----------------------------------------------------------
188
+
189
+ # Pick the active alignment for a pattern from its family
190
+ {
191
+ ?pat a :InteractionPattern ;
192
+ :enabled true ;
193
+ :usesAlignmentFamily ?fam .
194
+ ?fam :hasMember ?al .
195
+ ?al :active true .
196
+ } => {
197
+ ?pat :usesAlignment ?al .
198
+ }.
199
+
200
+ # Equivalent class mapping
201
+ {
202
+ ?pat :usesAlignment ?al .
203
+ ?al :hasFormula ?A.
204
+ ?A log:includes { ?c1 owl:equivalentClass ?c2. }.
205
+ ?x a ?c1.
206
+ } => {
207
+ ?x a ?c2.
208
+ ?x :alignedBy ?pat.
209
+ }.
210
+
211
+ # Equivalent property mapping
212
+ {
213
+ ?pat :usesAlignment ?al .
214
+ ?al :hasFormula ?A.
215
+ ?A log:includes { ?p1 owl:equivalentProperty ?p2. }.
216
+ ?s ?p1 ?o.
217
+ } => {
218
+ ?s ?p2 ?o.
219
+ ?s :alignedBy ?pat.
220
+ }.
221
+
222
+ # Inverse property mapping (shape translation)
223
+ {
224
+ ?pat :usesAlignment ?al .
225
+ ?al :hasFormula ?A.
226
+ ?A log:includes { ?p1 owl:inverseOf ?p2. }.
227
+ ?s ?p1 ?o.
228
+ } => {
229
+ ?o ?p2 ?s.
230
+ ?o :alignedBy ?pat.
231
+ }.
232
+
233
+ # ----------------------------------------------------------
234
+ # 6) Interaction-pattern outputs (same checks as original)
235
+ # These rules correspond directly to the SHACL shapes above.
236
+ # ----------------------------------------------------------
237
+
238
+ # Partner A “ready” (corresponds to :AStudentShape + :ACourseShape)
239
+ {
240
+ ?pat a :InteractionPattern ; :enabled true .
241
+ ?pat :usesShape :AStudentShape, :ACourseShape.
242
+
243
+ ?student a a:Student ;
244
+ a:identifier ?id ;
245
+ a:givenName ?gn ;
246
+ a:familyName ?fn .
247
+ ?course a a:Course ;
248
+ a:hasStudent ?student .
249
+ ?student :alignedBy ?pat.
250
+ } => {
251
+ :PartnerA_Roster :fromPattern ?pat ;
252
+ :includesCourse ?course ;
253
+ :includesStudent ?student ;
254
+ :ready true .
255
+ }.
256
+
257
+ # Partner B “ready” (corresponds to :BLearnerShape + :BModuleShape)
258
+ {
259
+ ?pat a :InteractionPattern ; :enabled true .
260
+ ?pat :usesShape :BLearnerShape, :BModuleShape.
261
+
262
+ ?learner a b:Learner ;
263
+ b:learnerId ?id ;
264
+ b:fullName ?name .
265
+ ?module a b:Module ;
266
+ b:moduleCode ?code ;
267
+ b:hasLearner ?learner .
268
+ ?learner :alignedBy ?pat.
269
+ } => {
270
+ :PartnerB_Roster :fromPattern ?pat ;
271
+ :includesModule ?module ;
272
+ :includesLearner ?learner ;
273
+ :ready true .
274
+ }.
275
+
276
+ # ----------------------------------------------------------
277
+ # 7) SHACL validation in N3 (minimal subset)
278
+ # - sh:minCount 1
279
+ # - sh:class
280
+ #
281
+ # Reports are per interaction pattern.
282
+ # ----------------------------------------------------------
283
+
284
+ # Reports (stable IRIs; keeps things simple/deterministic)
285
+ :PartnerA_Report a sh:ValidationReport ;
286
+ :forPattern :RosterExportToPartnerA ;
287
+ :forRoster :PartnerA_Roster .
288
+
289
+ :PartnerB_Report a sh:ValidationReport ;
290
+ :forPattern :RosterExportToPartnerB ;
291
+ :forRoster :PartnerB_Roster .
292
+
293
+ # --------------------------
294
+ # minCount violations
295
+ # --------------------------
296
+
297
+ {
298
+ ?rep a sh:ValidationReport ;
299
+ :forPattern ?pat .
300
+
301
+ # this pattern uses this shape
302
+ ?pat :usesShape ?shape .
303
+
304
+ # shape definition
305
+ ?shape a sh:NodeShape ;
306
+ sh:targetClass ?class ;
307
+ sh:property ?ps .
308
+
309
+ ?ps sh:path ?path ;
310
+ sh:minCount ?min .
311
+
312
+ # focus nodes: nodes aligned for this pattern and of the target class
313
+ ?focus a ?class ;
314
+ :alignedBy ?pat .
315
+ # count values in the closure for (focus,path)
316
+ ( 1 { ?focus ?path ?v. } ?vals ) log:collectAllIn ?SCOPE.
317
+ ?vals list:length ?count .
318
+
319
+ # violation if count < min
320
+ ?count math:lessThan ?min .
321
+ } => {
322
+ ?rep sh:result [
323
+ a sh:ValidationResult ;
324
+ sh:focusNode ?focus ;
325
+ sh:resultPath ?path ;
326
+ sh:sourceShape ?shape ;
327
+ sh:sourceConstraintComponent sh:MinCountConstraintComponent ;
328
+ sh:resultMessage "Missing required property (minCount)."
329
+ ] .
330
+ }.
331
+
332
+ # --------------------------
333
+ # sh:class violations
334
+ # --------------------------
335
+
336
+ {
337
+ ?rep a sh:ValidationReport ;
338
+ :forPattern ?pat .
339
+
340
+ ?pat :usesShape ?shape .
341
+
342
+ ?shape a sh:NodeShape ;
343
+ sh:targetClass ?class ;
344
+ sh:property ?ps .
345
+
346
+ ?ps sh:path ?path ;
347
+ sh:class ?requiredClass .
348
+
349
+ ?focus a ?class ;
350
+ :alignedBy ?pat ;
351
+ ?path ?value .
352
+
353
+ # scoped NAF: value does NOT have the required rdf:type
354
+ ( 1 { ?value a ?requiredClass. } () ) log:collectAllIn ?SCOPE.
355
+ } => {
356
+ ?rep sh:result [
357
+ a sh:ValidationResult ;
358
+ sh:focusNode ?focus ;
359
+ sh:resultPath ?path ;
360
+ sh:sourceShape ?shape ;
361
+ sh:sourceConstraintComponent sh:ClassConstraintComponent ;
362
+ sh:resultMessage "Value does not have required sh:class."
363
+ ] .
364
+ }.
365
+
366
+ # --------------------------
367
+ # sh:conforms
368
+ # --------------------------
369
+
370
+ # If any result exists (in the current graph) -> conforms false
371
+ { ?rep a sh:ValidationReport.
372
+ ?rep sh:result ?r.
373
+ } => { ?rep sh:conforms false. }.
374
+
375
+ # If no result exists (SNAF in current scope) -> conforms true
376
+ { ?rep a sh:ValidationReport.
377
+ (1 { ?rep sh:result ?r. } () ) log:collectAllIn 2.
378
+ } => { ?rep sh:conforms true. }.
379
+
@@ -0,0 +1,242 @@
1
+ # =============================================================================
2
+ # ODRL Policies with scoped NAF via log:conclusion + log:(not)Includes
3
+ #
4
+ # Pattern:
5
+ # - Put "data + rules" in a quoted KB formula
6
+ # - Compute its deductive closure with log:conclusion
7
+ # - Ask questions about what the closure entails using log:includes / log:notIncludes
8
+ #
9
+ # What you get as output:
10
+ # - :decisionStatus in { :PermitOnly, :DenyOnly, :Both, :Neither }
11
+ # - :effectiveDecision applying ODRL conflict strategy
12
+ #
13
+ # ODRL conflict strategies:
14
+ # odrl:perm => Permission overrides Prohibition
15
+ # odrl:prohibit => Prohibition overrides Permission
16
+ # odrl:invalid => Conflicts void the whole policy
17
+ # =============================================================================
18
+
19
+ @prefix : <http://example.org/odrl-paraconsistent#>.
20
+ @prefix odrl: <http://www.w3.org/ns/odrl/2/>.
21
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
22
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
23
+
24
+ # ---------------------------------------------------------------
25
+ # 1) KB formula: ODRL policies + requests + matching rules
26
+ # (kept inside a formula so raw derivations don’t clutter output)
27
+ # ---------------------------------------------------------------
28
+
29
+ :kb :hasFormula {
30
+
31
+ # ----------------------
32
+ # Assets / Parties
33
+ # ----------------------
34
+ :Report a odrl:Asset.
35
+ :Alice a odrl:Party.
36
+ :Bob a odrl:Party.
37
+
38
+ # ----------------------
39
+ # Policy Sets
40
+ # ----------------------
41
+
42
+ # Same rules, different conflict strategy:
43
+ :PS_Prohibit a odrl:Set; odrl:conflict odrl:prohibit.
44
+ :PS_Perm a odrl:Set; odrl:conflict odrl:perm.
45
+ :PS_Invalid a odrl:Set; odrl:conflict odrl:invalid.
46
+
47
+ # Rule templates (blank nodes are fine here)
48
+ # Permission: Alice may print Report for purpose Research
49
+ :PS_Prohibit odrl:permission [
50
+ odrl:target :Report;
51
+ odrl:action odrl:print;
52
+ odrl:assignee :Alice;
53
+ odrl:constraint [
54
+ odrl:leftOperand odrl:purpose;
55
+ odrl:operator odrl:eq;
56
+ odrl:rightOperand :Research
57
+ ]
58
+ ].
59
+
60
+ :PS_Perm odrl:permission [
61
+ odrl:target :Report;
62
+ odrl:action odrl:print;
63
+ odrl:assignee :Alice;
64
+ odrl:constraint [
65
+ odrl:leftOperand odrl:purpose;
66
+ odrl:operator odrl:eq;
67
+ odrl:rightOperand :Research
68
+ ]
69
+ ].
70
+
71
+ :PS_Invalid odrl:permission [
72
+ odrl:target :Report;
73
+ odrl:action odrl:print;
74
+ odrl:assignee :Alice;
75
+ odrl:constraint [
76
+ odrl:leftOperand odrl:purpose;
77
+ odrl:operator odrl:eq;
78
+ odrl:rightOperand :Research
79
+ ]
80
+ ].
81
+
82
+ # Prohibition: Alice is prohibited to print Report (no constraint => always)
83
+ :PS_Prohibit odrl:prohibition [
84
+ odrl:target :Report;
85
+ odrl:action odrl:print;
86
+ odrl:assignee :Alice
87
+ ].
88
+
89
+ :PS_Perm odrl:prohibition [
90
+ odrl:target :Report;
91
+ odrl:action odrl:print;
92
+ odrl:assignee :Alice
93
+ ].
94
+
95
+ :PS_Invalid odrl:prohibition [
96
+ odrl:target :Report;
97
+ odrl:action odrl:print;
98
+ odrl:assignee :Alice
99
+ ].
100
+
101
+ # Extra: Bob may read (permit-only example)
102
+ :PS_Prohibit odrl:permission [
103
+ odrl:target :Report;
104
+ odrl:action odrl:read;
105
+ odrl:assignee :Bob
106
+ ].
107
+
108
+ # Extra: Bob is prohibited to distribute (deny-only example)
109
+ :PS_Prohibit odrl:prohibition [
110
+ odrl:target :Report;
111
+ odrl:action odrl:distribute;
112
+ odrl:assignee :Bob
113
+ ].
114
+
115
+ # ----------------------
116
+ # Requests we want to evaluate
117
+ # ----------------------
118
+
119
+ :Req_Alice_Print_Research_Prohibit
120
+ a :Request;
121
+ :policy :PS_Prohibit;
122
+ :assignee :Alice;
123
+ :action odrl:print;
124
+ :target :Report;
125
+ :purpose :Research.
126
+
127
+ :Req_Alice_Print_Research_Perm
128
+ a :Request;
129
+ :policy :PS_Perm;
130
+ :assignee :Alice;
131
+ :action odrl:print;
132
+ :target :Report;
133
+ :purpose :Research.
134
+
135
+ :Req_Alice_Print_Research_Invalid
136
+ a :Request;
137
+ :policy :PS_Invalid;
138
+ :assignee :Alice;
139
+ :action odrl:print;
140
+ :target :Report;
141
+ :purpose :Research.
142
+
143
+ :Req_Bob_Read
144
+ a :Request;
145
+ :policy :PS_Prohibit;
146
+ :assignee :Bob;
147
+ :action odrl:read;
148
+ :target :Report.
149
+
150
+ :Req_Bob_Distribute
151
+ a :Request;
152
+ :policy :PS_Prohibit;
153
+ :assignee :Bob;
154
+ :action odrl:distribute;
155
+ :target :Report.
156
+
157
+ :Req_Bob_Archive
158
+ a :Request;
159
+ :policy :PS_Prohibit;
160
+ :assignee :Bob;
161
+ :action odrl:archive; # no rules mention this action => Neither
162
+ :target :Report.
163
+
164
+ # ----------------------
165
+ # Decision derivation rules (inside KB)
166
+ # ----------------------
167
+
168
+ # Permission without constraint
169
+ { ?req a :Request; :policy ?ps; :assignee ?who; :action ?act; :target ?asset.
170
+ ?ps odrl:permission ?perm.
171
+ ?perm odrl:assignee ?who; odrl:action ?act; odrl:target ?asset.
172
+ } => { ?req :decision :Permit. }.
173
+
174
+ # Permission with purpose constraint (eq)
175
+ { ?req a :Request; :policy ?ps; :assignee ?who; :action ?act; :target ?asset; :purpose ?purpose.
176
+ ?ps odrl:permission ?perm.
177
+ ?perm odrl:assignee ?who; odrl:action ?act; odrl:target ?asset; odrl:constraint ?c.
178
+ ?c odrl:leftOperand odrl:purpose; odrl:operator odrl:eq; odrl:rightOperand ?purpose.
179
+ } => { ?req :decision :Permit. }.
180
+
181
+ # Prohibition without constraint
182
+ { ?req a :Request; :policy ?ps; :assignee ?who; :action ?act; :target ?asset.
183
+ ?ps odrl:prohibition ?proh.
184
+ ?proh odrl:assignee ?who; odrl:action ?act; odrl:target ?asset.
185
+ } => { ?req :decision :Deny. }.
186
+ }.
187
+
188
+ # ---------------------------------------------------------------
189
+ # 2) Pull a few “queryable” facts out of the closure
190
+ # ---------------------------------------------------------------
191
+
192
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
193
+ ?C log:includes { ?req :policy ?ps. ?ps odrl:conflict ?strategy. }.
194
+ } => { ?req :conflictStrategy ?strategy. }.
195
+
196
+ # ---------------------------------------------------------------
197
+ # 3) DecisionStatus via scoped NAF on the closure
198
+ # ---------------------------------------------------------------
199
+
200
+ # Both (conflict)
201
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
202
+ ?C log:includes { ?req :decision :Permit. }.
203
+ ?C log:includes { ?req :decision :Deny. }.
204
+ } => { ?req :decisionStatus :Both. }.
205
+
206
+ # PermitOnly
207
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
208
+ ?C log:includes { ?req :decision :Permit. }.
209
+ ?C log:notIncludes { ?req :decision :Deny. }.
210
+ } => { ?req :decisionStatus :PermitOnly. }.
211
+
212
+ # DenyOnly
213
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
214
+ ?C log:includes { ?req :decision :Deny. }.
215
+ ?C log:notIncludes { ?req :decision :Permit. }.
216
+ } => { ?req :decisionStatus :DenyOnly. }.
217
+
218
+ # Neither (no applicable rules)
219
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
220
+ ?C log:notIncludes { ?req :decision :Permit. }.
221
+ ?C log:notIncludes { ?req :decision :Deny. }.
222
+ } => { ?req :decisionStatus :Neither. }.
223
+
224
+ # ---------------------------------------------------------------
225
+ # 4) Effective decision applying ODRL conflict strategy
226
+ # ---------------------------------------------------------------
227
+
228
+ # Non-conflicts
229
+ { ?req :decisionStatus :PermitOnly. } => { ?req :effectiveDecision :Permit. }.
230
+ { ?req :decisionStatus :DenyOnly. } => { ?req :effectiveDecision :Deny. }.
231
+ { ?req :decisionStatus :Neither. } => { ?req :effectiveDecision :NotSpecified. }.
232
+
233
+ # Conflicts resolved by strategy + flag in the SAME derivation
234
+ { ?req :decisionStatus :Both. ?req :conflictStrategy odrl:prohibit. }
235
+ => { ?req :effectiveDecision :Deny; :needsReview true. }.
236
+
237
+ { ?req :decisionStatus :Both. ?req :conflictStrategy odrl:perm. }
238
+ => { ?req :effectiveDecision :Permit; :needsReview true. }.
239
+
240
+ { ?req :decisionStatus :Both. ?req :conflictStrategy odrl:invalid. }
241
+ => { ?req :effectiveDecision :VoidPolicy; :needsReview true. }.
242
+
@@ -0,0 +1,13 @@
1
+ @prefix : <http://example.org/#> .
2
+
3
+ :Request_Jos_Prog_Home :agent :Jos .
4
+ :Request_Jos_Prog_Home :job :ProgrammingWork .
5
+ :Request_Jos_Prog_Home :location :Home .
6
+ :Request_Jos_Prog_Office :agent :Jos .
7
+ :Request_Jos_Prog_Office :job :ProgrammingWork .
8
+ :Request_Jos_Prog_Office :location :Office .
9
+ :Request_Jos_Prog_Home :decisionStatus :BothPermitDeny .
10
+ :Request_Jos_Prog_Office :decisionStatus :BothPermitDeny .
11
+ :Request_Jos_Prog_Office :effectiveDecision :Deny .
12
+ :Request_Jos_Prog_Home :effectiveDecision :Permit .
13
+ :Jos :workDecision :RemoteWorkOnly .
@@ -0,0 +1,47 @@
1
+ @prefix : <http://example.org/demo#> .
2
+ @prefix a: <http://example.org/partnerA#> .
3
+ @prefix b: <http://example.org/partnerB#> .
4
+ @prefix my: <http://example.org/my#> .
5
+ @prefix sh: <http://www.w3.org/ns/shacl#> .
6
+
7
+ :RosterExportToPartnerA :usesAlignment :Align_A_v2 .
8
+ :RosterExportToPartnerB :usesAlignment :Align_B_v1 .
9
+ my:Student_A a a:Student .
10
+ my:Student_A :alignedBy :RosterExportToPartnerA .
11
+ my:Student_B a a:Student .
12
+ my:Student_B :alignedBy :RosterExportToPartnerA .
13
+ my:Course_KG a a:Course .
14
+ my:Course_KG :alignedBy :RosterExportToPartnerA .
15
+ my:Student_A a b:Learner .
16
+ my:Student_A :alignedBy :RosterExportToPartnerB .
17
+ my:Student_B a b:Learner .
18
+ my:Student_B :alignedBy :RosterExportToPartnerB .
19
+ my:Course_KG a b:Module .
20
+ my:Course_KG :alignedBy :RosterExportToPartnerB .
21
+ my:Student_A a:identifier "S123" .
22
+ my:Student_B a:identifier "S124" .
23
+ my:Student_A a:givenName "Ava" .
24
+ my:Student_B a:givenName "Bram" .
25
+ my:Student_A a:familyName "Ng" .
26
+ my:Student_B a:familyName "Peeters" .
27
+ my:Student_A b:learnerId "S123" .
28
+ my:Student_B b:learnerId "S124" .
29
+ my:Student_A b:fullName "Ava Ng" .
30
+ my:Student_B b:fullName "Bram Peeters" .
31
+ my:Course_KG b:moduleCode "KG-101" .
32
+ my:Course_KG a:hasStudent my:Student_A .
33
+ my:Course_KG a:hasStudent my:Student_B .
34
+ my:Course_KG b:hasLearner my:Student_A .
35
+ my:Course_KG b:hasLearner my:Student_B .
36
+ :PartnerA_Roster :fromPattern :RosterExportToPartnerA .
37
+ :PartnerA_Roster :includesCourse my:Course_KG .
38
+ :PartnerA_Roster :includesStudent my:Student_A .
39
+ :PartnerA_Roster :ready true .
40
+ :PartnerA_Roster :includesStudent my:Student_B .
41
+ :PartnerB_Roster :fromPattern :RosterExportToPartnerB .
42
+ :PartnerB_Roster :includesModule my:Course_KG .
43
+ :PartnerB_Roster :includesLearner my:Student_A .
44
+ :PartnerB_Roster :ready true .
45
+ :PartnerB_Roster :includesLearner my:Student_B .
46
+ :PartnerA_Report sh:conforms true .
47
+ :PartnerB_Report sh:conforms true .
@@ -0,0 +1,22 @@
1
+ @prefix : <http://example.org/odrl-paraconsistent#> .
2
+ @prefix odrl: <http://www.w3.org/ns/odrl/2/> .
3
+
4
+ :Req_Alice_Print_Research_Prohibit :conflictStrategy odrl:prohibit .
5
+ :Req_Alice_Print_Research_Perm :conflictStrategy odrl:perm .
6
+ :Req_Alice_Print_Research_Invalid :conflictStrategy odrl:invalid .
7
+ :Req_Bob_Read :conflictStrategy odrl:prohibit .
8
+ :Req_Bob_Distribute :conflictStrategy odrl:prohibit .
9
+ :Req_Bob_Archive :conflictStrategy odrl:prohibit .
10
+ :Req_Alice_Print_Research_Prohibit :decisionStatus :Both .
11
+ :Req_Alice_Print_Research_Perm :decisionStatus :Both .
12
+ :Req_Alice_Print_Research_Invalid :decisionStatus :Both .
13
+ :Req_Bob_Read :decisionStatus :PermitOnly .
14
+ :Req_Bob_Distribute :decisionStatus :DenyOnly .
15
+ :Req_Bob_Read :effectiveDecision :Permit .
16
+ :Req_Bob_Distribute :effectiveDecision :Deny .
17
+ :Req_Alice_Print_Research_Prohibit :effectiveDecision :Deny .
18
+ :Req_Alice_Print_Research_Prohibit :needsReview true .
19
+ :Req_Alice_Print_Research_Perm :effectiveDecision :Permit .
20
+ :Req_Alice_Print_Research_Perm :needsReview true .
21
+ :Req_Alice_Print_Research_Invalid :effectiveDecision :VoidPolicy .
22
+ :Req_Alice_Print_Research_Invalid :needsReview true .
@@ -0,0 +1,38 @@
1
+ @prefix : <http://example.org/paraconsistent#> .
2
+
3
+ :tweety :canFlyStatus :Both .
4
+ :mythic :canFlyStatus :Both .
5
+ :falco :canFlyStatus :TrueOnly .
6
+ :batsy :canFlyStatus :TrueOnly .
7
+ :opus :canFlyStatus :FalseOnly .
8
+ :batsy :hasWingsStatus :Both .
9
+ :tweety :hasWingsStatus :TrueOnly .
10
+ :falco :hasWingsStatus :TrueOnly .
11
+ :mythic :hasWingsStatus :TrueOnly .
12
+ :nemo :hasWingsStatus :FalseOnly .
13
+ :tweety :inconsistent :canFly .
14
+ :tweety :needsReview :canFly .
15
+ :mythic :inconsistent :canFly .
16
+ :mythic :needsReview :canFly .
17
+ :batsy :inconsistent :hasWings .
18
+ :batsy :needsReview :hasWings .
19
+ :falco :canFlySafely true .
20
+ :batsy :canFlySafely true .
21
+ :opus :canFlySafely false .
22
+ :tweety :canFlySafely :Undecided .
23
+ :mythic :canFlySafely :Undecided .
24
+ :tweety :hasWingsSafely true .
25
+ :falco :hasWingsSafely true .
26
+ :mythic :hasWingsSafely true .
27
+ :nemo :hasWingsSafely false .
28
+ :batsy :hasWingsSafely :Undecided .
29
+ :falco :movesBySafely :Flying .
30
+ :falco :migratesSafely true .
31
+ :batsy :movesBySafely :Flying .
32
+ :batsy :migratesSafely true .
33
+ :opus :movesBySafely :Walking .
34
+ :opus :migratesSafely false .
35
+ :tweety :movesBySafely :Unknown .
36
+ :tweety :migratesSafely :Undecided .
37
+ :mythic :movesBySafely :Unknown .
38
+ :mythic :migratesSafely :Undecided .
@@ -0,0 +1,5 @@
1
+ @prefix : <http://example.org/demo#> .
2
+ @prefix sh: <http://www.w3.org/ns/shacl#> .
3
+
4
+ :PartnerA_Report sh:conforms true .
5
+ :PartnerB_Report sh:conforms true .
@@ -0,0 +1,109 @@
1
+ # =============================================================================
2
+ # Paraconsistent N3 example
3
+ #
4
+ # What it demonstrates:
5
+ # 1) Contradictions tolerated: we can have both :p true and :p false.
6
+ # 2) Scoped NAF via deductive closure:
7
+ # - compute closure with log:conclusion
8
+ # - check "absence" using log:notIncludes (scoped NAF)
9
+ # - check "presence" using log:includes
10
+ # 3) Only meaningful derivations are materialized:
11
+ # - raw/possibly-contradictory facts are kept inside a quoted KB graph
12
+ # - we only output statuses + "safe" summaries
13
+ # =============================================================================
14
+
15
+ @prefix : <http://example.org/paraconsistent#>.
16
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
17
+
18
+ # ---------------------------------------------------------------
19
+ # 1) Knowledge base (data + domain rules) stored as a graph term
20
+ # (so its internal derivations are not printed as top-level output)
21
+ # ---------------------------------------------------------------
22
+
23
+ :kb :hasFormula {
24
+
25
+ # --- Data (intentionally inconsistent in places) ---
26
+ :tweety a :Bird, :Penguin.
27
+ :falco a :Bird.
28
+ :opus a :Penguin.
29
+ :batsy a :Mammal, :Bat.
30
+ :nemo a :Fish.
31
+
32
+ # explicit observation that conflicts with "Birds fly"
33
+ :mythic a :Bird.
34
+ :mythic :canFly false.
35
+
36
+ # --- Domain rules that may conflict ---
37
+ { ?x a :Bird. } => { ?x :canFly true. ?x :hasWings true. }.
38
+ { ?x a :Penguin. } => { ?x :canFly false. }.
39
+
40
+ { ?x a :Mammal. } => { ?x :hasWings false. }.
41
+ { ?x a :Bat. } => { ?x :hasWings true. ?x :canFly true. }.
42
+
43
+ { ?x a :Fish. } => { ?x :canSwim true. ?x :hasWings false. }.
44
+ }.
45
+
46
+ # ---------------------------------------------------------------
47
+ # 2) Status computation using:
48
+ # ?KB log:conclusion ?C .
49
+ # ?C log:includes / log:notIncludes { ... } .
50
+ #
51
+ # Status values: :TrueOnly / :FalseOnly / :Both
52
+ # ---------------------------------------------------------------
53
+
54
+ # Helper: get the closure once per match (no need to print it)
55
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
56
+ ?C log:includes { ?x :canFly true. }.
57
+ ?C log:includes { ?x :canFly false. }.
58
+ } => { ?x :canFlyStatus :Both. }.
59
+
60
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
61
+ ?C log:includes { ?x :canFly true. }.
62
+ ?C log:notIncludes { ?x :canFly false. }.
63
+ } => { ?x :canFlyStatus :TrueOnly. }.
64
+
65
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
66
+ ?C log:includes { ?x :canFly false. }.
67
+ ?C log:notIncludes { ?x :canFly true. }.
68
+ } => { ?x :canFlyStatus :FalseOnly. }.
69
+
70
+
71
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
72
+ ?C log:includes { ?x :hasWings true. }.
73
+ ?C log:includes { ?x :hasWings false. }.
74
+ } => { ?x :hasWingsStatus :Both. }.
75
+
76
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
77
+ ?C log:includes { ?x :hasWings true. }.
78
+ ?C log:notIncludes { ?x :hasWings false. }.
79
+ } => { ?x :hasWingsStatus :TrueOnly. }.
80
+
81
+ { :kb :hasFormula ?F. ?F log:conclusion ?C.
82
+ ?C log:includes { ?x :hasWings false. }.
83
+ ?C log:notIncludes { ?x :hasWings true. }.
84
+ } => { ?x :hasWingsStatus :FalseOnly. }.
85
+
86
+ # ---------------------------------------------------------------
87
+ # 3) Localize inconsistencies (paraconsistent: flag, don't explode)
88
+ # ---------------------------------------------------------------
89
+
90
+ { ?x :canFlyStatus :Both. } => { ?x :inconsistent :canFly. ?x :needsReview :canFly. }.
91
+ { ?x :hasWingsStatus :Both. } => { ?x :inconsistent :hasWings. ?x :needsReview :hasWings. }.
92
+
93
+ # ---------------------------------------------------------------
94
+ # 4) Meaningful materialized summaries ("safe" single-valued views)
95
+ # ---------------------------------------------------------------
96
+
97
+ { ?x :canFlyStatus :TrueOnly. } => { ?x :canFlySafely true. }.
98
+ { ?x :canFlyStatus :FalseOnly. } => { ?x :canFlySafely false. }.
99
+ { ?x :canFlyStatus :Both. } => { ?x :canFlySafely :Undecided. }.
100
+
101
+ { ?x :hasWingsStatus :TrueOnly. } => { ?x :hasWingsSafely true. }.
102
+ { ?x :hasWingsStatus :FalseOnly. } => { ?x :hasWingsSafely false. }.
103
+ { ?x :hasWingsStatus :Both. } => { ?x :hasWingsSafely :Undecided. }.
104
+
105
+ # Safe behavior derived only from the safe canFlyStatus
106
+ { ?x :canFlyStatus :TrueOnly. } => { ?x :movesBySafely :Flying. ?x :migratesSafely true. }.
107
+ { ?x :canFlyStatus :FalseOnly. } => { ?x :movesBySafely :Walking. ?x :migratesSafely false. }.
108
+ { ?x :canFlyStatus :Both. } => { ?x :movesBySafely :Unknown. ?x :migratesSafely :Undecided. }.
109
+
@@ -0,0 +1,24 @@
1
+ # --------------------------
2
+ # sh:conforms example
3
+ # --------------------------
4
+
5
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
6
+ @prefix sh: <http://www.w3.org/ns/shacl#>.
7
+ @prefix : <http://example.org/demo#>.
8
+
9
+ # If any result exists (in the closure) -> conforms false
10
+ { <https://eyereasoner.github.io/eyeling/examples/eventual-interoperability-interaction-patterns.n3> log:semantics ?formula.
11
+ #<file:///home/jdroo/github.com/eyereasoner/eyeling/examples/eventual-interoperability-interaction-patterns.n3> log:semantics ?formula.
12
+ ?formula log:conclusion ?closure.
13
+ ?closure log:includes { ?rep a sh:ValidationReport. }.
14
+ ?closure log:includes { ?rep sh:result ?r. }.
15
+ } => { ?rep sh:conforms false. }.
16
+
17
+ # If no result exists (in the closure) -> conforms true
18
+ { <https://eyereasoner.github.io/eyeling/examples/eventual-interoperability-interaction-patterns.n3> log:semantics ?formula.
19
+ #<file:///home/jdroo/github.com/eyereasoner/eyeling/examples/eventual-interoperability-interaction-patterns.n3> log:semantics ?formula.
20
+ ?formula log:conclusion ?closure.
21
+ ?closure log:includes { ?rep a sh:ValidationReport. }.
22
+ ?closure log:notIncludes { ?rep sh:result ?r. }.
23
+ } => { ?rep sh:conforms true. }.
24
+
package/eyeling.js CHANGED
@@ -4454,6 +4454,30 @@ function evalCryptoHashBuiltin(g, subst, algo) {
4454
4454
  return s2 !== null ? [s2] : [];
4455
4455
  }
4456
4456
 
4457
+ // ---------------------------------------------------------------------------
4458
+ // log: scoped-closure priority helper
4459
+ // ---------------------------------------------------------------------------
4460
+ // When log:collectAllIn / log:forAllIn are used with an object that is a
4461
+ // natural number literal, that number is treated as a *priority* (closure level).
4462
+ // See the adapted semantics near those builtins.
4463
+ function __logNaturalPriorityFromTerm(t) {
4464
+ const info = parseNumericLiteralInfo(t);
4465
+ if (!info) return null;
4466
+ if (info.dt !== XSD_INTEGER_DT) return null;
4467
+
4468
+ const v = info.value;
4469
+ if (typeof v === 'bigint') {
4470
+ if (v < 0n) return null;
4471
+ if (v > BigInt(Number.MAX_SAFE_INTEGER)) return null;
4472
+ return Number(v);
4473
+ }
4474
+ if (typeof v === 'number') {
4475
+ if (!Number.isInteger(v) || v < 0) return null;
4476
+ return v;
4477
+ }
4478
+ return null;
4479
+ }
4480
+
4457
4481
  // ===========================================================================
4458
4482
  // Builtin evaluation
4459
4483
  // ===========================================================================
@@ -6103,6 +6127,15 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
6103
6127
  const [valueTempl, clauseTerm, listTerm] = g.s.elems;
6104
6128
  if (!(clauseTerm instanceof GraphTerm)) return [];
6105
6129
 
6130
+ // Priority / closure semantics:
6131
+ // - object = GraphTerm: explicit scope, run immediately (no closure gating)
6132
+ // - object = natural number literal N: delay until saturated closure level >= N
6133
+ // * N = 0 => run immediately (use the live fact store)
6134
+ // * N >= 1 => run only when a scoped snapshot exists at closure level >= N
6135
+ // - object = Var: treat as priority 1 and bind it to 1
6136
+ // - any other object: backward-compatible default priority 1
6137
+
6138
+ let outSubst = { ...subst };
6106
6139
  let scopeFacts = null;
6107
6140
  let scopeBackRules = backRules;
6108
6141
 
@@ -6114,15 +6147,40 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
6114
6147
  enumerable: false,
6115
6148
  writable: true,
6116
6149
  });
6150
+ const lvlHere = facts && typeof facts.__scopedClosureLevel === 'number' ? facts.__scopedClosureLevel : 0;
6151
+ Object.defineProperty(scopeFacts, '__scopedClosureLevel', {
6152
+ value: lvlHere,
6153
+ enumerable: false,
6154
+ writable: true,
6155
+ });
6117
6156
  scopeBackRules = [];
6118
6157
  } else {
6119
- scopeFacts = facts.__scopedSnapshot || null;
6120
- if (!scopeFacts) return []; // DELAY until snapshot exists
6158
+ let prio = 1;
6159
+ if (g.o instanceof Var) {
6160
+ // Bind the object var to 1 (default priority)
6161
+ outSubst[g.o.name] = internLiteral('1');
6162
+ prio = 1;
6163
+ } else {
6164
+ const p0 = __logNaturalPriorityFromTerm(g.o);
6165
+ if (p0 !== null) prio = p0;
6166
+ }
6167
+
6168
+ if (prio === 0) {
6169
+ // Immediate: use live closure during the current fixpoint phase.
6170
+ scopeFacts = facts;
6171
+ scopeBackRules = backRules;
6172
+ } else {
6173
+ const snap = facts.__scopedSnapshot || null;
6174
+ const lvl = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
6175
+ if (!snap) return []; // DELAY until snapshot exists
6176
+ if (lvl < prio) return []; // DELAY until saturated closure prio exists
6177
+ scopeFacts = snap;
6178
+ }
6121
6179
  }
6122
6180
 
6123
6181
  // If sols is a blank node succeed without collecting/binding.
6124
6182
  if (listTerm instanceof Blank) {
6125
- return [{ ...subst }];
6183
+ return [outSubst];
6126
6184
  }
6127
6185
 
6128
6186
  const visited2 = [];
@@ -6139,7 +6197,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
6139
6197
  const collected = sols.map((sBody) => applySubstTerm(valueTempl, sBody));
6140
6198
  const collectedList = new ListTerm(collected);
6141
6199
 
6142
- const s2 = unifyTerm(listTerm, collectedList, { ...subst });
6200
+ const s2 = unifyTerm(listTerm, collectedList, outSubst);
6143
6201
  return s2 ? [s2] : [];
6144
6202
  }
6145
6203
 
@@ -6149,6 +6207,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
6149
6207
  const [whereClause, thenClause] = g.s.elems;
6150
6208
  if (!(whereClause instanceof GraphTerm) || !(thenClause instanceof GraphTerm)) return [];
6151
6209
 
6210
+ // See log:collectAllIn above for the priority / closure semantics.
6211
+
6212
+ let outSubst = { ...subst };
6152
6213
  let scopeFacts = null;
6153
6214
  let scopeBackRules = backRules;
6154
6215
 
@@ -6160,10 +6221,33 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
6160
6221
  enumerable: false,
6161
6222
  writable: true,
6162
6223
  });
6224
+ const lvlHere = facts && typeof facts.__scopedClosureLevel === 'number' ? facts.__scopedClosureLevel : 0;
6225
+ Object.defineProperty(scopeFacts, '__scopedClosureLevel', {
6226
+ value: lvlHere,
6227
+ enumerable: false,
6228
+ writable: true,
6229
+ });
6163
6230
  scopeBackRules = [];
6164
6231
  } else {
6165
- scopeFacts = facts.__scopedSnapshot || null;
6166
- if (!scopeFacts) return []; // DELAY until snapshot exists
6232
+ let prio = 1;
6233
+ if (g.o instanceof Var) {
6234
+ outSubst[g.o.name] = internLiteral('1');
6235
+ prio = 1;
6236
+ } else {
6237
+ const p0 = __logNaturalPriorityFromTerm(g.o);
6238
+ if (p0 !== null) prio = p0;
6239
+ }
6240
+
6241
+ if (prio === 0) {
6242
+ scopeFacts = facts;
6243
+ scopeBackRules = backRules;
6244
+ } else {
6245
+ const snap = facts.__scopedSnapshot || null;
6246
+ const lvl = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
6247
+ if (!snap) return []; // DELAY until snapshot exists
6248
+ if (lvl < prio) return []; // DELAY until saturated closure prio exists
6249
+ scopeFacts = snap;
6250
+ }
6167
6251
  }
6168
6252
 
6169
6253
  const visited1 = [];
@@ -6190,7 +6274,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
6190
6274
  );
6191
6275
  if (!sols2.length) return [];
6192
6276
  }
6193
- return [{ ...subst }];
6277
+ return [outSubst];
6194
6278
  }
6195
6279
 
6196
6280
  // log:skolem
@@ -6844,7 +6928,45 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
6844
6928
  backRules.__allForwardRules = forwardRules;
6845
6929
  backRules.__allBackwardRules = backRules;
6846
6930
 
6847
- function setScopedSnapshot(snap) {
6931
+ // Closure level counter used by log:collectAllIn/log:forAllIn priority gating.
6932
+ // Level 0 means "no frozen snapshot" (during Phase A of each outer iteration).
6933
+ let scopedClosureLevel = 0;
6934
+
6935
+ // Scan known rules for the maximum requested closure priority in
6936
+ // log:collectAllIn / log:forAllIn goals.
6937
+ function computeMaxScopedClosurePriorityNeeded() {
6938
+ let maxP = 0;
6939
+ function scanTriple(tr) {
6940
+ if (!(tr && tr.p instanceof Iri)) return;
6941
+ const pv = tr.p.value;
6942
+ if (pv !== LOG_NS + 'collectAllIn' && pv !== LOG_NS + 'forAllIn') return;
6943
+ // Explicit scope graphs are immediate and do not require a closure.
6944
+ if (tr.o instanceof GraphTerm) return;
6945
+ // Variable or non-numeric object => default priority 1 (if used).
6946
+ if (tr.o instanceof Var) {
6947
+ if (maxP < 1) maxP = 1;
6948
+ return;
6949
+ }
6950
+ const p0 = __logNaturalPriorityFromTerm(tr.o);
6951
+ if (p0 !== null) {
6952
+ if (p0 > maxP) maxP = p0;
6953
+ } else {
6954
+ if (maxP < 1) maxP = 1;
6955
+ }
6956
+ }
6957
+
6958
+ for (const r of forwardRules) {
6959
+ for (const tr of r.premise) scanTriple(tr);
6960
+ }
6961
+ for (const r of backRules) {
6962
+ for (const tr of r.premise) scanTriple(tr);
6963
+ }
6964
+ return maxP;
6965
+ }
6966
+
6967
+ let maxScopedClosurePriorityNeeded = computeMaxScopedClosurePriorityNeeded();
6968
+
6969
+ function setScopedSnapshot(snap, level) {
6848
6970
  if (!Object.prototype.hasOwnProperty.call(facts, '__scopedSnapshot')) {
6849
6971
  Object.defineProperty(facts, '__scopedSnapshot', {
6850
6972
  value: snap,
@@ -6855,6 +6977,17 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
6855
6977
  } else {
6856
6978
  facts.__scopedSnapshot = snap;
6857
6979
  }
6980
+
6981
+ if (!Object.prototype.hasOwnProperty.call(facts, '__scopedClosureLevel')) {
6982
+ Object.defineProperty(facts, '__scopedClosureLevel', {
6983
+ value: level,
6984
+ enumerable: false,
6985
+ writable: true,
6986
+ configurable: true,
6987
+ });
6988
+ } else {
6989
+ facts.__scopedClosureLevel = level;
6990
+ }
6858
6991
  }
6859
6992
 
6860
6993
  function makeScopedSnapshot() {
@@ -6866,6 +6999,13 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
6866
6999
  writable: true,
6867
7000
  configurable: true,
6868
7001
  });
7002
+ // Propagate closure level so nested scoped builtins can see it.
7003
+ Object.defineProperty(snap, '__scopedClosureLevel', {
7004
+ value: scopedClosureLevel,
7005
+ enumerable: false,
7006
+ writable: true,
7007
+ configurable: true,
7008
+ });
6869
7009
  return snap;
6870
7010
  }
6871
7011
 
@@ -7045,20 +7185,26 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
7045
7185
 
7046
7186
  while (true) {
7047
7187
  // Phase A: scoped builtins disabled => they “delay” (fail) during saturation
7048
- setScopedSnapshot(null);
7188
+ setScopedSnapshot(null, 0);
7049
7189
  const changedA = runFixpoint();
7050
7190
 
7051
7191
  // Freeze saturated scope
7192
+ scopedClosureLevel += 1;
7052
7193
  const snap = makeScopedSnapshot();
7053
7194
 
7054
7195
  // Phase B: scoped builtins enabled, but they query only `snap`
7055
- setScopedSnapshot(snap);
7196
+ setScopedSnapshot(snap, scopedClosureLevel);
7056
7197
  const changedB = runFixpoint();
7057
7198
 
7058
- if (!changedA && !changedB) break;
7199
+ // Rules may have been added dynamically (rule-producing triples), possibly
7200
+ // introducing higher closure priorities. Keep iterating until we have
7201
+ // reached the maximum requested priority and no further changes occur.
7202
+ maxScopedClosurePriorityNeeded = Math.max(maxScopedClosurePriorityNeeded, computeMaxScopedClosurePriorityNeeded());
7203
+
7204
+ if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
7059
7205
  }
7060
7206
 
7061
- setScopedSnapshot(null);
7207
+ setScopedSnapshot(null, 0);
7062
7208
 
7063
7209
  return derivedForward;
7064
7210
  }
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare module 'eyeling' {
2
+ export function reason(opts:any,input: string): string;
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -17,8 +17,10 @@
17
17
  "bin": {
18
18
  "eyeling": "./eyeling.js"
19
19
  },
20
+ "types": "./index.d.ts",
20
21
  "files": [
21
22
  "index.js",
23
+ "index.d.ts",
22
24
  "eyeling.js",
23
25
  "eyeling-builtins.ttl",
24
26
  "test",