docs(teams): phase 3 — Teams is a contract, not a surface #156

Merged
whitlocktech merged 2 commits from docs/teams-phase3 into edge 2026-08-18 02:09:34 +00:00
Member

Teams phase 3's docs. Pairs with website#152 and Module-uo#11.

The headline correction

§3.1, §3.4 and §3.5 are superseded, marked in place. They put four public pages, two extension
slots and three nav rows in core. The org lead's correction: Teams is the platform primitive the
API contract exposes; the module builds the pages on it.
module-uo builds guilds, the Rust module
will build clans — core does not own the word for a Team, so a core page under a noun core invented
would have sat beside module-uo's own /uo/guilds saying the same thing in the wrong vocabulary.

Core keeps the tables, the sync, the access resolver, the activity feed and the whole API.
/admin/teams stays: an operator inspecting the primitive is looking at the primitive.

MODULE_API.md §3.7a — a new direction

Because core renders no Team page, the extension slots invert, and that is a genuinely new shape
rather than a rename. New §3.7a documents it: what forced it (a core primitive whose vocabulary
core does not own), the enforced namespace, why core's fills are applied at mount rather than eagerly
— core's bundle evaluates before every module chunk, so the slot does not exist when core registers —
and why a fill for an undeclared slot is a no-op where §3.7's unknown slot throws. The
asymmetry is real: there, core declares first, so an unknown name is always a typo.

1.6.0's list swaps the two client slots for registry.declareModuleSlot(...) and Slot as the
eighth member of the UI kit. Still eight additions, still amended in place rather than bumped —
a contract owes a bump only once it has landed on main.

The rest of the phase 3 amendment (Part 12)

Five corrections found by building:

  1. §3.2 and §3.4 contradicted each other about the row slot's props, and §3.2 — the security rule
    — wins.
  2. §3.3's projection is an eighth MODULE_API member where 1.6.0 listed seven.
  3. "the module declines" needed splitting in two, or bare core serves an empty roster on every
    page.
  4. The module answers with member keys, not rows, so it can narrow what is published and never
    widen it.
  5. Core's five activity kinds are four until the forum lands, and a Team's first roster emits
    no join items at all.

BACKEND_DESIGN.md

The seventh Team table and its retention; the three public routes' new behaviour; and
GET /teams/by-external/:moduleId/:externalId, which exists so a module can find core's Team without
holding core's identifiers.


  • AI-assisted: written with Claude Code (Claude Opus 5).
Teams phase 3's docs. Pairs with website#152 and Module-uo#11. ## The headline correction **§3.1, §3.4 and §3.5 are superseded, marked in place.** They put four public pages, two extension slots and three nav rows in core. The org lead's correction: **Teams is the platform primitive the API contract exposes; the module builds the pages on it.** module-uo builds guilds, the Rust module will build clans — core does not own the word for a Team, so a core page under a noun core invented would have sat beside module-uo's own `/uo/guilds` saying the same thing in the wrong vocabulary. Core keeps the tables, the sync, the access resolver, the activity feed and the whole API. `/admin/teams` stays: an operator inspecting the primitive is looking at the primitive. ## `MODULE_API.md` §3.7a — a new direction Because core renders no Team page, the extension slots invert, and that is a genuinely new shape rather than a rename. New §3.7a documents it: what forced it (a core primitive whose *vocabulary* core does not own), the enforced namespace, why core's fills are applied at mount rather than eagerly — core's bundle evaluates before every module chunk, so the slot does not exist when core registers — and why a fill for an undeclared slot is a **no-op** where §3.7's unknown slot **throws**. The asymmetry is real: there, core declares first, so an unknown name is always a typo. 1.6.0's list swaps the two client slots for `registry.declareModuleSlot(...)` and `Slot` as the eighth member of the UI kit. Still eight additions, still amended in place rather than bumped — a contract owes a bump only once it has landed on `main`. ## The rest of the phase 3 amendment (Part 12) Five corrections found by building: 1. **§3.2 and §3.4 contradicted each other** about the row slot's props, and §3.2 — the security rule — wins. 2. **§3.3's projection is an eighth `MODULE_API` member** where 1.6.0 listed seven. 3. **"the module declines" needed splitting in two**, or bare core serves an empty roster on every page. 4. **The module answers with member *keys*, not rows**, so it can narrow what is published and never widen it. 5. **Core's five activity kinds are four** until the forum lands, and a Team's *first* roster emits no join items at all. ## `BACKEND_DESIGN.md` The seventh Team table and its retention; the three public routes' new behaviour; and `GET /teams/by-external/:moduleId/:externalId`, which exists so a module can find core's Team without holding core's identifiers. --- - [x] AI-assisted: written with Claude Code (Claude Opus 5).
wtclaude added 2 commits 2026-08-18 02:06:10 +00:00
TEAMS.md gains a dated amendment on phase 3 with five corrections, all found by
building the thing it describes:

  - §3.2 and §3.4 contradict each other about `team.member.row`'s props, and
    §3.2 wins because it is the security rule. A client slot can only receive
    what the browser was sent, so §3.4's `{ memberKey, userId, displayName }`
    means publishing both identifiers in every public roster, module installed
    or not. The slot is redeclared with what core can honestly supply.
  - §3.3's projection is an EIGHTH MODULE_API member where 1.6.0 listed seven.
    Settled by the org lead: 1.6.0 is amended in place, on the rule Protocol 4
    was given in phase 2 — a contract owes a bump only once it has reached
    `main`.
  - "the module declines" needed splitting in two before it could be built. No
    module at all withholds nothing and must serve the roster whole; a module
    whose rungs could not be consulted must serve none of it. Only the second
    fails closed, or bare core shows an empty roster on every Team page.
  - the module answers with member KEYS, not rows, so it can narrow what is
    published and cannot widen it.
  - core's five activity kinds are four until the forum lands, and a Team's
    FIRST roster emits no join items at all.

§2.11's route table gains the activity endpoint it never had, and MODULE_API.md
documents `projectRoster`, the inverted fail-closed semantics that make it
different from every other provider call, and `ctx.teams.activity.push`'s item
shape and its four contractual properties.

BACKEND_DESIGN.md: the seventh Team table, its retention, and the three public
routes' new behaviour — `enabled` on the index, the slot props on the single
Team, the per-caller row projection on the roster, and the feed.

Co-Authored-By: Claude <noreply@anthropic.com>
The org lead's correction to Part 3, and the inverted extension-slot direction
it forces.

TEAMS.md: §3.1's routes, §3.4's two slots and §3.5's three nav entries are all
marked superseded in place, and Part 12's phase 3 entry gains the amendment
explaining why — core does not own the word for a Team, so the module that owns
the vocabulary owns the page. The five corrections found by building are kept
alongside it.

MODULE_API.md: 1.6.0's list swaps the two client slots for
registry.declareModuleSlot + Slot in the UI kit, and a new §3.7a documents the
inverted direction: what forced it, the enforced namespace, why core's fills are
applied at mount rather than eagerly, and why a fill for an undeclared slot is a
no-op where §3.7's unknown slot throws.

BACKEND_DESIGN.md: the by-external-id lookup route.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 7121b1a028 into edge 2026-08-18 02:09:34 +00:00
whitlocktech deleted branch docs/teams-phase3 2026-08-18 02:09:35 +00:00
Sign in to join this conversation.
No description provided.