@factoidal/core 0.15.0 → 0.16.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 +16 -0
- package/browser.d.ts +2 -0
- package/browser.js +7 -3
- package/index.d.ts +73 -4
- 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 +11 -2
- package/lib/evidence.mjs +16 -1
- package/lib/nodes.mjs +73 -7
- package/lib/sbm11.mjs +66 -1
- package/lib/version.mjs +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.16.0 (2026-10-26)
|
|
6
|
+
|
|
7
|
+
- Node settings in the evidence (https://github.com/danbri/factoidal/issues/975). The params of `node()` are the node's settings. With a wasm module that has the new op `pipelineCheckNodes` (Lean `Wasm/Ops/PipelineNodes.lean`, `Dataflow/NodeSettings.lean`), each evidence record carries `settings` (canonical: keys sorted, defaults of absent settings that narrow the contract such as `lenient: false` and `entail: 'none'`, a Dataset value as `{nquadsSha256}`) and `settingsDigest` (`sha256:` and the hex SHA-256 of the canonical text). A setting outside the proved domain of a proved stage (a lenient reader, an evaluator with `entail` other than `none` or with `output`, a key the stage's settings table does not name) makes the node's status `unproved`, with `registryStatus` and `outside` (each setting and the reason) in the record, so `run()` reports `proved: false`. A reader's `format` must name the stage's syntax (`ttl12` selects RDF 1.2 mode, reported as `derived.mode`); an evaluator's query must parse, else `node()` rejects. Design record: `docs/designissues/2026-10-25-pipeline-settings-and-provenance.md`.
|
|
8
|
+
- The output wire of a `shippedEvaluator` node follows its query form: SELECT gives `Table`, ASK `Answer`, CONSTRUCT and DESCRIBE `Dataset` (owner, 2026-10-25: "select give table? Sure"). So reader, CONSTRUCT, CONSTRUCT is well typed, and reader, SELECT, CONSTRUCT is not. Proved: every answer that meets the evaluator contract has the wire of its query form (`Dataflow.evalContract_wire`) (https://github.com/danbri/factoidal/issues/1026). The Lean wire enumeration has a new wire `table`.
|
|
9
|
+
- CSVW is a registered, bound stage: `node('CSVW (tabular data to RDF)', { metadata, mode, base, url })` runs `csvwToRdf` on CSV text and gives a Dataset and an evidence record with status `tested` (the W3C csv2rdf suite; no Lean contract, so `checked: false`) (https://github.com/danbri/factoidal/issues/975). `stages()` marks a stage `bound` only when the engine also reports it runnable.
|
|
10
|
+
- With the wasm of 0.15.0 or older (for example a mismatched local install), records have no settings, the evaluator's wire is the registry's `Answer`, and the CSVW stage stays `missing` and unbound.
|
|
11
|
+
|
|
12
|
+
- `packSbm11(text, { syntax, base })` packs an RDF document into the files of one SBM11 generation in memory and resolves to a `Map` from file name to bytes (data files, `.merkle` sidecars, `manifest.sbm11`; no `CURRENT`); `mapReader(files)` is a reader over such files for `openSbm11(source, { reader })`. The wasm op is `sbm11PackFiles` (Lean `Storage/Sbm11/PackBytes.lean`), one call per file. The files are byte for byte those of the native `l4sbm11-pack` for the same quads: 23 of 23 files for `test/fixtures/sbm11-tiny`, and all files of 605 of 605 W3C SPARQL 1.0 and 1.1 inputs (`SBM11_PACKFILES=1 tests/sbm11/census.sh`) (https://github.com/danbri/factoidal/issues/1009 stage 2). On the wasm of 0.15.0 or older, `packSbm11` rejects with `code: 'SBM11_NO_PACK_OP'`. The whole document and every file are in memory at once. Not yet: `factoidal pack` on SBM11, and a pack that streams its input.
|
|
13
|
+
- SPARQL: the inverse path `^p` keeps a pair whose object is a literal. SPARQL 1.1 §9.3 and §18.4 make `?x ^p ?y` the same as `?y p ?x`, so `?x` can bind a literal; up to 0.15.0 both evaluators (the in-memory one and the SBM11 id path) dropped those pairs, and `SELECT ?x ?y WHERE { ?x ^<http://e/p> ?y }` over `<http://e/s> <http://e/p> "v" .` gave no row. Apache Jena ARQ 5.6.0 gives the same rows as the fix (https://github.com/danbri/factoidal/issues/1023). Tests: `test/inverse-path-literal-1023.test.mjs` and the `#guard`s in `SPARQL/QueryTests.lean`. The F* tree has the same fix.
|
|
14
|
+
- SPARQL: a `FROM` or `FROM NAMED` IRI that names no graph of the dataset gives a warning of class `dataset-iri-not-found` in the `warnings` member of the query envelope (`class`, `clause` (`FROM` or `FROM NAMED`), `iri`, `message`), and on standard error in the native `l4factoidal query` command. The rows do not change: the clause adds no graph, as before (https://github.com/danbri/factoidal/issues/1032). Lean: `SPARQL.datasetWarnings` beside `applyDataset`, whose merge theorem is unchanged; `#guard`s in `SPARQL/QueryTests.lean` and `Wasm/Ops/Query.lean`. Tests: `test/from-iri-not-found.test.mjs` (plain assertions; https://github.com/danbri/factoidal/issues/1038 records why an expected failure that waits for a rebuild blocked the build of this release).
|
|
15
|
+
- Tests, no engine change: GeoSPARQL `geo:wktLiteral` values made with `STRDT(CONCAT("POINT(", STR(?lon), " ", STR(?lat), ")"), geo:wktLiteral)` in SELECT, in a sub-SELECT that feeds `geof:sfWithin`, and by an INSERT whose stored literal `geof:sfWithin` then tests; from a CSVW column whose datatype object is `{"@id": "http://www.opengis.net/ont/geosparql#wktLiteral", "base": "string"}`; and from an RML object map with `rml:datatype` or a `rml:datatypeMap` with `rml:constant`. `test/wkt-generation.test.mjs` (6 pass on the 0.15.0 wasm) and `#guard`s in `Wasm/Ops/Query.lean` and `Wasm/Ops/Formats.lean`.
|
|
16
|
+
|
|
17
|
+
- Tests, no engine change: `test/sparql-string-arg-types.test.mjs` holds 6 expected failures for SPARQL 1.1 string functions that accept arguments the specification makes type errors, and for SUBSTR rounding (https://github.com/danbri/factoidal/issues/1036), found by porting Apache Jena ARQ expression tests (`docs/designissues/2026-10-25-jena-style-facade-feasibility.md`).
|
|
18
|
+
|
|
3
19
|
## 0.15.0
|
|
4
20
|
|
|
5
21
|
- `openSbm11(source)` and `registerSbm11Source(source, reader)` are back, with the wasm ops `sbm11Open`, `sbm11Query`, `sbm11Close`: SPARQL over an SBM11 generation (the Shardborough store) in Node and in the browser, reading each file at an offset through a host reader (https://github.com/danbri/factoidal/issues/1009, https://github.com/danbri/factoidal-core/pull/93). Fix: in 0.14.0 `sbm11Open` trapped (wasm `unreachable`) because the C host-read functions had a signature that differs from the generated one; the wasm build now fails on any such mismatch. Not yet: the Merkle check of each read, a generation with a delta log (refused at open), packing SBM11 from npm.
|
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
|
@@ -701,14 +701,31 @@ export interface StageInfo {
|
|
|
701
701
|
note: string;
|
|
702
702
|
/** false exactly when status is 'missing': such a stage does not run. */
|
|
703
703
|
runnable: boolean;
|
|
704
|
-
/**
|
|
704
|
+
/**
|
|
705
|
+
* true when this package has a function that runs the stage as a node AND
|
|
706
|
+
* the engine reports the stage runnable (an older engine that reports a
|
|
707
|
+
* stage 'missing' leaves it unbound).
|
|
708
|
+
*/
|
|
705
709
|
bound: boolean;
|
|
706
710
|
}
|
|
707
711
|
|
|
712
|
+
/**
|
|
713
|
+
* The status of a node: the registry status, or 'unproved' when the stage is
|
|
714
|
+
* 'proved' and one of the node's settings is outside its proved domain (op
|
|
715
|
+
* pipelineCheckNodes, Lean Dataflow/NodeSettings.lean).
|
|
716
|
+
*/
|
|
717
|
+
export type NodeStatus = StageInfo['status'] | 'unproved';
|
|
718
|
+
|
|
719
|
+
/** A setting outside the proved domain of its stage, with the reason. */
|
|
720
|
+
export interface OutsideSetting {
|
|
721
|
+
readonly setting: string;
|
|
722
|
+
readonly reason: string;
|
|
723
|
+
}
|
|
724
|
+
|
|
708
725
|
/** One evidence record: the node that ran and what it produced. */
|
|
709
726
|
export interface EvidenceRecord {
|
|
710
727
|
stage: string;
|
|
711
|
-
status:
|
|
728
|
+
status: NodeStatus;
|
|
712
729
|
contract: string | null;
|
|
713
730
|
assumptions: string[];
|
|
714
731
|
/** true iff `status` is 'proved'. */
|
|
@@ -719,6 +736,19 @@ export interface EvidenceRecord {
|
|
|
719
736
|
reason?: string;
|
|
720
737
|
/** The node's own output beside the graph: the explain result, the SHACL report, the written text, ... */
|
|
721
738
|
artefact: unknown;
|
|
739
|
+
/**
|
|
740
|
+
* The node's settings in canonical form (keys sorted at every depth; the
|
|
741
|
+
* defaults of absent settings that narrow the contract, e.g. `lenient:
|
|
742
|
+
* false`, `entail: 'none'`; a Dataset value as `{nquadsSha256}`). Present
|
|
743
|
+
* when the engine has the op pipelineCheckNodes.
|
|
744
|
+
*/
|
|
745
|
+
settings?: Record<string, unknown>;
|
|
746
|
+
/** `sha256:` and the hex SHA-256 of the canonical settings text. */
|
|
747
|
+
settingsDigest?: string;
|
|
748
|
+
/** With status 'unproved': the registry status ('proved'). */
|
|
749
|
+
registryStatus?: StageInfo['status'];
|
|
750
|
+
/** With status 'unproved': the settings outside the proved domain. */
|
|
751
|
+
outside?: OutsideSetting[];
|
|
722
752
|
}
|
|
723
753
|
|
|
724
754
|
/** The value on a wire. */
|
|
@@ -731,14 +761,31 @@ export interface Wire<G = unknown> {
|
|
|
731
761
|
export interface FactoidalNode {
|
|
732
762
|
readonly stage: string;
|
|
733
763
|
readonly kind: string | null;
|
|
734
|
-
|
|
764
|
+
/** The engine's status of this node with its settings (see NodeStatus). */
|
|
765
|
+
readonly status: NodeStatus;
|
|
766
|
+
/** The registry status of the stage. */
|
|
767
|
+
readonly registryStatus: StageInfo['status'];
|
|
735
768
|
readonly contract: string | null;
|
|
736
769
|
readonly assumptions: readonly string[];
|
|
737
770
|
readonly wireIn: string;
|
|
771
|
+
/**
|
|
772
|
+
* The output wire with these settings: for shippedEvaluator, 'Table' for
|
|
773
|
+
* SELECT, 'Answer' for ASK, 'Dataset' for CONSTRUCT and DESCRIBE (Lean
|
|
774
|
+
* formWire, evalContract_wire). With an older engine, the registry wire.
|
|
775
|
+
*/
|
|
738
776
|
readonly wireOut: string;
|
|
739
777
|
readonly runnable: boolean;
|
|
740
778
|
readonly bound: boolean;
|
|
779
|
+
/** The settings as given (passed to the package function that runs the stage). */
|
|
741
780
|
readonly params: Readonly<Record<string, unknown>>;
|
|
781
|
+
/** The settings as sent to the engine: JSON values, a Dataset as {$nquads}. */
|
|
782
|
+
readonly settingsWire: Readonly<Record<string, unknown>>;
|
|
783
|
+
/** The engine's canonical settings, or null with an older engine. */
|
|
784
|
+
readonly settings: Readonly<Record<string, unknown>> | null;
|
|
785
|
+
readonly settingsDigest: string | null;
|
|
786
|
+
readonly outside: readonly OutsideSetting[];
|
|
787
|
+
/** Values the engine read from the settings: `form` of a query, `mode` ('1.1' | '1.2') of a reader. */
|
|
788
|
+
readonly derived: Readonly<Record<string, string>> | null;
|
|
742
789
|
}
|
|
743
790
|
|
|
744
791
|
/** The engine's check of a list of stages (op pipelineCheck, Lean pipelineReport). */
|
|
@@ -755,9 +802,17 @@ export interface PipelineCheck {
|
|
|
755
802
|
readonly assumptions: readonly string[];
|
|
756
803
|
readonly contract: { readonly theorem: string; readonly stages: readonly (string | null)[] };
|
|
757
804
|
readonly stages: readonly {
|
|
758
|
-
readonly name: string; readonly status:
|
|
805
|
+
readonly name: string; readonly status: NodeStatus; readonly checked: boolean;
|
|
759
806
|
readonly contract: string | null; readonly assumptions: readonly string[];
|
|
760
807
|
readonly wireIn: string; readonly wireOut: string;
|
|
808
|
+
/** The fields below come from the op pipelineCheckNodes only. */
|
|
809
|
+
readonly registryStatus?: StageInfo['status'];
|
|
810
|
+
readonly settings?: Record<string, unknown>;
|
|
811
|
+
readonly settingsDigest?: string;
|
|
812
|
+
/** The role of each setting: 'parameter' | 'selects-wire' | 'inert' | 'narrows' | 'unlisted'. */
|
|
813
|
+
readonly roles?: Readonly<Record<string, string>>;
|
|
814
|
+
readonly outside?: readonly OutsideSetting[];
|
|
815
|
+
readonly derived?: Readonly<Record<string, string>>;
|
|
761
816
|
}[];
|
|
762
817
|
}
|
|
763
818
|
|
|
@@ -1136,6 +1191,18 @@ export function openSbm11(source: string, options?: { reader?: Sbm11Reader }): P
|
|
|
1136
1191
|
/** Serve `source` with `reader`; `null` removes it. */
|
|
1137
1192
|
export function registerSbm11Source(source: string, reader: Sbm11Reader | null): void;
|
|
1138
1193
|
|
|
1194
|
+
/** Pack an RDF document into the files of one SBM11 generation, in memory
|
|
1195
|
+
* (wasm op `sbm11PackFiles`, https://github.com/danbri/factoidal/issues/1009
|
|
1196
|
+
* stage 2): file name to bytes, the files `l4sbm11-pack` writes for the same
|
|
1197
|
+
* quads, without `CURRENT`. Rejects with `code: 'SBM11_NO_PACK_OP'` on a wasm
|
|
1198
|
+
* module that has no such op. */
|
|
1199
|
+
export function packSbm11(text: string | Uint8Array,
|
|
1200
|
+
options: { syntax: 'turtle' | 'trig' | 'nquads' | 'ntriples' | 'rdfxml' | 'jsonld' | string; base?: string }
|
|
1201
|
+
): Promise<Map<string, Uint8Array>>;
|
|
1202
|
+
|
|
1203
|
+
/** A reader over files held in memory (for example the result of `packSbm11`). */
|
|
1204
|
+
export function mapReader(files: Map<string, Uint8Array> | Record<string, Uint8Array>): Sbm11Reader;
|
|
1205
|
+
|
|
1139
1206
|
/** One LWS resource an SBM11 reader read (https://github.com/danbri/factoidal/issues/1010). */
|
|
1140
1207
|
export interface Sbm11ReadEvidence {
|
|
1141
1208
|
resource: string;
|
|
@@ -1570,6 +1637,8 @@ export function capabilities(): Promise<{
|
|
|
1570
1637
|
stages: boolean;
|
|
1571
1638
|
/** The op pipelineCheck: pipe() and run() use the engine's type check and assumption union. */
|
|
1572
1639
|
pipelineCheck: boolean;
|
|
1640
|
+
/** The op pipelineCheckNodes: node settings in evidence, and the evaluator's output wire from the query form. */
|
|
1641
|
+
pipelineCheckNodes: boolean;
|
|
1573
1642
|
tableau: boolean;
|
|
1574
1643
|
rml: boolean;
|
|
1575
1644
|
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-0069da82efd7-web",
|
|
6
|
+
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40281606727",
|
|
7
|
+
"gitSha": "0069da82efd75cc3bfe10785ef8853bd84931957",
|
|
8
|
+
"builtAt": "2026-10-26T08:11:18Z",
|
|
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": "1b10444e44160b8689ebd5375c074509196930b1927ce4c9fbc829a73d9b58ac",
|
|
15
|
+
"wasmBytes": 17034490,
|
|
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) {
|
|
@@ -2140,6 +2148,7 @@ function buildApi(driver) {
|
|
|
2140
2148
|
explain: has('explainGraph'),
|
|
2141
2149
|
stages: has('stageList'),
|
|
2142
2150
|
pipelineCheck: has('pipelineCheck'),
|
|
2151
|
+
pipelineCheckNodes: has('pipelineCheckNodes'),
|
|
2143
2152
|
entailment: has('queryRaw'),
|
|
2144
2153
|
tableau: has('tableauMaterialise') && has('tableauDlInconsistent'),
|
|
2145
2154
|
rml: has('rmlMap'),
|
|
@@ -2176,7 +2185,7 @@ function buildApi(driver) {
|
|
|
2176
2185
|
// The node-composition API (lib/nodes.mjs, issue 1002 step 2).
|
|
2177
2186
|
const nodeApi = buildNodes({
|
|
2178
2187
|
entry, entryResult, requireEntryFn, Dataset,
|
|
2179
|
-
parse, closure, shaclValidate, query, serialize, canonicalize, explain,
|
|
2188
|
+
parse, closure, shaclValidate, query, serialize, canonicalize, explain, csvwToRdf,
|
|
2180
2189
|
});
|
|
2181
2190
|
|
|
2182
2191
|
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
|
}
|
package/lib/version.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factoidal/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "RDF parsing and writing (Turtle, TriG, N-Triples, N-Quads, RDF/XML, JSON-LD), SPARQL 1.1 and 1.2, RDFS and OWL reasoning, SHACL and ShEx validation, CSVW and RML mapping, RDFC-1.0, and SPARQL, LWS and Solid servers, for Node, Deno and browsers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sparql",
|