@factoidal/core 0.4.0 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,93 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 — 2026-09-04
4
+
5
+ **Queries against a persisted store are about six times faster.** Measured
6
+ end to end the way a caller runs them — process start, engine load,
7
+ digest verification, block decode and scan — on a 141-graph store with a
8
+ 5,571,302-byte `skos:prefLabel` block of 45,806 rows, at the same machine
9
+ load:
10
+
11
+ | query | 0.4.0 | 0.5.0 |
12
+ |---|---|---|
13
+ | `CONTAINS` over labels, `LIMIT 8` | 12.03 s | **2.06 s** |
14
+ | the same for a second word | 12.18 s | **2.04 s** |
15
+ | the same for a third | 11.96 s | **2.02 s** |
16
+
17
+ Two causes, both fixed.
18
+
19
+ - **A quadratic byte copy in SHA-256.** `Crypto.processBlocks256` copied
20
+ the whole remaining message once per 64-byte block, so verifying an
21
+ artifact was quadratic in its size. Fitting `t = c*n^k` to the
22
+ admission step gave k = 2.08 before and **k = 0.98 after**. On the
23
+ three blocks of that store, admission went 1,179 / 2,150 / 11,069 ms to
24
+ 303 / 413 / 872 ms. Decode and evaluation were linear throughout.
25
+ - **`LIMIT` was not pushed down through `GRAPH`.** A `LIMIT 8` cost what
26
+ a full count cost, because `GRAPH ?g { ... }` fell through to the
27
+ reference evaluator over the whole materialised dataset. The push-down
28
+ now takes one `GRAPH` layer with a constant IRI or a variable.
29
+ `ORDER BY`, `OFFSET`, `DISTINCT`, `REDUCED`, `GROUP BY`, `HAVING`,
30
+ `VALUES` and aggregates still reject, two of them pinned by theorems.
31
+ - A `RangeError: Maximum call stack size exceeded` on `SELECT ... LIMIT 8`
32
+ is gone with it, because the query no longer materialises 45,806 rows
33
+ to return eight.
34
+
35
+ **A correctness fix found while measuring.** The pre-existing bare-BGP
36
+ `LIMIT` push-down could answer SHORT: a repeated variable (`?x ?p ?x`) or
37
+ an RDF-star triple term let the backend stop early on rows the match then
38
+ rejected. It now refuses both shapes.
39
+
40
+ **New exports.** `@factoidal/core/store`, `/pack` and `/engine`. A caller
41
+ can drive the store in process instead of spawning the command:
42
+
43
+ ```js
44
+ import { openStore, queryStore } from '@factoidal/core/store'
45
+ import { loadEngine } from '@factoidal/core/engine'
46
+ const engine = await loadEngine()
47
+ const store = openStore('/path/to/store', null)
48
+ const { result } = queryStore(engine, store, 'PREFIX skos: ... SELECT ...')
49
+ ```
50
+
51
+ **The engine carries a day of OWL work**: OWL RL 1,181 pass, 266 fail
52
+ (out of 1,457) and OWL DL about 1,326 pass, 121 fail (out of 1,457), both
53
+ against a conclusion check corrected to require one functional blank-node
54
+ mapping (RDF 1.1 Semantics §1.5 and the interpolation lemma). A false
55
+ clash was removed — the materialiser had minted one existential witness
56
+ for several obligations, so the engine denied three consistent
57
+ ontologies; ConsistencyTest went 758 pass, 4 fail to 761 pass, 1 fail.
58
+
59
+ **Named graphs now pack at scale.** The IBK4 quad path read the whole
60
+ source file and a 553 MB, 194-graph corpus could not be packed at all. It
61
+ streams now, and a quadratic term that only named graphs paid — a hash
62
+ map copied per quad in `addQuadFast` — is gone. Peak memory per source
63
+ byte fell from 37 and 20 to between 7.4 and 11.4; a 316,816,934-byte,
64
+ 194-graph input that used to fail now packs in 750 s at 2.34 GB. Byte
65
+ identity with the previous packer holds by theorem, not only by diff.
66
+
67
+ **Documentation corrected.** The GeoSPARQL section named functions that
68
+ do not exist. Six topological functions are implemented — `geof:sfEquals`,
69
+ `sfDisjoint`, `sfIntersects`, `sfTouches`, `sfWithin`, `sfContains` — and
70
+ they work against a persisted store, verified. There is no
71
+ `geof:distance`, `buffer`, `envelope`, `boundary`, `convexHull`, no
72
+ `relate` with a DE-9IM matrix, no CRS handling beyond the WKT literal and
73
+ no GML. Full text is SPARQL 1.1's own `CONTAINS` / `STRSTARTS` / `REGEX`,
74
+ evaluated per row after a block decodes: **there is no inverted index**.
75
+
76
+ Known limits, measured:
77
+
78
+ - A query is still O(rows) per search string, and nothing is retained
79
+ between queries: `storeQuery` re-reads, re-verifies and re-decodes the
80
+ block every call. A store handle that decodes once is the next step.
81
+ - `ORDER BY ... LIMIT n` still overflows the call stack above about
82
+ 14,576 materialised rows.
83
+ https://github.com/danbri/factoidal/issues/653
84
+ - `update` and `compact` still exit 3.
85
+ https://github.com/danbri/factoidal/issues/641
86
+ - A query plan is refused above 64 artifacts, 8,388,608 blob bytes or
87
+ 100,000 rows. https://github.com/danbri/factoidal/issues/648
88
+ - The two SHA-256 folds are checked equal by the FIPS 180-4 build-time
89
+ guards and the HACL* differential, not proved.
90
+
3
91
  ## 0.4.0 — 2026-09-04
4
92
 
5
93
  The package builds a store of its own. `pack` and `activate` join
package/README.md CHANGED
@@ -653,14 +653,42 @@ value transforms:
653
653
  API; the `_*` functions (e.g. `_deltaLogCorruptLastForTest`) are
654
654
  test-only and intentionally left untyped.
655
655
 
656
- ### GeoSPARQL
657
-
658
- There is no separate GeoSPARQL function: the `geof:` functions
659
- (`geof:sfWithin`, `geof:sfDisjoint`, `geof:distance`, `geof:envelope`,
660
- …) are built into the SPARQL engine and work through ordinary
661
- `query()` / `fn.query()` — e.g.
662
- `query(data, 'PREFIX geof: <http://www.opengis.net/def/function/geosparql/> SELECT ?a ?b WHERE { … FILTER(geof:sfWithin(?a, ?b)) }')`.
663
- Nothing to import; nothing "missing".
656
+ ### GeoSPARQL — six topological functions
657
+
658
+ The `geof:` functions below are built into the SPARQL engine and need no
659
+ import. They work through `query()` / `fn.query()` AND against a
660
+ persisted store through `factoidal query`, because both paths evaluate
661
+ in the same environment.
662
+
663
+ geof:sfEquals geof:sfDisjoint geof:sfIntersects
664
+ geof:sfTouches geof:sfWithin geof:sfContains
665
+
666
+ ```sparql
667
+ PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
668
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
669
+ SELECT ?a WHERE {
670
+ ?a :footprint ?w
671
+ FILTER(geof:sfWithin(?w, "POLYGON((0 0,0 2,2 2,2 0,0 0))"^^geo:wktLiteral))
672
+ }
673
+ ```
674
+
675
+ **What is NOT there**, stated so nobody plans around it: no
676
+ `geof:distance`, `geof:buffer`, `geof:envelope`, `geof:boundary`,
677
+ `geof:convexHull` or any other non-topological measure; no
678
+ `geof:relate` with a DE-9IM matrix; no coordinate reference system
679
+ handling beyond what the WKT literal carries; no GML literals. Geometry
680
+ comes from a WKT parser, so a shapefile, GeoJSON or GML source must be
681
+ converted to `geo:wktLiteral` before it is loaded.
682
+
683
+ ### Full text: SPARQL's own functions, no index
684
+
685
+ `CONTAINS`, `STRSTARTS`, `STRENDS` and `REGEX` (SPARQL 1.1 §17.4.3) are
686
+ implemented and are the way to search text. They are evaluated per row
687
+ after a block is decoded — **there is no inverted index and no
688
+ `text:query`-style extension**. Measured 2026-09-04: a `CONTAINS` over
689
+ 45,806 `skos:prefLabel` values in one block answers in about 6 seconds.
690
+ That is fine for a vocabulary and will not scale to a large literal
691
+ corpus.
664
692
 
665
693
  ## Limits (deliberate, documented)
666
694
 
@@ -37,7 +37,7 @@
37
37
  // bytes change.
38
38
 
39
39
  // Stamped by formal/lean4/Wasm/build-wasm.sh step 9 -- do not hand-edit.
40
- const WASM_VERSION = "125d391e0ccc";
40
+ const WASM_VERSION = "20551e4329bb";
41
41
 
42
42
  import createModule from './l4factoidal.mjs';
43
43
 
Binary file
@@ -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": "6b9f12003805f5bc1136949b12dc6b1f41885d00",
6
- "builtAt": "2026-09-03T23:23:57+00:00",
5
+ "gitSha": "4295b2917e700fb3ead2d74fad05c9d2f592b7d9",
6
+ "builtAt": "2026-09-04T20:28:04+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": "125d391e0ccc7be1a673d748d40b8443fd66bb5f0a6e3736223fd6ed19307d03",
11
- "wasmBytes": 5296153,
10
+ "wasmSha256": "20551e4329bb3fa363e4c64b9f9d62cc1371a2c563161d938226c0d2e102c87a",
11
+ "wasmBytes": 5384150,
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.4.0",
3
+ "version": "0.5.0",
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",
@@ -79,6 +79,15 @@
79
79
  "./sample-store": {
80
80
  "types": "./sample-store.d.ts",
81
81
  "default": "./sample-store.mjs"
82
+ },
83
+ "./store": {
84
+ "default": "./bin/store.mjs"
85
+ },
86
+ "./pack": {
87
+ "default": "./bin/pack.mjs"
88
+ },
89
+ "./engine": {
90
+ "default": "./bin/engine.mjs"
82
91
  }
83
92
  },
84
93
  "files": [
package/version.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.0",
2
+ "version": "0.5.0",
3
3
  "gitSha": "49f8ca4d70bf57c12fea445611e2b01b067bf6ba",
4
4
  "builtAt": "2026-08-26T21:45:40Z",
5
5
  "claims": {