Skip to content

docs: define team ownership and Chief collaboration boundaries - #59

Merged
hsliuustc0106 merged 1 commit into
mainfrom
docs/team-authority-contract-46
Oct 14, 2026
Merged

hsliuustc0106 merged 1 commit into
mainfrom
docs/team-authority-contract-46

Conversation

@hsliuustc0106

Copy link
Copy Markdown
Collaborator

Summary

Doc-only Magpie follow-up to #45: define the planned multi-human ownership / authority contract before schema work (#47+).

  • New docs/subsystems/team.md — identity (human owner ↔ designated Chief ↔ specialists), invariants, routing matrix with table examples (same-owner delegation, Chief-to-Chief, forbidden cross-owner specialist, team-admin vs private-owner, no-forwarding-to-approval), ownership/disabled-Chief lifecycle, team-mode gate
  • Index + architecture + AGENTS + code-standards pointers; tree.md cross-link only
  • Team mode stays disabled until authorization gates pass
  • Decision: should non-Main tree Bots be allowed to call create_child_bot? (PR #2 review) #16 left open with both Option A and Option B documented — no maintainer pick

Fixes #46

Verification

  • OpenCode (opencode/mimo-v2.6-flash-free) implement
  • npm run arch-docs OK · npm run lint exit 0 · npm test 580/580 (Node 22)
  • No ds-bot/ / patches/ / core/src/ / plugins/ / workflow edits

Out of scope

Implementation, social federation, resolving #16, inventing APIs.

Fixes #46. Planned multi-human authority contract (human owner <-> designated
Chief <-> specialists) written before schema work (#47+); team mode stays
disabled until the documented authorization gates pass, and nothing here
claims multi-human is shipped.

Issue #16 stays open: both Option A (restrict create_child_bot to
root/Main/Chief or require owner approval) and Option B (amend AGENTS.md so
specialists create children within owner limits) are documented as
unresolved; schema and tests must assume neither.

Co-authored-by: hs liu <hsliuustc0106@users.noreply.github.com>
@hsliuustc0106
hsliuustc0106 merged commit 113a73b into main Oct 14, 2026
5 checks passed
@hsliuustc0106
hsliuustc0106 deleted the docs/team-authority-contract-46 branch October 14, 2026 17:11
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 24, 2026
Owner: Dev3
Reviewer: Reviewer2

Written-by: Dev3

Fixes ThinkFlowLab#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 `ThinkFlowLab#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 ThinkFlowLab#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 ThinkFlowLab#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 `ThinkFlowLab#909`/`ThinkFlowLab#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 - ThinkFlowLab#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 `ThinkFlowLab#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 `ThinkFlowLab#852`/`ThinkFlowLab#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 `ThinkFlowLab#884`/`ThinkFlowLab#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 `ThinkFlowLab#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.
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.

[Team] Define the human, Chief and specialist authority contract

1 participant