Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(leveva): record remote-originated channel messages into chat-history — P11 slice 276

The S2S relay's inbound channel fan (s2s::relay::inbound_message) delivered
a remote user's PRIVMSG/NOTICE to local members but never recorded it, so on
a multi-server network CHATHISTORY held only locally-sent lines (slice 274
recorded only the local delivery plane). Record the inbound line on this
server too, keyed by the carried network msgid (slice 130) + @time (slice
134), gated on this server having a local member of the channel (only a local
member can CHATHISTORY it; a hub relaying for a channel it has no local stake
in stores nothing). The +z op-redirect and non-channel branches never record,
mirroring the local plane; an inbound REDACT (slice 275) now deletes the
identical row everywhere.

Tests: s2s::relay units (recorded-under-carried-msgid, NOTICE, + three
inverses) + proptest (total/ordered recording); golden_s2s_chathistory.rs
(+ s2s_history.kdl) end-to-end through the real binary.

+410 -1
+1 -1
PLAN.md
··· 90 90 | **P8 — Delete the C + retire the oracle harness ✅ DONE** (per-sub-phase: [`PLAN-P8-progress.md`](PLAN-P8-progress.md) + [`docs/progress-log/p8.md`](docs/progress-log/p8.md)) | remaining C (the ~25 variadic sender trampolines + residual data globals) + `ircd-sys` C build + `ircd-golden` (L2) + `ircd-testkit`/`cref_*` archives (L1) + the frozen reference‑C tree | With all C _logic_ already Rust (P7 exit), delete the last C: replace the variadic sender trampolines with a non‑variadic Rust sender API at every call site (faithful `wire!`/`reply_one!`/`format!` first; leveva typed messages adopted alongside the IRC senders), drop the residual data globals via the data‑symbol seam (bindgen `extern` decls resolve to `#[no_mangle]` Rust defs at link), then drop **all** C compilation from `ircd-sys` (no more `cc::Build`), retire the differential harness (`cref_*`/L1 `ircd-testkit`/`ircd-golden` L2 can no longer be built once the oracle is gone), and **migrate the load‑bearing tests into `ircd-common`** as self‑contained Rust tests (own fixtures, no `cref_` oracle). **Progress (P8a–P8r — full entries in the progress logs):** **every variadic sender trampoline is now Rust** (P8a–P8m, incl. the keystones `sendto_one`/`sendto_flag`/`esendto_*`), and the five data‑global `.o`s are dropped outright (`s_auth.o` iauth globals P8n, `send.o` P8o, `s_bsd.o` `local[]`/`highest_fd`/… P8p, `ircd.o` `me`/`client`/timers/… P8q). **P8r dropped the last C‑logic TU `support.o`** — `dgets`/`make_isupport` ported to Rust + the `ipv6string`/`minus_one` data globals defined as `#[no_mangle]` statics (`snprintf_append` is dead in Rust — WHOX is field‑by‑field — so it died with the `.o`; `irc_sprintf` is deleted not ported, in P11). **Every ircd C TU is now Rust:** the link set holds only the generated `version.o` + the L1 harness's `ctruth.o`. **P8s migrated all 115 L1 `cref_` differentials into self‑contained `ircd-common` `insta` snapshot tests** (drive only the Rust port via `link_anchor()`, no oracle; soundness via the capture chain) — the suite is green + 0 warnings. **P8t ported the last *generated* C TU `version.c` to Rust** (`ircd-common/src/version.rs`): the data globals `generation`/`creation`/`pass_version`/`infotext`/`isupport` are now `#[no_mangle]` statics via the data‑symbol seam — `generation`/`creation` sourced from crate metadata at build time (`CARGO_PKG_VERSION` + an `ircd-common/build.rs` UTC build stamp), `pass_version`/`infotext` copied verbatim. `version.c.SH` generation + the `version.o` compile/archive are gone from `ircd-sys/build.rs`; `libircd_c.a` now holds **only** the L1 harness `ctruth.o`. (`creation` is now genuinely build‑time volatile, so the golden canonicalizer masks RPL_CREATED 003, matching the existing 371 Birth‑Date rule.) **P8u (FINAL) retired the oracle.** Ran the differential suite a last time green — L1 `ircd-testkit` all pass; L2 `ircd-golden` 86 pass + only the documented reference‑C‑garbage `s_serv_stats` flake (where Rust is the *correct* side) — then **dropped all C compilation** from `ircd-sys/build.rs`: the `make` object build, the `cref_*`/`ctruth.o`/`res.o` recompiles, the `libircd_c.a` archive, and the whole‑archive link + `-lz`/`-lm`/`-lcrypt` (none needed by Rust — `pow` resolves from glibc 2.29+). `build.rs` now only runs `configure` + bindgen over the C *headers* (the struct view `ircd-common` still uses until P9–P12) and expands the install‑path Makefile vars. `ircd-testkit` (L1) + `ircd-golden` (L2) are `exclude`d from the workspace (mothballed, git‑recoverable); the `ctruth.c`/`layout.rs` drift‑net is deleted. **The workspace is now 100% Rust — zero C TUs compiled.** **P8v (literal end of C) deleted the C *source tree* itself + retired `ircd-sys`:** froze the generated `bindings.rs` into a committed `ircd-common/src/bindings.rs` (a self‑alias `extern crate self as ircd_sys;` keeps every `ircd_sys::bindings::*` path resolving, zero src churn) + the install‑path consts as literals, then deleted `ircd-sys`, the mothballed `ircd-testkit`/`ircd-golden` oracle crates, the `iauth-rs` C‑iauth differential, and **all** C source (`common/ ircd/ iauth/ support/ contrib/ cbuild/ configure .clang-format* clangformat.yml`). `ircd-rs` calls `ircd_common::ircd::c_ircd_main` directly. **Not one line of C remains in the repo.** | **MET. Final differential run green; oracle mothballed; `cargo build --workspace` 0 warnings links pure Rust; `cargo test --workspace` 695 pass / 0 fail (P8v; was 698 before deleting the C‑oracle tests); the `ircd-common` snapshot tests carry the reference‑C correctness forward. C source tree deleted — repo is 100% Rust at the file level.** | 91 91 | **P9 — std‑library cleanup ❌ RETIRED (2026‑06‑08)** | — (was: whole Rust tree) | **Skipped by decision — superseded by the `leveva` greenfield.** P9 assumed an *in‑place* idiomatic transformation of the mechanical port (`MyMalloc`/`MyFree`→`Box`/`Vec`, `dbuf` freelist→owned, raw `libc`→`std`/`nix`, `[c_char;N]`→`[u8;N]`). Because the idiomatic end‑state is now a separate greenfield crate (`leveva`), `ircd-common` is throwaway code (the oracle, deleted at parity), so polishing it earns nothing. Nothing was implemented; the row is kept for provenance. | n/a — retired. | 92 92 | **P10 — `leveva` greenfield foundations 🔶 IN PROGRESS** ([`docs/progress-log/p10.md`](docs/progress-log/p10.md)) | new `leveva` crate (lib) + `leveva-integration` differential tests | The idiomatic‑Rust product, built from scratch (not transformed from the port). **Done so far:** RFC 1459 case‑folding (`casemap`); typed identifier strings (`IrcStr`/`IrcString` + `Nick`/`ChanName`/`Uid`/`Sid`/`Cid`/`ServerName`/`UserName`/`HostName` validating newtypes via `ident_newtype!`); `Message`/`MessageBuilder`; the full `Numeric` reply/error enum (184 codes, discriminant = wire code); a generic safe `patricia` trie; glob `matching`; `mode`; KDL `config` (model/parse/password/privilege); `rustls`/`tokio` `tls` endpoint with hot reload; `ident`. The async boot path (`main.rs`) binds listeners + watches certs but currently drops accepted connections — no protocol layer yet. Idiomatic Rust allowed everywhere; pulls real crates (tokio/rustls/kdl/nom/clap). | `cargo test -p leveva` green + clippy clean per module; `leveva-integration` pins each greenfield reimplementation against `ircd-common` (the oracle) within their shared domain (documented divergences asserted explicitly). | 93 - | **P11 — `leveva` protocol layer → feature parity 🔶 IN PROGRESS** ([`docs/progress-log/p11.md`](docs/progress-log/p11.md)) | `leveva` (client/server state machine + handlers) | The bulk of the remaining work: the connection + registration state machine, the post‑registration command handlers (JOIN/PRIVMSG/MODE/KICK/TOPIC/WHO/WHOIS/…), channels + channel/user modes, the S2S link/burst protocol (UID‑based: UNICK/NJOIN/SAVE/EOB), and `leveva-iauth` (native async auth — dnsbl/socks/webproxy/pipe/ident). Each slice is built idiomatically (owned per‑connection state, typed identifiers end‑to‑end, `Result`/`thiserror`, `format!`/typed builders — **no** `sprintf`/global `buf`), unit‑tested + boot‑golden + proptest‑fuzzed, and **differentially pinned against `ircd-common`** (or `iauth-rs` for auth) wherever a pure oracle entry point exists — structural skeleton + documented divergences, since leveva emits a clean modern burst rather than a byte copy. **Done: 275 slices** (275 = IRCv3 `message-redaction` now deletes from chat-history — an accepted channel `REDACT <chan> <msgid>` deletes the slice-274 stored line (`History::delete_by_msgid`, keyed `(target, msgid)`) so `CHATHISTORY` can never replay it; wired in both the local `CanSend::Ok` path and inbound `ENCAP * REDACT`, per-server; mirrors recording — a `+z` op-moderated/"shown to ops" attempt is never recorded so never deleted; authorization stays a stateless relay; 274 = IRCv3 `draft/chathistory` — SQLite-backed (`rusqlite`, bundled) channel message history behind an optional `ircd.kdl` `database {}` block; all channels auto-enrolled (PRIVMSG/NOTICE + JOIN/PART/QUIT recorded at the delivery plane), joining clients auto-replayed `replay-lines` recent lines unless they negotiated `draft/chathistory` (then exempt, per spec), full `CHATHISTORY LATEST/BEFORE/AFTER/BETWEEN/AROUND/TARGETS` (timestamp=/msgid= selectors) framed as a `chathistory` batch, `draft/chathistory`+`draft/event-playback` dynamic caps + `CHATHISTORY`/`MSGREFTYPES` ISUPPORT, age-based `retention-days` prune ticker; `Message` gained serde; 273 = registration-time nick `RESV` enforcement — a connection may no longer *register* directly as a reserved nick, only the post-registration `NICK`/JOIN were gated before; refuse `432` + reopen registration, mirroring the `433` nick-in-use path; 272 = charybdis `RESV`/`UNRESV` nick/channel reservation — JOIN→`437`/NICK→`432` gates, opers exempt, new `OperPrivilege::Resv`; defaults declared in `resv {}` config blocks, boot-seeded + REHASH-re-seeded keeping runtime `RESV`s). The slices span the full client command surface (registration; channels + the complete channel-mode surface; WHO/WHOIS/WHOX; OPER/STATS; and the query/util commands incl. OPERWALL/LOCOPS/USERIP); the UID-based S2S link/burst/netsplit protocol (keepalive, live introduction, NJOIN chunking, TS merge arbitration); `leveva-iauth` (dnsbl/socks/webproxy/pipe/ident); config live-rehash across every block; CertFP; the **IRCv3 cap track** (message-tags/server-time/msgid/account/bot/oper/echo/labeled-response/CAP, `draft/metadata-2`, caller-id `+g`/`+G`, the **STS/TLS** sub-track); elemental channel modes + extbans; KNOCK; command-rate flood protection (fakelag); the **complete charybdis channel-mode set** (`+C/+c/+S/+j/+z/+T/+g/+r/+f/+Q/+F/+P/+L`, with `+L` the last standard one) and **user-mode set** (`+D/+R/+Q/+G/+z/+S/+l`, plus the product decision that leveva has no local-only umodes — `SEND_UMODES` is every umode); and the `+s` server-notice **snomask** (a charybdis category mask with a producer wired for every category — the matrix is complete). Most const limits (`max-penalty`/`max-bans`/`service-string`/`default-snomask`/…) have been promoted to REHASH-able `options { … }` knobs. **Full per-slice detail (scope, mechanism, divergences, gate) for every slice lives in [`docs/progress-log/p11.md`](docs/progress-log/p11.md); the standing testing plan is [`docs/superpowers/specs/2026-06-10-leveva-testing-plan.md`](docs/superpowers/specs/2026-06-10-leveva-testing-plan.md). This row stays thin.** | Per‑slice: leveva boots and serves the slice; leveva golden snapshot + proptest green; the `leveva-integration` differential agrees with `ircd-common` on the shared skeleton with divergences asserted. Across the eventual matrix: registration, channels, WHO/WHOIS/WHOX, OPER/STATS, server burst, netsplit, rehash, restart; iauth end‑to‑end. | 93 + | **P11 — `leveva` protocol layer → feature parity 🔶 IN PROGRESS** ([`docs/progress-log/p11.md`](docs/progress-log/p11.md)) | `leveva` (client/server state machine + handlers) | The bulk of the remaining work: the connection + registration state machine, the post‑registration command handlers (JOIN/PRIVMSG/MODE/KICK/TOPIC/WHO/WHOIS/…), channels + channel/user modes, the S2S link/burst protocol (UID‑based: UNICK/NJOIN/SAVE/EOB), and `leveva-iauth` (native async auth — dnsbl/socks/webproxy/pipe/ident). Each slice is built idiomatically (owned per‑connection state, typed identifiers end‑to‑end, `Result`/`thiserror`, `format!`/typed builders — **no** `sprintf`/global `buf`), unit‑tested + boot‑golden + proptest‑fuzzed, and **differentially pinned against `ircd-common`** (or `iauth-rs` for auth) wherever a pure oracle entry point exists — structural skeleton + documented divergences, since leveva emits a clean modern burst rather than a byte copy. **Done: 276 slices** (276 = remote-originated channel messages are now recorded into chat-history — the S2S relay's inbound channel fan (`s2s::relay::inbound_message`) records the delivered line on this server too, keyed by the carried network msgid (slice 130) + `@time` (slice 134), gated on this server having a local member of the channel (only a local member can `CHATHISTORY` it; no whole-network accumulation on a hub); the `+z` op-redirect/non-channel branches never record, mirroring the local plane; closes the slice-274 gap where `CHATHISTORY` held only locally-sent lines; 275 = IRCv3 `message-redaction` now deletes from chat-history — an accepted channel `REDACT <chan> <msgid>` deletes the slice-274 stored line (`History::delete_by_msgid`, keyed `(target, msgid)`) so `CHATHISTORY` can never replay it; wired in both the local `CanSend::Ok` path and inbound `ENCAP * REDACT`, per-server; mirrors recording — a `+z` op-moderated/"shown to ops" attempt is never recorded so never deleted; authorization stays a stateless relay; 274 = IRCv3 `draft/chathistory` — SQLite-backed (`rusqlite`, bundled) channel message history behind an optional `ircd.kdl` `database {}` block; all channels auto-enrolled (PRIVMSG/NOTICE + JOIN/PART/QUIT recorded at the delivery plane), joining clients auto-replayed `replay-lines` recent lines unless they negotiated `draft/chathistory` (then exempt, per spec), full `CHATHISTORY LATEST/BEFORE/AFTER/BETWEEN/AROUND/TARGETS` (timestamp=/msgid= selectors) framed as a `chathistory` batch, `draft/chathistory`+`draft/event-playback` dynamic caps + `CHATHISTORY`/`MSGREFTYPES` ISUPPORT, age-based `retention-days` prune ticker; `Message` gained serde; 273 = registration-time nick `RESV` enforcement — a connection may no longer *register* directly as a reserved nick, only the post-registration `NICK`/JOIN were gated before; refuse `432` + reopen registration, mirroring the `433` nick-in-use path; 272 = charybdis `RESV`/`UNRESV` nick/channel reservation — JOIN→`437`/NICK→`432` gates, opers exempt, new `OperPrivilege::Resv`; defaults declared in `resv {}` config blocks, boot-seeded + REHASH-re-seeded keeping runtime `RESV`s). The slices span the full client command surface (registration; channels + the complete channel-mode surface; WHO/WHOIS/WHOX; OPER/STATS; and the query/util commands incl. OPERWALL/LOCOPS/USERIP); the UID-based S2S link/burst/netsplit protocol (keepalive, live introduction, NJOIN chunking, TS merge arbitration); `leveva-iauth` (dnsbl/socks/webproxy/pipe/ident); config live-rehash across every block; CertFP; the **IRCv3 cap track** (message-tags/server-time/msgid/account/bot/oper/echo/labeled-response/CAP, `draft/metadata-2`, caller-id `+g`/`+G`, the **STS/TLS** sub-track); elemental channel modes + extbans; KNOCK; command-rate flood protection (fakelag); the **complete charybdis channel-mode set** (`+C/+c/+S/+j/+z/+T/+g/+r/+f/+Q/+F/+P/+L`, with `+L` the last standard one) and **user-mode set** (`+D/+R/+Q/+G/+z/+S/+l`, plus the product decision that leveva has no local-only umodes — `SEND_UMODES` is every umode); and the `+s` server-notice **snomask** (a charybdis category mask with a producer wired for every category — the matrix is complete). Most const limits (`max-penalty`/`max-bans`/`service-string`/`default-snomask`/…) have been promoted to REHASH-able `options { … }` knobs. **Full per-slice detail (scope, mechanism, divergences, gate) for every slice lives in [`docs/progress-log/p11.md`](docs/progress-log/p11.md); the standing testing plan is [`docs/superpowers/specs/2026-06-10-leveva-testing-plan.md`](docs/superpowers/specs/2026-06-10-leveva-testing-plan.md). This row stays thin.** | Per‑slice: leveva boots and serves the slice; leveva golden snapshot + proptest green; the `leveva-integration` differential agrees with `ircd-common` on the shared skeleton with divergences asserted. Across the eventual matrix: registration, channels, WHO/WHOIS/WHOX, OPER/STATS, server burst, netsplit, rehash, restart; iauth end‑to‑end. | 94 94 | **P12 — Retire the mechanical port; `leveva` is the product ✅ DONE (2026‑06‑13)** ([`docs/progress-log/p12.md`](docs/progress-log/p12.md)) | deleted `ircd-common`/`ircd-rs` + C‑era `iauth-rs` + `leveva-integration` + the mechanical port's deployment rigging; workspace = `leveva` + `leveva-iauth` | Ran the differential suite a **final time green** (`cargo test -p leveva-integration`, skeleton‑identical to the oracle), then **deleted the mechanical port** (`ircd-common`, `ircd-rs`), its C‑era auth scaffolding (`iauth-rs`, long since subsumed by the native `leveva-iauth`), and the oracle differential crate (`leveva-integration`). The oracle tests retired with their oracle — the load‑bearing behavior was already carried by leveva's self‑contained 212‑file golden + proptest suite (verified: leveva/leveva-iauth have zero Cargo/`use` dep on the deleted crates). Also stripped the mechanical port's **deployment rigging** (`docker/Dockerfile.ircd`, the `ircd` bake target, the entire `docs/k8s/` example network) and updated the README to describe the finished strangler. This is the literal end of the strangler — same lifecycle the C tree had at P8. | **MET. Final differential run green; `ircd-common`/`ircd-rs`/`iauth-rs`/`leveva-integration` deleted; `cargo build --workspace` 0 warnings; `cargo clippy --workspace --tests` clean; `cargo test -p leveva -p leveva-iauth` green — the behavioral guarantees carry forward. The workspace is now `leveva` + `leveva-iauth`.** | 95 95 96 96 ---
+35
docs/progress-log/p11.md
··· 15160 15160 `redact_proptest.rs` gained two command-path fuzzes (`redact_deletes_exactly_the_named_channel_msgids` 15161 15161 via a real `Session`, + `rejected_channel_redact_never_deletes` — a non-member of a `+m` channel 15162 15162 deletes nothing). `cargo clippy -p leveva --tests` clean; full `cargo test -p leveva` green. 15163 + 15164 + - **2026-06-19 — P11 slice 276 (record remote-originated channel messages into chat-history) merged.** 15165 + Closes the deferred follow-on implied by slice 274: history was recorded only on the **local** 15166 + delivery plane (`command::message`), so a remote user's channel line — delivered to local members 15167 + by the S2S relay but never stored — was missing from `CHATHISTORY` on a multi-server network. 15168 + - **Gap.** `s2s::relay::inbound_message`'s `is_channel(target)` branch fanned the remote 15169 + PRIVMSG/NOTICE to local members (`fanout_local_render`) but did not record it. Each server's 15170 + history therefore held only its own clients' lines. 15171 + - **Fix (`s2s/relay.rs`).** After the fan, record the line keyed by the **carried** network msgid 15172 + (slice 130 — every server stores the identical id, so an inbound `ENCAP * REDACT` (slice 275) 15173 + deletes the identical row everywhere; a peer that omits it → mint locally) and the carried 15174 + `@time` parsed back to nanos (slice 134, millisecond resolution, consistent cross-server; falls 15175 + back to our clock). The stored base mirrors the local plane: no server tags, client-only (`+`) 15176 + tags kept, prefix = the remote user's resolved display mask. 15177 + - **Local-stake gate (`has_local_member`).** Record only when this server has ≥1 **local** member 15178 + of the channel — only a local member can `CHATHISTORY` it, so a hub merely relaying the line for 15179 + a channel it has no local clients in stores nothing (no whole-network accumulation on a hub). 15180 + The local originating plane always satisfies this (the sender is a local member). 15181 + - **The semantic boundary.** Only the full-delivery `is_channel` branch records — the `+z` 15182 + op-redirect (`@<chan>`) branch never does (mirrors the local "an op-moderated message is shown 15183 + to ops but never stored", so a later REDACT has nothing to delete and CHATHISTORY never replays 15184 + it), and the nick/mask targets are channel-history-exempt (chat-history is channel-only; DMs 15185 + out of scope by the project hard rule). 15186 + - **Tests (RED first).** `s2s/relay.rs` units — recorded-under-carried-msgid (no server tags 15187 + stored, ts = parsed carried `@time`), NOTICE recorded, + three inverses (no local member / 15188 + `+z` op-redirect / non-channel PM → not recorded); proptest 15189 + `inbound_channel_recording_is_total_and_ordered` (arbitrary run records exactly one row each in 15190 + arrival order under its carried id). `golden_s2s_chathistory.rs` (+ `fixtures/s2s_history.kdl`, 15191 + a database-enabled s2s fixture) — end-to-end through the real binary: a scripted peer bursts 15192 + `rover` into a shared `#hub`, `rover` speaks, local `alice` sees the line via `CHATHISTORY 15193 + LATEST` sourced from rover under the carried msgid. 15194 + - **Plan:** [`docs/superpowers/plans/2026-06-19-p11-slice276-s2s-chathistory-recording.md`](../superpowers/plans/2026-06-19-p11-slice276-s2s-chathistory-recording.md). 15195 + - **Gate.** `cargo test -p leveva --lib s2s::relay` + `--test golden_s2s_chathistory 15196 + --test golden_chathistory --test relay_proptest` green; `cargo clippy -p leveva --tests` clean; 15197 + `cargo build --workspace` 0 warnings.
+55
docs/superpowers/plans/2026-06-19-p11-slice276-s2s-chathistory-recording.md
··· 1 + # P11 slice 276 — record remote-originated channel messages into chat-history (S2S inbound recording) 2 + 3 + ## Problem 4 + 5 + Slice 274 records chat-history only on the **local** delivery plane (`command::message`, the 6 + `CanSend::Ok` arm) — i.e. only for channel lines a *local* client sent. A remote user's 7 + PRIVMSG/NOTICE, delivered to local members by the S2S relay 8 + (`s2s::relay::inbound_message`, the `is_channel(target)` branch), was delivered but **never 9 + recorded**. So on a multi-server network `CHATHISTORY` on a given server contained only its own 10 + clients' lines — remote-originated lines were silently missing. This is the deferred follow-on 11 + implied by slice 274's "recorded at the delivery plane" framing being applied only to the local 12 + plane. 13 + 14 + ## Decision 15 + 16 + Record the inbound channel line on this server too, mirroring the local recording plane: 17 + 18 + - **Where.** `s2s::relay::inbound_message`, immediately after `fanout_local_render` in the 19 + `is_channel(target)` branch only. The `+z` op-redirect (`@<chan>`) branch and the nick/mask 20 + branches never record (mirrors the local "an op-moderated message is shown to ops but never 21 + stored", and chat-history is channel-only — DMs are out of scope by the project hard rule). 22 + - **Stored base.** A fresh `Message` with no server tags — `prefix(&mask)` (the remote user's 23 + resolved display mask), the verb, the channel param, the trailing text, plus the sender's 24 + client-only (`+`) tags when present — exactly the shape the local plane stores. Replay re-adds 25 + `@msgid`/`@time` per recipient. 26 + - **Network identity.** Keyed by the **carried** network msgid (slice 130): the originating 27 + leveva server mints it and rides it on the wire (`@msgid`), so every server stores the identical 28 + id and an inbound `ENCAP * REDACT` (slice 275) deletes the identical row everywhere. A peer that 29 + omits the msgid → mint locally (no canonical network id exists). Receive time is the carried 30 + `@time` parsed back to nanos (slice 134, millisecond resolution; consistent across servers), 31 + falling back to our own clock. 32 + - **Local-stake gate.** Record only when this server has at least one **local** member of the 33 + channel (`has_local_member`): only a local member can `CHATHISTORY` it, so a hub merely relaying 34 + the line for a channel it has no local clients in stores nothing (avoids a hub accumulating the 35 + whole network's traffic). The local originating plane always satisfies this (the sender is a 36 + local member), so this is the faithful cross-server extension. 37 + 38 + ## Tests (RED first) 39 + 40 + - Unit (`s2s/relay.rs`): a remote channel PRIVMSG to a channel with a local member is recorded 41 + under the carried msgid/time/body with no server tags; a NOTICE is recorded too; **inverses** — 42 + no local member ⇒ not recorded, `+z` op-redirect ⇒ not recorded, a remote PM (non-channel) ⇒ not 43 + recorded. 44 + - Proptest (`s2s/relay.rs`): an arbitrary run of remote channel PRIVMSGs records exactly one row 45 + each, in arrival order, each under its carried id (recording is total over the body/msgid domain, 46 + never drops/reorders). 47 + - Golden (`golden_s2s_chathistory.rs` + `fixtures/s2s_history.kdl`): end-to-end through the real 48 + binary — a scripted peer bursts `rover` into a shared `#hub`, `rover` speaks, and local `alice` 49 + sees the line via `CHATHISTORY LATEST` sourced from rover under the carried msgid. 50 + 51 + ## Gate 52 + 53 + `cargo test -p leveva --lib s2s::relay` + `--test golden_s2s_chathistory --test golden_chathistory 54 + --test relay_proptest` green; `cargo clippy -p leveva --tests` clean; `cargo build --workspace` 0 55 + warnings.
+7
leveva/proptest-regressions/s2s/relay.txt
··· 1 + # Seeds for failure cases proptest has generated in the past. It is 2 + # automatically read and these particular cases re-run before any 3 + # novel cases are generated. 4 + # 5 + # It is recommended to check this file in to source control so that 6 + # everyone who runs the test benefits from these saved cases. 7 + cc b08a158ef83928ba76f82ea83235060d863dbfd55f239a47d6b21f73936df112 # shrinks to bodies = ["A"]
+220
leveva/src/s2s/relay.rs
··· 663 663 prefix.to_string() 664 664 } 665 665 666 + /// Whether this server has at least one **local** member of `chan` — the gate for recording an 667 + /// inbound (remote-originated) channel line into chat history (slice 276): only a local member can 668 + /// query it, so a server merely relaying the line for a channel it has no local stake in records 669 + /// nothing. 670 + fn has_local_member(ctx: &ServerContext, chan: &str) -> bool { 671 + ctx.channels 672 + .members(chan) 673 + .is_some_and(|ms| ms.iter().any(|m| ctx.is_local_uid(m))) 674 + } 675 + 666 676 /// Deliver `wire` (client form) to every **local** member of `chan`, optionally skipping 667 677 /// `skip`. Remote members are not pushed here — they are served by the onward relay. 668 678 pub(super) fn fanout_local(ctx: &ServerContext, chan: &str, skip: Option<&Uid>, wire: &[u8]) { ··· 867 877 .tag("time") 868 878 .filter(|t| crate::clock::is_valid_server_time(t)) 869 879 .unwrap_or(&fallback_time); 880 + // Chat history (slice 276): the receive instant under which this remote-originated channel 881 + // line is recorded on *this* server. Parse the carried network-stable `@time` (slice 134) 882 + // back to nanos so every server stores one consistent timestamp (millisecond resolution); 883 + // fall back to our own clock when the peer sent none. Computed once — it is a property of the 884 + // message, not of an individual channel target. 885 + let record_nanos = msg 886 + .tag("time") 887 + .filter(|t| crate::clock::is_valid_server_time(t)) 888 + .and_then(crate::clock::parse_rfc3339_millis) 889 + .unwrap_or_else(crate::clock::unixnano); 870 890 for target in targets.split(',').filter(|t| !t.is_empty()) { 871 891 // Render the client-form line for a local recipient by its caps: `server-time` adds 872 892 // `@time` (independent of message-tags), `message-tags` adds the carried `@msgid` + the ··· 945 965 fanout_local_ops_render(ctx, &chan, render_chan); 946 966 } else if is_channel(target) { 947 967 fanout_local_render(ctx, target, None, render); 968 + // Chat history (slice 276): record the remote-originated channel line on this server 969 + // too, so `CHATHISTORY` here is complete on a multi-server network — not just for 970 + // locally-sent lines (`command::message`). Gated on this server having a **local** 971 + // member of the channel: only a local member can `CHATHISTORY` it, so a hub merely 972 + // relaying the line stores nothing. The stored base mirrors the local plane — no 973 + // server tags, client-only (`+`) tags kept — keyed by the carried network msgid 974 + // (slice 130) so every server shares one id (an inbound REDACT, slice 275, then 975 + // deletes the identical row everywhere); a peer that omits the msgid → mint locally. 976 + // The `+z` op-redirect branch above never records, mirroring the local "an 977 + // op-moderated message is shown to ops but never stored". 978 + if ctx.history.enabled() && has_local_member(ctx, target) { 979 + let mut b = Message::builder(verb).prefix(&mask); 980 + for (k, v) in &cotags { 981 + b = b.tag(k, v); 982 + } 983 + let stored = b.param(target).trailing(text).build(); 984 + let id = match in_msgid { 985 + Some(id) => id.to_string(), 986 + None => crate::msgid::generate(ctx.uids.sid().as_str()), 987 + }; 988 + ctx.history.record(target, record_nanos, &id, verb, &stored); 989 + } 948 990 } else if let Some(uid) = ctx.registry.uid_of(target) { 949 991 // Only a *local* nick is delivered here; a remote target is reached by the onward 950 992 // relay below (it lives behind another peer). ··· 1331 1373 use crate::mode::ChanMode; 1332 1374 use crate::registry::Envelope; 1333 1375 use crate::s2s::network::{RemoteServer, RemoteUser}; 1376 + use proptest::prelude::*; 1334 1377 use std::sync::Arc; 1335 1378 use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; 1336 1379 ··· 1339 1382 server { name "leveva.test"; description "Leveva Test"; sid "0ABC" } 1340 1383 admin { name "A"; email "a@test"; network "Net" } 1341 1384 class "c" { ping-freq 90; max-links 10; sendq 1000 } 1385 + "#; 1386 + let cfg = crate::config::Config::from_kdl(kdl).expect("valid test config"); 1387 + ServerContext::from_config(&cfg, "now".to_string(), None) 1388 + } 1389 + 1390 + /// As [`ctx`] but with an in-memory chat-history store — used by the slice-276 inbound 1391 + /// recording tests (a remote-originated channel line is recorded on this server too). 1392 + fn ctx_hist() -> Arc<ServerContext> { 1393 + let kdl = r#" 1394 + server { name "leveva.test"; description "Leveva Test"; sid "0ABC" } 1395 + admin { name "A"; email "a@test"; network "Net" } 1396 + class "c" { ping-freq 90; max-links 10; sendq 1000 } 1397 + database { path ":memory:"; replay-lines 5; max-query 50 } 1342 1398 "#; 1343 1399 let cfg = crate::config::Config::from_kdl(kdl).expect("valid test config"); 1344 1400 ServerContext::from_config(&cfg, "now".to_string(), None) ··· 3612 3668 ); 3613 3669 assert_eq!(next(&mut a_rx), ":rover!rover@rh PRIVMSG alice :hi\r\n"); 3614 3670 assert!(peer_rx.try_recv().is_err()); 3671 + } 3672 + 3673 + // ---- chat-history: inbound (remote-originated) channel recording (slice 276) ---- 3674 + 3675 + /// A remote user's channel PRIVMSG that this server delivers to a local member is also 3676 + /// recorded into its own chat-history, keyed by the **carried** network msgid (slice 130) and 3677 + /// receive time (slice 134) — so `CHATHISTORY` here is complete on a multi-server net, not just 3678 + /// for locally-sent lines. The stored base carries no server tags (replay re-adds them). 3679 + #[test] 3680 + fn inbound_channel_message_is_recorded_under_the_carried_msgid() { 3681 + let ctx = ctx_hist(); 3682 + let (mut link, _peer_rx) = link_peer(&ctx); 3683 + introduce(&ctx, &link, "0XYZAAAAA", "rover"); 3684 + let (alice, mut a_rx) = local_client(&ctx, "0ABCAAAAA", "alice"); 3685 + ctx.channels.join("#rust", &uid("0XYZAAAAA"), 0); 3686 + ctx.channels.join("#rust", &alice, 0); 3687 + 3688 + inbound_message( 3689 + &mut link, 3690 + &parse( 3691 + "@msgid=0XYZ-deadbeef;time=2026-06-19T12:00:00.000Z \ 3692 + :0XYZAAAAA PRIVMSG #rust :hello from afar", 3693 + ), 3694 + &ctx, 3695 + "PRIVMSG", 3696 + ); 3697 + 3698 + // Delivered to the local member. 3699 + assert_eq!(next(&mut a_rx), ":rover!rover@rh PRIVMSG #rust :hello from afar\r\n"); 3700 + // And recorded under the carried network id, command, and body — no server tags stored. 3701 + let events = ctx.history.latest("#rust", None, 10); 3702 + assert_eq!(events.len(), 1, "the remote line was recorded once"); 3703 + assert_eq!(events[0].msgid, "0XYZ-deadbeef"); 3704 + assert_eq!(events[0].command, "PRIVMSG"); 3705 + assert_eq!(events[0].message.trailing(), Some("hello from afar")); 3706 + assert_eq!(events[0].message.prefix(), Some("rover!rover@rh")); 3707 + assert!( 3708 + events[0].message.tag("msgid").is_none() && events[0].message.tag("time").is_none(), 3709 + "stored base carries no server tags" 3710 + ); 3711 + // Receive time = the carried @time parsed back to nanos (millisecond resolution). 3712 + assert_eq!( 3713 + events[0].ts_nanos, 3714 + crate::clock::parse_rfc3339_millis("2026-06-19T12:00:00.000Z").unwrap() 3715 + ); 3716 + } 3717 + 3718 + /// A remote NOTICE is recorded too (parity with the local plane, which records both verbs). 3719 + #[test] 3720 + fn inbound_channel_notice_is_recorded() { 3721 + let ctx = ctx_hist(); 3722 + let (mut link, _peer_rx) = link_peer(&ctx); 3723 + introduce(&ctx, &link, "0XYZAAAAA", "rover"); 3724 + let (alice, _a_rx) = local_client(&ctx, "0ABCAAAAA", "alice"); 3725 + ctx.channels.join("#rust", &alice, 0); 3726 + 3727 + inbound_message( 3728 + &mut link, 3729 + &parse(":0XYZAAAAA NOTICE #rust :heads up"), 3730 + &ctx, 3731 + "NOTICE", 3732 + ); 3733 + let events = ctx.history.latest("#rust", None, 10); 3734 + assert_eq!(events.len(), 1); 3735 + assert_eq!(events[0].command, "NOTICE"); 3736 + assert_eq!(events[0].message.trailing(), Some("heads up")); 3737 + } 3738 + 3739 + /// Inverse — a channel with **no local member** is not recorded here: this server has no 3740 + /// stake (only a local member can `CHATHISTORY` it), so a hub relaying the line stores nothing. 3741 + #[test] 3742 + fn inbound_channel_message_with_no_local_member_is_not_recorded() { 3743 + let ctx = ctx_hist(); 3744 + let (mut link, _peer_rx) = link_peer(&ctx); 3745 + introduce(&ctx, &link, "0XYZAAAAA", "rover"); 3746 + // The only member is the remote sender (a passing-through relay, no local interest). 3747 + ctx.channels.join("#rust", &uid("0XYZAAAAA"), 0); 3748 + 3749 + inbound_message( 3750 + &mut link, 3751 + &parse(":0XYZAAAAA PRIVMSG #rust :nobody local here"), 3752 + &ctx, 3753 + "PRIVMSG", 3754 + ); 3755 + assert!( 3756 + ctx.history.latest("#rust", None, 10).is_empty(), 3757 + "no local member ⇒ nothing recorded" 3758 + ); 3759 + } 3760 + 3761 + /// Inverse — a `+z` op-redirected line (`@#chan` target) is **not** recorded, mirroring the 3762 + /// local plane (an op-moderated "shown to ops" attempt is never stored, so a later REDACT has 3763 + /// nothing to delete and CHATHISTORY never replays it). 3764 + #[test] 3765 + fn inbound_opmod_redirect_is_not_recorded() { 3766 + let ctx = ctx_hist(); 3767 + let (mut link, _peer_rx) = link_peer(&ctx); 3768 + introduce(&ctx, &link, "0XYZAAAAA", "rover"); 3769 + let (alice, _a_rx) = local_client(&ctx, "0ABCAAAAA", "alice"); 3770 + // alice creates (and thus is op of) #rust, so the op-redirect has a local recipient. 3771 + ctx.channels.join("#rust", &alice, 0); 3772 + 3773 + inbound_message( 3774 + &mut link, 3775 + &parse(":0XYZAAAAA PRIVMSG @#rust :muted speech"), 3776 + &ctx, 3777 + "PRIVMSG", 3778 + ); 3779 + assert!( 3780 + ctx.history.latest("#rust", None, 10).is_empty(), 3781 + "+z op-redirect is never recorded" 3782 + ); 3783 + } 3784 + 3785 + /// Inverse — a remote PM to a local nick (a non-channel target) is never recorded 3786 + /// (chat-history is channel-only; DMs are out of scope by the project's hard rule). 3787 + #[test] 3788 + fn inbound_private_message_is_not_recorded() { 3789 + let ctx = ctx_hist(); 3790 + let (mut link, _peer_rx) = link_peer(&ctx); 3791 + introduce(&ctx, &link, "0XYZAAAAA", "rover"); 3792 + let (_alice, mut a_rx) = local_client(&ctx, "0ABCAAAAA", "alice"); 3793 + 3794 + inbound_message( 3795 + &mut link, 3796 + &parse(":0XYZAAAAA PRIVMSG alice :psst"), 3797 + &ctx, 3798 + "PRIVMSG", 3799 + ); 3800 + assert_eq!(next(&mut a_rx), ":rover!rover@rh PRIVMSG alice :psst\r\n"); 3801 + // Nothing is keyed under the nick (and `latest` on a non-channel target is empty anyway). 3802 + assert!(ctx.history.latest("alice", None, 10).is_empty()); 3803 + } 3804 + 3805 + proptest! { 3806 + /// Feeding an arbitrary run of remote channel PRIVMSGs through the inbound plane records 3807 + /// exactly one history row per message, in arrival order, each under its carried msgid — 3808 + /// the recording is total over the body/msgid domain and never drops or reorders a line. 3809 + #[test] 3810 + fn inbound_channel_recording_is_total_and_ordered( 3811 + bodies in proptest::collection::vec("[a-zA-Z0-9 ]{1,40}", 1..12), 3812 + ) { 3813 + let ctx = ctx_hist(); 3814 + let (mut link, _peer_rx) = link_peer(&ctx); 3815 + introduce(&ctx, &link, "0XYZAAAAA", "rover"); 3816 + let (alice, _a_rx) = local_client(&ctx, "0ABCAAAAA", "alice"); 3817 + ctx.channels.join("#rust", &alice, 0); 3818 + 3819 + for (i, body) in bodies.iter().enumerate() { 3820 + let line = format!(":0XYZAAAAA PRIVMSG #rust :{body}"); 3821 + let mut msg = parse(&line); 3822 + // A distinct carried network id per message (the originating server's stable id). 3823 + msg = msg.with_msgid(&format!("0XYZ-{i:x}")); 3824 + inbound_message(&mut link, &msg, &ctx, "PRIVMSG"); 3825 + } 3826 + 3827 + let events = ctx.history.latest("#rust", None, 100); 3828 + prop_assert_eq!(events.len(), bodies.len()); 3829 + // `latest` returns ascending seq → arrival order; ids/bodies line up one-to-one. 3830 + for (i, (ev, body)) in events.iter().zip(bodies.iter()).enumerate() { 3831 + prop_assert_eq!(&ev.msgid, &format!("0XYZ-{i:x}")); 3832 + prop_assert_eq!(ev.message.trailing(), Some(body.as_str())); 3833 + } 3834 + } 3615 3835 } 3616 3836 }
+13
leveva/tests/fixtures/s2s_history.kdl
··· 1 + server { name "leveva.test"; description "Leveva Test Server"; sid "0ABC" } 2 + admin { name "Admin"; email "admin@test"; location "Test Lab"; network "TestNet" } 3 + class "c" { ping-freq 90; max-links 10; sendq 512000 } 4 + listen { port 16700 } 5 + listen { port 16701; server-only #true } 6 + allow { host "*@*"; class "c" } 7 + operator "root" { mask "*@*"; password "hunter2"; class "c"; privileges "die" "restart" "rehash" "trace" "tkline" } 8 + connect "peer.test" { host "127.0.0.1"; port 6667; class "c"; accept-password "linkpw"; send-password "ourpw" } 9 + 10 + options { default-channel-modes "+" } // slice 80: scenarios authored against modeless channels 11 + 12 + // slice 276: chat-history enabled so a remote-originated channel line is recorded here too. 13 + database { path ":memory:"; replay-lines 10; max-query 50 }
+79
leveva/tests/golden_s2s_chathistory.rs
··· 1 + //! Boot-level golden for **cross-server chat-history recording** (P11 slice 276), end-to-end 2 + //! through the real `leveva` binary. 3 + //! 4 + //! Closes the gap left by slice 274: history was recorded only for *locally-sent* channel lines 5 + //! (`command::message`). A remote user's PRIVMSG, delivered to local members by the S2S relay 6 + //! (`s2s::relay::inbound_message`), was never stored — so on a multi-server network `CHATHISTORY` 7 + //! was incomplete. This proves the wiring live: a remote user `rover` (behind a scripted peer) 8 + //! speaks in a shared channel, and a local member `alice` then sees that line via 9 + //! `CHATHISTORY LATEST`. 10 + //! 11 + //! Mirrors `golden_s2s_redact`'s handshake/burst (pinned identity `leveva.test`/`0ABC`, `alice` = 12 + //! local, peer `1ZZZ`/`1ZZZAAAAA`). The server boots with a `database { path ":memory:" }` block 13 + //! (`s2s_history.kdl`), so history is enabled. Single-daemon, single-channel; no cross-daemon 14 + //! differential (chat-history is leveva-native — the C oracle has none), so this golden plus the 15 + //! `s2s::relay` unit/proptest tiers are the gate. 16 + 17 + mod harness; 18 + use harness::{boot_fixture, Client, Peer}; 19 + 20 + /// The S2S `server-only` listener port (matches `s2s_history.kdl`). 21 + const S2S_PORT: u16 = 16701; 22 + 23 + #[test] 24 + fn remote_channel_message_is_recorded_and_replayed_via_chathistory() { 25 + let _srv = boot_fixture("s2s_history.kdl", harness::PORT); 26 + 27 + // alice negotiates chathistory + batch + message-tags + server-time, registers, and creates 28 + // #hub (she is its chanop, and — crucially — a *local* member, so this server records the 29 + // channel's history). 30 + let mut alice = Client::connect(); 31 + alice.send("CAP LS 302"); 32 + alice.read_until(" LS :"); 33 + alice.send("CAP REQ :draft/chathistory batch message-tags server-time"); 34 + alice.read_until(" ACK "); 35 + alice.send("NICK alice"); 36 + alice.send("USER alice 0 * :Alice Tester"); 37 + alice.send("CAP END"); 38 + alice.read_until(" 422 "); 39 + alice.send("JOIN #hub"); 40 + alice.read_until(" 366 "); 41 + 42 + // The peer links and absorbs our handshake + burst + EOB. 43 + let mut peer = Peer::connect(S2S_PORT); 44 + peer.send("PASS linkpw 2.11 IRC|"); 45 + peer.send("SERVER peer.test 1 1ZZZ :A Peer Server"); 46 + peer.read_until(":0ABC EOB"); 47 + 48 + // The peer bursts `rover` and joins it into the shared #hub, then EOB → we EOBACK. 49 + peer.send(":1ZZZ UNICK rover 1ZZZAAAAA ro rover.host 198.51.100.7 +i :Rover Remote"); 50 + peer.send(":1ZZZ NJOIN #hub :1ZZZAAAAA"); 51 + peer.send(":1ZZZ EOB"); 52 + peer.read_until("EOBACK"); 53 + 54 + // rover speaks in #hub. We deliver it to alice (local member) AND record it (slice 276), 55 + // keyed by the carried network msgid (slice 130). 56 + peer.send("@msgid=1ZZZ-cafe :1ZZZAAAAA PRIVMSG #hub :hello from afar"); 57 + alice.read_until("PRIVMSG #hub :hello from afar"); 58 + 59 + // alice queries history: the remote-originated line is present, sourced from rover, under the 60 + // carried network msgid. 61 + alice.send("CHATHISTORY LATEST #hub * 10"); 62 + let hist = alice.read_until("BATCH -"); 63 + assert!( 64 + hist.contains(" chathistory #hub") && hist.contains("BATCH +"), 65 + "CHATHISTORY LATEST must return a chathistory batch:\n{hist}" 66 + ); 67 + assert!( 68 + hist.contains("PRIVMSG #hub :hello from afar"), 69 + "the remote-originated line must be in history:\n{hist}" 70 + ); 71 + assert!( 72 + hist.contains("rover!ro@rover.host"), 73 + "the replayed line must be sourced from the remote user:\n{hist}" 74 + ); 75 + assert!( 76 + hist.contains("msgid=1ZZZ-cafe"), 77 + "the stored line carries the network-stable carried msgid:\n{hist}" 78 + ); 79 + }