@factoidal/core 0.2.0 → 0.3.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.
@@ -0,0 +1,212 @@
1
+ // Node implementation of the four host primitives the Lean persisted store
2
+ // needs (Harness/PosixRangeIO.lean). See ./index.mjs for the contract and
3
+ // for the places where Node's semantics differ from the C externs.
4
+ //
5
+ // Nothing here parses, verifies or interprets a byte. It opens files,
6
+ // moves bytes, and syncs.
7
+
8
+ import {
9
+ closeSync, fstatSync, fsyncSync, openSync, readSync, readdirSync,
10
+ renameSync, statSync, unlinkSync, writeSync
11
+ } from 'node:fs'
12
+
13
+ import { StoreHostError } from './errors.mjs'
14
+ import { baseName, dirName, joinPath } from './paths.mjs'
15
+
16
+ export const runtime = 'node'
17
+
18
+ function wrap (code, message, path, cause) {
19
+ return new StoreHostError(code, `${message}: ${String(cause && cause.message ? cause.message : cause)}`, { path, cause })
20
+ }
21
+
22
+ function isInterrupt (error) {
23
+ return error && (error.code === 'EINTR' || error.code === 'EAGAIN')
24
+ }
25
+
26
+ function openRead (path) {
27
+ try {
28
+ return openSync(path, 'r')
29
+ } catch (cause) {
30
+ throw wrap('OPEN_FAILED', `cannot open ${path} for reading`, path, cause)
31
+ }
32
+ }
33
+
34
+ /** Read `length` bytes at `offset` into `out` at `outOffset`. Returns how many. */
35
+ function preadInto (fd, out, outOffset, length, offset, path) {
36
+ let done = 0
37
+ while (done < length) {
38
+ let read
39
+ try {
40
+ read = readSync(fd, out, outOffset + done, length - done, offset + done)
41
+ } catch (cause) {
42
+ if (isInterrupt(cause)) continue
43
+ throw wrap('READ_FAILED', `read failed on ${path}`, path, cause)
44
+ }
45
+ if (read === 0) break
46
+ done += read
47
+ }
48
+ return done
49
+ }
50
+
51
+ export function readWhole (path) {
52
+ const fd = openRead(path)
53
+ try {
54
+ const size = fstatSync(fd).size
55
+ if (!Number.isSafeInteger(size)) {
56
+ throw new StoreHostError('FILE_TOO_LARGE', `${path} is larger than 2^53 - 1 bytes`, { path })
57
+ }
58
+ const out = new Uint8Array(size)
59
+ const done = preadInto(fd, out, 0, size, 0, path)
60
+ if (done !== size) {
61
+ throw new StoreHostError('SHORT_READ', `${path} shrank during the read (${done} of ${size} bytes)`, { path })
62
+ }
63
+ return out
64
+ } finally {
65
+ closeSync(fd)
66
+ }
67
+ }
68
+
69
+ export function readRange (path, offset, length) {
70
+ if (length === 0) return new Uint8Array(0)
71
+ const fd = openRead(path)
72
+ try {
73
+ const out = new Uint8Array(length)
74
+ const done = preadInto(fd, out, 0, length, offset, path)
75
+ if (done !== length) {
76
+ throw new StoreHostError(
77
+ 'SHORT_READ',
78
+ `${path} returned ${done} of ${length} bytes at offset ${offset}`,
79
+ { path }
80
+ )
81
+ }
82
+ return out
83
+ } finally {
84
+ closeSync(fd)
85
+ }
86
+ }
87
+
88
+ export function appendSyncAtSize (path, bytes, expectedSize) {
89
+ let fd
90
+ try {
91
+ // 'a' is O_WRONLY | O_CREAT | O_APPEND, matching the C extern's open.
92
+ fd = openSync(path, 'a')
93
+ } catch (cause) {
94
+ throw wrap('OPEN_FAILED', `cannot open ${path} for append`, path, cause)
95
+ }
96
+ try {
97
+ const size = fstatSync(fd).size
98
+ if (size !== expectedSize) return false
99
+ let done = 0
100
+ while (done < bytes.length) {
101
+ let written
102
+ try {
103
+ // position null keeps the O_APPEND placement the C extern relies on.
104
+ written = writeSync(fd, bytes, done, bytes.length - done, null)
105
+ } catch (cause) {
106
+ if (isInterrupt(cause)) continue
107
+ throw wrap('WRITE_FAILED', `append failed on ${path}`, path, cause)
108
+ }
109
+ done += written
110
+ }
111
+ try {
112
+ fsyncSync(fd)
113
+ } catch (cause) {
114
+ throw wrap('FSYNC_FAILED', `fsync failed on ${path}`, path, cause)
115
+ }
116
+ return true
117
+ } finally {
118
+ closeSync(fd)
119
+ }
120
+ }
121
+
122
+ function temporaryName (path) {
123
+ const suffix = Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')
124
+ return joinPath(dirName(path), baseName(path) + '.tmp.' + suffix)
125
+ }
126
+
127
+ function fsyncDirectory (directory) {
128
+ let fd
129
+ try {
130
+ fd = openSync(directory, 'r')
131
+ } catch (cause) {
132
+ throw wrap('DIR_OPEN_FAILED', `cannot open ${directory} to sync it`, directory, cause)
133
+ }
134
+ try {
135
+ fsyncSync(fd)
136
+ } finally {
137
+ closeSync(fd)
138
+ }
139
+ }
140
+
141
+ export function atomicReplace (path, bytes) {
142
+ const directory = dirName(path)
143
+ let temporary = null
144
+ let fd = null
145
+ try {
146
+ for (let attempt = 0; attempt < 8 && fd === null; attempt += 1) {
147
+ temporary = temporaryName(path)
148
+ try {
149
+ // 'wx' is O_WRONLY | O_CREAT | O_EXCL, the exclusive create that
150
+ // makes the name ours the way mkstemp does in the C extern.
151
+ fd = openSync(temporary, 'wx')
152
+ } catch (cause) {
153
+ if (cause && cause.code === 'EEXIST') continue
154
+ throw wrap('OPEN_FAILED', `cannot create ${temporary}`, temporary, cause)
155
+ }
156
+ }
157
+ if (fd === null) {
158
+ throw new StoreHostError('TEMP_NAME_EXHAUSTED', `no free temporary name beside ${path}`, { path })
159
+ }
160
+ let done = 0
161
+ while (done < bytes.length) {
162
+ let written
163
+ try {
164
+ written = writeSync(fd, bytes, done, bytes.length - done, null)
165
+ } catch (cause) {
166
+ if (isInterrupt(cause)) continue
167
+ throw wrap('WRITE_FAILED', `write failed on ${temporary}`, temporary, cause)
168
+ }
169
+ done += written
170
+ }
171
+ fsyncSync(fd)
172
+ closeSync(fd)
173
+ fd = null
174
+ renameSync(temporary, path)
175
+ temporary = null
176
+ try {
177
+ fsyncDirectory(directory)
178
+ } catch (_error) {
179
+ // The C extern also returns false here, with the replacement already
180
+ // done. Reported the same way; see ./index.mjs for what false means.
181
+ return false
182
+ }
183
+ return true
184
+ } catch (error) {
185
+ if (fd !== null) closeSync(fd)
186
+ if (temporary !== null) {
187
+ try { unlinkSync(temporary) } catch (_ignored) { /* the temporary may not exist */ }
188
+ }
189
+ throw error
190
+ }
191
+ }
192
+
193
+ export function listGeneration (directory) {
194
+ let names
195
+ try {
196
+ names = readdirSync(directory)
197
+ } catch (cause) {
198
+ throw wrap('DIR_READ_FAILED', `cannot list ${directory}`, directory, cause)
199
+ }
200
+ const out = []
201
+ for (const name of names.sort()) {
202
+ let info
203
+ try {
204
+ info = statSync(joinPath(directory, name))
205
+ } catch (_cause) {
206
+ continue // a name that vanished between readdir and stat
207
+ }
208
+ if (!info.isFile()) continue
209
+ out.push({ name, size: info.size })
210
+ }
211
+ return out
212
+ }
@@ -0,0 +1,77 @@
1
+ // Path plumbing for the store host. No dependency on `node:path`, so the
2
+ // same file loads under Deno without the Node compatibility layer.
3
+ //
4
+ // These functions join and split path strings. They make no decision about
5
+ // what a Shardborough generation contains.
6
+
7
+ import { StoreHostError } from './errors.mjs'
8
+
9
+ const SEPARATORS = ['/', '\\']
10
+
11
+ function isSeparator (character) {
12
+ return SEPARATORS.indexOf(character) >= 0
13
+ }
14
+
15
+ /** Join with '/'. Every runtime this module targets accepts '/'. */
16
+ export function joinPath (base, child) {
17
+ if (base.length === 0) return child
18
+ const last = base[base.length - 1]
19
+ return isSeparator(last) ? base + child : base + '/' + child
20
+ }
21
+
22
+ /** The directory part of a path, or '.' when the path has no separator. */
23
+ export function dirName (path) {
24
+ let end = path.length
25
+ while (end > 1 && isSeparator(path[end - 1])) end -= 1
26
+ let index = end - 1
27
+ while (index >= 0 && !isSeparator(path[index])) index -= 1
28
+ if (index < 0) return '.'
29
+ if (index === 0) return path[0]
30
+ return path.slice(0, index)
31
+ }
32
+
33
+ /** The final component of a path. */
34
+ export function baseName (path) {
35
+ let end = path.length
36
+ while (end > 1 && isSeparator(path[end - 1])) end -= 1
37
+ let index = end - 1
38
+ while (index >= 0 && !isSeparator(path[index])) index -= 1
39
+ return path.slice(index + 1, end)
40
+ }
41
+
42
+ /**
43
+ * A single child name that may be appended to a directory path.
44
+ *
45
+ * `CURRENT` holds a generation name written by the Lean activation path;
46
+ * this module still refuses a value that would leave the collection root
47
+ * when joined. That is a filesystem-safety guard on a string the host is
48
+ * about to turn into a path, not a check of the pointer's format.
49
+ */
50
+ export function requireChildName (name, label) {
51
+ if (typeof name !== 'string' || name.length === 0) {
52
+ throw new StoreHostError('BAD_CHILD_NAME', `${label} is empty`)
53
+ }
54
+ if (name === '.' || name === '..') {
55
+ throw new StoreHostError('BAD_CHILD_NAME', `${label} is "${name}"`)
56
+ }
57
+ for (const character of name) {
58
+ if (isSeparator(character) || character === '\u0000') {
59
+ throw new StoreHostError(
60
+ 'BAD_CHILD_NAME',
61
+ `${label} contains a path separator or NUL byte`
62
+ )
63
+ }
64
+ }
65
+ return name
66
+ }
67
+
68
+ /** Convert a `file:` URL string to a filesystem path. */
69
+ export function fileUrlToPath (url) {
70
+ const text = typeof url === 'string' ? url : String(url)
71
+ if (!text.startsWith('file://')) return text
72
+ let path = decodeURIComponent(text.slice('file://'.length))
73
+ const host = path.indexOf('/')
74
+ if (host > 0) path = path.slice(host)
75
+ if (/^\/[A-Za-z]:/.test(path)) path = path.slice(1)
76
+ return path
77
+ }
package/version.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
- "version": "0.2.0",
2
+ "version": "0.3.0",
3
3
  "gitSha": "49f8ca4d70bf57c12fea445611e2b01b067bf6ba",
4
4
  "builtAt": "2026-08-26T21:45:40Z",
5
5
  "claims": {
6
6
  "schema": "1",
7
- "statement": "Proved sound with respect to an independent F* formalization of the W3C RDF/RDFS/OWL semantics, under the stated fragment restrictions and the trust surface recorded in docs/theorem-registry.md never claimed as a complete formally verified implementation of RDF semantics (docs/theorem-registry.md § Calibrated claims).",
7
+ "statement": "Proved sound with respect to an independent F* formalization of the W3C RDF/RDFS/OWL semantics, under the stated fragment restrictions and the trust surface recorded in docs/theorem-registry.md \u2014 never claimed as a complete formally verified implementation of RDF semantics (docs/theorem-registry.md \u00a7 Calibrated claims).",
8
8
  "registry": "docs/theorem-registry.md",
9
- "trustSurface": "docs/theorem-registry.md § Trust surface",
10
- "notes": "Each item below names the exact theorem/lemma, the F* file that carries it, and the registry section a reader can open to check the claim. This is a summary, not the full registry read docs/theorem-registry.md for the complete rule-by-rule table, hypothesis provenance, and open findings.",
9
+ "trustSurface": "docs/theorem-registry.md \u00a7 Trust surface",
10
+ "notes": "Each item below names the exact theorem/lemma, the F* file that carries it, and the registry section a reader can open to check the claim. This is a summary, not the full registry \u2014 read docs/theorem-registry.md for the complete rule-by-rule table, hypothesis provenance, and open findings.",
11
11
  "items": [
12
12
  {
13
13
  "id": "rho-df-closure-decides",
14
- "claim": "The certified six-rule core-RDFS (ρdf) closure (rdfs2/3/5/7/9/11) decides entailment exactly on fragment inputs its simple-query answers are exactly the entailed consequences.",
14
+ "claim": "The certified six-rule core-RDFS (\u03c1df) closure (rdfs2/3/5/7/9/11) decides entailment exactly on fragment inputs \u2014 its simple-query answers are exactly the entailed consequences.",
15
15
  "theorem": "rho_df_closure_sound, rho_df_closure_decides",
16
16
  "file": "formal/fstar/RDF.Entailment.RDFS.RhoDFClosure.fst",
17
- "registrySection": "docs/theorem-registry.md § Layer 3 the composed regime theorem, rho_df_closure soundness/fragment-preservation rows",
17
+ "registrySection": "docs/theorem-registry.md \u00a7 Layer 3 \u2014 the composed regime theorem, rho_df_closure soundness/fragment-preservation rows",
18
18
  "npmSurface": [
19
19
  "coreRdfsClosure",
20
20
  "rhoDfClosure",
@@ -27,7 +27,7 @@
27
27
  "id": "owl-rl-licensing-truth",
28
28
  "claim": "Every OWL 2 RL/RDF engine rule classified [row] emits only input triples or a licensed W3C table-row application (licensing), and every row's conclusion is model-theoretically true given its premises (truth).",
29
29
  "file": "formal/fstar/OWL.RL.Spec.fst, OWL.RL.Refinement.fst, OWL.Semantics.fst",
30
- "registrySection": "docs/theorem-registry.md § 1 (OWL 2 RL/RDF licensing and truth, per engine rule)",
30
+ "registrySection": "docs/theorem-registry.md \u00a7 1 (OWL 2 RL/RDF \u2014 licensing and truth, per engine rule)",
31
31
  "npmSurface": [
32
32
  "owlClosure",
33
33
  "rdfsPlusClosure",
@@ -36,21 +36,21 @@
36
36
  "owlIsConsistent",
37
37
  "owlEntails"
38
38
  ],
39
- "status": "proved per-rule; see §1 table for per-row status and PARKED clash-row exceptions"
39
+ "status": "proved per-rule; see \u00a71 table for per-row status and PARKED clash-row exceptions"
40
40
  },
41
41
  {
42
42
  "id": "rdfs-entailment-13-rows",
43
- "claim": "11 of 13 RDF 1.1 Semantics §9 RDFS entailment rows have a shipping engine rule with both licensing and truth proved; the remaining 2 (rdfs6, rdfs10) are proved at the spec-predicate level but the shipping engine's reflexivity over-approximation for them is correctly flagged unsound (finding RS-1), not silently shipped as sound.",
43
+ "claim": "11 of 13 RDF 1.1 Semantics \u00a79 RDFS entailment rows have a shipping engine rule with both licensing and truth proved; the remaining 2 (rdfs6, rdfs10) are proved at the spec-predicate level but the shipping engine's reflexivity over-approximation for them is correctly flagged unsound (finding RS-1), not silently shipped as sound.",
44
44
  "file": "formal/fstar/RDF.Entailment.RDFS.Spec.fst, RDF.Entailment.RDFS.Refinement.fst, RDF.Entailment.RDFS.ModelTheory.fst",
45
- "registrySection": "docs/theorem-registry.md § 2 (RDFS entailment 13 rows)",
45
+ "registrySection": "docs/theorem-registry.md \u00a7 2 (RDFS entailment \u2014 13 rows)",
46
46
  "status": "11/13 proved sound+true; 2/13 truth-only, engine unsoundness flagged not hidden"
47
47
  },
48
48
  {
49
49
  "id": "sparql-rdfs-regime-exact-answer",
50
- "claim": "The SPARQL evaluator's answer set over the ρdf closure IS the RDFS entailment regime's answer set, on fragment inputs, for ground answers (soundness + completeness, unconditional).",
50
+ "claim": "The SPARQL evaluator's answer set over the \u03c1df closure IS the RDFS entailment regime's answer set, on fragment inputs, for ground answers (soundness + completeness, unconditional).",
51
51
  "theorem": "theorem_rdfs_regime_bgp_exact_answer, theorem_rdfs_regime_ask_query_complete",
52
52
  "file": "formal/fstar/SPARQL11.EntailmentRegime.RDFS.fst",
53
- "registrySection": "docs/theorem-registry.md § 5 (SPARQL algebra refinement, the query rung) § Layer 3",
53
+ "registrySection": "docs/theorem-registry.md \u00a7 5 (SPARQL algebra refinement, the query rung) \u00a7 Layer 3",
54
54
  "npmSurface": [
55
55
  "query (options.entail = 'RDFS')"
56
56
  ],
@@ -58,18 +58,18 @@
58
58
  },
59
59
  {
60
60
  "id": "streaming-multichunk",
61
- "claim": "N-Quads streaming parse over ANY list of input chunks equals a batch parse of their concatenation (stream_parse chunks == batch_parse (concat_all chunks)) the machine-checked answer to whether chunked/streamed parsing of arbitrarily large files agrees with parsing the whole file at once.",
61
+ "claim": "N-Quads streaming parse over ANY list of input chunks equals a batch parse of their concatenation (stream_parse chunks == batch_parse (concat_all chunks)) \u2014 the machine-checked answer to whether chunked/streamed parsing of arbitrarily large files agrees with parsing the whole file at once.",
62
62
  "theorem": "theorem_stream_eq_batch",
63
63
  "file": "formal/fstar/RDF.NQuads.Streaming.fst",
64
- "registrySection": "docs/theorem-registry.md § 7 (G4/M1 parser round-trip theorems) Task #48/#402 MULTI-CHUNK STREAMING THEOREM, 2026-08-11",
64
+ "registrySection": "docs/theorem-registry.md \u00a7 7 (G4/M1 parser round-trip theorems) \u2014 Task #48/#402 MULTI-CHUNK STREAMING THEOREM, 2026-08-11",
65
65
  "status": "proved"
66
66
  },
67
67
  {
68
68
  "id": "srj-n-row",
69
- "claim": "SPARQL Results JSON serialisation (serialise_response_json) equals the fixed SRJ shape (head/vars block, comma-joined row texts) for ANY variable list and ANY row list of ANY length N not just the 0/1/2-row cases spot-checked before.",
69
+ "claim": "SPARQL Results JSON serialisation (serialise_response_json) equals the fixed SRJ shape (head/vars block, comma-joined row texts) for ANY variable list and ANY row list of ANY length N \u2014 not just the 0/1/2-row cases spot-checked before.",
70
70
  "theorem": "lemma_srj_n_rows",
71
71
  "file": "formal/fstar/SPARQL.Protocol.RoundTrip.fst",
72
- "registrySection": "docs/theorem-registry.md § 7 (G4/M1 parser round-trip theorems) G4 M4 N-ROW symbolic SRJ theorem, 2026-08-11",
72
+ "registrySection": "docs/theorem-registry.md \u00a7 7 (G4/M1 parser round-trip theorems) \u2014 G4 M4 N-ROW symbolic SRJ theorem, 2026-08-11",
73
73
  "npmSurface": [
74
74
  "query (SELECT, output SPARQL Results JSON)"
75
75
  ],
@@ -77,10 +77,10 @@
77
77
  },
78
78
  {
79
79
  "id": "symbolic-iri-roundtrip",
80
- "claim": "Parsing the printed N-Triples form of a SYMBOLIC IRI term (any ASCII codepoint list of IRI-safe characters, no escapes, no controls) recovers the original term exactly proved against the real serializer, not a transcription.",
80
+ "claim": "Parsing the printed N-Triples form of a SYMBOLIC IRI term (any ASCII codepoint list of IRI-safe characters, no escapes, no controls) recovers the original term exactly \u2014 proved against the real serializer, not a transcription.",
81
81
  "theorem": "lemma_term_iri_round_trip_build_string",
82
82
  "file": "formal/fstar/RDF.NTriples.RoundTrip.fst",
83
- "registrySection": "docs/theorem-registry.md § 7 (G4/M1 parser round-trip theorems) G4 M1 SYMBOLIC IRI round-trip theorem, 2026-08-11",
83
+ "registrySection": "docs/theorem-registry.md \u00a7 7 (G4/M1 parser round-trip theorems) \u2014 G4 M1 SYMBOLIC IRI round-trip theorem, 2026-08-11",
84
84
  "npmSurface": [
85
85
  "parse",
86
86
  "serialize"
@@ -92,10 +92,11 @@
92
92
  "claim": "The FastString byte-primitive migration (steps 0-6) is complete: zero assume vals in the FastString family except the one documented CharBoundary primitive (unsafe_char_of_d7ff); the equivalence corpus is IDENTICAL between native OCaml and Node (js_of_ocaml) execution.",
93
93
  "measurement": "93846 pass, 962 expected-fail, 0 unexpected fail (out of 94808), verified 3 ways (fresh Node run, fresh same-source native compile, prior recorded number)",
94
94
  "file": "formal/fstar/Parser.FastString.fst, Parser.FastString.Spec.fst",
95
- "registrySection": "docs/theorem-registry.md § 7 (G4/M1 parser round-trip theorems) FastString migration COMPLETE, steps 0-6, 2026-08-11",
95
+ "registrySection": "docs/theorem-registry.md \u00a7 7 (G4/M1 parser round-trip theorems) \u2014 FastString migration COMPLETE, steps 0-6, 2026-08-11",
96
96
  "status": "complete, measured equivalent native+Node"
97
97
  }
98
98
  ],
99
- "notClaimed": "A complete formally verified implementation of RDF semantics. See docs/theorem-registry.md § Trust surface for the assume-val count (~146, mostly COTTAS/HDT storage I/O per iron rule #11), the extraction-step caveat (fstar.exe --codegen OCaml is not itself re-verified), and the rdf-mt / W3C conformance suites this registry relies on as the independent runtime check. CLAUDE.md's standing qualifier applies: parser and algebra spec verified in F*; the on-disk backend carries unverified OCaml-side optimization layers being migrated back to F*."
100
- }
99
+ "notClaimed": "A complete formally verified implementation of RDF semantics. See docs/theorem-registry.md \u00a7 Trust surface for the assume-val count (~146, mostly COTTAS/HDT storage I/O per iron rule #11), the extraction-step caveat (fstar.exe --codegen OCaml is not itself re-verified), and the rdf-mt / W3C conformance suites this registry relies on as the independent runtime check. CLAUDE.md's standing qualifier applies: parser and algebra spec verified in F*; the on-disk backend carries unverified OCaml-side optimization layers being migrated back to F*."
100
+ },
101
+ "note": "The F* engine bundle in this package was built at gitSha 49f8ca4d7 (2026-08-26T21:45:40Z) and is unchanged since; the version member mirrors the package version. The Lean engine's own provenance is in l4-assets/version.json."
101
102
  }