OverviewFilesCommitsIssuesDocsReleasesPeers

keet-daemon commits / a5bcc08

CPP-PORT-DESIGN: M5g-4e-2 done — extensions, inspect, session arrays, safety-catch; drill 17/17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a5bcc0861627d4170afd0ed04bae8e2fbb4d054f
by plankatron <fleeky@gmail.com> · 2026-10-04T17:01:23.000Z

Changed files (1) +85 -0

docs/CPP-PORT-DESIGN.md +85 -0
……@@ -2554,6 +2554,91 @@ SessionState's truncate / reorg / named-session machinery.
25542554 uses an identity `stylize`.
25552555 - Gates: pipe scenarios (register before and after the peer opens, a repeated name, an
25562556 unknown name, broadcast to 2 peers, close), and a core-gate `inspect` op.
2557 - **4e-2 design (2026-10-04).** Read in full:
2558 - hypercore 11.37: `registerExtension`, the extension gc in `_close`, `_open`'s
2559 session array, `_removeSession`, lib/inspect.js;
2560 - the replicator's `extension` / `onextension` / `_onpeerupdate`;
2561 - corestore 7.12.6's SessionTracker and `_ongc`;
2562 - compact-encoding 3.5.0's `buffer` / `from`;
2563 - protomux 3.12's `_ondata`.
2564 - **Extension encodings are byte-level** (the 4a decision). The default `c.buffer` is
2565 NOT a raw codec:
2566 - encode: varint length + bytes;
2567 - decode: varint ("Out of bounds" on empty input), a bounds check ("Out of bounds"),
2568 then the slice (trailing bytes ignored).
2569
2570 So it is a built-in `ValueEncoding` of its own. `c.from(name)` names reuse 4a's
2571 `ValueEncoding::from` (`'buffer'` there is binary, as fromNamed's default).
2572 - **A throw in `_onmessage`** (a decode, or the handler itself) reaches protomux's
2573 `_ondata` catch and destroys the STREAM. keetd: the message handler returns false with
2574 the error, and the mux's `_safe_destroy` follows, as for every other message.
2575 - **The peer-add order is interleaved:** `_onpeerupdate` walks the monitors newest
2576 first, and for EACH one emits 'peer-add' and then copies that session's extensions
2577 into `peer.extensions`. A name registered on several sessions ends up with the
2578 OLDEST session's extension. keetd's `emitMonitors` (all emits, then nothing) gets a
2579 per-session hook for this.
2580 - **Identity matters:** `destroy` deletes a peer's entry only if it is THIS extension
2581 (`peer.extensions.get(name) === ext`). Re-registering keeps the same object and
2582 replaces its handlers, encoding and session. Extensions live in an insertion-ordered
2583 map on the session (JS Map) and in `peer.extensions`. An extension holds its session
2584 weakly (`alive_`): JS's `session.peers` is `[]` once closed.
2585 - **`inspect` needs JS's session ARRAY** (`this.sessions`), which keetd lacks. It is
2586 shared by a parent and every session made from it, or comes from `opts.sessions`.
2587 Each opening session pushes itself (`_sessionIndex`), and `_removeSession` swap-pops
2588 it, then `ongc`. Corestore passes a PER-CORE array (SessionTracker: id → array; `_ongc`
2589 drops an empty one). keetd's corestore keeps one store-wide `std::set`, which also
2590 iterates in POINTER order on close (JS: map insertion order, then array order); it is
2591 replaced by the tracker.
2592 - `inspect(depth, { indentationLvl, stylize })`: lib/inspect.js's text, stylize called
2593 with JS's `String(value)` and the style name. `toHex(null)` prints `null`; at most 5
2594 peers, then `... and N more`.
2595 - **OWED (not 4e-2):**
2596 - the replicator's per-message wire stats (`stats.wire*` tx / rx, `wireExtension`
2597 among them): keetd's stats count only invalid data / requests / backoffs;
2598 - more than 5 peers in `inspect`, and broadcast to 2 peers: these need the multi-peer
2599 pipe harness (already owed by M5g-1e).
2600 - **Gates:**
2601 - **Pipe s20.** JS uploader `upload.extensions` echoes each message back on its
2602 extension, and `logExt` records the raw wire frames (name, `''` when deduped, and
2603 bytes). Script ops `registerExtension` / `extSend` / `extBroadcast` / `extDestroy` /
2604 `inspect`. The script registers before replicating (deferred), sends on c.buffer /
2605 utf8 / json, re-registers with a new encoding, sends on a name the remote doesn't
2606 have, destroys one and gets its echo dropped, closes a session (its extensions go),
2607 and inspects with a peer.
2608 - **Pipe s20b:** an undecodable message destroys the stream (both sides log
2609 `stream-error`).
2610 - **Core gate `inspect`** (no peers, `indentationLvl` 0 / 4, before and after open, a
2611 snapshot), plus the sessions count through child sessions and closes.
2612 - **DONE 2026-10-04** (keetd b6714cd + 6041179).
2613 - **Upstream fact found by s20b:** a throw in a message handler does NOT just destroy
2614 the stream. protomux's `_safeDestroy` runs **safety-catch** first, and that RETHROWS
2615 "programmer" errors as UNCAUGHT exceptions: TypeError, SyntaxError, ReferenceError,
2616 EvalError, RangeError, URIError, code ERR_ASSERTION, name AssertionError. It throws
2617 synchronously AND again in a microtask, and the stream stays up. So a peer sending
2618 invalid JSON on a json extension raises the uncaught exception twice (measured:
2619 `["SyntaxError", "SyntaxError"]`). A plain Error (c.buffer's "Out of bounds") still
2620 destroys the stream.
2621 - **User decision: faithful + hook.**
2622 - `hc::JsError` carries a JS class / code; `hcore::Error` is one, of class Error.
2623 - `isActuallyUncaught` is safety-catch's rule.
2624 - A process-wide `setUncaughtExceptionHandler` mirrors `Bare.on('uncaughtException')`;
2625 the default prints and aborts, as Bare does with no handler.
2626 - A handler throws `mux::Uncaught`; the mux calls the hook now and again deferred,
2627 and abandons the frame without destroying the stream.
2628 - **`Session::id()` is the core's id whether or not the session opened.** The inspect
2629 gate caught the old `opened ? … : null`.
2630 - Gates green: pipe s20 / s20b, core gate `inspect`, store gate `sessions`; ctest 64/64.
2631 - **Drill `m5g4e2`: 17/17 red.** The first run killed 15; its 2 survivors were gate
2632 gaps and became cases:
2633 - **e6 on two sessions:** a later registration overwrites the peer's entry, and
2634 destroying the earlier one must leave it (identity);
2635 - **store gate `sessions`:** a moved session leaves from its NEW slot, and
2636 store.close() walks the arrays.
2637 - **TRAP:** keetd's pipe runner made its script sessions bare on the core, so each had
2638 its OWN array, while peer.js's `store.get()` sessions share corestore's. Script
2639 sessions now share one array.
2640 - **Process note:** 4e-2's gates were written alongside the code, not run red first.
2641 The drill is the evidence that they are sensitive.
25572642 - **4e-3, the secret-stream layer** (a new `ss/` library, transport-agnostic: bytes in,
25582643 bytes out).
25592644 - The whole noise-handshake surface: NN / NNpsk0 / XX / XXpsk0 / IK / XK, psk,