@dcasegr/skein 0.0.0-stage → 0.1.2

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.
Files changed (176) hide show
  1. package/README.md +525 -2
  2. package/bin/skein-host.js +22 -0
  3. package/bin/skein.js +8 -0
  4. package/dist/client/admin-cli.d.ts +10 -0
  5. package/dist/client/admin-cli.js +370 -0
  6. package/dist/client/admin.d.ts +139 -0
  7. package/dist/client/admin.js +256 -0
  8. package/dist/client/bundle.d.ts +9 -0
  9. package/dist/client/bundle.js +34 -0
  10. package/dist/client/cli.d.ts +44 -0
  11. package/dist/client/cli.js +306 -0
  12. package/dist/client/client.d.ts +161 -0
  13. package/dist/client/client.js +237 -0
  14. package/dist/client/config.d.ts +18 -0
  15. package/dist/client/config.js +31 -0
  16. package/dist/client/conversation.d.ts +62 -0
  17. package/dist/client/conversation.js +113 -0
  18. package/dist/client/raw.d.ts +94 -0
  19. package/dist/client/raw.js +154 -0
  20. package/dist/client/target.d.ts +34 -0
  21. package/dist/client/target.js +158 -0
  22. package/dist/dev/cli.d.ts +8 -0
  23. package/dist/dev/cli.js +248 -0
  24. package/dist/dev/scan.d.ts +15 -0
  25. package/dist/dev/scan.js +173 -0
  26. package/dist/envelope-cbor.d.ts +110 -0
  27. package/dist/envelope-cbor.js +160 -0
  28. package/dist/envelope.d.ts +38 -0
  29. package/dist/envelope.js +101 -0
  30. package/dist/host/arc.d.ts +131 -0
  31. package/dist/host/arc.js +425 -0
  32. package/dist/host/billing.d.ts +102 -0
  33. package/dist/host/billing.js +167 -0
  34. package/dist/host/boot.d.ts +246 -0
  35. package/dist/host/boot.js +643 -0
  36. package/dist/host/cli.d.ts +57 -0
  37. package/dist/host/cli.js +1253 -0
  38. package/dist/host/clock.d.ts +3 -0
  39. package/dist/host/clock.js +8 -0
  40. package/dist/host/control.d.ts +45 -0
  41. package/dist/host/control.js +170 -0
  42. package/dist/host/cron.d.ts +99 -0
  43. package/dist/host/cron.js +196 -0
  44. package/dist/host/deliver.d.ts +53 -0
  45. package/dist/host/deliver.js +251 -0
  46. package/dist/host/deploy.d.ts +29 -0
  47. package/dist/host/deploy.js +55 -0
  48. package/dist/host/door-answer.d.ts +24 -0
  49. package/dist/host/door-answer.js +21 -0
  50. package/dist/host/events.d.ts +89 -0
  51. package/dist/host/events.js +222 -0
  52. package/dist/host/fake-arcade.d.ts +40 -0
  53. package/dist/host/fake-arcade.js +149 -0
  54. package/dist/host/fake-discovery.d.ts +4 -0
  55. package/dist/host/fake-discovery.js +58 -0
  56. package/dist/host/feeds.d.ts +127 -0
  57. package/dist/host/feeds.js +422 -0
  58. package/dist/host/frontdoor.d.ts +92 -0
  59. package/dist/host/frontdoor.js +172 -0
  60. package/dist/host/genesis.d.ts +259 -0
  61. package/dist/host/genesis.js +353 -0
  62. package/dist/host/handles.d.ts +40 -0
  63. package/dist/host/handles.js +88 -0
  64. package/dist/host/hostenv.d.ts +30 -0
  65. package/dist/host/hostenv.js +83 -0
  66. package/dist/host/image-chain.d.ts +127 -0
  67. package/dist/host/image-chain.js +440 -0
  68. package/dist/host/install.d.ts +22 -0
  69. package/dist/host/install.js +139 -0
  70. package/dist/host/instances.d.ts +227 -0
  71. package/dist/host/instances.js +430 -0
  72. package/dist/host/kernel.d.ts +246 -0
  73. package/dist/host/kernel.js +603 -0
  74. package/dist/host/liveness.d.ts +65 -0
  75. package/dist/host/liveness.js +155 -0
  76. package/dist/host/manifest.d.ts +160 -0
  77. package/dist/host/manifest.js +597 -0
  78. package/dist/host/p2p.d.ts +337 -0
  79. package/dist/host/p2p.js +1002 -0
  80. package/dist/host/packet.d.ts +121 -0
  81. package/dist/host/packet.js +562 -0
  82. package/dist/host/plan.d.ts +154 -0
  83. package/dist/host/plan.js +418 -0
  84. package/dist/host/providers.d.ts +238 -0
  85. package/dist/host/providers.js +565 -0
  86. package/dist/host/roster.d.ts +72 -0
  87. package/dist/host/roster.js +172 -0
  88. package/dist/host/router.d.ts +782 -0
  89. package/dist/host/router.js +2411 -0
  90. package/dist/host/signer.d.ts +49 -0
  91. package/dist/host/signer.js +167 -0
  92. package/dist/host/store-sqlite.d.ts +39 -0
  93. package/dist/host/store-sqlite.js +170 -0
  94. package/dist/host/stores.d.ts +43 -0
  95. package/dist/host/stores.js +342 -0
  96. package/dist/host/sync.d.ts +121 -0
  97. package/dist/host/sync.js +467 -0
  98. package/dist/host/testbeef.d.ts +21 -0
  99. package/dist/host/testbeef.js +37 -0
  100. package/dist/host/testhost.d.ts +149 -0
  101. package/dist/host/testhost.js +211 -0
  102. package/dist/host/vcdiff.d.ts +9 -0
  103. package/dist/host/vcdiff.js +210 -0
  104. package/dist/host/wasm-kernel.d.ts +87 -0
  105. package/dist/host/wasm-kernel.js +296 -0
  106. package/dist/host/wasm-worker.d.ts +1 -0
  107. package/dist/host/wasm-worker.js +186 -0
  108. package/dist/peers/cron.d.ts +33 -0
  109. package/dist/peers/cron.js +75 -0
  110. package/dist/peers/infer-main.d.ts +2 -0
  111. package/dist/peers/infer-main.js +74 -0
  112. package/dist/peers/infer.d.ts +163 -0
  113. package/dist/peers/infer.js +367 -0
  114. package/dist/runtime/beef.d.ts +28 -0
  115. package/dist/runtime/beef.js +107 -0
  116. package/dist/runtime/bitcoin.d.ts +36 -0
  117. package/dist/runtime/bitcoin.js +141 -0
  118. package/dist/runtime/cid.d.ts +14 -0
  119. package/dist/runtime/cid.js +51 -0
  120. package/dist/runtime/dispatch.d.ts +92 -0
  121. package/dist/runtime/dispatch.js +142 -0
  122. package/dist/runtime/envelope.d.ts +60 -0
  123. package/dist/runtime/envelope.js +101 -0
  124. package/dist/runtime/heads.d.ts +25 -0
  125. package/dist/runtime/heads.js +37 -0
  126. package/dist/runtime/identity.d.ts +28 -0
  127. package/dist/runtime/identity.js +67 -0
  128. package/dist/runtime/index-store.d.ts +152 -0
  129. package/dist/runtime/index-store.js +715 -0
  130. package/dist/runtime/log.d.ts +62 -0
  131. package/dist/runtime/log.js +104 -0
  132. package/dist/runtime/programs.d.ts +30 -0
  133. package/dist/runtime/programs.js +61 -0
  134. package/dist/runtime/records.d.ts +135 -0
  135. package/dist/runtime/records.js +149 -0
  136. package/dist/runtime/sqlite.d.ts +33 -0
  137. package/dist/runtime/sqlite.js +604 -0
  138. package/dist/runtime/store.d.ts +141 -0
  139. package/dist/runtime/store.js +24 -0
  140. package/dist/runtime/syscalls.d.ts +9 -0
  141. package/dist/runtime/syscalls.js +41 -0
  142. package/dist/runtime/tree.d.ts +60 -0
  143. package/dist/runtime/tree.js +196 -0
  144. package/dist/runtime/types.d.ts +100 -0
  145. package/dist/runtime/types.js +7 -0
  146. package/dist/testapps.d.ts +101 -0
  147. package/dist/testapps.js +275 -0
  148. package/dist/testkit.d.ts +25 -0
  149. package/dist/testkit.js +48 -0
  150. package/dist/wallet.d.ts +35 -0
  151. package/dist/wallet.js +87 -0
  152. package/images/README.md +68 -0
  153. package/images/default/README.md +38 -0
  154. package/images/default/bin/frontdoor.cid +1 -0
  155. package/images/default/bin/frontdoor.json +5 -0
  156. package/images/default/bin/messagebox.cid +1 -0
  157. package/images/default/bin/messagebox.json +8 -0
  158. package/images/default/bin/wallet.cid +1 -0
  159. package/images/default/bin/wallet.json +14 -0
  160. package/images/default/etc/config.json +1 -0
  161. package/images/default/etc/dispatch.json +82 -0
  162. package/images/host/etc/apps.json +8 -0
  163. package/kernel-zig/README.md +1247 -0
  164. package/kernel-zig/web/client.js +65 -0
  165. package/kernel-zig/web/kernel.d.ts +59 -0
  166. package/kernel-zig/web/kernel.js +329 -0
  167. package/kernel-zig/web/store.js +67 -0
  168. package/kernel-zig/web/worker.js +79 -0
  169. package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
  170. package/package.json +77 -4
  171. package/wasm/README.md +35 -0
  172. package/wasm/frontdoor.wasm +0 -0
  173. package/wasm/messagebox.wasm +0 -0
  174. package/wasm/resolve.wasm +0 -0
  175. package/wasm/wallet.wasm +0 -0
  176. package/wasm/wire-probe.wasm +0 -0
@@ -0,0 +1,715 @@
1
+ // The store format of issue #30, read from TypeScript: a SQLite file holding
2
+ // only `blocks` and a `pointers` row `state` → the state record
3
+ // {kind: "skein-state", log, cursor, heads, index: {<map>: <root>}}, whose
4
+ // maps are Merkle search trees of dag-cbor nodes [left, [[key, value, right]…]]
5
+ // (the SDK's mst.zig, shruggr/skein-sdk src/mst.zig; kernel-zig/src/index.zig; kernel-zig/README.md "The index").
6
+ //
7
+ // - `openStoreFile(path)`: the right reader for a store file — this one when
8
+ // the file has the state pointer, else sqlite.ts's (a store of blocks the
9
+ // tools write, or a file from before #30). The explorer, skein-dev and
10
+ // skein-host open files through it. A store may instead be a set of the
11
+ // host's LMDB environment (#96, #133): `lmdb:<env dir>#<set>` (`storeSpec`
12
+ // makes it from host.db's `store` column, `lmdb:<env dir>`, and the row's
13
+ // handle) — read directly, read only, by src/host/stores.ts (lmdb-js: the
14
+ // disk is the host's), over `indexStoreOver` here, with the kernel's member
15
+ // gate (kernel-zig/src/lmdb_store.zig).
16
+ // - `indexStore(db)`: the `Store` interface over the maps, read only (blocks
17
+ // may be added: they are a key→bytes map). The pointer is read on every
18
+ // call, so a reader beside a live kernel follows it.
19
+ // - `derive(store)` + `buildTree`: the same maps built here from any Store's
20
+ // chains and log, canonically — each the root the kernel keeps for the same
21
+ // log (index-store.test.ts and equiv/overlay.ts check it against stores the
22
+ // kernel wrote).
23
+ //
24
+ // Keys: numbers 8-byte big-endian with the sign bit flipped; binary CIDs;
25
+ // strings uvarint-length-prefixed. The derivation is sqlite.ts's.
26
+ import { createHash } from "node:crypto";
27
+ import { DatabaseSync } from "node:sqlite";
28
+ import { CID, decode, encode, fmt, fromBytes, isCID } from "./cid.js";
29
+ import { keptEdges, openStore, originEdges, toRef, updateEdges } from "./sqlite.js";
30
+ import { BITCOIN_BLOCK, bitcoinEdges, bitcoinLinks, decodeBitcoin, displayHash, isBitcoin } from "./bitcoin.js";
31
+ import { NotFound } from "./store.js";
32
+ export const MAPS = ["log", "unique", "chains", "updates", "threads", "resting", "sleepers", "awaits", "edges", "heads"];
33
+ export const STATE_KIND = "skein-state";
34
+ // ---------------------------------------------------------------- format 2, for display
35
+ const KEY_FIELDS = new Set(["identity", "owner", "sender", "to", "identityKey"]);
36
+ const HEX_FIELDS = new Set(["contentHash", "signature"]);
37
+ const hexOf = (b) => Buffer.from(b).toString("hex");
38
+ /**
39
+ * A record as the TypeScript readers (the explorer, skein-dev, skein-host)
40
+ * show it. Format 2 (issue #33, kernel-zig/src/log.zig) keeps identity keys,
41
+ * hashes and signatures as byte strings and a genesis's `names` as a list;
42
+ * here they read back as the hex (and the `{key: name}` map) the display code
43
+ * knows, and a §7.3 envelope's `content` as base64. Display only: the
44
+ * record's CID is its stored bytes'.
45
+ */
46
+ export function display(v) {
47
+ if (!v || typeof v !== "object" || v instanceof Uint8Array || isCID(v))
48
+ return v;
49
+ if (Array.isArray(v))
50
+ return v.map(display);
51
+ const o = v;
52
+ const out = {};
53
+ for (const [k, x] of Object.entries(o)) {
54
+ if (x instanceof Uint8Array && KEY_FIELDS.has(k) && x.length === 33)
55
+ out[k] = hexOf(x);
56
+ else if (x instanceof Uint8Array && HEX_FIELDS.has(k))
57
+ out[k] = hexOf(x);
58
+ else if (x instanceof Uint8Array && k === "content" && o.metanetHandles === "1.0")
59
+ out[k] = Buffer.from(x).toString("base64");
60
+ else if (k === "peers" && x && typeof x === "object" && !Array.isArray(x))
61
+ out[k] = Object.fromEntries(Object.entries(x).map(([r, p]) => [r, p instanceof Uint8Array ? hexOf(p) : p]));
62
+ else if (k === "names" && Array.isArray(x) && o.kind === "genesis")
63
+ out[k] = Object.fromEntries(x.map((n) => { const e = n; return [e.identityKey instanceof Uint8Array ? hexOf(e.identityKey) : String(e.identityKey), { handle: e.handle, domain: e.domain }]; }));
64
+ else
65
+ out[k] = display(x);
66
+ }
67
+ return out;
68
+ }
69
+ // ---------------------------------------------------------------- bitcoin blocks, for display
70
+ /**
71
+ * The Zig kernel's bitcoin codecs decoded the IPLD way (#42, bitcoin.ts,
72
+ * kernel-zig/src/bitcoin.zig): a header (bitcoin-block) links the previous
73
+ * header and its merkle root; a 64-byte bitcoin-tx is a merkle node linking
74
+ * its two children (nodes, or at the bottom transactions); a transaction's
75
+ * inputs link what they spend. The tree and the ancestry are sparse, so a
76
+ * link's target may not be held. The fields are the decoded node's.
77
+ */
78
+ export { BITCOIN_BLOCK, BITCOIN_TX, DBL_SHA2_256 } from "./bitcoin.js";
79
+ export function bitcoinView(cid, bytes) {
80
+ const v = decodeBitcoin(cid, bytes);
81
+ if (v === undefined)
82
+ return undefined;
83
+ const id = displayHash(cid.multihash.digest);
84
+ if (Array.isArray(v)) {
85
+ const [l, r] = v;
86
+ return { codec: "bitcoin-tx (merkle node)", fields: [["hash", id], ["left", l], ["right", l.equals(r) ? "duplicate of left" : r], ["left (as a txid)", displayHash(l.multihash.digest)], ["right (as a txid)", displayHash(r.multihash.digest)]] };
87
+ }
88
+ if (cid.code === BITCOIN_BLOCK) {
89
+ return { codec: "bitcoin-block", fields: [["block hash", id], ["version", String(v.version)], ["previousblockhash", isCID(v.previousblockhash) ? v.previousblockhash : "none (genesis)"], ["merkleroot", v.merkleroot], ["time", String(v.time)], ["bits", v.bits.toString(16)], ["nonce", String(v.nonce)]] };
90
+ }
91
+ const fields = [["txid", id], ["version", String(v.version)], ["locktime", String(v.locktime)]];
92
+ v.vin.forEach((x, i) => {
93
+ if (isCID(x.txid))
94
+ fields.push([`vin ${i}`, x.txid], [`vin ${i} spends`, `${displayHash(x.txid.multihash.digest)}:${x.vout}`]);
95
+ else
96
+ fields.push([`vin ${i}`, "coinbase"]);
97
+ });
98
+ v.vout.forEach((o, i) => fields.push([`vout ${i}`, `${o.value} sat · ${o.script.length}-byte script`]));
99
+ return { codec: "bitcoin-tx", fields };
100
+ }
101
+ // ---------------------------------------------------------------- keys
102
+ export function be64(n) {
103
+ const u = BigInt.asUintN(64, BigInt(n)) ^ (1n << 63n);
104
+ const b = new Uint8Array(8);
105
+ new DataView(b.buffer).setBigUint64(0, u);
106
+ return b;
107
+ }
108
+ export function readBe64(b, at = 0) {
109
+ const u = new DataView(b.buffer, b.byteOffset + at, 8).getBigUint64(0) ^ (1n << 63n);
110
+ return Number(BigInt.asIntN(64, u));
111
+ }
112
+ export function strKey(s) {
113
+ const t = new TextEncoder().encode(s);
114
+ const len = [];
115
+ let v = t.length;
116
+ while (v >= 0x80) {
117
+ len.push((v & 0x7f) | 0x80);
118
+ v >>>= 7;
119
+ }
120
+ len.push(v);
121
+ return cat(Uint8Array.from(len), t);
122
+ }
123
+ function readStrKey(b) {
124
+ let n = 0, shift = 0, i = 0;
125
+ for (;;) {
126
+ const c = b[i++];
127
+ n += (c & 0x7f) * 2 ** shift;
128
+ if (!(c & 0x80))
129
+ break;
130
+ shift += 7;
131
+ }
132
+ return { s: new TextDecoder().decode(b.subarray(i, i + n)), used: i + n };
133
+ }
134
+ export function cat(...parts) {
135
+ const out = new Uint8Array(parts.reduce((s, p) => s + p.length, 0));
136
+ let o = 0;
137
+ for (const p of parts) {
138
+ out.set(p, o);
139
+ o += p.length;
140
+ }
141
+ return out;
142
+ }
143
+ export function compare(a, b) {
144
+ const n = Math.min(a.length, b.length);
145
+ for (let i = 0; i < n; i++)
146
+ if (a[i] !== b[i])
147
+ return a[i] - b[i];
148
+ return a.length - b.length;
149
+ }
150
+ /** The length of the binary CID at the front of `b` (binary CIDs are prefix-free). */
151
+ function cidLen(b) {
152
+ if (b[0] === 0x12 && b[1] === 0x20)
153
+ return 34;
154
+ let i = 0;
155
+ const varint = () => { let n = 0, shift = 0; for (;;) {
156
+ const c = b[i++];
157
+ n += (c & 0x7f) * 2 ** shift;
158
+ if (!(c & 0x80))
159
+ return n;
160
+ shift += 7;
161
+ } };
162
+ varint();
163
+ varint();
164
+ varint();
165
+ const len = varint();
166
+ return i + len;
167
+ }
168
+ /** A number as SQLite orders it: an integer as is, a float floored, anything else the least. */
169
+ function numKey(v) {
170
+ if (typeof v !== "number" || !Number.isFinite(v))
171
+ return -(1n << 63n);
172
+ return BigInt(Math.floor(v));
173
+ }
174
+ const succ = (p) => {
175
+ const b = Uint8Array.from(p);
176
+ for (let i = b.length - 1; i >= 0; i--) {
177
+ if (b[i] !== 0xff) {
178
+ b[i]++;
179
+ return b.subarray(0, i + 1);
180
+ }
181
+ }
182
+ return undefined;
183
+ };
184
+ // ---------------------------------------------------------------- the tree
185
+ /** A key's level: sha2-256(key)'s leading zero 5-bit groups. */
186
+ export function level(key) {
187
+ const d = createHash("sha256").update(key).digest();
188
+ let bit = 0;
189
+ while (bit < 255 && !((d[bit >> 3] >> (7 - (bit & 7))) & 1))
190
+ bit++;
191
+ return Math.floor(bit / 5);
192
+ }
193
+ /** Read the trees through `get` (a block's bytes by CID). */
194
+ export class Trees {
195
+ cache = new Map();
196
+ get;
197
+ constructor(get) { this.get = get; }
198
+ node(cid) {
199
+ const k = cid.toString();
200
+ let n = this.cache.get(k);
201
+ if (n)
202
+ return n;
203
+ const b = this.get(cid);
204
+ if (!b)
205
+ throw new NotFound(k);
206
+ n = decode(b);
207
+ if (this.cache.size > 20_000)
208
+ this.cache.clear();
209
+ this.cache.set(k, n);
210
+ return n;
211
+ }
212
+ lookup(root, key) {
213
+ for (let c = root; c;) {
214
+ const [left, es] = this.node(c);
215
+ let i = 0;
216
+ while (i < es.length && compare(es[i][0], key) < 0)
217
+ i++;
218
+ if (i < es.length && compare(es[i][0], key) === 0)
219
+ return es[i][1];
220
+ c = i === 0 ? left : es[i - 1][2];
221
+ }
222
+ return undefined;
223
+ }
224
+ /** Entries with lo <= key < hi, in key order. */
225
+ *range(root, lo, hi) {
226
+ if (!root)
227
+ return;
228
+ const [left, es] = this.node(root);
229
+ for (let j = 0; j <= es.length; j++) {
230
+ const gap = j === 0 ? left : es[j - 1][2];
231
+ const loOk = j === es.length || !lo || compare(lo, es[j][0]) < 0;
232
+ const hiOk = j === 0 || !hi || compare(es[j - 1][0], hi) < 0;
233
+ if (loOk && hiOk)
234
+ yield* this.range(gap, lo, hi);
235
+ if (j === es.length)
236
+ break;
237
+ const k = es[j][0];
238
+ if (hi && compare(k, hi) >= 0)
239
+ break;
240
+ if (!lo || compare(k, lo) >= 0)
241
+ yield [k, es[j][1]];
242
+ }
243
+ }
244
+ prefixed(root, p) { return this.range(root, p, succ(p)); }
245
+ }
246
+ /** The canonical tree of these pairs: its root, and its nodes (to store). */
247
+ export function buildTree(pairs) {
248
+ const sorted = [...pairs].sort((a, b) => compare(a[0], b[0]));
249
+ const lv = sorted.map(([k]) => level(k));
250
+ const blocks = [];
251
+ const make = (from, to) => {
252
+ if (from >= to)
253
+ return null;
254
+ let top = -1;
255
+ for (let i = from; i < to; i++)
256
+ top = Math.max(top, lv[i]);
257
+ const at = [];
258
+ for (let i = from; i < to; i++)
259
+ if (lv[i] === top)
260
+ at.push(i);
261
+ const left = make(from, at[0]);
262
+ const es = at.map((i, j) => [sorted[i][0], sorted[i][1], make(i + 1, j + 1 < at.length ? at[j + 1] : to)]);
263
+ const blk = encode([left, es]);
264
+ blocks.push(blk);
265
+ return blk.cid;
266
+ };
267
+ return { root: make(0, sorted.length), blocks };
268
+ }
269
+ export function readState(get, cid) {
270
+ const b = get(cid);
271
+ if (!b)
272
+ throw new NotFound(cid.toString());
273
+ const v = decode(b);
274
+ if (v.kind !== STATE_KIND)
275
+ throw new Error(`${cid} is not a state record`);
276
+ const idx = v.index;
277
+ const roots = Object.fromEntries(MAPS.map((m) => [m, (m === "heads" ? v.heads : idx[m]) ?? null]));
278
+ return { cid, log: v.log ?? null, cursor: Number(v.cursor ?? 0), roots };
279
+ }
280
+ // ---------------------------------------------------------------- building (the derivation)
281
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && !isCID(v);
282
+ /** Every map's pairs for a store's chains and log, as index.zig derives them. */
283
+ export async function derive(store) {
284
+ const pairs = Object.fromEntries(MAPS.map((m) => [m, []]));
285
+ let tip = null;
286
+ // The unique map, first admission wins: record CID → [entry n, entry].
287
+ const unique = new Map();
288
+ const mark = (u, n, e) => { const k = fmt(u); const had = unique.get(k); if (!had || had[0] > n)
289
+ unique.set(k, [n, e, u.bytes]); };
290
+ const nOf = new Map();
291
+ for await (const { cid, entry } of store.log.entries()) {
292
+ pairs.log.push([be64(entry.n), cid]);
293
+ nOf.set(fmt(cid), entry.n);
294
+ // The record an entry is unique by (index.zig `uniqueOf`): its mail record (format 3, #40), a
295
+ // libp2p `p2p` event record (#51, #42: a redelivered message is refused), or format 2's envelope / emit.
296
+ const x = entry;
297
+ let u = x.mail ?? x.envelope ?? x.outcome?.emit;
298
+ if (!u && x.event && (await store.get(x.event).catch(() => undefined))?.kind === "p2p")
299
+ u = x.event;
300
+ if (u)
301
+ mark(u, entry.n, cid);
302
+ tip = cid;
303
+ }
304
+ const seenEdge = new Set();
305
+ for await (const origin of store.edges.query({})) {
306
+ const o = (await store.get(origin));
307
+ const kind = typeof o.kind === "string" ? o.kind : undefined;
308
+ const ups = [];
309
+ for await (const c of store.chains.history(origin))
310
+ if (!c.equals(origin))
311
+ ups.push(c);
312
+ const tipCid = ups.at(-1) ?? origin;
313
+ pairs.chains.push([origin.bytes, { tip: tipCid, seq: ups.length, ...(kind !== undefined ? { kind } : {}) }]);
314
+ const edges = (seq, rows) => rows.forEach((e, ord) => {
315
+ const k = cat(strKey(e.to), origin.bytes, be64(seq), be64(ord));
316
+ const ks = Buffer.from(k).toString("hex");
317
+ if (seenEdge.has(ks))
318
+ return;
319
+ seenEdge.add(ks);
320
+ pairs.edges.push([k, [e.rel, e.locator]]);
321
+ });
322
+ edges(0, originEdges(o));
323
+ for (const [i, c] of ups.entries()) {
324
+ pairs.updates.push([cat(origin.bytes, be64(i + 1)), c]);
325
+ const u = (await store.get(c));
326
+ if (kind === "thread" && isRequestThread(o))
327
+ for (const r of routed(u))
328
+ mark(r, nOf.get(fmt(u.input)) ?? Infinity, u.input);
329
+ const keptCids = (Array.isArray(u.kept) ? u.kept : []).filter(isCID);
330
+ const kept = await Promise.all((Array.isArray(u.kept) ? u.kept : []).map((k) => (isCID(k) && !isBitcoin(k) ? store.get(k).catch(() => undefined) : undefined)));
331
+ edges(i + 1, [...updateEdges(u), ...keptEdges(kept)]);
332
+ // A kept bitcoin transaction's inputs (#42): from the block itself at seq 0; headers and merkle nodes none.
333
+ for (const k of keptCids) {
334
+ if (!isBitcoin(k))
335
+ continue;
336
+ const b = await store.bytes(k).catch(() => undefined);
337
+ if (!b)
338
+ continue;
339
+ bitcoinEdges(k, b).forEach((l, ord) => {
340
+ const key = cat(strKey(fmt(l.to)), k.bytes, be64(0), be64(ord));
341
+ const ks = Buffer.from(key).toString("hex");
342
+ if (seenEdge.has(ks))
343
+ return;
344
+ seenEdge.add(ks);
345
+ pairs.edges.push([key, [l.rel, l.locator]]);
346
+ });
347
+ }
348
+ }
349
+ const t = ups.length ? (await store.get(tipCid)) : undefined;
350
+ if (kind === "thread") {
351
+ const at = be64(numKey(o.at));
352
+ pairs.threads.push([cat(at, origin.bytes), null]);
353
+ if (t?.state !== "finished")
354
+ pairs.resting.push([cat(at, origin.bytes), null]);
355
+ if (t && t.state === "waiting" && typeof t.until === "number")
356
+ pairs.sleepers.push([cat(be64(numKey(t.until)), origin.bytes), null]);
357
+ if (t && Array.isArray(t.awaits))
358
+ for (const c of t.awaits)
359
+ if (isCID(c))
360
+ pairs.awaits.push([cat(c.bytes, at, origin.bytes), null]);
361
+ }
362
+ else if (kind === "head" && t && typeof o.name === "string" && isCID(t.tree))
363
+ pairs.heads.push([strKey(o.name), t.tree]);
364
+ }
365
+ for (const [, e, u] of unique.values())
366
+ pairs.unique.push([u, e]);
367
+ return { pairs, log: tip, cursor: await store.live.cursor.get() };
368
+ }
369
+ /** A request's thread (#68, scheduler.zig isRequestThread): the middleware launched by the request record it names. */
370
+ function isRequestThread(o) {
371
+ const args = o.args;
372
+ return isObj(args) && isCID(args.request) && typeof args.transport === "string" && isCID(o.launchedBy) && args.request.equals(o.launchedBy);
373
+ }
374
+ /**
375
+ * What a request thread's step routed that is unique (#68, scheduler.zig
376
+ * routeAdmits): the messages and libp2p `p2p` events its answer's `admit`
377
+ * lists — marked under the entry that drove the step, unless admitted before.
378
+ */
379
+ function routed(u) {
380
+ if (u.state === "errored")
381
+ return [];
382
+ const out = u.result?.stdout;
383
+ if (!(out instanceof Uint8Array))
384
+ return [];
385
+ let v;
386
+ try {
387
+ v = decode(out);
388
+ }
389
+ catch {
390
+ return [];
391
+ } // stdout that is not dag-cbor admits nothing: a program's output is its own
392
+ const admit = isObj(v) && Array.isArray(v.admit) ? v.admit : [];
393
+ const got = [];
394
+ for (const x of admit) {
395
+ if (!isObj(x))
396
+ continue;
397
+ if (isObj(x.mail) && x.mail.kind === "mail")
398
+ got.push(encode(x.mail).cid);
399
+ else if (isObj(x.event) && x.event.kind === "p2p" && typeof x.box === "string" && x.box.length > 0)
400
+ got.push(encode(x.event).cid);
401
+ }
402
+ return got;
403
+ }
404
+ // ---------------------------------------------------------------- reading (the Store)
405
+ /** Does this SQLite file carry the state pointer (the format of #30)? */
406
+ export function hasStatePointer(path) {
407
+ const db = new DatabaseSync(path, { readOnly: true });
408
+ try {
409
+ const t = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'pointers'").get();
410
+ return !!t && !!db.prepare("SELECT 1 FROM pointers WHERE name = 'state'").get();
411
+ }
412
+ finally {
413
+ db.close();
414
+ }
415
+ }
416
+ /**
417
+ * A store file, whichever format it is in: the new format (#30) through the
418
+ * index, read only (blocks may be added unless `readOnly`); any other
419
+ * through sqlite.ts. (An LMDB set, `lmdb:<env dir>#<set>`: src/host/stores.ts.)
420
+ */
421
+ export function openStoreFile(path, o = {}) {
422
+ if (isSet(path))
423
+ throw new Error(`${path}: a set (LMDB, RocksDB) is opened by src/host/stores.ts (openStoreFile there)`);
424
+ let fresh = true;
425
+ try {
426
+ fresh = !hasStatePointer(path);
427
+ }
428
+ catch { /* no such file yet: sqlite.ts makes it, and any other open failure throws there with its reason */ }
429
+ if (fresh)
430
+ return openStore(path, o);
431
+ return indexStore(new DatabaseSync(path, { readOnly: o.readOnly ?? false }), o);
432
+ }
433
+ class ReadOnly extends Error {
434
+ constructor(what) { super(`${what}: this store's index is kept by the Zig kernel (issue #30); readonly here`); }
435
+ }
436
+ export function indexStore(db, o = {}) {
437
+ db.exec("PRAGMA busy_timeout = 5000;");
438
+ const blockGet = db.prepare("SELECT bytes FROM blocks WHERE cid = ?");
439
+ const blockHas = db.prepare("SELECT 1 AS x FROM blocks WHERE cid = ?");
440
+ const blockPut = o.readOnly ? undefined : db.prepare("INSERT OR IGNORE INTO blocks (cid, bytes) VALUES (?, ?)");
441
+ const pointer = db.prepare("SELECT cid FROM pointers WHERE name = 'state'");
442
+ return indexStoreOver({
443
+ get: (c) => blockGet.get(c)?.bytes,
444
+ has: (c) => blockHas.get(c) !== undefined,
445
+ put: blockPut ? (c, b) => { blockPut.run(c, b); } : undefined,
446
+ pointer: () => pointer.get()?.cid,
447
+ close: () => db.close(),
448
+ });
449
+ }
450
+ export function indexStoreOver(src) {
451
+ const blockPut = src.put;
452
+ const get = (c) => src.get(c.bytes);
453
+ const trees = new Trees(get);
454
+ let last;
455
+ const state = () => {
456
+ const r = src.pointer();
457
+ if (!r)
458
+ throw new Error("no state pointer");
459
+ const c = fromBytes(r);
460
+ if (!last || !last.cid.equals(c))
461
+ last = readState(get, c);
462
+ return last;
463
+ };
464
+ const root = (m) => state().roots[m];
465
+ const block = (c) => { const b = get(c); if (!b)
466
+ throw new NotFound(fmt(c)); return decode(b); };
467
+ const chain = (origin) => trees.lookup(root("chains"), origin.bytes);
468
+ const updatesOf = (origin) => [...trees.prefixed(root("updates"), origin.bytes)].map(([, v]) => v);
469
+ const tipOf = (origin, row) => (row.tip.equals(origin) ? undefined : block(row.tip));
470
+ const metas = () => [...trees.range(root("chains"))].map(([k, v]) => {
471
+ const cid = fromBytes(k);
472
+ const row = v;
473
+ const o = block(cid);
474
+ const t = tipOf(cid, row);
475
+ const at = typeof o.at === "number" ? o.at : null;
476
+ return { cid, kind: row.kind, o, t, at, tipAt: t ? (typeof t.at === "number" ? t.at : null) : at };
477
+ });
478
+ const threadsBy = (pred) => metas()
479
+ .filter((m) => m.kind === "thread" && pred(m))
480
+ .sort((a, b) => (a.at ?? -Infinity) - (b.at ?? -Infinity) || compare(a.cid.bytes, b.cid.bytes))
481
+ .map((m) => m.cid);
482
+ const cids = (list) => (Array.isArray(list) ? list.filter(isCID).map(fmt) : []);
483
+ const store = {
484
+ state,
485
+ heads: () => [...trees.range(root("heads"))].map(([k, v]) => ({ name: readStrKey(k).s, root: v })),
486
+ async put(value) {
487
+ if (!blockPut)
488
+ throw new ReadOnly("put");
489
+ const b = encode(value);
490
+ blockPut(b.cid.bytes, b.bytes);
491
+ return b.cid;
492
+ },
493
+ async get(cid) { return display(block(cid)); },
494
+ async has(cid) { return src.has(cid.bytes); },
495
+ async bytes(cid) { const b = get(cid); if (!b)
496
+ throw new NotFound(fmt(cid)); return b; },
497
+ async putBlock(cid, bytes) { if (!blockPut)
498
+ throw new ReadOnly("putBlock"); blockPut(cid.bytes, bytes); },
499
+ async putMessage() { throw new ReadOnly("putMessage"); },
500
+ async findByPrefix(prefix) {
501
+ return [...trees.range(root("chains"))].map(([k]) => fromBytes(k)).filter((c) => fmt(c).startsWith(prefix));
502
+ },
503
+ chains: {
504
+ async open() { throw new ReadOnly("chains.open"); },
505
+ async append() { throw new ReadOnly("chains.append"); },
506
+ async tip(origin) {
507
+ const row = chain(origin);
508
+ if (!row)
509
+ throw new NotFound(fmt(origin));
510
+ return row.tip;
511
+ },
512
+ async *history(origin) {
513
+ if (!chain(origin))
514
+ throw new NotFound(fmt(origin));
515
+ yield origin;
516
+ yield* updatesOf(origin);
517
+ },
518
+ async originOf(cid) {
519
+ if (chain(cid))
520
+ return cid;
521
+ const b = get(cid);
522
+ if (b) {
523
+ const u = decode(b);
524
+ if (isObj(u) && isCID(u.origin) && typeof u.seq === "number") {
525
+ const at = trees.lookup(root("updates"), cat(u.origin.bytes, be64(u.seq)));
526
+ if (isCID(at) && at.equals(cid))
527
+ return u.origin;
528
+ }
529
+ }
530
+ throw new NotFound(fmt(cid));
531
+ },
532
+ },
533
+ log: {
534
+ async append() { throw new ReadOnly("log.append"); },
535
+ async byEnvelope(envelope) {
536
+ const c = trees.lookup(root("unique"), envelope.bytes);
537
+ return c && block(c).envelope ? c : undefined;
538
+ },
539
+ async outcomeOf(emit) {
540
+ const c = trees.lookup(root("unique"), emit.bytes);
541
+ return c && block(c).outcome ? c : undefined;
542
+ },
543
+ async tip() { return state().log ?? undefined; },
544
+ async *entries(from = 0) {
545
+ for (const [, v] of [...trees.range(root("log"), be64(from))])
546
+ yield { cid: v, entry: block(v) };
547
+ },
548
+ },
549
+ edges: {
550
+ async refsFrom(cid) {
551
+ // A bitcoin block's own links (#42): what an input spends, a header's previous header and root, a node's children.
552
+ if (isBitcoin(cid)) {
553
+ const b = get(cid);
554
+ return b ? bitcoinLinks(cid, b).map((l) => toRef(l.to, l.rel, l.locator === null ? null : String(l.locator))) : [];
555
+ }
556
+ const row = chain(cid);
557
+ if (!row)
558
+ return [];
559
+ const rows = [];
560
+ originEdges(block(cid)).forEach((e, ord) => rows.push({ pos: ord, ...e }));
561
+ const withKept = (u) => [...updateEdges(u), ...keptEdges((Array.isArray(u.kept) ? u.kept : []).map((k) => {
562
+ const b = isCID(k) ? get(k) : undefined;
563
+ try {
564
+ return b && decode(b);
565
+ }
566
+ catch {
567
+ return undefined;
568
+ } // a kept block that is not dag-cbor has no edges
569
+ }))];
570
+ updatesOf(cid).forEach((u, i) => withKept(block(u)).forEach((e, ord) => rows.push({ pos: (i + 1) * 1048576 + ord, ...e })));
571
+ const first = new Map();
572
+ for (const r of rows) {
573
+ const k = JSON.stringify([r.to, r.rel, r.locator]);
574
+ const had = first.get(k);
575
+ if (!had || r.pos < had.pos)
576
+ first.set(k, r);
577
+ }
578
+ return [...first.values()].sort((a, b) => a.pos - b.pos).map((r) => toRef(r.to, r.rel, r.locator));
579
+ },
580
+ async refsTo(cid) {
581
+ const p = strKey(fmt(cid));
582
+ const groups = new Map();
583
+ for (const [k, v] of trees.prefixed(root("edges"), p)) {
584
+ const rest = k.subarray(p.length);
585
+ const from = fromBytes(rest.subarray(0, cidLen(rest)));
586
+ const [rel, loc] = v;
587
+ const locator = typeof loc === "number" ? String(loc) : loc; // a bitcoin block's vout / child (#42)
588
+ const g = JSON.stringify([fmt(from), rel, locator]);
589
+ if (groups.has(g))
590
+ continue;
591
+ const o = isBitcoin(from) ? {} : block(from); // a kept bitcoin block points from itself
592
+ groups.set(g, { from, rel, locator, at: typeof o.at === "number" ? o.at : null });
593
+ }
594
+ return [...groups.values()]
595
+ .sort((a, b) => (b.at ?? -Infinity) - (a.at ?? -Infinity) || compare(b.from.bytes, a.from.bytes))
596
+ .map((g) => ({ ...toRef(cid, g.rel, g.locator), from: g.from }));
597
+ },
598
+ async *query(f) {
599
+ if (f.kind === "message" || (f.kind === undefined && (f.from !== undefined || f.to !== undefined)))
600
+ return; // the kernel keeps no messages
601
+ const key = (m) => (f.orderBy === "tipAt" ? m.tipAt : m.at);
602
+ const hits = metas()
603
+ .filter((m) => !f.kind || m.kind === f.kind)
604
+ .filter((m) => !f.thread || (m.kind === "node" && isCID(m.o.thread) && m.o.thread.equals(f.thread)))
605
+ .filter((m) => !f.program || (m.kind === "thread" && isCID(m.o.program) && m.o.program.equals(f.program)))
606
+ .filter((m) => !f.state || (m.kind === "thread" && typeof m.t?.state === "string" && f.state.includes(m.t.state)))
607
+ .filter((m) => f.parentless === undefined || (m.kind === "thread" && !isCID(m.o.launchedBy) === f.parentless))
608
+ .filter((m) => f.since === undefined || (m.at !== null && m.at >= f.since))
609
+ .filter((m) => f.before === undefined || (m.at !== null && m.at < f.before))
610
+ .sort((a, b) => (key(b) ?? -Infinity) - (key(a) ?? -Infinity) || compare(b.cid.bytes, a.cid.bytes));
611
+ yield* hits.slice(0, f.limit ?? hits.length).map((m) => m.cid);
612
+ },
613
+ async rebuild() { throw new ReadOnly("rebuild"); },
614
+ },
615
+ live: {
616
+ handles: {
617
+ async set() { throw new ReadOnly("handles.set"); },
618
+ async get() { return undefined; },
619
+ async clear() { throw new ReadOnly("handles.clear"); },
620
+ },
621
+ async *resting(f = {}) {
622
+ let n = 0;
623
+ for (const [k] of [...trees.range(root("resting"))]) {
624
+ if (f.limit !== undefined && n >= f.limit)
625
+ return;
626
+ const origin = fromBytes(k.subarray(8));
627
+ if (f.state) {
628
+ const row = chain(origin);
629
+ const t = row ? tipOf(origin, row) : undefined;
630
+ if (typeof t?.state !== "string" || !f.state.includes(t.state))
631
+ continue;
632
+ }
633
+ n++;
634
+ yield origin;
635
+ }
636
+ },
637
+ async *due(now) {
638
+ for (const [k] of [...trees.range(root("sleepers"), undefined, be64(Math.floor(now) + 1))])
639
+ yield fromBytes(k.subarray(8));
640
+ },
641
+ async *waitersOn(thread) {
642
+ yield* threadsBy((m) => cids(m.t?.waitingOn).includes(fmt(thread)));
643
+ },
644
+ async *waitingFrom(identity) {
645
+ yield* threadsBy((m) => m.t?.waitingFrom === identity);
646
+ },
647
+ async *awaiting(envelope) {
648
+ for (const [k] of [...trees.prefixed(root("awaits"), envelope.bytes)])
649
+ yield fromBytes(k.subarray(envelope.bytes.length + 8));
650
+ },
651
+ cursor: {
652
+ async get() { return state().cursor; },
653
+ async set() { throw new ReadOnly("cursor.set"); },
654
+ },
655
+ },
656
+ async close() { src.close(); },
657
+ };
658
+ return store;
659
+ }
660
+ // ---------------------------------------------------------------- the LMDB environment (#96, #133)
661
+ /** host.db's `store` column for a set of the host's LMDB environment: `lmdb:<env dir>` (the set: the row's handle). */
662
+ export const LMDB = "lmdb:";
663
+ /** The chain's member set (#133): readable by every set (kernel-zig/src/lmdb_store.zig CHAIN_SET). */
664
+ export const CHAIN_SET = "host/chain";
665
+ /** Whether host.db's `store` column names a set of an LMDB environment. */
666
+ export function isLmdb(store) { return store.startsWith(LMDB); }
667
+ /** host.db's `store` column for a set of the one-process kernel's RocksDB database (#158): `rocksdb:<db dir>` (the set: the row's handle). */
668
+ export const ROCKSDB = "rocksdb:";
669
+ /** Whether host.db's `store` column names a set of a RocksDB database (#158). */
670
+ export function isRocks(store) { return store.startsWith(ROCKSDB); }
671
+ /**
672
+ * host.db's `store` column for a set of the host's one SQLite database (the settled split,
673
+ * 2026-10-31: the host loads the wasm kernel and keeps the storage, src/host/store-sqlite.ts):
674
+ * `sqlite:<db path>` (the set: the row's handle).
675
+ */
676
+ export const SQLITE = "sqlite:";
677
+ /** Whether host.db's `store` column names a set of the host's SQLite database. */
678
+ export function isSqliteSet(store) { return store.startsWith(SQLITE); }
679
+ /** Whether a store is a set of a host's shared store (LMDB #96, RocksDB #158, SQLite 2026-10-31), not a file of its own. */
680
+ export function isSet(store) { return isLmdb(store) || isRocks(store) || isSqliteSet(store); }
681
+ /** A row's store as the kernel's tools and `openStoreFile` name it: its path, `lmdb:<env dir>#<handle>`, `rocksdb:<db dir>#<handle>` or `sqlite:<db path>#<handle>`. */
682
+ export function storeSpec(row) {
683
+ return isSet(row.store) ? `${row.store}#${row.handle}` : row.store;
684
+ }
685
+ /** `rocksdb:<db dir>#<set>` → its parts (kernel-zig/src/any_store.zig parseRocksSpec); undefined for anything else. */
686
+ export function parseRocksSpec(spec) {
687
+ if (!isRocks(spec))
688
+ return undefined;
689
+ const rest = spec.slice(ROCKSDB.length);
690
+ const i = rest.lastIndexOf("#");
691
+ if (i <= 0 || i === rest.length - 1)
692
+ throw new Error(`${spec}: a RocksDB store is rocksdb:<db dir>#<set>`);
693
+ return { dir: rest.slice(0, i), set: rest.slice(i + 1) };
694
+ }
695
+ /** `sqlite:<db path>#<set>` → its parts; undefined for anything else. */
696
+ export function parseSqliteSpec(spec) {
697
+ if (!isSqliteSet(spec))
698
+ return undefined;
699
+ const rest = spec.slice(SQLITE.length);
700
+ const i = rest.lastIndexOf("#");
701
+ if (i <= 0 || i === rest.length - 1)
702
+ throw new Error(`${spec}: a SQLite set is sqlite:<db path>#<set>`);
703
+ return { db: rest.slice(0, i), set: rest.slice(i + 1) };
704
+ }
705
+ /** `lmdb:<env dir>#<set>` → its parts (kernel-zig/src/any_store.zig parseSpec); undefined for a path. */
706
+ export function parseLmdbSpec(spec) {
707
+ if (!isLmdb(spec))
708
+ return undefined;
709
+ const rest = spec.slice(LMDB.length);
710
+ const i = rest.lastIndexOf("#");
711
+ if (i <= 0 || i === rest.length - 1)
712
+ throw new Error(`${spec}: an LMDB store is lmdb:<env dir>#<set>`);
713
+ return { dir: rest.slice(0, i), set: rest.slice(i + 1) };
714
+ }
715
+ export { readStrKey, cidLen };