Skip to content

feat: consolidate cross-site room-creation federation event#173

Merged
Joey0538 merged 4 commits into
mainfrom
claude/impl-consolidate-room-create-federation
May 18, 2026
Merged

feat: consolidate cross-site room-creation federation event#173
Joey0538 merged 4 commits into
mainfrom
claude/impl-consolidate-room-create-federation

Conversation

@Joey0538
Copy link
Copy Markdown
Collaborator

@Joey0538 Joey0538 commented May 12, 2026

Summary

Consolidates the cross-site room-creation federation event: outbox.{origin}.to.{remote}.member_added does double duty — drives sub creation in inbox-worker (with correct DM/botDM/channel shapes) AND MV update in search-sync-worker. The redundant room_created event is removed entirely.

Originated from @mliu33's review on PR #169: the per-room-creation 2-event federation was a smell.

This PR contains both the spec doc and the implementation in one branch.

The bug, in one sentence

Every room creation emitted two cross-site events per remote site: room_created (consumed by inbox-worker for sub creation) and member_added (consumed by search-sync-worker for MV update). Both carried mostly-overlapping payloads. With one small schema extension on MemberAddEvent, both consumers can share a single event.

Schema change — pkg/model/event.go

Add two omitempty fields to MemberAddEvent:

type MemberAddEvent struct {
    Type               string   `json:"type"`
    RoomID             string   `json:"roomId"`
    RoomName           string   `json:"roomName"`
    Accounts           []string `json:"accounts"`
    SiteID             string   `json:"siteId"`
    JoinedAt           int64    `json:"joinedAt"`
    HistorySharedSince *int64   `json:"historySharedSince,omitempty"`
    Timestamp          int64    `json:"timestamp"`

    // NEW
    RoomType         RoomType `json:"roomType,omitempty"`
    RequesterAccount string   `json:"requesterAccount,omitempty"`
}

Delete RoomCreatedOutbox struct and OutboxTypeRoomCreated constant. MessageTypeRoomCreated stays (distinct constant used for the chat-history sys-message, same string value but unrelated).

Consumer change — inbox-worker/handler.go

handleMemberAdded now dispatches on event.RoomType:

roomType := event.RoomType
if roomType == "" {
    roomType = model.RoomTypeChannel
}
// ...
sub := &model.Subscription{
    RoomType:     roomType,
    Roles:        rolesForType(roomType),
    Name:         subscriptionName(roomType, event.RoomName, event.RequesterAccount, &user),
    IsSubscribed: subscriptionIsSubscribed(roomType, &user),
    ...
}

Empty RoomType defaults to channel for backward-compat. subscriptionName / subscriptionIsSubscribed helpers refactored to take primitives instead of *RoomCreatedOutbox. handleRoomCreated function and its switch arm in HandleEvent deleted. mongo.IsDuplicateKeyError swallowed so JetStream replays after a crashed prior delivery are idempotent (carries over the dup-key fix from #169).

Publisher change — room-worker/handler.go

  • finishCreateRoom: delete the per-remote-site room_created OUTBOX publish; the existing member_added cross-site publish now populates RoomType: room.Type and RequesterAccount: requester.Account.
  • processAddMembers: populate the two new fields on all three member_added emissions (UI fan-out, local INBOX, cross-site OUTBOX).
  • publishSyncDMOutbox: switch from room_created to member_added with the full new schema.

Incidental fix

search-sync-worker/spotlight.go:120 writes RoomType: string(evt.RoomType) into the spotlight ES doc, but MemberAddEvent's wire format didn't carry RoomType until this PR. Once room-worker populates it, the spotlight typeahead UI gets correct room-type info on freshly-indexed rooms for the first time. No code change in search-sync-worker; existing TestSpotlightCollection_BuildAction_MemberAdded already asserts assert.Equal(t, "channel", doc["roomType"]) via baseInboxMemberEvent().

Rollout

Cross-site federation isn't yet integrated end-to-end, so the theoretical mixed-version DM-sub-malformation window during a rolling deploy has no real-world incidence today. Defensive deploy order (room-worker first) recommended but not strictly required. See spec § Rollout for full detail.

Tests

Unit tests only, mirroring #145 / #169 precedent:

  • inbox-worker/handler_test.go: 5 TestHandleRoomCreated* cases replaced with TestHandleMemberAdded_{DM,BotDM,Channel,EmptyRoomType,DuplicateKey}. Helpers refactored to match new primitive signatures.
  • inbox-worker/integration_test.go: 2 room_created integration tests rewritten to go through HandleEvent with member_added payloads.
  • room-worker/handler_test.go + integration_test.go: outbox-payload assertions look for OutboxMemberAdded subjects with full RoomType + RequesterAccount payload. Field-by-field room_created checks dropped.

Diff size

LOC
Spec doc +267 (new)
pkg/model/event.go -1 struct, -1 const, +2 fields
inbox-worker/handler.go -73 (handleRoomCreated deleted, errors import gone)
room-worker/handler.go -28 (room_created publishes removed; new fields added)
Tests net -100 (5 tests replaced with 5 tighter ones; helpers simplified)

8 code files, +190 / -367 = net -177 LOC. Plus the spec doc (+267).

Test plan

  • make lint clean
  • make test clean (full repo)
  • go vet -tags integration ./... clean
  • Per-site post-deploy verification per spec § Rollout: create a federated DM (alice@s1 → bob@s2); verify on s2 that subscriptions doc has Name="alice", Roles=nil, IsSubscribed=false, RoomType=dm. Confirm spotlight roomType field is populated.

Files

  • docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md (NEW)
  • pkg/model/event.go
  • inbox-worker/handler.go, handler_test.go, integration_test.go
  • room-worker/handler.go, handler_test.go, integration_test.go

What's NOT in this PR

  • Search-sync-worker code changes (incidental fix doesn't require any).
  • Backward-compat shim for RoomCreatedOutbox (no out-of-tree consumer exists per grep).
  • MessageTypeRoomCreated constant removal (kept; it's the chat-history sys-message type, unrelated).

Note on commit history

This PR was originally split as PR #172 (spec only) + PR #173 (implementation stacked on the spec branch). Combined here per request for review convenience. PR #172 closed in favor of this one.

https://claude.ai/code/session_01UkLD7hpaypxjeh5zbEWTjp

Summary by CodeRabbit

  • New Features

    • Cross-site room creation now consolidates on a single member-added event; member events include RoomType and optional requester account.
  • Bug Fixes

    • Member-add processing is idempotent: duplicate-key errors are swallowed to prevent replay failures.
  • Refactor

    • Removed the separate room-created outbox path; subscription construction is room-type-aware (empty defaults to channel) and handlers unified.
  • Tests

    • Updated and added unit/integration tests covering room-type behavior, DM/channel subscription naming, payload shape, replay/idempotency, and rollout expectations.
  • Documentation

    • Added rollout, observability, and verification guidance.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Consolidates cross-site room-creation federation onto a single member_added OUTBOX event by extending MemberAddEvent with RoomType and RequesterAccount, removing RoomCreatedOutbox/OutboxTypeRoomCreated. room-worker populates and emits the extended member_added payload; inbox-worker dispatches and builds subscriptions based on RoomType. Tests and integrations updated.

Changes

Consolidate Cross-Site Room-Creation Federation onto Member-Added Event

Layer / File(s) Summary
Design specification and event contract
docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md, pkg/model/event.go, pkg/model/model_test.go
Adds the design doc and extends MemberAddEvent with RoomType and optional RequesterAccount. Removes RoomCreatedOutbox and OutboxTypeRoomCreated and deletes the associated roundtrip test.
Room-worker publisher: emit extended member-added events
room-worker/handler.go, room-worker/handler_test.go, room-worker/integration_test.go
processAddMembers and finishCreateRoom now set RoomType and RequesterAccount on local and cross-site member_added publishes. publishSyncDMOutbox emits OutboxMemberAdded with a MemberAddEvent payload for DMs. Tests updated to assert the new outbox type and fields.
Inbox-worker consumer: dispatch and build subscriptions on RoomType
inbox-worker/handler.go, inbox-worker/handler_test.go, inbox-worker/integration_test.go
Removes the room_created dispatch lane and associated imports. handleMemberAdded defaults empty RoomTypeRoomTypeChannel, uses room-type-aware helpers that accept primitives, early-returns when no subscriptions are built, and swallows duplicate-key bulk-create errors for idempotency. New unit and integration tests cover DM, botDM, channel behaviors, empty-RoomType backward compatibility, and duplicate-key handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hmchangw/chat#169: Modifies the same federation flow (replacing per-remote room_created outboxes with member_added publishes) and overlaps on room-worker/inbox-worker changes.

Suggested reviewers

  • mliu33

Poem

A rabbit peeks at code all night,
swaps two messages for one delight,
MemberAdd now wears RoomType’s crown,
RoomCreated gently steps down,
federation hops — small, tidy, bright 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The linked issue #39 'Claude/review history service 4 ip ap' contains no coding objectives or requirements and appears to be unrelated to this federation consolidation PR. The linked issue provides no specific coding requirements to validate against. Confirm that issue #39 is the intended tracking issue or link a properly documented issue with coding objectives.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: consolidate cross-site room-creation federation event' directly and clearly summarizes the main change: consolidating room-creation federation into a single event.
Out of Scope Changes check ✅ Passed All changes are focused on consolidating room-creation federation: schema updates to MemberAddEvent, publisher changes in room-worker, consumer changes in inbox-worker, and corresponding test updates. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/impl-consolidate-room-create-federation

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Joey0538 Joey0538 changed the title feat(model,room-worker,inbox-worker): consolidate room-create federation feat: consolidate cross-site room-creation federation event May 12, 2026
@Joey0538 Joey0538 changed the base branch from claude/spec-consolidate-room-create-federation to main May 12, 2026 06:20
@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from 9f473cb to c250d68 Compare May 12, 2026 08:20
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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
`@docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md`:
- Around line 251-260: The document contains a duplicated 4-step post-deploy
verification checklist (the block starting with "Create a federated DM (alice@s1
→ bob@s2)..." and listing four checks including "Query the spotlight ES
index..." and "nats stream info OUTBOX_{site}..."); remove the repeated copy so
only a single instance of that checklist remains in the rollout/post-deploy
verification section, leaving the original checklist intact and removing the
exact duplicate paragraph to avoid redundancy.
- Around line 204-220: The spec's heading "Unit only, per spec." contradicts the
integration-test changes described below; update the document to clearly state
both scopes by replacing that line with a concise summary that both unit tests
(in inbox-worker/handler_test.go — e.g.,
TestHandleMemberAdded_Channel_BuildsChannelSub,
TestHandleMemberAdded_DM_BuildsRecipientSubWithCounterpartName,
TestHandleMemberAdded_BotDM_BuildsBotSub,
TestHandleMemberAdded_DuplicateKey_IsIdempotent) are added/modified and
integration tests (in inbox-worker/integration_test.go — e.g.,
TestHandleMemberAddedDM_PersistsCorrectShape_Integration) are updated/deleted as
listed, so readers know unit and integration changes are intentional and
distinct.

In `@inbox-worker/handler_test.go`:
- Around line 1156-1173: The current test
TestHandleMemberAdded_DuplicateKey_IsIdempotent only asserts idempotent behavior
for duplicate-key (11000) bulkCreateErr; add a new test that sets
stubInboxStore.bulkCreateErr to a non-duplicate mongo.WriteException (e.g.,
WriteError with Code != 11000 or a generic error) and assert that calling
Handler.HandleEvent with a member_added OutboxEvent returns an error,
referencing the handler function handleMemberAdded via NewHandler/HandleEvent
and the stubInboxStore.bulkCreateErr field to trigger the "real store failure"
branch.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90745eeb-10f8-4ddb-b5dc-ee4debcf46d1

📥 Commits

Reviewing files that changed from the base of the PR and between f5189d3 and c250d68.

📒 Files selected for processing (9)
  • docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md
  • inbox-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/integration_test.go
  • pkg/model/event.go
  • pkg/model/model_test.go
  • room-worker/handler.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/model/model_test.go

Comment thread docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md Outdated
Comment thread docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md Outdated
Comment thread inbox-worker/handler_test.go
@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from c250d68 to d998609 Compare May 12, 2026 08:28
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
inbox-worker/integration_test.go (1)

450-451: ⚡ Quick win

Include SiteID/DestSiteID in OutboxEvent fixtures for production-shape parity.

These tests currently pass minimal events; adding routing fields makes them more robust against future handler logic that may branch on federation metadata.

Suggested test-fixture update
-evt, err := json.Marshal(model.OutboxEvent{Type: "member_added", Payload: payload})
+evt, err := json.Marshal(model.OutboxEvent{
+	Type:       "member_added",
+	SiteID:     "site-A",
+	DestSiteID: "site-B",
+	Payload:    payload,
+})
-evt, err := json.Marshal(model.OutboxEvent{Type: "member_added", Payload: payload})
+evt, err := json.Marshal(model.OutboxEvent{
+	Type:       "member_added",
+	SiteID:     "site-A",
+	DestSiteID: "site-B",
+	Payload:    payload,
+})

Also applies to: 486-487

🤖 Prompt for 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.

In `@inbox-worker/integration_test.go` around lines 450 - 451, Update the
OutboxEvent test fixtures to include federation routing fields so they mirror
production shape: when creating model.OutboxEvent instances (e.g., the
member_added event constructed in integration_test.go where evt is built via
json.Marshal(model.OutboxEvent{Type: "member_added", Payload: payload})), add
SiteID and DestSiteID fields with appropriate test values (and repeat the same
change for the other occurrence around lines 486–487). Ensure you set these
fields on every OutboxEvent fixture used by the tests so handlers that branch on
SiteID/DestSiteID see production-like events.
🤖 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
`@docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md`:
- Around line 166-167: Update the spec text so the testing and implementation
references match the actual changes: replace the mention of a new
search-sync-worker/spotlight_test.go and the single
TestHandleMemberAddedDM_PersistsCorrectShape_Integration entry with the two
inbox-worker integration tests added (TestHandleRoomCreatedPersistsRemoteSubs
and TestHandleRoomCreatedDM_PersistsRemoteCounterpartSub), and remove any stale
reference to the search-sync test; also update the implementation summary for
room-worker/handler.go::finishCreateRoom to state that the per-remote-site
room_created publish was removed and that RoomType and RequesterAccount were
added to the existing member_added publishes (both local-INBOX and cross-site
OUTBOX); apply the same corrections in the other duplicated sections that
currently repeat the outdated test/spec text.

---

Nitpick comments:
In `@inbox-worker/integration_test.go`:
- Around line 450-451: Update the OutboxEvent test fixtures to include
federation routing fields so they mirror production shape: when creating
model.OutboxEvent instances (e.g., the member_added event constructed in
integration_test.go where evt is built via json.Marshal(model.OutboxEvent{Type:
"member_added", Payload: payload})), add SiteID and DestSiteID fields with
appropriate test values (and repeat the same change for the other occurrence
around lines 486–487). Ensure you set these fields on every OutboxEvent fixture
used by the tests so handlers that branch on SiteID/DestSiteID see
production-like events.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a0f6c20-f130-4afd-8110-f35065b8320d

📥 Commits

Reviewing files that changed from the base of the PR and between c250d68 and d998609.

📒 Files selected for processing (9)
  • docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md
  • inbox-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/integration_test.go
  • pkg/model/event.go
  • pkg/model/model_test.go
  • room-worker/handler.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/model/model_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • pkg/model/event.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go
  • room-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/handler.go

Comment thread docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md Outdated
@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from d998609 to df2359a Compare May 12, 2026 08:33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
room-worker/integration_test.go (1)

648-673: ⚡ Quick win

Add parity assertions for site-C member_added payload fields.

Line 660/Line 663 validate RoomType and RequesterAccount only for site-B. Mirroring those checks for site-C tightens the federation contract test and catches partial fan-out regressions.

Suggested test addition
 var memberEnvC model.OutboxEvent
 require.NoError(t, json.Unmarshal(memberC[0].data, &memberEnvC))
 var memberPayloadC model.MemberAddEvent
 require.NoError(t, json.Unmarshal(memberEnvC.Payload, &memberPayloadC))
 assert.ElementsMatch(t, []string{"ian"}, memberPayloadC.Accounts)
+assert.Equal(t, model.RoomTypeChannel, memberPayloadC.RoomType)
+assert.Equal(t, "alice", memberPayloadC.RequesterAccount)
 assert.Equal(t, reqID+":site-C", memberC[0].msgID)
🤖 Prompt for 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.

In `@room-worker/integration_test.go` around lines 648 - 673, The site-C
member_added payload (memberPayloadC from memberEnvC/memberC) currently only
asserts Accounts and msgID; add parity assertions mirroring site-B: check
memberPayloadC.RoomType equals model.RoomTypeChannel, memberPayloadC.RoomName
equals "deal team", memberPayloadC.SiteID equals "site-A",
memberPayloadC.RequesterAccount equals "alice", and assert
memberPayloadC.HistorySharedSince is nil so the site-C payload is validated the
same as site-B.
docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md (1)

180-183: ⚡ Quick win

Make the idempotency section declarative (it currently contradicts itself).

Line 180 says “No change,” then Line 182 says handling must be added to handleMemberAdded. Please rewrite this as final-state behavior only.

Suggested wording cleanup
- No change. The unique index on `subscriptions.(roomId, u.account)` already handles concurrent or redelivered creates idempotently. The dedup-key fall-through fix from PR `#169` (CodeRabbit's catch) still applies — `mongo.IsDuplicateKeyError` is swallowed and execution continues so search-sync-worker's MV update still fires on replays.
-
- Wait — that fix is in `handleRoomCreated`. After deleting `handleRoomCreated`, the dup-key handling needs to be present in `handleMemberAdded` too.
+ Idempotency behavior is preserved: the unique index on `subscriptions.(roomId, u.account)` remains the source of truth, and duplicate-key errors are intentionally swallowed in `handleMemberAdded` so JetStream replays do not cause retry loops.

Also applies to: 190-198

🤖 Prompt for 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.

In
`@docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md`
around lines 180 - 183, Rewrite the "idempotency" section to state the final
intended behavior (declarative), removing contradictory language like "No
change" vs. "must be added": state that the unique index on
subscriptions.(roomId, u.account) makes concurrent or redelivered creates
idempotent; assert that the dedup-key fall-through fix (the
mongo.IsDuplicateKeyError swallow/continue behavior introduced in PR `#169`) must
be applied wherever room/member create logic runs, including both
handleRoomCreated and handleMemberAdded; update the text to explicitly require
that handleMemberAdded must ignore duplicate-key errors (not return them) so
search-sync-worker MV updates still fire on replays, and remove any legacy
phrasing that implies partial or conditional edits.
🤖 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.

Nitpick comments:
In
`@docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md`:
- Around line 180-183: Rewrite the "idempotency" section to state the final
intended behavior (declarative), removing contradictory language like "No
change" vs. "must be added": state that the unique index on
subscriptions.(roomId, u.account) makes concurrent or redelivered creates
idempotent; assert that the dedup-key fall-through fix (the
mongo.IsDuplicateKeyError swallow/continue behavior introduced in PR `#169`) must
be applied wherever room/member create logic runs, including both
handleRoomCreated and handleMemberAdded; update the text to explicitly require
that handleMemberAdded must ignore duplicate-key errors (not return them) so
search-sync-worker MV updates still fire on replays, and remove any legacy
phrasing that implies partial or conditional edits.

In `@room-worker/integration_test.go`:
- Around line 648-673: The site-C member_added payload (memberPayloadC from
memberEnvC/memberC) currently only asserts Accounts and msgID; add parity
assertions mirroring site-B: check memberPayloadC.RoomType equals
model.RoomTypeChannel, memberPayloadC.RoomName equals "deal team",
memberPayloadC.SiteID equals "site-A", memberPayloadC.RequesterAccount equals
"alice", and assert memberPayloadC.HistorySharedSince is nil so the site-C
payload is validated the same as site-B.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6b82080-fc2d-4900-a833-9813eae0430d

📥 Commits

Reviewing files that changed from the base of the PR and between d998609 and df2359a.

📒 Files selected for processing (9)
  • docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md
  • inbox-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/integration_test.go
  • pkg/model/event.go
  • pkg/model/model_test.go
  • room-worker/handler.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/model/model_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/model/event.go
  • room-worker/handler_test.go
  • inbox-worker/integration_test.go
  • room-worker/handler.go
  • inbox-worker/handler_test.go

@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from df2359a to fbc6c7d Compare May 12, 2026 08:37
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md (1)

38-39: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve the search-sync test contradiction in the spec.

Line 39 says a new regression test was added, but later sections state no new search-sync-worker tests were added. Keep one consistent statement to avoid confusion in review and rollout notes.

📝 Suggested doc fix
-- **Search-sync-worker code changes.** Zero structural change required. One regression test added to lock in the latent-bug fix.
+- **Search-sync-worker code changes.** Zero structural change required. No new tests were added; existing spotlight coverage already asserts `roomType`.

Also applies to: 176-177

🤖 Prompt for 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.

In
`@docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md`
around lines 38 - 39, The doc contains a contradiction about search-sync-worker
tests: the heading "Search-sync-worker code changes. Zero structural change
required. One regression test added to lock in the latent-bug fix." conflicts
with later lines saying no new search-sync-worker tests were added; pick one
consistent statement and update all occurrences (e.g., the heading and the later
section at lines referencing "search-sync-worker tests" or "regression test") so
the spec either consistently declares that a regression test was added or
consistently declares no new tests were added, and adjust any accompanying
explanation accordingly.
🤖 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.

Duplicate comments:
In
`@docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md`:
- Around line 38-39: The doc contains a contradiction about search-sync-worker
tests: the heading "Search-sync-worker code changes. Zero structural change
required. One regression test added to lock in the latent-bug fix." conflicts
with later lines saying no new search-sync-worker tests were added; pick one
consistent statement and update all occurrences (e.g., the heading and the later
section at lines referencing "search-sync-worker tests" or "regression test") so
the spec either consistently declares that a regression test was added or
consistently declares no new tests were added, and adjust any accompanying
explanation accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e1e8f3e3-156b-4824-93a8-5fe889250ba7

📥 Commits

Reviewing files that changed from the base of the PR and between df2359a and fbc6c7d.

📒 Files selected for processing (9)
  • docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md
  • inbox-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/integration_test.go
  • pkg/model/event.go
  • pkg/model/model_test.go
  • room-worker/handler.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/model/model_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • pkg/model/event.go
  • inbox-worker/handler.go
  • inbox-worker/handler_test.go
  • room-worker/handler.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go

@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from fbc6c7d to 22c0346 Compare May 12, 2026 08:40
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
`@docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md`:
- Around line 146-148: The example's handleMemberAdded block currently returns
any error from h.store.BulkCreateSubscriptions but the idempotency section
expects duplicate-key errors to be ignored for JetStream replay; update
handleMemberAdded to detect and swallow duplicate-key/unique-constraint errors
from BulkCreateSubscriptions (while returning other errors) so the main example
and idempotency snippet are consistent, then remove or condense the separate
idempotency snippet (lines ~182–188) to avoid duplication; refer to the
handleMemberAdded function and the h.store.BulkCreateSubscriptions call when
making this change.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6805e834-143d-4e7d-b6b1-d8c3e5b5ced3

📥 Commits

Reviewing files that changed from the base of the PR and between fbc6c7d and 22c0346.

📒 Files selected for processing (9)
  • docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md
  • inbox-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/integration_test.go
  • pkg/model/event.go
  • pkg/model/model_test.go
  • room-worker/handler.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/model/model_test.go
✅ Files skipped from review due to trivial changes (1)
  • room-worker/handler_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • pkg/model/event.go
  • room-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/handler.go
  • inbox-worker/integration_test.go
  • room-worker/integration_test.go

@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from 22c0346 to 9bd35c1 Compare May 12, 2026 08:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
inbox-worker/integration_test.go (1)

439-448: ⚡ Quick win

Populate JoinedAt in the member_added fixtures to avoid epoch defaults.

Both new event fixtures omit JoinedAt, so this path currently exercises JoinedAt=0 (1970-01-01) instead of realistic create-time behavior.

💡 Proposed test-fixture update
 payload, err := json.Marshal(model.MemberAddEvent{
   Type:             "member_added",
   RoomID:           "r_xyz",
   RoomName:         "deal team",
   RoomType:         model.RoomTypeChannel,
   Accounts:         []string{"bob", "ian"},
   SiteID:           "site-A",
   RequesterAccount: "alice",
+  JoinedAt:         time.Now().UTC().UnixMilli(),
   Timestamp:        time.Now().UTC().UnixMilli(),
 })
 payload, err := json.Marshal(model.MemberAddEvent{
   Type:             "member_added",
   RoomID:           roomID,
   RoomName:         "",
   RoomType:         model.RoomTypeDM,
   Accounts:         []string{"bob"},
   SiteID:           "site-A",
   RequesterAccount: "alice",
+  JoinedAt:         time.Now().UTC().UnixMilli(),
   Timestamp:        time.Now().UTC().UnixMilli(),
 })

Also applies to: 480-489

🤖 Prompt for 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.

In `@inbox-worker/integration_test.go` around lines 439 - 448, The member_added
test fixtures construct model.MemberAddEvent without setting JoinedAt, causing
JoinedAt to be 0; update the fixtures (the MemberAddEvent instances used in the
member_added tests) to set JoinedAt to a realistic timestamp (e.g.,
time.Now().UTC().UnixMilli() or the same Timestamp value used) so the tests no
longer exercise epoch defaults; ensure you update all occurrences (including the
second fixture referenced near lines 480-489) to assign JoinedAt on the struct
literal.
🤖 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.

Nitpick comments:
In `@inbox-worker/integration_test.go`:
- Around line 439-448: The member_added test fixtures construct
model.MemberAddEvent without setting JoinedAt, causing JoinedAt to be 0; update
the fixtures (the MemberAddEvent instances used in the member_added tests) to
set JoinedAt to a realistic timestamp (e.g., time.Now().UTC().UnixMilli() or the
same Timestamp value used) so the tests no longer exercise epoch defaults;
ensure you update all occurrences (including the second fixture referenced near
lines 480-489) to assign JoinedAt on the struct literal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7730e5b3-f2cb-481e-8ac9-87b9603ef2cc

📥 Commits

Reviewing files that changed from the base of the PR and between fbc6c7d and 9bd35c1.

📒 Files selected for processing (9)
  • docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md
  • inbox-worker/handler.go
  • inbox-worker/handler_test.go
  • inbox-worker/integration_test.go
  • pkg/model/event.go
  • pkg/model/model_test.go
  • room-worker/handler.go
  • room-worker/handler_test.go
  • room-worker/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/model/model_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • room-worker/handler_test.go
  • inbox-worker/handler.go
  • pkg/model/event.go
  • room-worker/integration_test.go
  • room-worker/handler.go
  • inbox-worker/handler_test.go

@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from 9bd35c1 to 79283a0 Compare May 12, 2026 08:55
Copy link
Copy Markdown
Collaborator

@mliu33 mliu33 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, thanks!

claude added 4 commits May 18, 2026 02:06
Drop the redundant outbox.{origin}.to.{remote}.room_created event in
favor of the existing outbox.{origin}.to.{remote}.member_added event
(added in PR #169) doing double duty: drive sub creation in inbox-worker
AND MV update in search-sync-worker, mirroring the add-members path
which already works that way since PR #145.

Extend MemberAddEvent with RoomType + RequesterAccount so
inbox-worker.handleMemberAdded can build correctly-shaped DM/botDM subs
via the existing helpers (subscriptionName / rolesForType /
subscriptionIsSubscribed) instead of needing a separate handleRoomCreated
path.

Full removal of room_created event, model, handler, and tests. Incidental
benefit: heals a latent search-sync-worker bug where the spotlight ES
doc's roomType field has been empty since PR #145 because today's
MemberAddEvent wire format doesn't carry RoomType.

https://claude.ai/code/session_01UkLD7hpaypxjeh5zbEWTjp
- Line 160: "consts" -> "constants" (style)
- Line 208: "sub shape" -> "sub-shape" (hyphenation)
- Rollout section: rewrite to honestly document the
  no-fully-safe-single-PR-deploy-order issue CodeRabbit flagged.
  Walks both deploy orders showing the malformed-DM window each
  produces. Presents three options (A: ship as-is, B: 2-PR split,
  C: single PR + follow-up cleanup) with a recommendation for
  option C. Marks this as an open question requiring user input
  before implementation begins.

https://claude.ai/code/session_01UkLD7hpaypxjeh5zbEWTjp
User confirmed cross-site federation is not yet integrated end-to-end,
so the theoretical mixed-version DM-sub-malformation window has no
real-world incidence today. Reverting the spec to option (A):
single PR ships both publisher and consumer changes; deploy order
(room-worker first) is defensive rather than strictly required.

Trims the option-discussion text and rolls the deploy-window risk into
the Risks section with explicit acknowledgment that it's theoretical
under current operational reality.

https://claude.ai/code/session_01UkLD7hpaypxjeh5zbEWTjp
Single cross-site event for room creation: outbox.{origin}.to.{remote}.
member_added does double duty — drives sub creation in inbox-worker
(with correct DM/botDM/channel shapes) AND MV update in
search-sync-worker. Drops the redundant room_created event entirely.

Schema (pkg/model/event.go):
- MemberAddEvent gains RoomType + RequesterAccount (both omitempty).
- Delete RoomCreatedOutbox struct.
- Delete OutboxTypeRoomCreated constant.
- MessageTypeRoomCreated stays — distinct system-message-type constant
  used by room-worker's publishChannelSysMessages, unrelated to
  federation.

Consumer (inbox-worker/handler.go):
- handleMemberAdded dispatches on event.RoomType. Empty RoomType
  defaults to RoomTypeChannel for backward-compat with pre-deploy
  publishers that didn't set the field.
- subscriptionName / subscriptionIsSubscribed helpers refactored to
  take primitives (roomType, roomName, requesterAccount, *user)
  instead of *RoomCreatedOutbox, so handleMemberAdded can call them.
- Duplicate-key BulkCreateSubscriptions errors swallowed (replay
  after a crashed prior delivery is idempotent — matches PR #169 fix).
- handleRoomCreated function deleted.
- case model.MessageTypeRoomCreated arm in HandleEvent switch deleted.

Publisher (room-worker/handler.go):
- finishCreateRoom: delete the per-remote-site room_created OUTBOX
  publish. Cross-site member_added publish now carries RoomType +
  RequesterAccount.
- finishCreateRoom local INBOX publish: same fields populated for
  consistency (search-sync-worker reads them).
- processAddMembers: populate RoomType + RequesterAccount on all
  three member_added publishes (UI fan-out, local INBOX, cross-site
  OUTBOX). Channels-only path, but consistent shape avoids surprises.
- publishSyncDMOutbox: switch from room_created to member_added with
  the full new schema.

Tests:
- inbox-worker/handler_test.go: replace 5 TestHandleRoomCreated* tests
  with TestHandleMemberAdded_DM/BotDM/Channel/EmptyRoomType/
  DuplicateKey cases. Helpers refactored to match new signatures.
- inbox-worker/integration_test.go: replace 2 room_created integration
  tests with member_added equivalents going through HandleEvent.
- room-worker/handler_test.go + integration_test.go: assertions on
  cross-site outboxes now look for OutboxMemberAdded subjects with
  full RoomType + RequesterAccount payload.

Incidental fix: search-sync-worker.spotlight.go has been writing an
empty `roomType` field to the spotlight ES doc since PR #145 because
MemberAddEvent's wire format didn't carry RoomType. Once room-worker
starts populating RoomType, the spotlight doc gets correct roomType
for the first time. No code change in search-sync-worker; existing
TestSpotlightCollection_BuildAction_MemberAdded asserts the correct
value.

Spec: docs/superpowers/specs/2026-05-12-consolidate-room-create-federation-design.md

https://claude.ai/code/session_01UkLD7hpaypxjeh5zbEWTjp
@Joey0538 Joey0538 force-pushed the claude/impl-consolidate-room-create-federation branch from 79283a0 to 88f7c66 Compare May 18, 2026 02:16
@Joey0538 Joey0538 merged commit 7d6180b into main May 18, 2026
6 checks passed
general-lex pushed a commit that referenced this pull request May 20, 2026
…tract

PR #173 (federation consolidation) reshaped model.CreateRoomRequest
and moved the room-creation subject from chat.user.<account>.request.rooms.create
to chat.user.<account>.request.room.<siteID>.create. Update the
"creates channel room" step accordingly:

- Use subject.RoomCreate(account, siteID) — the wildcard room-service
  now subscribes to.
- Drop removed fields Type/CreatedBy/CreatedByAccount/SiteID/Members
  from the payload. Send only Name + RequesterAccount, which the new
  handler classifies as a channel (requesterAccount is parsed from
  the subject; user/org/channel lists empty = channel by Name).

Restores compilation of the godog test binary; all 7 scenarios are
discovered and execute (failing at network as expected against the
example.invalid stub URLs).

Note: the new handler also requires the requester to exist in Mongo
via store.GetUser — a fixture the suite cannot establish in Part-1.
The scenario will surface ErrUserNotFound (HandlerError class) when
run against a live stack; a follow-up should either tag it
@blindSpot:room-create-needs-user-fixture or add the Mongo fixture
once Part-2 state-observation primitives land.

https://claude.ai/code/session_0139upFqMPspygX8XqTjpRN1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants