eyeling 1.8.3 → 1.8.5
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 +1 -1
- package/examples/doctor-advice-work-conflict.n3 +182 -0
- package/examples/eventual-interoperability-interaction-patterns.n3 +379 -0
- package/examples/output/doctor-advice-work-conflict.n3 +13 -0
- package/examples/output/eventual-interoperability-interaction-patterns.n3 +47 -0
- package/examples/output/shacl-conforms.n3 +5 -0
- package/examples/shacl-conforms.n3 +24 -0
- package/eyeling.js +157 -12
- package/package.json +1 -1
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,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,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 (do not bind)
|
|
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,39 @@ 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
|
-
|
|
6120
|
-
if (
|
|
6158
|
+
let prio = 1;
|
|
6159
|
+
if (g.o instanceof Var) {
|
|
6160
|
+
// Unbound var: behave as priority 1 (do not bind)
|
|
6161
|
+
prio = 1;
|
|
6162
|
+
} else {
|
|
6163
|
+
const p0 = __logNaturalPriorityFromTerm(g.o);
|
|
6164
|
+
if (p0 !== null) prio = p0;
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
if (prio === 0) {
|
|
6168
|
+
// Immediate: use live closure during the current fixpoint phase.
|
|
6169
|
+
scopeFacts = facts;
|
|
6170
|
+
scopeBackRules = backRules;
|
|
6171
|
+
} else {
|
|
6172
|
+
const snap = facts.__scopedSnapshot || null;
|
|
6173
|
+
const lvl = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
|
|
6174
|
+
if (!snap) return []; // DELAY until snapshot exists
|
|
6175
|
+
if (lvl < prio) return []; // DELAY until saturated closure prio exists
|
|
6176
|
+
scopeFacts = snap;
|
|
6177
|
+
}
|
|
6121
6178
|
}
|
|
6122
6179
|
|
|
6123
6180
|
// If sols is a blank node succeed without collecting/binding.
|
|
6124
6181
|
if (listTerm instanceof Blank) {
|
|
6125
|
-
return [
|
|
6182
|
+
return [outSubst];
|
|
6126
6183
|
}
|
|
6127
6184
|
|
|
6128
6185
|
const visited2 = [];
|
|
@@ -6139,7 +6196,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
6139
6196
|
const collected = sols.map((sBody) => applySubstTerm(valueTempl, sBody));
|
|
6140
6197
|
const collectedList = new ListTerm(collected);
|
|
6141
6198
|
|
|
6142
|
-
const s2 = unifyTerm(listTerm, collectedList,
|
|
6199
|
+
const s2 = unifyTerm(listTerm, collectedList, outSubst);
|
|
6143
6200
|
return s2 ? [s2] : [];
|
|
6144
6201
|
}
|
|
6145
6202
|
|
|
@@ -6149,6 +6206,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
6149
6206
|
const [whereClause, thenClause] = g.s.elems;
|
|
6150
6207
|
if (!(whereClause instanceof GraphTerm) || !(thenClause instanceof GraphTerm)) return [];
|
|
6151
6208
|
|
|
6209
|
+
// See log:collectAllIn above for the priority / closure semantics.
|
|
6210
|
+
|
|
6211
|
+
let outSubst = { ...subst };
|
|
6152
6212
|
let scopeFacts = null;
|
|
6153
6213
|
let scopeBackRules = backRules;
|
|
6154
6214
|
|
|
@@ -6160,10 +6220,33 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
6160
6220
|
enumerable: false,
|
|
6161
6221
|
writable: true,
|
|
6162
6222
|
});
|
|
6223
|
+
const lvlHere = facts && typeof facts.__scopedClosureLevel === 'number' ? facts.__scopedClosureLevel : 0;
|
|
6224
|
+
Object.defineProperty(scopeFacts, '__scopedClosureLevel', {
|
|
6225
|
+
value: lvlHere,
|
|
6226
|
+
enumerable: false,
|
|
6227
|
+
writable: true,
|
|
6228
|
+
});
|
|
6163
6229
|
scopeBackRules = [];
|
|
6164
6230
|
} else {
|
|
6165
|
-
|
|
6166
|
-
if (
|
|
6231
|
+
let prio = 1;
|
|
6232
|
+
if (g.o instanceof Var) {
|
|
6233
|
+
// Unbound var: behave as priority 1 (do not bind)
|
|
6234
|
+
prio = 1;
|
|
6235
|
+
} else {
|
|
6236
|
+
const p0 = __logNaturalPriorityFromTerm(g.o);
|
|
6237
|
+
if (p0 !== null) prio = p0;
|
|
6238
|
+
}
|
|
6239
|
+
|
|
6240
|
+
if (prio === 0) {
|
|
6241
|
+
scopeFacts = facts;
|
|
6242
|
+
scopeBackRules = backRules;
|
|
6243
|
+
} else {
|
|
6244
|
+
const snap = facts.__scopedSnapshot || null;
|
|
6245
|
+
const lvl = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
|
|
6246
|
+
if (!snap) return []; // DELAY until snapshot exists
|
|
6247
|
+
if (lvl < prio) return []; // DELAY until saturated closure prio exists
|
|
6248
|
+
scopeFacts = snap;
|
|
6249
|
+
}
|
|
6167
6250
|
}
|
|
6168
6251
|
|
|
6169
6252
|
const visited1 = [];
|
|
@@ -6190,7 +6273,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
6190
6273
|
);
|
|
6191
6274
|
if (!sols2.length) return [];
|
|
6192
6275
|
}
|
|
6193
|
-
return [
|
|
6276
|
+
return [outSubst];
|
|
6194
6277
|
}
|
|
6195
6278
|
|
|
6196
6279
|
// log:skolem
|
|
@@ -6844,7 +6927,45 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6844
6927
|
backRules.__allForwardRules = forwardRules;
|
|
6845
6928
|
backRules.__allBackwardRules = backRules;
|
|
6846
6929
|
|
|
6847
|
-
|
|
6930
|
+
// Closure level counter used by log:collectAllIn/log:forAllIn priority gating.
|
|
6931
|
+
// Level 0 means "no frozen snapshot" (during Phase A of each outer iteration).
|
|
6932
|
+
let scopedClosureLevel = 0;
|
|
6933
|
+
|
|
6934
|
+
// Scan known rules for the maximum requested closure priority in
|
|
6935
|
+
// log:collectAllIn / log:forAllIn goals.
|
|
6936
|
+
function computeMaxScopedClosurePriorityNeeded() {
|
|
6937
|
+
let maxP = 0;
|
|
6938
|
+
function scanTriple(tr) {
|
|
6939
|
+
if (!(tr && tr.p instanceof Iri)) return;
|
|
6940
|
+
const pv = tr.p.value;
|
|
6941
|
+
if (pv !== LOG_NS + 'collectAllIn' && pv !== LOG_NS + 'forAllIn') return;
|
|
6942
|
+
// Explicit scope graphs are immediate and do not require a closure.
|
|
6943
|
+
if (tr.o instanceof GraphTerm) return;
|
|
6944
|
+
// Variable or non-numeric object => default priority 1 (if used).
|
|
6945
|
+
if (tr.o instanceof Var) {
|
|
6946
|
+
if (maxP < 1) maxP = 1;
|
|
6947
|
+
return;
|
|
6948
|
+
}
|
|
6949
|
+
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
6950
|
+
if (p0 !== null) {
|
|
6951
|
+
if (p0 > maxP) maxP = p0;
|
|
6952
|
+
} else {
|
|
6953
|
+
if (maxP < 1) maxP = 1;
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
|
|
6957
|
+
for (const r of forwardRules) {
|
|
6958
|
+
for (const tr of r.premise) scanTriple(tr);
|
|
6959
|
+
}
|
|
6960
|
+
for (const r of backRules) {
|
|
6961
|
+
for (const tr of r.premise) scanTriple(tr);
|
|
6962
|
+
}
|
|
6963
|
+
return maxP;
|
|
6964
|
+
}
|
|
6965
|
+
|
|
6966
|
+
let maxScopedClosurePriorityNeeded = computeMaxScopedClosurePriorityNeeded();
|
|
6967
|
+
|
|
6968
|
+
function setScopedSnapshot(snap, level) {
|
|
6848
6969
|
if (!Object.prototype.hasOwnProperty.call(facts, '__scopedSnapshot')) {
|
|
6849
6970
|
Object.defineProperty(facts, '__scopedSnapshot', {
|
|
6850
6971
|
value: snap,
|
|
@@ -6855,6 +6976,17 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6855
6976
|
} else {
|
|
6856
6977
|
facts.__scopedSnapshot = snap;
|
|
6857
6978
|
}
|
|
6979
|
+
|
|
6980
|
+
if (!Object.prototype.hasOwnProperty.call(facts, '__scopedClosureLevel')) {
|
|
6981
|
+
Object.defineProperty(facts, '__scopedClosureLevel', {
|
|
6982
|
+
value: level,
|
|
6983
|
+
enumerable: false,
|
|
6984
|
+
writable: true,
|
|
6985
|
+
configurable: true,
|
|
6986
|
+
});
|
|
6987
|
+
} else {
|
|
6988
|
+
facts.__scopedClosureLevel = level;
|
|
6989
|
+
}
|
|
6858
6990
|
}
|
|
6859
6991
|
|
|
6860
6992
|
function makeScopedSnapshot() {
|
|
@@ -6866,6 +6998,13 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6866
6998
|
writable: true,
|
|
6867
6999
|
configurable: true,
|
|
6868
7000
|
});
|
|
7001
|
+
// Propagate closure level so nested scoped builtins can see it.
|
|
7002
|
+
Object.defineProperty(snap, '__scopedClosureLevel', {
|
|
7003
|
+
value: scopedClosureLevel,
|
|
7004
|
+
enumerable: false,
|
|
7005
|
+
writable: true,
|
|
7006
|
+
configurable: true,
|
|
7007
|
+
});
|
|
6869
7008
|
return snap;
|
|
6870
7009
|
}
|
|
6871
7010
|
|
|
@@ -7045,20 +7184,26 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
7045
7184
|
|
|
7046
7185
|
while (true) {
|
|
7047
7186
|
// Phase A: scoped builtins disabled => they “delay” (fail) during saturation
|
|
7048
|
-
setScopedSnapshot(null);
|
|
7187
|
+
setScopedSnapshot(null, 0);
|
|
7049
7188
|
const changedA = runFixpoint();
|
|
7050
7189
|
|
|
7051
7190
|
// Freeze saturated scope
|
|
7191
|
+
scopedClosureLevel += 1;
|
|
7052
7192
|
const snap = makeScopedSnapshot();
|
|
7053
7193
|
|
|
7054
7194
|
// Phase B: scoped builtins enabled, but they query only `snap`
|
|
7055
|
-
setScopedSnapshot(snap);
|
|
7195
|
+
setScopedSnapshot(snap, scopedClosureLevel);
|
|
7056
7196
|
const changedB = runFixpoint();
|
|
7057
7197
|
|
|
7058
|
-
|
|
7198
|
+
// Rules may have been added dynamically (rule-producing triples), possibly
|
|
7199
|
+
// introducing higher closure priorities. Keep iterating until we have
|
|
7200
|
+
// reached the maximum requested priority and no further changes occur.
|
|
7201
|
+
maxScopedClosurePriorityNeeded = Math.max(maxScopedClosurePriorityNeeded, computeMaxScopedClosurePriorityNeeded());
|
|
7202
|
+
|
|
7203
|
+
if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
|
|
7059
7204
|
}
|
|
7060
7205
|
|
|
7061
|
-
setScopedSnapshot(null);
|
|
7206
|
+
setScopedSnapshot(null, 0);
|
|
7062
7207
|
|
|
7063
7208
|
return derivedForward;
|
|
7064
7209
|
}
|