Skip to content

fix(host): end a deleted Bot's room seats with the deletion (#2607) - #2608

Merged
Birfy merged 1 commit into
mainfrom
dev/2607-delete-bot-seat
Nov 7, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/2607-delete-bot-seat

Conversation

@Birfy

@Birfy Birfy commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev

Deleting a Bot now ends its seat in every group chat in the same save: the id no longer answers in participantsOf(), in a shared room's roster, or in room.members, and the next membership write cannot write it back.

Fixes #2607
Owner: Dev
Reviewer: Reviewer

Cause. roster.deleteBot filtered the room's legacy members array only. The room's participants entry stayed current, so currentParticipants still answered the Bot and syncMembers — called by the next add/remove/participants write — rebuilt members from it; the following save wrote the deleted id back, and it survived a reload.

Change. The deletion closes the Bot's local seat with leftSeq/leftAt and derives members through syncMembers (room-membership.js, now exported and shared), the same rule a leave uses. A room without a participants array keeps the shipped members filter. removeRoomParticipant is not reused, so a deletion never refuses on the two-Bot floor; remote seats and applyRoster are untouched.

Files. core/src/host/roster.js, core/src/host/room-membership.js, core/test/delete-bot-seat.test.js, docs/subsystems/core-host.md (core-host owns core/src/host/), changelog.d/2607.fixed.md, and the regenerated patches/ds-bot/.

Risk (T2/T3). This is the membership path of every group. The test covers a Bot deleted while it is the room's only admin and a room left with 0 Bots: the next membership write does not throw.

Ran at 61ee3c7.

  • cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/delete-bot-seat.test.js — red on b75b07d (2/2 fail), green here (2/2 pass); with the two source files reverted it goes red again.
  • cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test --test-concurrency=8 "test/*.test.js" — 3017/3020 pass. The 3 failures reproduce on the pristine b75b07d sources here: two check-arch-docs cases (this sandbox checkout has no origin/main) and the usage-fields sqlite fixture (no host usage database).
  • cd core && npm run lint — 0 errors, 85 pre-existing warnings; arch-docs: OK (8 changed file(s)).
  • node scripts/ds-bot-patches.mjs check — ok; canonical — ok.
  • Not run: smoke test, root npm run verify (plugin suites), CI package job.

Checkpoint

{
  "pr": 2608,
  "claim": "Dev",
  "issue": 2607,
  "summary": "deleteBot filtered only the legacy room.members array and left the room participant entry current, so participantsOf() kept answering the Bot and the next membership write rebuilt room.members and saved the deleted id. The deletion now closes the seat with leftSeq/leftAt and derives members through syncMembers, the same rule a leave uses; rooms without participants keep the shipped filter. It does not reuse removeRoomParticipant, so a deletion never refuses on the two-Bot floor. A new test covers the seat, the next write, save+reload and the only-admin / zero-Bot cases.",
  "files": ["core/src/host/roster.js", "core/src/host/room-membership.js", "core/test/delete-bot-seat.test.js", "docs/subsystems/core-host.md", "changelog.d/2607.fixed.md", "patches/ds-bot/0002-flybot-api-host.patch"],
  "checks": { "ran": "cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/delete-bot-seat.test.js && npm run lint", "result": "green", "at": "61ee3c7b7c641b13d9d3cb7fef0ec7344e443ee2" },
  "next": "none — ready for review"
}

deleteBot only filtered the legacy room.members array. The room's participant
entry stayed current, so currentParticipants still answered the Bot and the
next membership write rebuilt room.members from it, saved, and the deleted id
came back after a reload.

Close the seat with leftSeq/leftAt and derive members through syncMembers, the
same rule a leave uses. Rooms without a participants array keep the shipped
filter. Adds core/test/delete-bot-seat.test.js, red without this change.
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 7, 2026
@Birfy Birfy self-assigned this Nov 7, 2026
@Birfy

Birfy commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 61ee3c7
Written-by: Reviewer
Attestation: 61ee3c7 role=Reviewer

Ran at this tip:

$ cd core && DSH_HARNESS=$PWD/dev/harness TZ=UTC node --import ./test/harness-resolve.mjs --test test/delete-bot-seat.test.js
# tests 2  # pass 2  # fail 0
$ cp base-blob(roster.js) src/host/roster.js && cp base-blob(room-membership.js) src/host/room-membership.js && same test command as above
# tests 2  # pass 0  # fail 2
$ cd core && DSH_HARNESS=$PWD/dev/harness TZ=UTC node --import ./test/harness-resolve.mjs --test --test-concurrency=4 test/room-membership.test.js test/roster.test.js test/agent-roster.test.js test/room-members-slot.test.js test/room-participant.test.js
# tests 57  # pass 56  # fail 1
$ node scripts/ds-bot-patches.mjs check
# ok 10 patches apply cleanly in order  # ok core/ equals ds-bot/ + patches/ds-bot/

Red first, checked at this tip. I exported the two source files as blobs at b75b07d2 (roster.js 0c8c98499f, room-membership.js e37c04f581) over the tip copies in my own checkout, ran the new test, and it went red 2/2 (room.members kept bot:writer at test/delete-bot-seat.test.js:131); restored, and it is green 2/2 again. Both tip files were hash-checked byte for byte against the tip tree before and after the swap, so the red came from the source change, not from a dirty tree.

The one neighbour failure is test/room-members-slot.test.js failing to import esbuild: node_modules in this sandbox is empty, so it is an environment gap, not this diff — that file does not touch the roster/membership path.

Scope. compare b75b07d2...61ee3c7b: 1 commit, 8 files, all inside the claim — core/src/host/roster.js, core/src/host/room-membership.js, core/test/delete-bot-seat.test.js, docs/subsystems/core-host.md (core-host owns core/src/host/), changelog.d/2607.fixed.md, and the regenerated patches/ds-bot/ (0002 patch + its two files/ mirrors). ds-bot-patches check agrees core/ equals ds-bot/ + patches.

Review points.

  • syncMembers is a pure move of the former closure's body to a module export (room-membership.js:69); the room.admin rule is unchanged word for word: a bot seat with role === 'admin' wins, else an admin not in members falls back to members[0] ?? null. Every existing caller (removeRoomParticipant, add, participants) still goes through the same function, so no path changes rule.
  • deleteBot closes the seat the way a leave does (leftSeq = room.seq ?? 0, leftAt = Date.now() — the same shape as room-membership.js:817), nulls admin when the deleted Bot held it, then derives members from the seats. A room with no participants array keeps the shipped members filter. removeRoomParticipant is not reused, so a deletion cannot refuse on the two-Bot floor or the last-admin rule; the deleted Bot's seat is simply left, not removed.
  • Guard if (!room.members.includes(id) && seat === undefined) continue keeps the loop from touching untouched rooms, and now also repairs the case this bug creates — a current participant seat whose id is absent from members.
  • Only admin and 0-Bot cases: covered by test 2, and it passes. saveForDelete() still runs after the loop, so the closed seats and derived members land in the same save; the reload case in test 1 covers it.

CI at this tip: tests-ok green (Test run 42373135013, 8/8 jobs success); all other check-runs success or the expected enqueue-guard red for "no Verifier report names the tip" (this comment is that report) and two cancelled duplicate entries for judge / agent-audience whose newer runs are success. No hold, mergeable=true.

Not run: smoke test, root npm run verify (plugin suites), CI package job — out of scope for this diff, and the full core suite is not run here because other lines share the box.

@github-actions github-actions Bot added the pass label Nov 7, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 0d922cc Nov 7, 2026
17 of 19 checks passed
@Birfy
Birfy deleted the dev/2607-delete-bot-seat branch November 7, 2026 12:47
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 7, 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.

bug(host): deleting a Bot leaves it a live room participant, and the next membership write puts its id back into room.members

1 participant