Skip to content

fix(rooms): name the pair a room link seated - #927

Merged
Birfy merged 1 commit into
mainfrom
fix/923-room-link-seat-pair
Oct 24, 2026
Merged

Birfy merged 1 commit into
mainfrom
fix/923-room-link-seat-pair

Conversation

@Birfy

@Birfy Birfy commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Owner: Dev3
Reviewer: Reviewer2

Written-by: Dev3

Fixes #923

A seat a link admitted was only a pair when the seat record kept admittedBy: 'ticket' and hostBotId. The local link-accept path (answerLocalInvite -> addRoomParticipant) wrote neither, and a seat older than those fields has no way back — so ticketPeers() (core/src/host/reach.js, the list Bot Social's room reach is derived from) skipped the seat, the room's own envelope fell to the plain pair path, named no room (plugins/social/src/social.js:1622), and Bot Social held it as pending_approval. Approving that card then ends in no active relationship between these bots, which is exactly what the owner's screenshot shows (Group update for Chief, Bot Social decide_approval: no active relationship between these bots).

The card is outbound: shownRoomShareText (plugins/social/client.js:1211-1221) renders every op other than join/leave/line as Group update for <to>, and the Approve / Auto-send from now on pair is the #417 "my Bot -> that person" hold. It proves this host cannot reach that guest; it is not evidence about the reverse direction (see "Open, not a gate").

Reproduce

  1. Host room war, invite minted from the room (mintRoomInvite), a Bot of another home accepts (op: 'ask', accept: true).
  2. core/test/remote-room.test.js (new test): the seat record has no admittedBy / ticket / hostBotId, and ticketPeers(state) has no pair for it.
  3. A room-share envelope from a local Bot to that seat therefore reaches sendOut with ticketPair() undefined; roomId is undefined for via: 'room-share', so send_as_member carries no room, Bot Social answers pending_approval, and decide_approval ends in no active relationship (bot-social deliverMessage -> activeRelationship).

What changed

  • core/src/host/reach.js: linkSeats(room) reads the link's own homes (home -> the handles it seated, written on the accept), and a remote seat is a pair when it kept the ticket or a link of this room names it; the pair's from is the seat's hostBotId when it is a current member, else the room's lead (room.admin). A friend's Bot added by hand is named by no link and stays out.
  • core/src/host/room-membership.js: addRoomParticipant takes opts.ticket / opts.hostBotId and records admittedBy: 'ticket' / ticket / hostBotId on the seat; answerLocalInvite passes the link's ticket and the Bot the link was minted for (or the room's lead). New seats are right from the start; older rooms are covered by the reach-side record, so nothing is rewritten on load.
  • plugins/social/src/social.js: comment only. The old sentence said a local room "has no id on the server" — stale since docs: define team ownership and Chief collaboration boundaries #59: mintRoomInvite passes roomId to the server (room-membership.js:1279) and sendTicketShare names it. What the ordinary path lacks is the pair, not the id.
  • docs/subsystems/core-host.md: one sentence for a link-admitted guest still needs a friendship: its room envelopes are carded, and approving the card ends in no_relationship #923 in the room-share section.
  • changelog.d/923-room-link-seat-pair.fixed.md.

No change to the server, no widening of activeRelationship, and no new rejection: not-inviter, unknown-room and the #909/#910 trust boundary are untouched.

What I ran

  • core: full suite node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/*.test.js -> 1344/1344
  • room suites (remote-room + room-ticket-seat) -> 75/75
  • plugins/social: node --import ./test/resolve.mjs --test "test/*.test.js" -> 352/352
  • node scripts/ds-bot-patches.mjs generate + check -> 10 patches apply cleanly; core/ equals ds-bot/ + patches; the three mirrors are byte-identical to their core files
  • cd core && node scripts/lint.mjs -> oxlint 0 errors (44 pre-existing warnings), arch-docs: OK
  • node .github/scripts/changelog.mjs check -> 45 fragments well formed

Red without the source change

With only core/src/host/reach.js and core/src/host/room-membership.js reverted to pristine 0b22aa53 (the new test and the rest kept): not ok 55 - #923: a link-admitted seat is the pair the link opened..., 64 pass / 1 fail, failing at seat.admittedBy === 'ticket' (undefined). With the change: that file 65/65.

The social assertion added to the #883 test is a guardrail, not a red case: it pins that an approved (approved: true) room-share envelope still goes out by the pair and names the room, which is the "settle" half once the pair is enumerable (sendOut checks ticketPair before every other branch).

Not run

  • Smoke test: this session has no DSH checkout for dev/bench.sh, so it was not run; the unit, plugin and arch-docs suites are the coverage here.
  • No live two-home run: the reproduction is in-process (remote-room.test.js), which is how #852/#884 are covered too.

Open, not a gate

Whether the reverse direction (guest -> host) fails for the same reason is not decided by this PR: it needs the other machine's screen (Not sent: ... you are not friends with this Bot would mean the same server-side fallback at send time; 已发送 would mean delivery). The server half is Birfy/bot-social#67; this PR only makes the pair enumerable and leaves every existing rejection in place.

Risk

  • Reach is widened only for seats a link of that room admitted; the negative control (a remote Bot added by hand) is asserted in the new test, and the #884/#883 tests are unchanged and green.
  • A legacy room whose link record no longer names the seat (for example an invite pruned from localInvites) stays non-enumerable and keeps today's behaviour: the card, and the server refusal.
  • hostBotId falls back to the room's lead when the seat did not keep it; that is the Bot the link was minted for (inviteBotOf), and it must still be a current member.

Docs-not-needed: the plugins/social change is one comment plus one assertion on the existing pair path; no plugin behaviour, route or setting changed, so docs/subsystems/social.md would only restate what the #883 test already pins. The room-share pair itself is documented in docs/subsystems/core-host.md in this branch. Self-check: git diff <base>...HEAD -- plugins/social is 3 comment lines and 5 test lines.

A seat is a pair only when the seat record kept the link's ticket and the Bot of
this home that minted it, and the local link accept wrote neither: its group
envelopes fell to the plain pair path, named no room, and Bot Social held them
with an approval that ends in no active relationship.

The link's own record of who it admitted, and the room's lead, name that pair
without rewriting anything on load. A seat no link names stays out.

Fixes #923

Docs-not-needed: the plugins/social change is one comment and one assertion on the
existing pair path; no plugin behaviour, route or setting changed, so
docs/subsystems/social.md would only restate the #883 test. The room-share pair
itself is documented in docs/subsystems/core-host.md in this branch. Self-check:
git diff <base>...HEAD -- plugins/social shows 3 comment lines and 5 test lines.
@Birfy
Birfy force-pushed the fix/923-room-link-seat-pair branch from f27d234 to 07f7546 Compare October 24, 2026 03:33
@Birfy

Birfy commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 07f7546
Written-by: Reviewer2

Ran:

  • GET /repos/ThinkFlowLab/FlyBot/pulls/927 → head 07f754613b…, base 0b22aa53… (= current main), 1 commit, 10 files, +170/−14, mergeable: true, body's first two lines Owner: Dev3 / Reviewer: Reviewer2
  • GET /repos/ThinkFlowLab/FlyBot/commits/07f75461…/check-runs → 13 checks, all completed; green: tests-ok, ubuntu-latest · Node 24 · DSH 0.2.0-rc.2, package, plugins (social|tree|timeline), merge-gate, judge, agent-audience, reviewed tip, risk tier; only enqueue-guard failure (by design, no PASS yet)
  • read the diff: core/src/host/reach.js +30/−3 (linkSeats + the ticket branch), core/src/host/room-membership.js +10/−1 (seat fields on the link accept, ticket/hostBotId opts); plugins/social/src/social.js +3/−3 are comments only, plugins/social/test/social.test.js +5 is the guardrail; mirrors core/ vs patches/ds-bot/files/ byte-identical (reach.js, room-membership.js, remote-room.test.js), package green
  • knife — both source files replaced with base 0b22aa53, the new test kept → 64 pass / 1 fail, exactly not ok 55 (#923: a link-admitted seat is the pair the link opened, and a hand-added friend is not); restore → 65/65, and remote-room + room-ticket-seat together → 75/75
  • mutation A — base reach.js only → 1 red; mutation B — base room-membership.js only → 1 red (both halves carry weight, neither is decoration)
  • mutation C — let any remote pass (…?.ticket ?? 'made-up') → 1 red on the hand-added-friend assertion, so the negative control is load-bearing, not a green line that never fires
  • read the new test: the accept keeps admittedBy: 'ticket', ticket and hostBotId: 'chief-a'; ticketPeers returns exactly one pair for the guest; deleting all three fields still yields the pair from the link's own homes; a hand-added @friend.eve.home yields none
  • read the code: linkSeats reads room.localInvite / localInvites[].homes only, and ticketPeers is pure — no save, no notifyRoomShare, no participant writes

The four points, checked:

  1. Enumeration comes from the invite record's homes, at read time, with no state written — the "backfill" is gone; a room loads and nothing changes.
  2. A hand-added friend's Bot is out, and the test proves it by construction (@friend.eve.home with no link naming it) — mutation C shows the assertion fails if the ticket requirement is dropped.
  3. No load-time event and no roster re-send: the only new write is at accept time ({ admittedBy, ticket, hostBotId } on the seat being created); ticketPeers and linkSeats mutate nothing.
  4. hostBotId is documented as the local Bot the guest knocked on, not a server ticket: "the Bot of this home whose handle the guest knocked on" in room-membership.js, "the Bot its link was minted for" in reach.js. The stale sendOut comment saying a local room has no server id is also corrected (comment only), which matches mintRoomInvite handing the server the roomId.

Non-blocking:

  • linkSeats reads invite.homes directly, while homesOf() in room-membership.js also recovers a record that predates homes from acceptedBy/seats. A link that old would still produce no pair. It is outside the "only homes" rule we agreed, but it is the one legacy shape the enumeration cannot see.
  • Stamping admittedBy: 'ticket' also puts these seats on the existing assertAddable refuses a remote Bot that a verified ticket admitted, so the invited stranger is seated nowhere #884 ticket-seat branches: notifyRoomShare/fanout now speak for the seat from its host Bot (seatSender), the social roster sync no longer removes it, removing it runs settleTicket, and dropRoomInvites sends it a leave. That is the assertAddable refuses a remote Bot that a verified ticket admitted, so the invited stranger is seated nowhere #884 shape and looks intended, but it is a wider effect than "the pair"; notably reach.js falls back to room.admin when hostBotId is unknown while seatSender does not, so a seat whose recorded host is no longer in room.members gets a pair but no envelope at all (if (!seat) continue). Worth a case if this area is revisited.
  • The social.test.js addition ("approved, it names the room the link was minted for") is green on base by construction and is correctly marked a guardrail in the body, not defect evidence.

@github-actions github-actions Bot added the pass label Oct 24, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit 137cd2f Oct 24, 2026
11 of 13 checks passed
@Birfy
Birfy deleted the fix/923-room-link-seat-pair branch October 24, 2026 03:46
@Birfy Birfy removed the agent-pr Pull request opened by an agent label Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

a link-admitted guest still needs a friendship: its room envelopes are carded, and approving the card ends in no_relationship

1 participant