@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/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
- * OWL DL consistency verdict via the clash-detecting tableau
684
- * (L4Factoidal/OWL/Refute.lean) over the OWL RL closure.
685
- * Default graph only. `consistent` is three-valued: `false` (a clash on
686
- * every branch), `true` (a model built), or `null` -- a budget-out, with
687
- * `reason` naming the fuel cap (never a silent false).
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. `entailed` is `true`, `false`, or `null`
698
- * (a refutation budget-out, with `reason` naming the cap).
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;
@@ -37,7 +37,7 @@
37
37
  // bytes change.
38
38
 
39
39
  // Stamped by formal/lean4/Wasm/build-wasm.sh step 9 -- do not hand-edit.
40
- const WASM_VERSION = "ec99e3524f1a";
40
+ const WASM_VERSION = "2e65b7c2fda1";
41
41
 
42
42
  import createModule from './l4factoidal.mjs';
43
43
 
Binary file
@@ -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-d9cc50253669-web",
6
- "releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/39759947727",
7
- "gitSha": "d9cc502536698eaf40ab74f7fb5de194c81f104f",
8
- "builtAt": "2026-10-22T10:39:28Z",
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": "ec99e3524f1a78667c2b41801556a9d25d65b01cea7afa60ec14c33523da3978",
15
- "wasmBytes": 13212135,
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
- const { ok: _ok, handle, count, prefixes: own, ...info } = r;
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, { format: tag, formatInferred: inferred });
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 DL consistency: `consistent` is true, false, or null (the refuter's
1075
- * budget ran out; `reason` names the cap).
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. */
@@ -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
+ }