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 +1 -1
- package/examples/doctor-advice-work-conflict.n3 +182 -0
- package/examples/eventual-interoperability-interaction-patterns.n3 +379 -0
- package/examples/odrl-policy-evaluation-snaf.n3 +242 -0
- package/examples/output/doctor-advice-work-conflict.n3 +13 -0
- package/examples/output/eventual-interoperability-interaction-patterns.n3 +47 -0
- package/examples/output/odrl-policy-evaluation-snaf.n3 +22 -0
- package/examples/output/paraconsistent-animals.n3 +38 -0
- package/examples/output/shacl-conforms.n3 +5 -0
- package/examples/paraconsistent-animals.n3 +109 -0
- package/examples/shacl-conforms.n3 +24 -0
- package/eyeling.js +158 -12
- package/index.d.ts +3 -0
- package/package.json +3 -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,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,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
|
-
|
|
6120
|
-
if (
|
|
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 [
|
|
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,
|
|
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
|
-
|
|
6166
|
-
if (
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyeling",
|
|
3
|
-
"version": "1.8.
|
|
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",
|