@factoidal/core 0.18.0 → 0.20.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 +27 -0
- package/README.md +37 -14
- package/authz/server.mjs +5 -0
- package/bin/factoidal.mjs +47 -8
- package/browser.d.ts +13 -0
- package/browser.js +14 -0
- package/index.d.ts +224 -21
- package/index.mjs +14 -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 +251 -23
- package/lib/dataset.mjs +96 -12
- package/lib/lean-entry.mjs +30 -12
- package/lib/nodes.mjs +92 -14
- package/lib/sbm11-lws.mjs +4 -2
- package/lib/sbm11.mjs +16 -6
- package/lib/version.mjs +1 -1
- package/package.json +2 -2
- package/rdfjs.mjs +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.20.0 (2026-10-27)
|
|
6
|
+
|
|
7
|
+
- **Breaking:** in a checked pipeline (`node`, `pipe`, `run`), each node now puts a value of its output wire on the wire: text after a writer, the rows after SELECT, a boolean after ASK, the report after `shaclValidator`, `[canonical, sha256]` after `rdfcStage`, the result after `rdfsExplainGraphStage`. Before, such a node left its input Dataset on the wire, so a well-typed reader → writer → reader pipeline failed at run time. `run()` rejects a runner value that is not of the node's `wireOut`. `rdfcStage` as a node takes a Dataset only (https://github.com/danbri/factoidal-core/pull/155).
|
|
8
|
+
- `node()` keeps its own copy of its settings (frozen at every depth; a Dataset is cloned). A change to the caller's objects after `node()` no longer changes what runs while the evidence keeps the old `settingsDigest`. `run()` rejects a node whose settings changed through `node.params` (https://github.com/danbri/factoidal-core/pull/155).
|
|
9
|
+
- **Breaking:** `canonicalHash(dataset)` keeps graph names: it is the SHA-256 of the text `canonicalize()` answers. 0.17.0 to 0.19.0 dropped graph names, so datasets that differ only in graph names got one hash. New `asDefaultGraph(dataset)`; `canonicalHash(asDefaultGraph(g))` gives a graph's hash whatever its name (https://github.com/danbri/factoidal-core/pull/155).
|
|
10
|
+
- SBM11 stores opened with `openSbm11` (and the wasm ops `sbm11Open` / `sbm11Query`) are checked against their Merkle roots by default: the open admits each file's `.merkle` sidecar against `manifest.sbm11`, and every byte a query decodes comes from a 65,536-byte chunk checked by its Merkle proof. A changed data file, sidecar or Merkle reference in the manifest fails the open or the query with an error that names the file (before, a changed `dict.gdv1` was answered as data). `openSbm11(source, { verify: 'none' })` keeps the earlier length-only check; `store.verify` says which mode is in use. Measured cost: within run-to-run noise on stores of 1 to 6 MB (https://github.com/danbri/factoidal-core/pull/157).
|
|
11
|
+
- Parse is faster again after the IRI check of 0.19.0 (https://github.com/danbri/factoidal/issues/1057): the check tests bytes, and a repeated IRI is not checked again. Parse of 100,000 triples is now about 3% slower than 0.18.0 (it was 10% to 19%). The IRIs that are accepted and refused do not change (https://github.com/danbri/factoidal-core/pull/158).
|
|
12
|
+
- CID 1.0 (Controlled Identifiers, W3C Recommendation 2025-05-15): new Lean module `L4Factoidal/CID/` with the section 2 data model (one named error per MUST), Multikey and JsonWebKey material, the five verification relationships, Fragment Resolution, Retrieve Verification Method and the section 3.5 processing errors. The CID context is vendored. Authorization server, CID suite (lws10-authn-ssi-cid): the subject document is checked as a whole and the method is found by Retrieve Verification Method with `authentication`; `expires`/`revoked` need a time zone; `Accept` starts with `application/cid`. VC Data Integrity (Lean): `verificationMethod` can be a method in a controlled identifier document (not only an Ed25519 did:key), and it must be in the relationship that `proofPurpose` names. No npm functions yet (https://github.com/danbri/factoidal/issues/1100, https://github.com/danbri/factoidal-core/pull/161).
|
|
13
|
+
- `parse` takes the option `rdfstar` for JSON-LD, as the 0.18.0 changelog says: `parse(text, { format: 'jsonld', rdfstar: true })` reads `@triple`, `@reifies` and `@annotation` as RDF 1.2 triple terms and reifiers, as `jsonldToRdf` does. 0.18.0 and 0.19.0 refused the option with `option.unknown`. `rdfstar` with another format, or with `lenient`, is refused with `option.conflict` (https://github.com/danbri/factoidal/issues/1091, https://github.com/danbri/factoidal-core/pull/152).
|
|
14
|
+
- Docs: hub page "JSON-LD-star and JSON-LD 1.3" (`docs/web/hub/jsonld-star.md`), with live cells on 0.19.0; the hub pages load 0.19.0 (were 0.16.0).
|
|
15
|
+
- Value matching (https://github.com/danbri/factoidal/issues/1052): new `sameValue(a, b)` is SPARQL 1.1 `=` on two terms (sections 17.3, 17.4.1.7), decided by the engine (Lean `SPARQL/SameValue.lean`). It answers `true`, `false`, or `null` for incomparable (the type error of `=`). `Dataset.has`, `match` and `delete` take `{match: 'value'}`: a literal object then matches each object literal of the same value (`"1"^^xsd:int` finds `"01"^^xsd:integer` and `"1.0"^^xsd:decimal`), through an index of the literals by value. Term matching stays the default. New wasm op `sameValue`; `datasetMatch`, `datasetMatchCount` and `datasetDeleteQuadsJson` take an optional options argument.
|
|
16
|
+
- Syntax names (https://github.com/danbri/factoidal/issues/1053): new `syntaxFor(fileName)` and `syntaxFor({fileName, mediaType})` give the syntax of a document from the W3C registrations (Lean `RDF/MediaType.lean`), or `null`. Each row of `formats()` has `mediaTypes` and `extensions` from the same table (README format table: two new columns). New wasm op `syntaxFor`.
|
|
17
|
+
- IRIs, vocabularies, lists and containers (https://github.com/danbri/factoidal/issues/1051 items 4 to 6): new `splitIri(iri)` (namespace and the longest XML NCName suffix), `isNCName(text)`, `vocabularies()` (rdf, rdfs, xsd and owl with their terms; foaf, dcterms, dc and schema namespaces), `memberIri(n)` and `memberOrdinal(iri)` (`rdf:_n`). Dataset methods `listItems(head, graph?)` (refuses a list that is not well formed, with the reason), `containerItems(node, graph?)` and `addList(items, graph?)`. Lean `RDF/IriSplit.lean`, `RDF/VocabularyTable.lean`, `RDF/Collections.lean`. New wasm ops `splitIri`, `isNCName`, `vocabularies`, `memberIri`, `memberOrdinal`, `datasetListItems`, `datasetContainerItems`, `datasetAddList`. These need a wasm built from this source.
|
|
18
|
+
- Literal values (https://github.com/danbri/factoidal/issues/1051 items 1 to 3). New functions `literalValue(term)`, `isValidLiteral(term)`, `canonicalLiteral(term)` and `literalFromValue(value, {datatype?})`, synchronous after `ready()` (API.md "Literal values"). `literalValue` gives the XSD 1.1 lexical-to-value mapping of a literal for every XSD built-in the engine has (numeric types and the derived integer types with their bounds, boolean, the string types, date/time and `g*` types, durations, binary, lists) and for `rdf:langString`, `rdf:dirLangString`, `rdf:XMLLiteral`, `rdf:JSON`: `{datatype, recognized, valid, value?, canonical?}`, with the value as JSON-safe data (a number that a JavaScript number does not hold exactly is a decimal string). `valid: false` is an ill-typed literal; `recognized: false` is a datatype the engine does not have. `literalFromValue` maps a boolean, number, bigint, string or Date to a typed literal with one engine table (safe integer `xsd:integer`, other number `xsd:double`, bigint `xsd:integer`, Date `xsd:dateTime` in UTC), or to a named datatype (`{datatype: 'int'}`) when the value is in its value space, else a `FactoidalError` with the new code `literal.value`. New wasm ops `literalValue` and `literalFromValue` (Lean `L4Factoidal/RDF/LiteralValue.lean`, `Wasm/Ops/LiteralValue.lean`). An extension function may now answer a `Date` (an `xsd:dateTime`).
|
|
19
|
+
- The canonical form of an `xsd:float` or `xsd:double` value is now the XSD 1.1 Part 2 §3.3.4.2/§3.3.5.2 form (algorithm §E.1): the fewest significant digits that map back to the value. `"0.1"^^xsd:double` has the canonical form `1.0E-1`; before, it was the exact binary expansion `1.000000000000000055511151231257827021181583404541015625E-1`. It is the `canonical` member of `literalValue` and RML's canonical form of a double (RML-Core natural mapping). SPARQL casts do not change (`xsd:double("0.1")` is still `"0.1"^^xsd:double`). Theorems `double_canonical_roundTrip`, `float_canonical_roundTrip`, `integer_canonical_roundTrip` (`XSD/CanonicalTheorems.lean`).
|
|
20
|
+
|
|
21
|
+
## 0.19.0 (2026-10-27)
|
|
22
|
+
|
|
23
|
+
- Notice (owner decision, 2026-10-27): "EXPERIMENTAL UNREVIEWED CODE - USE WITH CARE - DO NOT CREATE PUBLIC SERVICES USING THIS WITHOUT CAREFUL INDEPENDENT EXPERT REVIEW." It is at the top of both READMEs and the hub, in the package description, in every `factoidal` help text, and on standard error when `serve`, `lws-serve`, `solid-serve`, `solid-client`, `lws-client`, `authz-serve`, `authz-keys`, `xmpp-serve` or `webdav-serve` starts.
|
|
24
|
+
- Authorization server (https://github.com/danbri/factoidal-core/pull/150): SAML 2.0 subject tokens (lws10-authn-saml, token type `urn:ietf:params:oauth:token-type:saml2`) from the identity providers the configuration trusts: `authz-serve --saml-idp FILE` (repeatable; also `lws-serve --authz`), config member `samlIdps` (`[{ entityId, jwk }]`, an RSA public JWK of 2048 bits or more). The assertion needs one enveloped XML Signature in the SAML Core §5 profile (Exclusive XML Canonicalization 1.0, rsa-sha256, SHA-256 digest, the reference `#` + the assertion's `ID`), verified under the configured key, never a key from `ds:KeyInfo`; a document type declaration, comments, processing instructions, a second signature or a wrapped assertion are refused. `saml:NameID` is the subject, the bearer `Recipient` the client; `Conditions` times and every `AudienceRestriction` must name this server. Encrypted assertions are refused (https://github.com/danbri/factoidal/issues/1073). The token type is advertised only when a provider is configured (https://github.com/danbri/factoidal/issues/926). Lean: `XML/ExcC14n.lean`, `XML/DSig.lean`, `SAML/Assertion.lean`; record `docs/designissues/2026-10-27-saml-xml-signature.md`.
|
|
25
|
+
|
|
26
|
+
Fixes for defects found by the echo test ports (https://github.com/danbri/echochamber: Jena-, RDFLib-, N3.js- and RDF4J-style test suites run against this package).
|
|
27
|
+
|
|
28
|
+
- SPARQL (https://github.com/danbri/factoidal-core/pull/146): blank nodes written by SPARQL Update get valid N-Triples labels, so CONSTRUCT, DESCRIBE and `shaclValidate` on that Dataset no longer fail (https://github.com/danbri/factoidal/issues/1074); IN and NOT IN raise an error when a comparison errs and no other element decides, so `2 IN (3, 1/0)` is an error, not false (§17.4.1.9, https://github.com/danbri/factoidal/issues/1049); ORDER BY an aggregate (`ORDER BY DESC(COUNT(?s))`) orders the groups of a grouped query (https://github.com/danbri/factoidal/issues/1050); SUBSTR takes start and length of types derived from xsd:integer, such as xsd:int (https://github.com/danbri/factoidal/issues/1060); no XSD constructor function casts a language-tagged literal: `xsd:string("foo"@en)` and `xsd:byte("1"@en)` are errors, use `STR` (§17.5, https://github.com/danbri/factoidal/issues/1062); `true.` and `false.` read as the keyword and the triple terminator (https://github.com/danbri/factoidal/issues/1075); a variable used only inside MINUS is not in scope, so `(1 AS ?z)` is allowed (§18.2.1, https://github.com/danbri/factoidal/issues/1069).
|
|
29
|
+
- Readers and writers (https://github.com/danbri/factoidal-core/pull/147): N-Triples 1.2 and N-Quads 1.2 read the VERSION directive (https://github.com/danbri/factoidal/issues/1055); TriG refuses a top-level triple with no final '.' (https://github.com/danbri/factoidal/issues/1056); Turtle and TriG read white space or a comment after `^^`, and a prefix named `true` or `false` (https://github.com/danbri/factoidal/issues/1063); one RFC 3987 IRI check: the readers, `fromQuads`/`Dataset.add`, the SPARQL parser and the writers refuse a string that is not an IRI, such as `<a_b:c>`, `<http://www:example.org/>` or `<http://a/%zz>`, with `term.invalid-iri`, so no writer writes text that no reader reads (https://github.com/danbri/factoidal/issues/1057); RDF/XML writer: CR in a literal is written `
` (https://github.com/danbri/factoidal/issues/1047), a predicate such as `urn:example:p` is written with namespace `urn:example:` (https://github.com/danbri/factoidal/issues/1048); RDF/JS: `dataFactory.literal` gives the language tag in lowercase, and the `Literal` type has `direction` (https://github.com/danbri/factoidal/issues/1054); JSON-LD: a `"@foo"` key in a value object is ignored with or without a base (https://github.com/danbri/factoidal/issues/1064); `parse()` with no format: JSON-LD only for JSON text, N-Quads when a statement names a graph; the rules are in API.md (https://github.com/danbri/factoidal/issues/1058).
|
|
30
|
+
- Cost: the IRI check makes parsing slower: 100,000 triples, median of 3, 0.18 → 0.19: N-Triples 581 → 646 ms (+11%), Turtle 606 → 718 ms (+19%), N-Quads 1.2 616 → 681 ms (+10%). Work to recover it is in progress.
|
|
31
|
+
|
|
5
32
|
## 0.18.0 (2026-10-27)
|
|
6
33
|
|
|
7
34
|
- XSD constructor functions for datatypes outside the SPARQL 1.1 §17.5 table (`xsd:byte`, `xsd:int`, `xsd:unsignedInt`, `xsd:date`, `xsd:time`, `xsd:gYear` and the other `xsd:g*` types, the duration types, `xsd:token`, …) now cast as XPath F&O 3.1 §19 says. Before, they changed only the datatype and gave ill-typed literals such as `"HaHa"^^xsd:byte`. An invalid value or a cast that F&O does not allow is now an error (the variable is unbound) (https://github.com/danbri/factoidal/issues/1041, https://github.com/danbri/factoidal-core/pull/142).
|
package/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @factoidal/core
|
|
2
2
|
|
|
3
|
+
> ⚠️ **EXPERIMENTAL UNREVIEWED CODE - USE WITH CARE - DO NOT CREATE PUBLIC SERVICES USING THIS WITHOUT CAREFUL INDEPENDENT EXPERT REVIEW.**
|
|
4
|
+
>
|
|
5
|
+
> This covers the whole core library and every command line tool, and in
|
|
6
|
+
> particular all protocol, authentication and security code (the LWS, Solid,
|
|
7
|
+
> SPARQL Protocol, WebDAV and XMPP servers and clients, the authorization
|
|
8
|
+
> server, JOSE, XML Signature and SAML, the crypto bindings). Owner decision,
|
|
9
|
+
> 2026-10-27.
|
|
10
|
+
|
|
3
11
|
RDF, SPARQL, reasoning, validation and Linked Data servers in one package.
|
|
4
12
|
Runs in Node 22.12+, Deno 2 and browsers. No dependencies.
|
|
5
13
|
|
|
@@ -30,10 +38,14 @@ every consumer accepts one, so a pipeline does not re-parse between steps.
|
|
|
30
38
|
An unknown option key throws. With no `format`, the format is inferred and
|
|
31
39
|
reported in `dataset.format`; an explicit `format` is never overridden.
|
|
32
40
|
RDF 1.2 input uses `turtle12`, `ntriples12`, `nquads12` or `trig12`.
|
|
41
|
+
RDF 1.2 in JSON-LD (JSON-LD-star: `@triple`, `@reifies`, `@annotation`,
|
|
42
|
+
`@direction`) needs the option `rdfstar`: `jsonldToRdf(text, { rdfstar: true })`
|
|
43
|
+
and `dataset.serialize({ format: 'jsonld', rdfstar: true })`.
|
|
33
44
|
|
|
34
45
|
## What it does
|
|
35
46
|
|
|
36
47
|
- Syntaxes: N-Triples, N-Quads, Turtle, TriG, RDF/XML, JSON-LD; RDF 1.1 and 1.2
|
|
48
|
+
(RDF 1.2 in JSON-LD through the JSON-LD-star draft, option `rdfstar`)
|
|
37
49
|
- SPARQL 1.1 and 1.2 query and update; GeoSPARQL functions, text search,
|
|
38
50
|
JavaScript extension functions, SERVICE
|
|
39
51
|
- Reasoning: RDFS, OWL 2 RL closure, restricted checked OWL consistency,
|
|
@@ -43,7 +55,13 @@ RDF 1.2 input uses `turtle12`, `ntriples12`, `nquads12` or `trig12`.
|
|
|
43
55
|
- Canonicalization: RDFC-1.0; Verifiable Credentials Data Integrity (eddsa-rdfc-2022), did:key
|
|
44
56
|
- XML: XML 1.0, XPath 1.0, XSLT 1.0, XForms
|
|
45
57
|
- Servers: SPARQL 1.1 Protocol and Graph Store (Fuseki-compatible paths,
|
|
46
|
-
Jena TDB2 read only), Linked Web Storage 1.0, Solid
|
|
58
|
+
Jena TDB2 read only), Linked Web Storage 1.0, Solid, an OAuth 2.0
|
|
59
|
+
authorization server (token exchange for OpenID Connect ID tokens,
|
|
60
|
+
self-issued CID credentials and SAML 2.0 assertions)
|
|
61
|
+
- Security: JWS/JWT (ES256, RS256, EdDSA), XML Signature verification in the
|
|
62
|
+
SAML Core profile (Exclusive XML Canonicalization 1.0, rsa-sha256), with
|
|
63
|
+
HACL\* primitives. XML Encryption and JWE are not implemented yet:
|
|
64
|
+
https://github.com/danbri/factoidal/issues/1081
|
|
47
65
|
- A persisted store with a delta log for updates (early stage)
|
|
48
66
|
- Terms: a JavaScript extension function can answer with an exact term
|
|
49
67
|
(`{value: "007", datatype: XSD + "integer"}`, an RDF/JS term, a triple
|
|
@@ -104,19 +122,19 @@ and marks those steps in the evidence. Details: API.md, "Stages as nodes" and
|
|
|
104
122
|
Formats (generated from `formats()`):
|
|
105
123
|
|
|
106
124
|
<!-- formats-table:start (generated by test/formats-table.test.mjs) -->
|
|
107
|
-
| format | read RDF 1.1 | read RDF 1.2 | write RDF 1.1 | write RDF 1.2 | engine |
|
|
108
|
-
|
|
109
|
-
| nquads | yes | yes | yes | yes | core |
|
|
110
|
-
| ntriples | yes | yes | yes | yes | core |
|
|
111
|
-
| turtle | yes | yes | yes | yes | core |
|
|
112
|
-
| trig | yes | yes | yes | yes | core |
|
|
113
|
-
| rdfxml | yes | no | yes | no | core |
|
|
114
|
-
| jsonld | yes | no | yes | no | full |
|
|
115
|
-
| rdfc | no | no | yes | no | core |
|
|
116
|
-
| sparql-results-json | no | no | yes | yes | core |
|
|
117
|
-
| sparql-results-xml | no | no | yes | yes | core |
|
|
118
|
-
| sparql-results-csv | no | no | yes | yes | core |
|
|
119
|
-
| sparql-results-tsv | no | no | yes | yes | core |
|
|
125
|
+
| format | read RDF 1.1 | read RDF 1.2 | write RDF 1.1 | write RDF 1.2 | engine | media type | extension |
|
|
126
|
+
|---|---|---|---|---|---|---|---|
|
|
127
|
+
| nquads | yes | yes | yes | yes | core | `application/n-quads` | `.nq` |
|
|
128
|
+
| ntriples | yes | yes | yes | yes | core | `application/n-triples` | `.nt` |
|
|
129
|
+
| turtle | yes | yes | yes | yes | core | `text/turtle` | `.ttl` |
|
|
130
|
+
| trig | yes | yes | yes | yes | core | `application/trig` | `.trig` |
|
|
131
|
+
| rdfxml | yes | no | yes | no | core | `application/rdf+xml` | `.rdf` |
|
|
132
|
+
| jsonld | yes | no | yes | no | full | `application/ld+json` | `.jsonld` |
|
|
133
|
+
| rdfc | no | no | yes | no | core | | |
|
|
134
|
+
| sparql-results-json | no | no | yes | yes | core | | |
|
|
135
|
+
| sparql-results-xml | no | no | yes | yes | core | | |
|
|
136
|
+
| sparql-results-csv | no | no | yes | yes | core | | |
|
|
137
|
+
| sparql-results-tsv | no | no | yes | yes | core | | |
|
|
120
138
|
<!-- formats-table:end -->
|
|
121
139
|
|
|
122
140
|
Missing writers (HDT, N3, RDFa): https://github.com/danbri/factoidal/issues/883.
|
|
@@ -146,6 +164,7 @@ Codes (generated from `engineTables().errorCodes`):
|
|
|
146
164
|
| `term.invalid-iri` | <https://www.w3.org/TR/rdf12-concepts/#section-IRIs> | A term that must be an IRI is not one. |
|
|
147
165
|
| `term.invalid-langtag` | <https://www.w3.org/TR/rdf12-concepts/#section-Graph-Literal> | A language tag is not well-formed (BCP 47). |
|
|
148
166
|
| `term.invalid` | <https://www.w3.org/TR/sparql11-results-json/#select-encode-terms> | A term or quad given as JSON is not an RDF term or quad. |
|
|
167
|
+
| `literal.value` | <https://www.w3.org/TR/xmlschema11-2/#value-space> | The value is not in the value space of the named datatype, or the datatype is not one the engine has. |
|
|
149
168
|
| `query.syntax` | <https://www.w3.org/TR/sparql11-query/#grammar> | The SPARQL query does not match the SPARQL grammar. |
|
|
150
169
|
| `query.failed` | <https://www.w3.org/TR/sparql11-query/#sparqlDefinition> | The SPARQL query parsed but its evaluation was refused. |
|
|
151
170
|
| `update.syntax` | <https://www.w3.org/TR/sparql11-update/#grammar> | The SPARQL update does not match the SPARQL Update grammar. |
|
|
@@ -189,6 +208,7 @@ $ npx factoidal query store 'SELECT * WHERE { ?s ?p ?o } LIMIT 5'
|
|
|
189
208
|
$ npx factoidal query "$(npx factoidal sample-store)" 'SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }'
|
|
190
209
|
$ npx factoidal lws-serve DIR --issuer IRI # Linked Web Storage
|
|
191
210
|
$ npx factoidal solid-serve DIR # Solid Protocol
|
|
211
|
+
$ npx factoidal authz-serve DIR --resource IRI --saml-idp idp.json # OAuth 2.0 authorization server
|
|
192
212
|
```
|
|
193
213
|
|
|
194
214
|
`npx factoidal --help` lists every command. Replacing Apache Jena Fuseki:
|
|
@@ -224,6 +244,8 @@ and the [assurance record](https://github.com/danbri/factoidal-core/blob/main/do
|
|
|
224
244
|
| JSON-LD 1.1 toRdf, expand, flatten | 910 / 0 / 910 |
|
|
225
245
|
| JSON-LD 1.1 compact, fromRdf | 298 / 0 / 300 (2 not run) |
|
|
226
246
|
| JSON-LD 1.1 Framing | 29 / 63 / 92 |
|
|
247
|
+
| JSON-LD-star draft (RDF 1.2), option `rdfstar` | 168 / 1 / 171 (2 local override; 170 of 171 with them) [J] |
|
|
248
|
+
| RDF 1.2 eval tests written as JSON-LD-star and read back | 140 / 0 / 140 [J] |
|
|
227
249
|
| RDFC-1.0 | 86 / 0 / 86 |
|
|
228
250
|
| OWL 2 RL | 126 / 0 / 126 |
|
|
229
251
|
| OWL 2 DL type-inconsistency | 116 / 11 / 128 (1 other) |
|
|
@@ -247,6 +269,7 @@ and the [assurance record](https://github.com/danbri/factoidal-core/blob/main/do
|
|
|
247
269
|
[L] 2026-10-22, https://github.com/danbri/factoidal-core/blob/2b9d2e39af53be31ed92463b58d3e132e65c4fe4/docs/designissues/2026-10-21-lws-touchstone-run.md (with server options, 220 of 221 pass in at least one configuration)
|
|
248
270
|
[S] 2026-10-21, the current W3C manifest (29 active tests), https://github.com/danbri/factoidal-core/blob/2b9d2e39af53be31ed92463b58d3e132e65c4fe4/docs/claude-rules/current-state.md
|
|
249
271
|
[C] 2026-10-21, validation test308, https://github.com/danbri/factoidal/issues/700
|
|
272
|
+
[J] 2026-10-27, tests of https://github.com/json-ld/json-ld-star/pull/61; the fail is a defective fixture (fromRdf tst07), https://github.com/danbri/factoidal-core/blob/2a656d7bfb4af329d77669afb5e62eb00625bd15/docs/designissues/2026-10-26-jsonld-star.md
|
|
250
273
|
|
|
251
274
|
## Limits
|
|
252
275
|
|
package/authz/server.mjs
CHANGED
|
@@ -169,6 +169,8 @@ async function fetchFrom (url, accept, address) {
|
|
|
169
169
|
* @param {string|null} [options.storageKey] the key text; default FACTOIDAL_LWS_KEY; null reads none
|
|
170
170
|
* @param {boolean|string[]} [options.cors] false for no CORS fields, or the origins to answer;
|
|
171
171
|
* default: echo any valid request Origin (issue 923)
|
|
172
|
+
* @param {{entityId: string, jwk: object}[]} [options.samlIdps] the SAML 2.0 identity providers
|
|
173
|
+
* whose signed assertions are accepted, each with its RSA public JWK (issue 926)
|
|
172
174
|
* @param {object} [options.engine] an engine to reuse
|
|
173
175
|
*/
|
|
174
176
|
export async function openAuthorizationServer (options = {}) {
|
|
@@ -192,6 +194,9 @@ export async function openAuthorizationServer (options = {}) {
|
|
|
192
194
|
// CORS (issue 923): false or a list of origins, passed unread; absent, the
|
|
193
195
|
// engine echoes the request Origin (L4Factoidal/Authz/Server.lean stepCors).
|
|
194
196
|
if (options.cors !== undefined) config.cors = options.cors
|
|
197
|
+
// SAML 2.0 identity providers (issue 926): [{ entityId, jwk }], passed
|
|
198
|
+
// unread; the engine checks each (Wasm/Ops/Authz.lean samlIdpsOf?).
|
|
199
|
+
if (options.samlIdps !== undefined) config.samlIdps = options.samlIdps
|
|
195
200
|
const key = storageKeyText(options.storageKey)
|
|
196
201
|
if (key !== undefined) config.storageKey = key
|
|
197
202
|
if (dir !== null) {
|
package/bin/factoidal.mjs
CHANGED
|
@@ -90,6 +90,15 @@ function err (line) { console.error(line) }
|
|
|
90
90
|
|
|
91
91
|
// ---------------------------------------------------------------- usage
|
|
92
92
|
|
|
93
|
+
// Owner, 2026-10-27: every protocol, authentication and security component,
|
|
94
|
+
// the CLIs and the core library carry this notice. It is printed with every
|
|
95
|
+
// help text and on standard error when a server, client or key command starts.
|
|
96
|
+
const REVIEW_WARNING = 'EXPERIMENTAL UNREVIEWED CODE - USE WITH CARE - DO NOT CREATE PUBLIC SERVICES USING THIS WITHOUT CAREFUL INDEPENDENT EXPERT REVIEW.'
|
|
97
|
+
|
|
98
|
+
/** The commands that serve, speak a protocol, or handle keys and tokens. */
|
|
99
|
+
const WARNED_COMMANDS = new Set(['serve', 'lws-serve', 'solid-serve', 'solid-client', 'lws-client',
|
|
100
|
+
'authz-serve', 'authz-keys', 'xmpp-serve', 'webdav-serve'])
|
|
101
|
+
|
|
93
102
|
const USAGE = `factoidal - the Factoidal persisted store, from Node or Deno
|
|
94
103
|
|
|
95
104
|
usage: factoidal <command> [options]
|
|
@@ -355,6 +364,7 @@ usage: factoidal authz-serve DIR --resource IRI[,IRI...] [--issuer IRI]
|
|
|
355
364
|
[--token-lifetime SECONDS] [--rotate-after SECONDS]
|
|
356
365
|
[--allow-http] [--allow-private-fetch]
|
|
357
366
|
[--cors-origin ORIGIN]... [--no-cors]
|
|
367
|
+
[--saml-idp FILE]...
|
|
358
368
|
|
|
359
369
|
An authorization server as a component (https://github.com/danbri/factoidal/issues/916),
|
|
360
370
|
for lws-serve and any resource server that reads RFC 8414 metadata:
|
|
@@ -363,8 +373,17 @@ for lws-serve and any resource server that reads RFC 8414 metadata:
|
|
|
363
373
|
POST /token RFC 8693 token exchange for an RFC 9068
|
|
364
374
|
access token whose aud is the resource
|
|
365
375
|
Subject tokens: OpenID Connect ID tokens (lws10-authn-openid) and
|
|
366
|
-
self-issued JWTs for https and did:key subjects (lws10-authn-ssi-cid)
|
|
367
|
-
2.0
|
|
376
|
+
self-issued JWTs for https and did:key subjects (lws10-authn-ssi-cid), and
|
|
377
|
+
SAML 2.0 assertions (lws10-authn-saml) from the identity providers --saml-idp
|
|
378
|
+
names (https://github.com/danbri/factoidal/issues/926).
|
|
379
|
+
|
|
380
|
+
--saml-idp FILE (repeatable) trusts one SAML 2.0 identity provider. FILE is
|
|
381
|
+
JSON: {"entityId": "<the saml:Issuer value>", "jwk": <its RSA public JWK>}.
|
|
382
|
+
An assertion must carry one enveloped XML Signature (exclusive
|
|
383
|
+
canonicalization, rsa-sha256) that verifies under that key, an audience that
|
|
384
|
+
names the issuer of this server, and bearer subject confirmation; its NameID
|
|
385
|
+
is the subject and its Recipient the client. Encrypted assertions are
|
|
386
|
+
refused. Without --saml-idp the token type is refused and not advertised.
|
|
368
387
|
|
|
369
388
|
--resource names the resource IRIs (for lws-serve: BASE.well-known/lws-storage)
|
|
370
389
|
tokens may be issued for; any other is refused with invalid_target.
|
|
@@ -520,7 +539,8 @@ is required):
|
|
|
520
539
|
BASE.well-known/lws-token, the JWK Set at
|
|
521
540
|
BASE.well-known/lws-jwks, signing keys in DIR (as
|
|
522
541
|
authz-serve; --alg, --token-lifetime, --rotate-after,
|
|
523
|
-
--allow-http
|
|
542
|
+
--allow-http, --allow-private-fetch and --saml-idp as
|
|
543
|
+
there). The
|
|
524
544
|
storage trusts it, and any --issuer too. Each request
|
|
525
545
|
is the authorization server's when the engine says so
|
|
526
546
|
(L4Factoidal/Authz/Mount.lean); every other is the
|
|
@@ -807,7 +827,7 @@ function parseArguments (args, valueOptions) {
|
|
|
807
827
|
class UsageError extends Error {}
|
|
808
828
|
|
|
809
829
|
/** Options that may be given more than once; their value is the list. */
|
|
810
|
-
const REPEATABLE_OPTIONS = new Set(['cors-origin'])
|
|
830
|
+
const REPEATABLE_OPTIONS = new Set(['cors-origin', 'saml-idp'])
|
|
811
831
|
|
|
812
832
|
const VALUE_OPTIONS = {
|
|
813
833
|
version: new Set([]),
|
|
@@ -821,7 +841,8 @@ const VALUE_OPTIONS = {
|
|
|
821
841
|
serve: new Set(['port', 'host', 'query-path', 'gsp-path', 'file', 'layout', 'tdb2']),
|
|
822
842
|
'lws-serve': new Set(['port', 'host', 'base', 'issuer', 'auth-config', 'owner', 'root-acl',
|
|
823
843
|
'audience', 'page-size', 'moves', 'controller-inbox', 'delivery-limit', 'delivery-window',
|
|
824
|
-
'redirects-max', 'storage', 'cors-origin', 'authz', 'alg', 'token-lifetime', 'rotate-after'
|
|
844
|
+
'redirects-max', 'storage', 'cors-origin', 'authz', 'alg', 'token-lifetime', 'rotate-after',
|
|
845
|
+
'saml-idp']),
|
|
825
846
|
'solid-serve': new Set(['port', 'host', 'base', 'owner', 'issuer', 'auth-config']),
|
|
826
847
|
'solid-client': new Set(['body', 'file', 'content-type', 'interpret']),
|
|
827
848
|
'lws-client': new Set(['body', 'file', 'content-type', 'slug', 'type', 'link', 'if-match',
|
|
@@ -829,7 +850,7 @@ const VALUE_OPTIONS = {
|
|
|
829
850
|
'target', 'subject-token-file', 'subject-token-type', 'access-token', 'storage', 'root',
|
|
830
851
|
'inbox']),
|
|
831
852
|
'authz-serve': new Set(['port', 'host', 'issuer', 'resource', 'alg', 'token-lifetime', 'rotate-after',
|
|
832
|
-
'cors-origin']),
|
|
853
|
+
'cors-origin', 'saml-idp']),
|
|
833
854
|
'authz-keys': new Set([]),
|
|
834
855
|
'xmpp-serve': new Set(['port', 'host', 'domain', 'accounts', 'cert', 'key']),
|
|
835
856
|
'webdav-serve': new Set(['port', 'host'])
|
|
@@ -1744,6 +1765,16 @@ function corsConfig (options) {
|
|
|
1744
1765
|
return {}
|
|
1745
1766
|
}
|
|
1746
1767
|
|
|
1768
|
+
// --saml-idp FILE (repeatable): a SAML 2.0 identity provider whose signed
|
|
1769
|
+
// assertions the authorization server accepts, as the config member
|
|
1770
|
+
// `samlIdps`. FILE is JSON {"entityId": ID, "jwk": RSA public JWK}; passed
|
|
1771
|
+
// unread, the engine checks it (Wasm/Ops/Authz.lean, issue 926).
|
|
1772
|
+
function samlConfig (options) {
|
|
1773
|
+
const files = options['saml-idp']
|
|
1774
|
+
if (!Array.isArray(files)) return {}
|
|
1775
|
+
return { samlIdps: files.map((f) => readJsonFile(f)) }
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1747
1778
|
function serveHost (options) {
|
|
1748
1779
|
if (options.host === undefined) return '127.0.0.1'
|
|
1749
1780
|
if (typeof options.host !== 'string' || options.host.length === 0) {
|
|
@@ -1877,7 +1908,8 @@ async function commandLwsServe (positional, options) {
|
|
|
1877
1908
|
...(options['rotate-after'] !== undefined ? { rotateAfter: natOption('rotate-after', options['rotate-after']) } : {}),
|
|
1878
1909
|
...(options['allow-http'] === true ? { allowHttp: true } : {}),
|
|
1879
1910
|
...(options['allow-private-fetch'] === true ? { allowPrivateFetch: true } : {}),
|
|
1880
|
-
...corsConfig(options)
|
|
1911
|
+
...corsConfig(options),
|
|
1912
|
+
...samlConfig(options)
|
|
1881
1913
|
}
|
|
1882
1914
|
let auth
|
|
1883
1915
|
if (authFile !== null) {
|
|
@@ -1964,7 +1996,8 @@ async function commandAuthzServe (positional, options) {
|
|
|
1964
1996
|
...(options['rotate-after'] !== undefined ? { rotateAfter: natOption('rotate-after', options['rotate-after']) } : {}),
|
|
1965
1997
|
...(options['allow-http'] === true ? { allowHttp: true } : {}),
|
|
1966
1998
|
...(options['allow-private-fetch'] === true ? { allowPrivateFetch: true } : {}),
|
|
1967
|
-
...corsConfig(options)
|
|
1999
|
+
...corsConfig(options),
|
|
2000
|
+
...samlConfig(options)
|
|
1968
2001
|
})
|
|
1969
2002
|
} catch (error) {
|
|
1970
2003
|
if (error !== null && typeof error === 'object' && error.envelope !== undefined) {
|
|
@@ -2287,12 +2320,14 @@ const COMMANDS = {
|
|
|
2287
2320
|
|
|
2288
2321
|
async function main () {
|
|
2289
2322
|
if (argv.length === 0) {
|
|
2323
|
+
out(REVIEW_WARNING + '\n')
|
|
2290
2324
|
out(USAGE)
|
|
2291
2325
|
return EXIT_USAGE
|
|
2292
2326
|
}
|
|
2293
2327
|
const command = argv[0]
|
|
2294
2328
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
2295
2329
|
const topic = argv[1]
|
|
2330
|
+
out(REVIEW_WARNING + '\n')
|
|
2296
2331
|
if (typeof topic === 'string' && Object.prototype.hasOwnProperty.call(COMMAND_USAGE, topic)) {
|
|
2297
2332
|
out(COMMAND_USAGE[topic])
|
|
2298
2333
|
} else {
|
|
@@ -2317,9 +2352,13 @@ async function main () {
|
|
|
2317
2352
|
throw error
|
|
2318
2353
|
}
|
|
2319
2354
|
if (parsed.options.help) {
|
|
2355
|
+
out(REVIEW_WARNING + '\n')
|
|
2320
2356
|
out(COMMAND_USAGE[command])
|
|
2321
2357
|
return EXIT_OK
|
|
2322
2358
|
}
|
|
2359
|
+
if (WARNED_COMMANDS.has(command)) {
|
|
2360
|
+
err(`factoidal ${command}: WARNING: ${REVIEW_WARNING}`)
|
|
2361
|
+
}
|
|
2323
2362
|
try {
|
|
2324
2363
|
return await COMMANDS[command](parsed.positional, parsed.options)
|
|
2325
2364
|
} catch (error) {
|
package/browser.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type {
|
|
|
12
12
|
QueryOptions, SerializeOptions, StoreReader, OpenStoreOptions,
|
|
13
13
|
DeltaLogHandle, DeltaLogInspection,
|
|
14
14
|
Sbm11Reader, Sbm11Answer, Sbm11Store, Sbm11ReadEvidence, Sbm11Transport,
|
|
15
|
+
LiteralValueData, LiteralValueResult,
|
|
15
16
|
ExplainResult, StageInfo, EvidenceRecord, Wire, FactoidalNode, Pipeline, RunResult, RunOptions,
|
|
16
17
|
} from './index';
|
|
17
18
|
|
|
@@ -23,10 +24,22 @@ export {
|
|
|
23
24
|
canonicalize,
|
|
24
25
|
graphs,
|
|
25
26
|
canonicalHash,
|
|
27
|
+
asDefaultGraph,
|
|
26
28
|
isomorphic,
|
|
27
29
|
fromQuads,
|
|
30
|
+
literalValue,
|
|
31
|
+
isValidLiteral,
|
|
32
|
+
canonicalLiteral,
|
|
33
|
+
literalFromValue,
|
|
28
34
|
ready,
|
|
29
35
|
engineTables,
|
|
36
|
+
sameValue,
|
|
37
|
+
syntaxFor,
|
|
38
|
+
splitIri,
|
|
39
|
+
isNCName,
|
|
40
|
+
vocabularies,
|
|
41
|
+
memberIri,
|
|
42
|
+
memberOrdinal,
|
|
30
43
|
openDataset,
|
|
31
44
|
closure,
|
|
32
45
|
formats,
|
package/browser.js
CHANGED
|
@@ -83,10 +83,24 @@ export const serialize = api.serialize;
|
|
|
83
83
|
export const canonicalize = api.canonicalize;
|
|
84
84
|
export const graphs = api.graphs;
|
|
85
85
|
export const canonicalHash = api.canonicalHash;
|
|
86
|
+
export const asDefaultGraph = api.asDefaultGraph;
|
|
86
87
|
export const isomorphic = api.isomorphic;
|
|
87
88
|
export const fromQuads = api.fromQuads;
|
|
89
|
+
// Literal values (https://github.com/danbri/factoidal/issues/1051): synchronous
|
|
90
|
+
// after the engine is loaded.
|
|
91
|
+
export const literalValue = api.literalValue;
|
|
92
|
+
export const isValidLiteral = api.isValidLiteral;
|
|
93
|
+
export const canonicalLiteral = api.canonicalLiteral;
|
|
94
|
+
export const literalFromValue = api.literalFromValue;
|
|
88
95
|
export const ready = api.ready;
|
|
89
96
|
export const engineTables = api.engineTables;
|
|
97
|
+
export const sameValue = api.sameValue;
|
|
98
|
+
export const syntaxFor = api.syntaxFor;
|
|
99
|
+
export const splitIri = api.splitIri;
|
|
100
|
+
export const isNCName = api.isNCName;
|
|
101
|
+
export const vocabularies = api.vocabularies;
|
|
102
|
+
export const memberIri = api.memberIri;
|
|
103
|
+
export const memberOrdinal = api.memberOrdinal;
|
|
90
104
|
export const openDataset = api.openDataset;
|
|
91
105
|
export const closure = api.closure;
|
|
92
106
|
export const formats = api.formats;
|