Repository navigation
perf(profiles): keep verified names checked through a planned renewal - #2234
Conversation
b987a1a to
98836c7
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
Verdict: one blocker (P2), inline. The outage-vs-renewal ordering holds: reconnect()/terminal() call clearSocket() then notify() in the same task, so connected(false) lands before the microtask. No hot loop either: syncVerifiedProfiles never closes synchronously, and a missing transport returns undefined and fails.
Exit criteria: carry unpublished pre-EOSE changes into the re-sent batch, plus a probe for two renewals before EOSE. Also drop "no regressions or behavior changes" from the body, since this is one.
Reviewed with Mongo; probe fails at this head and passes with the fix.
a planned nip-fi renewal ends every sent verified-profile sync with disconnected: while the connection stays reported up. each batch then cleared its checked status and republished the whole profile directory, and its re-sent batch republished again at eose. a batch ended that way is now re-sent at once with its checked status kept, as a rebuilt batch already is, and an eose that confirms what is shown does not notify. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
a relay closed reason starting with disconnected: reached the verified profile directory as local socket retirement, which resent the batch at once and kept its checked status. the live transport now maps such a relay reason to error: relay closed the sync, so only a lost socket reports disconnected: and a relay refusal takes the failure path. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
a renewal that ended a re-sent batch before eose dropped a head it had accepted but not published. the replacement replayed the same head, accept returned false and no notification followed. the replacement now inherits the pending change, so its eose publishes it. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
9cebcbc to
c7e2e09
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
Verdict: one blocker (P2), inline. The prior P2 is fixed: the renewal restart carries !ready && dirty, and the new test covers two renewals before EOSE. The live.ts guard keeps a relay CLOSED from selecting the renewal path, and the re-send cannot loop (an empty or invalid batch throws in verifiedProfileFilter and fails once).
Exit criteria: carry pending changes through the schedule() rebuild too, plus a probe for it. I missed this path in the first round; it has been there since the first head.
Reviewed with Mordecai. The probe fails at this head and passes with the sketched fix; verified-profile.test.ts stays green. CI required is green.
schedule rebuilt an open batch with a fresh pending flag, so a head the old batch accepted before eose stayed unpublished when the rebuilt batch replayed it unchanged and flipped no checked status. batches now expose their pending state and both the rebuild and the renewal restart pass it on. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
Verdict: no blockers. The rebuild P2 is fixed in 2a7c0a8: Batch.pending() feeds both the schedule() rebuild and the renewal restart, and the only other start() call creates a batch with no predecessor. The new test matches the probe.
Validation: verified-profile.test.ts and live.test.ts pass locally at this head (201 tests). Hosted CI is green, including CI required.
…xecution-support-for-native-buzz-33ea2bea * origin/main: (74 commits) feat(host): harden byte lifecycle and add protected image upload (#2199) Updated onboarding (#2183) perf(relay): read each signed roster once in large channels (#2244) AIDA-2346: Keep spoilers hidden in reminder previews (#2261) fix: AIDA-2371 Keep sidebar selection pill aligned during resize (#2182) fix(relay): skip background rereads of profiles a read found absent (#2147) fix: prevent startup logo selection (#1823) fix(agents): send sign-in evidence with agent Git credentials (#2240) perf(mentions): stop scanning every member on each mention keystroke (#2246) fix(attachments): send image dimensions so previews don't shift (#2262) feat(shortcuts): add Slack-style hotkeys (#2170) perf(plugins): share host request clients across calls (#2243) fix(identity): keep federated sessions and agent grants in one keychain item (#2225) fix(profiles): evict the least recently requested profile (#2255) fix(unread): repair unread from the recovery tick only every 15 minutes (#2252) AIDA-2374: Use readable labels in chat titles (#2185) perf(profiles): keep verified names checked through a planned renewal (#2234) feat(plugins): let a plugin ask Buzz to review an install (#2059) fix(identity): sign FI builds in at first run through the native session (FOUNDATION: App.tsx, services.ts) (#2249) Place profile verification beside the verified name (#2224) ... Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
* commit 'c8e882165': (50 commits) Scope execution targets by harness session Read durable execution state before admission feat(host): harden byte lifecycle and add protected image upload (#2199) Updated onboarding (#2183) perf(relay): read each signed roster once in large channels (#2244) AIDA-2346: Keep spoilers hidden in reminder previews (#2261) fix: AIDA-2371 Keep sidebar selection pill aligned during resize (#2182) fix(relay): skip background rereads of profiles a read found absent (#2147) fix: prevent startup logo selection (#1823) fix(agents): send sign-in evidence with agent Git credentials (#2240) perf(mentions): stop scanning every member on each mention keystroke (#2246) fix(attachments): send image dimensions so previews don't shift (#2262) feat(shortcuts): add Slack-style hotkeys (#2170) perf(plugins): share host request clients across calls (#2243) fix(identity): keep federated sessions and agent grants in one keychain item (#2225) fix(profiles): evict the least recently requested profile (#2255) fix(unread): repair unread from the recovery tick only every 15 minutes (#2252) AIDA-2374: Use readable labels in chat titles (#2185) perf(profiles): keep verified names checked through a planned renewal (#2234) feat(plugins): let a plugin ask Buzz to review an install (#2059) ... Signed-off-by: Daniel Rouleau <drouleau@squareup.com> Co-authored-by: Codex <noreply@openai.com>
…block#2234) ## Problem A native NIP-FI socket renews its assertion every ~285 s: the lifetime is capped at 300 s, minus a 15 s margin. `live.ts` reconnects quietly and the connection stays reported `connected`, but `clearSocket()` ends every sent sync with `disconnected: live connection lost`. Each verified-profile (kind 30382) batch treated that as a failure: - it cleared `checked` for its subjects; - it republished the whole profile directory, and every name flipped from checked to cached; - the next `ensure` re-sent the batch, and its replay republished the directory again at EOSE, even though nothing had changed. With 1,024 verified subjects that is 8 batches. Each renewal therefore caused 16 full republishes and 2,048 changed entries, and every checked name dropped to cached for a moment. ## Change - A batch that ends with `disconnected:` is re-sent at once with its checked status kept, as a rebuilt batch already is. The decision waits one microtask. An outage reports the connection down in the same task, which stops every batch first, so an outage still clears status and sends nothing extra. - EOSE notifies only when it changed a head, a checked status or a retraction. A replay that confirms what is shown is quiet. ## Evidence Blox matrix: the real `createRelaySession` with the profile and verified directories over a scripted live transport, 1,024 verified subjects. Each renewal ends every sync as `clearSocket()` does, while states stay `connected`. Base and variant runs were interleaved, 5 runs on each of 2 Linux and 2 macOS workstations. Values are medians of 10 per OS. | Metric (5 renewals, ≈24 min of uptime) | Linux before | Linux after | Linux Δ | macOS before | macOS after | macOS Δ | |---|---|---|---|---|---|---| | CPU (ms) | 327.5 | 22 | −93% | 194 | 27 | −86% | | Profile notifications | 80 | 0 | −100% | 80 | 0 | −100% | | Entries changed | 10,240 | 0 | −100% | 10,240 | 0 | −100% | | Names checked after | 1,024 | 1,024 | 0 | 1,024 | 1,024 | 0 | | Verified REQs / events | 40 / 5,120 | 40 / 5,120 | 0 | 40 / 5,120 | 40 / 5,120 | 0 | What you gain: every listener stops refolding on every renewal, about 12.6 times an hour on a NIP-FI relay, and verified names no longer flicker from checked to cached. Wire traffic is unchanged. The Blox runs predate the one-microtask deferral. Locally, the same harness gives an identical renewal result (0 notifications, 1,024 checked). An outage scenario keeps `main`'s 40 REQs and clears status as before. - `verified-profile.test.ts`, new: "re-sends a batch a replaced socket ended and keeps its check quiet while connected" fails on `main`. "does not re-send a batch an outage ended" pins the outage path. - `vitest run` on `src/features/relay`, `src/features/profiles` and `src/features/messages`: 220 files and 5,002 tests pass, 4 skipped. `tsc --noEmit` and biome are clean. ## Related block#2216 makes verified publishes one per macrotask. That reduces how many republishes a renewal causes, but each ended batch still drops its names to cached until the re-sent batch answers. This PR keeps them checked and keeps a confirming replay quiet, so the two compose. Same Blox harness, stacked on block#2216, interleaved, 4 runs on each of 4 Linux and 4 macOS workstations. Values are medians of 16 per OS. | 5 renewals @1,024 verified | Linux block#2216 | Linux block#2216 + this | Linux Δ | macOS block#2216 | macOS block#2216 + this | macOS Δ | |---|---|---|---|---|---|---| | CPU (ms) | 74.5 | 21 | −72% | 64 | 27.5 | −57% | | Profile notifications | 10 | 0 | −100% | 10 | 0 | −100% | | Checked↔cached flips | 10,240 | 0 | −100% | 10,240 | 0 | −100% | | Verified REQs | 40 | 40 | 0 | 40 | 40 | 0 | Outage (5 disconnect/reconnect cycles) on block#2216 with and without this change: 10 notifications and 40 REQs in both, CPU 78 vs 78 ms on Linux and 132.5 vs 131.5 ms on macOS. The outage path is unchanged. ## Tradeoff - The directory infers a renewal from the documented `disconnected:` reason and from the connection still being up after the current task. It does not get an explicit signal from `live.ts`, which keeps the change to one file. - During an outage, batches now end through `connected(false)` alone. That is one profile notification per outage instead of one per batch plus one. The end state is unchanged. - A relay CLOSED whose reason starts with `disconnected:` is now reported as `error: relay closed the sync`, so only local socket loss takes the renewal path. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Problem
A native NIP-FI socket renews its assertion every ~285 s: the lifetime is capped at 300 s, minus a 15 s margin.
live.tsreconnects quietly and the connection stays reportedconnected, butclearSocket()ends every sent sync withdisconnected: live connection lost. Each verified-profile (kind 30382) batch treated that as a failure:checkedfor its subjects;ensurere-sent the batch, and its replay republished the directory again at EOSE, even though nothing had changed.With 1,024 verified subjects that is 8 batches. Each renewal therefore caused 16 full republishes and 2,048 changed entries, and every checked name dropped to cached for a moment.
Change
disconnected:is re-sent at once with its checked status kept, as a rebuilt batch already is. The decision waits one microtask. An outage reports the connection down in the same task, which stops every batch first, so an outage still clears status and sends nothing extra.Evidence
Blox matrix: the real
createRelaySessionwith the profile and verified directories over a scripted live transport, 1,024 verified subjects. Each renewal ends every sync asclearSocket()does, while states stayconnected. Base and variant runs were interleaved, 5 runs on each of 2 Linux and 2 macOS workstations. Values are medians of 10 per OS.What you gain: every listener stops refolding on every renewal, about 12.6 times an hour on a NIP-FI relay, and verified names no longer flicker from checked to cached. Wire traffic is unchanged.
The Blox runs predate the one-microtask deferral. Locally, the same harness gives an identical renewal result (0 notifications, 1,024 checked). An outage scenario keeps
main's 40 REQs and clears status as before.verified-profile.test.ts, new: "re-sends a batch a replaced socket ended and keeps its check quiet while connected" fails onmain. "does not re-send a batch an outage ended" pins the outage path.vitest runonsrc/features/relay,src/features/profilesandsrc/features/messages: 220 files and 5,002 tests pass, 4 skipped.tsc --noEmitand biome are clean.Related
#2216 makes verified publishes one per macrotask. That reduces how many republishes a renewal causes, but each ended batch still drops its names to cached until the re-sent batch answers. This PR keeps them checked and keeps a confirming replay quiet, so the two compose. Same Blox harness, stacked on #2216, interleaved, 4 runs on each of 4 Linux and 4 macOS workstations. Values are medians of 16 per OS.
Outage (5 disconnect/reconnect cycles) on #2216 with and without this change: 10 notifications and 40 REQs in both, CPU 78 vs 78 ms on Linux and 132.5 vs 131.5 ms on macOS. The outage path is unchanged.
Tradeoff
disconnected:reason and from the connection still being up after the current task. It does not get an explicit signal fromlive.ts, which keeps the change to one file.connected(false)alone. That is one profile notification per outage instead of one per batch plus one. The end state is unchanged.disconnected:is now reported aserror: relay closed the sync, so only local socket loss takes the renewal path.🤖 Generated with Claude Code