| … | … | @@ -1,4 +1,4 @@ |
| 1 | # hyper-atomspace bridge — M0 · M1 · M3 | |
| 1 | # hyper-atomspace bridge — M0 · M1 · M3 · M4 | |
| 2 | 2 | |
| 3 | 3 | Milestones of the HyperStorageNode (spec: `../../spec/hyperstoragenode.md`; |
| 4 | 4 | `atomspace` org issue #4). A Bare daemon holds the op-log; a C++ client speaks the |
| … | … | @@ -39,13 +39,24 @@ All three ActiveGraph properties (arXiv:2605.21997) now hold over the log: |
| 39 | 39 | and the causally-unrelated `cat@0 animal@1 Inheritance@2` correctly **excluded**. |
| 40 | 40 | `[lineage]` phase → `GOLD #3`. The daemon answers `seqof` (log position); the C++ |
| 41 | 41 | side composes the causal walk — structure is the projection's job, not the log's. |
| 42 | - **M4 — multi-agent convergence.** TWO daemons (agents), each its own **Autobase** | |
| 43 | writer, replicate cross-process and converge to ONE linearized log. Agent A stores | |
| 44 | {cat, animal, Inheritance}, agent B stores {whale, water, lives-in, List, Eval}; | |
| 45 | both project to the **identical full 8-atom AtomSpace** (A==src, B==src, A==B). | |
| 46 | `[m4]` phase → `M4 MULTI-AGENT PASS`. The device-as-agent step: the log is the | |
| 47 | agent's memory, the projection its world model, replication its communication. | |
| 48 | (Production form runs this over Hyperswarm — already proven in | |
| 49 | `pear-git-v3/test/keet-pattern-e2e.js`; here the peer transport is a direct UDS | |
| 50 | pipe for a deterministic gold check.) | |
| 42 | 51 | |
| 43 | 52 | ## Files |
| 44 | 53 | | File | Role | |
| 45 | 54 | |---|---| |
| 46 | | `daemon.js` | Bare daemon. Op-log = a **Hypercore** (`append` on store, `get(0..len)` on load, `get(0..K)` on `loadat`, `truncate(0)` on clear). Ordered promise-chain so load/barrier observe all prior stores. `allowHalfOpen:false` + destroy-on-`end` (hyper-svc leak fix). `usage: bare daemon.js <sock> <storage-dir>`. | | |
| 47 | | `m0_roundtrip.cc` | C++ harness. `HyperChannel` = reusable `AF_UNIX` transport (from `CogSimpleStorage` do_send/do_recv) that lifts into `HyperStorageNode : public StorageNode`. Modes `full` / `verify` / `fork`. | | |
| 48 | | `run.sh` | build + 3-phase drive (full → restart-verify → fork) + cleanup. | | |
| 55 | | `daemon.js` | Bare daemon (M0-M3). Op-log = a **Hypercore** (`append`/`get`/`truncate`). Ordered promise-chain so load/barrier observe all prior stores. `allowHalfOpen:false` + destroy-on-`end` (hyper-svc leak fix). `usage: bare daemon.js <sock> <storage-dir>`. | | |
| 56 | | `daemon-ab.js` | Bare daemon (M4). Op-log = an **Autobase** (keet pattern: `optimistic + ackInterval:1000 + ackThreshold:4`); plain-core view; Pattern-B `addwriter`; cross-process peer replication over a UDS pipe (`--peer-listen`/`--peer-connect`). Kept separate so M0-M3 stay on the simpler Hypercore path. | | |
| 57 | | `m0_roundtrip.cc` | C++ harness. `HyperChannel` = reusable `AF_UNIX` transport (from `CogSimpleStorage` do_send/do_recv) that lifts into `HyperStorageNode : public StorageNode`. Modes `full` / `verify` / `fork` / `lineage` / `m4`. | | |
| 58 | | `abctl.js` | One-shot Bare client for the M4 writer-add ceremony + polling (`addwriter`, `writable`). | | |
| 59 | | `run.sh` | build + 5-phase drive (full → verify → fork → lineage → m4) + cleanup. | | |
| 49 | 60 | |
| 50 | 61 | ## Run |
| 51 | 62 | ```bash |
| … | … | @@ -54,12 +65,14 @@ All three ActiveGraph properties (arXiv:2605.21997) now hold over the log: |
| 54 | 65 | # [verify] durable replay src=8 dst=8 GOLD #1 PASS |
| 55 | 66 | # [fork] full=8 checkout@3=3 subset=1 strict=1 GOLD #2 PASS |
| 56 | 67 | # [lineage] X=(EvaluationLink …) @seq 7 … found=1 deps<X=1 excl-unrelated=1 chain=1 GOLD #3 PASS |
| 57 | # M0-M3 ALL PASS | |
| 68 | # [m4] converge A=8 B=8 | A==src=1 B==src=1 A==B=1 M4 MULTI-AGENT PASS | |
| 69 | # M0-M4 ALL PASS | |
| 58 | 70 | ``` |
| 59 | 71 | Requires: `bare` (v1.26+), `g++` (**C++20** — opencog headers use `std::atomic::wait`), |
| 60 | 72 | opencog under `/usr/local` (`libatomspace`/`libsexpr`/`libatombase`/`libatom_types`). |
| 61 | `npm install` pulls `bare-net`/`bare-fs`/`bare-process`/`hypercore` into a local | |
| 62 | `node_modules` (gitignored). | |
| 73 | `npm install` pulls `bare-net`/`bare-fs`/`bare-process`/`hypercore` (M0-M3) and | |
| 74 | `autobase`/`corestore`/`b4a`/`autobase-test-helpers` (M4) into a local `node_modules` | |
| 75 | (gitignored). | |
| 63 | 76 | |
| 64 | 77 | ## Wire protocol (newline-framed) |
| 65 | 78 | | Request | Reply | Meaning | |
| … | … | @@ -74,12 +87,15 @@ opencog under `/usr/local` (`libatomspace`/`libsexpr`/`libatombase`/`libatom_typ |
| 74 | 87 | |
| 75 | 88 | ## Deferred (next) |
| 76 | 89 | - **`HyperStorageNode : public StorageNode` + guile module** — packaging, not new |
| 77 | capability: all three properties are proven and `HyperChannel` is the transplant | |
| 90 | capability: all four milestones are proven and `HyperChannel` is the transplant | |
| 78 | 91 | unit. Needs atom_types codegen + PersistSCM + CMake (a loadable `.so`) so opencog |
| 79 | 92 | can `(cog-open (HyperStorageNode "hyper://…"))` directly. |
| 80 | 93 | - **Values** — structure only; per *event-source the graph, checkpoint the Values* |
| 81 | 94 | (`../../spec/activegraph-synthesis.md`). The GPU slice-table (Linas Step 4). |
| 82 | - **Autobase multi-writer** (M4) — two daemons, one linearized log = two agents; | |
| 83 | the device-as-agent step. `seqof`'s linear scan also wants a real seq index here. | |
| 84 | - **Branch-fork with divergent writes** — `loadat` is a read-only checkout; true | |
| 85 | forking-with-writes rides Autobase. | |
| 95 | - **Hyperswarm peer transport** — M4 replicates over a direct UDS pipe for a | |
| 96 | deterministic gold check; the real device-as-agent transport is Hyperswarm (join | |
| 97 | `base.discoveryKey`), already proven in `pear-git-v3`. | |
| 98 | - **Seq index** — `seqof`/convergence use linear scans; fine at this scale, an index | |
| 99 | is the scale upgrade. | |
| 100 | - **Branch-fork with divergent writes** — `loadat` is a read-only checkout; genuine | |
| 101 | divergent-history forking now has its substrate (Autobase) from M4. |
| … | … | @@ -0,0 +1,23 @@ |
| 1 | // abctl.js — one-shot client for the M4 autobase daemon (ceremony + polling). | |
| 2 | // usage: bare abctl.js <client-sock> "<protocol line>" | |
| 3 | // Prints the daemon's single reply line (nothing for fire-and-forget `store`). | |
| 4 | import net from 'bare-net' | |
| 5 | import process from 'bare-process' | |
| 6 | ||
| 7 | const sock = process.argv[2] | |
| 8 | const line = process.argv[3] | |
| 9 | if (!sock || line === undefined) { console.error('usage: abctl.js <sock> "<line>"'); process.exit(1) } | |
| 10 | ||
| 11 | const noReply = line.startsWith('store ') | |
| 12 | const c = net.createConnection(sock) | |
| 13 | let buf = '' | |
| 14 | c.on('connect', () => { | |
| 15 | c.write(line + '\n') | |
| 16 | if (noReply) setTimeout(() => { c.destroy(); process.exit(0) }, 60) | |
| 17 | }) | |
| 18 | c.on('data', (d) => { | |
| 19 | buf += d.toString() | |
| 20 | const nl = buf.indexOf('\n') | |
| 21 | if (nl !== -1) { console.log(buf.slice(0, nl)); c.destroy(); process.exit(0) } | |
| 22 | }) | |
| 23 | c.on('error', (e) => { console.error('ERR ' + e.message); process.exit(2) }) |
| … | … | @@ -0,0 +1,174 @@ |
| 1 | // hyper-atomspace daemon (Bare) — M4: Autobase multi-writer op-log. | |
| 2 | // | |
| 3 | // Two agents (daemons), each its own Autobase writer, replicate over a peer UDS | |
| 4 | // and converge to ONE linearized log -> ONE AtomSpace projection. This is the | |
| 5 | // device-as-agent step: the production form runs this over Hyperswarm (already | |
| 6 | // proven in pear-git-v3/test/keet-pattern-e2e.js); here the peer transport is a | |
| 7 | // direct UDS pipe for a deterministic gold check. | |
| 8 | // | |
| 9 | // usage: | |
| 10 | // bare daemon-ab.js <client-sock> <storage-dir> [--bootstrap <hex>] | |
| 11 | // [--peer-listen <path> | --peer-connect <path>] | |
| 12 | // | |
| 13 | // Startup prints (for orchestration): | |
| 14 | // AB key=<autobase-key> local=<local-writer-key> writable=<bool> | |
| 15 | // | |
| 16 | // Client protocol — newline-framed over the client UDS (superset of M0-M3, minus | |
| 17 | // clear which an append-only multi-writer log does not support): | |
| 18 | // store <sexpr>\n append an atom op to THIS writer. no reply (fire-and-forget). | |
| 19 | // load\n reply: (<sexpr>…)\n — the linearized view. | |
| 20 | // loadat <K>\n reply: (<sexpr0>…<sexpr_{K-1}>)\n | |
| 21 | // seqof <sexpr>\n reply: <seq>\n | |
| 22 | // count\n reply: <view.length>\n | |
| 23 | // barrier\n await base.update(); reply: ok\n | |
| 24 | // addwriter <hex>\n append an add-writer op for that local key; reply: ok\n | |
| 25 | // writable\n reply: true|false\n | |
| 26 | ||
| 27 | import net from 'bare-net' | |
| 28 | import fs from 'bare-fs' | |
| 29 | import process from 'bare-process' | |
| 30 | import Corestore from 'corestore' | |
| 31 | import Autobase from 'autobase' | |
| 32 | import b4a from 'b4a' | |
| 33 | ||
| 34 | const argv = process.argv.slice(2) | |
| 35 | const clientSock = argv[0] | |
| 36 | const storageDir = argv[1] | |
| 37 | function flag (name) { const i = argv.indexOf(name); return i === -1 ? null : argv[i + 1] } | |
| 38 | const bootstrapHex = flag('--bootstrap') | |
| 39 | const peerListen = flag('--peer-listen') | |
| 40 | const peerConnect = flag('--peer-connect') | |
| 41 | ||
| 42 | if (!clientSock || !storageDir) { | |
| 43 | console.error('usage: bare daemon-ab.js <client-sock> <storage-dir> [--bootstrap hex] [--peer-listen p | --peer-connect p]') | |
| 44 | process.exit(1) | |
| 45 | } | |
| 46 | try { fs.unlinkSync(clientSock) } catch (_) {} | |
| 47 | ||
| 48 | const store = new Corestore(storageDir) | |
| 49 | const base = new Autobase(store, bootstrapHex ? b4a.from(bootstrapHex, 'hex') : null, { | |
| 50 | open: (s) => s.get('atomlog', { valueEncoding: 'utf-8' }), | |
| 51 | apply: async (nodes, view, host) => { | |
| 52 | for (const node of nodes) { | |
| 53 | const op = node.value | |
| 54 | if (op.t === 'add-writer') { | |
| 55 | await host.addWriter(b4a.from(op.key, 'hex'), { indexer: !!op.indexer }) | |
| 56 | continue | |
| 57 | } | |
| 58 | await view.append(op.s) | |
| 59 | } | |
| 60 | }, | |
| 61 | valueEncoding: 'json', | |
| 62 | optimistic: true, | |
| 63 | ackInterval: 1000, | |
| 64 | ackThreshold: 4 | |
| 65 | }) | |
| 66 | await base.ready() | |
| 67 | ||
| 68 | // ── peer replication (cross-process, over a UDS pipe) ───────────────────────── | |
| 69 | function wireReplication (sock, initiator) { | |
| 70 | const s = base.replicate(initiator) | |
| 71 | s.on('error', () => {}) | |
| 72 | sock.on('error', () => {}) | |
| 73 | s.pipe(sock).pipe(s) | |
| 74 | } | |
| 75 | if (peerListen) { | |
| 76 | try { fs.unlinkSync(peerListen) } catch (_) {} | |
| 77 | const rserver = net.createServer({ allowHalfOpen: false }, (sock) => wireReplication(sock, false)) | |
| 78 | rserver.listen(peerListen) | |
| 79 | } | |
| 80 | if (peerConnect) { | |
| 81 | const connect = () => { | |
| 82 | const sock = net.createConnection(peerConnect) | |
| 83 | sock.on('error', () => setTimeout(connect, 100)) // peer may not be listening yet | |
| 84 | sock.on('connect', () => wireReplication(sock, true)) | |
| 85 | } | |
| 86 | connect() | |
| 87 | } | |
| 88 | ||
| 89 | // ── replay helper ───────────────────────────────────────────────────────────── | |
| 90 | async function replay (upto) { | |
| 91 | await base.update() | |
| 92 | const v = base.view | |
| 93 | const n = Math.max(0, Math.min(upto, v.length)) | |
| 94 | let body = '' | |
| 95 | for (let i = 0; i < n; i++) body += await v.get(i) | |
| 96 | return '(' + body + ')\n' | |
| 97 | } | |
| 98 | ||
| 99 | async function handleLine (line, sock) { | |
| 100 | if (line.length === 0) return | |
| 101 | const sp = line.indexOf(' ') | |
| 102 | const verb = sp === -1 ? line : line.slice(0, sp) | |
| 103 | const rest = sp === -1 ? '' : line.slice(sp + 1) | |
| 104 | ||
| 105 | switch (verb) { | |
| 106 | case 'store': | |
| 107 | if (!base.writable) return // silently drop; harness checks `writable` first | |
| 108 | await base.append({ t: 'atom', s: rest }) | |
| 109 | return | |
| 110 | case 'load': | |
| 111 | sock.write(await replay(Infinity)) | |
| 112 | return | |
| 113 | case 'loadat': | |
| 114 | sock.write(await replay(parseInt(rest, 10) || 0)) | |
| 115 | return | |
| 116 | case 'seqof': { | |
| 117 | await base.update() | |
| 118 | const v = base.view | |
| 119 | let found = -1 | |
| 120 | for (let i = 0; i < v.length; i++) { if (await v.get(i) === rest) { found = i; break } } | |
| 121 | sock.write(String(found) + '\n') | |
| 122 | return | |
| 123 | } | |
| 124 | case 'count': | |
| 125 | await base.update() | |
| 126 | sock.write(String(base.view.length) + '\n') | |
| 127 | return | |
| 128 | case 'barrier': | |
| 129 | await base.update() | |
| 130 | sock.write('ok\n') | |
| 131 | return | |
| 132 | case 'addwriter': | |
| 133 | await base.append({ t: 'add-writer', key: rest, indexer: false }) | |
| 134 | sock.write('ok\n') | |
| 135 | return | |
| 136 | case 'writable': | |
| 137 | sock.write((base.writable ? 'true' : 'false') + '\n') | |
| 138 | return | |
| 139 | default: | |
| 140 | sock.write('err unknown-verb ' + verb + '\n') | |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | const server = net.createServer({ allowHalfOpen: false }, (sock) => { | |
| 145 | let buf = '' | |
| 146 | let tail = Promise.resolve() | |
| 147 | sock.on('data', (chunk) => { | |
| 148 | buf += chunk.toString() | |
| 149 | let nl | |
| 150 | while ((nl = buf.indexOf('\n')) !== -1) { | |
| 151 | const line = buf.slice(0, nl) | |
| 152 | buf = buf.slice(nl + 1) | |
| 153 | tail = tail.then(() => handleLine(line, sock)).catch((e) => console.error(e)) | |
| 154 | } | |
| 155 | }) | |
| 156 | sock.on('end', () => sock.destroy()) | |
| 157 | sock.on('error', () => sock.destroy()) | |
| 158 | }) | |
| 159 | ||
| 160 | server.listen(clientSock, () => { | |
| 161 | console.log('AB key=' + b4a.toString(base.key, 'hex') + | |
| 162 | ' local=' + b4a.toString(base.local.key, 'hex') + | |
| 163 | ' writable=' + base.writable + ' sock=' + clientSock) | |
| 164 | }) | |
| 165 | ||
| 166 | async function shutdown () { | |
| 167 | try { server.close() } catch (_) {} | |
| 168 | try { await base.close() } catch (_) {} | |
| 169 | try { fs.unlinkSync(clientSock) } catch (_) {} | |
| 170 | try { if (peerListen) fs.unlinkSync(peerListen) } catch (_) {} | |
| 171 | process.exit(0) | |
| 172 | } | |
| 173 | process.on('SIGINT', shutdown) | |
| 174 | process.on('SIGTERM', shutdown) |
| … | … | @@ -19,6 +19,9 @@ |
| 19 | 19 | * causal history through the log via seqof and check it is the |
| 20 | 20 | * exact set of ops that produced X, ordered, excluding |
| 21 | 21 | * causally-unrelated atoms (complete lineage, M3 #3) |
| 22 | * m4 <sockB> TWO daemons (agents) on one Autobase log: agent A stores | |
| 23 | * half the graph, agent B (arg 3) the other half; both must | |
| 24 | * converge to the identical full AtomSpace (multi-agent, M4) | |
| 22 | 25 | * |
| 23 | 26 | * SPDX-License-Identifier: AGPL-3.0-or-later |
| 24 | 27 | */ |
| … | … | @@ -114,6 +117,8 @@ public: |
| 114 | 117 | send_line("seqof " + sexpr + "\n"); |
| 115 | 118 | return std::stoi(recv_line()); |
| 116 | 119 | } |
| 120 | int count() { send_line("count\n"); return std::stoi(recv_line()); } | |
| 121 | bool writable(){ send_line("writable\n"); return recv_line() == "true"; } | |
| 117 | 122 | }; |
| 118 | 123 | |
| 119 | 124 | // --------------------------------------------------------------------------- |
| … | … | @@ -280,6 +285,56 @@ int main(int argc, char** argv) |
| 280 | 285 | return ok ? 0 : 1; |
| 281 | 286 | } |
| 282 | 287 | |
| 288 | if (mode == "m4") | |
| 289 | { | |
| 290 | if (argc < 4) { std::cerr << "m4 needs <sockB>\n"; return 2; } | |
| 291 | HyperChannel chB; | |
| 292 | chB.open(argv[3]); | |
| 293 | ||
| 294 | // Ceremony (addWriter) is done by run.sh before we run; both must be | |
| 295 | // writable now. A = this channel (ch), B = chB. | |
| 296 | if (!ch.writable() || !chB.writable()) | |
| 297 | { | |
| 298 | std::cout << "[m4] A.writable=" << ch.writable() | |
| 299 | << " B.writable=" << chB.writable() | |
| 300 | << " FAIL (ceremony not complete)\n"; | |
| 301 | return 1; | |
| 302 | } | |
| 303 | ||
| 304 | // Each agent stores a self-contained half of the graph (no cross-agent | |
| 305 | // dependency): A = {cat, animal, Inheritance}; B = {whale, water, | |
| 306 | // lives-in, List, Eval}. | |
| 307 | for (int i = 0; i < 3; i++) ch.store(Sexpr::encode_atom(order[i])); | |
| 308 | for (int i = 3; i < 8; i++) chB.store(Sexpr::encode_atom(order[i])); | |
| 309 | ch.barrier(); | |
| 310 | chB.barrier(); | |
| 311 | ||
| 312 | // Wait for the two writers' streams to linearize + replicate to 8 ops. | |
| 313 | int ca = 0, cb = 0; | |
| 314 | for (int t = 0; t < 100; t++) // ~10s budget | |
| 315 | { | |
| 316 | ca = ch.count(); | |
| 317 | cb = chB.count(); | |
| 318 | if (ca == 8 && cb == 8) break; | |
| 319 | usleep(100000); | |
| 320 | } | |
| 321 | ||
| 322 | AtomSpacePtr dstA = decode_reply(ch.load()); | |
| 323 | AtomSpacePtr dstB = decode_reply(chB.load()); | |
| 324 | std::vector<std::string> ea = encoded_sorted(dstA); | |
| 325 | std::vector<std::string> eb = encoded_sorted(dstB); | |
| 326 | ||
| 327 | bool a_ok = (ea == es); // agent A converged to the full graph | |
| 328 | bool b_ok = (eb == es); // agent B converged to the full graph | |
| 329 | bool agree = (ea == eb); // and to the SAME graph | |
| 330 | bool ok = a_ok && b_ok && agree && dstA->get_size() == 8; | |
| 331 | std::cout << "[m4] converge A=" << ca << " B=" << cb | |
| 332 | << " | A==src=" << a_ok << " B==src=" << b_ok | |
| 333 | << " A==B=" << agree | |
| 334 | << (ok ? " M4 MULTI-AGENT PASS\n" : " M4 MULTI-AGENT FAIL\n"); | |
| 335 | return ok ? 0 : 1; | |
| 336 | } | |
| 337 | ||
| 283 | 338 | // mode == full : live append + replay round-trip. |
| 284 | 339 | ch.clear(); |
| 285 | 340 | for (const Handle& h : order) ch.store(Sexpr::encode_atom(h)); |
| … | … | @@ -8,9 +8,13 @@ |
| 8 | 8 | "name": "hyper-atomspace-m0", |
| 9 | 9 | "version": "0.0.0", |
| 10 | 10 | "dependencies": { |
| 11 | "autobase": "^7.28.1", | |
| 12 | "autobase-test-helpers": "^3.1.0", | |
| 13 | "b4a": "^1.8.1", | |
| 11 | 14 | "bare-fs": "^4.7.3", |
| 12 | 15 | "bare-net": "^2.3.2", |
| 13 | 16 | "bare-process": "^4.5.0", |
| 17 | "corestore": "^7.11.0", | |
| 14 | 18 | "hypercore": "^11.33.5" |
| 15 | 19 | } |
| 16 | 20 | }, |
| … | … | @@ -31,6 +35,45 @@ |
| 31 | 35 | "unslab": "^1.3.0" |
| 32 | 36 | } |
| 33 | 37 | }, |
| 38 | "node_modules/autobase": { | |
| 39 | "version": "7.28.1", | |
| 40 | "resolved": "https://registry.npmjs.org/autobase/-/autobase-7.28.1.tgz", | |
| 41 | "integrity": "sha512-I8SHcJE/ru5Nun6NpqwRjenB8aK01b9z1qNeyx4XuG+BxeFUpXk0ZT2u3/nG+GZnObUTFMCKOo2fbAv7ipcaLw==", | |
| 42 | "license": "Apache-2.0", | |
| 43 | "dependencies": { | |
| 44 | "b4a": "^1.6.1", | |
| 45 | "bare-events": "^2.2.0", | |
| 46 | "compact-encoding": "^3.0.0", | |
| 47 | "core-coupler": "^2.0.0", | |
| 48 | "debounceify": "^1.0.0", | |
| 49 | "encryption-encoding": "^1.0.3", | |
| 50 | "hyperbee": "^2.22.0", | |
| 51 | "hypercore": "^11.27.12", | |
| 52 | "hypercore-crypto": "^3.4.0", | |
| 53 | "hypercore-id-encoding": "^1.2.0", | |
| 54 | "hyperschema": "^1.12.1", | |
| 55 | "index-encoder": "^3.3.2", | |
| 56 | "nanoassert": "^2.0.0", | |
| 57 | "protomux-wakeup": "^2.0.0", | |
| 58 | "ready-resource": "^1.0.0", | |
| 59 | "resolve-reject-promise": "^1.1.0", | |
| 60 | "safety-catch": "^1.0.2", | |
| 61 | "scope-lock": "^1.2.4", | |
| 62 | "signal-promise": "^1.0.3", | |
| 63 | "sodium-universal": "^5.0.1", | |
| 64 | "sub-encoder": "^2.1.1", | |
| 65 | "tiny-buffer-map": "^1.1.1" | |
| 66 | } | |
| 67 | }, | |
| 68 | "node_modules/autobase-test-helpers": { | |
| 69 | "version": "3.1.0", | |
| 70 | "resolved": "https://registry.npmjs.org/autobase-test-helpers/-/autobase-test-helpers-3.1.0.tgz", | |
| 71 | "integrity": "sha512-3Jk1nWEcMxYOtU6rt4ujpchLz0W/Uv7S6v0tea53wW9rZqwFzgffEYbufNak53/93DBbp3gwX+AgHdfHtpx4RQ==", | |
| 72 | "license": "Apache-2.0", | |
| 73 | "dependencies": { | |
| 74 | "b4a": "^1.6.4" | |
| 75 | } | |
| 76 | }, | |
| 34 | 77 | "node_modules/b4a": { |
| 35 | 78 | "version": "1.8.1", |
| 36 | 79 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", |
| … | … | @@ -370,6 +413,15 @@ |
| 370 | 413 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", |
| 371 | 414 | "license": "MIT" |
| 372 | 415 | }, |
| 416 | "node_modules/codecs": { | |
| 417 | "version": "3.1.0", | |
| 418 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", | |
| 419 | "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==", | |
| 420 | "license": "MIT", | |
| 421 | "dependencies": { | |
| 422 | "b4a": "^1.6.3" | |
| 423 | } | |
| 424 | }, | |
| 373 | 425 | "node_modules/compact-encoding": { |
| 374 | 426 | "version": "3.3.0", |
| 375 | 427 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.0.tgz", |
| … | … | @@ -379,6 +431,39 @@ |
| 379 | 431 | "b4a": "^1.3.0" |
| 380 | 432 | } |
| 381 | 433 | }, |
| 434 | "node_modules/core-coupler": { | |
| 435 | "version": "2.0.0", | |
| 436 | "resolved": "https://registry.npmjs.org/core-coupler/-/core-coupler-2.0.0.tgz", | |
| 437 | "integrity": "sha512-FJuEvsdCMwx0Wu+gFQ49rGCi8LCXh8kizHsCQwkdgPZFEFiF0z2HDvyIs+fPt5wMIfU2UVFDuN+dtpfbIxJE6g==", | |
| 438 | "license": "Apache-2.0", | |
| 439 | "dependencies": { | |
| 440 | "safety-catch": "^1.0.2" | |
| 441 | } | |
| 442 | }, | |
| 443 | "node_modules/corestore": { | |
| 444 | "version": "7.11.0", | |
| 445 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.11.0.tgz", | |
| 446 | "integrity": "sha512-zGlF6dBZ9bvyRliz6g+NW0tGRwGYmmc84o1TARbXOJ+fPRXeKTkVzVV95eWIJEU3+6WBUbcvIBQKmKiNmiTAdg==", | |
| 447 | "license": "MIT", | |
| 448 | "dependencies": { | |
| 449 | "b4a": "^1.6.7", | |
| 450 | "bare-events": "^2.8.3", | |
| 451 | "hypercore": "^11.32.0", | |
| 452 | "hypercore-crypto": "^3.4.2", | |
| 453 | "hypercore-errors": "^1.4.0", | |
| 454 | "hypercore-id-encoding": "^1.3.0", | |
| 455 | "ready-resource": "^1.1.1", | |
| 456 | "sodium-universal": "^5.0.1", | |
| 457 | "streamx": "^2.26.0", | |
| 458 | "which-runtime": "^1.2.1" | |
| 459 | } | |
| 460 | }, | |
| 461 | "node_modules/debounceify": { | |
| 462 | "version": "1.1.0", | |
| 463 | "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz", | |
| 464 | "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==", | |
| 465 | "license": "MIT" | |
| 466 | }, | |
| 382 | 467 | "node_modules/device-file": { |
| 383 | 468 | "version": "2.3.1", |
| 384 | 469 | "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz", |
| … | … | @@ -393,6 +478,15 @@ |
| 393 | 478 | "ready-resource": "^1.2.0" |
| 394 | 479 | } |
| 395 | 480 | }, |
| 481 | "node_modules/encryption-encoding": { | |
| 482 | "version": "1.0.3", | |
| 483 | "resolved": "https://registry.npmjs.org/encryption-encoding/-/encryption-encoding-1.0.3.tgz", | |
| 484 | "integrity": "sha512-+SlKCeULnNnwBF2rGrJlSLYjwITwfjO8PLSHzue4yt+2DsVJUU8GakRkH4+mTaa3FzpxVjpTy1kKR2gYGctNWg==", | |
| 485 | "license": "Apache-2.0", | |
| 486 | "dependencies": { | |
| 487 | "hyperschema": "^1.19.0" | |
| 488 | } | |
| 489 | }, | |
| 396 | 490 | "node_modules/events-universal": { |
| 397 | 491 | "version": "1.0.1", |
| 398 | 492 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", |
| … | … | @@ -451,6 +545,26 @@ |
| 451 | 545 | "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==", |
| 452 | 546 | "license": "MIT" |
| 453 | 547 | }, |
| 548 | "node_modules/hyperbee": { | |
| 549 | "version": "2.27.3", | |
| 550 | "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", | |
| 551 | "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", | |
| 552 | "license": "MIT", | |
| 553 | "dependencies": { | |
| 554 | "b4a": "^1.6.0", | |
| 555 | "codecs": "^3.0.0", | |
| 556 | "debounceify": "^1.0.0", | |
| 557 | "hypercore-errors": "^1.0.0", | |
| 558 | "mutexify": "^1.4.0", | |
| 559 | "protocol-buffers-encodings": "^1.2.0", | |
| 560 | "rache": "^1.0.0", | |
| 561 | "ready-resource": "^1.0.0", | |
| 562 | "resolve-reject-promise": "^1.1.0", | |
| 563 | "safety-catch": "^1.0.2", | |
| 564 | "streamx": "^2.12.4", | |
| 565 | "unslab": "^1.2.0" | |
| 566 | } | |
| 567 | }, | |
| 454 | 568 | "node_modules/hypercore": { |
| 455 | 569 | "version": "11.33.5", |
| 456 | 570 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.33.5.tgz", |
| … | … | @@ -566,6 +680,15 @@ |
| 566 | 680 | "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", |
| 567 | 681 | "license": "MIT" |
| 568 | 682 | }, |
| 683 | "node_modules/mutexify": { | |
| 684 | "version": "1.4.0", | |
| 685 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", | |
| 686 | "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", | |
| 687 | "license": "MIT", | |
| 688 | "dependencies": { | |
| 689 | "queue-tick": "^1.0.0" | |
| 690 | } | |
| 691 | }, | |
| 569 | 692 | "node_modules/nanoassert": { |
| 570 | 693 | "version": "2.0.0", |
| 571 | 694 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", |
| … | … | @@ -594,6 +717,17 @@ |
| 594 | 717 | "sodium-universal": "^5.0.0" |
| 595 | 718 | } |
| 596 | 719 | }, |
| 720 | "node_modules/protocol-buffers-encodings": { | |
| 721 | "version": "1.2.0", | |
| 722 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", | |
| 723 | "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==", | |
| 724 | "license": "MIT", | |
| 725 | "dependencies": { | |
| 726 | "b4a": "^1.6.0", | |
| 727 | "signed-varint": "^2.0.1", | |
| 728 | "varint": "5.0.0" | |
| 729 | } | |
| 730 | }, | |
| 597 | 731 | "node_modules/protomux": { |
| 598 | 732 | "version": "3.11.0", |
| 599 | 733 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.11.0.tgz", |
| … | … | @@ -607,6 +741,18 @@ |
| 607 | 741 | "unslab": "^1.3.0" |
| 608 | 742 | } |
| 609 | 743 | }, |
| 744 | "node_modules/protomux-wakeup": { | |
| 745 | "version": "2.9.0", | |
| 746 | "resolved": "https://registry.npmjs.org/protomux-wakeup/-/protomux-wakeup-2.9.0.tgz", | |
| 747 | "integrity": "sha512-K93WhS9qIRL8WAQPU2V3PxdpQ1oRlQCFyCyIYInoq/iRkcuO1BegRERWo7SS1Tcme14VKGWOr/IivjjYnHan3Q==", | |
| 748 | "license": "Apache-2.0", | |
| 749 | "dependencies": { | |
| 750 | "b4a": "^1.6.7", | |
| 751 | "hypercore-crypto": "^3.5.0", | |
| 752 | "hyperschema": "^1.10.4", | |
| 753 | "protomux": "^3.10.1" | |
| 754 | } | |
| 755 | }, | |
| 610 | 756 | "node_modules/queue-tick": { |
| 611 | 757 | "version": "1.0.1", |
| 612 | 758 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", |
| … | … | @@ -636,6 +782,12 @@ |
| 636 | 782 | "quickbit-native": "^2.2.0" |
| 637 | 783 | } |
| 638 | 784 | }, |
| 785 | "node_modules/rache": { | |
| 786 | "version": "1.0.0", | |
| 787 | "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz", | |
| 788 | "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==", | |
| 789 | "license": "Apache-2.0" | |
| 790 | }, | |
| 639 | 791 | "node_modules/random-array-iterator": { |
| 640 | 792 | "version": "1.0.0", |
| 641 | 793 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", |
| … | … | @@ -717,6 +869,15 @@ |
| 717 | 869 | "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==", |
| 718 | 870 | "license": "MIT" |
| 719 | 871 | }, |
| 872 | "node_modules/signed-varint": { | |
| 873 | "version": "2.0.1", | |
| 874 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", | |
| 875 | "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==", | |
| 876 | "license": "MIT", | |
| 877 | "dependencies": { | |
| 878 | "varint": "~5.0.0" | |
| 879 | } | |
| 880 | }, | |
| 720 | 881 | "node_modules/simdle-native": { |
| 721 | 882 | "version": "1.3.9", |
| 722 | 883 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", |
| … | … | @@ -792,6 +953,16 @@ |
| 792 | 953 | "text-decoder": "^1.1.0" |
| 793 | 954 | } |
| 794 | 955 | }, |
| 956 | "node_modules/sub-encoder": { | |
| 957 | "version": "2.1.3", | |
| 958 | "resolved": "https://registry.npmjs.org/sub-encoder/-/sub-encoder-2.1.3.tgz", | |
| 959 | "integrity": "sha512-Xxx04ygZo/1J3yHvaSA6VhDmiSaBQkw/PmO3YnnYFXle+tfOGToC6FcDpIfMztWZXJzuKG14b/57HMkiL58C6A==", | |
| 960 | "license": "Apache-2.0", | |
| 961 | "dependencies": { | |
| 962 | "b4a": "^1.6.0", | |
| 963 | "codecs": "^3.1.0" | |
| 964 | } | |
| 965 | }, | |
| 795 | 966 | "node_modules/teex": { |
| 796 | 967 | "version": "1.0.1", |
| 797 | 968 | "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", |
| … | … | @@ -816,6 +987,15 @@ |
| 816 | 987 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", |
| 817 | 988 | "license": "MIT" |
| 818 | 989 | }, |
| 990 | "node_modules/tiny-buffer-map": { | |
| 991 | "version": "1.1.1", | |
| 992 | "resolved": "https://registry.npmjs.org/tiny-buffer-map/-/tiny-buffer-map-1.1.1.tgz", | |
| 993 | "integrity": "sha512-C1eDw6ks9CmkDbWVCPHobuixPTkxGa7IDERlaVk98dv4tOUdz42o3haHBr0uhNxbj0gczBTVIyS2uQsu+1vc2Q==", | |
| 994 | "license": "MIT", | |
| 995 | "dependencies": { | |
| 996 | "b4a": "^1.6.0" | |
| 997 | } | |
| 998 | }, | |
| 819 | 999 | "node_modules/unslab": { |
| 820 | 1000 | "version": "1.3.0", |
| 821 | 1001 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", |
| … | … | @@ -825,6 +1005,12 @@ |
| 825 | 1005 | "b4a": "^1.6.6" |
| 826 | 1006 | } |
| 827 | 1007 | }, |
| 1008 | "node_modules/varint": { | |
| 1009 | "version": "5.0.0", | |
| 1010 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", | |
| 1011 | "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==", | |
| 1012 | "license": "MIT" | |
| 1013 | }, | |
| 828 | 1014 | "node_modules/which-runtime": { |
| 829 | 1015 | "version": "1.4.0", |
| 830 | 1016 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz", |
| … | … | @@ -5,9 +5,13 @@ |
| 5 | 5 | "description": "M0 UDS bridge experiment: Bare daemon (in-memory s-expr log) for HyperStorageNode", |
| 6 | 6 | "type": "module", |
| 7 | 7 | "dependencies": { |
| 8 | "autobase": "^7.28.1", | |
| 9 | "autobase-test-helpers": "^3.1.0", | |
| 10 | "b4a": "^1.8.1", | |
| 8 | 11 | "bare-fs": "^4.7.3", |
| 9 | 12 | "bare-net": "^2.3.2", |
| 10 | 13 | "bare-process": "^4.5.0", |
| 14 | "corestore": "^7.11.0", | |
| 11 | 15 | "hypercore": "^11.33.5" |
| 12 | 16 | } |
| 13 | 17 | } |
| … | … | @@ -13,13 +13,16 @@ WORK="$(mktemp -d /tmp/m1-XXXXXX)" |
| 13 | 13 | SOCK="$WORK/svc.sock" |
| 14 | 14 | STORE1="$WORK/store1" |
| 15 | 15 | STORE2="$WORK/store2" |
| 16 | DPID="" | |
| 16 | SA="$WORK/a.sock"; SB="$WORK/b.sock"; PEER="$WORK/peer.sock" | |
| 17 | DPID=""; PA=""; PB="" | |
| 17 | 18 | |
| 18 | 19 | cleanup() { |
| 19 | # `bare` is an nvm shim that execs a child runtime; kill $DPID AND match the | |
| 20 | # daemon by its unique socket path. | |
| 21 | [ -n "$DPID" ] && kill -9 "$DPID" 2>/dev/null || true | |
| 20 | # `bare` is an nvm shim that execs a child runtime; kill the shims AND match | |
| 21 | # the daemons by their unique socket paths. | |
| 22 | for p in "$DPID" "$PA" "$PB"; do [ -n "$p" ] && kill -9 "$p" 2>/dev/null || true; done | |
| 22 | 23 | pkill -9 -f "daemon.js $SOCK" 2>/dev/null || true |
| 24 | pkill -9 -f "daemon-ab.js $SA" 2>/dev/null || true | |
| 25 | pkill -9 -f "daemon-ab.js $SB" 2>/dev/null || true | |
| 23 | 26 | rm -rf "$WORK" |
| 24 | 27 | } |
| 25 | 28 | trap cleanup EXIT |
| … | … | @@ -41,7 +44,7 @@ stop_daemon() { |
| 41 | 44 | } |
| 42 | 45 | |
| 43 | 46 | # deps + build |
| 44 | [ -d node_modules/hypercore ] || npm install --silent | |
| 47 | [ -d node_modules/autobase ] || npm install --silent | |
| 45 | 48 | g++ -std=gnu++20 -O2 -I/usr/local/include m0_roundtrip.cc \ |
| 46 | 49 | -L/usr/local/lib/opencog -Wl,-rpath,/usr/local/lib/opencog \ |
| 47 | 50 | -latomspace -lsexpr -latombase -latom_types \ |
| … | … | @@ -67,4 +70,29 @@ start_daemon "$WORK/store3" |
| 67 | 70 | ./m0_roundtrip "$SOCK" lineage |
| 68 | 71 | stop_daemon |
| 69 | 72 | |
| 70 | echo "M0-M3 ALL PASS" | |
| 73 | # Phase 5 — M4: two Autobase daemons (agents) converge to one linearized log. | |
| 74 | bare daemon-ab.js "$SA" "$WORK/abA" --peer-listen "$PEER" > "$WORK/a.log" 2>&1 & | |
| 75 | PA=$! | |
| 76 | for _ in $(seq 1 100); do grep -q "AB key=" "$WORK/a.log" && break; sleep 0.05; done | |
| 77 | AKEY=$(sed -n 's/.*AB key=\([0-9a-f]*\).*/\1/p' "$WORK/a.log") | |
| 78 | [ -n "$AKEY" ] || { echo "daemon A failed:"; cat "$WORK/a.log"; exit 1; } | |
| 79 | ||
| 80 | bare daemon-ab.js "$SB" "$WORK/abB" --bootstrap "$AKEY" --peer-connect "$PEER" > "$WORK/b.log" 2>&1 & | |
| 81 | PB=$! | |
| 82 | for _ in $(seq 1 100); do grep -q "AB key=" "$WORK/b.log" && break; sleep 0.05; done | |
| 83 | BLOCAL=$(sed -n 's/.*local=\([0-9a-f]*\).*/\1/p' "$WORK/b.log") | |
| 84 | [ -n "$BLOCAL" ] || { echo "daemon B failed:"; cat "$WORK/b.log"; exit 1; } | |
| 85 | ||
| 86 | # Pattern-B ceremony: A (the writer) addWriters B's announced local key. | |
| 87 | bare abctl.js "$SA" "addwriter $BLOCAL" >/dev/null | |
| 88 | for _ in $(seq 1 60); do | |
| 89 | [ "$(bare abctl.js "$SB" writable)" = "true" ] && break | |
| 90 | sleep 0.1 | |
| 91 | done | |
| 92 | ./m0_roundtrip "$SA" m4 "$SB" | |
| 93 | for p in "$PA" "$PB"; do kill -9 "$p" 2>/dev/null || true; done | |
| 94 | pkill -9 -f "daemon-ab.js $SA" 2>/dev/null || true | |
| 95 | pkill -9 -f "daemon-ab.js $SB" 2>/dev/null || true | |
| 96 | PA=""; PB="" | |
| 97 | ||
| 98 | echo "M0-M4 ALL PASS" |