@factoidal/core 0.2.0 → 0.4.0

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.
Files changed (132) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/NOTICE +31 -0
  3. package/README.md +131 -1
  4. package/bin/engine.mjs +124 -0
  5. package/bin/factoidal.mjs +894 -0
  6. package/bin/pack-host.mjs +341 -0
  7. package/bin/pack-worker.mjs +31 -0
  8. package/bin/pack.mjs +174 -0
  9. package/bin/store.mjs +238 -0
  10. package/l4-assets/l4factoidal.js +125 -5
  11. package/l4-assets/l4factoidal.mjs +1 -1
  12. package/l4-assets/l4factoidal.wasm +0 -0
  13. package/l4-assets/package.json +4 -0
  14. package/l4-assets/version.json +5 -5
  15. package/l4.d.ts +14 -0
  16. package/l4.js +15 -1
  17. package/package.json +17 -2
  18. package/sample-store/CURRENT +1 -0
  19. package/sample-store/gen-1/manifest.sbm2 +0 -0
  20. package/sample-store/gen-1/manifest.tsv +14 -0
  21. package/sample-store/gen-1/predicate-0.ibk3 +0 -0
  22. package/sample-store/gen-1/predicate-0.ibk3.merkle +1 -0
  23. package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
  24. package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +1 -0
  25. package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
  26. package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +1 -0
  27. package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
  28. package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
  29. package/sample-store/gen-1/predicate-1.ibk3 +0 -0
  30. package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
  31. package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
  32. package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
  33. package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
  34. package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +1 -0
  35. package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
  36. package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +1 -0
  37. package/sample-store/gen-1/predicate-10.ibk3 +0 -0
  38. package/sample-store/gen-1/predicate-10.ibk3.merkle +1 -0
  39. package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
  40. package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
  41. package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
  42. package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +1 -0
  43. package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
  44. package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +1 -0
  45. package/sample-store/gen-1/predicate-11.ibk3 +0 -0
  46. package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
  47. package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
  48. package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +1 -0
  49. package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
  50. package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +1 -0
  51. package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
  52. package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +1 -0
  53. package/sample-store/gen-1/predicate-12.ibk3 +0 -0
  54. package/sample-store/gen-1/predicate-12.ibk3.merkle +1 -0
  55. package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
  56. package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +1 -0
  57. package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
  58. package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +1 -0
  59. package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
  60. package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +1 -0
  61. package/sample-store/gen-1/predicate-2.ibk3 +0 -0
  62. package/sample-store/gen-1/predicate-2.ibk3.merkle +1 -0
  63. package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
  64. package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +1 -0
  65. package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
  66. package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
  67. package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
  68. package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
  69. package/sample-store/gen-1/predicate-3.ibk3 +0 -0
  70. package/sample-store/gen-1/predicate-3.ibk3.merkle +1 -0
  71. package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
  72. package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +1 -0
  73. package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
  74. package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +1 -0
  75. package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
  76. package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +1 -0
  77. package/sample-store/gen-1/predicate-4.ibk3 +0 -0
  78. package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
  79. package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
  80. package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +1 -0
  81. package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
  82. package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +1 -0
  83. package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
  84. package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +2 -0
  85. package/sample-store/gen-1/predicate-5.ibk3 +0 -0
  86. package/sample-store/gen-1/predicate-5.ibk3.merkle +1 -0
  87. package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
  88. package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +1 -0
  89. package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
  90. package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +1 -0
  91. package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
  92. package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +1 -0
  93. package/sample-store/gen-1/predicate-6.ibk3 +0 -0
  94. package/sample-store/gen-1/predicate-6.ibk3.merkle +1 -0
  95. package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
  96. package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +1 -0
  97. package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
  98. package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +1 -0
  99. package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
  100. package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
  101. package/sample-store/gen-1/predicate-7.ibk3 +0 -0
  102. package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
  103. package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
  104. package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +2 -0
  105. package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
  106. package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +1 -0
  107. package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
  108. package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +1 -0
  109. package/sample-store/gen-1/predicate-8.ibk3 +0 -0
  110. package/sample-store/gen-1/predicate-8.ibk3.merkle +1 -0
  111. package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
  112. package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +1 -0
  113. package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
  114. package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +1 -0
  115. package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
  116. package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +1 -0
  117. package/sample-store/gen-1/predicate-9.ibk3 +0 -0
  118. package/sample-store/gen-1/predicate-9.ibk3.merkle +1 -0
  119. package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
  120. package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +1 -0
  121. package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
  122. package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +3 -0
  123. package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
  124. package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +2 -0
  125. package/sample-store.d.ts +16 -0
  126. package/sample-store.mjs +37 -0
  127. package/store-host/deno.mjs +262 -0
  128. package/store-host/errors.mjs +55 -0
  129. package/store-host/index.mjs +247 -0
  130. package/store-host/node.mjs +273 -0
  131. package/store-host/paths.mjs +77 -0
  132. package/version.json +22 -21
package/CHANGELOG.md CHANGED
@@ -1,5 +1,127 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 — 2026-09-04
4
+
5
+ The package builds a store of its own. `pack` and `activate` join
6
+ `inspect` and `query`, so `npm install @factoidal/core` gives a complete
7
+ RDF store — import, activate, query — with no native binary on the
8
+ machine. https://github.com/danbri/factoidal/issues/641
9
+
10
+ - **`factoidal pack INPUT OUTPUT`** builds one immutable Shardborough
11
+ generation from a Turtle, TriG, N-Triples or N-Quads file, streaming
12
+ it in 65,536-byte chunks through the Lean engine's WebAssembly module.
13
+ The generation is BYTE-IDENTICAL to what the native
14
+ `l4block-shard-pack` writes: `diff -r` is empty for the `ibk3` triple
15
+ layout and for the `ibk4` quad layout, on inputs up to 888,949
16
+ triples. `--layout ibk3|ibk4`, `--syntax`, `--base`.
17
+ - **`factoidal activate STORE GENERATION`** verifies every artifact
18
+ against the SHA-256 the manifest commits, and every cross-artifact
19
+ relation, then replaces `CURRENT` atomically. A generation that fails
20
+ verification never becomes current.
21
+ - **`factoidal sample-store`** prints the path of a store this package
22
+ now carries, so a fresh install answers a SPARQL query with nothing
23
+ else to download: 4,434 triples in 13 predicate blocks, five IPTC
24
+ NewsCodes vocabularies under CC BY 4.0. Also exported as
25
+ `@factoidal/core/sample-store`. See NOTICE.
26
+ - The engine gained a raw byte path out of the module
27
+ (`l4_call_blob_io`), so artifact bytes cross the boundary with no
28
+ encoding. Hexadecimal doubled them; base64 was refused. Measured on
29
+ the read path: 242,416 bytes and 96 ms hexadecimal against 4,893 bytes
30
+ and 70 ms raw.
31
+ - The pack hashes with HACL* SHA-256, the same primitive the native
32
+ packer uses. It hashed with the pure Lean SHA-256 in development,
33
+ which cost 3.3 times: 104 s against 31 s on 888,949 triples. Now at
34
+ parity, 29.24 s against the native packer's 29.38 s.
35
+ - `packBegin` takes a base IRI, defaulted by the command to
36
+ `file://<input>` so relative IRIs resolve exactly as the native packer
37
+ resolves them. `--base ''` asks for no base, which makes a relative
38
+ IRI a parse error rather than a silently different term.
39
+ - `pack` on a syntax the streaming fold cannot read now says so by name
40
+ rather than raising an unhandled error.
41
+ - `store-host` gained `readChunk` (a short read means end of file, not
42
+ an error), `writeNew` (create and fsync, refusing an existing file so
43
+ a name collision in a generation is reported) and `makeDirectory`, on
44
+ Node and on Deno both.
45
+ - `factoidal activate` runs on the raised stack too. Verification decodes
46
+ the same blocks the pack encoded, so it recurses as deep; the worker was
47
+ given to `pack` alone at first, and a 112,742-row generation packed
48
+ successfully and then failed to activate with `Maximum call stack size
49
+ exceeded`, leaving a store that could be built and not opened. Found by
50
+ installing the tarball and running the command, which is why that step
51
+ is in the release procedure. `tests/store-host/cli.mjs` now gates
52
+ pack-then-activate on both runtimes.
53
+ - `factoidal pack` no longer needs a runtime flag. The pack fold recurses
54
+ deeper than either runtime's default call stack allows, so an input
55
+ above roughly 0.5 MB ended with `Maximum call stack size exceeded`
56
+ (https://github.com/danbri/factoidal/issues/649). Under Node the pack
57
+ now runs on a `worker_threads` worker with a 64 MiB stack; under Deno
58
+ the command re-executes itself once with
59
+ `--v8-flags=--stack-size=65536`, which needs `--allow-run` and
60
+ `--allow-env` in addition to `--allow-read` and `--allow-write`.
61
+ `gene.ttl`, 17,363,312 bytes and 888,949 triples, packs on the default
62
+ stack of both runtimes, byte-identical to `l4block-shard-pack`.
63
+ `--no-worker` forces the in-process path, which still reports the
64
+ frame budget and the flag that raises it rather than crashing. A
65
+ browser tab has a fixed frame budget and no flag, so this does not
66
+ make an in-page packer possible.
67
+
68
+ Known limits, measured:
69
+
70
+ - `update` and `compact` still exit 3. The delta-log operations are
71
+ stage 4 of https://github.com/danbri/factoidal/issues/641.
72
+ - The `ibk4` quad layout reads the whole source rather than streaming,
73
+ because a quad block commits a graph-set summary over the entire
74
+ input. The wasm packer refuses a quad file above 128 MiB.
75
+ https://github.com/danbri/factoidal/issues/650
76
+ - A query plan is refused above 64 artifacts, 8,388,608 blob bytes or
77
+ 100,000 rows. https://github.com/danbri/factoidal/issues/648
78
+ - Packing in a browser tab is limited to about 7,800 distinct terms in
79
+ one block, whatever the file size, and no host flag raises it.
80
+ https://github.com/danbri/factoidal/issues/647
81
+
82
+ ## 0.3.0 — 2026-09-03
83
+
84
+ - The `factoidal` command answers SPARQL against a persisted
85
+ Shardborough store with no native binary. `factoidal inspect STORE`
86
+ decodes the manifest through the engine's `storeManifestInspect`
87
+ operation and prints the wire version, layout, blank-node profile and
88
+ the entry table; `factoidal query STORE 'SELECT ...'` asks the engine
89
+ which artifacts the query needs, reads exactly those, and hands their
90
+ bytes to `storeQuery` through one WebAssembly heap buffer with no
91
+ encoding. Every artifact is verified against the SHA-256 the manifest
92
+ commits before an answer is given. Formats: `table`, `json`,
93
+ `nquads`, `turtle`; `--explain` prints the artifact plan. Runs under
94
+ Node and under Deno (`--allow-read`).
95
+ - The three caps of the store query operation — 64 artifacts, 8388608
96
+ artifact bytes, 100000 rows — are reported with the cap, the value
97
+ and a next step, and are decided before a single file is read.
98
+ Nothing is truncated.
99
+ - `pack`, `activate`, `update` and `compact` still exit 3; they need
100
+ operations that do not exist yet
101
+ (https://github.com/danbri/factoidal/issues/641).
102
+ - A large piped result is no longer truncated: the command sets
103
+ `process.exitCode` instead of calling `process.exit()`, which dropped
104
+ everything past the 64 KiB pipe boundary.
105
+
106
+ ## 0.3.0 — Lean block-worker preview
107
+
108
+ - The bundled Lean-derived WASM artifact exports
109
+ `scanIBK2Predicate(ibk2Hex, predicateIri)` through `factoidal/l4`.
110
+ It validates a canonical IBK2 block and executes its selective predicate
111
+ scan, returning N-Triples plus a row count. This is a narrow physical
112
+ helper for the Shardborough work, not full SPARQL execution inside a storage
113
+ backend and not a high-throughput buffer ABI (the current hex transport is
114
+ intentionally diagnostic).
115
+ - The regenerated Lean artifact is synchronized across the package's
116
+ `l4-assets/` and the Hub build, and is exercised against the music IBK2
117
+ fixture by `tools/wasm-ibk2-smoke.mjs` in the repository.
118
+ - The current-format companion
119
+ `scanIBK3Predicate(ibk3Hex, predicateIri, blankNodeScope)` validates and
120
+ scans complete predicate-local IBK3 artifacts. Its mandatory source/dataset
121
+ scope preserves one blank node across blocks from the same RDF import while
122
+ keeping same-spelled labels in unrelated inputs apart. Hub post 51 composes
123
+ three real IBK3 scans into an editable Lean-WASM SPARQL query.
124
+
3
125
  ## 0.2.0 — both engines in one package, with a backend selector
4
126
 
5
127
  - The Lean 4 engine (`L4Factoidal`, wasm) now ships INSIDE
package/NOTICE ADDED
@@ -0,0 +1,31 @@
1
+ @factoidal/core — third-party content notices
2
+ =============================================
3
+
4
+ The code in this package is Apache-2.0; see LICENSE.
5
+
6
+ sample-store/
7
+ -------------
8
+
9
+ The bundled sample Shardborough store holds five IPTC NewsCodes
10
+ vocabularies, converted from the publisher's RDF/XML and packed into
11
+ IBK3 predicate blocks:
12
+
13
+ spamfstat, videoqualifier, subjectqualifier, videocodec, spct
14
+
15
+ Publisher: International Press Telecommunications Council
16
+ https://iptc.org/
17
+ Licence: Creative Commons Attribution 4.0 International (CC BY 4.0)
18
+ https://creativecommons.org/licenses/by/4.0/
19
+ Obtained through: https://github.com/danbri/skosdex third_party/skos
20
+
21
+ The IRIs, labels and structure are the IPTC's. The block layout, the
22
+ dictionary, the indexes and the manifest are this package's encoding of
23
+ them. Redistribution of the vocabulary content stays under CC BY 4.0.
24
+
25
+ hacl-wasm/
26
+ ----------
27
+
28
+ HACL* verified cryptographic primitives, from
29
+ https://github.com/hacl-star/hacl-star, Apache-2.0 / MIT. See the
30
+ provenance recorded in the Factoidal repository under
31
+ skills/node-crypto-haclstar-vc-wasm-build/.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @factoidal/core
2
2
 
3
- > First published cut (0.1.0). The API surface is early and may
3
+ > Release 0.3.0. The API surface is early and may
4
4
  > change before 1.0. This package was previously developed in-tree
5
5
  > under the placeholder names `factoidal` and `@danbri/foafos`; it was
6
6
  > never published under those names. See [CHANGELOG.md](CHANGELOG.md).
@@ -312,6 +312,16 @@ Two engines now ship in one package. `factoidal` and `factoidal/wasm`
312
312
  are the F\*-extracted engine, unchanged. The subpaths below are the
313
313
  Lean 4 engine (`L4Factoidal`, compiled to wasm).
314
314
 
315
+ `factoidal/l4` also exposes two deliberately narrow physical helpers:
316
+ `scanIBK2Predicate(ibk2Hex, predicateIri)` for the predecessor format and
317
+ `scanIBK3Predicate(ibk3Hex, predicateIri, blankNodeScope)` for the current
318
+ predicate-local format. They validate one canonical RDF block and scan its
319
+ named predicate, returning N-Triples and a row count. The IBK3 source scope
320
+ must be shared across blocks partitioned from one RDF import unit and differ
321
+ across unrelated units; this preserves document-scoped blank-node identity
322
+ when fragments are composed. The hexadecimal argument is a portable
323
+ diagnostic ABI, not the intended high-throughput buffer interface.
324
+
315
325
  ```js
316
326
  const l4 = require('factoidal/l4-core'); // Lean engine, same API shape
317
327
  const { select } = require('factoidal/select'); // choose an engine per call
@@ -358,6 +368,126 @@ model-theory modules run to about 22,000 lines — but only these reach
358
368
  JavaScript today. Everything else in the Lean tree is used through
359
369
  `parse`/`query`/`closure`, or not exposed at all.
360
370
 
371
+ ## The `factoidal` command: querying a persisted store
372
+
373
+ Installing this package puts a `factoidal` command on PATH. It reads a
374
+ **Shardborough** store — the on-disk format the Lean `l4block-*` tools
375
+ write — with no native binary: JavaScript reads the files and moves the
376
+ bytes, and the Lean engine running as WebAssembly makes every format
377
+ decision (parsing the manifest, choosing the blocks, verifying their
378
+ SHA-256, evaluating the SPARQL).
379
+
380
+ > This command is not the native F\* `factoidal` binary that the API
381
+ > table below refers to. That one is `bin/<platform>/factoidal` in the
382
+ > repository and takes subcommands such as `shex` and `compact`. This
383
+ > one takes `version`, `sample-store`, `inspect` and `query`.
384
+
385
+ ### First query, with nothing else to download
386
+
387
+ The package carries an activated store, so a fresh install answers a
388
+ SPARQL query at once:
389
+
390
+ ```console
391
+ $ npm install @factoidal/core
392
+ $ npx factoidal query "$(npx factoidal sample-store)" \
393
+ 'SELECT ?c ?l
394
+ WHERE { ?c <http://www.w3.org/2004/02/skos/core#inScheme>
395
+ <http://cv.iptc.org/newscodes/videocodec/> ;
396
+ <http://www.w3.org/2004/02/skos/core#prefLabel> ?l .
397
+ FILTER(langMatches(lang(?l), "en")) }
398
+ LIMIT 4'
399
+ c l
400
+ <http://cv.iptc.org/newscodes/videocodec/c001> "Analogue Black and White"@en-gb
401
+ <http://cv.iptc.org/newscodes/videocodec/c002> "PAL"@en-gb
402
+ <http://cv.iptc.org/newscodes/videocodec/c003> "NTSC"@en-gb
403
+ <http://cv.iptc.org/newscodes/videocodec/c004> "SECAM"@en-gb
404
+ ```
405
+
406
+ `factoidal sample-store` prints the path; `--json` adds what was
407
+ recorded when the store was packed. From JavaScript:
408
+
409
+ ```js
410
+ import { sampleStorePath, sampleStoreFacts } from '@factoidal/core/sample-store'
411
+ ```
412
+
413
+ The store holds 4,434 triples in 13 predicate blocks: five IPTC
414
+ NewsCodes vocabularies, published by the IPTC under CC BY 4.0 and taken
415
+ from [danbri/skosdex](https://github.com/danbri/skosdex). See `NOTICE`.
416
+
417
+ ### Any other store
418
+
419
+ ```console
420
+ $ factoidal inspect ./mystore
421
+ store ./mystore
422
+ generation gen-1 (activated through CURRENT)
423
+ manifest manifest.sbm2, 2372 bytes, wire version 6
424
+ layout predicate-ibk3-ptd1-sri2-tli1-oli2-merkle-v0
425
+ blank-node profile (none recorded)
426
+ term registry local-ibk3-ptd1-v0
427
+ fixed-chunk Merkle commitment yes
428
+ 5 entries, 393775 bytes, 6455 rows
429
+ generation directory holds 42 files, 846592 bytes
430
+
431
+ # rows bytes kind graphs predicate
432
+ 0 1800 110085 IBK3 - http://www.wikidata.org/prop/direct/P31
433
+ 1 719 35535 IBK3 - http://www.wikidata.org/prop/direct/P361
434
+ ...
435
+
436
+ $ factoidal query ./mystore 'SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }'
437
+ mode ibk3-paged-merkle-full-manifest(5), 5 artifacts, 393775 bytes read, plan declares 6455 block rows
438
+ n
439
+ "6455"^^<http://www.w3.org/2001/XMLSchema#integer>
440
+ 1 row
441
+ ```
442
+
443
+ `STORE` is a collection root: the directory holding `CURRENT`. The plan
444
+ line goes to stderr, so stdout carries only the result; `--quiet`
445
+ removes it.
446
+
447
+ | Option | What it does |
448
+ |---|---|
449
+ | `--format table` | default; a human display of the results |
450
+ | `--format json` | SELECT prints the engine's SPARQL 1.1 Query Results JSON; ASK and CONSTRUCT print the operation's envelope |
451
+ | `--format nquads` | CONSTRUCT only: the graph the engine serialized |
452
+ | `--format turtle` | CONSTRUCT only: that graph through the engine's own Turtle writer |
453
+ | `--explain` | print the artifacts the query needs and the open mode, and stop |
454
+ | `--limit N` | print at most N table rows; the total is always named |
455
+ | `--file PATH` | read the query text from a file |
456
+ | `--generation NAME` | read that generation rather than the activated one |
457
+
458
+ Under Deno, run the file directly; `inspect` and `query` need only
459
+ `--allow-read`:
460
+
461
+ ```console
462
+ $ deno run --allow-read node_modules/@factoidal/core/bin/factoidal.mjs query ./mystore 'ASK { ?s ?p ?o }'
463
+ ```
464
+
465
+ ### What the command answers for, and what it does not
466
+
467
+ * **Every artifact is verified.** The engine refuses the whole query
468
+ when a block's bytes do not hash to the SHA-256 the manifest commits,
469
+ and names the artifact.
470
+ * **Three caps.** One call reads at most 64 artifacts, 8388608 artifact
471
+ bytes and 100000 rows. A query over any of them is refused before a
472
+ single file is read, with the cap and the value named. Nothing is
473
+ truncated.
474
+ * **Committed artifacts only.** A store carrying uncompacted delta-log
475
+ updates is not served by this path; use the native `l4block-*` tools.
476
+ * **`pack`, `activate`, `update` and `compact` exit 3.** They need
477
+ WebAssembly operations that do not exist yet
478
+ (https://github.com/danbri/factoidal/issues/641).
479
+ * **Node's WebAssembly frame budget.** Some evaluator paths recurse once
480
+ per row. Measured 2026-09-03 on a 6455-row store, `SELECT ?s ?p ?o
481
+ WHERE { ?s ?p ?o }` overflows the stack under Node's default while
482
+ `SELECT *`, or the same query with a `LIMIT`, does not, and Deno
483
+ clears all of them. The command reports it and exits 1 rather than
484
+ crashing; `node --stack-size=4000` clears it.
485
+
486
+ Measured 2026-09-03 on macOS arm64, the 6455-triple `sequence_variant`
487
+ store, `SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }`, whole process
488
+ including start-up: 220 ms through this command, 33 ms through the
489
+ native `l4block-id-v3-query`.
490
+
361
491
  ## API (draft)
362
492
 
363
493
  The `factoidal` CLI (`bin/factoidal-cli/factoidal_cli.ml`, built to
package/bin/engine.mjs ADDED
@@ -0,0 +1,124 @@
1
+ // Loading the Lean engine (L4Factoidal compiled to WebAssembly) for the
2
+ // `factoidal` command, under Node and under Deno.
3
+ // https://github.com/danbri/factoidal/issues/641
4
+ //
5
+ // This file resolves three files that must stay together and keep their
6
+ // names -- l4factoidal.js, l4factoidal.mjs and l4factoidal.wasm -- and
7
+ // imports the loader. The Emscripten glue resolves the .wasm sidecar from
8
+ // its own basename, so a rename breaks the load on every runtime
9
+ // (skills/lean4-wasm-export, "the naming trap").
10
+ //
11
+ // `npm/factoidal/l4.js` does the same resolution for the CommonJS API. It
12
+ // is not reused here because it is CommonJS and this command must load
13
+ // under Deno without the require() compatibility path.
14
+ //
15
+ // WHAT THIS FILE IS ALLOWED TO DO
16
+ // Find the engine, load it, and turn bytes into the hexadecimal string
17
+ // two of the three store operations take as their small argument. It
18
+ // makes no format decision: it never parses a manifest, verifies a
19
+ // digest, decodes a block, or chooses an artifact.
20
+
21
+ import { readWhole } from '../store-host/index.mjs'
22
+ import { fileUrlToPath, joinPath } from '../store-host/paths.mjs'
23
+
24
+ /** Read one environment variable, or null where reading it is refused. */
25
+ function environment (name) {
26
+ try {
27
+ if (typeof globalThis.Deno !== 'undefined' && globalThis.Deno.env) {
28
+ const value = globalThis.Deno.env.get(name)
29
+ return typeof value === 'string' && value.length > 0 ? value : null
30
+ }
31
+ } catch (_error) {
32
+ // Deno without --allow-env. An absent override is the normal case.
33
+ return null
34
+ }
35
+ const value = globalThis.process && globalThis.process.env
36
+ ? globalThis.process.env[name]
37
+ : undefined
38
+ return typeof value === 'string' && value.length > 0 ? value : null
39
+ }
40
+
41
+ function readable (path) {
42
+ try {
43
+ readWhole(path)
44
+ return true
45
+ } catch (_error) {
46
+ return false
47
+ }
48
+ }
49
+
50
+ /** The package directory that holds this command. */
51
+ export function packageDirectory () {
52
+ return fileUrlToPath(new URL('..', import.meta.url).href).replace(/\/$/, '')
53
+ }
54
+
55
+ /**
56
+ * The loader file of the first engine source that exists, or null.
57
+ *
58
+ * Order, first hit wins:
59
+ * 1. this package's own l4-assets/ (what `npm install` gives);
60
+ * 2. $FACTOIDAL_L4_ASSETS (a custom deployment);
61
+ * 3. the repository checkout layout, docs/web/hub/assets/l4/.
62
+ */
63
+ export function resolveEngine () {
64
+ const inPackage = joinPath(packageDirectory(), 'l4-assets/l4factoidal.js')
65
+ if (readable(inPackage)) return inPackage
66
+ const override = environment('FACTOIDAL_L4_ASSETS')
67
+ if (override !== null) {
68
+ const path = joinPath(override, 'l4factoidal.js')
69
+ if (readable(path)) return path
70
+ throw new Error(`FACTOIDAL_L4_ASSETS=${override} holds no l4factoidal.js`)
71
+ }
72
+ const inCheckout = joinPath(packageDirectory(),
73
+ '../../docs/web/hub/assets/l4/l4factoidal.js')
74
+ if (readable(inCheckout)) return inCheckout
75
+ return null
76
+ }
77
+
78
+ let enginePromise = null
79
+
80
+ /**
81
+ * Load the engine once and return its handle: `call(op, args)` and
82
+ * `callBlob(op, args, bytes)`.
83
+ */
84
+ export function loadEngine () {
85
+ if (enginePromise !== null) return enginePromise
86
+ enginePromise = (async () => {
87
+ const loaderPath = resolveEngine()
88
+ if (loaderPath === null) {
89
+ throw new Error(
90
+ 'the Lean engine assets are missing. They normally ship in this ' +
91
+ "package's own l4-assets/ directory; if it is absent this install " +
92
+ 'is incomplete. Otherwise set FACTOIDAL_L4_ASSETS to a directory ' +
93
+ 'holding l4factoidal.js, l4factoidal.mjs and l4factoidal.wasm.')
94
+ }
95
+ const url = loaderPath.startsWith('file://')
96
+ ? loaderPath
97
+ : 'file://' + encodeURI(loaderPath).replace(/#/g, '%23')
98
+ const module = await import(url)
99
+ return module.loadL4()
100
+ })()
101
+ return enginePromise
102
+ }
103
+
104
+ const HEX = (() => {
105
+ const table = new Array(256)
106
+ for (let byte = 0; byte < 256; byte += 1) {
107
+ table[byte] = byte.toString(16).padStart(2, '0')
108
+ }
109
+ return table
110
+ })()
111
+
112
+ /**
113
+ * Lowercase hexadecimal of a byte array. This is a transport encoding for
114
+ * the manifest argument of `storeManifestInspect` and `storeQueryPlan`,
115
+ * not an interpretation of the bytes. Artifact bytes never take this
116
+ * path: they cross raw through `callBlob`.
117
+ * @param {Uint8Array} bytes
118
+ * @returns {string}
119
+ */
120
+ export function hexOfBytes (bytes) {
121
+ const parts = new Array(bytes.length)
122
+ for (let index = 0; index < bytes.length; index += 1) parts[index] = HEX[bytes[index]]
123
+ return parts.join('')
124
+ }