docs(backend): protocol negotiation, replay protection, caching, and testing guides - #600
Merged
codebestia merged 1 commit intoAug 31, 2026
Conversation
…and testing guides Adds four backend documents and indexes them in docs/README.md. concepts-protocol-negotiation.md (codebestia#557): the device capability payload shape, the sealed_box BASELINE_PROTOCOL applied to clients predating the field, how selectProtocol picks a mutually supported protocol, why the protocol is recorded per envelope rather than per device, the protocol_mismatch rejection and the client recovery steps, and how the whole mechanism supports a staged sealed_box to Signal rollout. concepts-replay-protection.md (codebestia#558): the two distinct layers of duplicate suppression - transport-level eventId dedup and message-level messageId idempotency - the device-scoped Redis key, the env-configurable TTL and its fail-open behaviour, the dispatch_ack duplicate flag, and the dispatcher path that applies the check to every registered handler. concepts-caching.md (codebestia#567): the conversation-list cache key, TTL and payload, every invalidateConversationCaches call site with its trigger, the degraded behaviour when Redis is unavailable, and the per-device scoping hazard created by a device-specific preview under a key that does not name the device. testing.md (codebestia#566): the backend companion to the cross-app testing guide - the standard route-test mock set with a copyable skeleton, the Drizzle chain-mocking traps, driving socket handlers through the enveloped dispatch event, adding service mocks when a route gains a dependency, and resetting shared in-process state between tests.
|
@AdaBliss Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds four backend documents covering areas whose behaviour is currently only learnable by reading the source, and indexes them in
docs/README.mdas the index's maintenance rule requires.This is a documentation-only change. No source file is touched.
apps/backend/docs/concepts-protocol-negotiation.mdapps/backend/docs/concepts-replay-protection.mdapps/backend/docs/testing.mdapps/backend/docs/concepts-caching.mdcloses #557
closes #558
closes #566
closes #567
Device capability and E2EE protocol negotiation (#557)
Covers
lib/capabilities.tsandservices/e2eeProtocol.ts:protocols,ciphersuites,fileTransfer), why every field is optional and unrecognised values are preserved rather than rejected.BASELINE_PROTOCOLand the three ways a client predating the field ends up on the sealed-box baseline: the column default, an omittedcapabilities, and a documentnormalizeCapabilitiescannot parse.POST /auth/verify,POST /devices/link/verify), including the update-in-place upgrade path, and the three endpoints that read capabilities back, one of which also returns the server-computednegotiatedProtocol.selectProtocoland the strongest-firstPROTOCOL_PRIORITY, plus MLS ciphersuite selection and the per-device-pair rationale.message_envelopes.protocolcolumn and why it is recorded per envelope rather than per device: capabilities are mutable, so a device-level record would make all pre-cutover history appear to have been built with the newer construction.checkEnvelopeProtocols, both violation reasons, the400/409split, and why unresolvable device ids are skipped rather than rejected.protocol_mismatchrejection on both transports, with the four-step client recovery, including "do not fall back to a weaker protocol".KNOWN_PROTOCOLS, thee2ee_protocolenum, andPROTOCOL_PRIORITYtogether.Deliberately does not restate the migration plan in
signal-migration.md; it links to it and documents the mechanism instead.Replay protection and event idempotency (#558)
Covers
services/replay-protection.service.tsand the dispatcher path:replay:{deviceId}:{eventId}key and why the device component is load-bearing: with a global key, one device's replayed id would silently drop another device's legitimate first-time event.REPLAY_PROTECTION_TTL_SECONDS, default 300, accepted range 1-86400, invalid values degrading to the default, and its relationship toSOCKET_EVENT_MAX_AGE_MS.dispatch_ackwithduplicate: true/false, and thatduplicate: trueis an acknowledgement rather than an error.messageIdcheck on all four send paths, spanning transports and with no window.dispatcher.registerhandlers are reachable only through the envelopeddispatchpath, so every registered event gets the check.One accuracy note for reviewers:
send_file_messageis still attached with a rawsocket.oninsrc/socket/messaging.ts, so it does not currently get envelope validation or theeventIdcheck; its duplicate suppression is themessageIdlayer alone. The document states the rule and flags that single exception rather than overstating the coverage.Backend testing guide (#566)
The backend-specific companion to
docs/testing.md, which it links to rather than duplicating:db/index.js,db/schema.js,drizzle-orm,lib/redis.js, andmiddleware/auth.js, with the reasoning attached to each block, and why the subject is imported withawait import(...).dispatch, why grabbing a raw listener finds nothing (and why a test written that way used to pass while bypassing the auth gate, envelope validation, and idempotency), and the five properties to preserve when copying the helper..values()needing to be both thenable and expose.returning(), with the two wrong stubs and their opposite failure modes; transaction mocks;select().from().where().groupBy();sqlas both tag and namespace; and the two import-time failures from a missing table or operator.vi.mockadded to every existing test file for that route in the same change, with a table of the frequently mocked services.Backend caching reference (#567)
Covers
lib/conversationCache.tsand the Redis cache key inlib/redis.ts:conversations:{userId}key, the 30-secondCONV_CACHE_TTL, the cached payload, and why the archived view is not cached at all.redisbeing non-null and bytry/catch.redis.delinPATCH /conversations/:id/settings(correct, because mute and archive are per-member) and the device-added/revoked path inemitDeviceChangeEvent, plus the general rule for new call sites.GET /conversationsbuilds its preview throughgetConversationRelations(req.auth.deviceId), which filters envelopes to the requesting device, so the response is device-specific.convCacheKeyisconversations:{userId}and does not carry the device, so for a multi-device user the first device to populate the entry serves its own ciphertext to the user's other devices for up to the TTL. The document states this as a live, TTL-bounded hazard, namesconvCacheKeyas the single place a fix belongs, and notes that a device-scoped key also changes invalidation, sinceinvalidateConversationCachestakes user ids. Flagged for maintainer triage rather than changing behaviour in a documentation PR.Checks
npx prettier --checkon all five changed files: clean.devat the time of writing.