Repository navigation
Description
What this is
The FlyBot half of bot-social #43, to be finished now that #50 has landed. A home that joined a platform room by ticket can answer in that room, and nowhere else.
Scope revised 2026-10-23 (owner decision, after triage of the local-ticket path). The first version of this issue assumed one ticket path. There are two, and only one of them needs a room_id:
- A local FlyBot room (
#852/#864,sendShareTo) has noface_roomsrow at all. Its in-room lines areroom-shareenvelopes over plain 1:1 (room-membership.js:1092-1113→flybotSend via: 'room-share'→social.js:1540→send_as_member), and their authority is the seat the ticket created. Addingroom_idthere would be fixing a path that does not exist:roomReachgate 1 (face_roomsrow) fails for such an id, so anyroom_idwe sent would come backroom_not_shared. - The platform room / claimed room case that bot-social
#43describes is the pair that shares only a room: two Bots both current members of an open face room, sending over the 1:1 transport.
Only the second one gets room_id. The first one must not — that is the whole point of the negative control.
Also note what a guest's room looks like on our side: when the host answers a knock with op: 'join', join is not one of the dispatched ops (room-membership.js:1287 ask, :1288 invite, :1304 leave, :1342 line), so it falls through to recordInboundSharedRoom and the guest's room is created as origin: 'inbound-share' with sharedFrom.remoteRoomId = 'war' — a FlyBot room id. No platform room id appears anywhere on that path.
What the platform offers
bot-social #50 (merged 50bbebe4 → master b1b0b7e7) adds room_id to send_as_member and a room-scoped reach: the two Bots are current members of that open room and the room's chat.public grant is alive. Without room_id such a pair is still strangers (no_relationship); naming another room is room_not_shared.
Known gap, tracked separately: on b1b0b7e7 a claimed guest still gets no_relationship with the correct room_id — Birfy/bot-social#59. Acceptance 1 below cannot pass until that lands. Do not paper over it with a mock.
What FlyBot does today
Never passes a room. On main 38629bf4 the three call sites are plugins/social/src/social.js:1540 (the transport for every flybot.bot.send through bot-social), :1731 and :1740 (the owner-typed line). The line numbers in the original report (:1518, :1709, :1718) are stale.
Scope
- Step 0 — the fixture, before any code. The fake server in
plugins/social/test/social.test.js:67answers everysend_as_memberwith a message id, and no test in that file mentionsroom_not_sharedorno_relationship. It must learn room reach first: both Bots current members of that open room and the room'schat.publicgrant alive; noroom_id→no_relationship; another room →room_not_shared. Until then a green suite proves nothing and the negative controls cannot stand up. - Pass
room_idon the platform-room path only — the pair that shares an open face room and sends over 1:1. Never on a local room line, never on a send that is not part of a room. That is the difference between "this ticket opened this room" and a general reach exception. - Map the new refusals:
room_not_shared, andno_relationshipfrom a room-scoped send, each get one sentence instead of a raw platform error.
The four minimum steps that reach the local-ticket path (build the room → mint → redeem → send the first line, with the exact assertions) are in a comment on this issue; run them once to confirm the "no path needs room_id here" reading before writing the platform-room half.
Acceptance
- Gated on bot-social
#59. A guest that claimed a platform room with a ticket, with no prior relationship, sends its first line naming that room, and the inviter's side receives an ordinary peer line. - The same home sending to a Bot outside that room is still refused
no_relationship; a send naming another room getsroom_not_shared. - Once the room is left, ended, or the grant dies, the next send is refused, and the person reads a sentence, not a code.
- Negative control, both ways: the same platform-room conversation sent without
room_idreproduces today's refusal, and a line into a local room never carries aroom_id(assert on the fake server's recordedsend_as_memberarguments, not on the room's shape). - The fixture from step 0 is part of the PR: undoing the far end of the fake (making it answer every send again) must red exactly the room-scoped cases.
Related
FlyBot #853 — cross-repo tracking for a stranger redeeming a link.
FlyBot #852 — the in-repo link for homes that can already reach each other.
bot-social #43 — the platform issue this follows. bot-social #59 — the platform prerequisite for acceptance 1. bot-social #57 item 3 — the same findRoomGrant/roomReach predicate, decided from data.
Activity
Birfy commentedon Oct 23, 2026
Status: not startable yet, so it carries no
agent-ready. Gate: bot-social#50must merge (it is at INC now — the session binds to the newest room of a pair), and this issue gets the master SHA it merged at. Triage will mark it ready then.Birfy commentedon Oct 23, 2026
Gate cleared: bot-social #50 merged (
50bbebe4→ masterb1b0b7e7, 12:50:01Z), and#43closed with it. So the prerequisite named here now exists on master:send_as_membertakesroom_id, and a claimed room is reach is verified per that room (platform.ts:450-456atb1b0b7e7).agent-readyadded. Two facts to start from, measured on FlyBot5db75d07:plugins/social/src/social.js:1518(transport),:1709/:1718(owner-typed line).ask/inviteandanswerLocalInviteare in feat(rooms): answer a local room's invite link (#852) #864 (0d1f5db8, PASS, not merged). Do not rebuild a guest by hand — land feat(rooms): answer a local room's invite link (#852) #864 first, join with a ticket, then reproduce.Nothing else about the scope changed: name the room only on the path that actually carries a room line, and the two negative cases (
no_relationshipoutside the room,room_not_sharedfor another room) stay as acceptance 2.Birfy commentedon Oct 23, 2026
Minimum steps to reproduce (Triage)
Nothing in the product mints or redeems one of these tickets yet, so "run it in the app" is not a step anyone can take today:
mintRoomInviteexists only on the room record (core/src/host/room-membership.js:987),channel.jshas no endpoint, the client never calls it, andparseRoomInviteLinkis referenced from tests and docs only. The smallest real run is therefore the core fixture that already exercises the ticket, plus one call out through the social transport. Run against main38629bf4(after#864).core/test/remote-room.test.js, the#852: the knock asks the room record…block already holds the fixture:host(t, TEAM)→signAliceAndBob(rt)→rt.authenticate(alice.sessionToken)→publish(rt)→grantToFriend(rt, 'chief-a'), then setrt.state.bots['chief-a'].fields = { social: { member: { home: '@alice.home', handle: '@chief.a.home' } } }. The room iswar.const invite = await rt.mintRoomInvite('war')(room-membership.js:987). Expect a 22-characterinvite.ticketandparseRoomInviteLink(invite.url)deep-equal to{ kind: 'local', handle: '@chief.a.home', ticket: invite.ticket }. Note the shape: this link carries a FlyBot handle and a ticket — nofrm_platform room id anywhere in it.rt.applyInboundRoomShare({ text: encodeRoomShare({ op: 'ask', ticket: invite.ticket, accept: true }), fromHandle: '@coder.bob.home', fromHome: '@bob.home', fromName: 'Coder' })→{ accepted: true },acceptedBy === '@coder.bob.home', exactly one remote seat. The host then answers withop: 'join'(notifyRoomShare). On the guest sidejoinis not one of the dispatched ops (:1287ask,:1288invite,:1304leave,:1342line), so it falls through torecordInboundSharedRoomand the guest's room is created asorigin: 'inbound-share'withsharedFrom.remoteRoomId = 'war'— a FlyBot room id, not a platform one.rt.fanoutRoomLine(guestRoom, { who: 'Coder', text: 'hi', authorKind: 'human' }). BecauseguestRoom.origin === 'inbound-share'(:1092-1113), the line goes out assendShareTo(..., 'room-share')→rt.flybotSend({ from, to, text, via: 'room-share' })→ the transport atplugins/social/src/social.js:1540→callTool('send_as_member', { from_bot_id, to, text }). The evidence is that argument object: it carries noroom_id. Assert onserver.callsinplugins/social/test/social.test.js, not on the shape of the room.What these four steps already settle:
38629bf4the three call sites aresocial.js:1540(transport),:1731and:1740(owner-typed line), not:1518/:1709/:1718.addRoomParticipant→assertAddable→publishedRemoteOfdemands it (the@stranger.eve.homecase attest/remote-room.test.js:1404is refusednot-addable). So for this roomroom_idis not what opens the path, and acceptance 1 as written ("the inviter's side receives an ordinary peer line") is met without it.What these four steps do not prove, and must not be reported as proven:
send_as_memberwith a message id (plugins/social/test/social.test.js:67:a => ({ conversation_id: 'c1', message: { message_id: … } })), and no test in that file mentionsroom_not_sharedorno_relationship. Until the fake learns room-scoped reach — both Bots current members of that open room and the room'schat.publicgrant alive; noroom_id→no_relationship, another room →room_not_shared— a green suite says nothing about scope item 2.So run steps 1-4 once and state in one line which path carried the line. If it is the
room-shareenvelope, then theroom_idhalf belongs to a pair that shares only a platform room (the claimed-room case bot-social#43describes), which this local ticket cannot produce. Name the room that needs the field before adding it.Birfy commentedon Oct 23, 2026
Platform prerequisite is now
Birfy/bot-social#59(opened from your four probes; the body carries them verbatim and adds one correction that matters for the fix).Correction to the diagnosis in your message: on
b1b0b7e7aroom_idsend cannot fail insideactiveRelationship(platform.ts:446) —:455(assert(roomRel, 403, "no_relationship")) fires first, andactiveRelationshipis only reached on the 1:1 fallback at:457, i.e. whenroomId === null. SofindActiveRelationship(:874,r.status === 'active') is not on this path.roomReach(:1050-1062) runs four gates, and gates 1-3 are the ones the:450assert already passed; gate 4 isSELECT * FROM relationships WHERE id = ? AND status = 'room', which is stricter thanfindRoomGrant'sr.status IN ('active','room')(:1067) — the same asymmetry as#57item 3.#59asks for one instrumentation run to name the null gate rather than guessing, and says the correction should be decided under#57.For the FlyBot half, go ahead as (a). Your reading that
room_idbelongs only to a pair sharing an open face room, and only on the 1:1 transport path, is consistent with what I measured: a local room (#864sendShareTo) has noface_roomsrow, so anyroom_idwe put on it would failroomReachgate 1.Birfy commentedon Oct 23, 2026
Split (owner decision, (b)): the buildable half is now
#881.#881carries everything that can be decided and pinned today: the fake server learns room reach first (test/social.test.js:67),room_idonly on the platform-room 1:1 path, both refusals in sentences, and the two negative controls (platform-room send withoutroom_idstill refused; a local-room line never carries one).Birfy/bot-social#59lands. It closes when the guest's first line is actually observed end to end, not when the client half merges — that is the#864/#877discipline, and the reason not to mark this done early.Fixes #881plusRefs #866, so the guard has a closable reference and this umbrella is not closed by a half.Nothing in the scope above changes;
#881restates it without the end-to-end acceptance, and this issue keeps that acceptance as its exit condition.Send a room-scoped line by naming the room
Send a room-scoped line by naming the room
Birfy commentedon Oct 24, 2026
Label check 2026-10-24: this issue is still the gate, not a task — it closes when
Birfy/bot-social#59lands, and#59is open. Nothing here is startable inside this repo, so it isneeds-info, with the blocker named in one place rather than inferred from five comments.Unblock condition:
Birfy/bot-social#59merges (room_idaccepted on a claimed room, membership checked against that room), and the master SHA it merged at is recorded here. Then the remaining work — the three call sites that still send no room (plugins/social/src/social.js:1518,:1709,:1718on5db75d07) — becomesagent-readyagain in one edit.The buildable half already shipped as
#881; do not reopen that split.Birfy commentedon Oct 24, 2026
Gate cleared, so this moves forward again. Both blockers are now closed:
Birfy/bot-social#59(closed/completed) and#67(closed by#68, masterc318e19c), and the buildable half split out of here,#881, is closed/completed too.What is left is the whole of it: run acceptance 1–3 end to end on two homes — a guest that claimed a platform room with a ticket, no prior relationship, sending its first line naming that room, and the inviter receiving an ordinary peer line; the platform-room send without
room_idstill refusedno_relationship; another room named →room_not_shared; after leave/end/grant death the next send refused.If that run is green, this issue closes with the evidence and no code change; if a step is red, that step is the ticket, and the failing refusal text goes in the PR body. No
needs-infoleft: the blockers it named are gone.Written-by: Triage
Birfy commentedon Oct 24, 2026
Verification round on the two-home harness, main
c7286584(2026-10-24, no code touched). Probe:selftest/probe866.mjs, two isolated FlyBot homes, Bot Socialsocial.botawaken.com, nothing mocked.Verified live (setup half of acceptance 1):
frm_01M5PF2KWX3WWN5RF0RBSY52FS.https://social.botawaken.com/r/jbEN0AeDcf04IL-TaHPJNXgQXFzo3; the guest home previews it without joining (members=1, host seat only).status=candidate, and the host's room then lists two seats from two homes —@roomhost9vlww.selftesta2.home,@roomguestotn7w.selftestb2.home.So the ticket path itself is exercised end to end; what is still unverified is the send that has to name that room.
Not verified — blocked on the face-join quota, not on code: the same probe then needs a live room with both bots still present, and opening a fresh one hit home A's limit:
flybot/social/face-join: Too many attempts. Try again in 60 minutes.(home A's 5-per-60-min window is spent; earlier rounds of the day used the rest). I stopped there instead of retrying, so acceptance 1's actual send, and acceptance 3's post-leave refusal with real handles, are not yet demonstrated live. Retry at the window reset (~09:12Z), two joins.Argument-level cover that already exists (merged in #886, CI green):
#881: a line between two Bots in one room names that room(both the reply path and the owner-typed line recordroom_id = frm_123456onsend_as_member),#881: a line with no shared room names none,#881: after leaving the room the next line names none again,#881: the two room refusals reach the person as sentences. That is acceptance 2'sroom_not_sharedhalf and acceptance 4's negative controls at the recorded-argument level;room_not_sharedcannot be reached from the plugin live, because the plugin only ever names the one room it found, never a second one.One live observation to keep separate from acceptance 2: a send to a Bot outside the room is refused today by FlyBot's own sharing gate with
… is not shared with that friend, so it cannot message their Bots. To allow it, share … in its settings → Sharing. That is a sentence, but it is the local pre-check, not Bot Social'sno_relationship, so it does not stand in for acceptance 2.Birfy commentedon Oct 24, 2026
Second round on the two-home harness, main
ed60c5a18aec(2026-10-24). Two things to separate: the setup is proven, the 1:1 vehicle is not, and the reason is our own code, not Bot Social.Proven (three independent runs today): two fresh Bots, no relationship; host home opens a platform room with a 6-digit code; host mints a ticket; guest home previews it (host seat only) and claims it with its own Bot (
status=candidate); the room then lists two seats from two homes and the guest home's friend list still has no row for the host Bot.The 1:1 vehicle is refused inside FlyBot before Bot Social is asked:
Path, on
ed60c5a18aec:plugins/social/src/social.js:728— a Bot registered for a face room (register(bot, { forFace: true })) is stored withroomOnly: true.plugins/social/src/social.js:637—activeMember()returns undefined for anyroomOnlymember.plugins/social/src/social.js:1603-1608—sendOut()throws that sentence whenactiveMember(msg.from)is undefined, and only after that does it reachroomContextFor(:3221), the function that decides the room a 1:1 may name (Name the room on a platform-room send: pass room_id on the claimed-room path only #881). So for a ticket-claimed guest the room id is never computed and nosend_as_memberwithroom_idis ever sent.That is deterministic and reproduced twice; it needs no face join to see again. It also matches the comment at
:628-631— "a room-only identity is not polled, does not sign friend sends" — so this may be a deliberate design line rather than a bug.The room-line vehicle does reach Bot Social (same room, same guest Bot):
social/face-send {faceRoomId, botId: guestBotId, text}returns{ typedByOwner: false, messageId: 'fmd_…', status: 'pending_approval', approvalId: 'apr_…' }. Delivery to the inviter side is not verified: that send is held for the guest owner's approval, my approve lookup missed the card on that run, and the next attempt was blocked by the face-join quota (Too many attempts. Try again in 60 minutes; window resets ~11:26Z).So acceptance 1 needs one decision before it can be judged, and it is not mine to make: which vehicle is acceptance 1 about? If it is the room line, the remaining work is a clean approve-and-deliver run (2 joins, next window). If it is the 1:1 that names the room, then a ticket-claimed guest cannot do it today, and the red point is
social.js:1604+:637.#853and#923are both closed and neither tracks this shape, so it is not already covered.Birfy commentedon Oct 24, 2026
Scope split, by the owner's decision in this round: #866 keeps only the in-room half (
face-send → approve → deliver, one clean run in a fresh face-join window). The 1:1 half — "send into a claimed room namesroom_id" — is now #948: a ticket-claimed (roomOnly) Bot is refused atplugins/social/src/social.js:1603-1608beforeroomContextForruns at:1622, so the room path is unreachable for it. No code change in this issue.Birfy commentedon Oct 24, 2026
Room-line half re-run on
d5889968(two isolated homes, real Bot Social, no mocks)Ticket-claimed guest, no prior friendship: A opens a platform room, mints a ticket, B redeems it with its own new Bot.
frm_01M5PTG5CMZJ5KPCR34MHMB2FTstatus=candidate@hostke0lg.selftesta2.home,@guest2q7gl.selftestb2.home{"status":"pending_approval","approvalId":"apr_01M5PTG7BMTZK3CPQW3SRNB9PP"}Correction to my earlier comment (probe bugs, not product findings)
state.roomsandface-rooms.roomsare maps keyed by local room id, not arrays. Myrooms.find(...)helper threw (... .find is not a function); the C-07 failure in the previous run was mine.social/face-send(social.js:3845) callsfaceApi.senddirectly and never records the hold's room, while the UI pathrooms.onFaceLine(:3705) does at:3731(rememberHoldRoom).pendingApprovalsdrops aface_room_messagehold that names no room (:2917), so a line created through that test endpoint is unapprovable by construction. The card flow has to be exercised through the room composer (UI), and I make no claim about it either way from this run.Acceptance 1 is still blocked, but not by bot-social
#59bot-social#59/#67landing did not unblock it. On the same tree,activeMemberstill returnsundefinedfor a room-only member (social.js:637,return member && !member.roomOnly ? member : undefined), so a 1:1sendfrom the guest hitsowner-policybefore anyroom_idis considered — reproduced twice oned60c5a1:That is FlyBot
#948(open, in progress). So acceptance 1 tracks#948, and this issue's room-line half is the part that holds.Birfy commentedon Oct 24, 2026
Split, 2026-10-24 (owner asked that #866 stop being one block held by one person):
What is left here is one verification, now #977 — the room-line approval card through the room's own input box. Everything else is either proven or already moved.
Proven (three runs today,
d5889968, two homes, real Bot Social): room opened, ticket minted and redeemed by the guest's own Bot (status=candidate), two live seats from two homes, the guest's room line accepted withpending_approval.Already covered in code, so there is nothing to split out for a Dev:
plugins/social/test/social.test.js:10389-10407(roomServer(): a send naming the right open room is delivered; noroom_id→no_relationship; another room →room_not_shared) and:10466-10477(#881: both refusals reach the person as sentences);room_idon a 1:1 after leaving" assertions —:10461-10463;social.js:1603-1608refuses aroomOnlymember beforeroomContextForat:1622), not this one.So this issue is no longer dev-actionable, and I am dropping
agent-readyfor that reason — it would only invite a PR that changes nothing. It closes when #977's five steps hold, or when #977 produces a red that becomes its own bug.Written-by: Triage
Birfy commentedon Oct 25, 2026
Written-by: Triage
Closed as completed in this repository. The in-repo half this issue defined has landed; what is left is another repository's acceptance gate, and holding this issue open would mean this repo keeps a lock it cannot turn.
Checked on
main 97065bac:plugins/social/test/social.test.js:12291-12292refusesroom_not_sharedfor a room the pair is not in, and the file now carriesno_relationshipcases too. The fake is no longer the yes-machine the issue opened against.social.js:1818-1822(...(scope ? { room_id: scope } : {})),:1863,:1918,:2161all passroom_id, while:12212/:12229/:12337assert that a local-room line and an out-of-room line carry noroom_id. That is the negative control the issue asked for, asserted on the recorded arguments.social.js:1714-1720mapsroom_not_sharedand room-scopedno_relationshipto sentences.0c487992“Send a room-scoped line by naming the room (Send a room-scoped line by naming the room #886)”.Acceptance 1 (a ticket-claimed guest sending its first line into the room end-to-end against the real platform) is gated on bot-social #59, exactly as this issue said. That is the platform's own acceptance, not ours: it cannot be made green from this tree, it is tracked there, and
#853is the cross-repo pointer. The FlyBot-side check that is still genuinely missing is the approval-card run, and that lives on#977— which is where a red here should go next.