@factoidal/core 0.12.0 → 0.14.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 +198 -0
- package/README.md +60 -2
- package/authz/server.mjs +331 -0
- package/bin/factoidal.mjs +378 -12
- package/browser.d.ts +7 -0
- package/browser.js +8 -0
- package/index.d.ts +221 -8
- package/index.mjs +8 -0
- 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 +57 -5
- package/lib/dataset.mjs +8 -0
- package/lib/evidence.mjs +84 -0
- package/lib/nodes.mjs +215 -0
- package/lib/sbm11-node.mjs +61 -0
- package/lib/sbm11.mjs +148 -0
- package/lib/version.mjs +1 -1
- package/lws/client.mjs +319 -0
- package/lws/persist.mjs +17 -1
- package/lws/server.mjs +25 -3
- package/package.json +9 -1
- package/sample-store.mjs +1 -1
- package/xmpp/README.md +82 -4
- package/xmpp/server.mjs +549 -0
package/index.d.ts
CHANGED
|
@@ -184,7 +184,8 @@ export interface ParseDiagnostic {
|
|
|
184
184
|
* 'unterminated-string', 'bad-literal', 'bad-blank-node',
|
|
185
185
|
* 'bad-prefixed-name', 'bad-directive', 'bad-graph-name', 'rdf12-syntax',
|
|
186
186
|
* 'unclosed-group', 'nesting-limit', 'unexpected-token', 'internal' or
|
|
187
|
-
* 'too-many-errors'.
|
|
187
|
+
* 'too-many-errors'. A warning (`Dataset.warnings`) has the class
|
|
188
|
+
* 'ill-formed-language-tag'.
|
|
188
189
|
*/
|
|
189
190
|
class: string;
|
|
190
191
|
}
|
|
@@ -250,6 +251,13 @@ export class Dataset implements Iterable<Quad> {
|
|
|
250
251
|
readonly prefixes: Record<string, string>;
|
|
251
252
|
/** Diagnostics of a `{lenient:true}` parse; else empty. */
|
|
252
253
|
readonly diagnostics: ParseDiagnostic[];
|
|
254
|
+
/**
|
|
255
|
+
* Warnings of the parse of text: one per language tag that is not
|
|
256
|
+
* well-formed under BCP 47 section 2.2.9 (class 'ill-formed-language-tag',
|
|
257
|
+
* the position of the tag's first character). The reader keeps the triple
|
|
258
|
+
* and the writers write the tag unchanged. Empty otherwise.
|
|
259
|
+
*/
|
|
260
|
+
readonly warnings: ParseDiagnostic[];
|
|
253
261
|
/** The parse format tag, when the data was read from text. */
|
|
254
262
|
readonly format: string | undefined;
|
|
255
263
|
/** True when `format` was inferred because the caller gave none. */
|
|
@@ -627,6 +635,159 @@ export function coreRdfsCheck(
|
|
|
627
635
|
options?: { format?: DataFormat; baseIRI?: string }
|
|
628
636
|
): Promise<{ ok: boolean; fragment: boolean }>;
|
|
629
637
|
|
|
638
|
+
/** The evidence bundle and verdict of explain(). */
|
|
639
|
+
export interface ExplainResult {
|
|
640
|
+
profile: 'rdfs';
|
|
641
|
+
/** true: the checker accepted `bundle`; the sources RDFS-entail the description. */
|
|
642
|
+
entailed: boolean;
|
|
643
|
+
bundle: null | {
|
|
644
|
+
sources: string;
|
|
645
|
+
description: string;
|
|
646
|
+
mapping: Array<{ bnode: string; term: string }>;
|
|
647
|
+
steps: Array<{ rule: string; conclusion: string; premises: number[] }>;
|
|
648
|
+
support: number[];
|
|
649
|
+
};
|
|
650
|
+
/** Named assumptions that `entailed: false` relies on. */
|
|
651
|
+
assumptions: string[];
|
|
652
|
+
rejection:
|
|
653
|
+
| null
|
|
654
|
+
| { kind: 'noMatch'; index: number; triple: string }
|
|
655
|
+
| { kind: 'noJointMapping' }
|
|
656
|
+
| { kind: 'checkerRefused'; reason: string };
|
|
657
|
+
entailedMeans: string;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Is `description` (blank nodes shared across its triples) RDFS-entailed by
|
|
662
|
+
* `sources`? Full engine only; the description is default-graph only. Text
|
|
663
|
+
* inputs are Turtle unless `format` says otherwise.
|
|
664
|
+
* https://github.com/danbri/factoidal/issues/1002
|
|
665
|
+
*/
|
|
666
|
+
export function explain(
|
|
667
|
+
sources: DataInput,
|
|
668
|
+
description: DataInput,
|
|
669
|
+
options?: {
|
|
670
|
+
profile?: 'rdfs';
|
|
671
|
+
format?: DataFormat;
|
|
672
|
+
baseIRI?: string;
|
|
673
|
+
sourcesFormat?: DataFormat;
|
|
674
|
+
descriptionFormat?: DataFormat;
|
|
675
|
+
}
|
|
676
|
+
): Promise<ExplainResult>;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* One registered stage, as the engine's op stageList reads it from the Lean
|
|
680
|
+
* registry (L4Factoidal.Dataflow.registry).
|
|
681
|
+
* https://github.com/danbri/factoidal/issues/1002
|
|
682
|
+
*/
|
|
683
|
+
export interface StageInfo {
|
|
684
|
+
/** The stage declaration relative to L4Factoidal.Dataflow (or L4Factoidal); the area when there is none. */
|
|
685
|
+
name: string;
|
|
686
|
+
/** The full Lean declaration name, or null (status tested or missing). */
|
|
687
|
+
declaration: string | null;
|
|
688
|
+
area: string;
|
|
689
|
+
dir: string;
|
|
690
|
+
/** The interface type of the declaration (Stage, RdfReader, SoundReasoner, ...), or null. */
|
|
691
|
+
kind: string | null;
|
|
692
|
+
wireIn: string;
|
|
693
|
+
wireOut: string;
|
|
694
|
+
status: 'proved' | 'tested' | 'assumed' | 'missing';
|
|
695
|
+
/** The contract declaration (full Lean name), or null. */
|
|
696
|
+
contract: string | null;
|
|
697
|
+
theorems: string[];
|
|
698
|
+
/** The named assumptions of the stage (full Lean names). */
|
|
699
|
+
assumptions: string[];
|
|
700
|
+
tests: string[];
|
|
701
|
+
note: string;
|
|
702
|
+
/** false exactly when status is 'missing': such a stage does not run. */
|
|
703
|
+
runnable: boolean;
|
|
704
|
+
/** true when this package has a function that runs the stage as a node. */
|
|
705
|
+
bound: boolean;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** One evidence record: the node that ran and what it produced. */
|
|
709
|
+
export interface EvidenceRecord {
|
|
710
|
+
stage: string;
|
|
711
|
+
status: StageInfo['status'];
|
|
712
|
+
contract: string | null;
|
|
713
|
+
assumptions: string[];
|
|
714
|
+
/** true iff `status` is 'proved'. */
|
|
715
|
+
checked: boolean;
|
|
716
|
+
/** true on a step that ran under run()'s `unchecked` opt-in and is not checked. */
|
|
717
|
+
unchecked?: true;
|
|
718
|
+
/** With `unchecked`: the status and named assumptions, or 'no contract' for a missing stage. */
|
|
719
|
+
reason?: string;
|
|
720
|
+
/** The node's own output beside the graph: the explain result, the SHACL report, the written text, ... */
|
|
721
|
+
artefact: unknown;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/** The value on a wire. */
|
|
725
|
+
export interface Wire<G = unknown> {
|
|
726
|
+
graph: G;
|
|
727
|
+
evidence: EvidenceRecord[];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** A node: a registered stage with its parameters. */
|
|
731
|
+
export interface FactoidalNode {
|
|
732
|
+
readonly stage: string;
|
|
733
|
+
readonly kind: string | null;
|
|
734
|
+
readonly status: StageInfo['status'];
|
|
735
|
+
readonly contract: string | null;
|
|
736
|
+
readonly assumptions: readonly string[];
|
|
737
|
+
readonly wireIn: string;
|
|
738
|
+
readonly wireOut: string;
|
|
739
|
+
readonly runnable: boolean;
|
|
740
|
+
readonly bound: boolean;
|
|
741
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** A pipeline: nodes in order; `assumptions` is the union of theirs. */
|
|
745
|
+
export interface Pipeline {
|
|
746
|
+
readonly nodes: readonly FactoidalNode[];
|
|
747
|
+
readonly assumptions: readonly string[];
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** The result of run(): the last graph, one record per node, the assumption union. */
|
|
751
|
+
export interface RunResult<G = unknown> extends Wire<G> {
|
|
752
|
+
assumptions: string[];
|
|
753
|
+
/** true iff every evidence record is checked (status 'proved'). */
|
|
754
|
+
proved: boolean;
|
|
755
|
+
/** The stage names of the records that are not checked. */
|
|
756
|
+
uncheckedSteps: string[];
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Options of run(). */
|
|
760
|
+
export interface RunOptions {
|
|
761
|
+
/**
|
|
762
|
+
* Let stages that are not 'proved' run, and 'missing' stages too when the
|
|
763
|
+
* package has a runner for them. Each such step's record says so, and the
|
|
764
|
+
* result's `proved` is false. A stage with no runner still rejects (op.unknown).
|
|
765
|
+
*/
|
|
766
|
+
unchecked?: boolean;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** The engine's stage list (op stageList, full engine). */
|
|
770
|
+
export function stages(): Promise<StageInfo[]>;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* A node for the registered stage `name`. `params` are passed to the
|
|
774
|
+
* package function that runs it: reader options; `{shapes}` for
|
|
775
|
+
* shaclValidator; `{query}` for shippedEvaluator; `{description}` for
|
|
776
|
+
* rdfsExplainGraphStage. An unknown name rejects with a FactoidalError.
|
|
777
|
+
*/
|
|
778
|
+
export function node(name: string, params?: Record<string, unknown>): Promise<FactoidalNode>;
|
|
779
|
+
|
|
780
|
+
/** Compose nodes and pipelines left to right. */
|
|
781
|
+
export function pipe(...nodes: Array<FactoidalNode | Pipeline>): Pipeline;
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Run a node or pipeline on `input` (a Wire, or the graph: text for a
|
|
785
|
+
* reader, else a Dataset or data input). Rejects before any node runs when
|
|
786
|
+
* a node's stage has status 'missing' (unless `options.unchecked`) or has
|
|
787
|
+
* no package function.
|
|
788
|
+
*/
|
|
789
|
+
export function run(pipeline: FactoidalNode | Pipeline, input: unknown, options?: RunOptions): Promise<RunResult>;
|
|
790
|
+
|
|
630
791
|
/** Literature-name alias for coreRdfsClosure (ρdf). */
|
|
631
792
|
export function rhoDfClosure(
|
|
632
793
|
data: DataInput,
|
|
@@ -680,11 +841,18 @@ export function tableauDlInconsistent(
|
|
|
680
841
|
): Promise<{ inconsistent: boolean; rlAlone: boolean }>;
|
|
681
842
|
|
|
682
843
|
/**
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
844
|
+
* Checked consistency for the original default graph in a restricted
|
|
845
|
+
* OWL fragment (named subclass/class assertions, Thing/Nothing,
|
|
846
|
+
* owl:someValuesFrom restrictions as a superclass or a class assertion,
|
|
847
|
+
* owl:TransitiveProperty, sameAs/differentFrom, declarations, annotations
|
|
848
|
+
* and annotated axioms).
|
|
849
|
+
* true requires a checked finite model and has no `reason`; false requires
|
|
850
|
+
* an accepted refutation certificate; null means unsupported-syntax,
|
|
851
|
+
* budget-out or incomplete-search. Named graphs do not contribute axioms.
|
|
852
|
+
* RDF 1.2 terms are refused. Other restrictions, object/data property
|
|
853
|
+
* axioms, imports and equivalentClass are unsupported. `fuel` bounds the
|
|
854
|
+
* model search and the certificate checker (default 20000 rounds).
|
|
855
|
+
* This is not complete OWL DL support.
|
|
688
856
|
*/
|
|
689
857
|
export function owlIsConsistent(
|
|
690
858
|
data: DataInput,
|
|
@@ -694,8 +862,15 @@ export function owlIsConsistent(
|
|
|
694
862
|
/**
|
|
695
863
|
* OWL entailment check: does `premise` entail `conclusion`? Two paths: `via: "closure"` (the conclusion is in the OWL-RL
|
|
696
864
|
* closure of the premise) or `via: "refutation"` (the negated
|
|
697
|
-
* conclusion is refuted on every goal). Default graph only.
|
|
698
|
-
*
|
|
865
|
+
* conclusion is refuted on every goal). Default graph only. The current
|
|
866
|
+
* implementation returns `true` for those positive results and `null`
|
|
867
|
+
* when it cannot establish entailment. `reason` distinguishes budget-out,
|
|
868
|
+
* incomplete-search, unsupported-conclusion and unsupported-punning.
|
|
869
|
+
* `false` (via `refutation`, reason `countermodel: ...`) requires a checked
|
|
870
|
+
* finite model of the premise in which the conclusion fails; both graphs
|
|
871
|
+
* must be in the fragment that `owlIsConsistent` reads.
|
|
872
|
+
* The punning refusal is limited; full OWL DL recognition and universal
|
|
873
|
+
* Direct Semantics soundness are not established by this API.
|
|
699
874
|
*/
|
|
700
875
|
export function owlEntails(
|
|
701
876
|
premise: DataInput,
|
|
@@ -887,6 +1062,42 @@ export class Store {
|
|
|
887
1062
|
*/
|
|
888
1063
|
export function openStore(source: string | StoreReader, options?: OpenStoreOptions): Promise<Store>;
|
|
889
1064
|
|
|
1065
|
+
/** A synchronous reader of the files of one SBM11 generation directory (or
|
|
1066
|
+
* collection root): the engine names every file and checks every answer. */
|
|
1067
|
+
export interface Sbm11Reader {
|
|
1068
|
+
readAt(file: string, offset: number, length: number): Uint8Array | null;
|
|
1069
|
+
size(file: string): number | null;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/** The answer of one SBM11 query. */
|
|
1073
|
+
export interface Sbm11Answer {
|
|
1074
|
+
/** `path=ids` or `path=reference reason="..." ...` */
|
|
1075
|
+
path: string;
|
|
1076
|
+
rows: number;
|
|
1077
|
+
/** `srj` (SELECT, ASK) or `ntriples` (CONSTRUCT, DESCRIBE) */
|
|
1078
|
+
format: 'srj' | 'ntriples';
|
|
1079
|
+
/** the bytes `l4sbm11 query` writes, as text */
|
|
1080
|
+
readonly result: string;
|
|
1081
|
+
/** the same answer as UTF-8 bytes */
|
|
1082
|
+
readonly bytes: Uint8Array;
|
|
1083
|
+
bytesRead: number;
|
|
1084
|
+
reads: number;
|
|
1085
|
+
/** the parsed SPARQL JSON for SELECT and ASK */
|
|
1086
|
+
readonly json: any;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/** An open SBM11 generation. */
|
|
1090
|
+
export interface Sbm11Store {
|
|
1091
|
+
handle: string;
|
|
1092
|
+
/** the generation CURRENT names, or "" when the source is a generation */
|
|
1093
|
+
generation: string;
|
|
1094
|
+
terms: number;
|
|
1095
|
+
query(sparql: string, options?: { base?: string; sparql12?: boolean; reference?: boolean }): Sbm11Answer;
|
|
1096
|
+
close(): void;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
|
|
890
1101
|
/** One query against a store: open with the query's plan, query, close. */
|
|
891
1102
|
export function queryStore(
|
|
892
1103
|
source: string | StoreReader,
|
|
@@ -1292,6 +1503,8 @@ export function capabilities(): Promise<{
|
|
|
1292
1503
|
shacl: boolean;
|
|
1293
1504
|
shex: boolean;
|
|
1294
1505
|
owlClosure: boolean;
|
|
1506
|
+
explain: boolean;
|
|
1507
|
+
stages: boolean;
|
|
1295
1508
|
tableau: boolean;
|
|
1296
1509
|
rml: boolean;
|
|
1297
1510
|
csvw: boolean;
|
package/index.mjs
CHANGED
|
@@ -66,6 +66,9 @@ export const clearExtensionFunctions = api.clearExtensionFunctions;
|
|
|
66
66
|
export const registerServiceEndpoint = api.registerServiceEndpoint;
|
|
67
67
|
export const clearServiceEndpoints = api.clearServiceEndpoints;
|
|
68
68
|
|
|
69
|
+
// SBM11 (openSbm11, registerSbm11Source) is not exported in 0.14.0: the op
|
|
70
|
+
// sbm11Open traps in the wasm build (https://github.com/danbri/factoidal/issues/1009).
|
|
71
|
+
|
|
69
72
|
// The persisted store
|
|
70
73
|
export const openStore = api.openStore;
|
|
71
74
|
export const queryStore = api.queryStore;
|
|
@@ -86,6 +89,11 @@ export const shexValidate = api.shexValidate;
|
|
|
86
89
|
export const owlClosure = api.owlClosure;
|
|
87
90
|
export const coreRdfsClosure = api.coreRdfsClosure;
|
|
88
91
|
export const coreRdfsCheck = api.coreRdfsCheck;
|
|
92
|
+
export const explain = api.explain;
|
|
93
|
+
export const stages = api.stages;
|
|
94
|
+
export const node = api.node;
|
|
95
|
+
export const pipe = api.pipe;
|
|
96
|
+
export const run = api.run;
|
|
89
97
|
export const rdfsPlusClosure = api.rdfsPlusClosure;
|
|
90
98
|
export const rhoDfClosure = api.rhoDfClosure;
|
|
91
99
|
export const rhoDfFragmentCheck = api.rhoDfFragmentCheck;
|
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-159a7adfbd01-web",
|
|
6
|
+
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40192154339",
|
|
7
|
+
"gitSha": "159a7adfbd0155fc560583bc028a54f4508cd4ee",
|
|
8
|
+
"builtAt": "2026-10-25T12:48:55Z",
|
|
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": "2e65b7c2fda13352e80f73ee224d3614551455f7361fa8b1596bb5f702653feb",
|
|
15
|
+
"wasmBytes": 15069305,
|
|
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
|
@@ -29,6 +29,7 @@ import { openGeneration, readRegion, manifestKeys, hexOfBytes } from './store.mj
|
|
|
29
29
|
import { fullEngineMessage } from './lean-entry.mjs';
|
|
30
30
|
import { memoryDeltaLogStore } from './delta-log-hosts.mjs';
|
|
31
31
|
import { FactoidalError, errorFromEnvelope, envelopeResult } from './errors.mjs';
|
|
32
|
+
import { buildNodes } from './nodes.mjs';
|
|
32
33
|
|
|
33
34
|
// The format names, regimes and query outputs are the engine's tables (op
|
|
34
35
|
// engineTables, formal/lean4/Wasm/Ops/Handles.lean), read once per engine:
|
|
@@ -530,7 +531,10 @@ function buildApi(driver) {
|
|
|
530
531
|
},
|
|
531
532
|
missing: (what, op) => missingOpError(what, e, op),
|
|
532
533
|
wrap(r, prefixes, extra) {
|
|
533
|
-
|
|
534
|
+
// `warnings` (the reader's language-tag warnings) is not an info
|
|
535
|
+
// member: openText() and the lenient parse() pass the warnings of the
|
|
536
|
+
// caller's text in `extra`, with their offsets in UTF-16 units.
|
|
537
|
+
const { ok: _ok, handle, count, prefixes: own, warnings: _w, ...info } = r;
|
|
534
538
|
return new Dataset(INTERNAL, c, handle, count,
|
|
535
539
|
prefixes !== undefined ? prefixes : own, { ...(extra || {}), info });
|
|
536
540
|
},
|
|
@@ -595,7 +599,10 @@ function buildApi(driver) {
|
|
|
595
599
|
}
|
|
596
600
|
throw err;
|
|
597
601
|
}
|
|
598
|
-
return c.wrap(r, undefined, {
|
|
602
|
+
return c.wrap(r, undefined, {
|
|
603
|
+
format: tag, formatInferred: inferred,
|
|
604
|
+
warnings: utf16Diagnostics(text, r.warnings),
|
|
605
|
+
});
|
|
599
606
|
}
|
|
600
607
|
|
|
601
608
|
// The Dataset a consumer reads: `data` itself when it is a Dataset, else
|
|
@@ -741,6 +748,7 @@ function buildApi(driver) {
|
|
|
741
748
|
const opened = entryResult(c.e.datasetOpen(r.nquads, 'nquads', ''), 'parse');
|
|
742
749
|
return c.wrap(opened, r.prefixes, {
|
|
743
750
|
diagnostics: utf16Diagnostics(text, r.diagnostics),
|
|
751
|
+
warnings: utf16Diagnostics(text, r.warnings),
|
|
744
752
|
format: tag, formatInferred: inferred,
|
|
745
753
|
});
|
|
746
754
|
}
|
|
@@ -1023,6 +1031,36 @@ function buildApi(driver) {
|
|
|
1023
1031
|
/** Literature-name alias for coreRdfsCheck. */
|
|
1024
1032
|
const rhoDfFragmentCheck = coreRdfsCheck;
|
|
1025
1033
|
|
|
1034
|
+
/**
|
|
1035
|
+
* Is `description` (a graph whose blank nodes are shared variables)
|
|
1036
|
+
* RDFS-entailed by `sources`? Returns the evidence bundle when it is
|
|
1037
|
+
* (https://github.com/danbri/factoidal/issues/1002). Op explainGraph, full
|
|
1038
|
+
* engine only. `entailed: false` also relies on the named assumption in
|
|
1039
|
+
* `assumptions`. Text inputs are Turtle unless `format` says otherwise.
|
|
1040
|
+
* @param {Dataset|string|Array} sources
|
|
1041
|
+
* @param {Dataset|string|Array} description default graph only
|
|
1042
|
+
* @param {{profile?: 'rdfs', format?: string, baseIRI?: string,
|
|
1043
|
+
* sourcesFormat?: string, descriptionFormat?: string}} [options]
|
|
1044
|
+
* @returns {Promise<{profile: string, entailed: boolean, bundle: object|null,
|
|
1045
|
+
* assumptions: string[], rejection: object|null, entailedMeans: string}>}
|
|
1046
|
+
*/
|
|
1047
|
+
async function explain(sources, description, options) {
|
|
1048
|
+
const o = optionsOf('explain', options);
|
|
1049
|
+
if (o.profile !== undefined && o.profile !== 'rdfs') {
|
|
1050
|
+
throw new TypeError(`explain: unsupported profile '${o.profile}' (only 'rdfs')`);
|
|
1051
|
+
}
|
|
1052
|
+
const { profile: _profile, sourcesFormat, descriptionFormat, ...rest } = o;
|
|
1053
|
+
// The engine's option table has no entry for explain; the options here are read-only hints.
|
|
1054
|
+
const opts = rest;
|
|
1055
|
+
const fmt = opts.format || 'turtle';
|
|
1056
|
+
const srcNq = await nquadsOf(sources, { ...opts, format: sourcesFormat || fmt }, 'explain(sources)');
|
|
1057
|
+
const descNq = await nquadsOf(description, { ...opts, format: descriptionFormat || fmt }, 'explain(description)');
|
|
1058
|
+
const e = await entry();
|
|
1059
|
+
requireEntryFn(e, 'explainGraph', 'explain');
|
|
1060
|
+
const { ok: _ok, ...out } = entryResult(e.explainGraph(srcNq, descNq), 'explain');
|
|
1061
|
+
return out;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1026
1064
|
/**
|
|
1027
1065
|
* RDFS-Plus closure (RDFS with owl:sameAs, inverseOf, Symmetric,
|
|
1028
1066
|
* Transitive, Functional and InverseFunctional properties,
|
|
@@ -1071,8 +1109,9 @@ function buildApi(driver) {
|
|
|
1071
1109
|
}
|
|
1072
1110
|
|
|
1073
1111
|
/**
|
|
1074
|
-
* OWL
|
|
1075
|
-
*
|
|
1112
|
+
* Checked atomic OWL consistency of the default graph: false is a checked
|
|
1113
|
+
* contradiction; null means unsupported syntax or no accepted refutation.
|
|
1114
|
+
* No model producer exists. fuel does not change the fixed checker bound.
|
|
1076
1115
|
* @param {Dataset|string|Array} data
|
|
1077
1116
|
* @param {{format?: string, baseIRI?: string, fuel?: number|string}} [options]
|
|
1078
1117
|
* @returns {Promise<{consistent: boolean|null, reason?: string}>}
|
|
@@ -2077,7 +2116,7 @@ function buildApi(driver) {
|
|
|
2077
2116
|
* @returns {Promise<{entry: boolean, construct: boolean,
|
|
2078
2117
|
* update: boolean, canonicalize: boolean, graphs: boolean,
|
|
2079
2118
|
* canonicalHash: boolean, shacl: boolean, shex: boolean,
|
|
2080
|
-
* owlClosure: boolean, rml: boolean, csvw: boolean, jsonld: boolean,
|
|
2119
|
+
* owlClosure: boolean, explain: boolean, stages: boolean, rml: boolean, csvw: boolean, jsonld: boolean,
|
|
2081
2120
|
* rif: boolean}>}
|
|
2082
2121
|
*/
|
|
2083
2122
|
let capsCache = null;
|
|
@@ -2098,6 +2137,8 @@ function buildApi(driver) {
|
|
|
2098
2137
|
shacl: has('shaclValidate'),
|
|
2099
2138
|
shex: has('shexValidate'),
|
|
2100
2139
|
owlClosure: has('owlClosure'),
|
|
2140
|
+
explain: has('explainGraph'),
|
|
2141
|
+
stages: has('stageList'),
|
|
2101
2142
|
entailment: has('queryRaw'),
|
|
2102
2143
|
tableau: has('tableauMaterialise') && has('tableauDlInconsistent'),
|
|
2103
2144
|
rml: has('rmlMap'),
|
|
@@ -2131,6 +2172,12 @@ function buildApi(driver) {
|
|
|
2131
2172
|
};
|
|
2132
2173
|
}
|
|
2133
2174
|
|
|
2175
|
+
// The node-composition API (lib/nodes.mjs, issue 1002 step 2).
|
|
2176
|
+
const nodeApi = buildNodes({
|
|
2177
|
+
entry, entryResult, requireEntryFn, Dataset,
|
|
2178
|
+
parse, closure, shaclValidate, query, serialize, canonicalize, explain,
|
|
2179
|
+
});
|
|
2180
|
+
|
|
2134
2181
|
return {
|
|
2135
2182
|
parse,
|
|
2136
2183
|
query,
|
|
@@ -2153,6 +2200,11 @@ function buildApi(driver) {
|
|
|
2153
2200
|
owlClosure,
|
|
2154
2201
|
coreRdfsClosure,
|
|
2155
2202
|
coreRdfsCheck,
|
|
2203
|
+
explain,
|
|
2204
|
+
stages: nodeApi.stages,
|
|
2205
|
+
node: nodeApi.node,
|
|
2206
|
+
pipe: nodeApi.pipe,
|
|
2207
|
+
run: nodeApi.run,
|
|
2156
2208
|
rdfsPlusClosure,
|
|
2157
2209
|
rhoDfClosure,
|
|
2158
2210
|
rhoDfFragmentCheck,
|
package/lib/dataset.mjs
CHANGED
|
@@ -152,6 +152,7 @@ class Dataset {
|
|
|
152
152
|
this._prefixes = Object.freeze({ ...(prefixes || {}) });
|
|
153
153
|
const x = extra || {};
|
|
154
154
|
this._diagnostics = Object.freeze((x.diagnostics || []).slice());
|
|
155
|
+
this._warnings = Object.freeze((x.warnings || []).slice());
|
|
155
156
|
this._format = x.format;
|
|
156
157
|
this._formatInferred = !!x.formatInferred;
|
|
157
158
|
this._info = Object.freeze({ ...(x.info || {}) });
|
|
@@ -169,6 +170,13 @@ class Dataset {
|
|
|
169
170
|
get prefixes() { return this._prefixes; }
|
|
170
171
|
/** Diagnostics of a lenient parse ({lenient:true}); else empty. */
|
|
171
172
|
get diagnostics() { return this._diagnostics; }
|
|
173
|
+
/**
|
|
174
|
+
* Warnings of the parse: one per language tag that is not well-formed
|
|
175
|
+
* under BCP 47 (class 'ill-formed-language-tag'). The triple is kept and
|
|
176
|
+
* the writers write the tag unchanged. Empty for a Dataset not read from
|
|
177
|
+
* text.
|
|
178
|
+
*/
|
|
179
|
+
get warnings() { return this._warnings; }
|
|
172
180
|
/** The syntax the data was read from, when it was read from text. */
|
|
173
181
|
get format() { return this._format; }
|
|
174
182
|
/** True when `format` was inferred because the caller gave none. */
|
package/lib/evidence.mjs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The evidence logic of the node-composition API (issue 1002), kept apart
|
|
2
|
+
// from the engine so it runs, and is tested, without the wasm.
|
|
3
|
+
//
|
|
4
|
+
// Rule (owner-approved, issue 975 comment 6393905605 item 3): a stage that
|
|
5
|
+
// is not `proved` may run only after an explicit opt-in, and its evidence
|
|
6
|
+
// record says so. A result never reports a guarantee the path lacks:
|
|
7
|
+
// `proved` is true only when every record is `checked`.
|
|
8
|
+
//
|
|
9
|
+
// ES module, no Node built-ins.
|
|
10
|
+
|
|
11
|
+
import { FactoidalError } from './errors.mjs';
|
|
12
|
+
|
|
13
|
+
/** A stage is checked exactly when its registry status is `proved`. */
|
|
14
|
+
export const isChecked = (status) => status === 'proved';
|
|
15
|
+
|
|
16
|
+
/** The reason text of an unchecked step. */
|
|
17
|
+
export function reasonOf(n) {
|
|
18
|
+
if (n.status === 'missing') return 'no contract';
|
|
19
|
+
return n.assumptions.length > 0
|
|
20
|
+
? `status ${n.status}; assumptions: ${n.assumptions.join(', ')}`
|
|
21
|
+
: `status ${n.status}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The refusal for node `n` before anything runs, or null. `hasRunner` is
|
|
26
|
+
* whether the package has a function that runs the stage.
|
|
27
|
+
* Without the opt-in a `missing` stage refuses (op.failed). With it, a stage
|
|
28
|
+
* runs when it has a runner; one with no runner refuses (op.unknown).
|
|
29
|
+
*/
|
|
30
|
+
export function refusalFor(n, unchecked, hasRunner) {
|
|
31
|
+
if (!n.runnable && !unchecked) {
|
|
32
|
+
return new FactoidalError(`run: stage '${n.stage}' has status ${n.status}: ` +
|
|
33
|
+
'no contract and no test suite, so it does not run as a node ' +
|
|
34
|
+
'(pass { unchecked: true } to run it without a guarantee)', { code: 'op.failed' });
|
|
35
|
+
}
|
|
36
|
+
if (!hasRunner) {
|
|
37
|
+
return new FactoidalError(`run: stage '${n.stage}' has no package function that runs it`,
|
|
38
|
+
{ code: 'op.unknown' });
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* One evidence record. `checked` is true iff the status is `proved`.
|
|
45
|
+
* A step that ran under the opt-in and is not checked also carries
|
|
46
|
+
* `unchecked: true` and `reason`.
|
|
47
|
+
*/
|
|
48
|
+
export function makeRecord(n, artefact, unchecked) {
|
|
49
|
+
const checked = isChecked(n.status);
|
|
50
|
+
const base = {
|
|
51
|
+
stage: n.stage,
|
|
52
|
+
status: n.status,
|
|
53
|
+
contract: n.contract,
|
|
54
|
+
assumptions: n.assumptions,
|
|
55
|
+
checked,
|
|
56
|
+
artefact: artefact === undefined ? null : artefact,
|
|
57
|
+
};
|
|
58
|
+
if (unchecked && !checked) {
|
|
59
|
+
base.unchecked = true;
|
|
60
|
+
base.reason = reasonOf(n);
|
|
61
|
+
}
|
|
62
|
+
return Object.freeze(base);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A record is checked; a record without `checked` (older producer) falls back to its status. */
|
|
66
|
+
export const recordChecked = (r) => (typeof r.checked === 'boolean' ? r.checked : isChecked(r.status));
|
|
67
|
+
|
|
68
|
+
/** The union of the named assumptions of the records, first-seen order. */
|
|
69
|
+
export function unionAssumptions(records) {
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const r of records) {
|
|
73
|
+
for (const a of r.assumptions) {
|
|
74
|
+
if (!seen.has(a)) { seen.add(a); out.push(a); }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** `{ assumptions, proved, uncheckedSteps }` of a record list. */
|
|
81
|
+
export function summarize(records) {
|
|
82
|
+
const uncheckedSteps = records.filter((r) => !recordChecked(r)).map((r) => r.stage);
|
|
83
|
+
return { assumptions: unionAssumptions(records), proved: uncheckedSteps.length === 0, uncheckedSteps };
|
|
84
|
+
}
|