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.
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
@@ -1691,15 +1691,85 @@ function __listElemsForBuiltin(listLike, facts) {
1691
1691
  return null;
1692
1692
  }
1693
1693
 
1694
- function evalListFirstLikeBuiltin(sTerm, oTerm, subst) {
1695
- if (!(sTerm instanceof ListTerm)) return [];
1696
- if (!sTerm.elems.length) return [];
1697
- const first = sTerm.elems[0];
1698
- const s2 = unifyTerm(oTerm, first, subst);
1699
- return s2 !== null ? [s2] : [];
1694
+ function __pushBuiltinDeltaLimited(out, delta, maxResults) {
1695
+ if (delta === null) return false;
1696
+ out.push(delta);
1697
+ return typeof maxResults === 'number' && maxResults > 0 && out.length >= maxResults;
1698
+ }
1699
+
1700
+ function __collectListLikeTermsFromTerm(t, out, seen) {
1701
+ if (t instanceof ListTerm || t instanceof OpenListTerm) {
1702
+ const k = termFastKey(t);
1703
+ if (k === null || !seen.has(k)) {
1704
+ if (k !== null) seen.add(k);
1705
+ out.push(t);
1706
+ }
1707
+ if (t instanceof ListTerm) {
1708
+ for (const e of t.elems) __collectListLikeTermsFromTerm(e, out, seen);
1709
+ } else {
1710
+ for (const e of t.prefix) __collectListLikeTermsFromTerm(e, out, seen);
1711
+ }
1712
+ return;
1713
+ }
1714
+ if (t instanceof GraphTerm) {
1715
+ for (const tr of t.triples) {
1716
+ __collectListLikeTermsFromTerm(tr.s, out, seen);
1717
+ __collectListLikeTermsFromTerm(tr.p, out, seen);
1718
+ __collectListLikeTermsFromTerm(tr.o, out, seen);
1719
+ }
1720
+ }
1721
+ }
1722
+
1723
+ function __collectListLikeTermsFromFacts(facts) {
1724
+ const out = [];
1725
+ const seen = new Set();
1726
+ for (const tr of facts) {
1727
+ __collectListLikeTermsFromTerm(tr.s, out, seen);
1728
+ __collectListLikeTermsFromTerm(tr.o, out, seen);
1729
+ }
1730
+ return out;
1731
+ }
1732
+
1733
+ function evalListFirstLikeBuiltin(sTerm, oTerm, subst, facts, maxResults) {
1734
+ if (sTerm instanceof ListTerm) {
1735
+ if (!sTerm.elems.length) return [];
1736
+ const first = sTerm.elems[0];
1737
+ const s2 = unifyTerm(oTerm, first, subst);
1738
+ return s2 !== null ? [s2] : [];
1739
+ }
1740
+
1741
+ // For a variable subject, enumerate already-existing collection terms.
1742
+ // This lets a rule body such as `_:x rdf:first 1; rdf:rest rdf:nil`
1743
+ // bind `_:x` to an N3 collection literal `(1)` used elsewhere as a term.
1744
+ // Also include ordinary rdf:first facts so the builtin path does not hide
1745
+ // RDF-serialized lists when the subject starts unbound.
1746
+ if (sTerm instanceof Var) {
1747
+ const out = [];
1748
+
1749
+ for (const listTerm of __collectListLikeTermsFromFacts(facts)) {
1750
+ if (!(listTerm instanceof ListTerm) || !listTerm.elems.length) continue;
1751
+ let s2 = unifyTerm(sTerm, listTerm, subst);
1752
+ if (s2 === null) continue;
1753
+ s2 = unifyTerm(oTerm, listTerm.elems[0], s2);
1754
+ if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
1755
+ }
1756
+
1757
+ const RDF_FIRST = RDF_NS + 'first';
1758
+ for (const tr of facts) {
1759
+ if (!(tr.p instanceof Iri) || tr.p.value !== RDF_FIRST) continue;
1760
+ let s2 = unifyTerm(sTerm, tr.s, subst);
1761
+ if (s2 === null) continue;
1762
+ s2 = unifyTerm(oTerm, tr.o, s2);
1763
+ if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
1764
+ }
1765
+
1766
+ return out;
1767
+ }
1768
+
1769
+ return [];
1700
1770
  }
1701
1771
 
1702
- function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
1772
+ function evalListRestLikeBuiltin(sTerm, oTerm, subst, facts, maxResults) {
1703
1773
  // Closed list: (a b c) -> (b c)
1704
1774
  if (sTerm instanceof ListTerm) {
1705
1775
  if (!sTerm.elems.length) return [];
@@ -1720,6 +1790,32 @@ function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
1720
1790
  return s2 !== null ? [s2] : [];
1721
1791
  }
1722
1792
 
1793
+ // See evalListFirstLikeBuiltin(): if the collection subject is still a
1794
+ // variable, enumerate known list literals and ordinary rdf:rest facts.
1795
+ if (sTerm instanceof Var) {
1796
+ const out = [];
1797
+
1798
+ for (const listTerm of __collectListLikeTermsFromFacts(facts)) {
1799
+ if (!(listTerm instanceof ListTerm) || !listTerm.elems.length) continue;
1800
+ let s2 = unifyTerm(sTerm, listTerm, subst);
1801
+ if (s2 === null) continue;
1802
+ const rest = new ListTerm(listTerm.elems.slice(1));
1803
+ s2 = unifyTerm(oTerm, rest, s2);
1804
+ if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
1805
+ }
1806
+
1807
+ const RDF_REST = RDF_NS + 'rest';
1808
+ for (const tr of facts) {
1809
+ if (!(tr.p instanceof Iri) || tr.p.value !== RDF_REST) continue;
1810
+ let s2 = unifyTerm(sTerm, tr.s, subst);
1811
+ if (s2 === null) continue;
1812
+ s2 = unifyTerm(oTerm, tr.o, s2);
1813
+ if (__pushBuiltinDeltaLimited(out, s2, maxResults)) return out;
1814
+ }
1815
+
1816
+ return out;
1817
+ }
1818
+
1723
1819
  return [];
1724
1820
  }
1725
1821
 
@@ -2859,14 +2955,14 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
2859
2955
  return s2 !== null ? [s2] : [];
2860
2956
  }
2861
2957
  if (pv === RDF_NS + 'first') {
2862
- return evalListFirstLikeBuiltin(g.s, g.o, subst);
2958
+ return evalListFirstLikeBuiltin(g.s, g.o, subst, facts, maxResults);
2863
2959
  }
2864
2960
 
2865
2961
  // list:rest and rdf:rest
2866
2962
  // true iff $s is a (non-empty) list and $o is the rest (tail) of that list.
2867
2963
  // Schema: $s+ list:rest $o-
2868
2964
  if (pv === LIST_NS + 'rest') {
2869
- if (g.s instanceof ListTerm || g.s instanceof OpenListTerm) return evalListRestLikeBuiltin(g.s, g.o, subst);
2965
+ if (g.s instanceof ListTerm || g.s instanceof OpenListTerm) return evalListRestLikeBuiltin(g.s, g.o, subst, facts, maxResults);
2870
2966
  const xs = __listElemsForBuiltin(g.s, facts);
2871
2967
  if (!xs || !xs.length) return [];
2872
2968
  const rest = new ListTerm(xs.slice(1));
@@ -2874,7 +2970,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
2874
2970
  return s2 !== null ? [s2] : [];
2875
2971
  }
2876
2972
  if (pv === RDF_NS + 'rest') {
2877
- return evalListRestLikeBuiltin(g.s, g.o, subst);
2973
+ return evalListRestLikeBuiltin(g.s, g.o, subst, facts, maxResults);
2878
2974
  }
2879
2975
 
2880
2976
  // list:iterate
@@ -7918,7 +8014,10 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
7918
8014
  const isRdfFirstOrRest = goalPredicateIri === RDF_NS + 'first' || goalPredicateIri === RDF_NS + 'rest';
7919
8015
  const shouldTreatAsBuiltin =
7920
8016
  isBuiltinPred(goal0.p) &&
7921
- !(isRdfFirstOrRest && !(goal0.s instanceof ListTerm || goal0.s instanceof OpenListTerm));
8017
+ !(
8018
+ isRdfFirstOrRest &&
8019
+ !(goal0.s instanceof ListTerm || goal0.s instanceof OpenListTerm || goal0.s instanceof Var)
8020
+ );
7922
8021
 
7923
8022
  if (shouldTreatAsBuiltin) {
7924
8023
  const remaining = max - results.length;
@@ -11399,6 +11498,20 @@ class Parser {
11399
11498
  if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
11400
11499
  this.next();
11401
11500
  pred = internIri(RDF_NS + 'type');
11501
+ } else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'has') {
11502
+ // N3 syntactic sugar is also valid in predicate-object lists,
11503
+ // including blank node property lists: [ has :p :o ] means _:b :p :o.
11504
+ this.next();
11505
+ pred = this.parseTerm();
11506
+ } else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'is') {
11507
+ // N3 syntactic sugar: [ is :p of :s ] means :s :p _:b.
11508
+ this.next();
11509
+ pred = this.parseTerm();
11510
+ if (!(this.peek().typ === 'Ident' && (this.peek().value || '') === 'of')) {
11511
+ this.fail(`Expected 'of' after 'is <expr>', got ${this.peek().toString()}`);
11512
+ }
11513
+ this.next();
11514
+ invert = true;
11402
11515
  } else if (this.peek().typ === 'OpPredInvert') {
11403
11516
  this.next();
11404
11517
  pred = this.parseTerm();
@@ -11703,14 +11816,142 @@ const STRING_NS = 'http://www.w3.org/2000/10/swap/string#';
11703
11816
  const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
11704
11817
  const RDF_JSON_DT = RDF_NS + 'JSON';
11705
11818
 
11819
+ function parseUriReferenceForResolution(uri) {
11820
+ // RFC 3986 Appendix B-style component parser, with the scheme tightened to
11821
+ // the RFC scheme grammar. Capturing delimiter presence matters: `?` with an
11822
+ // empty query is defined, while no `?` means undefined.
11823
+ const m = /^(([A-Za-z][A-Za-z0-9+.-]*):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/u.exec(String(uri));
11824
+ if (!m) return null;
11825
+ return {
11826
+ scheme: m[2] !== undefined ? m[2] : undefined,
11827
+ authority: m[4] !== undefined ? m[4] : undefined,
11828
+ path: m[5] || '',
11829
+ query: m[6] !== undefined ? (m[7] || '') : undefined,
11830
+ fragment: m[8] !== undefined ? (m[9] || '') : undefined,
11831
+ };
11832
+ }
11833
+
11834
+ function recomposeUriReference(parts) {
11835
+ let out = '';
11836
+ if (parts.scheme !== undefined) out += `${parts.scheme}:`;
11837
+ if (parts.authority !== undefined) out += `//${parts.authority}`;
11838
+ out += parts.path || '';
11839
+ if (parts.query !== undefined) out += `?${parts.query}`;
11840
+ if (parts.fragment !== undefined) out += `#${parts.fragment}`;
11841
+ return out;
11842
+ }
11843
+
11844
+ function removeLastPathSegment(path) {
11845
+ if (!path) return '';
11846
+ const i = path.lastIndexOf('/');
11847
+ if (i < 0) return '';
11848
+ if (i === 0) return '';
11849
+ return path.slice(0, i);
11850
+ }
11851
+
11852
+ function removeDotSegments(path) {
11853
+ // RFC 3986 section 5.2.4. This deliberately avoids WHATWG URL parsing so
11854
+ // Eyeling preserves IRI spelling (for example, it does not add a trailing
11855
+ // slash to `http://example.org`) while still normalizing `.` and `..` path
11856
+ // segments as required by section 5.2.2.
11857
+ let input = String(path || '');
11858
+ let output = '';
11859
+
11860
+ while (input.length > 0) {
11861
+ if (input.startsWith('../')) {
11862
+ input = input.slice(3);
11863
+ } else if (input.startsWith('./')) {
11864
+ input = input.slice(2);
11865
+ } else if (input.startsWith('/./')) {
11866
+ input = `/${input.slice(3)}`;
11867
+ } else if (input === '/.') {
11868
+ input = '/';
11869
+ } else if (input.startsWith('/../')) {
11870
+ input = `/${input.slice(4)}`;
11871
+ output = removeLastPathSegment(output);
11872
+ } else if (input === '/..') {
11873
+ input = '/';
11874
+ output = removeLastPathSegment(output);
11875
+ } else if (input === '.' || input === '..') {
11876
+ input = '';
11877
+ } else {
11878
+ let segmentEnd;
11879
+ if (input[0] === '/') {
11880
+ segmentEnd = input.indexOf('/', 1);
11881
+ } else {
11882
+ segmentEnd = input.indexOf('/');
11883
+ }
11884
+
11885
+ if (segmentEnd < 0) {
11886
+ output += input;
11887
+ input = '';
11888
+ } else {
11889
+ output += input.slice(0, segmentEnd);
11890
+ input = input.slice(segmentEnd);
11891
+ }
11892
+ }
11893
+ }
11894
+
11895
+ return output;
11896
+ }
11897
+
11898
+ function mergePaths(base, refPath) {
11899
+ if (base.authority !== undefined && base.path === '') {
11900
+ return `/${refPath}`;
11901
+ }
11902
+ const i = base.path.lastIndexOf('/');
11903
+ if (i < 0) return refPath;
11904
+ return `${base.path.slice(0, i + 1)}${refPath}`;
11905
+ }
11906
+
11706
11907
  function resolveIriRef(ref, base) {
11707
- if (!base) return ref;
11708
- if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(ref)) return ref; // already absolute
11709
- try {
11710
- return new URL(ref, base).toString();
11711
- } catch {
11712
- return ref;
11908
+ const r = parseUriReferenceForResolution(ref);
11909
+ if (!r) return ref;
11910
+
11911
+ const baseParts = base ? parseUriReferenceForResolution(base) : null;
11912
+
11913
+ // Absolute references do not need a base, but RFC 3986 section 5.2.2 still
11914
+ // applies remove_dot_segments(R.path) when R.scheme is defined.
11915
+ if (r.scheme !== undefined) {
11916
+ return recomposeUriReference({
11917
+ scheme: r.scheme,
11918
+ authority: r.authority,
11919
+ path: removeDotSegments(r.path),
11920
+ query: r.query,
11921
+ fragment: r.fragment,
11922
+ });
11923
+ }
11924
+
11925
+ // Without a usable base, preserve relative references as written.
11926
+ if (!baseParts || baseParts.scheme === undefined) return ref;
11927
+
11928
+ const t = {
11929
+ scheme: baseParts.scheme,
11930
+ authority: undefined,
11931
+ path: '',
11932
+ query: undefined,
11933
+ fragment: r.fragment,
11934
+ };
11935
+
11936
+ if (r.authority !== undefined) {
11937
+ t.authority = r.authority;
11938
+ t.path = removeDotSegments(r.path);
11939
+ t.query = r.query;
11940
+ } else if (r.path === '') {
11941
+ t.authority = baseParts.authority;
11942
+ t.path = baseParts.path;
11943
+ t.query = r.query !== undefined ? r.query : baseParts.query;
11944
+ } else {
11945
+ t.authority = baseParts.authority;
11946
+ if (r.path.startsWith('/')) {
11947
+ t.path = removeDotSegments(r.path);
11948
+ } else {
11949
+ t.path = removeDotSegments(mergePaths(baseParts, r.path));
11950
+ }
11951
+ t.query = r.query;
11713
11952
  }
11953
+
11954
+ return recomposeUriReference(t);
11714
11955
  }
11715
11956
 
11716
11957
  // -----------------------------------------------------------------------------
@@ -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
+ }