eyeling 1.24.30 → 1.24.31

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.
@@ -9769,6 +9769,24 @@ function stripQuotes(lex) {
9769
9769
  // This keeps all downstream parsing/reasoning N3-only.
9770
9770
  const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
9771
9771
  const RDF_REIFIES_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies>';
9772
+ const RDF_TYPE_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>';
9773
+ const XSD_INTEGER_IRI = '<http://www.w3.org/2001/XMLSchema#integer>';
9774
+ const EYMSG_NS = 'https://eyereasoner.github.io/eyeling/vocab/message#';
9775
+ const EYMSG = Object.freeze({
9776
+ RDFMessageStream: `<${EYMSG_NS}RDFMessageStream>`,
9777
+ MessageEnvelope: `<${EYMSG_NS}MessageEnvelope>`,
9778
+ envelope: `<${EYMSG_NS}envelope>`,
9779
+ firstEnvelope: `<${EYMSG_NS}firstEnvelope>`,
9780
+ lastEnvelope: `<${EYMSG_NS}lastEnvelope>`,
9781
+ orderedEnvelopes: `<${EYMSG_NS}orderedEnvelopes>`,
9782
+ messageCount: `<${EYMSG_NS}messageCount>`,
9783
+ offset: `<${EYMSG_NS}offset>`,
9784
+ nextEnvelope: `<${EYMSG_NS}nextEnvelope>`,
9785
+ payloadGraph: `<${EYMSG_NS}payloadGraph>`,
9786
+ payloadKind: `<${EYMSG_NS}payloadKind>`,
9787
+ empty: `<${EYMSG_NS}empty>`,
9788
+ nonEmpty: `<${EYMSG_NS}nonEmpty>`,
9789
+ });
9772
9790
 
9773
9791
  function normalizeRdfCompatibility(inputText) {
9774
9792
  let text = String(inputText ?? '');
@@ -9779,10 +9797,11 @@ function normalizeRdfCompatibility(inputText) {
9779
9797
  // plausible top-level TriG named graph block.
9780
9798
  const hasTripleTerms = text.includes('<<');
9781
9799
  const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
9800
+ const hasMessageVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im.test(text);
9782
9801
  const hasNamedGraphCandidate = /(?:^|[.\r\n])\s*(?:GRAPH\s+)?(?:<[^>\r\n]*>|_:[A-Za-z][A-Za-z0-9_-]*|[A-Za-z][A-Za-z0-9_-]*:[^\s{};,.()[\]]*)\s*\{/m.test(text);
9783
9802
  const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
9784
9803
 
9785
- if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
9804
+ if (!hasTripleTerms && !hasVersionDirective && !hasMessageVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
9786
9805
 
9787
9806
  function isWordChar(ch) {
9788
9807
  return ch != null && /[A-Za-z0-9_:-]/.test(ch);
@@ -10136,7 +10155,7 @@ function normalizeRdfCompatibility(inputText) {
10136
10155
  }
10137
10156
 
10138
10157
  function stripVersionDirectives(s) {
10139
- return s.replace(/^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/gim, '');
10158
+ return s.replace(/^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)(?:-messages)?\1\s*\.?\s*(?:#.*)?$/gim, '');
10140
10159
  }
10141
10160
 
10142
10161
  function skipWsAndComments(s, at) {
@@ -10290,10 +10309,270 @@ function normalizeRdfCompatibility(inputText) {
10290
10309
  return out;
10291
10310
  }
10292
10311
 
10312
+
10313
+ function isOnlyWhitespaceAndComments(s) {
10314
+ return skipWsAndComments(s, 0) >= s.length;
10315
+ }
10316
+
10317
+
10318
+ function skipOldStyleDirective(s, at) {
10319
+ let i = at;
10320
+ while (i < s.length) {
10321
+ const ch = s[i];
10322
+ if (ch === '"' || ch === "'") {
10323
+ i = readStringAt(s, i).end;
10324
+ continue;
10325
+ }
10326
+ if (ch === '<') {
10327
+ i = readIriAt(s, i).end;
10328
+ continue;
10329
+ }
10330
+ if (ch === '#') {
10331
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
10332
+ continue;
10333
+ }
10334
+ i += 1;
10335
+ if (ch === '.') return i;
10336
+ }
10337
+ return i;
10338
+ }
10339
+
10340
+ function stripDirectivesAndCommentsForEmptiness(s) {
10341
+ let out = '';
10342
+ let i = 0;
10343
+ let statementStart = true;
10344
+ while (i < s.length) {
10345
+ const ch = s[i];
10346
+ if (ch === '"' || ch === "'") {
10347
+ const str = readStringAt(s, i);
10348
+ out += str.text;
10349
+ i = str.end;
10350
+ statementStart = false;
10351
+ continue;
10352
+ }
10353
+ if (ch === '<') {
10354
+ const iri = readIriAt(s, i);
10355
+ out += iri.text;
10356
+ i = iri.end;
10357
+ statementStart = false;
10358
+ continue;
10359
+ }
10360
+ if (ch === '#') {
10361
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
10362
+ statementStart = true;
10363
+ continue;
10364
+ }
10365
+ if (statementStart) {
10366
+ const start = skipWsAndComments(s, i);
10367
+ out += s.slice(i, start);
10368
+ i = start;
10369
+ if (startsWordAt(s, 'PREFIX', i) || startsWordAt(s, 'BASE', i) || startsWordAt(s, 'VERSION', i)) {
10370
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
10371
+ statementStart = true;
10372
+ continue;
10373
+ }
10374
+ if (s[i] === '@') {
10375
+ const lower = s.slice(i, i + 9).toLowerCase();
10376
+ if (lower.startsWith('@prefix') || lower.startsWith('@base') || lower.startsWith('@version')) {
10377
+ i = skipOldStyleDirective(s, i);
10378
+ statementStart = true;
10379
+ continue;
10380
+ }
10381
+ }
10382
+ }
10383
+ out += ch;
10384
+ if (ch === '.' || ch === '}' || ch === '\n' || ch === '\r') statementStart = true;
10385
+ else if (!/\s/.test(ch)) statementStart = false;
10386
+ i += 1;
10387
+ }
10388
+ return out;
10389
+ }
10390
+
10391
+ function simpleHashText(s) {
10392
+ let h = 0x811c9dc5;
10393
+ for (let i = 0; i < s.length; i += 1) {
10394
+ h ^= s.charCodeAt(i);
10395
+ h = Math.imul(h, 0x01000193) >>> 0;
10396
+ }
10397
+ return h.toString(16).padStart(8, '0');
10398
+ }
10399
+
10400
+ function rewriteMessageBlankLabels(s, messageIndex) {
10401
+ let out = '';
10402
+ let i = 0;
10403
+ const prefix = `_:eyeling_m${String(messageIndex).padStart(3, '0')}_`;
10404
+ while (i < s.length) {
10405
+ const ch = s[i];
10406
+ if (ch === '"' || ch === "'") {
10407
+ const str = readStringAt(s, i);
10408
+ out += str.text;
10409
+ i = str.end;
10410
+ continue;
10411
+ }
10412
+ if (ch === '<' && !s.startsWith('<<', i)) {
10413
+ const iri = readIriAt(s, i);
10414
+ out += iri.text;
10415
+ i = iri.end;
10416
+ continue;
10417
+ }
10418
+ if (ch === '#') {
10419
+ while (i < s.length) {
10420
+ const c = s[i++];
10421
+ out += c;
10422
+ if (c === '\n' || c === '\r') break;
10423
+ }
10424
+ continue;
10425
+ }
10426
+ if (s.startsWith('_:', i)) {
10427
+ let j = i + 2;
10428
+ while (j < s.length && !/\s/.test(s[j]) && !'{}[](),;.'.includes(s[j])) j += 1;
10429
+ const label = s.slice(i + 2, j);
10430
+ if (label) {
10431
+ out += prefix + label.replace(/[^A-Za-z0-9_]/g, '_');
10432
+ i = j;
10433
+ continue;
10434
+ }
10435
+ }
10436
+ out += ch;
10437
+ i += 1;
10438
+ }
10439
+ return out;
10440
+ }
10441
+
10442
+ function findMessageDirectiveAt(s, at) {
10443
+ if (startsWordAt(s, 'MESSAGE', at)) return { start: at, end: at + 'MESSAGE'.length };
10444
+ if (s.slice(at, at + 8).toLowerCase() === '@message' && !isWordChar(s[at + 8])) {
10445
+ let end = at + 8;
10446
+ end = skipWsAndComments(s, end);
10447
+ if (s[end] === '.') end += 1;
10448
+ return { start: at, end };
10449
+ }
10450
+ return null;
10451
+ }
10452
+
10453
+ function splitRdfMessageLog(s) {
10454
+ const chunks = [];
10455
+ let i = 0;
10456
+ let start = 0;
10457
+ let braceDepth = 0;
10458
+ let bracketDepth = 0;
10459
+ let parenDepth = 0;
10460
+ let statementStart = true;
10461
+ let sawDelimiter = false;
10462
+
10463
+ while (i < s.length) {
10464
+ const ch = s[i];
10465
+ if (ch === '"' || ch === "'") {
10466
+ i = readStringAt(s, i).end;
10467
+ statementStart = false;
10468
+ continue;
10469
+ }
10470
+ if (ch === '<' && !s.startsWith('<<', i)) {
10471
+ i = readIriAt(s, i).end;
10472
+ statementStart = false;
10473
+ continue;
10474
+ }
10475
+ if (ch === '#') {
10476
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
10477
+ statementStart = true;
10478
+ continue;
10479
+ }
10480
+ if (statementStart && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
10481
+ const termStart = skipWsAndComments(s, i);
10482
+ const msg = findMessageDirectiveAt(s, termStart);
10483
+ if (msg) {
10484
+ chunks.push(s.slice(start, termStart));
10485
+ start = msg.end;
10486
+ i = msg.end;
10487
+ statementStart = true;
10488
+ sawDelimiter = true;
10489
+ continue;
10490
+ }
10491
+ if (termStart !== i) {
10492
+ i = termStart;
10493
+ continue;
10494
+ }
10495
+ }
10496
+ if (ch === '{') braceDepth += 1;
10497
+ else if (ch === '}' && braceDepth > 0) braceDepth -= 1;
10498
+ else if (ch === '[') bracketDepth += 1;
10499
+ else if (ch === ']' && bracketDepth > 0) bracketDepth -= 1;
10500
+ else if (ch === '(') parenDepth += 1;
10501
+ else if (ch === ')' && parenDepth > 0) parenDepth -= 1;
10502
+
10503
+ if (ch === '.' && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) statementStart = true;
10504
+ else if (ch === '\n' || ch === '\r') statementStart = true;
10505
+ else if (!/\s/.test(ch)) statementStart = false;
10506
+ i += 1;
10507
+ }
10508
+
10509
+ const tail = s.slice(start);
10510
+ if (!sawDelimiter || !isOnlyWhitespaceAndComments(tail)) chunks.push(tail);
10511
+ return chunks;
10512
+ }
10513
+
10514
+ function normalizeMessageChunk(chunk, messageIndex) {
10515
+ let body = String(chunk || '');
10516
+ if (hasTripleTerms || body.includes('<<')) body = convertTripleTerms(body);
10517
+ if (hasAnnotationSyntax || /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(body)) {
10518
+ body = convertAnnotations(body);
10519
+ }
10520
+ body = normalizeNamedGraphs(body);
10521
+ body = rewriteMessageBlankLabels(body, messageIndex);
10522
+ return body.trim();
10523
+ }
10524
+
10525
+ function messageChunkHasRdf(body) {
10526
+ return !isOnlyWhitespaceAndComments(stripDirectivesAndCommentsForEmptiness(body));
10527
+ }
10528
+
10529
+ function normalizeRdfMessageLog(s) {
10530
+ const withoutVersion = stripVersionDirectives(s);
10531
+ const chunks = splitRdfMessageLog(withoutVersion);
10532
+ const hash = simpleHashText(s);
10533
+ const base = `urn:eyeling:message-log:${hash}`;
10534
+ const stream = `<${base}#stream>`;
10535
+ const envelopeIris = chunks.map((unused, idx) => `<${base}#m${String(idx + 1).padStart(3, '0')}>`);
10536
+ const payloadIris = chunks.map((unused, idx) => `<${base}#m${String(idx + 1).padStart(3, '0')}/payload>`);
10537
+ const out = [];
10538
+
10539
+ out.push(`${stream} ${RDF_TYPE_IRI} ${EYMSG.RDFMessageStream} .`);
10540
+ out.push(`${stream} ${EYMSG.messageCount} "${chunks.length}"^^${XSD_INTEGER_IRI} .`);
10541
+ if (envelopeIris.length) {
10542
+ out.push(`${stream} ${EYMSG.orderedEnvelopes} (${envelopeIris.join(' ')}) .`);
10543
+ out.push(`${stream} ${EYMSG.firstEnvelope} ${envelopeIris[0]} .`);
10544
+ out.push(`${stream} ${EYMSG.lastEnvelope} ${envelopeIris[envelopeIris.length - 1]} .`);
10545
+ }
10546
+
10547
+ for (let idx = 0; idx < chunks.length; idx += 1) {
10548
+ const n = idx + 1;
10549
+ const envelope = envelopeIris[idx];
10550
+ const payload = payloadIris[idx];
10551
+ const body = normalizeMessageChunk(chunks[idx], n);
10552
+ const hasBody = messageChunkHasRdf(body);
10553
+
10554
+ out.push(`${stream} ${EYMSG.envelope} ${envelope} .`);
10555
+ out.push(`${envelope} ${RDF_TYPE_IRI} ${EYMSG.MessageEnvelope} .`);
10556
+ out.push(`${envelope} ${EYMSG.offset} "${n}"^^${XSD_INTEGER_IRI} .`);
10557
+ out.push(`${envelope} ${EYMSG.payloadKind} ${hasBody ? EYMSG.nonEmpty : EYMSG.empty} .`);
10558
+ if (idx + 1 < envelopeIris.length) out.push(`${envelope} ${EYMSG.nextEnvelope} ${envelopeIris[idx + 1]} .`);
10559
+ if (hasBody) {
10560
+ out.push(`${envelope} ${EYMSG.payloadGraph} ${payload} .`);
10561
+ out.push(`${payload} ${LOG_NAME_OF_IRI} {`);
10562
+ out.push(body);
10563
+ out.push(`} .`);
10564
+ }
10565
+ }
10566
+
10567
+ return out.join('\n') + '\n';
10568
+ }
10569
+
10570
+ if (hasMessageVersionDirective) return normalizeRdfMessageLog(text);
10571
+
10293
10572
  if (hasTripleTerms) text = convertTripleTerms(text);
10294
10573
  if (hasAnnotationSyntax) text = convertAnnotations(text);
10295
- if (hasVersionDirective) text = stripVersionDirectives(text);
10296
- if (hasVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
10574
+ if (hasVersionDirective || hasMessageVersionDirective) text = stripVersionDirectives(text);
10575
+ if (hasVersionDirective || hasMessageVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
10297
10576
  return text;
10298
10577
  }
10299
10578
 
@@ -1,50 +1,36 @@
1
- # ==============================
2
- # RDF Message Flow input sidecar
3
- # ==============================
1
+ # =============================
2
+ # RDF Message Flow message log
3
+ # =============================
4
4
  #
5
5
  # This file is the data half of the runnable example:
6
6
  #
7
7
  # eyeling -r examples/rdf-message-flow.n3 examples/input/rdf-message-flow.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 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).
9
+ # It is a parser-level RDF Message Log using the RDF 1.2 Messages draft syntax:
10
+ # VERSION "1.2-messages" selects message-log parsing and MESSAGE delimiters
11
+ # separate the ordered RDF Messages. Eyeling replays this log internally by
12
+ # materializing message envelopes and one payload graph per non-empty message.
13
+ # The sidecar itself does not contain application-level envelope facts.
17
14
  #
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.
15
+ # The third message is deliberately empty. The same blank node label _:obs is
16
+ # also reused in several messages; an RDF Message Log parser scopes blank node
17
+ # identifiers to each message, so those labels do not collide.
24
18
 
25
- @prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow#> .
26
- @prefix flow: <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow/vocab#> .
27
- @prefix prov: <http://www.w3.org/ns/prov#> .
28
- @prefix sosa: <http://www.w3.org/ns/sosa/> .
29
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
30
- @prefix math: <http://www.w3.org/2000/10/swap/math#> .
31
- @prefix list: <http://www.w3.org/2000/10/swap/list#> .
32
- @prefix log: <http://www.w3.org/2000/10/swap/log#> .
33
- @prefix string: <http://www.w3.org/2000/10/swap/string#> .
34
- @prefix see: <https://example.org/see#> .
35
- @prefix in: <https://example.org/see/input#> .
19
+ VERSION "1.2-messages"
20
+ PREFIX : <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow#>
21
+ PREFIX flow: <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow/vocab#>
22
+ PREFIX prov: <http://www.w3.org/ns/prov#>
23
+ PREFIX sosa: <http://www.w3.org/ns/sosa/>
24
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
25
+ PREFIX see: <https://example.org/see#>
26
+ PREFIX in: <https://example.org/see/input#>
36
27
 
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 .
46
- :temperatureFlow :pipeline (:ingest :validate :interpret :route :sink) .
47
- :temperatureFlow :highThreshold 26 .
28
+ # Message 1: stream context plus first observation.
29
+ :temperatureFlow a flow:RDFMessageStream ;
30
+ :producer :thermometerA ;
31
+ :consumer :flowProcessor ;
32
+ :pipeline (:ingest :validate :interpret :route :sink) ;
33
+ :highThreshold 26 .
48
34
 
49
35
  :thermometerA a flow:StreamProducer .
50
36
  :flowProcessor a flow:StreamConsumer .
@@ -52,77 +38,42 @@
52
38
  :alertSink a flow:StreamConsumer .
53
39
  :heartbeatSink a flow:StreamConsumer .
54
40
 
55
- :m001 a flow:MessageEnvelope .
56
- :m001 :atStage :ingest .
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 .
41
+ _:obs a sosa:Observation ;
42
+ sosa:madeBySensor :thermometerA ;
43
+ sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime ;
44
+ sosa:hasSimpleResult 21 .
63
45
 
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 .
46
+ in:run a see:InputDataset ;
47
+ see:name "rdf_message_flow" ;
48
+ see:title "RDF Message Flow" ;
49
+ see:sourceFile "examples/rdf-message-flow.n3" ;
50
+ see:description "A true RDF 1.2 Message Log sidecar. Eyeling parses VERSION 1.2-messages and MESSAGE delimiters into replayable internal message envelopes." ;
51
+ see:compiler "Eyeling RDF Message Log input" .
71
52
 
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 .
53
+ MESSAGE
77
54
 
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 .
55
+ # Message 2: normal observation.
56
+ _:obs a sosa:Observation ;
57
+ sosa:madeBySensor :thermometerA ;
58
+ sosa:resultTime "2026-05-12T18:21:00Z"^^xsd:dateTime ;
59
+ sosa:hasSimpleResult 22 .
85
60
 
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 .
61
+ MESSAGE
92
62
 
93
- in:payload001 {
94
- _:m001b0 a sosa:Observation .
95
- _:m001b0 sosa:madeBySensor :thermometerA .
96
- _:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
97
- _:m001b0 sosa:hasSimpleResult 21 .
98
- }
63
+ # Message 3: empty heartbeat. The next MESSAGE delimiter finalizes this empty
64
+ # message and opens message 4.
65
+ MESSAGE
99
66
 
100
- in:payload002 {
101
- _:m002b0 a sosa:Observation .
102
- _:m002b0 sosa:madeBySensor :thermometerA .
103
- _:m002b0 sosa:resultTime "2026-05-12T18:21:00Z"^^xsd:dateTime .
104
- _:m002b0 sosa:hasSimpleResult 22 .
105
- }
67
+ # Message 4: hot observation.
68
+ _:obs a sosa:Observation ;
69
+ sosa:madeBySensor :thermometerA ;
70
+ sosa:resultTime "2026-05-12T18:23:00Z"^^xsd:dateTime ;
71
+ sosa:hasSimpleResult 28 .
106
72
 
107
- in:payload004 {
108
- _:m004b0 a sosa:Observation .
109
- _:m004b0 sosa:madeBySensor :thermometerA .
110
- _:m004b0 sosa:resultTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
111
- _:m004b0 sosa:hasSimpleResult 28 .
112
- }
73
+ MESSAGE
113
74
 
114
- in:payload005 {
115
- _:m005b0 a sosa:Observation .
116
- _:m005b0 sosa:madeBySensor :thermometerA .
117
- _:m005b0 sosa:resultTime "2026-05-12T18:24:00Z"^^xsd:dateTime .
118
- _:m005b0 sosa:hasSimpleResult 29 .
119
- }
120
-
121
- in:metadata {
122
- in:run a see:InputDataset .
123
- in:run see:name "rdf_message_flow" .
124
- in:run see:title "RDF Message Flow" .
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." .
127
- in:run see:compiler "Eyeling RDF/TriG input sidecar" .
128
- }
75
+ # Message 5: hot observation. EOF finalizes the message.
76
+ _:obs a sosa:Observation ;
77
+ sosa:madeBySensor :thermometerA ;
78
+ sosa:resultTime "2026-05-12T18:24:00Z"^^xsd:dateTime ;
79
+ sosa:hasSimpleResult 29 .
@@ -3,10 +3,10 @@
3
3
  ## Source files
4
4
 
5
5
  - [N3 rules](../rdf-message-flow.n3)
6
- - [Input TriG](../input/rdf-message-flow.trig)
6
+ - [Input RDF Message Log](../input/rdf-message-flow.trig)
7
7
 
8
8
  ## Answer
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.
9
+ Continuous RDF Message flow accepted: 5 ordered parser-replayed message envelopes moved through the ingest → validate → interpret → route → sink pipeline. The threshold was 26, so results 21 and 22 were archived, the empty heartbeat kept the stream alive, and results 28 and 29 were emitted as alerts.
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 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.
12
+ The input sidecar is now a true RDF Message Log using VERSION \"1.2-messages\" and MESSAGE delimiters. Eyeling parses the log internally into an eymsg: replay view with ordered envelopes, offsets, next-envelope links, and one payload graph per non-empty message. Only the first replayed envelope starts at ingress; each envelope must reach :sink before the continuous-flow rule releases its eymsg:nextEnvelope. Observation payloads are inspected with log:includes inside their own message formula, and the delimiter-only third message is replayed as an empty heartbeat. This keeps message boundaries in the parser/runtime instead of modeling them by hand in the TriG sidecar.