eyeling 1.24.31 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HANDBOOK.md +2 -2
- package/dist/browser/eyeling.browser.js +26 -7
- package/dist/browser/index.mjs +3 -0
- package/examples/deck/rdf-message-flow.md +273 -0
- package/examples/fuse.n3 +1 -1
- package/examples/input/rdf-message-microgrid.trig +39 -72
- package/examples/input/rdf-messages.trig +41 -84
- package/examples/liar.n3 +1 -1
- package/examples/output/rdf-message-microgrid.md +4 -6
- package/examples/output/rdf-messages.md +3 -5
- package/examples/rdf-message-microgrid.n3 +54 -68
- package/examples/rdf-messages.n3 +63 -76
- package/eyeling.js +26 -7
- package/index.d.ts +3 -0
- package/lib/builtins.js +18 -5
- package/lib/engine.js +7 -2
- package/lib/entry.js +1 -0
- package/package.json +1 -1
- package/test/api.test.js +34 -5
- package/test/package.test.js +2 -2
- package/tools/bundle.js +3 -0
|
@@ -1,90 +1,47 @@
|
|
|
1
|
-
#
|
|
2
|
-
# RDF Messages input
|
|
3
|
-
#
|
|
1
|
+
# ===============================
|
|
2
|
+
# RDF Messages parser-level input
|
|
3
|
+
# ===============================
|
|
4
4
|
#
|
|
5
5
|
# This file is the data half of the runnable example:
|
|
6
6
|
#
|
|
7
7
|
# eyeling -r examples/rdf-messages.n3 examples/input/rdf-messages.trig
|
|
8
8
|
#
|
|
9
|
-
# It is
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# default, and that blank-node labels are scoped to the message.
|
|
9
|
+
# It is a true RDF Message Log. VERSION "1.2-messages" tells Eyeling to parse
|
|
10
|
+
# MESSAGE delimiters as message boundaries before ordinary N3 reasoning starts.
|
|
11
|
+
# Eyeling then materializes an internal eymsg: replay view with ordered envelopes
|
|
12
|
+
# and one payload graph per non-empty message.
|
|
14
13
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
:
|
|
46
|
-
:
|
|
47
|
-
|
|
48
|
-
:
|
|
49
|
-
:m001 rmsg:offset 1 .
|
|
50
|
-
:m001 prov:generatedAtTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
51
|
-
:m001 rmsg:payloadKind :observation .
|
|
52
|
-
:m001 rmsg:localBlankLabel "_:b0" .
|
|
53
|
-
:m001 rmsg:expectedResult 22 .
|
|
54
|
-
:m001 rmsg:payloadGraph in:payload001 .
|
|
55
|
-
|
|
56
|
-
:m002 a rmsg:MessageEnvelope .
|
|
57
|
-
:m002 rmsg:offset 2 .
|
|
58
|
-
:m002 prov:generatedAtTime "2026-05-12T18:22:00Z"^^xsd:dateTime .
|
|
59
|
-
:m002 rmsg:payloadKind :heartbeat .
|
|
60
|
-
|
|
61
|
-
:m003 a rmsg:MessageEnvelope .
|
|
62
|
-
:m003 rmsg:offset 3 .
|
|
63
|
-
:m003 prov:generatedAtTime "2026-05-12T18:25:00Z"^^xsd:dateTime .
|
|
64
|
-
:m003 rmsg:payloadKind :observation .
|
|
65
|
-
:m003 rmsg:localBlankLabel "_:b0" .
|
|
66
|
-
:m003 rmsg:expectedResult 23 .
|
|
67
|
-
:m003 rmsg:payloadGraph in:payload003 .
|
|
68
|
-
|
|
69
|
-
in:payload001 {
|
|
70
|
-
_:m001b0 a sosa:Observation .
|
|
71
|
-
_:m001b0 sosa:madeBySensor :thermometerA .
|
|
72
|
-
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
73
|
-
_:m001b0 sosa:hasSimpleResult 22 .
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
in:payload003 {
|
|
77
|
-
_:m003b0 a sosa:Observation .
|
|
78
|
-
_:m003b0 sosa:madeBySensor :thermometerA .
|
|
79
|
-
_:m003b0 sosa:resultTime "2026-05-12T18:25:00Z"^^xsd:dateTime .
|
|
80
|
-
_:m003b0 sosa:hasSimpleResult 23 .
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
in:metadata {
|
|
84
|
-
in:run a see:InputDataset .
|
|
85
|
-
in:run see:name "rdf_messages" .
|
|
86
|
-
in:run see:title "RDF Messages" .
|
|
87
|
-
in:run see:sourceFile "examples/rdf-messages.n3" .
|
|
88
|
-
in:run see:description "A single Eyeling example split across an N3 rule file and a TriG input sidecar. It models a replayable RDF Message Log with application-level envelopes and named payload graphs so that each payload can be interpreted atomically without merging all message contents into one graph." .
|
|
89
|
-
in:run see:compiler "Eyeling RDF/TriG input sidecar" .
|
|
90
|
-
}
|
|
14
|
+
# The second message is deliberately empty. The first and third messages both use
|
|
15
|
+
# the source-local blank-node label _:b0. Because blank-node labels are scoped to
|
|
16
|
+
# each RDF Message, Eyeling rewrites them into distinct internal blank nodes.
|
|
17
|
+
|
|
18
|
+
VERSION "1.2-messages"
|
|
19
|
+
PREFIX : <https://eyereasoner.github.io/eyeling/examples/rdf-messages#>
|
|
20
|
+
PREFIX sosa: <http://www.w3.org/ns/sosa/>
|
|
21
|
+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
22
|
+
PREFIX see: <https://example.org/see#>
|
|
23
|
+
PREFIX in: <https://example.org/see/input#>
|
|
24
|
+
|
|
25
|
+
# Message 1: first temperature observation plus example metadata.
|
|
26
|
+
_:b0 a sosa:Observation ;
|
|
27
|
+
sosa:madeBySensor :thermometerA ;
|
|
28
|
+
sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime ;
|
|
29
|
+
sosa:hasSimpleResult 22 .
|
|
30
|
+
|
|
31
|
+
in:run a see:InputDataset ;
|
|
32
|
+
see:name "rdf_messages" ;
|
|
33
|
+
see:title "RDF Messages" ;
|
|
34
|
+
see:sourceFile "examples/rdf-messages.n3" ;
|
|
35
|
+
see:description "A true RDF 1.2 Message Log. Eyeling parses VERSION 1.2-messages and MESSAGE delimiters into replayable internal message envelopes." ;
|
|
36
|
+
see:compiler "Eyeling RDF Message Log input" .
|
|
37
|
+
|
|
38
|
+
MESSAGE
|
|
39
|
+
|
|
40
|
+
# Message 2: empty heartbeat.
|
|
41
|
+
MESSAGE
|
|
42
|
+
|
|
43
|
+
# Message 3: second observation. The _:b0 label is intentionally reused here.
|
|
44
|
+
_:b0 a sosa:Observation ;
|
|
45
|
+
sosa:madeBySensor :thermometerA ;
|
|
46
|
+
sosa:resultTime "2026-05-12T18:25:00Z"^^xsd:dateTime ;
|
|
47
|
+
sosa:hasSimpleResult 23 .
|
package/examples/liar.n3
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
## Source files
|
|
4
4
|
|
|
5
5
|
- [N3 rules](../rdf-message-microgrid.n3)
|
|
6
|
-
- [Input
|
|
6
|
+
- [Input RDF Message Log](../input/rdf-message-microgrid.trig)
|
|
7
7
|
|
|
8
8
|
## Answer
|
|
9
|
-
Storm clinic microgrid accepted: 4 RDF
|
|
9
|
+
Storm clinic microgrid accepted: 4 parser-replayed RDF Messages were processed atomically. Critical care needs 620 W, current battery plus solar gives 800 W, and deferring the EV chargers frees 600 W, so the protected budget is 1400 W. The reasoned action is to keep the oxygen concentrator and vaccine fridge online, while deferring EV charging.
|
|
10
10
|
|
|
11
|
-
## Why this is an RDF
|
|
12
|
-
The input
|
|
13
|
-
|
|
14
|
-
This is intentionally not a parser-level VERSION \"1.2-messages\" / MESSAGE delimiter test. It is a reasoning example over an already-materialized sidecar representation of a message log.
|
|
11
|
+
## Why this is an RDF Message Log example
|
|
12
|
+
The input now uses VERSION \"1.2-messages\" and MESSAGE delimiters. Eyeling parses those boundaries internally into an eymsg: replay view, so the rules do not need hand-written application envelopes. Each non-empty message is inspected with log:includes inside its own payload formula, and the final delimiter-only message is replayed as an empty heartbeat. The decision combines only the derived conclusions needed for load shedding while keeping the explanation tied to explicit message boundaries.
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
## Source files
|
|
4
4
|
|
|
5
5
|
- [N3 rules](../rdf-messages.n3)
|
|
6
|
-
- [Input
|
|
6
|
+
- [Input RDF Message Log](../input/rdf-messages.trig)
|
|
7
7
|
|
|
8
8
|
## Answer
|
|
9
|
-
RDF Message
|
|
9
|
+
RDF Message Log accepted: 3 parser-replayed message boundaries are preserved. The middle message is an empty heartbeat, and the same source-local blank-node label is safely reused because Eyeling scopes blank nodes per message.
|
|
10
10
|
|
|
11
11
|
## Explanation
|
|
12
|
-
The input
|
|
13
|
-
|
|
14
|
-
This is intentionally not a parser-level VERSION \"1.2-messages\" / MESSAGE delimiter test. It is a reasoning example over an already-materialized sidecar representation of a message log.
|
|
12
|
+
The input now uses VERSION \"1.2-messages\" and MESSAGE delimiters instead of hand-written application envelope facts. Eyeling parses the log internally into an eymsg: replay view with ordered envelopes and one payload graph per non-empty message. The rules inspect each payload with log:includes inside its own message formula, so the observation data stays inside the message boundary instead of being treated as one global graph. The two temperature results, 22 and 23, are different observations from the same stream and remain contextualized by their message boundaries.
|
|
@@ -6,63 +6,46 @@
|
|
|
6
6
|
#
|
|
7
7
|
# eyeling -r examples/rdf-message-microgrid.n3 examples/input/rdf-message-microgrid.trig
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# send RDF data: one message describes life-safety loads, another describes the
|
|
13
|
-
# current battery and solar situation, another describes flexible demand that may
|
|
9
|
+
# A clinic is running as an islanded microgrid after a storm. Several small
|
|
10
|
+
# systems send RDF data: one message describes life-safety loads, another
|
|
11
|
+
# describes battery and solar status, another describes flexible demand that may
|
|
14
12
|
# be deferred, and a fourth message is an empty heartbeat.
|
|
15
13
|
#
|
|
16
|
-
# The
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# the
|
|
20
|
-
# default graph, and each non-empty payload is placed in its own named graph.
|
|
21
|
-
#
|
|
22
|
-
# The point is not only technical. Atomic messages let a small reasoner make a
|
|
23
|
-
# careful decision under pressure: protect the oxygen concentrator and vaccine
|
|
24
|
-
# fridge, defer the EV chargers, and explain the action from the replayed message
|
|
25
|
-
# boundaries instead of silently merging every payload into one timeless graph.
|
|
26
|
-
#
|
|
27
|
-
# This is intentionally not a parser-level VERSION "1.2-messages" / MESSAGE
|
|
28
|
-
# delimiter test. It is a reasoning example over an already-materialized sidecar
|
|
29
|
-
# representation of a message log.
|
|
14
|
+
# The companion input is now a true parser-level RDF Message Log. Eyeling handles
|
|
15
|
+
# VERSION "1.2-messages" and MESSAGE delimiters internally, then exposes a replay
|
|
16
|
+
# view using eymsg: ordered envelopes and payload graphs. The N3 rules therefore
|
|
17
|
+
# focus on the domain decision rather than recreating log replay by hand.
|
|
30
18
|
|
|
31
19
|
@prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid#>.
|
|
32
|
-
@prefix
|
|
33
|
-
@prefix prov: <http://www.w3.org/ns/prov#>.
|
|
34
|
-
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
|
|
20
|
+
@prefix eymsg: <https://eyereasoner.github.io/eyeling/vocab/message#>.
|
|
35
21
|
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
36
22
|
@prefix list: <http://www.w3.org/2000/10/swap/list#>.
|
|
37
23
|
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
38
24
|
@prefix string: <http://www.w3.org/2000/10/swap/string#>.
|
|
39
25
|
|
|
40
|
-
# Every envelope
|
|
41
|
-
# application-local metadata; the payload graph is the dataset/message being
|
|
42
|
-
# interpreted atomically.
|
|
26
|
+
# Every envelope in the replay view is an explicit parser-recognized boundary.
|
|
43
27
|
{
|
|
44
|
-
?
|
|
45
|
-
|
|
46
|
-
?Message a
|
|
47
|
-
|
|
28
|
+
?Stream a eymsg:RDFMessageStream;
|
|
29
|
+
eymsg:envelope ?Message.
|
|
30
|
+
?Message a eymsg:MessageEnvelope;
|
|
31
|
+
eymsg:offset ?Offset.
|
|
48
32
|
} => {
|
|
49
|
-
?Message
|
|
50
|
-
?
|
|
33
|
+
?Message :boundaryExplicit true.
|
|
34
|
+
?Stream :replayContains ?Message.
|
|
51
35
|
}.
|
|
52
36
|
|
|
53
37
|
# Empty RDF Messages are valid. Here the fourth envelope is a heartbeat that
|
|
54
38
|
# confirms the stream is still alive without adding any payload triples.
|
|
55
39
|
{
|
|
56
|
-
?Message a
|
|
57
|
-
|
|
40
|
+
?Message a eymsg:MessageEnvelope;
|
|
41
|
+
eymsg:payloadKind eymsg:empty.
|
|
58
42
|
} => {
|
|
59
|
-
?Message
|
|
43
|
+
?Message :emptyMessageAllowed true.
|
|
60
44
|
}.
|
|
61
45
|
|
|
62
|
-
# Inspect the life-safety message inside its own payload graph.
|
|
46
|
+
# Inspect the life-safety message inside its own parser-replayed payload graph.
|
|
63
47
|
{
|
|
64
|
-
?Message
|
|
65
|
-
rmsg:payloadGraph ?Payload.
|
|
48
|
+
?Message eymsg:payloadGraph ?Payload.
|
|
66
49
|
?Payload log:nameOf ?PayloadContext.
|
|
67
50
|
?PayloadContext log:includes {
|
|
68
51
|
:oxygenConcentrator :requiresWatts ?Oxygen.
|
|
@@ -70,14 +53,13 @@
|
|
|
70
53
|
}.
|
|
71
54
|
(?Oxygen ?Fridge) math:sum ?CriticalWatts.
|
|
72
55
|
} => {
|
|
73
|
-
:clinicMicrogrid
|
|
74
|
-
:clinicMicrogrid
|
|
56
|
+
:clinicMicrogrid :criticalWatts ?CriticalWatts.
|
|
57
|
+
:clinicMicrogrid :mustKeep :oxygenConcentrator, :vaccineFridge.
|
|
75
58
|
}.
|
|
76
59
|
|
|
77
60
|
# Inspect the power-status message atomically.
|
|
78
61
|
{
|
|
79
|
-
?Message
|
|
80
|
-
rmsg:payloadGraph ?Payload.
|
|
62
|
+
?Message eymsg:payloadGraph ?Payload.
|
|
81
63
|
?Payload log:nameOf ?PayloadContext.
|
|
82
64
|
?PayloadContext log:includes {
|
|
83
65
|
:batteryBank :availableWatts ?BatteryWatts.
|
|
@@ -85,57 +67,61 @@
|
|
|
85
67
|
}.
|
|
86
68
|
(?BatteryWatts ?SolarWatts) math:sum ?AvailableWatts.
|
|
87
69
|
} => {
|
|
88
|
-
:clinicMicrogrid
|
|
70
|
+
:clinicMicrogrid :availableWatts ?AvailableWatts.
|
|
89
71
|
}.
|
|
90
72
|
|
|
91
73
|
# Inspect the flexible-demand message atomically. The EV chargers are useful, but
|
|
92
74
|
# they are safe to defer so life-safety loads keep running.
|
|
93
75
|
{
|
|
94
|
-
?Message
|
|
95
|
-
rmsg:payloadGraph ?Payload.
|
|
76
|
+
?Message eymsg:payloadGraph ?Payload.
|
|
96
77
|
?Payload log:nameOf ?PayloadContext.
|
|
97
78
|
?PayloadContext log:includes {
|
|
98
79
|
:evChargers :shedWatts ?ShedWatts.
|
|
99
80
|
}.
|
|
100
81
|
} => {
|
|
101
|
-
:clinicMicrogrid
|
|
102
|
-
:clinicMicrogrid
|
|
82
|
+
:clinicMicrogrid :deferrableWatts ?ShedWatts.
|
|
83
|
+
:clinicMicrogrid :mayDefer :evChargers.
|
|
103
84
|
}.
|
|
104
85
|
|
|
105
86
|
# The decision combines conclusions from the separate messages, while preserving
|
|
106
87
|
# the evidence that each conclusion came through an explicit message boundary.
|
|
107
88
|
{
|
|
108
|
-
:clinicMicrogrid
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
89
|
+
:clinicMicrogrid :criticalWatts ?CriticalWatts;
|
|
90
|
+
:availableWatts ?AvailableWatts;
|
|
91
|
+
:deferrableWatts ?ShedWatts;
|
|
92
|
+
:mustKeep :oxygenConcentrator, :vaccineFridge;
|
|
93
|
+
:mayDefer :evChargers.
|
|
113
94
|
(?AvailableWatts ?ShedWatts) math:sum ?ProtectedBudget.
|
|
114
95
|
?ProtectedBudget math:greaterThan ?CriticalWatts.
|
|
115
96
|
} => {
|
|
116
|
-
:clinicMicrogrid
|
|
117
|
-
:clinicMicrogrid
|
|
118
|
-
:clinicMicrogrid
|
|
119
|
-
:clinicMicrogrid
|
|
97
|
+
:clinicMicrogrid :protectedBudgetWatts ?ProtectedBudget.
|
|
98
|
+
:clinicMicrogrid :resilienceAction :protectClinic.
|
|
99
|
+
:clinicMicrogrid :keeps :oxygenConcentrator, :vaccineFridge.
|
|
100
|
+
:clinicMicrogrid :defers :evChargers.
|
|
120
101
|
}.
|
|
121
102
|
|
|
122
103
|
# Emit the example report only when the message stream, empty heartbeat, atomic
|
|
123
104
|
# payload inspection, and protection decision have all been derived.
|
|
124
105
|
{
|
|
125
|
-
|
|
106
|
+
?Stream a eymsg:RDFMessageStream;
|
|
107
|
+
eymsg:orderedEnvelopes ?Messages;
|
|
108
|
+
eymsg:firstEnvelope ?M1.
|
|
126
109
|
?Messages list:length ?Count.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
110
|
+
?M1 :boundaryExplicit true;
|
|
111
|
+
eymsg:nextEnvelope ?M2.
|
|
112
|
+
?M2 :boundaryExplicit true;
|
|
113
|
+
eymsg:nextEnvelope ?M3.
|
|
114
|
+
?M3 :boundaryExplicit true;
|
|
115
|
+
eymsg:nextEnvelope ?M4.
|
|
116
|
+
?M4 :boundaryExplicit true;
|
|
117
|
+
:emptyMessageAllowed true.
|
|
118
|
+
:clinicMicrogrid :criticalWatts ?CriticalWatts;
|
|
119
|
+
:availableWatts ?AvailableWatts;
|
|
120
|
+
:deferrableWatts ?ShedWatts;
|
|
121
|
+
:protectedBudgetWatts ?ProtectedBudget;
|
|
122
|
+
:resilienceAction :protectClinic.
|
|
123
|
+
("# rdf-message-microgrid\n\n## Source files\n\n- [N3 rules](../rdf-message-microgrid.n3)\n- [Input RDF Message Log](../input/rdf-message-microgrid.trig)\n\n## Answer\nStorm clinic microgrid accepted: %d parser-replayed RDF Messages were processed atomically. Critical care needs %d W, current battery plus solar gives %d W, and deferring the EV chargers frees %d W, so the protected budget is %d W. The reasoned action is to keep the oxygen concentrator and vaccine fridge online, while deferring EV charging.\n\n## Why this is an RDF Message Log example\nThe input now uses VERSION \\\"1.2-messages\\\" and MESSAGE delimiters. Eyeling parses those boundaries internally into an eymsg: replay view, so the rules do not need hand-written application envelopes. Each non-empty message is inspected with log:includes inside its own payload formula, and the final delimiter-only message is replayed as an empty heartbeat. The decision combines only the derived conclusions needed for load shedding while keeping the explanation tied to explicit message boundaries." ?Count ?CriticalWatts ?AvailableWatts ?ShedWatts ?ProtectedBudget) string:format ?Block.
|
|
138
124
|
} => {
|
|
139
125
|
:rdfMessageMicrogridExample log:outputString ?Block.
|
|
140
|
-
:rdfMessageMicrogridExample :demonstrates :AtomicMessageContext, :EmptyHeartbeat, :ReplayableMessageLog, :ResilientDecisionSupport.
|
|
126
|
+
:rdfMessageMicrogridExample :demonstrates :ParserLevelMessageLog, :AtomicMessageContext, :EmptyHeartbeat, :ReplayableMessageLog, :ResilientDecisionSupport.
|
|
141
127
|
}.
|
package/examples/rdf-messages.n3
CHANGED
|
@@ -11,115 +11,102 @@
|
|
|
11
11
|
# The RDF Messages draft defines an RDF Message as an RDF dataset interpreted
|
|
12
12
|
# atomically, an RDF Message Stream as an ordered sequence of messages, and an
|
|
13
13
|
# RDF Message Log as a replayable record of such a stream. The draft also says
|
|
14
|
-
# that messages should not be combined by default and that blank
|
|
14
|
+
# that messages should not be combined by default and that blank-node labels are
|
|
15
15
|
# scoped to the message in which they occur.
|
|
16
16
|
#
|
|
17
|
-
# This
|
|
18
|
-
#
|
|
19
|
-
# delimiters.
|
|
17
|
+
# This example now uses the parser-level syntax directly: the companion input
|
|
18
|
+
# starts with VERSION "1.2-messages" and separates messages with MESSAGE
|
|
19
|
+
# delimiters. Eyeling handles those delimiters before N3 reasoning starts and
|
|
20
|
+
# exposes an internal eymsg: replay view: a stream resource, ordered envelopes,
|
|
21
|
+
# next-envelope links, payload kind, and one payload graph per non-empty message.
|
|
20
22
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# dataset/message to be inspected.
|
|
26
|
-
#
|
|
27
|
-
# The rules below validate a replay archive with three message envelopes. The
|
|
28
|
-
# middle message is an empty heartbeat. The first and third messages both record
|
|
29
|
-
# the local blank-node label "_:b0"; because this sidecar is normal TriG, the
|
|
30
|
-
# actual TriG blank nodes are unique, while the envelope records show the
|
|
31
|
-
# message-scoped label reuse that a true RDF Message Log parser would preserve
|
|
32
|
-
# by resetting blank-node scope at each MESSAGE delimiter.
|
|
23
|
+
# The rules below validate that replay view. They never hand-model message
|
|
24
|
+
# envelopes in the TriG sidecar. They inspect payload graphs atomically, accept
|
|
25
|
+
# the delimiter-only heartbeat, and check that a reused source-local blank-node
|
|
26
|
+
# label becomes two distinct message-scoped blank nodes.
|
|
33
27
|
|
|
34
28
|
@prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-messages#>.
|
|
35
|
-
@prefix
|
|
36
|
-
@prefix prov: <http://www.w3.org/ns/prov#>.
|
|
29
|
+
@prefix eymsg: <https://eyereasoner.github.io/eyeling/vocab/message#>.
|
|
37
30
|
@prefix sosa: <http://www.w3.org/ns/sosa/>.
|
|
38
|
-
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
|
|
39
31
|
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
40
32
|
@prefix list: <http://www.w3.org/2000/10/swap/list#>.
|
|
41
33
|
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
42
34
|
@prefix string: <http://www.w3.org/2000/10/swap/string#>.
|
|
43
35
|
|
|
44
|
-
#
|
|
45
|
-
# The envelope resource is application-local; the payload graph is the dataset
|
|
46
|
-
# treated as the RDF Message.
|
|
36
|
+
# Every parser-replayed envelope is an explicit message boundary.
|
|
47
37
|
{
|
|
48
|
-
?
|
|
49
|
-
|
|
50
|
-
?
|
|
51
|
-
|
|
38
|
+
?Stream a eymsg:RDFMessageStream;
|
|
39
|
+
eymsg:envelope ?Envelope.
|
|
40
|
+
?Envelope a eymsg:MessageEnvelope;
|
|
41
|
+
eymsg:offset ?Offset.
|
|
52
42
|
} => {
|
|
53
|
-
?
|
|
54
|
-
?
|
|
43
|
+
?Envelope :boundaryExplicit true.
|
|
44
|
+
?Stream :replayContains ?Envelope.
|
|
55
45
|
}.
|
|
56
46
|
|
|
57
|
-
#
|
|
58
|
-
#
|
|
47
|
+
# Payload graphs are inspected with log:includes, so the observations remain
|
|
48
|
+
# inside their message contexts instead of being silently merged.
|
|
59
49
|
{
|
|
60
|
-
?
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
?Envelope a eymsg:MessageEnvelope;
|
|
51
|
+
eymsg:payloadKind eymsg:nonEmpty;
|
|
52
|
+
eymsg:payloadGraph ?Payload.
|
|
63
53
|
?Payload log:nameOf ?PayloadContext.
|
|
64
54
|
?PayloadContext log:includes { ?Observation sosa:hasSimpleResult ?Result. }.
|
|
65
55
|
} => {
|
|
66
|
-
?
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Empty messages are legal RDF Messages. Here the empty second envelope acts as
|
|
70
|
-
# a heartbeat/keep-alive and still occupies a boundary in the replay archive.
|
|
71
|
-
{
|
|
72
|
-
?Message a rmsg:MessageEnvelope;
|
|
73
|
-
rmsg:payloadKind :heartbeat.
|
|
74
|
-
} => {
|
|
75
|
-
?Message rmsg:emptyMessageAllowed true.
|
|
76
|
-
:HeartbeatEvidence :accepted ?Message.
|
|
56
|
+
?Envelope :payloadObservation ?Observation.
|
|
57
|
+
?Envelope :payloadResult ?Result.
|
|
77
58
|
}.
|
|
78
59
|
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
# records the per-message label visible in the source stream.
|
|
60
|
+
# Empty messages are valid RDF Messages. The second envelope is a heartbeat even
|
|
61
|
+
# though it has no payload graph.
|
|
82
62
|
{
|
|
83
|
-
?
|
|
84
|
-
|
|
85
|
-
?Second a rmsg:MessageEnvelope;
|
|
86
|
-
rmsg:localBlankLabel ?Label.
|
|
87
|
-
?First log:notEqualTo ?Second.
|
|
63
|
+
?Envelope a eymsg:MessageEnvelope;
|
|
64
|
+
eymsg:payloadKind eymsg:empty.
|
|
88
65
|
} => {
|
|
89
|
-
|
|
90
|
-
:
|
|
66
|
+
?Envelope :emptyMessageAllowed true.
|
|
67
|
+
:HeartbeatEvidence :accepted ?Envelope.
|
|
91
68
|
}.
|
|
92
69
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
70
|
+
# The input deliberately reuses the same source-local blank-node label in message
|
|
71
|
+
# 1 and message 3. Eyeling rewrites labels per message, so these are distinct
|
|
72
|
+
# blank nodes after replay.
|
|
95
73
|
{
|
|
96
|
-
?
|
|
97
|
-
|
|
98
|
-
?
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
?
|
|
74
|
+
?Stream a eymsg:RDFMessageStream;
|
|
75
|
+
eymsg:firstEnvelope ?First.
|
|
76
|
+
?First eymsg:nextEnvelope ?Second;
|
|
77
|
+
:payloadObservation ?FirstObservation;
|
|
78
|
+
:payloadResult ?FirstResult.
|
|
79
|
+
?Second eymsg:nextEnvelope ?Third;
|
|
80
|
+
:emptyMessageAllowed true.
|
|
81
|
+
?Third :payloadObservation ?ThirdObservation;
|
|
82
|
+
:payloadResult ?ThirdResult.
|
|
83
|
+
?FirstObservation log:notEqualTo ?ThirdObservation.
|
|
84
|
+
?FirstResult math:notEqualTo ?ThirdResult.
|
|
102
85
|
} => {
|
|
86
|
+
:BlankNodeScope :reusedSourceLabelIsMessageScoped true.
|
|
103
87
|
:MessageContext :differentObservationsStayContextual true.
|
|
104
88
|
}.
|
|
105
89
|
|
|
106
|
-
# The Eyeling verdict is emitted only when all message-specific
|
|
107
|
-
# been derived from the
|
|
108
|
-
# built-ins.
|
|
90
|
+
# The Eyeling verdict is emitted only when all parser-replayed message-specific
|
|
91
|
+
# validations have been derived from the stream, ordered envelopes, payload
|
|
92
|
+
# graphs, and built-ins.
|
|
109
93
|
{
|
|
110
|
-
|
|
94
|
+
?Stream a eymsg:RDFMessageStream;
|
|
95
|
+
eymsg:orderedEnvelopes ?Messages;
|
|
96
|
+
eymsg:firstEnvelope ?First.
|
|
111
97
|
?Messages list:length ?Count.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
:
|
|
98
|
+
?First :boundaryExplicit true;
|
|
99
|
+
:payloadResult ?FirstResult;
|
|
100
|
+
eymsg:nextEnvelope ?Second.
|
|
101
|
+
?Second :boundaryExplicit true;
|
|
102
|
+
:emptyMessageAllowed true;
|
|
103
|
+
eymsg:nextEnvelope ?Third.
|
|
104
|
+
?Third :boundaryExplicit true;
|
|
105
|
+
:payloadResult ?SecondResult.
|
|
106
|
+
:BlankNodeScope :reusedSourceLabelIsMessageScoped true.
|
|
120
107
|
:MessageContext :differentObservationsStayContextual true.
|
|
121
|
-
("# rdf-messages\n\n## Source files\n\n- [N3 rules](../rdf-messages.n3)\n- [Input
|
|
108
|
+
("# rdf-messages\n\n## Source files\n\n- [N3 rules](../rdf-messages.n3)\n- [Input RDF Message Log](../input/rdf-messages.trig)\n\n## Answer\nRDF Message Log accepted: %d parser-replayed message boundaries are preserved. The middle message is an empty heartbeat, and the same source-local blank-node label is safely reused because Eyeling scopes blank nodes per message.\n\n## Explanation\nThe input now uses VERSION \\\"1.2-messages\\\" and MESSAGE delimiters instead of hand-written application envelope facts. Eyeling parses the log internally into an eymsg: replay view with ordered envelopes and one payload graph per non-empty message. The rules inspect each payload with log:includes inside its own message formula, so the observation data stays inside the message boundary instead of being treated as one global graph. The two temperature results, %s and %s, are different observations from the same stream and remain contextualized by their message boundaries." ?Count ?FirstResult ?SecondResult) string:format ?Block.
|
|
122
109
|
} => {
|
|
123
110
|
:rdfMessagesExample log:outputString ?Block.
|
|
124
|
-
:rdfMessagesExample :demonstrates :ExplicitBoundaries, :AtomicMessageContext, :EmptyHeartbeat, :MessageScopedBlankNodes
|
|
111
|
+
:rdfMessagesExample :demonstrates :ParserLevelMessageLog, :ExplicitBoundaries, :AtomicMessageContext, :EmptyHeartbeat, :MessageScopedBlankNodes.
|
|
125
112
|
}.
|