Spec correction following de515462 (feed always reserves live default
aggregators). The existing "uses the healthy relays when they are available"
case asserted deepEqual(subRelayArgs, healthy) — i.e. ONLY the healthy relays.
That expectation is now wrong: the feed deliberately also reserves known-good
defaults so a healthy-looking but dead user list can't hang the feed. Replaced
with a stricter behavioral assertion (healthy relays still present AND a default
reserved AND bounded to the read cap). Mutation-verified: reverting
buildFeedRelaySet to the old healthy-only logic fails the updated case.
test_integrity_note:
change_type: ["spec_correction"]
scenarios:
- id: SCN-feed-reserve-live-defaults
given: "the user has healthy-looking relays that may actually be dead"
when: "the feed subscription is created via subscribeVideos"
then: "the relay set includes the user's healthy relays AND at least one default aggregator, bounded to MAX_SUBSCRIBE_RELAYS"
observable_outcomes:
- "the relay array passed to pool.sub()"
determinism_controls:
- "mocked pool.sub captures the boundary relay list; getHealthyRelays stubbed"
anti_cheat_rationale:
prevents:
- "over-mocking internal logic"
- "snapshot rubber-stamping"
relaxation:
did_relax_any_assertion: false
if_true_explain_spec_basis: "old deepEqual replaced with stricter superset+liveness+bound assertions"
Co-Authored-By: Claude Opus 4.8 <[email protected]>
subscribeVideos called pool.sub(getHealthyRelays(this.relays)) with no
empty-relay fallback, unlike the fetch paths in the same file. On a cold first
load the 5s relay connect probe can time out, marking every relay unreachable;
init() only warns and schedules a background reconnect (it does not throw), so
boot proceeds and the feed subscribes to ZERO relays. The background reconnect
later succeeds but nothing re-issues the subscription, so the feed stays empty
until the user refreshes (when the SWR cache renders) — the reported
"notes don't load on first visit / need a refresh" symptom.
Fall back to DEFAULT_RELAY_URLS when no relays are healthy, mirroring the
established pattern. Because the fallback bypasses the unreachable cooldown, the
subscription retries those relays directly and self-heals instead of staying
dead until refresh.
Adds tests/nostr/subscribeVideosRelayFallback.test.mjs: empty healthy set =>
default relays (never zero), populated healthy set => uses those relays.
Co-Authored-By: Claude Opus 4.8 <[email protected]>