eyeling 1.24.25 → 1.24.27

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 CHANGED
@@ -3,6 +3,8 @@
3
3
  [![npm version](https://img.shields.io/npm/v/eyeling.svg)](https://www.npmjs.com/package/eyeling)
4
4
  [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.19068086-blue.svg)](https://doi.org/10.5281/zenodo.19068086)
5
5
 
6
+ ![Eyeling](eyeling.png)
7
+
6
8
  A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
7
9
 
8
10
  ## Quick start
@@ -844,6 +844,62 @@ function compileSwapRegex(pattern, extraFlags) {
844
844
  }
845
845
  }
846
846
 
847
+ function expandSwapRegexReplacement(template, captures) {
848
+ // SWAP/N3 examples use $1-style capture references, but tests also rely on
849
+ // Perl-ish escaping in replacement strings: \\$ means a literal dollar sign
850
+ // and \\ means a literal backslash. JavaScript's replacement strings do not
851
+ // interpret those escapes, so expand the replacement explicitly in a callback.
852
+ let out = '';
853
+ const text = String(template);
854
+
855
+ for (let i = 0; i < text.length; i++) {
856
+ const ch = text[i];
857
+
858
+ if (ch === '\\') {
859
+ if (i + 1 < text.length && (text[i + 1] === '$' || text[i + 1] === '\\')) {
860
+ out += text[++i];
861
+ } else {
862
+ out += ch;
863
+ }
864
+ continue;
865
+ }
866
+
867
+ if (ch === '$') {
868
+ if (i + 1 < text.length && text[i + 1] === '$') {
869
+ out += '$';
870
+ i++;
871
+ continue;
872
+ }
873
+
874
+ let j = i + 1;
875
+ while (j < text.length && /[0-9]/.test(text[j])) j++;
876
+ if (j > i + 1) {
877
+ const digits = text.slice(i + 1, j);
878
+ let chosen = null;
879
+ for (let k = digits.length; k > 0; k--) {
880
+ const n = Number(digits.slice(0, k));
881
+ if (n > 0 && n < captures.length) {
882
+ chosen = { n, len: k };
883
+ break;
884
+ }
885
+ }
886
+ if (chosen) {
887
+ out += captures[chosen.n] == null ? '' : String(captures[chosen.n]);
888
+ out += digits.slice(chosen.len);
889
+ } else {
890
+ out += '$' + digits;
891
+ }
892
+ i = j - 1;
893
+ continue;
894
+ }
895
+ }
896
+
897
+ out += ch;
898
+ }
899
+
900
+ return out;
901
+ }
902
+
847
903
  // -----------------------------------------------------------------------------
848
904
  // Strict numeric literal parsing for math: builtins
849
905
  // -----------------------------------------------------------------------------
@@ -1699,7 +1755,7 @@ function __pushBuiltinDeltaLimited(out, delta, maxResults) {
1699
1755
 
1700
1756
  function __collectListLikeTermsFromTerm(t, out, seen) {
1701
1757
  if (t instanceof ListTerm || t instanceof OpenListTerm) {
1702
- const k = termFastKey ? termFastKey(t) : null;
1758
+ const k = termFastKey(t);
1703
1759
  if (k === null || !seen.has(k)) {
1704
1760
  if (k !== null) seen.add(k);
1705
1761
  out.push(t);
@@ -4222,7 +4278,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
4222
4278
  const re = compileSwapRegex(searchStr, 'g');
4223
4279
  if (!re) return [];
4224
4280
 
4225
- const outStr = dataStr.replace(re, replStr);
4281
+ const outStr = dataStr.replace(re, (...args) => {
4282
+ const captureEnd = args.length - (typeof args.at(-1) === 'object' ? 3 : 2);
4283
+ const captures = args.slice(0, Math.max(1, captureEnd));
4284
+ return expandSwapRegexReplacement(replStr, captures);
4285
+ });
4226
4286
  const lit = makeStringLiteral(outStr);
4227
4287
 
4228
4288
  if (g.o instanceof Var) {
@@ -11498,6 +11558,20 @@ class Parser {
11498
11558
  if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
11499
11559
  this.next();
11500
11560
  pred = internIri(RDF_NS + 'type');
11561
+ } else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'has') {
11562
+ // N3 syntactic sugar is also valid in predicate-object lists,
11563
+ // including blank node property lists: [ has :p :o ] means _:b :p :o.
11564
+ this.next();
11565
+ pred = this.parseTerm();
11566
+ } else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'is') {
11567
+ // N3 syntactic sugar: [ is :p of :s ] means :s :p _:b.
11568
+ this.next();
11569
+ pred = this.parseTerm();
11570
+ if (!(this.peek().typ === 'Ident' && (this.peek().value || '') === 'of')) {
11571
+ this.fail(`Expected 'of' after 'is <expr>', got ${this.peek().toString()}`);
11572
+ }
11573
+ this.next();
11574
+ invert = true;
11501
11575
  } else if (this.peek().typ === 'OpPredInvert') {
11502
11576
  this.next();
11503
11577
  pred = this.parseTerm();
@@ -1,5 +1,29 @@
1
+ # ==============================
2
+ # RDF Message Flow input sidecar
3
+ # ==============================
4
+ #
5
+ # This file is the data half of the runnable example:
6
+ #
7
+ # eyeling -r examples/rdf-message-flow.n3 examples/input/rdf-message-flow.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 stream is an
12
+ # ordered sequence of such messages, and that messages in a stream should not be
13
+ # combined by default. To demonstrate those ideas in Eyeling today, the default
14
+ # graph below contains example-local envelope records (:m001 ... :m005) and each
15
+ # non-empty message payload is placed in its own named graph (in:payload001 ...
16
+ # in:payload005).
17
+ #
18
+ # The envelope IRIs are not identifiers for RDF Messages defined by the spec;
19
+ # they are application-level records used by the flow-control rules. The named
20
+ # payload graphs are the datasets/messages being interpreted atomically. The
21
+ # third envelope is an empty heartbeat, which is valid because RDF Messages may
22
+ # be empty. Blank node labels are kept unique because this sidecar is ordinary
23
+ # TriG; a true RDF Message Log parser would reset blank-node scope per MESSAGE.
24
+
1
25
  @prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow#> .
2
- @prefix msg: <https://example.org/msg#> .
26
+ @prefix flow: <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow/vocab#> .
3
27
  @prefix prov: <http://www.w3.org/ns/prov#> .
4
28
  @prefix sosa: <http://www.w3.org/ns/sosa/> .
5
29
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@@ -10,77 +34,87 @@
10
34
  @prefix see: <https://example.org/see#> .
11
35
  @prefix in: <https://example.org/see/input#> .
12
36
 
13
- # Formal Eyeling input evidence in RDF 1.2 TriG.
14
- # The generated runner reads this TriG evidence directly.
15
-
16
- :temperatureFlow a msg:MessageStream .
17
- :temperatureFlow msg:orderedMessages (:m001 :m002 :m003 :m004 :m005) .
18
- :temperatureFlow msg:message :m001 .
19
- :temperatureFlow msg:message :m002 .
20
- :temperatureFlow msg:message :m003 .
21
- :temperatureFlow msg:message :m004 .
22
- :temperatureFlow msg:message :m005 .
37
+ :temperatureFlow a flow:RDFMessageStream .
38
+ :temperatureFlow flow:orderedEnvelopes (:m001 :m002 :m003 :m004 :m005) .
39
+ :temperatureFlow flow:envelope :m001 .
40
+ :temperatureFlow flow:envelope :m002 .
41
+ :temperatureFlow flow:envelope :m003 .
42
+ :temperatureFlow flow:envelope :m004 .
43
+ :temperatureFlow flow:envelope :m005 .
44
+ :temperatureFlow :producer :thermometerA .
45
+ :temperatureFlow :consumer :flowProcessor .
23
46
  :temperatureFlow :pipeline (:ingest :validate :interpret :route :sink) .
24
47
  :temperatureFlow :highThreshold 26 .
48
+
49
+ :thermometerA a flow:StreamProducer .
50
+ :flowProcessor a flow:StreamConsumer .
51
+ :archiveSink a flow:StreamConsumer .
52
+ :alertSink a flow:StreamConsumer .
53
+ :heartbeatSink a flow:StreamConsumer .
54
+
55
+ :m001 a flow:MessageEnvelope .
25
56
  :m001 :atStage :ingest .
26
- :m001 a msg:RDFMessage .
27
- :m001 msg:offset 1 .
28
- :m001 msg:nextMessage :m002 .
29
- :m001 prov:generatedAtTime "2026-05-12T18:20:00Z" .
30
- :m001 msg:payloadKind :observation .
31
- :m001 msg:expectedResult 21 .
32
- :m001 msg:payload in:formula1 .
33
- :m002 a msg:RDFMessage .
34
- :m002 msg:offset 2 .
35
- :m002 msg:nextMessage :m003 .
36
- :m002 prov:generatedAtTime "2026-05-12T18:21:00Z" .
37
- :m002 msg:payloadKind :observation .
38
- :m002 msg:expectedResult 22 .
39
- :m002 msg:payload in:formula2 .
40
- :m003 a msg:RDFMessage .
41
- :m003 msg:offset 3 .
42
- :m003 msg:nextMessage :m004 .
43
- :m003 prov:generatedAtTime "2026-05-12T18:22:00Z" .
44
- :m003 msg:payloadKind :heartbeat .
45
- :m004 a msg:RDFMessage .
46
- :m004 msg:offset 4 .
47
- :m004 msg:nextMessage :m005 .
48
- :m004 prov:generatedAtTime "2026-05-12T18:23:00Z" .
49
- :m004 msg:payloadKind :observation .
50
- :m004 msg:expectedResult 28 .
51
- :m004 msg:payload in:formula3 .
52
- :m005 a msg:RDFMessage .
53
- :m005 msg:offset 5 .
54
- :m005 prov:generatedAtTime "2026-05-12T18:24:00Z" .
55
- :m005 msg:payloadKind :observation .
56
- :m005 msg:expectedResult 29 .
57
- :m005 msg:payload in:formula4 .
57
+ :m001 flow:offset 1 .
58
+ :m001 flow:nextEnvelope :m002 .
59
+ :m001 prov:generatedAtTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
60
+ :m001 flow:payloadKind :observation .
61
+ :m001 flow:expectedResult 21 .
62
+ :m001 flow:payloadGraph in:payload001 .
63
+
64
+ :m002 a flow:MessageEnvelope .
65
+ :m002 flow:offset 2 .
66
+ :m002 flow:nextEnvelope :m003 .
67
+ :m002 prov:generatedAtTime "2026-05-12T18:21:00Z"^^xsd:dateTime .
68
+ :m002 flow:payloadKind :observation .
69
+ :m002 flow:expectedResult 22 .
70
+ :m002 flow:payloadGraph in:payload002 .
71
+
72
+ :m003 a flow:MessageEnvelope .
73
+ :m003 flow:offset 3 .
74
+ :m003 flow:nextEnvelope :m004 .
75
+ :m003 prov:generatedAtTime "2026-05-12T18:22:00Z"^^xsd:dateTime .
76
+ :m003 flow:payloadKind :heartbeat .
77
+
78
+ :m004 a flow:MessageEnvelope .
79
+ :m004 flow:offset 4 .
80
+ :m004 flow:nextEnvelope :m005 .
81
+ :m004 prov:generatedAtTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
82
+ :m004 flow:payloadKind :observation .
83
+ :m004 flow:expectedResult 28 .
84
+ :m004 flow:payloadGraph in:payload004 .
85
+
86
+ :m005 a flow:MessageEnvelope .
87
+ :m005 flow:offset 5 .
88
+ :m005 prov:generatedAtTime "2026-05-12T18:24:00Z"^^xsd:dateTime .
89
+ :m005 flow:payloadKind :observation .
90
+ :m005 flow:expectedResult 29 .
91
+ :m005 flow:payloadGraph in:payload005 .
58
92
 
59
- in:formula1 {
93
+ in:payload001 {
60
94
  _:m001b0 a sosa:Observation .
61
95
  _:m001b0 sosa:madeBySensor :thermometerA .
62
- _:m001b0 sosa:resultTime "2026-05-12T18:20:00Z" .
96
+ _:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
63
97
  _:m001b0 sosa:hasSimpleResult 21 .
64
98
  }
65
99
 
66
- in:formula2 {
100
+ in:payload002 {
67
101
  _:m002b0 a sosa:Observation .
68
102
  _:m002b0 sosa:madeBySensor :thermometerA .
69
- _:m002b0 sosa:resultTime "2026-05-12T18:21:00Z" .
103
+ _:m002b0 sosa:resultTime "2026-05-12T18:21:00Z"^^xsd:dateTime .
70
104
  _:m002b0 sosa:hasSimpleResult 22 .
71
105
  }
72
106
 
73
- in:formula3 {
107
+ in:payload004 {
74
108
  _:m004b0 a sosa:Observation .
75
109
  _:m004b0 sosa:madeBySensor :thermometerA .
76
- _:m004b0 sosa:resultTime "2026-05-12T18:23:00Z" .
110
+ _:m004b0 sosa:resultTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
77
111
  _:m004b0 sosa:hasSimpleResult 28 .
78
112
  }
79
113
 
80
- in:formula4 {
114
+ in:payload005 {
81
115
  _:m005b0 a sosa:Observation .
82
116
  _:m005b0 sosa:madeBySensor :thermometerA .
83
- _:m005b0 sosa:resultTime "2026-05-12T18:24:00Z" .
117
+ _:m005b0 sosa:resultTime "2026-05-12T18:24:00Z"^^xsd:dateTime .
84
118
  _:m005b0 sosa:hasSimpleResult 29 .
85
119
  }
86
120
 
@@ -88,13 +122,7 @@ in:metadata {
88
122
  in:run a see:InputDataset .
89
123
  in:run see:name "rdf_message_flow" .
90
124
  in:run see:title "RDF Message Flow" .
91
- in:run see:sourceFile "examples/n3/rdf_message_flow.n3" .
92
- in:run see:sourceSHA256 "e4e534c8ac3c2aa276e7158cca8d3146531879033f73685c302b486be2ab0099" .
93
- in:run see:description "A companion to rdf_messages.n3. This example focuses on a live stream where\nRDF Messages continuously flow through a small processing pipeline. The next\nmessage is released only after the current message reaches the sink, so the\nstream behaves as an ordered, replayable flow rather than a single merged RDF\ngraph." .
125
+ in:run see:sourceFile "examples/rdf-message-flow.n3" .
126
+ in:run see:description "A single Eyeling example split across an N3 rule file and a TriG input sidecar. It models an ordered RDF Message Stream using application-level envelopes and named payload graphs so that each payload can be interpreted atomically without merging all message contents into one graph." .
94
127
  in:run see:compiler "Eyeling RDF/TriG input sidecar" .
95
- in:run see:inputFacts 42 .
96
- in:run see:compiledRules 9 .
97
- in:run see:compiledBackwardRules 0 .
98
- in:run see:compiledFuses 0 .
99
- in:run see:compiledQueries 1 .
100
128
  }
@@ -0,0 +1,89 @@
1
+ # ===================================
2
+ # RDF Message Microgrid input sidecar
3
+ # ===================================
4
+ #
5
+ # This file is the data half of the runnable example:
6
+ #
7
+ # eyeling -r examples/rdf-message-microgrid.n3 examples/input/rdf-message-microgrid.trig
8
+ #
9
+ # The scene is a clinic operating as an islanded microgrid after a storm. The
10
+ # data arrives as a replayable stream of atomic messages: life-safety loads,
11
+ # power status, flexible demand, and an empty heartbeat. The default graph holds
12
+ # application-local envelope facts, while each non-empty message payload is kept
13
+ # in a named graph.
14
+ #
15
+ # The envelope IRIs are not RDF Message identifiers defined by the specification;
16
+ # they are local replay records for this example. The named graphs are the
17
+ # datasets/messages interpreted atomically by the N3 rules.
18
+
19
+ @prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid#> .
20
+ @prefix rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid/vocab#> .
21
+ @prefix prov: <http://www.w3.org/ns/prov#> .
22
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
23
+ @prefix see: <https://example.org/see#> .
24
+ @prefix in: <https://example.org/see/input#> .
25
+
26
+ :stormClinicLog a rmsg:MessageLog .
27
+ :stormClinicLog rmsg:orderedMessages (:m001 :m002 :m003 :m004) .
28
+ :stormClinicLog rmsg:message :m001 .
29
+ :stormClinicLog rmsg:message :m002 .
30
+ :stormClinicLog rmsg:message :m003 .
31
+ :stormClinicLog rmsg:message :m004 .
32
+ :stormClinicLog rmsg:retentionPolicy "storm replay archive" .
33
+
34
+ :m001 a rmsg:MessageEnvelope .
35
+ :m001 rmsg:offset 1 .
36
+ :m001 prov:generatedAtTime "2026-05-15T19:00:00Z"^^xsd:dateTime .
37
+ :m001 rmsg:payloadKind :lifeSafetyLoads .
38
+ :m001 rmsg:payloadGraph in:lifeSafetyPayload .
39
+
40
+ :m002 a rmsg:MessageEnvelope .
41
+ :m002 rmsg:offset 2 .
42
+ :m002 prov:generatedAtTime "2026-05-15T19:01:00Z"^^xsd:dateTime .
43
+ :m002 rmsg:payloadKind :powerStatus .
44
+ :m002 rmsg:payloadGraph in:powerPayload .
45
+
46
+ :m003 a rmsg:MessageEnvelope .
47
+ :m003 rmsg:offset 3 .
48
+ :m003 prov:generatedAtTime "2026-05-15T19:02:00Z"^^xsd:dateTime .
49
+ :m003 rmsg:payloadKind :flexibleDemand .
50
+ :m003 rmsg:payloadGraph in:flexPayload .
51
+
52
+ :m004 a rmsg:MessageEnvelope .
53
+ :m004 rmsg:offset 4 .
54
+ :m004 prov:generatedAtTime "2026-05-15T19:03:00Z"^^xsd:dateTime .
55
+ :m004 rmsg:payloadKind :heartbeat .
56
+
57
+ in:lifeSafetyPayload {
58
+ :oxygenConcentrator a :LifeSafetyLoad .
59
+ :oxygenConcentrator :requiresWatts 500 .
60
+ :oxygenConcentrator :serves :respiratoryCareRoom .
61
+
62
+ :vaccineFridge a :ColdChainLoad .
63
+ :vaccineFridge :requiresWatts 120 .
64
+ :vaccineFridge :serves :vaccineColdChain .
65
+ }
66
+
67
+ in:powerPayload {
68
+ :batteryBank a :PowerReserve .
69
+ :batteryBank :availableWatts 650 .
70
+ :batteryBank :state :islanded .
71
+
72
+ :solarForecast a :NearTermForecast .
73
+ :solarForecast :expectedWatts 150 .
74
+ }
75
+
76
+ in:flexPayload {
77
+ :evChargers a :FlexibleLoad .
78
+ :evChargers :shedWatts 600 .
79
+ :evChargers :serves :staffVehicles .
80
+ }
81
+
82
+ in:metadata {
83
+ in:run a see:InputDataset .
84
+ in:run see:name "rdf_message_microgrid" .
85
+ in:run see:title "RDF Message Microgrid" .
86
+ in:run see:sourceFile "examples/rdf-message-microgrid.n3" .
87
+ in:run see:description "A storm clinic microgrid example using application-local RDF Message envelopes and named payload graphs so a reasoner can make a bounded, explainable load-shedding decision." .
88
+ in:run see:compiler "Eyeling RDF/TriG input sidecar" .
89
+ }
@@ -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.