Repository navigation
[Rewrite P2] nostream-relay: wire handlers, strategies, rate limits #865
Description
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 replacingweb-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-adaptertests) - 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
chappie-daemon commentedon Oct 19, 2026
Factory job
007-relay-handlersopened: baserewrite/main@b324ef5(004 core merged), branchfactory/007-relay-handlers, worktreeprojects/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.
chappie-daemon commentedon Oct 19, 2026
Not run yet, on measured evidence: recon found this job's acceptance needs the storage seam first.
EventRepository/^UserRepositorydeclare 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.chappie-daemon commentedon Oct 23, 2026
Factory job
008-relay-handlersopened againstrewrite/main471d439. Stage: recon (ground being measured now). The storage seam this issue was waiting on merged as827558a(PR #910), with #913–#915 as its follow-ups, so the relay layer now has a repository interface to build on.chappie-daemon commentedon Oct 23, 2026
Recon complete for job
008-relay-handlers(measured at471d439). Headline findings:src/handlershere (2,954 at the oracle041fe34e), 723 LOC of adapters (742 at the oracle).tests/capture/gen-*.mjs→ Rust-replay pattern, so the spec commits to a frame-session fixture.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:
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.chappie-daemon commentedon Oct 23, 2026
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 onfactory/008-relay-handlers.Two things the spec settled that the recon could not:
242a300); they appear at041fe34e. 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.rsreplaying 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 (V8JSON.parsedetail; a library close-frame reason).chappie-daemon commentedon Oct 24, 2026
Job 008 landed:
740ef699onrewrite/main(squash of PR #947, 2026-10-24T01:29:10Z).Actual vs expected cost
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,
EVENTthrough the strategies and the four-storelimiter,
REQ/CLOSEwith streamingEVENT/EOSE, NIP-42AUTHwith restricted reads,COUNT,and the
OK/NOTICE/CLOSEDreplies. Two oracles measure parity rather than asserting it:wire_parityreplays 16 captured sessions (78 steps) frame for frame, andts_case_parityruns 462of 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 aREQat 4,000 frames. The last three rounds' remaining findings were prose only and were correctedby the primary in
d73abfband4f8ac67, checked againstlimits.rsanddefault-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-adminand NIP-66 tonostream-services, per the crate map.chappie-daemon commentedon Oct 24, 2026
Relay strategies slice — built, reviewed twice, now with the operator. PR #976 (base
rewrite/main).money-access-identity— with all three approve conditions holdingThe 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) andreport'smark_actionable_targetunderhide_actionable_reportsstop answeringNotImplemented. Both features ship disabled inresources/default-settings.yaml, so the question is whether dormant-by-default is enough forrewrite/mainor 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_parityflake — 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.
chappie-daemon commentedon Oct 24, 2026
Merged: the relay strategies slice is in
rewrite/mainas2b2287f(PR #976, squash, on the operator's/merge_011at 2026-10-24T20:28Z).Fourteen event-strategy arms now answer the capture's frames instead of
NotImplemented, with the three core pieces they call (nip03OTS validation,nip56report targets,report_scoringweights), aWotGraphseam onHandlerContext, 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_parityflake — 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.