@factoidal/core 0.25.0 → 0.27.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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.27.0 (2026-10-30)
6
+
7
+ - New CLI commands `factoidal xpath4`, `factoidal xquery` and `factoidal xslt4` expose the existing Lean XML operations. They accept explicit local input files, display JSON or the engine's serialized result, and preserve QT4 error codes such as `XPST0003`. See the README for arguments and examples.
8
+ - The public dataflow API now has `xpath4Eval`, `xqueryEval` and `xslt4Transform` nodes. Their output wires carry serialized XDM or XSLT results. These nodes are marked tested, with `checked: false` evidence; no evaluator correctness theorem is claimed. Existing registry IDs are preserved.
9
+ - Public `xpath4Eval` and `xqueryEval` now install the pure inline `fn:transform` adapter already used by the QT4 harness. It supports supplied source nodes and stylesheet text without fetching external resources. Assertions default to disabled for `fn:transform` and can be enabled explicitly; direct `xslt4Transform` retains its enabled default. A nested `fn:transform` inside a `post-process` callback remains unsupported (`FOXT0004`).
10
+ - QT4 fixes include assertion handling, QName environments and nested-transform resource bases (https://github.com/danbri/factoidal-core/pull/268). The native `fn-transform` set improves from 127 pass / 9 fail / 1 unsupported to 132 / 4 / 1. XPath grapheme behavior remains Unicode 15.1, with 1,189 / 0 / 0 on the pinned QT4 set. These are native measurements, not full WASM conformance claims.
11
+ - Schema-aware `fn:element-to-map` now uses schema components and typed values, including inherited declarations and conversions (https://github.com/danbri/factoidal-core/pull/270). Native QT4 results improve from 186 pass / 35 fail / 0 unsupported to 219 / 2 / 0; the two remaining fixture/specification disagreements remain visible. Schema-driven behavior requires a validating host and is not newly exposed through the pure public API.
12
+
13
+ The XProc atomic-step bridge to the Lean dataflow graph remains available in the Lean tree; this release adds no npm XProc operation. Native server executable publication is separate and remains blocked by the WebDAV regression failures tracked in https://github.com/danbri/factoidal/issues/1202.
14
+
15
+ ## 0.26.0 (2026-10-29)
16
+
17
+ - QT4 processor feature switches (https://github.com/danbri/factoidal-core/pull/252): the native qt4tests runner can run as a processor without a feature, so the tests written for such processors run instead of being unsupported; qt4tests in XQuery mode, measured on that PR: `fn` 17188 → 17217 pass, `prod` 13819 → 13823 pass, no new failures.
18
+ - XPointer in the Lean engine: the XPointer Framework, element() and xmlns() schemes (W3C Recommendations 2003) in `L4Factoidal/XML/XPointer.lean`, and the xpointer() scheme (W3C Working Draft 2002) through the XPath 4.0 engine in `XML/XPointerXPath.lean`. Over the 98 pointers of the W3C XInclude test suite: 98 pass, 0 fail (out of 98) (`lake exe l4xpointer`). Not yet used by XInclude, and no npm or wasm op yet.
19
+
5
20
  ## 0.25.0 (2026-10-29)
6
21
 
7
22
  - XQuery 4.0 and XPath 4.0 engines (behind `xqueryEval` and `xpath4Eval`) toward all of qt4tests (https://github.com/danbri/factoidal-core/pull/214). qt4tests b1616b79, native runner, measured on that PR: XQuery mode 45040 → 45855 pass, 1187 → 619 fail, 1397 → 1150 unsupported (out of 47624); XPath mode 32928 → 32982 pass, 106 → 78 fail. One test set has one pass fewer (`fn-system-properties`, 23 → 22): the runner now declares the `infoset-dtd` feature. The wasm limits on deep recursion and very large documents still apply (https://github.com/danbri/factoidal/issues/1110).
package/README.md CHANGED
@@ -123,6 +123,50 @@ console.log(r.assumptions, r.proved); // what the answer depends on
123
123
  and marks those steps in the evidence. Details: API.md, "Stages as nodes" and
124
124
  "Explaining an entailment" (`explain()` is the same check as one call).
125
125
 
126
+ ### XPath, XQuery and XSLT 4.0
127
+
128
+ `xpath4Eval(xml, expression)`, `xqueryEval(query, xml?)`, and
129
+ `xslt4Transform(stylesheet, xml)` use the same Lean engines as the native
130
+ XML test runners. They also have nodes named `xpath4Eval`, `xqueryEval`, and
131
+ `xslt4Transform`. Each node reads XML context **text** (`''` means no context)
132
+ and requires its one string setting: `expression`, `query`, or `stylesheet`.
133
+
134
+ ```js
135
+ import { node, run } from '@factoidal/core';
136
+
137
+ const evaluate = await node('xpath4Eval', { expression: 'count(//person)' });
138
+ const result = await run(evaluate, '<people><person/></people>', { unchecked: true });
139
+ console.log(result.graph.xml); // '1', serialized by Lean
140
+ console.log(result.proved); // false: this adapter is tested, with no stage contract
141
+ ```
142
+
143
+ XPath/XQuery nodes output `Qt4Result` (`count`, typed serialized `items`,
144
+ `adaptive`, nullable `xml`), not live XDM values. XSLT nodes output `Xslt4Result`
145
+ (`output`, `method`, `secondary`, `messages`); text or JSON results are not
146
+ mislabelled as XML. There is no implicit result-to-input conversion. Unknown
147
+ or wrongly typed settings fail in Lean. Under the existing node policy these
148
+ tested nodes can run, but return `proved: false` and `checked: false` evidence.
149
+ `unchecked: true` additionally records the explicit opt-in and its reason;
150
+ neither mode claims a proved evaluator contract.
151
+
152
+ The CLI has `factoidal xpath4 EXPRESSION`, `factoidal xquery QUERY` (or
153
+ `--file PATH`), and `factoidal xslt4 STYLESHEET_FILE`. Each accepts
154
+ `--input XML_FILE`; omission supplies no context item. Default output is the
155
+ engine's JSON envelope. XPath/XQuery accept `--format adaptive|xml`; XSLT
156
+ accepts `--format output` for the principal serialization. `--help` describes
157
+ the complete syntax. QT4 failures exit 1 and retain the error QName in JSON
158
+ on stderr; usage failures exit 2. Secondary results stay in the JSON envelope
159
+ and are not written to files.
160
+
161
+ Inline `fn:transform` uses the existing pure XSLT adapter, including its
162
+ `enable-assertions` option (default false). Direct `xslt4Transform` retains
163
+ enabled assertions. Calling `fn:transform` from its own `post-process`
164
+ callback currently fails with `FOXT0004`; callback reentrancy is not provided.
165
+ No stylesheet/module/resource fetch or schema provider
166
+ is added. Schema-dependent PSVI tests and XProc execution remain native/
167
+ host-dependent: the Lean XProc 3.1 reader and atomic dataflow bridge exist,
168
+ but there is no public npm XProc WASM entry point.
169
+
126
170
  Formats (generated from `formats()`):
127
171
 
128
172
  <!-- formats-table:start (generated by test/formats-table.test.mjs) -->
package/bin/factoidal.mjs CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  } from './store.mjs'
38
38
  import { sbm11Api, setDefaultSbm11Reader } from '../lib/sbm11.mjs'
39
39
  import { nodeDefaultReaderFactory } from '../lib/sbm11-node.mjs'
40
+ import { runQt4, Qt4UsageError } from './qt4.mjs'
40
41
 
41
42
  const EXIT_OK = 0
42
43
  const EXIT_FAILURE = 1
@@ -108,6 +109,9 @@ commands:
108
109
  sample-store [DIR] pack a small built-in store and print its path
109
110
  inspect STORE report what the activated manifest commits
110
111
  query STORE [QUERY] evaluate a SPARQL query against a store
112
+ xpath4 EXPRESSION XPath 4.0 over --input XML_FILE (optional)
113
+ xquery QUERY XQuery 4.0 over --input XML_FILE (optional)
114
+ xslt4 STYLESHEET_FILE XSLT 4.0 over --input XML_FILE (optional)
111
115
  pack INPUT OUTPUT build one immutable generation from an RDF file
112
116
  activate STORE GENERATION make one generation the activated generation
113
117
  update STORE [UPDATE] apply a SPARQL Update through the delta log
@@ -146,6 +150,23 @@ an RDF file with pack and activate, or try a small built-in one:
146
150
  factoidal query "$(factoidal sample-store)" 'SELECT * WHERE { ?s ?p ?o } LIMIT 5'`
147
151
 
148
152
  const COMMAND_USAGE = {
153
+ xpath4: `factoidal xpath4 - evaluate XPath 4.0
154
+ usage: factoidal xpath4 [EXPRESSION] [--expression TEXT | --file PATH] [--input XML_FILE] [--format json|adaptive|xml]
155
+ Supply exactly one expression source. No --input means no context item.
156
+ Default output is the engine's JSON result, including typed serialized items.
157
+ --format xml fails if XML serialization is unavailable (for example maps).
158
+ Only explicitly named files are read. External resources and schema providers are not supplied.`,
159
+ xquery: `factoidal xquery - evaluate XQuery 4.0
160
+ usage: factoidal xquery [QUERY] [--query TEXT | --file PATH] [--input XML_FILE] [--format json|adaptive|xml]
161
+ Supply exactly one query source. No --input means no context item.
162
+ Default output is the engine's JSON result; display serializations come from Lean.
163
+ External modules/resources and schema providers are not supplied.`,
164
+ xslt4: `factoidal xslt4 - transform with XSLT 4.0
165
+ usage: factoidal xslt4 [STYLESHEET_FILE] [--stylesheet PATH] [--input XML_FILE] [--format json|output]
166
+ Supply exactly one stylesheet file. No --input means no global context item.
167
+ Default JSON includes principal output, method, secondary results and messages.
168
+ --format output prints only the principal output. Secondary results are never written to files.
169
+ External stylesheet modules/resources and schema providers are not supplied.`,
149
170
  'sample-store': `factoidal sample-store - pack a small built-in store, print its path
150
171
 
151
172
  usage: factoidal sample-store [DIR] [--json]
@@ -1132,6 +1153,9 @@ class UsageError extends Error {}
1132
1153
  const REPEATABLE_OPTIONS = new Set(['cors-origin', 'saml-idp', 'saml-decrypt-key', 'allow-host', 'allow-origin'])
1133
1154
 
1134
1155
  const VALUE_OPTIONS = {
1156
+ xpath4: new Set(['expression', 'file', 'input', 'format']),
1157
+ xquery: new Set(['query', 'file', 'input', 'format']),
1158
+ xslt4: new Set(['stylesheet', 'input', 'format']),
1135
1159
  version: new Set([]),
1136
1160
  'sample-store': new Set([]),
1137
1161
  inspect: new Set(['generation']),
@@ -2965,7 +2989,16 @@ async function commandLwsClient (positional, options) {
2965
2989
  return EXIT_OK
2966
2990
  }
2967
2991
 
2992
+ const qt4Host = {
2993
+ loadEngine,
2994
+ readText: (path) => new TextDecoder('utf-8', { fatal: true }).decode(readWhole(path)),
2995
+ out, err,
2996
+ }
2997
+
2968
2998
  const COMMANDS = {
2999
+ xpath4: (positional, options) => runQt4('xpath4', positional, options, qt4Host),
3000
+ xquery: (positional, options) => runQt4('xquery', positional, options, qt4Host),
3001
+ xslt4: (positional, options) => runQt4('xslt4', positional, options, qt4Host),
2969
3002
  version: (positional, options) => commandVersion(options),
2970
3003
  'sample-store': (positional, options) => commandSampleStore(positional, options),
2971
3004
  inspect: commandInspect,
@@ -3017,7 +3050,7 @@ async function main () {
3017
3050
  try {
3018
3051
  parsed = parseArguments(argv.slice(1), VALUE_OPTIONS[command])
3019
3052
  } catch (error) {
3020
- if (error instanceof UsageError) {
3053
+ if (error instanceof UsageError || error instanceof Qt4UsageError) {
3021
3054
  err(`factoidal ${command}: ${error.message}`)
3022
3055
  return EXIT_USAGE
3023
3056
  }
@@ -3034,7 +3067,7 @@ async function main () {
3034
3067
  try {
3035
3068
  return await COMMANDS[command](parsed.positional, parsed.options)
3036
3069
  } catch (error) {
3037
- if (error instanceof UsageError) {
3070
+ if (error instanceof UsageError || error instanceof Qt4UsageError) {
3038
3071
  err(`factoidal ${command}: ${error.message}`)
3039
3072
  err('')
3040
3073
  err(COMMAND_USAGE[command])
package/bin/qt4.mjs ADDED
@@ -0,0 +1,49 @@
1
+ // CLI transport only: read explicit files, call the existing Lean operation,
2
+ // display its JSON/serialization, and preserve the engine's QT4 error QName.
3
+ import { errorFromEnvelope } from '../lib/errors.mjs';
4
+
5
+ export class Qt4UsageError extends Error {}
6
+
7
+ export async function runQt4(command, positional, options, host) {
8
+ const transform = command === 'xslt4';
9
+ const key = transform ? 'stylesheet' : command === 'xpath4' ? 'expression' : 'query';
10
+ const allowed = new Set([key, 'input', 'format', 'help', ...(transform ? [] : ['file'])]);
11
+ for (const option of Object.keys(options)) {
12
+ if (!allowed.has(option)) throw new Qt4UsageError(`unknown option --${option}`);
13
+ }
14
+ if (positional.length > 1) throw new Qt4UsageError('expected at most one positional argument');
15
+ const sources = [positional[0], options[key], options.file].filter((value) => value !== undefined);
16
+ if (sources.length !== 1 || typeof sources[0] !== 'string') {
17
+ throw new Qt4UsageError(`supply exactly one ${key}${transform ? ' file' : ' text or --file PATH'}`);
18
+ }
19
+ if (options.input !== undefined && typeof options.input !== 'string') throw new Qt4UsageError('--input needs a file path');
20
+ const format = options.format ?? 'json';
21
+ const formats = transform ? ['json', 'output'] : ['json', 'adaptive', 'xml'];
22
+ if (!formats.includes(format)) throw new Qt4UsageError(`--format must be ${formats.join(', ')}`);
23
+ try {
24
+ const expression = transform || options.file !== undefined ? host.readText(sources[0]) : sources[0];
25
+ const input = options.input === undefined ? '' : host.readText(options.input);
26
+ const op = transform ? 'xslt4Transform' : command === 'xpath4' ? 'xpath4Eval' : 'xqueryEval';
27
+ const args = command === 'xpath4' ? [input, expression] : [expression, input];
28
+ const result = (await host.loadEngine()).call(op, args);
29
+ if (format === 'json') host.out(JSON.stringify(result, null, 2));
30
+ else {
31
+ const text = result[format];
32
+ if (text === null || typeof text !== 'string') {
33
+ host.err(`factoidal ${command}: result has no ${format} serialization; use --format json`);
34
+ return 1;
35
+ }
36
+ host.out(text);
37
+ }
38
+ return 0;
39
+ } catch (error) {
40
+ // loadL4.call throws a plain Error with the original envelope. API wrappers
41
+ // normalize it separately; the direct CLI call must preserve it here too.
42
+ const envelope = error?.envelope;
43
+ const problem = envelope && typeof envelope === 'object' && envelope.ok === false
44
+ ? errorFromEnvelope(envelope) : error;
45
+ host.err(JSON.stringify({ error: String(problem?.message ?? problem),
46
+ ...(problem?.code ? { code: problem.code } : {}), ...(problem?.qtError ? { qtError: problem.qtError } : {}) }));
47
+ return 1;
48
+ }
49
+ }
package/index.d.ts CHANGED
@@ -971,6 +971,7 @@ export interface EvidenceRecord {
971
971
  * `wireOut`: a Dataset for the graph wires, text for String (a writer), the
972
972
  * rows for Table (SELECT), a boolean for Answer (ASK), `[canonical, sha256]`
973
973
  * for String × String (rdfcStage), the report object for ValidationReport,
974
+ * a Qt4Result for Serialized XDM result, or an Xslt4Result for XSLT result,
974
975
  * the explain result for ExplainGraphResult. Since 0.20.0; before, a node
975
976
  * whose output was not a Dataset left its input on the wire.
976
977
  */
@@ -985,7 +986,12 @@ export interface NodePort {
985
986
  readonly wire: string;
986
987
  }
987
988
 
988
- /** A node: a registered stage with its parameters. */
989
+ /** A node: a registered stage with its parameters. Public XML adapters use
990
+ * `xpath4Eval` with {expression}, `xqueryEval` with {query}, or
991
+ * `xslt4Transform` with {stylesheet}; each reads XML context text and has
992
+ * status tested, checked:false evidence and no proved evaluator contract.
993
+ * Their serialized output types are Qt4Result and Xslt4Result (see Wire).
994
+ */
989
995
  export interface FactoidalNode {
990
996
  readonly stage: string;
991
997
  readonly kind: string | null;
@@ -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 = "4eb84cd7df99";
40
+ const WASM_VERSION = "b2be461959a2";
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-6a098e57dba3-web",
6
- "releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40857881585",
7
- "gitSha": "6a098e57dba3b4533e74ee3d0f848c684cf7e672",
8
- "builtAt": "2026-10-29T11:11:37Z",
5
+ "release": "danbri/factoidal-builds build-a9bd50c3b28b-web",
6
+ "releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/41040584515",
7
+ "gitSha": "a9bd50c3b28b3d42c5071cc8c3e74a5ede8706db",
8
+ "builtAt": "2026-10-30T11:09:24Z",
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": "4eb84cd7df99ec058ab41780357f0002e86b5555ea2c3e34214838c2ab3fb2d1",
15
- "wasmBytes": 30053696,
14
+ "wasmSha256": "b2be461959a2ac90ffe8c010b61e70fa4b12f894c2973365904096f41fa6f6ee",
15
+ "wasmBytes": 30524277,
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
@@ -2696,7 +2696,7 @@ function buildApi(driver) {
2696
2696
  const nodeApi = buildNodes({
2697
2697
  entry, entryResult, requireEntryFn, Dataset,
2698
2698
  parse, closure, shaclValidate, query, serialize, canonicalize, canonicalHash, explain, csvwToRdf,
2699
- datasetMerge,
2699
+ datasetMerge, xpath4Eval, xqueryEval, xslt4Transform,
2700
2700
  });
2701
2701
 
2702
2702
  // Controlled Identifiers v1.0 (lib/cid.mjs, issue 1100 items 7 and 9).
package/lib/nodes.mjs CHANGED
@@ -66,7 +66,7 @@ import { refusalFor, makeRecord, summarize, unionAssumptions, unionLists } from
66
66
  export function buildNodes(deps) {
67
67
  const { entry, entryResult, requireEntryFn, Dataset,
68
68
  parse, closure, shaclValidate, query, serialize, canonicalize, canonicalHash, explain, csvwToRdf,
69
- datasetMerge } = deps;
69
+ datasetMerge, xpath4Eval, xqueryEval, xslt4Transform } = deps;
70
70
 
71
71
  const reader = (format) => async (wire, params) => {
72
72
  if (typeof wire.graph !== 'string') {
@@ -92,6 +92,15 @@ export function buildNodes(deps) {
92
92
  // node's output wire (FactoidalNode.wireOut, the engine's), which run()
93
93
  // checks (wireHolds); `artefact` goes in the evidence record.
94
94
  const RUNNERS = {
95
+ xpath4Eval: async (wire, params) => ({
96
+ graph: await xpath4Eval(wire.graph, params.expression), artefact: { operation: 'xpath4Eval' },
97
+ }),
98
+ xqueryEval: async (wire, params) => ({
99
+ graph: await xqueryEval(params.query, wire.graph), artefact: { operation: 'xqueryEval' },
100
+ }),
101
+ xslt4Transform: async (wire, params) => ({
102
+ graph: await xslt4Transform(params.stylesheet, wire.graph), artefact: { operation: 'xslt4Transform' },
103
+ }),
95
104
  ntriplesDocumentReader: reader('ntriples'),
96
105
  nquadsDocumentReader: reader('nquads'),
97
106
  turtleDocumentReader: reader('turtle'),
@@ -181,6 +190,17 @@ export function buildNodes(deps) {
181
190
  case 'Table': return Array.isArray(value);
182
191
  case 'String × String':
183
192
  return Array.isArray(value) && value.length === 2 && value.every((v) => typeof v === 'string');
193
+ // Transport shapes of the existing Lean serializers, not live XDM nodes
194
+ // or a promise that arbitrary result text is a well-formed XML document.
195
+ case 'Serialized XDM result':
196
+ return value !== null && typeof value === 'object' && Number.isSafeInteger(value.count) && value.count >= 0 &&
197
+ Array.isArray(value.items) && value.items.length === value.count &&
198
+ value.items.every((item) => item && typeof item.type === 'string' && typeof item.value === 'string') &&
199
+ typeof value.adaptive === 'string' && (value.xml === null || typeof value.xml === 'string');
200
+ case 'XSLT result':
201
+ return value !== null && typeof value === 'object' && typeof value.output === 'string' && typeof value.method === 'string' &&
202
+ Array.isArray(value.secondary) && value.secondary.every((item) => item && typeof item.href === 'string' && typeof item.output === 'string') &&
203
+ Array.isArray(value.messages) && value.messages.every((item) => typeof item === 'string');
184
204
  default: return value !== undefined && value !== null;
185
205
  }
186
206
  }
package/lib/version.mjs CHANGED
@@ -2,4 +2,4 @@
2
2
  // needs import attributes, which not every runtime this package targets
3
3
  // reads). test/dts-drift.test.js checks that it equals package.json's
4
4
  // `version`.
5
- export const version = '0.25.0';
5
+ export const version = '0.27.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factoidal/core",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "EXPERIMENTAL UNREVIEWED CODE: do not run public services on it without careful independent expert review. 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",