@factoidal/core 0.15.0 → 0.17.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 +29 -0
- package/README.md +3 -2
- package/authz/server.mjs +17 -3
- package/bin/engine.mjs +12 -10
- package/bin/factoidal.mjs +264 -22
- package/bin/pack-host.mjs +31 -1
- package/browser.d.ts +2 -0
- package/browser.js +7 -3
- package/index.d.ts +82 -12
- package/index.mjs +7 -3
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +6 -6
- package/lib/api.mjs +19 -6
- package/lib/evidence.mjs +16 -1
- package/lib/nodes.mjs +73 -7
- package/lib/sbm11.mjs +66 -1
- package/lib/store.mjs +10 -8
- package/lib/version.mjs +1 -1
- package/lws/server.mjs +34 -3
- package/package.json +2 -1
package/bin/pack-host.mjs
CHANGED
|
@@ -42,11 +42,12 @@
|
|
|
42
42
|
// route rescues an in-page packer. See the closing section of
|
|
43
43
|
// `docs/designissues/2026-09-03-npm-pack-in-wasm.md`.
|
|
44
44
|
|
|
45
|
-
import { StoreHostError, listGeneration, readWhole } from '../store-host/index.mjs'
|
|
45
|
+
import { StoreHostError, listGeneration, readWhole, writeNew } from '../store-host/index.mjs'
|
|
46
46
|
import { fileUrlToPath, joinPath } from '../store-host/paths.mjs'
|
|
47
47
|
import { loadEngine } from './engine.mjs'
|
|
48
48
|
import { PackError, packFile, packSupported, verifyGeneration } from './pack.mjs'
|
|
49
49
|
import { StoreOperationError, openStore } from './store.mjs'
|
|
50
|
+
import { sbm11Api } from '../lib/sbm11.mjs'
|
|
50
51
|
|
|
51
52
|
const isDeno = typeof globalThis.Deno !== 'undefined'
|
|
52
53
|
|
|
@@ -207,6 +208,7 @@ export function isStackOverflow (error) {
|
|
|
207
208
|
*/
|
|
208
209
|
export async function packHere (task, onProgress) {
|
|
209
210
|
const engine = await loadEngine()
|
|
211
|
+
if (task.kind === 'packSbm11') return await packSbm11Here(engine, task)
|
|
210
212
|
if (!packSupported(engine)) return { notWired: true }
|
|
211
213
|
if (task.kind === 'activate') {
|
|
212
214
|
return { report: activateHere(engine, task) }
|
|
@@ -221,6 +223,34 @@ export async function packHere (task, onProgress) {
|
|
|
221
223
|
return { report }
|
|
222
224
|
}
|
|
223
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Pack one document into the files of one SBM11 generation (op
|
|
228
|
+
* sbm11PackFiles, through lib/sbm11.mjs packSbm11) and write each file
|
|
229
|
+
* under the name the engine gave it. The document is read whole: the op
|
|
230
|
+
* takes the whole document, and refuses one above its limit.
|
|
231
|
+
*
|
|
232
|
+
* @returns {{notWired: true}|{report: object}}
|
|
233
|
+
*/
|
|
234
|
+
async function packSbm11Here (engine, task) {
|
|
235
|
+
const { packSbm11 } = sbm11Api(async () => engine)
|
|
236
|
+
const bytes = readWhole(task.input)
|
|
237
|
+
let files
|
|
238
|
+
try {
|
|
239
|
+
files = await packSbm11(bytes, { syntax: task.syntax, base: task.base })
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error && error.code === 'SBM11_NO_PACK_OP') return { notWired: true }
|
|
242
|
+
throw error
|
|
243
|
+
}
|
|
244
|
+
const written = []
|
|
245
|
+
let bytesWritten = 0
|
|
246
|
+
for (const [name, fileBytes] of files) {
|
|
247
|
+
writeNew(joinPath(task.output, name), fileBytes)
|
|
248
|
+
written.push(name)
|
|
249
|
+
bytesWritten += fileBytes.length
|
|
250
|
+
}
|
|
251
|
+
return { report: { bytesRead: bytes.length, written, bytesWritten } }
|
|
252
|
+
}
|
|
253
|
+
|
|
224
254
|
/**
|
|
225
255
|
* Verify one generation and answer the engine's verdict.
|
|
226
256
|
*
|
package/browser.d.ts
CHANGED
package/browser.js
CHANGED
|
@@ -26,7 +26,7 @@ import { buildApi } from './lib/api.mjs';
|
|
|
26
26
|
import { entryFromEngine } from './lib/lean-entry.mjs';
|
|
27
27
|
import { browserEngine } from './lib/browser-engine.mjs';
|
|
28
28
|
import { indexedDbDeltaLogStore } from './lib/delta-log-hosts.mjs';
|
|
29
|
-
import { sbm11Api, registerSbm11Source } from './lib/sbm11.mjs';
|
|
29
|
+
import { sbm11Api, registerSbm11Source, mapReader } from './lib/sbm11.mjs';
|
|
30
30
|
import { lwsSbm11Reader } from './lib/sbm11-lws.mjs';
|
|
31
31
|
|
|
32
32
|
const l4Engine = browserEngine('l4factoidal', new URL('./l4-assets/', import.meta.url).href);
|
|
@@ -65,8 +65,12 @@ const api = buildApi({
|
|
|
65
65
|
// (lib/sbm11.mjs; https://github.com/danbri/factoidal/issues/1009). In a
|
|
66
66
|
// browser every source needs a registered synchronous reader, for example
|
|
67
67
|
// over OPFS FileSystemSyncAccessHandle in a dedicated worker.
|
|
68
|
-
|
|
69
|
-
export
|
|
68
|
+
const sbm11 = sbm11Api(loadEngine);
|
|
69
|
+
export const openSbm11 = sbm11.openSbm11;
|
|
70
|
+
// Pack a document into the files of an SBM11 generation in memory, and a
|
|
71
|
+
// reader over such files (issue 1009 stage 2).
|
|
72
|
+
export const packSbm11 = sbm11.packSbm11;
|
|
73
|
+
export { registerSbm11Source, mapReader };
|
|
70
74
|
// A reader of an SBM11 generation held as LWS resources (issue 1010 KR3). In
|
|
71
75
|
// a browser worker its default transport is a synchronous XMLHttpRequest.
|
|
72
76
|
export { lwsSbm11Reader };
|
package/index.d.ts
CHANGED
|
@@ -529,14 +529,15 @@ export function canonicalize(
|
|
|
529
529
|
export function graphs(dataset: Dataset): Array<[name: NamedNode | BlankNode, graph: Dataset]>;
|
|
530
530
|
|
|
531
531
|
/**
|
|
532
|
-
* RDFC-1.0 canonical
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
532
|
+
* The RDFC-1.0 canonical hash of a single graph: the SHA-256 of its
|
|
533
|
+
* canonical N-Quads document, as 64 lowercase hexadecimal digits (RDF
|
|
534
|
+
* Dataset Canonicalization 1.0, section 4.4 note 2). The engine computes
|
|
535
|
+
* the canonical form and the digest. Every quad's graph component is
|
|
536
|
+
* dropped first, so isomorphic graphs (including under blank-node
|
|
537
|
+
* relabeling) give the same hash whatever graph name they were extracted
|
|
538
|
+
* from. Typically called with one entry of graphs()'s output. For the
|
|
539
|
+
* canonical text, use canonicalize(). Rejects when the input holds an
|
|
540
|
+
* RDF 1.2 triple term or the section 4.4 call budget is exceeded.
|
|
540
541
|
*/
|
|
541
542
|
export function canonicalHash(datasetOrGraph: Dataset): Promise<string>;
|
|
542
543
|
|
|
@@ -701,14 +702,31 @@ export interface StageInfo {
|
|
|
701
702
|
note: string;
|
|
702
703
|
/** false exactly when status is 'missing': such a stage does not run. */
|
|
703
704
|
runnable: boolean;
|
|
704
|
-
/**
|
|
705
|
+
/**
|
|
706
|
+
* true when this package has a function that runs the stage as a node AND
|
|
707
|
+
* the engine reports the stage runnable (an older engine that reports a
|
|
708
|
+
* stage 'missing' leaves it unbound).
|
|
709
|
+
*/
|
|
705
710
|
bound: boolean;
|
|
706
711
|
}
|
|
707
712
|
|
|
713
|
+
/**
|
|
714
|
+
* The status of a node: the registry status, or 'unproved' when the stage is
|
|
715
|
+
* 'proved' and one of the node's settings is outside its proved domain (op
|
|
716
|
+
* pipelineCheckNodes, Lean Dataflow/NodeSettings.lean).
|
|
717
|
+
*/
|
|
718
|
+
export type NodeStatus = StageInfo['status'] | 'unproved';
|
|
719
|
+
|
|
720
|
+
/** A setting outside the proved domain of its stage, with the reason. */
|
|
721
|
+
export interface OutsideSetting {
|
|
722
|
+
readonly setting: string;
|
|
723
|
+
readonly reason: string;
|
|
724
|
+
}
|
|
725
|
+
|
|
708
726
|
/** One evidence record: the node that ran and what it produced. */
|
|
709
727
|
export interface EvidenceRecord {
|
|
710
728
|
stage: string;
|
|
711
|
-
status:
|
|
729
|
+
status: NodeStatus;
|
|
712
730
|
contract: string | null;
|
|
713
731
|
assumptions: string[];
|
|
714
732
|
/** true iff `status` is 'proved'. */
|
|
@@ -719,6 +737,19 @@ export interface EvidenceRecord {
|
|
|
719
737
|
reason?: string;
|
|
720
738
|
/** The node's own output beside the graph: the explain result, the SHACL report, the written text, ... */
|
|
721
739
|
artefact: unknown;
|
|
740
|
+
/**
|
|
741
|
+
* The node's settings in canonical form (keys sorted at every depth; the
|
|
742
|
+
* defaults of absent settings that narrow the contract, e.g. `lenient:
|
|
743
|
+
* false`, `entail: 'none'`; a Dataset value as `{nquadsSha256}`). Present
|
|
744
|
+
* when the engine has the op pipelineCheckNodes.
|
|
745
|
+
*/
|
|
746
|
+
settings?: Record<string, unknown>;
|
|
747
|
+
/** `sha256:` and the hex SHA-256 of the canonical settings text. */
|
|
748
|
+
settingsDigest?: string;
|
|
749
|
+
/** With status 'unproved': the registry status ('proved'). */
|
|
750
|
+
registryStatus?: StageInfo['status'];
|
|
751
|
+
/** With status 'unproved': the settings outside the proved domain. */
|
|
752
|
+
outside?: OutsideSetting[];
|
|
722
753
|
}
|
|
723
754
|
|
|
724
755
|
/** The value on a wire. */
|
|
@@ -731,14 +762,31 @@ export interface Wire<G = unknown> {
|
|
|
731
762
|
export interface FactoidalNode {
|
|
732
763
|
readonly stage: string;
|
|
733
764
|
readonly kind: string | null;
|
|
734
|
-
|
|
765
|
+
/** The engine's status of this node with its settings (see NodeStatus). */
|
|
766
|
+
readonly status: NodeStatus;
|
|
767
|
+
/** The registry status of the stage. */
|
|
768
|
+
readonly registryStatus: StageInfo['status'];
|
|
735
769
|
readonly contract: string | null;
|
|
736
770
|
readonly assumptions: readonly string[];
|
|
737
771
|
readonly wireIn: string;
|
|
772
|
+
/**
|
|
773
|
+
* The output wire with these settings: for shippedEvaluator, 'Table' for
|
|
774
|
+
* SELECT, 'Answer' for ASK, 'Dataset' for CONSTRUCT and DESCRIBE (Lean
|
|
775
|
+
* formWire, evalContract_wire). With an older engine, the registry wire.
|
|
776
|
+
*/
|
|
738
777
|
readonly wireOut: string;
|
|
739
778
|
readonly runnable: boolean;
|
|
740
779
|
readonly bound: boolean;
|
|
780
|
+
/** The settings as given (passed to the package function that runs the stage). */
|
|
741
781
|
readonly params: Readonly<Record<string, unknown>>;
|
|
782
|
+
/** The settings as sent to the engine: JSON values, a Dataset as {$nquads}. */
|
|
783
|
+
readonly settingsWire: Readonly<Record<string, unknown>>;
|
|
784
|
+
/** The engine's canonical settings, or null with an older engine. */
|
|
785
|
+
readonly settings: Readonly<Record<string, unknown>> | null;
|
|
786
|
+
readonly settingsDigest: string | null;
|
|
787
|
+
readonly outside: readonly OutsideSetting[];
|
|
788
|
+
/** Values the engine read from the settings: `form` of a query, `mode` ('1.1' | '1.2') of a reader. */
|
|
789
|
+
readonly derived: Readonly<Record<string, string>> | null;
|
|
742
790
|
}
|
|
743
791
|
|
|
744
792
|
/** The engine's check of a list of stages (op pipelineCheck, Lean pipelineReport). */
|
|
@@ -755,9 +803,17 @@ export interface PipelineCheck {
|
|
|
755
803
|
readonly assumptions: readonly string[];
|
|
756
804
|
readonly contract: { readonly theorem: string; readonly stages: readonly (string | null)[] };
|
|
757
805
|
readonly stages: readonly {
|
|
758
|
-
readonly name: string; readonly status:
|
|
806
|
+
readonly name: string; readonly status: NodeStatus; readonly checked: boolean;
|
|
759
807
|
readonly contract: string | null; readonly assumptions: readonly string[];
|
|
760
808
|
readonly wireIn: string; readonly wireOut: string;
|
|
809
|
+
/** The fields below come from the op pipelineCheckNodes only. */
|
|
810
|
+
readonly registryStatus?: StageInfo['status'];
|
|
811
|
+
readonly settings?: Record<string, unknown>;
|
|
812
|
+
readonly settingsDigest?: string;
|
|
813
|
+
/** The role of each setting: 'parameter' | 'selects-wire' | 'inert' | 'narrows' | 'unlisted'. */
|
|
814
|
+
readonly roles?: Readonly<Record<string, string>>;
|
|
815
|
+
readonly outside?: readonly OutsideSetting[];
|
|
816
|
+
readonly derived?: Readonly<Record<string, string>>;
|
|
761
817
|
}[];
|
|
762
818
|
}
|
|
763
819
|
|
|
@@ -1136,6 +1192,18 @@ export function openSbm11(source: string, options?: { reader?: Sbm11Reader }): P
|
|
|
1136
1192
|
/** Serve `source` with `reader`; `null` removes it. */
|
|
1137
1193
|
export function registerSbm11Source(source: string, reader: Sbm11Reader | null): void;
|
|
1138
1194
|
|
|
1195
|
+
/** Pack an RDF document into the files of one SBM11 generation, in memory
|
|
1196
|
+
* (wasm op `sbm11PackFiles`, https://github.com/danbri/factoidal/issues/1009
|
|
1197
|
+
* stage 2): file name to bytes, the files `l4sbm11-pack` writes for the same
|
|
1198
|
+
* quads, without `CURRENT`. Rejects with `code: 'SBM11_NO_PACK_OP'` on a wasm
|
|
1199
|
+
* module that has no such op. */
|
|
1200
|
+
export function packSbm11(text: string | Uint8Array,
|
|
1201
|
+
options: { syntax: 'turtle' | 'trig' | 'nquads' | 'ntriples' | 'rdfxml' | 'jsonld' | string; base?: string }
|
|
1202
|
+
): Promise<Map<string, Uint8Array>>;
|
|
1203
|
+
|
|
1204
|
+
/** A reader over files held in memory (for example the result of `packSbm11`). */
|
|
1205
|
+
export function mapReader(files: Map<string, Uint8Array> | Record<string, Uint8Array>): Sbm11Reader;
|
|
1206
|
+
|
|
1139
1207
|
/** One LWS resource an SBM11 reader read (https://github.com/danbri/factoidal/issues/1010). */
|
|
1140
1208
|
export interface Sbm11ReadEvidence {
|
|
1141
1209
|
resource: string;
|
|
@@ -1570,6 +1638,8 @@ export function capabilities(): Promise<{
|
|
|
1570
1638
|
stages: boolean;
|
|
1571
1639
|
/** The op pipelineCheck: pipe() and run() use the engine's type check and assumption union. */
|
|
1572
1640
|
pipelineCheck: boolean;
|
|
1641
|
+
/** The op pipelineCheckNodes: node settings in evidence, and the evaluator's output wire from the query form. */
|
|
1642
|
+
pipelineCheckNodes: boolean;
|
|
1573
1643
|
tableau: boolean;
|
|
1574
1644
|
rml: boolean;
|
|
1575
1645
|
csvw: boolean;
|
package/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { readFileSync } from 'node:fs';
|
|
|
21
21
|
import { join } from 'node:path';
|
|
22
22
|
import { buildApi } from './lib/api.mjs';
|
|
23
23
|
import { entryFromEngine } from './lib/lean-entry.mjs';
|
|
24
|
-
import { sbm11Api, registerSbm11Source, setDefaultSbm11Reader } from './lib/sbm11.mjs';
|
|
24
|
+
import { sbm11Api, registerSbm11Source, setDefaultSbm11Reader, mapReader } from './lib/sbm11.mjs';
|
|
25
25
|
import { nodeDefaultReaderFactory } from './lib/sbm11-node.mjs';
|
|
26
26
|
import { lwsSbm11Reader as lwsSbm11ReaderWith } from './lib/sbm11-lws.mjs';
|
|
27
27
|
import { nodeSyncTransport } from './lib/sync-http-node.mjs';
|
|
@@ -74,8 +74,12 @@ export const clearServiceEndpoints = api.clearServiceEndpoints;
|
|
|
74
74
|
// (lib/sbm11.mjs; https://github.com/danbri/factoidal/issues/1009). In Node a
|
|
75
75
|
// source with no registered reader is a directory.
|
|
76
76
|
setDefaultSbm11Reader(nodeDefaultReaderFactory());
|
|
77
|
-
|
|
78
|
-
export
|
|
77
|
+
const sbm11 = sbm11Api(loadEngine);
|
|
78
|
+
export const openSbm11 = sbm11.openSbm11;
|
|
79
|
+
// Pack a document into the files of an SBM11 generation in memory, and a
|
|
80
|
+
// reader over such files (issue 1009 stage 2).
|
|
81
|
+
export const packSbm11 = sbm11.packSbm11;
|
|
82
|
+
export { registerSbm11Source, mapReader };
|
|
79
83
|
|
|
80
84
|
// A reader of an SBM11 generation held as LWS resources (lib/sbm11-lws.mjs;
|
|
81
85
|
// https://github.com/danbri/factoidal/issues/1010 KR3). In Node the default
|
package/l4-assets/l4factoidal.js
CHANGED
|
Binary file
|
package/l4-assets/version.json
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
"engine": "lean4",
|
|
3
3
|
"note": "The Lean 4 (L4Factoidal) engine build provenance and claims: the only engine of @factoidal/core since 0.9.0 (issue 713). This committed file is a placeholder. tools/fetch-builds.sh --set web (through tools/stamp-l4-version.mjs) writes the provenance members from the danbri/factoidal-builds release that built the wasm; formal/lean4/Wasm/build-wasm.sh step 9 writes them for a local build. Do not commit a stamped copy.",
|
|
4
4
|
"stamped": true,
|
|
5
|
-
"release": "danbri/factoidal-builds build-
|
|
6
|
-
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/
|
|
7
|
-
"gitSha": "
|
|
8
|
-
"builtAt": "2026-10-
|
|
5
|
+
"release": "danbri/factoidal-builds build-fa72ba5bc828-web",
|
|
6
|
+
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40439252690",
|
|
7
|
+
"gitSha": "fa72ba5bc828c1eb75572c3436e9d739f76fdd57",
|
|
8
|
+
"builtAt": "2026-10-27T03:13:16Z",
|
|
9
9
|
"leanToolchain": "leanprover/lean4:v4.33.1",
|
|
10
10
|
"emscripten": "6.0.8",
|
|
11
11
|
"abiVersion": "1",
|
|
12
12
|
"engineVersion": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
|
|
13
13
|
"version": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
|
|
14
|
-
"wasmSha256": "
|
|
15
|
-
"wasmBytes":
|
|
14
|
+
"wasmSha256": "ebac38a482d03d4a6ce1cf4a59164288628fed6957483dd55180743e7a3daf40",
|
|
15
|
+
"wasmBytes": 17071472,
|
|
16
16
|
"claims": {
|
|
17
17
|
"source": "formal/lean4 (L4Factoidal): no sorry, no user axioms, no native_decide; W3C behaviour pinned by build-time #guard",
|
|
18
18
|
"suitesAtBuildSha": "see docs/test-results and formal/lean4/PORT_NOTES.md at gitSha"
|
package/lib/api.mjs
CHANGED
|
@@ -684,7 +684,15 @@ function buildApi(driver) {
|
|
|
684
684
|
if (r.kind === 'construct' || r.kind === 'describe') {
|
|
685
685
|
return c.fromNQuads(r.nquads, ds.prefixes);
|
|
686
686
|
}
|
|
687
|
-
|
|
687
|
+
const rows = bindingsFromSrj(r.srj);
|
|
688
|
+
// The engine's warnings (class dataset-iri-not-found for a FROM or FROM
|
|
689
|
+
// NAMED IRI that names no graph, https://github.com/danbri/factoidal/issues/1032),
|
|
690
|
+
// passed through unchanged as a non-enumerable `warnings` property, so
|
|
691
|
+
// the rows themselves are unchanged.
|
|
692
|
+
if (Array.isArray(r.warnings) && r.warnings.length > 0) {
|
|
693
|
+
Object.defineProperty(rows, 'warnings', { value: r.warnings, enumerable: false });
|
|
694
|
+
}
|
|
695
|
+
return rows;
|
|
688
696
|
}
|
|
689
697
|
|
|
690
698
|
async function shaclOnHandle(c, ds, shapes, opts) {
|
|
@@ -876,9 +884,12 @@ function buildApi(driver) {
|
|
|
876
884
|
}
|
|
877
885
|
|
|
878
886
|
/**
|
|
879
|
-
* RDFC-1.0 canonical
|
|
880
|
-
*
|
|
881
|
-
*
|
|
887
|
+
* The RDFC-1.0 canonical hash of one graph: the SHA-256, as 64 lowercase
|
|
888
|
+
* hexadecimal digits, of the canonical N-Quads document (RDF Dataset
|
|
889
|
+
* Canonicalization 1.0, section 4.4 note 2). Every quad's graph name is
|
|
890
|
+
* dropped first (op datasetGraphAsDefault with the union of the graphs);
|
|
891
|
+
* then op datasetCanonicalHash computes the canonical form and the digest
|
|
892
|
+
* in Lean. canonicalize() answers the canonical text.
|
|
882
893
|
* @param {Dataset} datasetOrGraph
|
|
883
894
|
* @returns {Promise<string>}
|
|
884
895
|
*/
|
|
@@ -889,10 +900,11 @@ function buildApi(driver) {
|
|
|
889
900
|
datasetOrGraph.size; // throws the disposed error
|
|
890
901
|
const c = datasetOrGraph._ctx;
|
|
891
902
|
requireEntryFn(c.e, 'datasetGraphAsDefault', 'canonicalHash');
|
|
903
|
+
requireEntryFn(c.e, 'datasetCanonicalHash', 'canonicalHash');
|
|
892
904
|
const merged = c.wrap(entryResult(c.e.datasetGraphAsDefault(datasetOrGraph.handle,
|
|
893
905
|
JSON.stringify({ type: 'union' })), 'canonicalHash'), {});
|
|
894
906
|
try {
|
|
895
|
-
return merged.
|
|
907
|
+
return entryResult(c.e.datasetCanonicalHash(merged.handle), 'canonicalHash').hash;
|
|
896
908
|
} finally {
|
|
897
909
|
merged.dispose();
|
|
898
910
|
}
|
|
@@ -2140,6 +2152,7 @@ function buildApi(driver) {
|
|
|
2140
2152
|
explain: has('explainGraph'),
|
|
2141
2153
|
stages: has('stageList'),
|
|
2142
2154
|
pipelineCheck: has('pipelineCheck'),
|
|
2155
|
+
pipelineCheckNodes: has('pipelineCheckNodes'),
|
|
2143
2156
|
entailment: has('queryRaw'),
|
|
2144
2157
|
tableau: has('tableauMaterialise') && has('tableauDlInconsistent'),
|
|
2145
2158
|
rml: has('rmlMap'),
|
|
@@ -2176,7 +2189,7 @@ function buildApi(driver) {
|
|
|
2176
2189
|
// The node-composition API (lib/nodes.mjs, issue 1002 step 2).
|
|
2177
2190
|
const nodeApi = buildNodes({
|
|
2178
2191
|
entry, entryResult, requireEntryFn, Dataset,
|
|
2179
|
-
parse, closure, shaclValidate, query, serialize, canonicalize, explain,
|
|
2192
|
+
parse, closure, shaclValidate, query, serialize, canonicalize, explain, csvwToRdf,
|
|
2180
2193
|
});
|
|
2181
2194
|
|
|
2182
2195
|
return {
|
package/lib/evidence.mjs
CHANGED
|
@@ -16,6 +16,10 @@ export const isChecked = (status) => status === 'proved';
|
|
|
16
16
|
/** The reason text of an unchecked step. */
|
|
17
17
|
export function reasonOf(n) {
|
|
18
18
|
if (n.status === 'missing') return 'no contract';
|
|
19
|
+
if (n.status === 'unproved' && n.outside && n.outside.length > 0) {
|
|
20
|
+
return `status unproved (registry status ${n.registryStatus || 'proved'}); settings outside ` +
|
|
21
|
+
`the proved domain: ${n.outside.map((o) => `${o.setting} (${o.reason})`).join('; ')}`;
|
|
22
|
+
}
|
|
19
23
|
return n.assumptions.length > 0
|
|
20
24
|
? `status ${n.status}; assumptions: ${n.assumptions.join(', ')}`
|
|
21
25
|
: `status ${n.status}`;
|
|
@@ -43,7 +47,10 @@ export function refusalFor(n, unchecked, hasRunner) {
|
|
|
43
47
|
/**
|
|
44
48
|
* One evidence record. `checked` is true iff the status is `proved`.
|
|
45
49
|
* A step that ran under the opt-in and is not checked also carries
|
|
46
|
-
* `unchecked: true` and `reason`.
|
|
50
|
+
* `unchecked: true` and `reason`. When the engine read the node's settings
|
|
51
|
+
* (op pipelineCheckNodes), the record also carries `settings` (canonical)
|
|
52
|
+
* and `settingsDigest`, and, when a setting left the proved domain,
|
|
53
|
+
* `registryStatus` and `outside`; its `status` is then `unproved`.
|
|
47
54
|
*/
|
|
48
55
|
export function makeRecord(n, artefact, unchecked) {
|
|
49
56
|
const checked = isChecked(n.status);
|
|
@@ -55,6 +62,14 @@ export function makeRecord(n, artefact, unchecked) {
|
|
|
55
62
|
checked,
|
|
56
63
|
artefact: artefact === undefined ? null : artefact,
|
|
57
64
|
};
|
|
65
|
+
if (n.settingsDigest) {
|
|
66
|
+
base.settings = n.settings;
|
|
67
|
+
base.settingsDigest = n.settingsDigest;
|
|
68
|
+
}
|
|
69
|
+
if (n.outside && n.outside.length > 0) {
|
|
70
|
+
base.registryStatus = n.registryStatus;
|
|
71
|
+
base.outside = n.outside;
|
|
72
|
+
}
|
|
58
73
|
if (unchecked && !checked) {
|
|
59
74
|
base.unchecked = true;
|
|
60
75
|
base.reason = reasonOf(n);
|
package/lib/nodes.mjs
CHANGED
|
@@ -26,6 +26,18 @@
|
|
|
26
26
|
// built before the op, pipe() and run() fall back to the union of the
|
|
27
27
|
// nodes' fields and report `wellTyped: null`.
|
|
28
28
|
//
|
|
29
|
+
// Node settings (https://github.com/danbri/factoidal/issues/975,
|
|
30
|
+
// https://github.com/danbri/factoidal/issues/1026): the params of node()
|
|
31
|
+
// are the node's settings. When the engine has the op pipelineCheckNodes
|
|
32
|
+
// (formal/lean4/Wasm/Ops/PipelineNodes.lean), the engine decides, per node:
|
|
33
|
+
// the settings in canonical form with their SHA-256 digest, the role of each
|
|
34
|
+
// setting in the stage's contract, the settings outside the proved domain
|
|
35
|
+
// (a proved stage with one is `unproved`), and the output wire the settings
|
|
36
|
+
// select (SELECT gives a Table, ASK an Answer, CONSTRUCT and DESCRIBE a
|
|
37
|
+
// Dataset, so a CONSTRUCT node can feed the next node). This file only marshals: a Dataset setting is sent as
|
|
38
|
+
// {"$nquads": its N-Quads text}. With an older engine, records carry no
|
|
39
|
+
// settings and the evaluator's output wire is the registry's.
|
|
40
|
+
//
|
|
29
41
|
// ES module, no Node built-ins: the browser loads this file as it is.
|
|
30
42
|
|
|
31
43
|
import { FactoidalError } from './errors.mjs';
|
|
@@ -37,7 +49,7 @@ import { refusalFor, makeRecord, summarize, unionAssumptions, unionLists } from
|
|
|
37
49
|
*/
|
|
38
50
|
export function buildNodes(deps) {
|
|
39
51
|
const { entry, entryResult, requireEntryFn, Dataset,
|
|
40
|
-
parse, closure, shaclValidate, query, serialize, canonicalize, explain } = deps;
|
|
52
|
+
parse, closure, shaclValidate, query, serialize, canonicalize, explain, csvwToRdf } = deps;
|
|
41
53
|
|
|
42
54
|
const reader = (format) => async (wire, params) => {
|
|
43
55
|
if (typeof wire.graph !== 'string') {
|
|
@@ -88,6 +100,14 @@ export function buildNodes(deps) {
|
|
|
88
100
|
rdfcStage: async (wire, params) => ({
|
|
89
101
|
artefact: { canonical: await canonicalize(wire.graph, params) },
|
|
90
102
|
}),
|
|
103
|
+
'CSVW (tabular data to RDF)': async (wire, params) => {
|
|
104
|
+
if (typeof wire.graph !== 'string') {
|
|
105
|
+
throw new TypeError(`node CSVW: the stage takes CSV text on its wire (got ${typeof wire.graph})`);
|
|
106
|
+
}
|
|
107
|
+
const { metadata, ...opts } = params;
|
|
108
|
+
const meta = metadata === undefined || typeof metadata === 'string' ? metadata : JSON.stringify(metadata);
|
|
109
|
+
return { graph: await csvwToRdf(wire.graph, meta, opts), artefact: { tabular: 'csv' } };
|
|
110
|
+
},
|
|
91
111
|
rdfsExplainGraphStage: async (wire, params) => {
|
|
92
112
|
const { description, ...opts } = params;
|
|
93
113
|
if (description === undefined) {
|
|
@@ -116,7 +136,10 @@ export function buildNodes(deps) {
|
|
|
116
136
|
theorems: Object.freeze([...s.theorems]),
|
|
117
137
|
assumptions: Object.freeze([...s.assumptions]),
|
|
118
138
|
tests: Object.freeze([...s.tests]),
|
|
119
|
-
bound:
|
|
139
|
+
// bound: the package has a runner AND the engine lets the stage
|
|
140
|
+
// run. A runner for a stage that an older engine reports as
|
|
141
|
+
// `missing` is not bound (the engine's registry decides).
|
|
142
|
+
bound: s.runnable && Object.prototype.hasOwnProperty.call(RUNNERS, s.name),
|
|
120
143
|
})));
|
|
121
144
|
})();
|
|
122
145
|
stageCache.catch(() => { stageCache = null; });
|
|
@@ -126,6 +149,32 @@ export function buildNodes(deps) {
|
|
|
126
149
|
|
|
127
150
|
const NODE = Symbol('factoidal.node');
|
|
128
151
|
|
|
152
|
+
// The settings as the engine reads them: JSON values, a Dataset as
|
|
153
|
+
// {"$nquads": text}. Anything else is refused.
|
|
154
|
+
function marshal(v, path) {
|
|
155
|
+
if (v === null || typeof v === 'string' || typeof v === 'boolean') return v;
|
|
156
|
+
if (typeof v === 'number') {
|
|
157
|
+
if (!Number.isFinite(v)) throw new TypeError(`node: setting ${path} is not a finite number`);
|
|
158
|
+
return v;
|
|
159
|
+
}
|
|
160
|
+
if (v instanceof Dataset) return { $nquads: v.toNQuads() };
|
|
161
|
+
if (Array.isArray(v)) return v.map((x, i) => marshal(x, `${path}[${i}]`));
|
|
162
|
+
if (typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype) {
|
|
163
|
+
const out = {};
|
|
164
|
+
for (const [k, x] of Object.entries(v)) if (x !== undefined) out[k] = marshal(x, `${path}.${k}`);
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
throw new TypeError(`node: setting ${path} must be a JSON value or a Dataset (got ${typeof v})`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const hasNodeCheck = () => !!engine && typeof engine.pipelineCheckNodes === 'function';
|
|
171
|
+
|
|
172
|
+
// The engine's check of nodes with settings (op pipelineCheckNodes).
|
|
173
|
+
function nodeCheck(items) {
|
|
174
|
+
return entryResult(engine.pipelineCheckNodes(JSON.stringify(
|
|
175
|
+
items.map((n) => ({ stage: n.stage, settings: n.settingsWire })))), 'pipelineCheckNodes');
|
|
176
|
+
}
|
|
177
|
+
|
|
129
178
|
/**
|
|
130
179
|
* A node for the registered stage `name`, with its parameters (the
|
|
131
180
|
* second argument of the package function it calls, e.g. `{shapes}`,
|
|
@@ -144,18 +193,28 @@ export function buildNodes(deps) {
|
|
|
144
193
|
throw new FactoidalError(`node: no registered stage named '${name}' (see stages())`,
|
|
145
194
|
{ code: 'option.invalid-value' });
|
|
146
195
|
}
|
|
196
|
+
const settingsWire = marshal({ ...(params || {}) }, 'params');
|
|
197
|
+
// The engine's reading of the settings, when it has the op.
|
|
198
|
+
let s = null;
|
|
199
|
+
if (hasNodeCheck()) s = nodeCheck([{ stage: info.name, settingsWire }]).stages[0];
|
|
147
200
|
return Object.freeze({
|
|
148
201
|
[NODE]: true,
|
|
149
202
|
stage: info.name,
|
|
150
203
|
kind: info.kind,
|
|
151
|
-
status: info.status,
|
|
204
|
+
status: s ? s.status : info.status,
|
|
205
|
+
registryStatus: info.status,
|
|
152
206
|
contract: info.contract,
|
|
153
207
|
assumptions: info.assumptions,
|
|
154
208
|
wireIn: info.wireIn,
|
|
155
|
-
wireOut: info.wireOut,
|
|
209
|
+
wireOut: s ? s.wireOut : info.wireOut,
|
|
156
210
|
runnable: info.runnable,
|
|
157
211
|
bound: info.bound,
|
|
158
212
|
params: Object.freeze({ ...(params || {}) }),
|
|
213
|
+
settingsWire,
|
|
214
|
+
settings: s ? s.settings : null,
|
|
215
|
+
settingsDigest: s ? s.settingsDigest : null,
|
|
216
|
+
outside: Object.freeze(s ? s.outside.map((o) => Object.freeze({ ...o })) : []),
|
|
217
|
+
derived: s ? Object.freeze({ ...s.derived }) : null,
|
|
159
218
|
});
|
|
160
219
|
}
|
|
161
220
|
|
|
@@ -174,8 +233,11 @@ export function buildNodes(deps) {
|
|
|
174
233
|
* or null when the loaded engine has no such op.
|
|
175
234
|
*/
|
|
176
235
|
function check(nodes) {
|
|
177
|
-
|
|
178
|
-
|
|
236
|
+
let r;
|
|
237
|
+
if (hasNodeCheck()) r = nodeCheck(nodes);
|
|
238
|
+
else if (engine && typeof engine.pipelineCheck === 'function') {
|
|
239
|
+
r = entryResult(engine.pipelineCheck(JSON.stringify(nodes.map((n) => n.stage))), 'pipelineCheck');
|
|
240
|
+
} else return null;
|
|
179
241
|
return Object.freeze({
|
|
180
242
|
wellTyped: r.wellTyped,
|
|
181
243
|
wireIn: r.wireIn,
|
|
@@ -184,7 +246,11 @@ export function buildNodes(deps) {
|
|
|
184
246
|
conversions: Object.freeze(r.conversions.map((c) => Object.freeze({ ...c }))),
|
|
185
247
|
assumptions: Object.freeze([...r.assumptions]),
|
|
186
248
|
contract: Object.freeze({ theorem: r.contract.theorem, stages: Object.freeze([...r.contract.stages]) }),
|
|
187
|
-
stages: Object.freeze(r.stages.map((s) => Object.freeze({
|
|
249
|
+
stages: Object.freeze(r.stages.map((s) => Object.freeze({
|
|
250
|
+
...s,
|
|
251
|
+
assumptions: Object.freeze([...s.assumptions]),
|
|
252
|
+
...(s.outside ? { outside: Object.freeze(s.outside.map((o) => Object.freeze({ ...o }))) } : {}),
|
|
253
|
+
}))),
|
|
188
254
|
});
|
|
189
255
|
}
|
|
190
256
|
|
package/lib/sbm11.mjs
CHANGED
|
@@ -68,6 +68,32 @@ export function setDefaultSbm11Reader (factory) {
|
|
|
68
68
|
defaultReader = factory
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* A reader over files held in memory: `files` is a Map (or a plain object)
|
|
73
|
+
* from file name to Uint8Array, for example the result of packSbm11. It
|
|
74
|
+
* moves bytes only; a read past the end of a file, or of a file it does not
|
|
75
|
+
* hold, answers null, and the engine refuses it with the file name.
|
|
76
|
+
*/
|
|
77
|
+
export function mapReader (files) {
|
|
78
|
+
const m = files instanceof Map ? files : new Map(Object.entries(files || {}))
|
|
79
|
+
return {
|
|
80
|
+
readAt (file, offset, length) {
|
|
81
|
+
const b = m.get(file)
|
|
82
|
+
if (!b || offset < 0 || length < 0 || offset + length > b.length) return null
|
|
83
|
+
return b.subarray(offset, offset + length)
|
|
84
|
+
},
|
|
85
|
+
size (file) {
|
|
86
|
+
const b = m.get(file)
|
|
87
|
+
return b ? b.length : null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function opsReflection (eng) {
|
|
93
|
+
const r = eng.call('ops', [])
|
|
94
|
+
return typeof r === 'string' ? JSON.parse(r) : r
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
function envelope (raw) {
|
|
72
98
|
const j = typeof raw === 'string' ? JSON.parse(raw) : raw
|
|
73
99
|
if (!j || j.ok !== true) {
|
|
@@ -151,5 +177,44 @@ export function sbm11Api (loadEngine) {
|
|
|
151
177
|
}
|
|
152
178
|
}
|
|
153
179
|
}
|
|
154
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Pack an RDF document into the files of one SBM11 generation, in memory
|
|
182
|
+
* (the wasm op sbm11PackFiles, formal/lean4/Wasm/Ops/Sbm11Pack.lean;
|
|
183
|
+
* https://github.com/danbri/factoidal/issues/1009 stage 2). `text` is a
|
|
184
|
+
* string or UTF-8 bytes; `options.syntax` is turtle, trig, nquads,
|
|
185
|
+
* ntriples, rdfxml or jsonld; `options.base` the base IRI. Resolves to a
|
|
186
|
+
* Map from file name to Uint8Array: the files `l4sbm11-pack` writes for the
|
|
187
|
+
* same quads (data files, `.merkle` sidecars, `manifest.sbm11`), without
|
|
188
|
+
* `CURRENT`. Open them with `openSbm11(name, { reader: mapReader(files) })`,
|
|
189
|
+
* or write each under its name into one directory. The engine names every
|
|
190
|
+
* file and computes every byte (iron rule 7).
|
|
191
|
+
*/
|
|
192
|
+
async function packSbm11 (text, options = {}) {
|
|
193
|
+
const syntax = options.syntax ?? options.format
|
|
194
|
+
if (typeof syntax !== 'string' || syntax === '') {
|
|
195
|
+
throw new TypeError('packSbm11: options.syntax is required (turtle, trig, nquads, ntriples, rdfxml or jsonld)')
|
|
196
|
+
}
|
|
197
|
+
const eng = await loadEngine()
|
|
198
|
+
const refl = opsReflection(eng)
|
|
199
|
+
const blobIoOps = Array.isArray(refl && refl.blobIoOps) ? refl.blobIoOps : []
|
|
200
|
+
if (typeof eng.callBlobIO !== 'function' || !blobIoOps.includes('sbm11PackFiles')) {
|
|
201
|
+
const e = new Error('packSbm11: this wasm module has no sbm11PackFiles op (it was built before ' +
|
|
202
|
+
'https://github.com/danbri/factoidal/issues/1009 stage 2)')
|
|
203
|
+
e.code = 'SBM11_NO_PACK_OP'
|
|
204
|
+
throw e
|
|
205
|
+
}
|
|
206
|
+
const bytes = typeof text === 'string' ? new TextEncoder().encode(text) : new Uint8Array(text)
|
|
207
|
+
const base = options.base == null ? '' : String(options.base)
|
|
208
|
+
const files = new Map()
|
|
209
|
+
let count = 1
|
|
210
|
+
for (let i = 0; i < count; i++) {
|
|
211
|
+
const answer = eng.callBlobIO('sbm11PackFiles', [syntax, base, String(i)], bytes)
|
|
212
|
+
const j = envelope(answer.envelope)
|
|
213
|
+
count = Number(j.count)
|
|
214
|
+
files.set(j.name, answer.bytes)
|
|
215
|
+
}
|
|
216
|
+
return files
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { openSbm11, packSbm11 }
|
|
155
220
|
}
|