eyeling 1.7.17 → 1.7.19

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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- ### Copyright 2021-2026 Jos De Roo, KNoWS office of IDLab, Ghent University - imec
3
+ ### Copyright 2025-2026 Jos De Roo, KNoWS office of IDLab, Ghent University - imec
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -17,13 +17,14 @@ A [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
17
17
  Try it here:
18
18
 
19
19
  - [Eyeling playground](https://eyereasoner.github.io/eyeling/demo)
20
- - [Eyeling streaming playground](https://eyereasoner.github.io/eyeling/stream)
21
-
22
- The playground runs `eyeling` client-side. You can:
20
+ - Edit an N3 program directly.
21
+ - Load an N3 program from a URL (in the "Load N3 from URL" box or as ?url=...).
22
+ - Share a link with the program encoded in the URL fragment (`#...`).
23
23
 
24
- - edit an N3 program directly
25
- - load an N3 program from a URL (in the "Load N3 from URL" box or as ?url=...)
26
- - share a link with the program encoded in the URL fragment (`#...`)
24
+ - [Eyeling streaming playground](https://eyereasoner.github.io/eyeling/stream)
25
+ - Browse a Wikidata entity, load its facts, and see Eyeling’s **deductive closure appear incrementally** as triples are derived.
26
+ - Edit **N3 rules live** and re-run to watch how different inference rules change what gets derived.
27
+ - Demo **CORS-safe dynamic fetching**: derived “fetch requests” can trigger extra facts (e.g., Wikiquote extracts) that are injected and re-reasoned.
27
28
 
28
29
  ## Quick start
29
30
 
@@ -120,6 +121,7 @@ Options:
120
121
  -s, --super-restricted Disable all builtins except => and <=.
121
122
  -a, --ast Print parsed AST as JSON and exit.
122
123
  --strings Print log:outputString strings (ordered by key) instead of N3 output.
124
+ --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.
123
125
  ```
124
126
 
125
127
  By default, `eyeling`:
@@ -165,6 +167,7 @@ The CLI prints only newly derived forward facts.
165
167
  - backward rules are indexed by head predicate
166
168
  - the backward prover is **iterative** (explicit stack), so deep chains won’t blow the JS call stack
167
169
  - for very deep backward chains, substitutions may be compactified (semantics-preserving) to avoid quadratic “copy a growing substitution object” behavior
170
+ - if the head is **structurally ground** and has no head blanks, one body proof suffices—and if the head triples are already known, we can skip the body proof.
168
171
 
169
172
  ## Blank nodes and quantification
170
173
 
@@ -202,7 +205,7 @@ Rules whose conclusion is `false` are treated as hard failures:
202
205
 
203
206
  As soon as the premise is provable, `eyeling` exits with status code `2`.
204
207
 
205
- ## Syntax + built-ins
208
+ ## Syntax
206
209
 
207
210
  `eyeling`’s parser targets (nearly) the full *Notation3 Language* grammar from the [W3C N3 Community Group spec](https://w3c.github.io/N3/spec/).
208
211
 
@@ -223,14 +226,9 @@ Commonly used N3/Turtle features:
223
226
  - Resource paths (`!` and `^`)
224
227
  - `#` line comments
225
228
 
226
- `eyeling` implements a pragmatic subset of common N3 builtin families and evaluates them during backward goal proving:
229
+ ## Builtins
227
230
 
228
- - **crypto**: `crypto:md5` `crypto:sha` `crypto:sha256` `crypto:sha512`
229
- - **list**: `list:append` `list:first` `list:firstRest` `list:in` `list:iterate` `list:last` `list:length` `list:map` `list:member` `list:memberAt` `list:notMember` `list:remove` `list:rest` `list:reverse` `list:sort`
230
- - **log**: `log:collectAllIn` `log:content` `log:dtlit` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:includes` `log:langlit` `log:notEqualTo` `log:notIncludes` `log:outputString` `log:parsedAsN3` `log:rawType` `log:semantics` `log:semanticsOrError` `log:skolem` `log:trace` `log:uri`
231
- - **math**: `math:absoluteValue` `math:acos` `math:asin` `math:atan` `math:cos` `math:cosh` `math:degrees` `math:difference` `math:equalTo` `math:exponentiation` `math:greaterThan` `math:integerQuotient` `math:lessThan` `math:negation` `math:notEqualTo` `math:notGreaterThan` `math:notLessThan` `math:product` `math:quotient` `math:remainder` `math:rounded` `math:sin` `math:sinh` `math:sum` `math:tan` `math:tanh`
232
- - **string**: `string:concatenation` `string:contains` `string:containsIgnoringCase` `string:endsWith` `string:equalIgnoringCase` `string:format` `string:greaterThan` `string:jsonPointer` `string:lessThan` `string:matches` `string:notEqualIgnoringCase` `string:notGreaterThan` `string:notLessThan` `string:notMatches` `string:replace` `string:scrape` `string:startsWith`
233
- - **time**: `time:day` `time:hour` `time:localTime` `time:minute` `time:month` `time:second` `time:timeZone` `time:year`
231
+ `eyeling` implements the builtins described in [eyeling-builtins](https://github.com/eyereasoner/eyeling/blob/main/eyeling-builtins.ttl).
234
232
 
235
233
  ## License
236
234
 
@@ -73,3 +73,154 @@ genid:8e2eaee7-90fd-39b8-cf83-6d8c8e5816c8 ex:email "ada@other.example" .
73
73
  <urn:example:customer:vat:NL999999999B01> a ex:CustomerEmailConflict .
74
74
  <urn:example:customer:vat:NL999999999B01> ex:conflictingEmail "acct@company.nl" .
75
75
  <urn:example:customer:vat:NL999999999B01> ex:conflictingEmail "billing@company.nl" .
76
+ <urn:example:order:A100> ex:line <urn:example:order:A100#line:0> .
77
+ <urn:example:order:A100#line:0> a ex:OrderLine .
78
+ <urn:example:order:A100#line:0> ex:lineIndex 0 .
79
+ <urn:example:order:A100#line:0> ex:product ex:Widget .
80
+ <urn:example:order:A100#line:0> ex:sku "WID-001" .
81
+ <urn:example:order:A100#line:0> ex:qty 2 .
82
+ <urn:example:order:A100#line:0> ex:unitCents 999 .
83
+ <urn:example:order:A100#line:0> ex:netCents 1998 .
84
+ <urn:example:order:A100> ex:line <urn:example:order:A100#line:1> .
85
+ <urn:example:order:A100#line:1> a ex:OrderLine .
86
+ <urn:example:order:A100#line:1> ex:lineIndex 1 .
87
+ <urn:example:order:A100#line:1> ex:product ex:Gadget .
88
+ <urn:example:order:A100#line:1> ex:sku "GAD-002" .
89
+ <urn:example:order:A100#line:1> ex:qty 1 .
90
+ <urn:example:order:A100#line:1> ex:unitCents 1500 .
91
+ <urn:example:order:A100#line:1> ex:netCents 1500 .
92
+ <urn:example:order:A200> ex:line <urn:example:order:A200#line:0> .
93
+ <urn:example:order:A200#line:0> a ex:OrderLine .
94
+ <urn:example:order:A200#line:0> ex:lineIndex 0 .
95
+ <urn:example:order:A200#line:0> ex:product ex:Widget .
96
+ <urn:example:order:A200#line:0> ex:sku "WID-001" .
97
+ <urn:example:order:A200#line:0> ex:qty 2 .
98
+ <urn:example:order:A200#line:0> ex:unitCents 999 .
99
+ <urn:example:order:A200#line:0> ex:netCents 1998 .
100
+ <urn:example:order:A200> ex:line <urn:example:order:A200#line:1> .
101
+ <urn:example:order:A200#line:1> a ex:OrderLine .
102
+ <urn:example:order:A200#line:1> ex:lineIndex 1 .
103
+ <urn:example:order:A200#line:1> ex:product ex:Gadget .
104
+ <urn:example:order:A200#line:1> ex:sku "GAD-002" .
105
+ <urn:example:order:A200#line:1> ex:qty 1 .
106
+ <urn:example:order:A200#line:1> ex:unitCents 1500 .
107
+ <urn:example:order:A200#line:1> ex:netCents 1500 .
108
+ <urn:example:order:A300> ex:line <urn:example:order:A300#line:0> .
109
+ <urn:example:order:A300#line:0> a ex:OrderLine .
110
+ <urn:example:order:A300#line:0> ex:lineIndex 0 .
111
+ <urn:example:order:A300#line:0> ex:product ex:Widget .
112
+ <urn:example:order:A300#line:0> ex:sku "WID-001" .
113
+ <urn:example:order:A300#line:0> ex:qty 2 .
114
+ <urn:example:order:A300#line:0> ex:unitCents 999 .
115
+ <urn:example:order:A300#line:0> ex:netCents 1998 .
116
+ <urn:example:order:A300> ex:line <urn:example:order:A300#line:1> .
117
+ <urn:example:order:A300#line:1> a ex:OrderLine .
118
+ <urn:example:order:A300#line:1> ex:lineIndex 1 .
119
+ <urn:example:order:A300#line:1> ex:product ex:Gadget .
120
+ <urn:example:order:A300#line:1> ex:sku "GAD-002" .
121
+ <urn:example:order:A300#line:1> ex:qty 1 .
122
+ <urn:example:order:A300#line:1> ex:unitCents 1500 .
123
+ <urn:example:order:A300#line:1> ex:netCents 1500 .
124
+ <urn:example:order:A301> ex:line <urn:example:order:A301#line:0> .
125
+ <urn:example:order:A301#line:0> a ex:OrderLine .
126
+ <urn:example:order:A301#line:0> ex:lineIndex 0 .
127
+ <urn:example:order:A301#line:0> ex:product ex:Widget .
128
+ <urn:example:order:A301#line:0> ex:sku "WID-001" .
129
+ <urn:example:order:A301#line:0> ex:qty 2 .
130
+ <urn:example:order:A301#line:0> ex:unitCents 999 .
131
+ <urn:example:order:A301#line:0> ex:netCents 1998 .
132
+ <urn:example:order:A301> ex:line <urn:example:order:A301#line:1> .
133
+ <urn:example:order:A301#line:1> a ex:OrderLine .
134
+ <urn:example:order:A301#line:1> ex:lineIndex 1 .
135
+ <urn:example:order:A301#line:1> ex:product ex:Gadget .
136
+ <urn:example:order:A301#line:1> ex:sku "GAD-002" .
137
+ <urn:example:order:A301#line:1> ex:qty 1 .
138
+ <urn:example:order:A301#line:1> ex:unitCents 1500 .
139
+ <urn:example:order:A301#line:1> ex:netCents 1500 .
140
+ <urn:example:order:A400> ex:line <urn:example:order:A400#line:0> .
141
+ <urn:example:order:A400#line:0> a ex:OrderLine .
142
+ <urn:example:order:A400#line:0> ex:lineIndex 0 .
143
+ <urn:example:order:A400#line:0> ex:product ex:Widget .
144
+ <urn:example:order:A400#line:0> ex:sku "WID-001" .
145
+ <urn:example:order:A400#line:0> ex:qty 2 .
146
+ <urn:example:order:A400#line:0> ex:unitCents 999 .
147
+ <urn:example:order:A400#line:0> ex:netCents 1998 .
148
+ <urn:example:order:A400> ex:line <urn:example:order:A400#line:1> .
149
+ <urn:example:order:A400#line:1> a ex:OrderLine .
150
+ <urn:example:order:A400#line:1> ex:lineIndex 1 .
151
+ <urn:example:order:A400#line:1> ex:product ex:Gadget .
152
+ <urn:example:order:A400#line:1> ex:sku "GAD-002" .
153
+ <urn:example:order:A400#line:1> ex:qty 1 .
154
+ <urn:example:order:A400#line:1> ex:unitCents 1500 .
155
+ <urn:example:order:A400#line:1> ex:netCents 1500 .
156
+ <urn:example:order:A200#line:0> ex:vatBps 0 .
157
+ <urn:example:order:A200#line:0> ex:vatCents 0 .
158
+ <urn:example:order:A200#line:0> ex:grossCents 1998 .
159
+ <urn:example:order:A200#line:0> ex:vatReason ex:Export .
160
+ <urn:example:order:A200#line:1> ex:vatBps 0 .
161
+ <urn:example:order:A200#line:1> ex:vatCents 0 .
162
+ <urn:example:order:A200#line:1> ex:grossCents 1500 .
163
+ <urn:example:order:A200#line:1> ex:vatReason ex:Export .
164
+ <urn:example:order:A300#line:0> ex:vatBps 0 .
165
+ <urn:example:order:A300#line:0> ex:vatCents 0 .
166
+ <urn:example:order:A300#line:0> ex:grossCents 1998 .
167
+ <urn:example:order:A300#line:0> ex:vatReason ex:ReverseCharge .
168
+ <urn:example:order:A300#line:1> ex:vatBps 0 .
169
+ <urn:example:order:A300#line:1> ex:vatCents 0 .
170
+ <urn:example:order:A300#line:1> ex:grossCents 1500 .
171
+ <urn:example:order:A300#line:1> ex:vatReason ex:ReverseCharge .
172
+ <urn:example:order:A301#line:0> ex:vatBps 0 .
173
+ <urn:example:order:A301#line:0> ex:vatCents 0 .
174
+ <urn:example:order:A301#line:0> ex:grossCents 1998 .
175
+ <urn:example:order:A301#line:0> ex:vatReason ex:ReverseCharge .
176
+ <urn:example:order:A301#line:1> ex:vatBps 0 .
177
+ <urn:example:order:A301#line:1> ex:vatCents 0 .
178
+ <urn:example:order:A301#line:1> ex:grossCents 1500 .
179
+ <urn:example:order:A301#line:1> ex:vatReason ex:ReverseCharge .
180
+ <urn:example:order:A100#line:0> ex:vatBps 2100 .
181
+ <urn:example:order:A100#line:0> ex:vatCents 419 .
182
+ <urn:example:order:A100#line:0> ex:grossCents 2417 .
183
+ <urn:example:order:A100#line:0> ex:vatReason ex:Domestic .
184
+ <urn:example:order:A100#line:1> ex:vatBps 600 .
185
+ <urn:example:order:A100#line:1> ex:vatCents 90 .
186
+ <urn:example:order:A100#line:1> ex:grossCents 1590 .
187
+ <urn:example:order:A100#line:1> ex:vatReason ex:Domestic .
188
+ <urn:example:order:A400#line:0> ex:vatBps 2100 .
189
+ <urn:example:order:A400#line:0> ex:vatCents 419 .
190
+ <urn:example:order:A400#line:0> ex:grossCents 2417 .
191
+ <urn:example:order:A400#line:0> ex:vatReason ex:Domestic .
192
+ <urn:example:order:A400#line:1> ex:vatBps 600 .
193
+ <urn:example:order:A400#line:1> ex:vatCents 90 .
194
+ <urn:example:order:A400#line:1> ex:grossCents 1590 .
195
+ <urn:example:order:A400#line:1> ex:vatReason ex:Domestic .
196
+ <urn:example:order:A100> ex:computedNetCents 3498 .
197
+ <urn:example:order:A100> ex:computedVatCents 509 .
198
+ <urn:example:order:A100> ex:computedGrossCents 4007 .
199
+ <urn:example:order:A200> ex:computedNetCents 3498 .
200
+ <urn:example:order:A200> ex:computedVatCents 0 .
201
+ <urn:example:order:A200> ex:computedGrossCents 3498 .
202
+ <urn:example:order:A300> ex:computedNetCents 3498 .
203
+ <urn:example:order:A300> ex:computedVatCents 0 .
204
+ <urn:example:order:A300> ex:computedGrossCents 3498 .
205
+ <urn:example:order:A301> ex:computedNetCents 3498 .
206
+ <urn:example:order:A301> ex:computedVatCents 0 .
207
+ <urn:example:order:A301> ex:computedGrossCents 3498 .
208
+ <urn:example:order:A400> ex:computedNetCents 3498 .
209
+ <urn:example:order:A400> ex:computedVatCents 509 .
210
+ <urn:example:order:A400> ex:computedGrossCents 4007 .
211
+ <urn:example:order:A100> a ex:OrderOk .
212
+ <urn:example:order:A200> a ex:OrderOk .
213
+ <urn:example:order:A300> a ex:OrderOk .
214
+ <urn:example:order:A301> a ex:OrderOk .
215
+ <urn:example:issue:A400:vat> a ex:Issue .
216
+ <urn:example:issue:A400:vat> ex:onOrder <urn:example:order:A400> .
217
+ <urn:example:issue:A400:vat> ex:field "vatCents" .
218
+ <urn:example:issue:A400:vat> ex:declared 508 .
219
+ <urn:example:issue:A400:vat> ex:computed 509 .
220
+ <urn:example:order:A400> ex:hasIssue <urn:example:issue:A400:vat> .
221
+ <urn:example:issue:A400:gross> a ex:Issue .
222
+ <urn:example:issue:A400:gross> ex:onOrder <urn:example:order:A400> .
223
+ <urn:example:issue:A400:gross> ex:field "grossCents" .
224
+ <urn:example:issue:A400:gross> ex:declared 4006 .
225
+ <urn:example:issue:A400:gross> ex:computed 4007 .
226
+ <urn:example:order:A400> ex:hasIssue <urn:example:issue:A400:gross> .
package/eyeling.js CHANGED
@@ -104,6 +104,17 @@ const __logSemanticsCache = new Map(); // iri -> GraphTerm | null (null means pa
104
104
  const __logSemanticsOrErrorCache = new Map(); // iri -> Term (GraphTerm | Literal) for log:semanticsOrError
105
105
  const __logConclusionCache = new WeakMap(); // GraphTerm -> GraphTerm (deductive closure)
106
106
 
107
+ // When enabled, force http:// IRIs to be dereferenced as https://
108
+ // (CLI: --enforce-https, API: reasonStream({ enforceHttps: true })).
109
+ let enforceHttpsEnabled = false;
110
+
111
+ function __maybeEnforceHttps(iri) {
112
+ if (!enforceHttpsEnabled) return iri;
113
+ return typeof iri === 'string' && iri.startsWith('http://')
114
+ ? 'https://' + iri.slice('http://'.length)
115
+ : iri;
116
+ }
117
+
107
118
  // Environment detection (Node vs Browser/Worker).
108
119
  // Eyeling is primarily synchronous, so we use sync XHR in browsers for log:content/log:semantics.
109
120
  // Note: Browser fetches are subject to CORS; use CORS-enabled resources or a proxy.
@@ -150,9 +161,9 @@ function __fetchHttpTextSyncBrowser(url) {
150
161
 
151
162
  function __normalizeDerefIri(iriNoFrag) {
152
163
  // In Node, treat non-http as local path; leave as-is.
153
- if (__IS_NODE) return iriNoFrag;
164
+ if (__IS_NODE) return __maybeEnforceHttps(iriNoFrag);
154
165
  // In browsers/workers, resolve relative references against the page URL.
155
- return __resolveBrowserUrl(iriNoFrag);
166
+ return __maybeEnforceHttps(__resolveBrowserUrl(iriNoFrag));
156
167
  }
157
168
 
158
169
  function __stripFragment(iri) {
@@ -195,9 +206,17 @@ function __fetchHttpTextViaSubprocess(url) {
195
206
  const cp = require('child_process');
196
207
  // Use a subprocess so this code remains synchronous without rewriting the whole reasoner to async.
197
208
  const script = `
209
+ const enforceHttps = ${enforceHttpsEnabled ? 'true' : 'false'};
198
210
  const url = process.argv[1];
199
211
  const maxRedirects = 10;
212
+ function norm(u) {
213
+ if (enforceHttps && typeof u === 'string' && u.startsWith('http://')) {
214
+ return 'https://' + u.slice('http://'.length);
215
+ }
216
+ return u;
217
+ }
200
218
  function get(u, n) {
219
+ u = norm(u);
201
220
  if (n > maxRedirects) { console.error('Too many redirects'); process.exit(3); }
202
221
  let mod;
203
222
  if (u.startsWith('https://')) mod = require('https');
@@ -219,7 +238,8 @@ function __fetchHttpTextViaSubprocess(url) {
219
238
  const req = mod.request(opts, (res) => {
220
239
  const sc = res.statusCode || 0;
221
240
  if (sc >= 300 && sc < 400 && res.headers && res.headers.location) {
222
- const next = new URL(res.headers.location, u).toString();
241
+ let next = new URL(res.headers.location, u).toString();
242
+ next = norm(next);
223
243
  res.resume();
224
244
  return get(next, n + 1);
225
245
  }
@@ -3061,19 +3081,34 @@ function termToJsXsdStringNoLang(t) {
3061
3081
  }
3062
3082
 
3063
3083
  function termToJsString(t) {
3064
- // Strict string extraction for SWAP/N3 string builtins:
3065
- // - accept plain string literals ("...") and language-tagged ones ("..."@en)
3066
- // - accept "..."^^xsd:string
3067
- // - reject any other datatype (e.g., "x"^^xsd:integer, "x"^^xsd:foobar)
3084
+ // Domain is xsd:string for SWAP/N3 string builtins (string:*).
3085
+ //
3086
+ // Per the N3 Builtins spec, when the domain is xsd:string we must be able to
3087
+ // cast *any* IRI or literal value (incl. numeric, boolean, dateTime, anyURI,
3088
+ // rdf:langString, and plain literals) to a string.
3089
+ //
3090
+ // We implement this as:
3091
+ // - IRI -> its IRI string
3092
+ // - Literal:
3093
+ // * quoted lexical form: decode N3/Turtle escapes and strip quotes
3094
+ // * unquoted lexical form: use as-is (e.g., 1234, true, 1971-..., 1.23E4)
3095
+ // - Everything else (blank nodes, lists, formulas, vars) -> fail
3096
+ if (t instanceof Iri) return t.value;
3068
3097
  if (!(t instanceof Literal)) return null;
3069
- const [lex, dt] = literalParts(t.value);
3070
- if (!isQuotedLexical(lex)) return null;
3071
- if (dt !== null && dt !== XSD_NS + 'string' && dt !== 'xsd:string') return null;
3072
- // Interpret N3/Turtle string escapes (\" \n \uXXXX \UXXXXXXXX …) to obtain
3073
- // the actual string value used by SWAP/N3 string builtins.
3074
- return decodeN3StringEscapes(stripQuotes(lex));
3098
+
3099
+ const [lex, _dt] = literalParts(t.value);
3100
+
3101
+ if (isQuotedLexical(lex)) {
3102
+ // Interpret N3/Turtle string escapes (\" \n \uXXXX \UXXXXXXXX …)
3103
+ // to obtain the actual string value.
3104
+ return decodeN3StringEscapes(stripQuotes(lex));
3105
+ }
3106
+
3107
+ // Unquoted lexical (numbers/booleans/dateTimes, etc.)
3108
+ return typeof lex === 'string' ? lex : String(lex);
3075
3109
  }
3076
3110
 
3111
+
3077
3112
  function makeStringLiteral(str) {
3078
3113
  // JSON.stringify gives us a valid N3/Turtle-style quoted string
3079
3114
  // (with proper escaping for quotes, backslashes, newlines, …).
@@ -6991,8 +7026,16 @@ function __collectOutputStringsFromFacts(facts, prefixes) {
6991
7026
  }
6992
7027
 
6993
7028
  function reasonStream(n3Text, opts = {}) {
6994
- const { baseIri = null, proof = false, onDerived = null, includeInputFactsInClosure = true } = opts;
6995
-
7029
+ const {
7030
+ baseIri = null,
7031
+ proof = false,
7032
+ onDerived = null,
7033
+ includeInputFactsInClosure = true,
7034
+ enforceHttps = false,
7035
+ } = opts;
7036
+
7037
+ const __oldEnforceHttps = enforceHttpsEnabled;
7038
+ enforceHttpsEnabled = !!enforceHttps;
6996
7039
  proofCommentsEnabled = !!proof;
6997
7040
 
6998
7041
  const toks = lex(n3Text);
@@ -7020,12 +7063,14 @@ function reasonStream(n3Text, opts = {}) {
7020
7063
 
7021
7064
  const closureTriples = includeInputFactsInClosure ? facts : derived.map((d) => d.fact);
7022
7065
 
7023
- return {
7066
+ const __out = {
7024
7067
  prefixes,
7025
7068
  facts, // saturated closure (Triple[])
7026
7069
  derived, // DerivedFact[]
7027
7070
  closureN3: closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n'),
7028
7071
  };
7072
+ enforceHttpsEnabled = __oldEnforceHttps;
7073
+ return __out;
7029
7074
  }
7030
7075
 
7031
7076
  // Minimal export surface for Node + browser/worker
@@ -7056,7 +7101,8 @@ function main() {
7056
7101
  ` -n, --no-proof-comments Disable proof explanations (default).\n` +
7057
7102
  ` -s, --super-restricted Disable all builtins except => and <=.\n` +
7058
7103
  ` -a, --ast Print parsed AST as JSON and exit.\n` +
7059
- ` --strings Print log:outputString strings (ordered by key) instead of N3 output.\n`;
7104
+ ` --strings Print log:outputString strings (ordered by key) instead of N3 output.\n` +
7105
+ ` --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n`;
7060
7106
  (toStderr ? console.error : console.log)(msg);
7061
7107
  }
7062
7108
 
@@ -7079,6 +7125,11 @@ function main() {
7079
7125
 
7080
7126
  const outputStringsMode = argv.includes('--strings');
7081
7127
 
7128
+ // --enforce-https: rewrite http:// -> https:// for log dereferencing builtins
7129
+ if (argv.includes('--enforce-https')) {
7130
+ enforceHttpsEnabled = true;
7131
+ }
7132
+
7082
7133
  // --proof-comments / -p: enable proof explanations
7083
7134
  if (argv.includes('--proof-comments') || argv.includes('-p')) {
7084
7135
  proofCommentsEnabled = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.17",
3
+ "version": "1.7.19",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [