docs(teams): design of record for platform Teams & community integration #151

Merged
whitlocktech merged 2 commits from docs/teams into main 2026-08-17 10:00:47 +00:00
Member

What & why

Adds website/TEAMS.md — the design of record for Teams as a core platform primitive rather than
a mechanism for auto-generating Discord infrastructure. A module stays authoritative for who a Team is
and who belongs to it; core owns everything the platform attaches to it (pages, roster, activity,
forums, notifications), and Discord becomes one optional integration surface among possible others.

Game → Module → Team (core) → pages · roster · activity · forums · notifications
                            → optional integrations (Discord today; another platform later)

Also adds a deferred Teams entry to android/PLAN.md § Deferred — recorded with a revival trigger,
deliberately not scheduled, since that milestone list is already behind.

Design only. Nothing is implemented, and MODULE_API.md remains normative — where the two ever
disagree, MODULE_API.md wins and this file is wrong.

Investigated against the working tree, which corrected three premises of the brief

These are in Part 0, because a plan built on the brief's version would have been wrong:

  1. guild.update carries member/online counts, not a roster, and a single leader.
    servuo-plugins/.../BridgeSocial.cs emits integers; module-uo's shard_guilds mirrors that; there
    is no guild.leave at all. So getTeamMembers, getTeamLeaders, the member events, the roster and
    the linked/unlinked split have no data source today — which is why a four-repo protocol change
    (PROTOCOL_VERSION 3 → 4, adding guild.roster + guild.leave) is the gating phase rather than an
    afterthought.
  2. There is no module→core news hook to generalise. ctx.posts is read-only, and both
    registerPostHook and registerAnnounceLeg run core → module. The activity feed therefore needs
    its own ingestion member modelled on ctx.push.publish.
  3. Core has no SSE. All of it left with the module cutover (module-uo/.../shardBroadcast.js), so
    live roster status is module-projected through an extension slot rather than a new core transport.

Two constraints that shaped the rest: the bot is a separate container with no modules volume, so a
module physically cannot put a handler in it (hence definition-in-core, execution-dispatched-over-HTTP);
and notification_subscriptions has no scope dimension, so Team scoping lives in a computed
recipient set rather than in a stream-per-Team the static catalog could never express.

What's settled

  • Four authority paths kept separate — game membership / leadership / forum access / external access
    — with the non-contamination invariant (a manual forum grant never writes the membership projection)
    and the full RG-account → game identity → membership → external identity binding chain.
  • Module unavailability is structurally staleness, never emptiness. Provider methods return
    { ok, complete, … } envelopes rather than bare arrays, so no failure shape can be read as "zero
    teams", plus a two-strikes quarantine on an authoritative-but-empty answer.
  • Reserved-name screening on game-sourced names (role names, the deployment's brand, the project),
    with auto-hide-never-reject and an admin-approval gate scoped to the three actions that publish
    untrusted game-sourced strings — not to staff actions generally.
  • Forum admin controls: an operator on/off switch that guards rather than destroys, and a three-value
    image policy where the author never writes an <img> tag — core's renderer decides, so the policy
    is enforceable and flipping it back un-renders every image with no data migration.
  • Abuse reporting, because core has no user-facing report path at all today and uploads arrive with
    a signed liability acknowledgement.
  • Email as a third notification sink (already built, currently unused) so a web-only user on a
    deployment running neither the app nor Discord isn't silently unreachable.
  • A capability-based integration contract, not a shared interface — Matrix has no
    channel-with-overwrites, no role object, no voice channel (MatrixRTC is a session inside a room needing
    an SFU the homeserver doesn't ship) and no slash-command registration, so of five interface methods a
    Matrix implementation could honestly provide two.

Part 10 — the contract boundary

Added specifically so this isn't ambiguous at implementation time. Every artifact is sorted into
contract / core-internal / public-API / module-owned / wire-protocol, because almost none of this
is contract
: the surface is ten members, and the ~15 tables are core-internal and off limits to a
module even though the module is what populates them
(MODULE_API.md §1.2 — table names are not
contract). It also separates the two version numbers in play: MODULE_API_VERSION 1.6.0 (Phase 2) and
PROTOCOL_VERSION 4 (Phase 1) are independent contracts and neither implies the other.

Proposals this makes

  • MODULE_API_VERSION 1.6.0 — minor, additions only; module-uo's coreApi: "^1.3.0" still resolves.
  • PROTOCOL_VERSION 3 → 4, bumped in overlay.toml in the same PR as the emitters so the installer
    can still pair a bundle.
  • An 11-phase plan (Phase 0 is a throwaway one-guild spike to de-risk the protocol bump), each phase
    independently shippable. Only Phases 1 → 2 are a hard serial dependency; Phase 3 is a natural stopping
    point if a smaller first bet is wanted.

How it was tested

Documentation only — no code, no schema, no routes, so no build, test suite, swagger regeneration or
route manifest is affected.

Verification done while writing:

  • Every claim about current behaviour was read out of the working tree and is cited by file and line
    (BridgeSocial.cs, shard_guilds, pushDispatch.js, sanitizeHtml.js, csp.js, imageUpload.js,
    discordManager.js, docker-compose.yml, registries.js, notification_subscriptions).
  • Two assumptions were checked rather than asserted, and one was wrong: the reconnect-baseline burst
    is not a queue-drop risk — BridgeConfig.QueueCap is 10,000 lines and ~200 guilds is ~200 lines. The
    doc records the real (smaller) concern instead: fat lines against an unbounded read_line.
  • All internal §x.y cross-references resolve; section and part numbering verified after two rounds of
    restructuring.
  • Matrix findings are from current sources (Element MatrixRTC, element-call self-hosting docs), not
    recalled.

Reviewed against the repos at website origin/main e0c961c, module-uo main 97e2fdd,
link main 7b65840, servuo-plugins main c045bdd.

Checklist

  • I have read CONTRIBUTING.md.
  • The change builds and existing tests/checks pass locally. (docs-only; nothing to build)
  • I have added or updated tests/docs where it makes sense. (this PR is the doc)
  • My commits are reasonably scoped with clear messages.

AI-assisted contributions (required)

  • No AI tools were used to produce this contribution.
  • AI tools were used. Tool(s): Claude Code (Opus 5). I have reviewed and understand
    every change, and take responsibility for it. AI-authored commits are
    marked with a Co-Authored-By / Assisted-By trailer.

License

  • I agree that my contribution is licensed under this project's license
    (GNU GPL v3.0 or later), and I have the right to contribute it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz

## What & why Adds `website/TEAMS.md` — the design of record for **Teams as a core platform primitive** rather than a mechanism for auto-generating Discord infrastructure. A module stays authoritative for who a Team is and who belongs to it; core owns everything the platform attaches to it (pages, roster, activity, forums, notifications), and Discord becomes one optional integration surface among possible others. ``` Game → Module → Team (core) → pages · roster · activity · forums · notifications → optional integrations (Discord today; another platform later) ``` Also adds a **deferred** Teams entry to `android/PLAN.md` § Deferred — recorded with a revival trigger, deliberately not scheduled, since that milestone list is already behind. **Design only. Nothing is implemented, and `MODULE_API.md` remains normative** — where the two ever disagree, `MODULE_API.md` wins and this file is wrong. ### Investigated against the working tree, which corrected three premises of the brief These are in Part 0, because a plan built on the brief's version would have been wrong: 1. **`guild.update` carries member/online *counts*, not a roster, and a single leader.** `servuo-plugins/.../BridgeSocial.cs` emits integers; `module-uo`'s `shard_guilds` mirrors that; there is no `guild.leave` at all. So `getTeamMembers`, `getTeamLeaders`, the member events, the roster and the linked/unlinked split have **no data source today** — which is why a four-repo protocol change (`PROTOCOL_VERSION` 3 → 4, adding `guild.roster` + `guild.leave`) is the gating phase rather than an afterthought. 2. **There is no module→core news hook to generalise.** `ctx.posts` is read-only, and both `registerPostHook` and `registerAnnounceLeg` run *core → module*. The activity feed therefore needs its own ingestion member modelled on `ctx.push.publish`. 3. **Core has no SSE.** All of it left with the module cutover (`module-uo/.../shardBroadcast.js`), so live roster status is module-projected through an extension slot rather than a new core transport. Two constraints that shaped the rest: the **bot is a separate container with no `modules` volume**, so a module physically cannot put a handler in it (hence definition-in-core, execution-dispatched-over-HTTP); and **`notification_subscriptions` has no scope dimension**, so Team scoping lives in a computed recipient set rather than in a stream-per-Team the static catalog could never express. ### What's settled - **Four authority paths kept separate** — game membership / leadership / forum access / external access — with the non-contamination invariant (a manual forum grant never writes the membership projection) and the full RG-account → game identity → membership → external identity binding chain. - **Module unavailability is structurally staleness, never emptiness.** Provider methods return `{ ok, complete, … }` envelopes rather than bare arrays, so no failure shape can be read as "zero teams", plus a two-strikes quarantine on an authoritative-but-empty answer. - **Reserved-name screening** on game-sourced names (role names, the deployment's brand, the project), with auto-**hide**-never-reject and an **admin-approval gate** scoped to the three actions that publish untrusted game-sourced strings — not to staff actions generally. - **Forum admin controls**: an operator on/off switch that guards rather than destroys, and a three-value image policy where **the author never writes an `<img>` tag** — core's renderer decides, so the policy is enforceable and flipping it back un-renders every image with no data migration. - **Abuse reporting**, because core has no user-facing report path at all today and uploads arrive with a signed liability acknowledgement. - **Email as a third notification sink** (already built, currently unused) so a web-only user on a deployment running neither the app nor Discord isn't silently unreachable. - **A capability-based integration contract, not a shared interface** — Matrix has no channel-with-overwrites, no role object, no voice channel (MatrixRTC is a session inside a room needing an SFU the homeserver doesn't ship) and no slash-command registration, so of five interface methods a Matrix implementation could honestly provide two. ### Part 10 — the contract boundary Added specifically so this isn't ambiguous at implementation time. Every artifact is sorted into **contract / core-internal / public-API / module-owned / wire-protocol**, because **almost none of this is contract**: the surface is ten members, and the ~15 tables are core-internal and **off limits to a module even though the module is what populates them** (`MODULE_API.md` §1.2 — table names are not contract). It also separates the two version numbers in play: `MODULE_API_VERSION` 1.6.0 (Phase 2) and `PROTOCOL_VERSION` 4 (Phase 1) are independent contracts and neither implies the other. ### Proposals this makes - **`MODULE_API_VERSION` 1.6.0** — minor, additions only; `module-uo`'s `coreApi: "^1.3.0"` still resolves. - **`PROTOCOL_VERSION` 3 → 4**, bumped in `overlay.toml` in the same PR as the emitters so the installer can still pair a bundle. - An **11-phase plan** (Phase 0 is a throwaway one-guild spike to de-risk the protocol bump), each phase independently shippable. Only Phases 1 → 2 are a hard serial dependency; Phase 3 is a natural stopping point if a smaller first bet is wanted. ## How it was tested Documentation only — no code, no schema, no routes, so no build, test suite, swagger regeneration or route manifest is affected. Verification done while writing: - Every claim about current behaviour was read out of the working tree and is cited by file and line (`BridgeSocial.cs`, `shard_guilds`, `pushDispatch.js`, `sanitizeHtml.js`, `csp.js`, `imageUpload.js`, `discordManager.js`, `docker-compose.yml`, `registries.js`, `notification_subscriptions`). - Two assumptions were checked rather than asserted, and one was **wrong**: the reconnect-baseline burst is *not* a queue-drop risk — `BridgeConfig.QueueCap` is 10,000 lines and ~200 guilds is ~200 lines. The doc records the real (smaller) concern instead: fat lines against an unbounded `read_line`. - All internal `§x.y` cross-references resolve; section and part numbering verified after two rounds of restructuring. - Matrix findings are from current sources (Element MatrixRTC, element-call self-hosting docs), not recalled. Reviewed against the repos at `website` `origin/main` `e0c961c`, `module-uo` `main` `97e2fdd`, `link` `main` `7b65840`, `servuo-plugins` `main` `c045bdd`. ## Checklist - [x] I have read [CONTRIBUTING.md](CONTRIBUTING.md). - [x] The change builds and existing tests/checks pass locally. *(docs-only; nothing to build)* - [x] I have added or updated tests/docs where it makes sense. *(this PR is the doc)* - [x] My commits are reasonably scoped with clear messages. ## AI-assisted contributions (required) - [ ] No AI tools were used to produce this contribution. - [x] AI tools were used. Tool(s): `Claude Code (Opus 5)`. I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a `Co-Authored-By` / `Assisted-By` trailer. ## License - [x] I agree that my contribution is licensed under this project's license (**GNU GPL v3.0 or later**), and I have the right to contribute it. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz
wtclaude added 1 commit 2026-08-17 08:54:16 +00:00
Teams become a core platform primitive rather than a mechanism for generating
Discord infrastructure: a module stays authoritative for who a Team is and who
belongs to it, and core owns everything the platform attaches to it — pages,
roster, activity, forums, notifications and optional external integrations.

Investigated against the working tree rather than the brief, which corrected
three of its premises:

- guild.update carries member/online COUNTS, not a roster, and one leader. Every
  membership-derived feature has no data source today, so a protocol change
  (PROTOCOL_VERSION 3 -> 4, guild.roster + guild.leave) is the gating phase.
- There is no module->core news hook to generalise. ctx.posts is read-only and
  both registerPostHook and registerAnnounceLeg run core->module, so the activity
  feed needs its own ingestion member modelled on ctx.push.publish.
- Core has no SSE at all; it left with the module cutover. Live roster status is
  module-projected through an extension slot rather than a new core transport.

Also settled: the four-path permission model (game membership / leadership /
forum access / external access) with the non-contamination invariant; envelope-
returning provider methods so module unavailability is structurally staleness
and never an authoritative empty; reserved-name screening with auto-hide and an
admin-approval gate on the three actions that publish untrusted game-sourced
strings; forum admin controls with a renderer-owned image path; abuse reporting;
email as a third notification sink; and a capability-based integration contract
rather than a shared interface, with the Matrix research behind that choice.

Part 10 sorts every artifact into contract / core-internal / module-owned / wire
protocol, because almost none of this is contract: the surface is ten members,
and the ~15 tables are core-internal and off limits to a module even though the
module is what populates them.

Proposes MODULE_API_VERSION 1.6.0 (minor, additions only) and an 11-phase plan.
Design only — nothing is implemented, and MODULE_API.md remains normative.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz
wtclaude added 1 commit 2026-08-17 10:00:20 +00:00
The drafted text was a placeholder the doc explicitly flagged as needing the org
lead's ownership. Replaced with the supplied wording, which is better in three
ways: it is factual rather than legalistic, it enumerates the specific
responsibilities being accepted (moderation, storage and backups, legal
compliance, community policy) instead of gesturing at them, and it states plainly
that RunicGateway provides no hosted storage or content moderation services.

Recorded as two surfaces rather than one, because they behave differently: a
settings help text that is always on screen and explains the setting, and a
confirmation dialog shown only when changing the mode to uploads, which is what
the acknowledgement actually records.

The dialog carries two checkboxes and the API still takes one `acknowledge: 1`.
Recording two booleans would add nothing — there is no reachable state where an
operator agreed to one clause and not the other and proceeded — while the stored
version is what answers the question that matters later: which text did they
agree to?

Three additions are proposed on top and marked as droppable, since none is
liability language and none changes what is being agreed to: that uploads are
attributed and staff-removable (the reason the attribution table exists), that
disabling uploads later does NOT delete files already uploaded, and that anyone
with forum access can upload — including manually granted accounts with no linked
game identity. Also adds the advisory `remote` mode needs, which the upload
wording correctly does not cover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz
whitlocktech merged commit c3e9a8aeb5 into main 2026-08-17 10:00:47 +00:00
whitlocktech deleted branch docs/teams 2026-08-17 10:00:48 +00:00
Sign in to join this conversation.
No description provided.