| … | … | @@ -4219,6 +4219,54 @@ injected per layer: |
| 4219 | 4219 | the JS side of the core gate's hops mode (R4c-4d). |
| 4220 | 4220 | - **Not measured here:** `waitForRecovery` with a pending op — it waits on a replicator's recovery peers |
| 4221 | 4221 | (R4f's pipe scenarios). |
| 4222 | - **R4c-4c DONE 2026-10-07** (keetd 8bb9b66, 767bd8d, 1f0ce8d, 1eea9ec, 41ee215, 9673499, bb707dd): l3 is |
| 4223 | async, ported line by line from merkle-tree.js, and every caller uses it. |
| 4224 | - **4c-1:** `wire::Node` IS `hs::CoreTreeNode` (JS has one node object for storage and the wire), so a |
| 4225 | storage read's promise is l3's promise with no conversion hop. hc_l3 links the schema library. |
| 4226 | - **4c-2: the async API** (`hc/merkle.h`, `l3/src/merkle_tree.cpp`; the pure helpers shared through |
| 4227 | `merkle_detail.h`): |
| 4228 | - `Rx` (CoreRX: getters queue, `tryFlush`, `destroy`), `TreeStorage` (`read()`), `SessionView` (a session |
| 4229 | read LIVE: `SessionState` and `TreeBatch` implement it); |
| 4230 | - `TreeBatch` (MerkleTreeBatch / ReorgBatch: get, byteRange, byteOffset, downgrade, restore, seek, update + |
| 4231 | `_update` with JS's `startingDiff` identity check), `ByteSeeker`, `TreeProof` + `settle` / settleProof, |
| 4232 | and the statics. JS's two reader shapes (one rx per batch vs one per node); the proof's reads queued |
| 4233 | on the caller's rx in JS's push order; async vs plain functions kept; the NaN quirk. |
| 4234 | - **Gate `r4c_mt`** (tools/mt: gen-mt.js, mt-trace.js, mt-trace.cpp, mt-gate.sh): 6 scripts × real / |
| 4235 | virtual vs hypercore 11.37's merkle-tree.js over hypercore-storage, traces identical line for line |
| 4236 | (hops included). The seek-proof quirk (R4c-4b) reproduces by construction. Drill r4c4c 11/11; its |
| 4237 | first-run survivor was a script gap (no offset past the first root), fixed. |
| 4238 | - **4c-3: every caller on the async API**, inside `scaffold::Sync` scopes (the frontier rule): |
| 4239 | - `SessionState` is the live `merkle::SessionView`; `Core::session()` returns the state. |
| 4240 | - `hc/merkle_l2.h` (temporary, deleted at R4c-4d): `L2Rx` behaves as hypercore-storage's CoreRX does. |
| 4241 | Its getters (tree nodes and blocks) QUEUE in call order, `tryFlush` resolves them in that order, and a |
| 4242 | getter after the flush rejects with the freed view's TypeError. A first version resolved reads at the |
| 4243 | call: the replicator's path (JS awaits the proof BEFORE `tryFlush`) would then reject reads with no |
| 4244 | handler. Also `L2TreeStorage`, and `proveAndSettle` (core.js's checkConflict / `_repairTreeNodes` |
| 4245 | chain: proof, `tryFlush`, await, settle — one chain, so settle's reactions precede any checkpoint). |
| 4246 | - Reorg batches are `TreeBatchPtr` (Core::verifyReorg, the replicator's `f.batch`, `reorgUpdate`). |
| 4247 | - keetd's own ByteSeeker (core/byte_seeker.cpp) is deleted: `Session::seek` and the replicator's |
| 4248 | seeks use l3's port of merkle-tree.js's. |
| 4249 | - **JS shapes restored:** the replicator reads the block in the proof's own read batch and `destroy()`s |
| 4250 | the batch when generation throws (inventory sr:H15); `Session::proof` reads its block in the proof's |
| 4251 | batch (index.js `Hypercore#proof`); core-ops likewise. |
| 4252 | - The m3b / m5g1b vector tests drive the async API over the generators' kind of rx (settled at the call) |
| 4253 | on the virtual runtime: m3b serve 8742 / verify 14246, m5g1b 14969 cases, all pass. Harness rule: |
| 4254 | proof + settle in one coroutine. |
| 4255 | - **4c-4:** the synchronous readers are deleted (merkle.cpp 1021 → 387 lines); l3 keeps synchronous only |
| 4256 | what JS does (the batch's append / checkout / prune / clone / verifyUpgrade, maxMissingNodes, |
| 4257 | upgradeLength). |
| 4258 | - **Drills.** 44 anchors on changed code were re-anchored and re-drilled. Results: 40 red; 3 known |
| 4259 | survivors (m5g1b `_update`'s sibling-miss equivalent; m5g1d repair's check repeated by |
| 4260 | `checkConflict`; m5g1e's owed tree-cache test); and 1 new equivalent: 'range: offset lookups before |
| 4261 | size' — `Promise.all` settles with the longer chain (offset +1 hop), so creation order changes nothing, |
| 4262 | confirmed on the hop-trace gate. core's 'descends past an exact fit' (`>` → `>=`) is equivalent in l3's |
| 4263 | ByteSeeker (the `==` return precedes it) and is replaced by 'never descends into a larger left child' |
| 4264 | (red). ctest 90/90. |
| 4265 | - **Process lessons:** |
| 4266 | - Freezing a drill (SIGSTOP) that is a direct child of the tool's shell ends it: the shell reports it |
| 4267 | Stopped, moves on (a loop starts the next set beside it: two drills on one tree), and SIGHUPs it on |
| 4268 | exit. Drills run under a Python `subprocess.run` runner, and sets pause between runs via a pause file. |
| 4269 | - A `ps | grep "[d]rill.py"` inside a shell whose own command line contains "drill.py" self-matches. |
| 4222 | 4270 | - **Then 4e-4** (`createProtocolStream` / `replicate`): the first gate that composes the stack |
| 4223 | 4271 | is a COMPOSED trace against JS (secret-stream + protomux + replicator), now meaningful. |
| 4224 | 4272 | |