@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 +88 -0
- package/README.md +36 -8
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +4 -4
- package/package.json +10 -1
- package/version.json +1 -1
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
|
-
|
|
659
|
-
(`
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
|
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": "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": "
|
|
11
|
-
"wasmBytes":
|
|
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.
|
|
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