@factoidal/core 0.1.0 → 0.2.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 +82 -0
- package/README.md +52 -0
- package/factoidal-npm-entry.js +13657 -30789
- package/factoidal-npm-entry.wasm.assets/{code-7ac046580f1bbdda8dc6.wasm → code-5a7fc68f2ab1323718b8.wasm} +0 -0
- package/factoidal-npm-entry.wasm.js +2 -2
- package/factoidal.js +11448 -25890
- package/fn.js +39 -0
- package/l4-assets/l4factoidal.js +137 -0
- package/l4-assets/l4factoidal.mjs +2 -0
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +16 -0
- package/l4-core.d.ts +159 -0
- package/l4-core.js +188 -0
- package/l4.d.ts +36 -0
- package/l4.js +90 -0
- package/lib/api.js +162 -2
- package/package.json +23 -3
- package/select.d.ts +116 -0
- package/select.js +492 -0
- package/version.json +3 -3
package/l4-core.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// factoidal — Node entry point for the **Lean 4** engine.
|
|
2
|
+
//
|
|
3
|
+
// The same typed public API shape as index.js / wasm.js (parse /
|
|
4
|
+
// query / update / serialize / canonicalize / dataFactory / Dataset /
|
|
5
|
+
// the closure and checker family), served by the Lean 4 extraction
|
|
6
|
+
// compiled to WebAssembly (L4Factoidal — formal/lean4/) instead of
|
|
7
|
+
// the F* extraction. Asset resolution is l4.js's three-step ladder:
|
|
8
|
+
// @factoidal/lean, $FACTOIDAL_L4_ASSETS, then the repository checkout.
|
|
9
|
+
//
|
|
10
|
+
// const factoidal = require('@factoidal/core/l4-core');
|
|
11
|
+
// const ds = await factoidal.parse('<a> <b> "c" .', { format: 'ntriples' });
|
|
12
|
+
//
|
|
13
|
+
// Surface: the subset capabilities() reports. Operations the Lean
|
|
14
|
+
// engine does not implement (SHACL, ShEx, HDT, COTTAS, VC crypto,
|
|
15
|
+
// entailment-regime query modes, tableauMaterialise /
|
|
16
|
+
// tableauDlInconsistent, RML/CSVW/JSON-LD/RIF) reject with a clear
|
|
17
|
+
// engine-capability error rather than silently falling back to the F*
|
|
18
|
+
// engine — the point of this entry is that everything it answers came
|
|
19
|
+
// through the Lean extraction. See ./select.js (factoidal/select) for
|
|
20
|
+
// an explicit, observable lean/fstar/lean1st/fstar1st/slowcompareboth
|
|
21
|
+
// switch built on top of this and ./index.js (issue #618); this module
|
|
22
|
+
// itself never falls back. owlIsConsistent / owlEntails ARE served
|
|
23
|
+
// (the three-valued OWL DL verdict, formal/lean4 issue 586) when the
|
|
24
|
+
// resolved wasm carries the ops — an older bundle answers "unknown op",
|
|
25
|
+
// which surfaces as the entryResult error, so probe the `ops`
|
|
26
|
+
// reflection before relying on them. The export list mirrors wasm.js
|
|
27
|
+
// so callers can swap engines by import path alone.
|
|
28
|
+
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const { buildApi } = require('./lib/api.js');
|
|
32
|
+
const rdfjs = require('./rdfjs.js');
|
|
33
|
+
const l4 = require('./l4.js');
|
|
34
|
+
const pkg = require('./package.json');
|
|
35
|
+
|
|
36
|
+
const NOT_SUPPORTED =
|
|
37
|
+
'factoidal/l4-core: this operation is not implemented by the Lean 4 ' +
|
|
38
|
+
'engine (see capabilities()); use the F* engine entry points for it. ' +
|
|
39
|
+
'If the Lean assets are missing, npm install @factoidal/lean or set ' +
|
|
40
|
+
'FACTOIDAL_L4_ASSETS.';
|
|
41
|
+
|
|
42
|
+
// The entry-ABI method names lib/api.js dispatches on, mapped onto the
|
|
43
|
+
// Lean wasm module's single dispatch export. Methods absent from this
|
|
44
|
+
// list are deliberately absent from the entry object: api.js
|
|
45
|
+
// typeof-guards each one and capabilities() reports the truth.
|
|
46
|
+
//
|
|
47
|
+
// The resolved wasm's full dispatch surface has 21 ops
|
|
48
|
+
// (`bin/linux-x86_64/l4factoidal ops`); this list wires 16 of them.
|
|
49
|
+
// `clParse` reads CLIF text into a CL syntax tree and reports its
|
|
50
|
+
// shape (sentence count, CL-vs-IKL dialect, canonical re-serialisation)
|
|
51
|
+
// -- it never produces RDF, so it is NOT part of the IKL-to-RDF
|
|
52
|
+
// projection family below. `clSerialize`, `clAlphaNorm` and
|
|
53
|
+
// `clNormalize` join it as the same kind of entry (CLIF text in, CLIF/
|
|
54
|
+
// CL text out, never RDF) -- see lib/api.js's doc comments on each for
|
|
55
|
+
// what their answers are worth (clSerialize's `roundTripProved: false`,
|
|
56
|
+
// clNormalize's `preserves`/`noIntrusion`). All four are Lean-only
|
|
57
|
+
// entries in the typed capability table (formal/fstar has no CL/IKL
|
|
58
|
+
// parser at all); see ./select.js, which throws on `backend:'fstar'`
|
|
59
|
+
// for each rather than answering from Lean silently.
|
|
60
|
+
//
|
|
61
|
+
// `clFiniteSat` (the fifth CL/IKL op) is DEFERRED from this typed
|
|
62
|
+
// layer, not excluded (owner decision, 2026-08-26): it takes a
|
|
63
|
+
// caller-supplied finite-interpretation JSON encoding that has no user
|
|
64
|
+
// yet, and a typed wrapper would freeze that shape before we know
|
|
65
|
+
// whether it is right. It stays reachable only through the raw
|
|
66
|
+
// dispatch ABI (`l4.call('clFiniteSat', [interpJson, clifText])`) --
|
|
67
|
+
// see lib/api.js's comment next to the other three clNormalize/
|
|
68
|
+
// clAlphaNorm/clSerialize wrappers for the same note.
|
|
69
|
+
//
|
|
70
|
+
// The IKL-to-RDF projection ("direction B": `clToDataset`,
|
|
71
|
+
// `queryWithIklService`) was removed from the engine source on
|
|
72
|
+
// 2026-08-26 (https://github.com/danbri/factoidal/issues/626): both
|
|
73
|
+
// ops went through the deleted `CL/ToRdf.lean`, whose content-addressed
|
|
74
|
+
// proposition graph names were never asked for. They had already been held off this JS surface by owner
|
|
75
|
+
// decision (https://github.com/danbri/factoidal/issues/618 — "I don't
|
|
76
|
+
// want npm code for direction b at this stage ... Take it out of npm
|
|
77
|
+
// for now."), and there is now nothing to wire. The compiled wasm
|
|
78
|
+
// artifact still CONTAINS both ops until it is rebuilt
|
|
79
|
+
// (https://github.com/danbri/factoidal/issues/627) — it is ahead of
|
|
80
|
+
// its source. The same #618 ruling is why an x-ikl-<suffix>
|
|
81
|
+
// entailment-regime family is rejected in lib/api.js's query() and
|
|
82
|
+
// fn.js's entail().
|
|
83
|
+
//
|
|
84
|
+
// CORRECTION 2026-08-26: an earlier version of this comment listed
|
|
85
|
+
// `clParse` alongside those two and cited #618 as the reason for all
|
|
86
|
+
// three. That misattributed a decision the owner did not make --
|
|
87
|
+
// `clParse` was unwired only because nobody had decided to wrap it,
|
|
88
|
+
// which is why it is wired above instead of here.
|
|
89
|
+
//
|
|
90
|
+
// A second group is withheld for an unrelated, non-owner reason:
|
|
91
|
+
//
|
|
92
|
+
// - dataset handles (`datasetOpen`/`datasetQuery`/`datasetUpdate`/
|
|
93
|
+
// `datasetSerialize`/`datasetClose`): ordinary RDF dataset handles,
|
|
94
|
+
// not part of the CL/IKL decision above. Held back only because
|
|
95
|
+
// lib/api.js has no typed wrapper shape for a stateful handle yet
|
|
96
|
+
// (every existing typed op is request/response) — a scope
|
|
97
|
+
// judgement, not an owner ruling. Wiring these in is a reasonable
|
|
98
|
+
// follow-up.
|
|
99
|
+
const OPS = [
|
|
100
|
+
'parseToDatasetJson',
|
|
101
|
+
'queryDataset',
|
|
102
|
+
'updateDataset',
|
|
103
|
+
'serializeNQuads',
|
|
104
|
+
'serializeTurtle',
|
|
105
|
+
'canonicalizeToNQuads',
|
|
106
|
+
'owlClosure',
|
|
107
|
+
'owlIsConsistent',
|
|
108
|
+
'owlEntails',
|
|
109
|
+
'rhoDfClosure',
|
|
110
|
+
'rhoDfFragmentCheck',
|
|
111
|
+
'rdfsPlusClosure',
|
|
112
|
+
'clParse',
|
|
113
|
+
'clSerialize',
|
|
114
|
+
'clAlphaNorm',
|
|
115
|
+
'clNormalize',
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
async function loadLeanEntry() {
|
|
119
|
+
const eng = await l4.loadL4();
|
|
120
|
+
if (typeof eng.call !== 'function') {
|
|
121
|
+
throw new Error(
|
|
122
|
+
'factoidal/l4-core: the resolved Lean wasm module predates the ' +
|
|
123
|
+
'dispatch ABI (no call export); update @factoidal/lean or ' +
|
|
124
|
+
'FACTOIDAL_L4_ASSETS to a build with l4_call'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const entry = { abiVersion: '1' };
|
|
128
|
+
for (const op of OPS) {
|
|
129
|
+
// lib/api.js's entryResult() takes the envelope as a JSON STRING
|
|
130
|
+
// (entry_jsoo.ml's wire shape). The Lean loader's call() parses the
|
|
131
|
+
// envelope and throws on {"ok":false}; re-encode both outcomes so
|
|
132
|
+
// this entry object is wire-compatible with the F* one.
|
|
133
|
+
entry[op] = (...args) => {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.stringify(eng.call(op, args.map(String)));
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return JSON.stringify({
|
|
138
|
+
ok: false,
|
|
139
|
+
error: String((err && err.message) || err),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return entry;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const api = buildApi({
|
|
148
|
+
engineName: 'l4',
|
|
149
|
+
loadEntry: loadLeanEntry,
|
|
150
|
+
runCli: async () => ({ exitCode: 1, stdout: '', stderr: NOT_SUPPORTED }),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
parse: api.parse,
|
|
155
|
+
query: api.query,
|
|
156
|
+
update: api.update,
|
|
157
|
+
serialize: api.serialize,
|
|
158
|
+
canonicalize: api.canonicalize,
|
|
159
|
+
graphs: api.graphs,
|
|
160
|
+
canonicalHash: api.canonicalHash,
|
|
161
|
+
owlClosure: api.owlClosure,
|
|
162
|
+
coreRdfsClosure: api.coreRdfsClosure,
|
|
163
|
+
coreRdfsCheck: api.coreRdfsCheck,
|
|
164
|
+
rhoDfClosure: api.rhoDfClosure,
|
|
165
|
+
rhoDfFragmentCheck: api.rhoDfFragmentCheck,
|
|
166
|
+
rdfsPlusClosure: api.rdfsPlusClosure,
|
|
167
|
+
// Lean-only: formal/fstar has no CL/IKL parser, so these are NOT
|
|
168
|
+
// exported from index.js/wasm.js. See the OPS comment above.
|
|
169
|
+
clParse: api.clParse,
|
|
170
|
+
clSerialize: api.clSerialize,
|
|
171
|
+
clAlphaNorm: api.clAlphaNorm,
|
|
172
|
+
clNormalize: api.clNormalize,
|
|
173
|
+
// Served by the Lean engine's dispatch ABI when the resolved wasm
|
|
174
|
+
// carries the ops (see the header note).
|
|
175
|
+
owlIsConsistent: api.owlIsConsistent,
|
|
176
|
+
owlEntails: api.owlEntails,
|
|
177
|
+
// Not implemented by this engine — kept on the surface so a caller
|
|
178
|
+
// swapping engines gets the pinned capability error, not
|
|
179
|
+
// `undefined is not a function`.
|
|
180
|
+
shaclValidate: api.shaclValidate,
|
|
181
|
+
shexValidate: api.shexValidate,
|
|
182
|
+
capabilities: api.capabilities,
|
|
183
|
+
Dataset: rdfjs.Dataset,
|
|
184
|
+
dataFactory: rdfjs.dataFactory,
|
|
185
|
+
engine: 'lean4-wasm',
|
|
186
|
+
available: l4.available,
|
|
187
|
+
version: pkg.version,
|
|
188
|
+
};
|
package/l4.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// TypeScript declarations for factoidal/l4 — the Lean 4 (L4Factoidal)
|
|
2
|
+
// engine compiled to WebAssembly, bundled in this package's l4-assets/
|
|
3
|
+
// since issue #618. See l4.js for the asset-resolution ladder and
|
|
4
|
+
// docs/designissues/2026-08-22-npm-l4-module-packaging.md for why the
|
|
5
|
+
// wasm was split out originally and why that no longer applies.
|
|
6
|
+
|
|
7
|
+
/** A term in SPARQL Query Results JSON shape, plus `var` for patterns. */
|
|
8
|
+
export interface L4Term {
|
|
9
|
+
type: 'uri' | 'literal' | 'bnode' | 'var';
|
|
10
|
+
value: string;
|
|
11
|
+
datatype?: string;
|
|
12
|
+
'xml:lang'?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface L4Triple {
|
|
16
|
+
subject: L4Term;
|
|
17
|
+
predicate: L4Term;
|
|
18
|
+
object: L4Term;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** SPARQL 1.1 Query Results JSON document. */
|
|
22
|
+
export interface L4ResultsDoc {
|
|
23
|
+
head: { vars: string[] };
|
|
24
|
+
results: { bindings: Array<Record<string, L4Term>> };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface L4Engine {
|
|
28
|
+
version(): string;
|
|
29
|
+
bgpQuery(triples: L4Triple[], bgp: L4Triple[]): Promise<L4ResultsDoc> | L4ResultsDoc;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const engine: 'lean4-wasm';
|
|
33
|
+
export function available(): boolean;
|
|
34
|
+
export function loadL4(): Promise<L4Engine>;
|
|
35
|
+
export function version(): Promise<string>;
|
|
36
|
+
export function bgpQuery(triples: L4Triple[], bgp: L4Triple[]): Promise<L4ResultsDoc>;
|
package/l4.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// factoidal/l4 — the Lean 4 engine (L4Factoidal compiled to wasm32)
|
|
2
|
+
// behind the same package namespace as the F*-extracted engines.
|
|
3
|
+
//
|
|
4
|
+
// const l4 = require('factoidal/l4'); // or import('factoidal/l4')
|
|
5
|
+
// await l4.version(); // "L4Factoidal ..."
|
|
6
|
+
// await l4.bgpQuery(triples, bgp); // SPARQL results JSON
|
|
7
|
+
//
|
|
8
|
+
// BUNDLED since issue #618: `@factoidal/core` now ships the Lean wasm
|
|
9
|
+
// directly under `l4-assets/` (l4factoidal.{js,mjs,wasm} + version.json
|
|
10
|
+
// — l4-assets/version.json, not this package's own version.json, which
|
|
11
|
+
// stays the F* engine's), so one `npm install @factoidal/core` gets
|
|
12
|
+
// both engines. The +23%-tarball objection that shaped the earlier
|
|
13
|
+
// companion-package split
|
|
14
|
+
// (docs/designissues/2026-08-22-npm-l4-module-packaging.md) no longer
|
|
15
|
+
// applies once measured against the real 21-op dispatch surface — see
|
|
16
|
+
// that doc's "issue #618: option A after all" section for the full
|
|
17
|
+
// argument. This module is now a resolver over FOUR sources, first hit
|
|
18
|
+
// wins:
|
|
19
|
+
//
|
|
20
|
+
// 1. this package's own l4-assets/ (the normal case, since #618);
|
|
21
|
+
// 2. the companion package `@factoidal/lean` (never published — kept
|
|
22
|
+
// only as a manual-override path for anyone pinning an older
|
|
23
|
+
// Lean build against a newer core; see npm/factoidal-lean/README.md);
|
|
24
|
+
// 3. $FACTOIDAL_L4_ASSETS — a directory holding
|
|
25
|
+
// l4factoidal.{js,mjs,wasm} (custom deployments);
|
|
26
|
+
// 4. the repository checkout layout (docs/web/hub/assets/l4/) —
|
|
27
|
+
// what the hub tests and in-repo development use.
|
|
28
|
+
//
|
|
29
|
+
// Every source's three files must stay together and keep their names:
|
|
30
|
+
// the Emscripten glue resolves the .wasm sidecar from its own basename
|
|
31
|
+
// (see skills/lean4-wasm-export, "the naming trap").
|
|
32
|
+
//
|
|
33
|
+
// API surface mirrors docs/web/hub/assets/l4/l4factoidal.js (phase-1
|
|
34
|
+
// ABI: version + bgpQuery). New exports appear here when they are
|
|
35
|
+
// added to formal/lean4/Wasm/Exports.lean and the wasm is rebuilt.
|
|
36
|
+
|
|
37
|
+
'use strict';
|
|
38
|
+
|
|
39
|
+
const { existsSync } = require('node:fs');
|
|
40
|
+
const path = require('node:path');
|
|
41
|
+
const { pathToFileURL } = require('node:url');
|
|
42
|
+
|
|
43
|
+
function resolveLoader() {
|
|
44
|
+
const inPkg = path.join(__dirname, 'l4-assets', 'l4factoidal.js');
|
|
45
|
+
if (existsSync(inPkg)) return inPkg;
|
|
46
|
+
try {
|
|
47
|
+
return require.resolve('@factoidal/lean/l4factoidal.js');
|
|
48
|
+
} catch { /* not installed */ }
|
|
49
|
+
const env = process.env.FACTOIDAL_L4_ASSETS;
|
|
50
|
+
if (env && existsSync(path.join(env, 'l4factoidal.js'))) {
|
|
51
|
+
return path.join(env, 'l4factoidal.js');
|
|
52
|
+
}
|
|
53
|
+
const repo = path.join(__dirname, '..', '..', 'docs', 'web', 'hub', 'assets', 'l4', 'l4factoidal.js');
|
|
54
|
+
if (existsSync(repo)) return repo;
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let l4Promise = null;
|
|
59
|
+
|
|
60
|
+
async function loadL4() {
|
|
61
|
+
if (l4Promise) return l4Promise;
|
|
62
|
+
const loaderPath = resolveLoader();
|
|
63
|
+
if (!loaderPath) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'factoidal/l4: Lean engine assets not found. They normally ship in ' +
|
|
66
|
+
"this package's own l4-assets/ directory — if it is missing, this " +
|
|
67
|
+
'checkout/install is incomplete. Otherwise set FACTOIDAL_L4_ASSETS ' +
|
|
68
|
+
'to a directory containing l4factoidal.js, l4factoidal.mjs and ' +
|
|
69
|
+
'l4factoidal.wasm, or install the (unpublished, override-only) ' +
|
|
70
|
+
'companion package @factoidal/lean.');
|
|
71
|
+
}
|
|
72
|
+
l4Promise = import(pathToFileURL(loaderPath).href).then((m) => m.loadL4());
|
|
73
|
+
return l4Promise;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
engine: 'lean4-wasm',
|
|
78
|
+
/** True when the wasm assets are resolvable without loading them. */
|
|
79
|
+
available: () => resolveLoader() !== null,
|
|
80
|
+
/** Load (once) and return the low-level engine handle. */
|
|
81
|
+
loadL4,
|
|
82
|
+
/** Engine identification string from the Lean side. */
|
|
83
|
+
version: async () => (await loadL4()).version(),
|
|
84
|
+
/**
|
|
85
|
+
* Evaluate a basic graph pattern over an in-memory triple list.
|
|
86
|
+
* Arguments and result use SPARQL Query Results JSON term shapes;
|
|
87
|
+
* see docs/web/hub/36-lean-in-the-browser.md for worked examples.
|
|
88
|
+
*/
|
|
89
|
+
bgpQuery: async (triples, bgp) => (await loadL4()).bgpQuery(triples, bgp),
|
|
90
|
+
};
|
package/lib/api.js
CHANGED
|
@@ -490,6 +490,31 @@ function buildApi(driver) {
|
|
|
490
490
|
}
|
|
491
491
|
const opts = options || {};
|
|
492
492
|
const entail = opts.entail || 'none';
|
|
493
|
+
// x-ikl-* is NOT IMPLEMENTED. It is not withheld by policy.
|
|
494
|
+
//
|
|
495
|
+
// CORRECTION 2026-08-26: this comment previously cited the owner's
|
|
496
|
+
// direction-B ruling (danbri/factoidal#618) as the reason. That
|
|
497
|
+
// ruling was about the IKL-to-RDF projection, not this regime
|
|
498
|
+
// family, and citing it here misstated a decision the owner did
|
|
499
|
+
// not make. The family is the OWNER'S design
|
|
500
|
+
// (danbri/factoidal#581); its Lean dispatch was deleted on
|
|
501
|
+
// 2026-08-26 as collateral of the projection purge
|
|
502
|
+
// (danbri/factoidal#626), not as its target. The semantics
|
|
503
|
+
// survived -- Unified/ClBridge.lean's asserted_merge_sound is the
|
|
504
|
+
// regime's soundness statement -- so restoring it is a dispatch
|
|
505
|
+
// branch, not a redesign. Checked explicitly (not just left out of
|
|
506
|
+
// ENTAIL_VALUES below) so a future ENTAIL_VALUES edit can't
|
|
507
|
+
// reopen this without deliberately removing this check too; see
|
|
508
|
+
// test/select.test.js's regression test.
|
|
509
|
+
if (/^x-ikl/i.test(entail)) {
|
|
510
|
+
throw new TypeError(
|
|
511
|
+
`query: entail '${entail}' is not implemented. The x-ikl-* ` +
|
|
512
|
+
"entailment regimes are the owner's design " +
|
|
513
|
+
"(danbri/factoidal#581); the Lean engine's dispatch for them " +
|
|
514
|
+
'was deleted on 2026-08-26 as collateral of the IKL-to-RDF ' +
|
|
515
|
+
'projection purge (danbri/factoidal#626). This is not a ' +
|
|
516
|
+
'policy exclusion.');
|
|
517
|
+
}
|
|
493
518
|
if (!ENTAIL_VALUES.has(entail)) {
|
|
494
519
|
throw new TypeError(
|
|
495
520
|
`query: entail must be one of ${[...ENTAIL_VALUES].join(', ')}`);
|
|
@@ -651,8 +676,13 @@ function buildApi(driver) {
|
|
|
651
676
|
|
|
652
677
|
if (outFormat === 'nquads') {
|
|
653
678
|
const e = await entry();
|
|
654
|
-
if (e
|
|
655
|
-
|
|
679
|
+
if (e) {
|
|
680
|
+
// Non-N-Quads documents normalize through parseToDatasetJson
|
|
681
|
+
// (docsToEntryNQuads), same as the turtle branch above — so
|
|
682
|
+
// entry-only drivers (e.g. l4-core.js) serve this path too.
|
|
683
|
+
const nq = docs.every((d) => d.ext === 'nq')
|
|
684
|
+
? docs.map((d) => d.content).join('')
|
|
685
|
+
: docsToEntryNQuads(e, docs, 'serialize(nquads)');
|
|
656
686
|
return entryResult(e.serializeNQuads(nq), 'serialize').nquads;
|
|
657
687
|
}
|
|
658
688
|
}
|
|
@@ -1254,6 +1284,132 @@ function buildApi(driver) {
|
|
|
1254
1284
|
return entryResult(e.xpathEval(xmlText, xpathExpr), 'xpathEval');
|
|
1255
1285
|
}
|
|
1256
1286
|
|
|
1287
|
+
/**
|
|
1288
|
+
* Parse Common Logic Interchange Format text (ISO/IEC 24707:2018),
|
|
1289
|
+
* with the IKL `that`-operator extension (entry_jsoo.ml's clParse
|
|
1290
|
+
* export -> L4Factoidal's CL/Clif.lean reader). Reads CLIF into a CL
|
|
1291
|
+
* syntax tree and reports its shape; it never produces RDF -- the
|
|
1292
|
+
* IKL-to-RDF projection that used to accompany it is deleted
|
|
1293
|
+
* (danbri/factoidal#626). Lean 4 only: formal/fstar has no CL/IKL parser, so this
|
|
1294
|
+
* function is absent from index.js/wasm.js -- see capabilities() /
|
|
1295
|
+
* factoidal/select's capability table.
|
|
1296
|
+
* @param {string} clifText
|
|
1297
|
+
* @returns {Promise<{ok: boolean, sentences: number, pureCL: boolean,
|
|
1298
|
+
* normalized: string}>} `pureCL` is a DIALECT flag, not a validity
|
|
1299
|
+
* or quality signal: true while the text stays inside ISO/IEC
|
|
1300
|
+
* 24707 Common Logic, false once it uses IKL's `that` operator.
|
|
1301
|
+
* Both values are returned only for text that parsed; a CLIF text
|
|
1302
|
+
* that fails to parse rejects instead (e.g. a bare `(that S)` used
|
|
1303
|
+
* as a proposition rather than a term -- see the GUIDE).
|
|
1304
|
+
*/
|
|
1305
|
+
async function clParse(clifText) {
|
|
1306
|
+
if (typeof clifText !== 'string') {
|
|
1307
|
+
throw new TypeError('clParse: clifText must be a string');
|
|
1308
|
+
}
|
|
1309
|
+
const e = await entry();
|
|
1310
|
+
if (!e) throw pendingError('clParse');
|
|
1311
|
+
requireEntryFn(e, 'clParse', 'Common Logic / IKL parse');
|
|
1312
|
+
return entryResult(e.clParse(clifText), 'clParse');
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Read Common Logic Interchange Format text and write it back out in
|
|
1317
|
+
* the canonical spacing of the CLIF writer (entry_jsoo.ml's
|
|
1318
|
+
* clSerialize export -> L4Factoidal's CL/Clif.lean reader/writer
|
|
1319
|
+
* pair). Lean 4 only: formal/fstar has no CL/IKL parser, so this
|
|
1320
|
+
* function is absent from index.js/wasm.js -- see capabilities() /
|
|
1321
|
+
* factoidal/select's capability table.
|
|
1322
|
+
*
|
|
1323
|
+
* `roundTripProved` is always `false`. The round-trip lemma
|
|
1324
|
+
* `clif_roundTrip` (`CL/ClifAdequacy.lean`) is an OPEN lemma: the
|
|
1325
|
+
* fragment boundary `marksLexable` is MEASURED, not proved. The
|
|
1326
|
+
* field is in the envelope, unmodified, so a caller does not have to
|
|
1327
|
+
* go and find that out.
|
|
1328
|
+
* @param {string} clifText
|
|
1329
|
+
* @returns {Promise<{ok: boolean, clif: string, sentences: number,
|
|
1330
|
+
* roundTripProved: false}>}
|
|
1331
|
+
*/
|
|
1332
|
+
async function clSerialize(clifText) {
|
|
1333
|
+
if (typeof clifText !== 'string') {
|
|
1334
|
+
throw new TypeError('clSerialize: clifText must be a string');
|
|
1335
|
+
}
|
|
1336
|
+
const e = await entry();
|
|
1337
|
+
if (!e) throw pendingError('clSerialize');
|
|
1338
|
+
requireEntryFn(e, 'clSerialize', 'Common Logic / IKL serialize');
|
|
1339
|
+
return entryResult(e.clSerialize(clifText), 'clSerialize');
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* Alpha-normalise Common Logic Interchange Format text: the canonical
|
|
1344
|
+
* representative of each sentence's bound-variable-renaming
|
|
1345
|
+
* equivalence class (entry_jsoo.ml's clAlphaNorm export ->
|
|
1346
|
+
* L4Factoidal's `CL/Alpha.lean`, `Sentence.alphaNorm`). Bound names
|
|
1347
|
+
* become `v1`, `v2`, ... in traversal order, so two sentences that
|
|
1348
|
+
* differ only in bound-variable names produce byte-identical output
|
|
1349
|
+
* -- IKL GUIDE Appendix B condition (1): renaming a bound variable
|
|
1350
|
+
* does not change the proposition expressed. Lean 4 only: formal/fstar
|
|
1351
|
+
* has no CL/IKL parser, so this function is absent from
|
|
1352
|
+
* index.js/wasm.js -- see capabilities() / factoidal/select's
|
|
1353
|
+
* capability table.
|
|
1354
|
+
* @param {string} clifText
|
|
1355
|
+
* @returns {Promise<{ok: boolean, clif: string, sentences: number}>}
|
|
1356
|
+
*/
|
|
1357
|
+
async function clAlphaNorm(clifText) {
|
|
1358
|
+
if (typeof clifText !== 'string') {
|
|
1359
|
+
throw new TypeError('clAlphaNorm: clifText must be a string');
|
|
1360
|
+
}
|
|
1361
|
+
const e = await entry();
|
|
1362
|
+
if (!e) throw pendingError('clAlphaNorm');
|
|
1363
|
+
requireEntryFn(e, 'clAlphaNorm', 'Common Logic / IKL alpha-normalise');
|
|
1364
|
+
return entryResult(e.clAlphaNorm(clifText), 'clAlphaNorm');
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Hayes's satisfiability-preserving reduction of IKL to Common Logic
|
|
1369
|
+
* (entry_jsoo.ml's clNormalize export -> L4Factoidal's
|
|
1370
|
+
* `CL/Normalize.lean`, `normalizeText`; danbri/factoidal#625), over a
|
|
1371
|
+
* whole text: one head text and one shared tail, with the
|
|
1372
|
+
* proposition-name counter running across the text. Lean 4 only:
|
|
1373
|
+
* formal/fstar has no CL/IKL parser, so this function is absent from
|
|
1374
|
+
* index.js/wasm.js -- see capabilities() / factoidal/select's
|
|
1375
|
+
* capability table.
|
|
1376
|
+
*
|
|
1377
|
+
* Two limits, both real, both in the answer, neither hidden:
|
|
1378
|
+
* - `preserves: "satisfiability"` -- the reduction preserves
|
|
1379
|
+
* satisfiability, NOT equivalence. It suits entailment and
|
|
1380
|
+
* consistency testing; it is not a transformation to apply to
|
|
1381
|
+
* data you intend to keep.
|
|
1382
|
+
* - `noIntrusion` IS the proof hypothesis `CL.noIntrSs [] []`
|
|
1383
|
+
* decides, not a paraphrase of it. The transformation runs either
|
|
1384
|
+
* way; when `noIntrusion` is `false`, the output is still
|
|
1385
|
+
* produced, but `tails_satisfiable` / `normalize_preserves` do
|
|
1386
|
+
* not cover that case.
|
|
1387
|
+
* @param {string} clifText
|
|
1388
|
+
* @returns {Promise<{ok: boolean, head: string[], tail: string[],
|
|
1389
|
+
* clif: string, sentences: number, thatCount: number,
|
|
1390
|
+
* noIntrusion: boolean, preserves: 'satisfiability',
|
|
1391
|
+
* provedUnder: string}>}
|
|
1392
|
+
*/
|
|
1393
|
+
async function clNormalize(clifText) {
|
|
1394
|
+
if (typeof clifText !== 'string') {
|
|
1395
|
+
throw new TypeError('clNormalize: clifText must be a string');
|
|
1396
|
+
}
|
|
1397
|
+
const e = await entry();
|
|
1398
|
+
if (!e) throw pendingError('clNormalize');
|
|
1399
|
+
requireEntryFn(e, 'clNormalize', 'Common Logic / IKL normalize');
|
|
1400
|
+
return entryResult(e.clNormalize(clifText), 'clNormalize');
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// clFiniteSat (entry_jsoo.ml's clFiniteSat -> L4Factoidal's
|
|
1404
|
+
// CL/FiniteSatTheorems.lean) is DEFERRED, not excluded, from this
|
|
1405
|
+
// typed layer (owner decision, 2026-08-26): it takes a caller-supplied
|
|
1406
|
+
// finite-interpretation JSON encoding (see Wasm/Ops/CL.lean's header
|
|
1407
|
+
// for the wire format) that has no user yet, and a typed wrapper here
|
|
1408
|
+
// would freeze that shape before anyone knows whether it is right. It
|
|
1409
|
+
// stays reachable through the raw dispatch ABI (`l4.call('clFiniteSat',
|
|
1410
|
+
// [interpJson, clifText])` / factoidal/select's `call('clFiniteSat',
|
|
1411
|
+
// ...)`), which needs no shape commitment on this layer.
|
|
1412
|
+
|
|
1257
1413
|
// -----------------------------------------------------------------
|
|
1258
1414
|
// VC Data Integrity crypto (eddsa-rdfc-2022) — HACL* wasm backend.
|
|
1259
1415
|
// entry_jsoo.ml's vc* exports realise VC_DataIntegrity's four crypto
|
|
@@ -2100,6 +2256,10 @@ function buildApi(driver) {
|
|
|
2100
2256
|
didKeyResolve,
|
|
2101
2257
|
xmlWellformed,
|
|
2102
2258
|
xpathEval,
|
|
2259
|
+
clParse,
|
|
2260
|
+
clSerialize,
|
|
2261
|
+
clAlphaNorm,
|
|
2262
|
+
clNormalize,
|
|
2103
2263
|
rifEval,
|
|
2104
2264
|
xsltTransform,
|
|
2105
2265
|
mathmlEval,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factoidal/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
7
|
"rdf",
|
|
@@ -57,7 +57,20 @@
|
|
|
57
57
|
"./factoidal.js": "./factoidal.js",
|
|
58
58
|
"./factoidal.wasm.js": "./factoidal.wasm.js",
|
|
59
59
|
"./factoidal.wasm.assets/*": "./factoidal.wasm.assets/*",
|
|
60
|
-
"./package.json": "./package.json"
|
|
60
|
+
"./package.json": "./package.json",
|
|
61
|
+
"./l4": {
|
|
62
|
+
"types": "./l4.d.ts",
|
|
63
|
+
"default": "./l4.js"
|
|
64
|
+
},
|
|
65
|
+
"./l4-core": {
|
|
66
|
+
"types": "./l4-core.d.ts",
|
|
67
|
+
"default": "./l4-core.js"
|
|
68
|
+
},
|
|
69
|
+
"./l4-assets/*": "./l4-assets/*",
|
|
70
|
+
"./select": {
|
|
71
|
+
"types": "./select.d.ts",
|
|
72
|
+
"default": "./select.js"
|
|
73
|
+
}
|
|
61
74
|
},
|
|
62
75
|
"files": [
|
|
63
76
|
"index.js",
|
|
@@ -68,6 +81,13 @@
|
|
|
68
81
|
"rdfjs.js",
|
|
69
82
|
"fn.js",
|
|
70
83
|
"fn.d.ts",
|
|
84
|
+
"l4.js",
|
|
85
|
+
"l4.d.ts",
|
|
86
|
+
"l4-core.js",
|
|
87
|
+
"l4-core.d.ts",
|
|
88
|
+
"l4-assets/",
|
|
89
|
+
"select.js",
|
|
90
|
+
"select.d.ts",
|
|
71
91
|
"lib/",
|
|
72
92
|
"browser.js",
|
|
73
93
|
"browser.d.ts",
|
package/select.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// TypeScript declarations for factoidal/select — the backend selector
|
|
2
|
+
// (issue #618): one typed surface over both the F* engine (./index.js)
|
|
3
|
+
// and the Lean 4 engine (./l4-core.js), with an explicit, observable
|
|
4
|
+
// lean/fstar/lean1st/fstar1st/slowcompareboth switch. See select.js for
|
|
5
|
+
// the full design rationale and the sub-question decisions.
|
|
6
|
+
|
|
7
|
+
export type Backend = 'lean' | 'fstar' | 'lean1st' | 'fstar1st' | 'slowcompareboth';
|
|
8
|
+
|
|
9
|
+
export const BACKENDS: readonly Backend[];
|
|
10
|
+
|
|
11
|
+
/** Function names the selector can route (see select.js's ALWAYS_IF_ENTRY/CAP_FLAG). */
|
|
12
|
+
export const ROUTABLE: readonly string[];
|
|
13
|
+
|
|
14
|
+
export interface CallOptions {
|
|
15
|
+
/** Per-call override of the selector instance's default backend. */
|
|
16
|
+
backend?: Backend;
|
|
17
|
+
/** lean1st/fstar1st only: function names routed to the OTHER engine
|
|
18
|
+
* regardless of what the primary engine implements. */
|
|
19
|
+
overrideFns?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SingleEngineResult<T> {
|
|
23
|
+
engine: 'lean' | 'fstar';
|
|
24
|
+
backend: Backend;
|
|
25
|
+
value: T;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CompareBothResult<T> {
|
|
29
|
+
engine: 'both';
|
|
30
|
+
backend: 'slowcompareboth';
|
|
31
|
+
/** false is a reportable finding, not a thrown error. */
|
|
32
|
+
agree: boolean;
|
|
33
|
+
comparison: { method: string };
|
|
34
|
+
lean: T;
|
|
35
|
+
fstar: T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type SelectResult<T> = SingleEngineResult<T> | CompareBothResult<T>;
|
|
39
|
+
|
|
40
|
+
/** { lean: boolean, fstar: boolean } per routable function name, derived live. */
|
|
41
|
+
export type CapabilityTable = Record<string, { lean: boolean; fstar: boolean }>;
|
|
42
|
+
|
|
43
|
+
export function capabilityTable(): Promise<CapabilityTable>;
|
|
44
|
+
export function engineSupports(engineApi: unknown, fnName: string): Promise<boolean>;
|
|
45
|
+
export function compareValues(
|
|
46
|
+
fnName: string, leanValue: unknown, fstarValue: unknown
|
|
47
|
+
): Promise<{ equal: boolean; method: string }>;
|
|
48
|
+
|
|
49
|
+
export interface SelectorOptions {
|
|
50
|
+
backend?: Backend;
|
|
51
|
+
overrideFns?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Selector {
|
|
55
|
+
readonly backend: Backend;
|
|
56
|
+
readonly overrideFns: string[];
|
|
57
|
+
call(fnName: string, args: unknown[], callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
58
|
+
capabilityTable(): Promise<CapabilityTable>;
|
|
59
|
+
parse(text: string, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
60
|
+
query(data: unknown, sparql: string, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
61
|
+
update(data: unknown, updateText: string, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
62
|
+
serialize(data: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<string>>;
|
|
63
|
+
canonicalize(data: unknown, callOptions?: CallOptions): Promise<SelectResult<string>>;
|
|
64
|
+
canonicalHash(data: unknown, callOptions?: CallOptions): Promise<SelectResult<string>>;
|
|
65
|
+
graphs(data: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
66
|
+
owlClosure(data: unknown, mode: string, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
67
|
+
owlIsConsistent(data: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
68
|
+
owlEntails(premise: unknown, conclusion: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
69
|
+
coreRdfsClosure(data: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
70
|
+
coreRdfsCheck(data: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
71
|
+
rhoDfClosure(data: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
72
|
+
rhoDfFragmentCheck(data: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
73
|
+
rdfsPlusClosure(data: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
74
|
+
shaclValidate(data: unknown, shapes: unknown, options?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
75
|
+
shexValidate(data: unknown, schema: string, focus: unknown, shape?: unknown, callOptions?: CallOptions): Promise<SelectResult<unknown>>;
|
|
76
|
+
/** Lean-only: `backend:'fstar'` throws (formal/fstar has no CL/IKL parser). */
|
|
77
|
+
clParse(clifText: string, callOptions?: CallOptions): Promise<SelectResult<{
|
|
78
|
+
ok: boolean;
|
|
79
|
+
sentences: number;
|
|
80
|
+
pureCL: boolean;
|
|
81
|
+
normalized: string;
|
|
82
|
+
}>>;
|
|
83
|
+
/** Lean-only, same reason as clParse. `roundTripProved` is always
|
|
84
|
+
* `false` -- `clif_roundTrip` is an OPEN lemma; see l4-core.d.ts. */
|
|
85
|
+
clSerialize(clifText: string, callOptions?: CallOptions): Promise<SelectResult<{
|
|
86
|
+
ok: boolean;
|
|
87
|
+
clif: string;
|
|
88
|
+
sentences: number;
|
|
89
|
+
roundTripProved: false;
|
|
90
|
+
}>>;
|
|
91
|
+
/** Lean-only, same reason as clParse. Bound-variable-renaming
|
|
92
|
+
* canonical form (IKL GUIDE Appendix B condition (1)). */
|
|
93
|
+
clAlphaNorm(clifText: string, callOptions?: CallOptions): Promise<SelectResult<{
|
|
94
|
+
ok: boolean;
|
|
95
|
+
clif: string;
|
|
96
|
+
sentences: number;
|
|
97
|
+
}>>;
|
|
98
|
+
/** Lean-only, same reason as clParse. Preserves SATISFIABILITY, not
|
|
99
|
+
* equivalence; `noIntrusion` is the proof hypothesis itself, not a
|
|
100
|
+
* paraphrase -- see l4-core.d.ts. */
|
|
101
|
+
clNormalize(clifText: string, callOptions?: CallOptions): Promise<SelectResult<{
|
|
102
|
+
ok: boolean;
|
|
103
|
+
head: string[];
|
|
104
|
+
tail: string[];
|
|
105
|
+
clif: string;
|
|
106
|
+
sentences: number;
|
|
107
|
+
thatCount: number;
|
|
108
|
+
noIntrusion: boolean;
|
|
109
|
+
preserves: 'satisfiability';
|
|
110
|
+
provedUnder: string;
|
|
111
|
+
}>>;
|
|
112
|
+
// clFiniteSat has no named sugar here (owner decision, 2026-08-26):
|
|
113
|
+
// use call('clFiniteSat', [interpJson, clifText], callOptions).
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createSelector(options?: SelectorOptions): Selector;
|