@@ -6,6 +6,14 @@ changed here — every item is localized to an adapter, a validation seam, a con
66the docs. Nothing touches the DAG shape, the four chain-building primitives, or the discovery
77algorithm.
88
9+ ## Overall status
10+
11+ ** Phases 1, 2, 4, 5, 7 are COMPLETE; Phase 3 is done except the v2 per-datagram MAC.** Every 🔴
12+ item and every cleanly-testable or minor 🟠/🟡/📄 item has landed. What remains is the larger
13+ reliability work: ** 3.2-v2** (a per-datagram MAC — the cryptographic upgrade over the v1
14+ source-address check) and ** Phase 6** (6.1 notification anti-entropy, 6.2 chain/datagram size
15+ bounds, 6.3 peer liveness, 6.4 IPv6, 6.5 log timestamps). 1.6 stays deferred (mooted by 1.1).
16+
917## Why now — the threat-model shift
1018
1119The code was written and tested against a ** trusted, lossless, single-operator** environment
@@ -92,7 +100,7 @@ guarantees directly.
92100** Status: partial.** 2.1 (reject empty signatures) and 2.2 (0600 key file) are DONE, along with
931012.x below (an upper-bound linkage soundness bug found while fixing 2.1); 2.3 (fail-loud key + backup writes)
94102is done, as is 2.5 (warn on unsigned mode) and 2.4 (128-bit NodeId — see plan/done/wider-node-id.md).
95- Only 2.6 (key scrub) remains. Full
103+ 2.6 (key scrub, OPENSSL_cleanse) is now done too — ** Phase 2 is complete. ** Full
96104suite + acceptance green.
97105
98106- [x] ** 2.1 — Reject unsigned/empty signatures in validation.** ` Ed25519KeyStore::verify ` returns
@@ -137,7 +145,7 @@ suite + acceptance green.
137145 unsigned (` --id ` ) — no cryptographic identity, and (since 2.1) its events verify under no signed
138146 peer; ` status ` already reports ` mode: signed|unsigned ` . (Kept ` --id ` for the simulation/test path
139147 rather than dropping it.)
140- - [ ] ** 2.6 — Scrub key material.** Raw private-key bytes pass through plain ` domain::Bytes ` in
148+ - [x ] ** 2.6 — Scrub key material.** Raw private-key bytes pass through plain ` domain::Bytes ` in
141149 ` keystore.cpp ` with no cleanse. ` OPENSSL_cleanse ` /` explicit_bzero ` before those buffers go out
142150 of scope. (Lower urgency than 2.1–2.3; include here for completeness.)
143151
@@ -149,8 +157,8 @@ failure and asserts `db-backup` reports `ERR`.
149157
150158## Phase 3 — Transport authentication & anti-DoS 🔴/🟠
151159
152- ** Status: 3.1, 3.2 (v1), 3.3 DONE. ** 3.4 (safe flood defaults) and 3.5 (socket buffers / getrandom
153- EINTR) remain .
160+ ** Status: 3.1, 3.2 (v1), 3.3, 3.4, 3.5 DONE. ** Only the v2 of 3.2 (a per-datagram MAC — the
161+ cryptographically strong upgrade over the source-address check) remains .
154162
155163- [x] ** 3.1 — Cap length prefixes before reserving.** DONE (landed in Phase 1 as the ` ensure_count `
156164 guard in [ codec.hpp] ( ../../src/core/wire/codec.hpp ) : ` refs ` /` node_ids ` /` chain ` reject a count
@@ -173,11 +181,11 @@ EINTR) remain.
173181 ** DONE (dedup):** exact-duplicate reverse edges are now skipped before append. A cap on
174182 * distinct* -but-spoofed edges (a forger varying the hash each time) is folded into 3.2 — only
175183 transport authentication stops an unauthenticated sender from minting distinct fake edges.
176- - [ ] ** 3.4 — Safe flood defaults.** With ` discovery_routing = flood ` , ` hop_limit ` , ` fanout ` , and
184+ - [x ] ** 3.4 — Safe flood defaults.** With ` discovery_routing = flood ` , ` hop_limit ` , ` fanout ` , and
177185 ` forward_cap ` all default to ` 0 ` = ** unlimited** ([ node.hpp:69] ( ../../src/core/node.hpp#L69 ) ). Give
178186 the flood policy non-zero safe defaults (bounded hop-limit, fan-out width, forward cap) so a single
179187 config flip cannot trigger a network-wide flood. The static (width-1) default stays as is.
180- - [ ] ** 3.5 — Socket robustness.** Set ` SO_RCVBUF ` /` SO_SNDBUF ` , and count (telemetry) inbound drops;
188+ - [x ] ** 3.5 — Socket robustness.** Set ` SO_RCVBUF ` /` SO_SNDBUF ` , and count (telemetry) inbound drops;
181189 handle ` getrandom ` ` EINTR ` with a retry rather than the fatal throw at
182190 [ rng.hpp:26] ( ../../src/adapters/os/rng.hpp#L26 ) .
183191
@@ -192,11 +200,12 @@ notifications do not grow `referencing_events`.
192200The one guarantee LOTI sells (time bounds) rides on ` CLOCK_REALTIME ` with no protection, on
193201hardware (Pi Zero) that has no RTC.
194202
195- ** Status: 4.2 + 4.3 DONE.** 4.1 deferred — it needs a monotonic * scheduling* clock decoupled from
196- the hashed timestamp (a scheduler/clock-wiring change, not unit-testable via the core harness);
197- tracked in the decision log.
203+ ** Status: Phase 4 COMPLETE.** 4.2 + 4.3 landed earlier; 4.1 is now done too — the reactor
204+ schedules timers on ` CLOCK_MONOTONIC ` (decoupled from the wall clock the Clock port hashes into
205+ timestamps), so a realtime clock jump no longer stalls or bunches up timers. ` ReactorScheduler `
206+ dropped its unused ` Clock ` reference.
198207
199- - [ ] ** 4.1 — Reactor timers on ` CLOCK_MONOTONIC ` .** ` Reactor::now_ns ` and ` WallClock ` both read
208+ - [x ] ** 4.1 — Reactor timers on ` CLOCK_MONOTONIC ` .** ` Reactor::now_ns ` and ` WallClock ` both read
200209 ` CLOCK_REALTIME ` ([ reactor.hpp:95] ( ../../src/adapters/os/reactor.hpp#L95 ) ,
201210 [ clock.hpp] ( ../../src/adapters/os/clock.hpp ) ). A backward NTP step stalls every pending timer; a
202211 forward step fires them in a burst. Schedule timers against ` CLOCK_MONOTONIC ` (keep ` CLOCK_REALTIME `
@@ -222,6 +231,12 @@ stored timestamps are non-decreasing and no inverted interval validates.
222231
223232## Phase 5 — Durability & operational correctness 🟠
224233
234+ ** Status: Phase 5 COMPLETE.** 5.1/5.2 (exception-safe prune, robust map growth) landed earlier;
235+ 5.3 (event storage is documented as unpruned + surfaced in ` db stat ` ), 5.4 (purge pending on
236+ client disconnect), 5.5 (full-hash lookups go through the index — short-prefix range-scan left as
237+ a further optimization), and 5.6 (the safe-mode fsync-on-reactor stall is documented; lazy mode
238+ moves it off the hot path) are all now done.
239+
225240- [x] ** 5.1 — ` prune_chain ` must not desync RAM from disk.** DONE: the loop is now read-only and the
226241 in-RAM ` chain_seqs_ ` tracker is trimmed only AFTER the delete transaction commits (was popped
227242 before the delete, so an I/O error mid-loop left RAM ahead of the aborted disk state, leaking
@@ -232,20 +247,20 @@ stored timestamps are non-decreasing and no inverted interval validates.
232247 ` grow_map() ` throws ` LmdbStoreFull ` at the 32-bit ceiling). Was a single retry — a record larger
233248 than one doubling threw uncaught → crash. Test: a 5 MiB single-record write into a 1 MiB map now
234249 succeeds (grows 1→2→4→8 MiB).
235- - [ ] ** 5.3 — Bound or prune published-event storage.** Only the clock chains are ring-pruned; the
250+ - [x ] ** 5.3 — Bound or prune published-event storage.** Only the clock chains are ring-pruned; the
236251 ` events ` /` event_index ` sub-DBs holding user content have ** no cap** — a publisher node grows disk
237252 without bound. Either document this as operator-driven (and surface it in ` db stat ` ) or add an
238253 event-retention policy. Note this qualifies the "storage stays flat" claim (see 📄).
239- - [ ] ** 5.4 — Key pending-discovery maps by a stable token, not a reusable fd.** ` lotid ` 's
254+ - [x ] ** 5.4 — Key pending-discovery maps by a stable token, not a reusable fd.** ` lotid ` 's
240255 ` pending_bounds_ ` /` chain_ ` /` order_ ` /` proof_ ` maps are keyed by raw ` int fd ` ; a client that
241256 disconnects mid-discovery leaves a stale entry, and OS fd-number reuse can misroute a later,
242257 unrelated client's reply. Key by a monotonic request id (or clear pending entries on
243258 ` close_client ` ). Confirm the misroute is reachable before fixing; add a regression test.
244- - [ ] ** 5.5 — Index-backed event lookup.** ` find_by_hash_prefix ` and ` event find ` linear-scan every
259+ - [x ] ** 5.5 — Index-backed event lookup.** ` find_by_hash_prefix ` and ` event find ` linear-scan every
245260 stored event ([ lotid.cpp:713] ( ../../src/app/lotid/lotid.cpp#L713 ) ); cost grows with the store over
246261 years. Use the ` clock_index_ ` /` event_index_ ` hash indices (range scan on prefix) instead of walking
247262 every record.
248- - [ ] ** 5.6 — fsync off the reactor thread (or document the stall).** Default ` safe ` sync fsyncs on
263+ - [x ] ** 5.6 — fsync off the reactor thread (or document the stall).** Default ` safe ` sync fsyncs on
249264 every commit on the single reactor thread, so a storage latency spike blocks all networking and
250265 control. Either move fsync to a helper thread that posts completion back to the reactor, or
251266 document that ` --store-sync-interval > 0 ` (lazy) is required for latency-sensitive deployments.
@@ -283,7 +298,9 @@ on-disk state stay consistent; a test drives a >2× single-record write and asse
283298Bring the docs in line with the code as it actually is (the review found many doc↔code and
284299doc↔doc conflicts). No code change; do this alongside the phases that settle each fact.
285300
286- ** Status: 7.1, 7.3, 7.4 DONE** (commit ` docs: reconcile store, retention, and socket-path claims ` );
301+ ** Status: Phase 7 COMPLETE** — 7.1/7.3/7.4 landed earlier; 7.2 (port is operator-chosen, `loti
302+ init` suggests 7000), 7.5 ( ` node start` / ` --reference` / ` config` flagged as planned), and 7.6
303+ (discoveries are best-effort; only clock-event storage is bounded) are now done. Earlier note:
287304` --rpc ` (part of 7.3) was already removed by a prior ` web: ` /` docs: ` commit. ** 7.2** (port numbers —
288305minor; all ports work, and ` loti init ` itself prints ` --port 7000 ` , so the 7000 quickstarts match
289306the code while cli.md's ` 4666 ` is the deferred config-file default) and ** 7.5 / 7.6** (unimplemented
@@ -297,7 +314,7 @@ concurrently-added proof-chain figure once that had landed on master.
297314 periodic full snapshot," but ` lotid ` instantiates the ** incremental LMDB store**
298315 ([ lotid.cpp:274] ( ../../src/app/lotid/lotid.cpp#L274 ) ). Update cli.md/paper-vs-impl to LMDB; align
299316 the ` --store ` file examples (` state.snap ` vs ` dag.mdb ` ).
300- - [ ] ** 7.2 — One port number.** Sim ` 666 ` , CLI default ` 4666 ` , quickstarts ` 7000 ` appear with no
317+ - [x ] ** 7.2 — One port number.** Sim ` 666 ` , CLI default ` 4666 ` , quickstarts ` 7000 ` appear with no
301318 reconciliation. Pick a default, use it consistently, and explain the sim's fixed ` 666 ` .
302319- [x] ** 7.3 — ` --control ` vs ` --rpc ` and the default socket path.** cli.md's canonical flag list
303320 names ` --rpc ` (used nowhere) while every example uses ` --control ` (listed nowhere); the default
@@ -310,10 +327,10 @@ concurrently-added proof-chain figure once that had landed on master.
310327 discovery), and the "prove-and-save within the horizon; the saved proof file is eternal" rule.
311328 Reconcile embedded.md's "~ 1–2 year 32-bit lifetime" (an old unbounded-growth artifact) with the
312329 bounded design. Define the undefined term ` a ` in the precision formula.
313- - [ ] ** 7.5 — Stop demoing unimplemented features.** cli.md worked examples use ` node start ` ,
330+ - [x ] ** 7.5 — Stop demoing unimplemented features.** cli.md worked examples use ` node start ` ,
314331 ` --reference <node> ` , and ` publish --sign/--salt/--wait ` that the same doc lists as deferred; and
315332 packet-format.md keeps a stale "fixed 61 B" chain-request size. Remove or mark clearly.
316- - [ ] ** 7.6 — Qualify absolute claims.** "mathematically prove … any digital event" vs discovery
333+ - [x ] ** 7.6 — Qualify absolute claims.** "mathematically prove … any digital event" vs discovery
317334 expiry/failure being first-class (exit code 5; ~ 83% completion in the sim); "storage stays flat"
318335 vs unbounded published-event content (5.3); the datagram/notary/"perfectly good" phrasing. Add the
319336 honest qualifiers this doc set already applies elsewhere.
0 commit comments