@factoidal/core 0.6.0 → 0.7.1
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/CHANGELOG.md +66 -0
- package/README.md +36 -0
- package/bin/factoidal.mjs +32 -6
- package/bin/pack-host.mjs +2 -1
- package/bin/pack.mjs +8 -2
- package/bin/store.mjs +34 -30
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1 — 2026-09-06
|
|
4
|
+
|
|
5
|
+
`factoidal pack --batch-bytes N` was documented in 0.7.0 and refused by the
|
|
6
|
+
option parser ("usage" exit 2): the value-option table for `pack` did not
|
|
7
|
+
list it. It is accepted now, and `tests/store-host/cli.mjs` packs with it.
|
|
8
|
+
No engine change; the wasm module is 0.7.0's.
|
|
9
|
+
|
|
10
|
+
## 0.7.0 — 2026-09-05
|
|
11
|
+
|
|
12
|
+
**Wire version 10.** The command and `bin/store.mjs` read a generation
|
|
13
|
+
packed at `--layout ibk5`: out-of-line literals, RDF 1.2 triple terms and
|
|
14
|
+
directional literals, and per-entry zone maps.
|
|
15
|
+
|
|
16
|
+
### The packer
|
|
17
|
+
|
|
18
|
+
`factoidal pack --layout ibk5` writes wire version 10. Blocks are now
|
|
19
|
+
published DURING the ingest pass, every `--batch-bytes` of source (default
|
|
20
|
+
64 MiB in the module, 256 MiB in the native tool), so peak memory is bounded
|
|
21
|
+
by a batch and not by the source: the native packer's peak on the
|
|
22
|
+
1,543,478,120-byte skosdex corpus fell from 5,951,730,560 to 396,869,632
|
|
23
|
+
bytes, and the module's 128 MiB source cap is gone for N-Quads, Turtle and
|
|
24
|
+
N-Triples (TriG still buffers). Rows are bucketed by predicate AND graph, so
|
|
25
|
+
an N-Quads file whose graphs interleave line by line packs to the same
|
|
26
|
+
block set as the same statements in graph order (measured: 11,636 blocks
|
|
27
|
+
became 483). The literal search index is LGI2, whose LEB128 gaps are 23% of
|
|
28
|
+
a `skos:prefLabel` block against LGI1's 54%; a version-10 generation of the
|
|
29
|
+
210 MB skosdex prefix is 14.5% smaller than version 9.
|
|
30
|
+
|
|
31
|
+
### Out-of-line literals
|
|
32
|
+
|
|
33
|
+
A literal above 65,536 UTF-8 bytes is stored as one `blob-<sha256
|
|
34
|
+
hex>.lit` file beside the block, committed by the manifest blob table.
|
|
35
|
+
The block holds only the byte extent and the digest, so a host must fetch
|
|
36
|
+
the blob as well as the block:
|
|
37
|
+
|
|
38
|
+
- `storeQueryPlan` lists them under `blobKeys`, after `keys` and
|
|
39
|
+
`sidecarKeys`, and counts their bytes in `bytes`;
|
|
40
|
+
- `queryStore` and `openStoreHandle` fetch them without a flag;
|
|
41
|
+
- a blob that is absent or whose bytes hash differently REFUSES the
|
|
42
|
+
query by name. Nothing is answered with a shortened literal.
|
|
43
|
+
|
|
44
|
+
A store handle resolves every blob ONCE, at `storeOpen`, and the open
|
|
45
|
+
envelope reports how many. The two candidate-filter indexes keep an
|
|
46
|
+
out-of-line literal in every candidate set, so `CONTAINS` over a
|
|
47
|
+
70,000-byte literal and `geof:sfIntersects` over a polygon above the
|
|
48
|
+
ceiling both answer their rows.
|
|
49
|
+
|
|
50
|
+
### RDF 1.2 terms
|
|
51
|
+
|
|
52
|
+
A triple term and a directional language literal are stored and read
|
|
53
|
+
back. Wire version 9 refuses both.
|
|
54
|
+
|
|
55
|
+
### Zone maps
|
|
56
|
+
|
|
57
|
+
Each manifest entry carries the first 64 bytes of the smallest and the
|
|
58
|
+
largest subject key and object key of its block. A query with a constant
|
|
59
|
+
subject or object skips every block whose range cannot hold it, from the
|
|
60
|
+
manifest alone. `storeQueryPlan` reports the count as `zoneExcluded`, and
|
|
61
|
+
`factoidal inspect` prints the bounds beside each entry.
|
|
62
|
+
|
|
63
|
+
### Unchanged
|
|
64
|
+
|
|
65
|
+
Wire version 9 and earlier answer exactly what they answered before: no
|
|
66
|
+
manifest below version 10 carries a blob table or a zone map, so
|
|
67
|
+
`blobKeys` is empty and `zoneExcluded` is zero for them.
|
|
68
|
+
|
|
3
69
|
## 0.6.0 — 2026-09-05
|
|
4
70
|
|
|
5
71
|
**A full-text index, a geometry index, extension functions from
|
package/README.md
CHANGED
|
@@ -942,6 +942,42 @@ would drop answers. A non-overlapping pair of boxes proves geometries
|
|
|
942
942
|
disjoint, but that saves work inside a scan rather than reducing the
|
|
943
943
|
candidate set.
|
|
944
944
|
|
|
945
|
+
### Wire version 10: large literals, RDF 1.2 terms, zone maps
|
|
946
|
+
|
|
947
|
+
A generation packed at wire version 10 (`--layout ibk5`) carries three
|
|
948
|
+
things earlier versions do not. Everything below is read by the command
|
|
949
|
+
and by `bin/store.mjs` without a flag; wire versions 9 and earlier are
|
|
950
|
+
read exactly as before.
|
|
951
|
+
|
|
952
|
+
**Out-of-line literals.** A literal whose lexical form is at most 65,536
|
|
953
|
+
UTF-8 bytes is stored inside the block. A longer one is stored as one
|
|
954
|
+
file beside it, `blob-<sha256 hex>.lit`, holding exactly those bytes; the
|
|
955
|
+
block keeps only the byte length and the SHA-256, and the manifest blob
|
|
956
|
+
table commits the file. The name is the content address, so the same
|
|
957
|
+
literal in twenty blocks is one file. `factoidal inspect` prints how many
|
|
958
|
+
such files a generation holds and their total size, and the plan a query
|
|
959
|
+
produces lists them under `blobKeys` beside `keys` and `sidecarKeys` — a
|
|
960
|
+
host reading a store itself must fetch all three. A blob that is missing
|
|
961
|
+
or whose bytes hash differently REFUSES the query; it is never answered
|
|
962
|
+
with a shortened literal.
|
|
963
|
+
|
|
964
|
+
Above 4,294,967,295 bytes the packer refuses the literal and names its
|
|
965
|
+
subject and predicate. That ceiling, and every other one, is stated in
|
|
966
|
+
`docs/designissues/2026-09-05-wire-version-10-scale.md` section 2.
|
|
967
|
+
|
|
968
|
+
**RDF 1.2 terms.** A triple term (`<<( :a :p :b )>>`) and a directional
|
|
969
|
+
language literal (`"..."@ar--rtl`) are stored and read back. Wire version
|
|
970
|
+
9 refuses both.
|
|
971
|
+
|
|
972
|
+
**Zone maps.** Each manifest entry carries the smallest and the largest
|
|
973
|
+
subject key and object key of its block, truncated to 64 bytes. A query
|
|
974
|
+
with a constant subject or object skips every block whose range cannot
|
|
975
|
+
hold it, from the manifest alone, with no block read. The plan reports
|
|
976
|
+
how many entries were dropped that way as `zoneExcluded`. Selectivity
|
|
977
|
+
depends on the source order: a subject-grouped or graph-grouped file
|
|
978
|
+
gives disjoint ranges per block, a shuffled one gives overlapping ranges
|
|
979
|
+
and a scan, which is correct and no worse than wire version 9.
|
|
980
|
+
|
|
945
981
|
## Limits (deliberate, documented)
|
|
946
982
|
|
|
947
983
|
- **In-memory only.** ~1.2 KB RAM per quad (measured); 1M quads ≈
|
package/bin/factoidal.mjs
CHANGED
|
@@ -45,7 +45,7 @@ const EXIT_NOT_WIRED = 3
|
|
|
45
45
|
// a time, so this is the window that catches one feed per report.
|
|
46
46
|
const FEED_PROGRESS = 65536
|
|
47
47
|
|
|
48
|
-
const PACK_LAYOUTS = ['ibk3', 'ibk4']
|
|
48
|
+
const PACK_LAYOUTS = ['ibk3', 'ibk4', 'ibk5']
|
|
49
49
|
const PACK_SYNTAXES = ['turtle', 'trig', 'nquads', 'ntriples']
|
|
50
50
|
const PACK_SUFFIXES = [
|
|
51
51
|
['.ttl', 'turtle'], ['.turtle', 'turtle'],
|
|
@@ -230,9 +230,15 @@ INPUT is an RDF file. OUTPUT is the generation directory to create; it is
|
|
|
230
230
|
normally STORE/gen-N. Packing does not activate: run activate next.
|
|
231
231
|
|
|
232
232
|
options:
|
|
233
|
-
--layout LAYOUT ibk3 (triples, default)
|
|
233
|
+
--layout LAYOUT ibk3 (triples, default), ibk4 (quads, wire version 9)
|
|
234
|
+
or ibk5 (quads, wire version 10: RDF 1.2 terms,
|
|
235
|
+
out-of-line literals, zone maps)
|
|
234
236
|
--syntax SYNTAX turtle, trig or nquads; default from the file extension
|
|
235
237
|
--chunk-bytes N Merkle chunk size; default is the engine's
|
|
238
|
+
--batch-bytes N ibk4 only: source bytes between publication batches;
|
|
239
|
+
default is the engine's. Blocks are published during
|
|
240
|
+
the ingest pass, so this bounds the packer's memory.
|
|
241
|
+
Match l4block-shard-pack --batch-bytes for its blocks.
|
|
236
242
|
--json emit one JSON object
|
|
237
243
|
--no-worker pack in this process instead of on a worker thread
|
|
238
244
|
|
|
@@ -325,7 +331,7 @@ const VALUE_OPTIONS = {
|
|
|
325
331
|
'sample-store': new Set([]),
|
|
326
332
|
inspect: new Set(['generation']),
|
|
327
333
|
query: new Set(['query', 'file', 'format', 'limit', 'base', 'generation']),
|
|
328
|
-
pack: new Set(['layout', 'syntax', 'chunk-bytes', 'base']),
|
|
334
|
+
pack: new Set(['layout', 'syntax', 'chunk-bytes', 'base', 'batch-bytes']),
|
|
329
335
|
activate: new Set([]),
|
|
330
336
|
update: new Set(['update', 'file']),
|
|
331
337
|
compact: new Set([])
|
|
@@ -470,10 +476,27 @@ async function commandInspect (positional, options) {
|
|
|
470
476
|
out(`term registry ${envelope.termRegistryVersion === '' ? '(none recorded)' : envelope.termRegistryVersion}`)
|
|
471
477
|
out(`fixed-chunk Merkle commitment ${envelope.rangeCommitted ? 'yes' : 'no'}`)
|
|
472
478
|
out(`${envelope.entries.length} ${envelope.entries.length === 1 ? 'entry' : 'entries'}, ${envelope.totalBytes} bytes, ${plural(envelope.totalRows, 'row')}`)
|
|
479
|
+
// Wire version 10: the manifest blob table. Each `blob-<hex>.lit` holds the
|
|
480
|
+
// lexical form of one literal above the 65,536-byte inline ceiling, and is
|
|
481
|
+
// shared by every block whose dictionary names it.
|
|
482
|
+
const blobs = envelope.blobs ?? []
|
|
483
|
+
if (blobs.length > 0) {
|
|
484
|
+
let blobBytes = 0
|
|
485
|
+
for (const blob of blobs) blobBytes += blob.bytes
|
|
486
|
+
out(`${plural(blobs.length, 'out-of-line literal')}, ${blobBytes} bytes`)
|
|
487
|
+
}
|
|
473
488
|
out(`generation directory holds ${files.length} files, ${directoryBytes} bytes`)
|
|
474
489
|
out('')
|
|
490
|
+
// The zone maps are per entry, so the columns appear only for a manifest
|
|
491
|
+
// that carries them. A bound is the first 64 bytes of the smallest (or
|
|
492
|
+
// largest) term key of the block; the first eight are enough to see that
|
|
493
|
+
// two blocks hold different ranges.
|
|
494
|
+
const zoned = envelope.entries.some((entry) => entry.subjectZone !== undefined)
|
|
495
|
+
const zoneCell = (zone) =>
|
|
496
|
+
zone === undefined ? '-' : `${zone.min.slice(0, 16)}..${zone.max.slice(0, 16)}`
|
|
475
497
|
printTable(
|
|
476
|
-
['#', 'rows', 'bytes', 'kind', 'graphs', 'predicate']
|
|
498
|
+
['#', 'rows', 'bytes', 'kind', 'graphs', 'predicate']
|
|
499
|
+
.concat(zoned ? ['blobs', 'subject zone'] : []),
|
|
477
500
|
envelope.entries.map((entry) => [
|
|
478
501
|
String(entry.ordinal),
|
|
479
502
|
String(entry.rows),
|
|
@@ -481,7 +504,9 @@ async function commandInspect (positional, options) {
|
|
|
481
504
|
entry.blockKind,
|
|
482
505
|
entry.graphs.length === 0 ? '-' : entry.graphs.map(graphLabel).join(' '),
|
|
483
506
|
entry.predicate
|
|
484
|
-
]
|
|
507
|
+
].concat(zoned
|
|
508
|
+
? [String((entry.blobRefs ?? []).length), zoneCell(entry.subjectZone)]
|
|
509
|
+
: [])))
|
|
485
510
|
return EXIT_OK
|
|
486
511
|
}
|
|
487
512
|
|
|
@@ -764,7 +789,8 @@ async function commandPack (positional, options) {
|
|
|
764
789
|
let answer
|
|
765
790
|
try {
|
|
766
791
|
answer = await runPack(
|
|
767
|
-
{ kind: 'pack', input, output, syntax, layout, base: packBase(input, options)
|
|
792
|
+
{ kind: 'pack', input, output, syntax, layout, base: packBase(input, options),
|
|
793
|
+
batchBytes: options['batch-bytes'] },
|
|
768
794
|
quiet
|
|
769
795
|
? undefined
|
|
770
796
|
: (progress) => {
|
package/bin/pack-host.mjs
CHANGED
|
@@ -215,6 +215,7 @@ export async function packHere (task, onProgress) {
|
|
|
215
215
|
syntax: task.syntax,
|
|
216
216
|
layout: task.layout,
|
|
217
217
|
base: task.base,
|
|
218
|
+
batchBytes: task.batchBytes,
|
|
218
219
|
onProgress
|
|
219
220
|
})
|
|
220
221
|
return { report }
|
|
@@ -298,7 +299,7 @@ async function packInWorker (task, onProgress) {
|
|
|
298
299
|
* already re-executed (see `denoReexec`), so this runs in process.
|
|
299
300
|
*
|
|
300
301
|
* @param {object} task {kind, ...}: a pack takes
|
|
301
|
-
* {input, output, syntax, layout, base}, an activate takes
|
|
302
|
+
* {input, output, syntax, layout, base, batchBytes}, an activate takes
|
|
302
303
|
* {root, generation}
|
|
303
304
|
* @param {(progress: object) => void} [onProgress]
|
|
304
305
|
* @param {object} [options] {worker: false} forces the in-process path
|
package/bin/pack.mjs
CHANGED
|
@@ -93,16 +93,22 @@ function drain (engine, handle, output) {
|
|
|
93
93
|
* @param {string} input the RDF file to read
|
|
94
94
|
* @param {string} output the generation directory to fill; it must exist
|
|
95
95
|
* @param {object} options
|
|
96
|
-
* @param {string} options.layout `ibk3` or `
|
|
96
|
+
* @param {string} options.layout `ibk3`, `ibk4` or `ibk5`
|
|
97
97
|
* @param {string} options.syntax `turtle`, `trig` or `nquads`
|
|
98
98
|
* @param {string} [options.base] the base IRI relative IRIs resolve against;
|
|
99
99
|
* the empty string means no base
|
|
100
|
+
* @param {number|string} [options.batchBytes] source bytes per publication
|
|
101
|
+
* batch for the IBK4 streamed grammars; omitted means the engine's own
|
|
102
|
+
* default. Match the native packer's `--batch-bytes` to get its block set.
|
|
100
103
|
* @param {(progress: object) => void} [options.onProgress]
|
|
101
104
|
* @returns {object} the engine's own finish envelope, plus what was written
|
|
102
105
|
*/
|
|
103
106
|
export function packFile (engine, input, output, options) {
|
|
104
107
|
const begun = engine.call('packBegin',
|
|
105
|
-
[options.syntax, options.layout,
|
|
108
|
+
[options.syntax, options.layout,
|
|
109
|
+
typeof options.base === 'string' ? options.base : '',
|
|
110
|
+
options.batchBytes === undefined || options.batchBytes === null
|
|
111
|
+
? '' : String(options.batchBytes)])
|
|
106
112
|
const handle = begun.handle
|
|
107
113
|
if (typeof handle !== 'string') {
|
|
108
114
|
throw new PackError('packBegin answered no handle')
|
package/bin/store.mjs
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
// WHAT THIS FILE IS ALLOWED TO DO
|
|
5
5
|
// Read CURRENT, read a manifest file by name, read the artifact files the
|
|
6
|
-
// engine's plan named,
|
|
6
|
+
// engine's plan named -- blocks, index sidecars, and the `blob-<hex>.lit`
|
|
7
|
+
// out-of-line literals of wire version 10 -- concatenate their bytes, and
|
|
8
|
+
// hand them over. It
|
|
7
9
|
// never parses a manifest, never verifies a digest, never decodes a block
|
|
8
10
|
// and never decides which artifact answers a query. Every one of those is
|
|
9
11
|
// a format decision and it lives in `formal/lean4/Wasm/Ops/Store.lean`
|
|
@@ -192,6 +194,22 @@ function capDecision (engine, store, sparql) {
|
|
|
192
194
|
}
|
|
193
195
|
}
|
|
194
196
|
|
|
197
|
+
/** Read the named artifacts and concatenate them into one region. */
|
|
198
|
+
function regionOf (store, keys) {
|
|
199
|
+
const chunks = keys.map((key) => readWhole(joinPath(store.generationDir, key)))
|
|
200
|
+
let total = 0
|
|
201
|
+
for (const chunk of chunks) total += chunk.length
|
|
202
|
+
const blob = new Uint8Array(total)
|
|
203
|
+
const artifacts = []
|
|
204
|
+
let offset = 0
|
|
205
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
206
|
+
blob.set(chunks[index], offset)
|
|
207
|
+
artifacts.push({ key: keys[index], offset, len: chunks[index].length })
|
|
208
|
+
offset += chunks[index].length
|
|
209
|
+
}
|
|
210
|
+
return { blob, artifacts, total }
|
|
211
|
+
}
|
|
212
|
+
|
|
195
213
|
/**
|
|
196
214
|
* Evaluate one SPARQL query against one generation.
|
|
197
215
|
*
|
|
@@ -210,17 +228,12 @@ export function queryStore (engine, store, sparql) {
|
|
|
210
228
|
if (empty !== null) {
|
|
211
229
|
return { plan, result: empty, blobBytes: 0, artifacts: [] }
|
|
212
230
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
for (let index = 0; index < chunks.length; index += 1) {
|
|
220
|
-
blob.set(chunks[index], offset)
|
|
221
|
-
artifacts.push({ key: plan.keys[index], offset, len: chunks[index].length })
|
|
222
|
-
offset += chunks[index].length
|
|
223
|
-
}
|
|
231
|
+
// `blobKeys` are the out-of-line literals the selected blocks name (wire
|
|
232
|
+
// version 10). A block holds only their byte extent and SHA-256, so a
|
|
233
|
+
// query that touches one needs its file as much as it needs the block.
|
|
234
|
+
// The engine chose these keys; this host reads the files and nothing more.
|
|
235
|
+
const keys = plan.keys.concat(plan.blobKeys ?? [])
|
|
236
|
+
const { blob, artifacts, total } = regionOf(store, keys)
|
|
224
237
|
let result
|
|
225
238
|
try {
|
|
226
239
|
result = engine.callBlobIO('storeQuery',
|
|
@@ -274,30 +287,18 @@ export function turtleOfNQuads (engine, nquads) {
|
|
|
274
287
|
* need no change here: the engine names them under `entry.sidecars`.
|
|
275
288
|
*/
|
|
276
289
|
function manifestKeys (engine, store) {
|
|
290
|
+
const envelope = inspectManifest(engine, store)
|
|
277
291
|
const keys = []
|
|
278
|
-
for (const entry of
|
|
292
|
+
for (const entry of envelope.entries) {
|
|
279
293
|
keys.push(entry.key)
|
|
280
294
|
for (const key of Object.values(entry.sidecars ?? {})) keys.push(key)
|
|
281
295
|
}
|
|
296
|
+
// The manifest blob table of wire version 10: one `blob-<hex>.lit` file per
|
|
297
|
+
// out-of-line literal, shared by every block whose dictionary names it.
|
|
298
|
+
for (const blob of envelope.blobs ?? []) keys.push(blob.key)
|
|
282
299
|
return keys
|
|
283
300
|
}
|
|
284
301
|
|
|
285
|
-
/** Read the named artifacts and concatenate them into one region. */
|
|
286
|
-
function regionOf (store, keys) {
|
|
287
|
-
const chunks = keys.map((key) => readWhole(joinPath(store.generationDir, key)))
|
|
288
|
-
let total = 0
|
|
289
|
-
for (const chunk of chunks) total += chunk.length
|
|
290
|
-
const blob = new Uint8Array(total)
|
|
291
|
-
const artifacts = []
|
|
292
|
-
let offset = 0
|
|
293
|
-
for (let index = 0; index < chunks.length; index += 1) {
|
|
294
|
-
blob.set(chunks[index], offset)
|
|
295
|
-
artifacts.push({ key: keys[index], offset, len: chunks[index].length })
|
|
296
|
-
offset += chunks[index].length
|
|
297
|
-
}
|
|
298
|
-
return { blob, artifacts }
|
|
299
|
-
}
|
|
300
|
-
|
|
301
302
|
/**
|
|
302
303
|
* An open store held inside the engine. Hold this object for as long as the
|
|
303
304
|
* process answers questions about the generation, then `close()` it.
|
|
@@ -316,6 +317,9 @@ export class StoreHandle {
|
|
|
316
317
|
this.artifacts = envelope.artifacts
|
|
317
318
|
this.bytes = envelope.bytes
|
|
318
319
|
this.rows = envelope.rows
|
|
320
|
+
// Wire version 10 only: how many out-of-line literals the engine
|
|
321
|
+
// resolved when it opened the handle. Zero elsewhere.
|
|
322
|
+
this.blobs = envelope.blobs ?? 0
|
|
319
323
|
this.closed = false
|
|
320
324
|
}
|
|
321
325
|
|
|
@@ -366,7 +370,7 @@ export function openStoreHandle (engine, store, options = {}) {
|
|
|
366
370
|
keys = options.keys
|
|
367
371
|
} else if (typeof options.sparql === 'string') {
|
|
368
372
|
const plan = planQuery(engine, store, options.sparql)
|
|
369
|
-
keys = plan.keys.concat(plan.sidecarKeys ?? [])
|
|
373
|
+
keys = plan.keys.concat(plan.sidecarKeys ?? [], plan.blobKeys ?? [])
|
|
370
374
|
} else {
|
|
371
375
|
keys = manifestKeys(engine, store)
|
|
372
376
|
}
|
package/l4-assets/l4factoidal.js
CHANGED
|
Binary file
|
package/l4-assets/version.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"engine": "lean4",
|
|
3
3
|
"note": "The Lean 4 (L4Factoidal) engine's own build provenance and claims -- distinct from ../version.json, which is the F* engine's. Do not merge the two: they come from different toolchains, different git SHAs in general, and carry different proof obligations (issue #618).",
|
|
4
4
|
"version": "0.1.0",
|
|
5
|
-
"gitSha": "
|
|
6
|
-
"builtAt": "2026-09-
|
|
5
|
+
"gitSha": "e32b58ac2ec41dca4f86ef6e6b6f1a5cc6f46726",
|
|
6
|
+
"builtAt": "2026-09-05T20:46:27+00:00",
|
|
7
7
|
"leanToolchain": "leanprover/lean4:v4.33.1",
|
|
8
8
|
"emscripten": "emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 6.0.8-git",
|
|
9
9
|
"abiVersion": "1",
|
|
10
|
-
"wasmSha256": "
|
|
11
|
-
"wasmBytes":
|
|
10
|
+
"wasmSha256": "a7720d5d563014d875d26f63576993166c45b8f3eef7a6be9b75adfebd626500",
|
|
11
|
+
"wasmBytes": 5857701,
|
|
12
12
|
"claims": {
|
|
13
13
|
"source": "formal/lean4 (L4Factoidal): no sorry, no user axioms, no native_decide; W3C behaviour pinned by build-time #guard",
|
|
14
14
|
"suitesAtBuildSha": "see docs/test-results and formal/lean4/PORT_NOTES.md at gitSha"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factoidal/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Factoidal: a linked information system with graph data and the Web at its heart - RDF parsing, SPARQL 1.1 query, canonicalization and entailment for Node and the browser (JS + Wasm), grounded in F* and Lean 4. Parser and algebra spec verified in F*; on-disk backend has unverified OCaml-side optimization layers being migrated back to F*.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sparql",
|