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.
@@ -1,90 +1,47 @@
1
- # ==========================
2
- # RDF Messages input sidecar
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 intentionally plain TriG rather than a parser-level RDF Message Log with
10
- # VERSION "1.2-messages" and MESSAGE delimiters. The RDF Messages draft says an
11
- # RDF Message is an RDF dataset interpreted atomically, that a message stream is
12
- # an ordered sequence of such messages, that messages are not combined by
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
- # To demonstrate those ideas in Eyeling today, the default graph contains
16
- # application-local envelope records (:m001 ... :m003) plus replay order and
17
- # offsets. Each non-empty message payload is placed in its own named graph
18
- # (in:payload001 and in:payload003). The second envelope is an empty heartbeat,
19
- # which is valid because RDF Messages may be empty.
20
- #
21
- # The envelope IRIs are not message identifiers defined by the spec; they are
22
- # application-level records used by the rules. The named payload graphs are the
23
- # datasets/messages being interpreted atomically. The rmsg:localBlankLabel values
24
- # show that the same source-local label "_:b0" can recur in different messages;
25
- # the concrete TriG blank nodes themselves are unique because this sidecar is
26
- # ordinary TriG rather than an RDF Message Log parser resetting blank-node scope.
27
-
28
- @prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-messages#> .
29
- @prefix rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-messages/vocab#> .
30
- @prefix prov: <http://www.w3.org/ns/prov#> .
31
- @prefix sosa: <http://www.w3.org/ns/sosa/> .
32
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
33
- @prefix math: <http://www.w3.org/2000/10/swap/math#> .
34
- @prefix list: <http://www.w3.org/2000/10/swap/list#> .
35
- @prefix log: <http://www.w3.org/2000/10/swap/log#> .
36
- @prefix string: <http://www.w3.org/2000/10/swap/string#> .
37
- @prefix see: <https://example.org/see#> .
38
- @prefix in: <https://example.org/see/input#> .
39
-
40
- :temperatureLog a rmsg:MessageLog .
41
- :temperatureLog rmsg:orderedMessages (:m001 :m002 :m003) .
42
- :temperatureLog rmsg:message :m001 .
43
- :temperatureLog rmsg:message :m002 .
44
- :temperatureLog rmsg:message :m003 .
45
- :temperatureLog rmsg:profile :generatedAtTimeProfile .
46
- :temperatureLog rmsg:retentionPolicy "replayable archive" .
47
-
48
- :m001 a rmsg:MessageEnvelope .
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,7 +3,7 @@
3
3
  # Example from Patrick Hochstenbach
4
4
  # =================================
5
5
 
6
- # expect-exit: 2
6
+ # expect-exit: 65
7
7
 
8
8
  @prefix : <https://eyereasoner.github.io/ns#>.
9
9
  @prefix log: <http://www.w3.org/2000/10/swap/log#>.
@@ -3,12 +3,10 @@
3
3
  ## Source files
4
4
 
5
5
  - [N3 rules](../rdf-message-microgrid.n3)
6
- - [Input TriG](../input/rdf-message-microgrid.trig)
6
+ - [Input RDF Message Log](../input/rdf-message-microgrid.trig)
7
7
 
8
8
  ## Answer
9
- Storm clinic microgrid accepted: 4 RDF Message envelopes were replayed 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.
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 Messages example
12
- The input is a single runnable example split across an N3 rule file and a TriG sidecar. The default graph records stream order, offsets, and envelope metadata. Each non-empty named graph is treated as an atomic message payload, and the fourth message is an empty heartbeat. The rules inspect each payload with log:includes inside its own formula, then combine only the derived conclusions needed for the microgrid decision. This keeps the explanation tied to message boundaries instead of silently flattening the stream into one global graph.
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 TriG](../input/rdf-messages.trig)
6
+ - [Input RDF Message Log](../input/rdf-messages.trig)
7
7
 
8
8
  ## Answer
9
- RDF Message replay archive accepted: 3 explicit message boundaries are preserved. Message :m002 is an empty heartbeat, and the local blank-node label _:b0 is safely reused in separate message envelopes.
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 is a single runnable example split across an N3 rule file and a TriG sidecar. The TriG file uses application-local envelope facts for stream order and replay metadata, while each non-empty named payload graph is treated as an atomic RDF Message dataset. Payloads are inspected with log:includes inside their own formulas, 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 but are contextualized by their message boundaries.
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
- # Motivation
10
- # ----------
11
- # A clinic has lost grid power during a storm. Several small systems continue to
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 RDF Messages draft treats each message as an RDF dataset intended to be
17
- # interpreted atomically. It also treats a message stream as an ordered sequence
18
- # and allows empty messages. This example models those ideas in ordinary TriG/N3:
19
- # the companion TriG file contains application-local envelope facts in 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 rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid/vocab#>.
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 with an offset is an explicit replay boundary. The envelope is
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
- ?Log a rmsg:MessageLog;
45
- rmsg:message ?Message.
46
- ?Message a rmsg:MessageEnvelope;
47
- rmsg:offset ?Offset.
28
+ ?Stream a eymsg:RDFMessageStream;
29
+ eymsg:envelope ?Message.
30
+ ?Message a eymsg:MessageEnvelope;
31
+ eymsg:offset ?Offset.
48
32
  } => {
49
- ?Message rmsg:boundaryExplicit true.
50
- ?Log rmsg:replayContains ?Message.
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 rmsg:MessageEnvelope;
57
- rmsg:payloadKind :heartbeat.
40
+ ?Message a eymsg:MessageEnvelope;
41
+ eymsg:payloadKind eymsg:empty.
58
42
  } => {
59
- ?Message rmsg:emptyMessageAllowed true.
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 rmsg:payloadKind :lifeSafetyLoads;
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 rmsg:criticalWatts ?CriticalWatts.
74
- :clinicMicrogrid rmsg:mustKeep :oxygenConcentrator, :vaccineFridge.
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 rmsg:payloadKind :powerStatus;
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 rmsg:availableWatts ?AvailableWatts.
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 rmsg:payloadKind :flexibleDemand;
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 rmsg:deferrableWatts ?ShedWatts.
102
- :clinicMicrogrid rmsg:mayDefer :evChargers.
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 rmsg:criticalWatts ?CriticalWatts;
109
- rmsg:availableWatts ?AvailableWatts;
110
- rmsg:deferrableWatts ?ShedWatts;
111
- rmsg:mustKeep :oxygenConcentrator, :vaccineFridge;
112
- rmsg:mayDefer :evChargers.
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 rmsg:protectedBudgetWatts ?ProtectedBudget.
117
- :clinicMicrogrid rmsg:resilienceAction :protectClinic.
118
- :clinicMicrogrid rmsg:keeps :oxygenConcentrator, :vaccineFridge.
119
- :clinicMicrogrid rmsg:defers :evChargers.
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
- :stormClinicLog rmsg:orderedMessages ?Messages.
106
+ ?Stream a eymsg:RDFMessageStream;
107
+ eymsg:orderedEnvelopes ?Messages;
108
+ eymsg:firstEnvelope ?M1.
126
109
  ?Messages list:length ?Count.
127
- :m001 rmsg:boundaryExplicit true.
128
- :m002 rmsg:boundaryExplicit true.
129
- :m003 rmsg:boundaryExplicit true.
130
- :m004 rmsg:boundaryExplicit true;
131
- rmsg:emptyMessageAllowed true.
132
- :clinicMicrogrid rmsg:criticalWatts ?CriticalWatts;
133
- rmsg:availableWatts ?AvailableWatts;
134
- rmsg:deferrableWatts ?ShedWatts;
135
- rmsg:protectedBudgetWatts ?ProtectedBudget;
136
- rmsg:resilienceAction :protectClinic.
137
- ("# rdf-message-microgrid\n\n## Source files\n\n- [N3 rules](../rdf-message-microgrid.n3)\n- [Input TriG](../input/rdf-message-microgrid.trig)\n\n## Answer\nStorm clinic microgrid accepted: %d RDF Message envelopes were replayed 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 Messages example\nThe input is a single runnable example split across an N3 rule file and a TriG sidecar. The default graph records stream order, offsets, and envelope metadata. Each non-empty named graph is treated as an atomic message payload, and the fourth message is an empty heartbeat. The rules inspect each payload with log:includes inside its own formula, then combine only the derived conclusions needed for the microgrid decision. This keeps the explanation tied to message boundaries instead of silently flattening the stream into one global graph.\n\nThis 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." ?Count ?CriticalWatts ?AvailableWatts ?ShedWatts ?ProtectedBudget) string:format ?Block.
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
  }.
@@ -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 node labels are
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 Eyeling example demonstrates those ideas in ordinary N3/TriG rather than
18
- # in a parser-level RDF Message Log with VERSION "1.2-messages" and MESSAGE
19
- # delimiters. The companion TriG file therefore has two layers:
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
- # 1. application-local envelope facts in the default graph, such as message
22
- # order, offset, expected result, and the textual blank-node label seen in
23
- # the original stream; and
24
- # 2. one named graph per non-empty message payload, treated as the atomic RDF
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 rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-messages/vocab#>.
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
- # A message with an offset inside a log has an explicit replay boundary.
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
- ?Log a rmsg:MessageLog;
49
- rmsg:message ?Message.
50
- ?Message a rmsg:MessageEnvelope;
51
- rmsg:offset ?Offset.
38
+ ?Stream a eymsg:RDFMessageStream;
39
+ eymsg:envelope ?Envelope.
40
+ ?Envelope a eymsg:MessageEnvelope;
41
+ eymsg:offset ?Offset.
52
42
  } => {
53
- ?Message rmsg:boundaryExplicit true.
54
- ?Log rmsg:replayContains ?Message.
43
+ ?Envelope :boundaryExplicit true.
44
+ ?Stream :replayContains ?Envelope.
55
45
  }.
56
46
 
57
- # The payload is a dataset-like named graph. Inspect it with log:includes instead
58
- # of merging all payload triples into the global graph.
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
- ?Message a rmsg:MessageEnvelope;
61
- rmsg:expectedResult ?Result;
62
- rmsg:payloadGraph ?Payload.
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
- ?Message rmsg:payloadResult ?Result.
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
- # The same local blank-node label may recur in different messages. In this
80
- # plain-TriG sidecar the real blank nodes are unique, and the envelope metadata
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
- ?First a rmsg:MessageEnvelope;
84
- rmsg:localBlankLabel ?Label.
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
- :BlankNodeScope :reusedLabel ?Label.
90
- :BlankNodeScope :isPerMessage true.
66
+ ?Envelope :emptyMessageAllowed true.
67
+ :HeartbeatEvidence :accepted ?Envelope.
91
68
  }.
92
69
 
93
- # Different observations from the same sensor are not a global contradiction:
94
- # by default, message payloads remain separate communication contexts.
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
- ?First a rmsg:MessageEnvelope;
97
- rmsg:payloadResult ?FirstResult.
98
- ?Second a rmsg:MessageEnvelope;
99
- rmsg:payloadResult ?SecondResult.
100
- ?FirstResult math:notEqualTo ?SecondResult.
101
- ?First log:notEqualTo ?Second.
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 validations have
107
- # been derived from the log metadata, ordered message list, payload graphs, and
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
- :temperatureLog rmsg:orderedMessages ?Messages.
94
+ ?Stream a eymsg:RDFMessageStream;
95
+ eymsg:orderedEnvelopes ?Messages;
96
+ eymsg:firstEnvelope ?First.
111
97
  ?Messages list:length ?Count.
112
- :m001 rmsg:boundaryExplicit true;
113
- rmsg:payloadResult ?FirstResult.
114
- :m002 rmsg:boundaryExplicit true;
115
- rmsg:emptyMessageAllowed true.
116
- :m003 rmsg:boundaryExplicit true;
117
- rmsg:payloadResult ?SecondResult.
118
- :BlankNodeScope :reusedLabel ?Label;
119
- :isPerMessage true.
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 TriG](../input/rdf-messages.trig)\n\n## Answer\nRDF Message replay archive accepted: %d explicit message boundaries are preserved. Message :m002 is an empty heartbeat, and the local blank-node label %s is safely reused in separate message envelopes.\n\n## Explanation\nThe input is a single runnable example split across an N3 rule file and a TriG sidecar. The TriG file uses application-local envelope facts for stream order and replay metadata, while each non-empty named payload graph is treated as an atomic RDF Message dataset. Payloads are inspected with log:includes inside their own formulas, 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 but are contextualized by their message boundaries.\n\nThis 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." ?Count ?Label ?FirstResult ?SecondResult) string:format ?Block.
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, :ReplayableMessageLog.
111
+ :rdfMessagesExample :demonstrates :ParserLevelMessageLog, :ExplicitBoundaries, :AtomicMessageContext, :EmptyHeartbeat, :MessageScopedBlankNodes.
125
112
  }.