eyeling 1.24.24 → 1.24.26

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,5 +1,32 @@
1
+ # ==========================
2
+ # RDF Messages input sidecar
3
+ # ==========================
4
+ #
5
+ # This file is the data half of the runnable example:
6
+ #
7
+ # eyeling -r examples/rdf-messages.n3 examples/input/rdf-messages.trig
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.
14
+ #
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
+
1
28
  @prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-messages#> .
2
- @prefix msg: <https://w3c-cg.github.io/rsp/spec/messages#> .
29
+ @prefix rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-messages/vocab#> .
3
30
  @prefix prov: <http://www.w3.org/ns/prov#> .
4
31
  @prefix sosa: <http://www.w3.org/ns/sosa/> .
5
32
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@@ -10,46 +37,46 @@
10
37
  @prefix see: <https://example.org/see#> .
11
38
  @prefix in: <https://example.org/see/input#> .
12
39
 
13
- # Formal Eyeling input evidence in RDF 1.2 TriG.
14
- # The generated runner reads this TriG evidence directly.
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 .
15
60
 
16
- :temperatureLog a msg:MessageLog .
17
- :temperatureLog msg:orderedMessages (:m001 :m002 :m003) .
18
- :temperatureLog msg:message :m001 .
19
- :temperatureLog msg:message :m002 .
20
- :temperatureLog msg:message :m003 .
21
- :temperatureLog msg:profile :generatedAtTimeProfile .
22
- :temperatureLog msg:retentionPolicy "replayable archive" .
23
- :m001 a msg:RDFMessage .
24
- :m001 msg:offset 1 .
25
- :m001 prov:generatedAtTime "2026-05-12T18:20:00Z" .
26
- :m001 msg:payloadKind :observation .
27
- :m001 msg:localBlankLabel "_:b0" .
28
- :m001 msg:expectedResult 22 .
29
- :m001 msg:payload in:formula1 .
30
- :m002 a msg:RDFMessage .
31
- :m002 msg:offset 2 .
32
- :m002 prov:generatedAtTime "2026-05-12T18:22:00Z" .
33
- :m002 msg:payloadKind :heartbeat .
34
- :m003 a msg:RDFMessage .
35
- :m003 msg:offset 3 .
36
- :m003 prov:generatedAtTime "2026-05-12T18:25:00Z" .
37
- :m003 msg:payloadKind :observation .
38
- :m003 msg:localBlankLabel "_:b0" .
39
- :m003 msg:expectedResult 23 .
40
- :m003 msg:payload in:formula2 .
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 .
41
68
 
42
- in:formula1 {
69
+ in:payload001 {
43
70
  _:m001b0 a sosa:Observation .
44
71
  _:m001b0 sosa:madeBySensor :thermometerA .
45
- _:m001b0 sosa:resultTime "2026-05-12T18:20:00Z" .
72
+ _:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
46
73
  _:m001b0 sosa:hasSimpleResult 22 .
47
74
  }
48
75
 
49
- in:formula2 {
76
+ in:payload003 {
50
77
  _:m003b0 a sosa:Observation .
51
78
  _:m003b0 sosa:madeBySensor :thermometerA .
52
- _:m003b0 sosa:resultTime "2026-05-12T18:25:00Z" .
79
+ _:m003b0 sosa:resultTime "2026-05-12T18:25:00Z"^^xsd:dateTime .
53
80
  _:m003b0 sosa:hasSimpleResult 23 .
54
81
  }
55
82
 
@@ -57,13 +84,7 @@ in:metadata {
57
84
  in:run a see:InputDataset .
58
85
  in:run see:name "rdf_messages" .
59
86
  in:run see:title "RDF Messages" .
60
- in:run see:sourceFile "examples/n3/rdf_messages.n3" .
61
- in:run see:sourceSHA256 "2ea8b414b92e65531cf384000955ca47811d5b7c779a8d2c9fb007515e745f32" .
62
- in:run see:description "This Eyeling example models the main idea from\nhttps://pietercolpaert.be/papers/eswc2026-rdf-messages/:\na message stream/log is not just one freely mergeable RDF graph. It is an\nordered sequence of RDF Datasets that are interpreted atomically, one message\nat a time. The middle message is deliberately empty to model a heartbeat, and\nthe local blank-node label \"_:b0\" is deliberately reused by two messages to\nshow message-scoped blank nodes." .
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." .
63
89
  in:run see:compiler "Eyeling RDF/TriG input sidecar" .
64
- in:run see:inputFacts 25 .
65
- in:run see:compiledRules 6 .
66
- in:run see:compiledBackwardRules 0 .
67
- in:run see:compiledFuses 0 .
68
- in:run see:compiledQueries 1 .
69
90
  }
@@ -6,7 +6,7 @@
6
6
  - [Input TriG](../input/rdf-message-flow.trig)
7
7
 
8
8
  ## Answer
9
- Continuous RDF Message flow accepted: 5 ordered messages moved through the ingest → validate → interpret → route → sink pipeline. The threshold was 26, so results 21 and 22 were archived, the heartbeat kept the stream alive, and results 28 and 29 were emitted as alerts.
9
+ Continuous RDF Message flow accepted: 5 ordered message envelopes moved through the ingest → validate → interpret → route → sink pipeline. The threshold was 26, so results 21 and 22 were archived, the heartbeat kept the stream alive, and results 28 and 29 were emitted as alerts.
10
10
 
11
11
  ## Explanation
12
- The N3 source starts only :m001 at ingress. Each message must reach :sink before the continuous-flow rule releases its msg:nextMessage. Observation payloads are inspected with log:includes inside each message formula, while the empty heartbeat uses the same envelope and routing stages without a payload. This models messages flowing through a live stream while preserving message boundaries.
12
+ The input is a single runnable example split across an N3 rule file and a TriG sidecar. The TriG file uses example-local envelope facts for stream order and processing state, while each named payload graph is treated as an atomic RDF Message dataset. Only :m001 starts at ingress; each envelope must reach :sink before the continuous-flow rule releases its flow:nextEnvelope. Observation payloads are inspected with log:includes inside their own payload formula, and the empty heartbeat advances without a payload graph. This keeps message boundaries visible to the reasoner instead of merging all payload triples into one global graph.
@@ -0,0 +1,14 @@
1
+ # rdf-message-microgrid
2
+
3
+ ## Source files
4
+
5
+ - [N3 rules](../rdf-message-microgrid.n3)
6
+ - [Input TriG](../input/rdf-message-microgrid.trig)
7
+
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.
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.
@@ -6,7 +6,9 @@
6
6
  - [Input TriG](../input/rdf-messages.trig)
7
7
 
8
8
  ## Answer
9
- RDF Message log 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 messages.
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.
10
10
 
11
11
  ## Explanation
12
- The N3 source models an RDF Message Log as an ordered sequence of RDF Messages. Each non-empty message has a formula-valued payload that is inspected with log:includes, 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.
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.
@@ -1,14 +1,35 @@
1
1
  # ================
2
2
  # RDF Message Flow
3
3
  # ================
4
- # A companion to rdf_messages.n3. This example focuses on a live stream where
5
- # RDF Messages continuously flow through a small processing pipeline. The next
6
- # message is released only after the current message reaches the sink, so the
7
- # stream behaves as an ordered, replayable flow rather than a single merged RDF
8
- # graph.
4
+ #
5
+ # Run as:
6
+ #
7
+ # eyeling -r examples/rdf-message-flow.n3 examples/input/rdf-message-flow.trig
8
+ #
9
+ # Motivation
10
+ # ----------
11
+ # The RDF Messages draft describes an RDF Message as an RDF dataset interpreted
12
+ # atomically, and an RDF Message Stream as an ordered sequence of such messages.
13
+ # It also defines RDF Message Logs with explicit MESSAGE delimiters. This Eyeling
14
+ # example keeps the same message-level discipline while staying in ordinary
15
+ # N3/TriG that Eyeling can reason over today.
16
+ #
17
+ # The companion TriG file therefore separates two layers:
18
+ #
19
+ # 1. example-local envelope facts in the default graph, such as order,
20
+ # processing stage, and payload graph; and
21
+ # 2. one named graph per non-empty message payload, treated here as the RDF
22
+ # dataset/message that must be inspected atomically.
23
+ #
24
+ # The rules below deliberately do not merge all payload graphs into one global
25
+ # graph. Instead, each observation is checked with log:includes inside the named
26
+ # payload formula. The empty heartbeat has no payload graph but still advances
27
+ # through the same pipeline. The next message is released only after the current
28
+ # one reaches the sink, making the example a small back-pressure / flow-control
29
+ # story rather than a batch merge of all input triples.
9
30
 
10
31
  @prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow#>.
11
- @prefix msg: <https://example.org/msg#>.
32
+ @prefix flow: <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow/vocab#>.
12
33
  @prefix prov: <http://www.w3.org/ns/prov#>.
13
34
  @prefix sosa: <http://www.w3.org/ns/sosa/>.
14
35
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@@ -17,126 +38,120 @@
17
38
  @prefix log: <http://www.w3.org/2000/10/swap/log#>.
18
39
  @prefix string: <http://www.w3.org/2000/10/swap/string#>.
19
40
 
20
-
21
- # The stream starts with just the first message at ingress. Later messages are
22
- # not pre-loaded; each one is released by the previous message after sink.
23
-
24
-
25
-
26
- # Empty heartbeat: it flows through the same pipeline and keeps the stream live.
27
-
28
-
29
-
30
- # Stage 1: once a message has entered the stream, validate its envelope.
31
-
32
- { ?Message :atStage :ingest. } => {
33
- ?Message :atStage :validate.
41
+ # Stage 1: once an envelope has entered the stream, validate it.
42
+ { ?Envelope :atStage :ingest. } => {
43
+ ?Envelope :atStage :validate.
34
44
  }.
35
45
 
36
- # Stage 2: validation preserves the message boundary before interpretation.
46
+ # Stage 2: validation records that the envelope keeps an explicit message
47
+ # boundary before the payload is interpreted.
37
48
  {
38
- ?Message :atStage :validate;
39
- a msg:RDFMessage;
40
- msg:offset ?Offset.
49
+ ?Envelope :atStage :validate;
50
+ a flow:MessageEnvelope;
51
+ flow:offset ?Offset.
41
52
  } => {
42
- ?Message msg:boundaryExplicit true.
43
- ?Message :atStage :interpret.
53
+ ?Envelope flow:boundaryExplicit true.
54
+ ?Envelope :atStage :interpret.
44
55
  }.
45
56
 
46
- # Stage 3a: observation payloads are inspected inside their message formula.
57
+ # Stage 3a: observation payloads are inspected inside their own payload graph.
58
+ # This models the RDF Messages idea that messages are separate atomic datasets.
47
59
  {
48
- ?Message :atStage :interpret;
49
- msg:payloadKind :observation;
50
- msg:expectedResult ?Result;
51
- msg:payload ?Payload.
60
+ ?Envelope :atStage :interpret;
61
+ flow:payloadKind :observation;
62
+ flow:expectedResult ?Result;
63
+ flow:payloadGraph ?Payload.
52
64
  ?Payload log:nameOf ?PayloadContext.
53
65
  ?PayloadContext log:includes { ?Observation sosa:hasSimpleResult ?Result. }.
54
66
  } => {
55
- ?Message msg:payloadResult ?Result.
56
- ?Message :atStage :route.
67
+ ?Envelope flow:payloadResult ?Result.
68
+ ?Envelope :atStage :route.
57
69
  }.
58
70
 
59
- # Stage 3b: empty heartbeats have no payload, but still move through the flow.
71
+ # Stage 3b: empty heartbeats contain no quads, but RDF Messages explicitly allow
72
+ # empty messages, so the envelope still moves through the flow.
60
73
  {
61
- ?Message :atStage :interpret;
62
- msg:payloadKind :heartbeat.
74
+ ?Envelope :atStage :interpret;
75
+ flow:payloadKind :heartbeat.
63
76
  } => {
64
- ?Message msg:emptyMessageAllowed true.
65
- ?Message :atStage :route.
77
+ ?Envelope flow:emptyMessageAllowed true.
78
+ ?Envelope :atStage :route.
66
79
  }.
67
80
 
68
81
  # Stage 4a: hot observations are routed to the alert sink.
69
82
  {
70
- ?Message :atStage :route;
71
- msg:payloadResult ?Result.
83
+ ?Envelope :atStage :route;
84
+ flow:payloadResult ?Result.
72
85
  :temperatureFlow :highThreshold ?Threshold.
73
86
  ?Result math:greaterThan ?Threshold.
74
87
  } => {
75
- ?Message :route :alertSink.
76
- ?Message :atStage :sink.
77
- :alertSink :received ?Message.
88
+ ?Envelope :route :alertSink.
89
+ ?Envelope :atStage :sink.
90
+ :alertSink :received ?Envelope.
78
91
  }.
79
92
 
80
93
  # Stage 4b: normal observations are routed to the archive sink.
81
94
  {
82
- ?Message :atStage :route;
83
- msg:payloadResult ?Result.
95
+ ?Envelope :atStage :route;
96
+ flow:payloadResult ?Result.
84
97
  :temperatureFlow :highThreshold ?Threshold.
85
98
  ?Result math:notGreaterThan ?Threshold.
86
99
  } => {
87
- ?Message :route :archiveSink.
88
- ?Message :atStage :sink.
89
- :archiveSink :received ?Message.
100
+ ?Envelope :route :archiveSink.
101
+ ?Envelope :atStage :sink.
102
+ :archiveSink :received ?Envelope.
90
103
  }.
91
104
 
92
105
  # Stage 4c: heartbeats are routed separately from observations.
93
106
  {
94
- ?Message :atStage :route;
95
- msg:emptyMessageAllowed true.
107
+ ?Envelope :atStage :route;
108
+ flow:emptyMessageAllowed true.
96
109
  } => {
97
- ?Message :route :heartbeatSink.
98
- ?Message :atStage :sink.
99
- :heartbeatSink :received ?Message.
110
+ ?Envelope :route :heartbeatSink.
111
+ ?Envelope :atStage :sink.
112
+ :heartbeatSink :received ?Envelope.
100
113
  }.
101
114
 
102
- # Continuous-flow rule: reaching the sink releases the next message into ingress.
115
+ # Continuous-flow rule: reaching the sink releases the next envelope into ingress.
116
+ # This mirrors a consumer-visible ordered stream: later messages are not processed
117
+ # until the earlier message has completed the pipeline.
103
118
  {
104
- ?Message :atStage :sink;
105
- msg:nextMessage ?Next.
106
- ?Next a msg:RDFMessage.
119
+ ?Envelope :atStage :sink;
120
+ flow:nextEnvelope ?Next.
121
+ ?Next a flow:MessageEnvelope.
107
122
  } => {
108
- ?Message :releases ?Next.
123
+ ?Envelope :releases ?Next.
109
124
  ?Next :atStage :ingest.
110
125
  }.
111
126
 
112
- # The Eyeling verdict is emitted only after all five messages have flowed through
113
- # ingest, validation, interpretation, routing, and sink.
127
+ # The Eyeling verdict is emitted only after all five envelopes have flowed
128
+ # through ingest, validation, interpretation, routing, and sink while preserving
129
+ # their message boundaries.
114
130
  {
115
- :temperatureFlow msg:orderedMessages ?Messages;
131
+ :temperatureFlow flow:orderedEnvelopes ?Envelopes;
116
132
  :highThreshold ?Threshold.
117
- ?Messages list:length ?Count.
133
+ ?Envelopes list:length ?Count.
118
134
  :m001 :atStage :sink;
119
- msg:payloadResult ?FirstResult;
135
+ flow:payloadResult ?FirstResult;
120
136
  :route :archiveSink;
121
137
  :releases :m002.
122
138
  :m002 :atStage :sink;
123
- msg:payloadResult ?SecondResult;
139
+ flow:payloadResult ?SecondResult;
124
140
  :route :archiveSink;
125
141
  :releases :m003.
126
142
  :m003 :atStage :sink;
127
- msg:emptyMessageAllowed true;
143
+ flow:emptyMessageAllowed true;
128
144
  :route :heartbeatSink;
129
145
  :releases :m004.
130
146
  :m004 :atStage :sink;
131
- msg:payloadResult ?FourthResult;
147
+ flow:payloadResult ?FourthResult;
132
148
  :route :alertSink;
133
149
  :releases :m005.
134
150
  :m005 :atStage :sink;
135
- msg:payloadResult ?FifthResult;
151
+ flow:payloadResult ?FifthResult;
136
152
  :route :alertSink.
137
- ("# rdf-message-flow\n\n## Source files\n\n- [N3 rules](../rdf-message-flow.n3)\n- [Input TriG](../input/rdf-message-flow.trig)\n\n## Answer\nContinuous RDF Message flow accepted: %d ordered messages moved through the ingest → validate → interpret → route → sink pipeline. The threshold was %d, so results %s and %s were archived, the heartbeat kept the stream alive, and results %s and %s were emitted as alerts.\n\n## Explanation\nThe N3 source starts only :m001 at ingress. Each message must reach :sink before the continuous-flow rule releases its msg:nextMessage. Observation payloads are inspected with log:includes inside each message formula, while the empty heartbeat uses the same envelope and routing stages without a payload. This models messages flowing through a live stream while preserving message boundaries." ?Count ?Threshold ?FirstResult ?SecondResult ?FourthResult ?FifthResult) string:format ?Block.
153
+ ("# rdf-message-flow\n\n## Source files\n\n- [N3 rules](../rdf-message-flow.n3)\n- [Input TriG](../input/rdf-message-flow.trig)\n\n## Answer\nContinuous RDF Message flow accepted: %d ordered message envelopes moved through the ingest → validate → interpret → route → sink pipeline. The threshold was %d, so results %s and %s were archived, the heartbeat kept the stream alive, and results %s and %s were emitted as alerts.\n\n## Explanation\nThe input is a single runnable example split across an N3 rule file and a TriG sidecar. The TriG file uses example-local envelope facts for stream order and processing state, while each named payload graph is treated as an atomic RDF Message dataset. Only :m001 starts at ingress; each envelope must reach :sink before the continuous-flow rule releases its flow:nextEnvelope. Observation payloads are inspected with log:includes inside their own payload formula, and the empty heartbeat advances without a payload graph. This keeps message boundaries visible to the reasoner instead of merging all payload triples into one global graph." ?Count ?Threshold ?FirstResult ?SecondResult ?FourthResult ?FifthResult) string:format ?Block.
138
154
  } => {
139
155
  :rdfMessageFlowExample log:outputString ?Block.
140
156
  :rdfMessageFlowExample :demonstrates :ContinuousFlow, :BackPressureRelease, :AtomicMessageContext, :HeartbeatInFlow, :ThresholdRouting.
141
157
  }.
142
-
@@ -0,0 +1,141 @@
1
+ # =====================
2
+ # RDF Message Microgrid
3
+ # =====================
4
+ #
5
+ # Run as:
6
+ #
7
+ # eyeling -r examples/rdf-message-microgrid.n3 examples/input/rdf-message-microgrid.trig
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
14
+ # be deferred, and a fourth message is an empty heartbeat.
15
+ #
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.
30
+
31
+ @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#>.
35
+ @prefix math: <http://www.w3.org/2000/10/swap/math#>.
36
+ @prefix list: <http://www.w3.org/2000/10/swap/list#>.
37
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
38
+ @prefix string: <http://www.w3.org/2000/10/swap/string#>.
39
+
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.
43
+ {
44
+ ?Log a rmsg:MessageLog;
45
+ rmsg:message ?Message.
46
+ ?Message a rmsg:MessageEnvelope;
47
+ rmsg:offset ?Offset.
48
+ } => {
49
+ ?Message rmsg:boundaryExplicit true.
50
+ ?Log rmsg:replayContains ?Message.
51
+ }.
52
+
53
+ # Empty RDF Messages are valid. Here the fourth envelope is a heartbeat that
54
+ # confirms the stream is still alive without adding any payload triples.
55
+ {
56
+ ?Message a rmsg:MessageEnvelope;
57
+ rmsg:payloadKind :heartbeat.
58
+ } => {
59
+ ?Message rmsg:emptyMessageAllowed true.
60
+ }.
61
+
62
+ # Inspect the life-safety message inside its own payload graph.
63
+ {
64
+ ?Message rmsg:payloadKind :lifeSafetyLoads;
65
+ rmsg:payloadGraph ?Payload.
66
+ ?Payload log:nameOf ?PayloadContext.
67
+ ?PayloadContext log:includes {
68
+ :oxygenConcentrator :requiresWatts ?Oxygen.
69
+ :vaccineFridge :requiresWatts ?Fridge.
70
+ }.
71
+ (?Oxygen ?Fridge) math:sum ?CriticalWatts.
72
+ } => {
73
+ :clinicMicrogrid rmsg:criticalWatts ?CriticalWatts.
74
+ :clinicMicrogrid rmsg:mustKeep :oxygenConcentrator, :vaccineFridge.
75
+ }.
76
+
77
+ # Inspect the power-status message atomically.
78
+ {
79
+ ?Message rmsg:payloadKind :powerStatus;
80
+ rmsg:payloadGraph ?Payload.
81
+ ?Payload log:nameOf ?PayloadContext.
82
+ ?PayloadContext log:includes {
83
+ :batteryBank :availableWatts ?BatteryWatts.
84
+ :solarForecast :expectedWatts ?SolarWatts.
85
+ }.
86
+ (?BatteryWatts ?SolarWatts) math:sum ?AvailableWatts.
87
+ } => {
88
+ :clinicMicrogrid rmsg:availableWatts ?AvailableWatts.
89
+ }.
90
+
91
+ # Inspect the flexible-demand message atomically. The EV chargers are useful, but
92
+ # they are safe to defer so life-safety loads keep running.
93
+ {
94
+ ?Message rmsg:payloadKind :flexibleDemand;
95
+ rmsg:payloadGraph ?Payload.
96
+ ?Payload log:nameOf ?PayloadContext.
97
+ ?PayloadContext log:includes {
98
+ :evChargers :shedWatts ?ShedWatts.
99
+ }.
100
+ } => {
101
+ :clinicMicrogrid rmsg:deferrableWatts ?ShedWatts.
102
+ :clinicMicrogrid rmsg:mayDefer :evChargers.
103
+ }.
104
+
105
+ # The decision combines conclusions from the separate messages, while preserving
106
+ # the evidence that each conclusion came through an explicit message boundary.
107
+ {
108
+ :clinicMicrogrid rmsg:criticalWatts ?CriticalWatts;
109
+ rmsg:availableWatts ?AvailableWatts;
110
+ rmsg:deferrableWatts ?ShedWatts;
111
+ rmsg:mustKeep :oxygenConcentrator, :vaccineFridge;
112
+ rmsg:mayDefer :evChargers.
113
+ (?AvailableWatts ?ShedWatts) math:sum ?ProtectedBudget.
114
+ ?ProtectedBudget math:greaterThan ?CriticalWatts.
115
+ } => {
116
+ :clinicMicrogrid rmsg:protectedBudgetWatts ?ProtectedBudget.
117
+ :clinicMicrogrid rmsg:resilienceAction :protectClinic.
118
+ :clinicMicrogrid rmsg:keeps :oxygenConcentrator, :vaccineFridge.
119
+ :clinicMicrogrid rmsg:defers :evChargers.
120
+ }.
121
+
122
+ # Emit the example report only when the message stream, empty heartbeat, atomic
123
+ # payload inspection, and protection decision have all been derived.
124
+ {
125
+ :stormClinicLog rmsg:orderedMessages ?Messages.
126
+ ?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.
138
+ } => {
139
+ :rdfMessageMicrogridExample log:outputString ?Block.
140
+ :rdfMessageMicrogridExample :demonstrates :AtomicMessageContext, :EmptyHeartbeat, :ReplayableMessageLog, :ResilientDecisionSupport.
141
+ }.