Repository navigation
Published Bots: only published Bots are reachable from outside (#51) - #87
Conversation
…FlowLab#51) A Bot is reachable from other owners and from friends only if its owner published it. The Chief is published by default; every other Bot (including tree children) is not. Published Bots are connected and pulled into rooms directly, never through the Chief; a Bot that joined a room by its room code (ThinkFlowLab#83) is reachable in that room only. Core (patch 0013-published-reach, with the touched files staying in their patches): - reach.js: `published` flag, `reach()` owner policy, rt.setPublished, rt.isPublished, `bot.published` event, `set-published` endpoint. - hooks.js runs reach() outside the plugin chain: before bot.send, on hold release and on every deliver with a sender or remote origin (resumed, retried, deferred, re-sent), so allow/bypass verdicts cannot skip it. - room-membership.js: a room with a friend's Bot (or several owners) takes published local Bots only; unpublishing or replacing the Chief evicts the Bot at once; fanoutRoomLine checks author and recipient. - authz.js: another owner's Bot joins only if published. Plugins: @flybot/social registers, polls and signs only for published Bots; @flybot/tree denies a friend's Bot toward an unpublished tree Bot, reaches a published one directly (no redirect through the root). Docs: team, ds-bot-core, plugin-runtime, social, tree subsystem pages, READMEs, CHANGELOG. Refs ThinkFlowLab#51, ThinkFlowLab#82.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aeaa342aed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const owner = ownerPolicy(msg) | ||
| if (owner.action === 'deny') return { action: 'deny', reason: owner.reason, by: owner.by } | ||
| const verdict = await evaluate(msg) |
There was a problem hiding this comment.
Recheck owner reach immediately before dispatch
When a sender or target is unpublished while evaluate() is awaiting an asynchronous send middleware, this pre-chain decision becomes stale before line 395; dispatch() marks local deliveries flybotChecked and calls transports directly, so neither path asks ownerPolicy again and the message can still reach a Bot that is no longer published. Re-run the non-bypassable policy after evaluation and immediately before dispatch, including the approval-release path.
Useful? React with 👍 / 👎.
| } | ||
| if (from === undefined || from === null) return ALLOW | ||
| const sender = state.bots?.[from] | ||
| if (sender === undefined) return ALLOW |
There was a problem hiding this comment.
Reject delayed deliveries from removed Bots
When a resumed, retried, deferred, re-sent, or held request still carries the fromId of a Bot deleted after it was queued, sender is undefined and this returns allow. The wrapper then treats the owner policy as passed, while delivery.js accepts the missing sender record and delivers the message as coming from You, so work from a removed Bot can still execute; a non-null unknown local sender must be denied rather than treated like the owner/system no-sender case.
Useful? React with 👍 / 👎.
| } | ||
| disposers.push(flybot.on('bot.created', bot => { void register(bot).catch(error => warn('register failed', error)) })) | ||
| // Publishing gives the Bot its identity; unpublishing only stops it being used. | ||
| disposers.push(flybot.on?.('bot.published', bot => { if (bot.published) void register(bot).catch(error => warn('register failed', error)) }) ?? (() => {})) |
There was a problem hiding this comment.
Feature-detect publication events before subscribing
When this Social plugin runs with a core that satisfies its existing ds-bot >=0.1.5-flybot.1 peer range but predates this commit, bot.isPublished is absent (which published() deliberately supports) while flybot.on still exists and throws for the unknown bot.published event. Optional-call syntax still invokes that method, so createSocial() rejects and the whole plugin becomes unavailable; only subscribe when the publication API/event is actually supported.
Useful? React with 👍 / 👎.
| rt.reach = (msg = {}) => reach(state, { | ||
| from: msg.remote?.handle ? { kind: 'remote', handle: msg.remote.handle } : (msg.from ?? msg.fromId), | ||
| to: msg.to ?? msg.toId, | ||
| roomId: msg.meta?.roomId ?? msg.roomId ?? msg.fromRoomId ?? msg.deliverArgs?.roomId ?? msg.deliverArgs?.fromRoomId, |
There was a problem hiding this comment.
Preserve room context on room-only replies
When a viaCode Bot sends a request from its group Session to another room member, the initial delivery carries fromRoomId and passes the room-only reach exception, but the answer path in turns.js:455-463 sends only sessionId: back. This mapper therefore supplies no roomId, sees the code-joined recipient as unpublished, and drops the reply; carry the sender room on the reply or derive it from the group Session.
Useful? React with 👍 / 👎.
| priority: flybot.bot.send.priority?.APPROVAL ?? 900, | ||
| async handle(msg) { | ||
| if (msg.target?.remote !== true || (msg.to?.transport ?? 'bot-social') !== 'bot-social') return undefined | ||
| if (typeof msg.from === 'string' && !published(msg.from)) return { action: 'deny', reason: `${flybot.bot.get(msg.from)?.name ?? 'this Bot'} is not published, so it cannot message friends' Bots` } |
There was a problem hiding this comment.
Honor room-code sends in Social middleware
When a previously registered specialist is unpublished, re-enters a shared room via viaCode, and becomes the only local speaker, fanoutRoomLine() sends with meta.roomId and the core owner policy explicitly allows that room-scoped local-to-remote send. This unconditional publication check rejects it before transport (and sendOut() likewise hides the retained identity via activeMember), so valid room lines silently never reach the friend's participant; preserve the room-code exception through the Social layer or disallow that room state.
Useful? React with 👍 / 👎.
…wLab#51) Bot settings (next to Invite to this Bot) gets a Published to friends checkbox that calls set-published { id, published }. The toggle stays hidden until PR ThinkFlowLab#87 lands (boolean bot.published, or a missing-endpoint error). Face-to-face join shows whether the brought Bot is published. Co-authored-by: Danyang Chen <birfychen@gmail.com>
#87 registers only published Bots, so joining a face room with an unpublished Bot failed with "has no Bot Social identity yet". Register it on join with the same add_home_member path, keep it room-only (unpublished, not polled, not listed for friends), and revoke that identity when the Bot leaves its last face room. Co-authored-by: Danyang Chen <birfychen@gmail.com>
Implements #51 (rewritten rule). Builds on #82.
Base:
main. #82 is already merged (main =9034e9e, the merge of #82), so this branch is one commit on top of main; no #82 branch is involved.Rule
Other owners' and friends' access is by published Bots only. The Chief is just a Bot that is published by default; every other Bot (tree children included) is unpublished until its owner publishes it. Published Bots (Chief included) can be connected directly and pulled into rooms; unpublished Bots are unreachable from outside; a Bot that joined a room by its room code (#83) is reachable in that room only, while a member. Nothing is forced through the Chief.
What changed
Core (
ds-bot/untouched;patches/ds-bot/0013-published-reach.patchadded withscripts/ds-bot-patches.mjs generate; nothing hand-edited outside the generate flow). The generator maps one file to one patch, so the new filesreach.jsandreach-policy.test.jsare in 0013 and files that already had a patch (hooks, room-membership, authz, channel, index, types, tests) stay in their existing patches.reach.js:state.bots[id].published(default false; the designated Chief is published unless the owner says otherwise; explicit true/false overrides; a Bot whose owner is not a member is never published), thereach()policy,rt.setPublished/rt.isPublished, thebot.publishedevent, theset-publishedendpoint,BotView.published,flybot.bot.isPublished.hooks.js:reach()is not a middleware. It runs before thebot.sendchain, again on hold release, and on everydeliverwith a sender or remote origin (resumed, retried, deferred, re-sent), so a pluginallow/bypasscannot skip it and queued messages are judged by what is true at delivery.room-membership.js: a room with a friend's Bot or several owners takes published local Bots only (assertAddable,applyParticipants); unpublishing or replacing the Chief evicts the Bot at once (evictUnshared);fanoutRoomLinechecks author and recipient and sends from a Bot that may speak.viaCode([Feature] Face-to-face group: Bots join one room by entering the same 6-digit number #83) is an internal option, never read from a request body.authz.js: another owner's Bot joins only if published.social: registers, polls and signs only for published Bots; invites/join name a published Bot; unpublishing keeps the identity unused (not revoked).
tree: a remote sender to an unpublished tree Bot is denied, to a published one it is no opinion (direct, no redirect to the root, any depth); published Bots on both sides are not the tree's call across trees or to non-tree Bots (core decides); parent/child and
stopped/reportunchanged; the prompt tells a child how to answer outside senders. Tests sit beside the plugin.Docs:
docs/subsystems/{team,ds-bot-core,plugin-runtime,social,tree}.md, plugin READMEs, root README(.zh), CHANGELOG.Behaviour changes to be aware of
set-publishedendpoint.FLYBOT_API_VERSIONand package versions are not bumped; plugins treat a missingbot.isPublishedas unknown (tree: no opinion; social: the older all-Bots behaviour).Verification
Tip
aeaa342onBirfy:51-published-reach(Node 24). Run from a worktree with core's harnessnode_modulessymlinked from an existing checkout.cd core && npm test: 663 pass, 0 fail (22 new:reach-policy.test.js14,remote-room.test.js+7,room-membership.test.js+1)plugins/socialnpm test: 39 pass (1 new, 1 rewritten);plugins/treenpm test: 54 pass (5 new, 1 updated, incl. a real-host test)node scripts/ds-bot-patches.mjs generatethencheck: ok (12 patches; core/ equals ds-bot/ + patches)cd core && npm run buildandnode scripts/build-client.mjs --check: ok (no client files changed)npm run lint(core): 0 errors;node scripts/lint-plugins.mjs: 0 errors;ARCH_DOCS_BASE=tfl/main npm run arch-docs: OKnode .github/scripts/check-pack.mjs: ok;tsc --noEmit --strict --skipLibCheck types/*.d.ts: okreach()disabled inhooks.js, 7 of the 14 reach tests and the tree real-host test fail.Refs #51, #82.