fix(l2ps-messaging): canonicalize peer identity — address #978 review - #979
Conversation
…978 review Follow-up to #978. The 0x strip was validation-only: the raw key was still used as the peer identity, so 0x<key> and <key> registered as two peers (CodeRabbit, major). Extract canonicalizeKey() (strip 0x/0X + lowercase) and use it for every identity/lookup — register, send recipient, request_public_key — while the signed proof keeps the client's raw representation. Also accepts uppercase 0X (Greptile). 4 canonicalization tests added (76 pass total). Note: requestId echo is threaded through all direct responses (per #978); the existing integration.test.ts drives a re-implemented fake server, so full real-server coverage is a separate harness improvement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
|
Warning Review limit reached
Next review available in: 55 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughL2PS messaging now canonicalizes public-key identities by removing one leading hexadecimal prefix and lowercasing keys. Routing, peer state, persistence, lookups, history queries, migration, and helper tests use this format. ChangesL2PS key canonicalization
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant L2PSMessagingServer
participant L2PSMessagingService
participant MessageStore
Client->>L2PSMessagingServer: Send message with public key
L2PSMessagingServer->>L2PSMessagingServer: canonicalizeKey
L2PSMessagingServer->>L2PSMessagingService: Route with canonical identity
L2PSMessagingService->>MessageStore: Store or query canonical keys
MessageStore-->>L2PSMessagingService: Matching messages
L2PSMessagingService-->>L2PSMessagingServer: Messages or history
L2PSMessagingServer-->>Client: Messaging response
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| src/features/l2ps-messaging/L2PSMessagingServer.ts | Applies canonical identities consistently to peer registration, routing, event payloads, and lookup paths while preserving raw signed-proof inputs. |
| src/features/l2ps-messaging/L2PSMessagingService.ts | Canonicalizes persisted message keys and queued-message and history queries, resolving the previously reported lookup gaps. |
| src/features/l2ps-messaging/keys.ts | Introduces a reusable, idempotent canonicalization helper shared across server and persistence boundaries. |
| src/migrations/1782680000000-CanonicalizeL2PSMessageKeys.ts | Rewrites legacy prefixed or mixed-case message keys using SQL semantics matching the runtime canonicalization helper. |
| src/features/l2ps-messaging/tests/L2PSMessagingServer.test.ts | Adds focused coverage for prefixes, casing, interior prefix-like text, and idempotence. |
Reviews (4): Last reviewed commit: "style(l2ps-messaging): no empty catch on..." | Re-trigger Greptile
…rows Addresses two P1 review comments on #979. 1) handleHistory queried getHistory() with the RAW peerKey while messages are persisted under canonical keys, so an existing conversation returned nothing. Now canonicalizes peerKey for the query (raw form kept for the signed proof; myKey is already canonical). 2) getQueuedMessages looked up only the canonical identity, stranding rows an earlier build queued under a non-canonical recipient key. deliverQueuedMessages now also passes the raw registration key, and getQueuedMessages matches to_key IN (canonical, raw) so legacy rows are delivered on reconnect. canonicalizeKey moved to ./keys (shared by server + service, no circular import); re-exported from the server module so existing imports/tests are unaffected. 76 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rows Addresses two P1 review comments on #979. 1) handleHistory queried getHistory() with the RAW peerKey while messages are persisted under canonical keys, so an existing conversation returned nothing. Now canonicalizes peerKey for the query (raw form kept for the signed proof; myKey is already canonical). 2) getQueuedMessages looked up only the canonical identity, stranding rows an earlier build queued under a non-canonical recipient key. deliverQueuedMessages now also passes the raw registration key, and getQueuedMessages matches to_key IN (canonical, raw) so legacy rows are delivered on reconnect. canonicalizeKey moved to ./keys (shared by server + service, no circular import); re-exported from the server module so existing imports/tests are unaffected. 76 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a7a9fcd to
aa341d1
Compare
|
Both P1s addressed in 1. History query was noncanonical — 2. Canonical lookup stranded legacy queued rows —
|
…y rows Replaces the read-time dual-lookup (P1 on #979: it only matched the one variant the recipient reconnected with, so a row stored as 0XABC while the peer returns as 0xabc stayed stranded). Any read-time variant matching is incomplete — the fix is to make the stored data canonical: - Migration CanonicalizeL2PSMessageKeys rewrites existing l2ps_messages.from_key/to_key to the canonical form (lower(regexp_replace(k,'^0[xX]',''))), matching canonicalizeKey exactly. Idempotent; runs via migrationsRun. - persistMessage canonicalizes from_key/to_key at the write boundary, so every new row is canonical regardless of caller. - getQueuedMessages / getHistory now do canonical-only lookups; removed the rawKey plumbing from the server. Server keeps in-memory (peer-map/routing) canonicalization; the service owns DB-key canonicalization. 76 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed in The dual-lookup was the wrong shape: any read-time variant matching is incomplete (as you showed — a row stored
Division of responsibility: the server canonicalizes for the in-memory peer map / routing; the service owns canonicalization at the DB boundary (write + read + migrated data). 76 tests pass. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/l2ps-messaging/L2PSMessagingServer.ts`:
- Line 175: Update the catch block around existing.ws.close() in
L2PSMessagingServer to satisfy the no-empty ESLint rule by adding an appropriate
debug log or explanatory comment while preserving the current close-attempt
behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fd6ceff1-fb69-4acf-ae0b-6123685e3e6d
📒 Files selected for processing (5)
src/features/l2ps-messaging/L2PSMessagingServer.tssrc/features/l2ps-messaging/L2PSMessagingService.tssrc/features/l2ps-messaging/keys.tssrc/features/l2ps-messaging/tests/L2PSMessagingServer.test.tssrc/migrations/1782680000000-CanonicalizeL2PSMessageKeys.ts
The re-registration close() sits in a try/catch because closing an already-dead socket can throw; the empty block tripped the no-empty ESLint rule. Add the WHY and an explicit ignore comment. eslint: 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed in |
Follow-up to #978, addressing the review there.
CodeRabbit (Major) — duplicate identities. In #978 the
0xstrip was validation-only; the raw key was still used as the peer identity (peers map,ws.data.publicKey, broadcasts). So0x<key>and<key>registered as two separate peers for one cryptographic key, and re-registration didn't close the other socket. Fixed by canonicalising the identity everywhere.Greptile (P2) — uppercase 0X.
hexToUint8Arrayalready accepts0x/0X, but the register validation only stripped lowercase0x. The canonical form now strips both.Changes
export function canonicalizeKey(key)— strip a leading0x/0X, lowercase.ws.data, online-peer list,registered/peer_joinedpayloads, queued-message delivery),sendrecipient (to), andrequest_public_key(targetId).register:{their-key}:{ts}), so verification is unaffected.Tests: 4 canonicalization cases added (0x/0X/bare → one identity; lowercasing; interior-0x untouched; idempotent). 76 pass.
Honest note:
requestIdecho (from #978) is threaded through all direct responses; verifiable in the diff. Full request/response-level coverage needs driving the real server — the existingintegration.test.tsre-implements a fake server, so a real-server harness is a separate improvement worth a ticket.Base:
stabilisation. Refs DEM-778.Summary by CodeRabbit
Bug Fixes
0x/0Xprefix.Data Migration
Tests