Skip to content

[Rewrite P2] nostream-relay: wire handlers, strategies, rate limits #865

Open

Description

@chappie-daemon

Parent: #860
Milestone: Phase 2: Protocol core
Host: both · Expected cost: $5.00

nostream-relay: the wire protocol handlers — the heart of the relay (measured 2,954 LOC in src/handlers, plus the adapter layer).

  • WebSocket server: tokio-tungstenite, per-connection state machine replacing web-socket-adapter.ts/web-socket-server-adapter.ts (742 LOC adapter layer) — ping/pong liveness, max-payload handling, RSV/compression handling, drain-on-close semantics (all measured behaviours in the TS tests)
  • Message handlers: EVENT/REQ/CLOSE/COUNT/AUTH/NEG (NIP-77 NEG-OPEN/NEG-MSG/NEG-CLOSE) with the exact notice/error strings the TS relay sends (clients depend on them)
  • Event strategies (measured: 10+ strategies — default, ephemeral, delete, gift-wrap, DVM job request, contact-list, group/invite/join/leave, replaceable) as a strategy trait; every strategy's acceptance/rejection logic carries its TS tests across
  • NIP-42 AUTH: challenge issuance, event verification, session expiry, replay rejection (measured behaviours in web-socket-adapter tests)
  • Rate limiting: the TS limiter's window semantics per message type, whitelist handling, REQ backoff-hint notices — port the algorithm, property-test the window math
  • Subscription lifecycle: subscribe/unsubscribe, filter-matching dispatch, NIP-56 actionable-report hiding, restricted kinds + authenticated read-through (measured test rows across the WS adapter suite)
  • NIP-11: the info document served from supportedNips + relay metadata; the relay_url validation ported (48fe9c4)
  • NIP-66: active monitoring probes + the published event (port relay-monitor-worker + nip66-event-publisher)

Acceptance: a differential harness drives both the TS relay (running from the capture commit) and the Rust relay with the same scripted client sessions; every response (events, notices, COUNTs, EOSEs, close codes) matches byte-for-byte modulo event ids the relays compute themselves; the negentropy flows converge to the same ranges.

Estimate basis (2026-10-24, re-costed): $5.00 - job 008 measured $6.91 for a 3,700-line handler port across five review rounds. Job 011's spec re-costed this row from $4.00: the 14 strategy bodies (802 TS lines) cannot be done without three core stubs they call - the NIP-03 OpenTimestamps parser (396 lines), NIP-56 extraction (118), and the web-of-trust seam - plus a census-backed test target of about 1,600 lines. Roughly 1,300 TS lines at two or three rounds; range $4.00-6.00. Model and per-unit costs: .factory/estimates.md.

Activity

  1. added
    rewritenostream 4.0 Rust/LMDB rewrite
    rustRust implementation work
    factoryBuilt by the software factory
    on Oct 17, 2026
  2. chappie-daemon commented on Oct 19, 2026

    @chappie-daemon
    CollaboratorAuthor

    Factory job 007-relay-handlers opened: base rewrite/main @ b324ef5 (004 core merged), branch factory/007-relay-handlers, worktree projects/nostream-worktrees/007-relay-handlers, factory 0.10.6, expected $2,000 (Phase 2 anchor — its phase has no measured job yet; replaced, not scaled, when this one closes). Stage: recon. Project item In progress.

    Order note: the milestones put this in Phase 2 and #864 in Phase 3, so the board's program order opens this job first even though #864's storage seam is a dependency the differential harness needs. Recon is briefed to measure whether storage is a hard prerequisite and, if it is, say so with evidence before the spec is written.

  3. chappie-daemon commented on Oct 19, 2026

    @chappie-daemon
    CollaboratorAuthor

    Not run yet, on measured evidence: recon found this job's acceptance needs the storage seam first. EventRepository/^ UserRepository declare 4/11 and 3/7 methods, so REQ/EVENT cannot be served by any implementation or test double until #864 lands, and the differential harness's TS half needs PostgreSQL + Redis, neither reachable on this host. The recon (outside the job tree) and a 12-frame capture from the real TS handlers are kept for when this runs. Order recorded on #860.

  4. chappie-daemon commented on Oct 23, 2026

    @chappie-daemon
    CollaboratorAuthor

    Factory job 008-relay-handlers opened against rewrite/main 471d439. Stage: recon (ground being measured now). The storage seam this issue was waiting on merged as 827558a (PR #910), with #913–#915 as its follow-ups, so the relay layer now has a repository interface to build on.

  5. chappie-daemon commented on Oct 23, 2026

    @chappie-daemon
    CollaboratorAuthor

    Recon complete for job 008-relay-handlers (measured at 471d439). Headline findings:

    • The TS surface reproduces: 2,720 LOC in src/handlers here (2,954 at the oracle 041fe34e), 723 LOC of adapters (742 at the oracle).
    • The per-unit oracle is the TS suite: 616 cases on this surface, running in ~20 s.
    • The differential harness the acceptance names does not exist; the repository's established way to build one is the tests/capture/gen-*.mjs → Rust-replay pattern, so the spec commits to a frame-session fixture.
    • Wire behaviour is measured, not read: AUTH challenge, the four NOTICE/invalid: strings, EOSE, COUNT, duplicate-subscription notice, the three OK shapes, close 1009 on a 210 KB frame, and both rate-limit frame shapes.

    Slicing (recorded on #860): this issue is one $2,000 job over ~2,815 TS LOC, so it splits:

    job scope cost
    008 (this) transport, codec, connection state machine, five handlers, subscriptions, NIP-42, rate limiting, strategy trait + default strategy, frame-session harness $1,400
    009 (next) the other 14 event strategies and their 105 cases $600

    NIP-11 moves to nostream-admin and NIP-66 to nostream-services, matching the crate map (crates/nostream-relay/src/lib.rs:21-22), rather than being ported into the relay crate.

  6. chappie-daemon commented on Oct 23, 2026

    @chappie-daemon
    CollaboratorAuthor

    Spec written for job 008-relay-handlers (841 lines): R1–R35, 51 acceptance criteria marked [measured] (command + expected output) or [judged] (one: the ADR). Build is now running on factory/008-relay-handlers.

    Two things the spec settled that the recon could not:

    • The case census is 557, not the recon's 616 for the whole surface. It splits 462 (this job) / 101 (job 009, the other 14 strategies) / 40 (NIP-11, admin) / 8 (NIP-66, services) / 5 (broadcast). Five files the recon's grouping missed — event/message schema, hidden-content cache, shutdown state, validation — belong to this job and are in its 462; without them the parse and EVENT criteria cannot be met.
    • NIP-77 cannot be in this slice. The NEG handlers do not exist at the capture commit (242a300); they appear at 041fe34e. The issue's "negentropy flows converge" therefore has no TS oracle in this tree and is carried as its own job.

    The oracle this job commits to: tests/wire_parity.rs replaying 16 required frame sessions recorded from the capture tree with frozen time, plus a 462-case census driver keyed to the TS specs — two declared tolerances only (V8 JSON.parse detail; a library close-frame reason).

  7. chappie-daemon commented on Oct 24, 2026

    @chappie-daemon
    CollaboratorAuthor

    Job 008 landed: 740ef699 on rewrite/main (squash of PR #947, 2026-10-24T01:29:10Z).

    Actual vs expected cost

    Expected $1,400 (the slice recorded for 008 at scoping)
    Actual $6.91 — 28 sessions, 2,902 requests, 6.63M in / 2.09M out / 401.7M cache-read, 19.30 h summed agent wall
    Split reviews $3.96 (57%), build $2.72 (39%), spec $0.23 (3%)

    The 200× gap is model choice, not efficiency: the ceiling assumed frontier-model lenses and the run
    used cheap models throughout. Full breakdown and process analysis in
    .factory/jobs/008-relay-handlers/cost.md.

    What the job delivered

    The relay's wire core: connection control, EVENT through the strategies and the four-store
    limiter, REQ/CLOSE with streaming EVENT/EOSE, NIP-42 AUTH with restricted reads, COUNT,
    and the OK/NOTICE/CLOSED replies. Two oracles measure parity rather than asserting it:
    wire_parity replays 16 captured sessions (78 steps) frame for frame, and ts_case_parity runs 462
    of the 557 census cases against the Rust handlers.

    Gates at the merged head: 758 passed / 0 failed / 4 ignored; fmt, both clippy profiles and the
    release build clean.

    Review history

    Five rounds. Round 1 found real defects (a limiter key leak of 6,968 kB over 30k pubkeys, a
    nondeterministic oracle); rounds 2–4 closed them and removed a per-record println! that stalled a
    REQ at 4,000 frames. The last three rounds' remaining findings were prose only and were corrected
    by the primary in d73abfb and 4f8ac67, checked against limits.rs and default-settings.yaml.
    The job escalated at rounds-exhausted with an architecture verdict of CHANGES on three prose
    findings, and landed on the operator's word.

    Remaining scope from this issue

    Job 009 carries the other fourteen strategies (776 LOC, 101 census cases, $600 expected). NIP-11
    belongs to nostream-admin and NIP-66 to nostream-services, per the crate map.

  8. chappie-daemon commented on Oct 24, 2026

    @chappie-daemon
    CollaboratorAuthor

    Relay strategies slice — built, reviewed twice, now with the operator. PR #976 (base rewrite/main).

    stage state
    build fourteen event-strategy arms + three core stubs, census migrated to 658 cases across 36 files; all five repository checks green in debug and release
    static round 1: two findings, both fixed with tests proved to bite; round 2: one finding, verified as inherited capture parity → #975
    review round 1: code and architecture CHANGES (four findings, all closed); round 2: four lenses PASS
    approve ESCALATE, condition money-access-identity — with all three approve conditions holding

    The escalation is about what the branch makes live, not how it was built: join_request (NIP-42 gate → claim_code → admit_user), leave_request (revocation) and report's mark_actionable_target under hide_actionable_reports stop answering NotImplemented. Both features ship disabled in resources/default-settings.yaml, so the question is whether dormant-by-default is enough for rewrite/main or whether those three arms should be read first. Job 007 escalated on the same condition for the storage half of this path.

    Also closed on the way: #974 (wire_parity flake — a harness race, classified by measurement: 10 failures in 20 release runs before, 0 in 30 after). Filed: #975 (a publish racing a kind-62 vanish survives it, reproduced from the capture, not introduced by the port).

    Nothing is merged. Expected cost for this job was $5.00; the actual lands with the close-out.

  9. chappie-daemon commented on Oct 24, 2026

    @chappie-daemon
    CollaboratorAuthor

    Merged: the relay strategies slice is in rewrite/main as 2b2287f (PR #976, squash, on the operator's /merge_011 at 2026-10-24T20:28Z).

    Fourteen event-strategy arms now answer the capture's frames instead of NotImplemented, with the three core pieces they call (nip03 OTS validation, nip56 report targets, report_scoring weights), a WotGraph seam on HandlerContext, and the census migrated to 658 cases across 36 spec files (612 mapped across 200 rows to 182 test functions, 7 exempt groups holding 46).

    Cost: $3.90 against $5.00 expected — 78%, $1.10 under. 16 sessions, 5.1M in / 1.0M out / 202.1M cache-read, 10.7 h summed wall.

    Two issues came out of it: #974 (the wire_parity flake — a harness race, fixed here) and #975 (a publish racing a kind-62 vanish survives it — inherited capture behaviour, left open with its decision points). #865 stays open: the HTTP surface is #867's and the remaining relay work is not this slice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    factoryBuilt by the software factoryrewritenostream 4.0 Rust/LMDB rewriterustRust implementation work

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions