fd9c02130cc5009274905bf28614a87f1d15d5ec
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| fd9c02130c |
docs(teams): Team pages and the activity feed, and what phase 3 disproved
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>
|
|||
| 680a4866ac |
docs(teams): Team core, MODULE_API 1.6.0, and what phase 2 disproved
Documents phase 2 of docs/website/TEAMS.md across the three files that had to
change, and records the five places building it disagreed with the design.
## MODULE_API.md — 1.6.0
The Team surface becomes contract: `api.registerTeamProvider(...)`,
`ctx.teams.publish` / `ctx.teams.reconcile` / `ctx.teams.activity.push`,
`api.registerSlashCommands(...)`, and the two client slots. Additions only, so
minor; module-uo's `coreApi: "^1.3.0"` still resolves.
Per the org lead's decision, one 1.6.0 covers the whole surface rather than a
minor per phase -- so the document names the phase against each member, and the
two that cannot work yet are marked as present-and-throwing rather than left to
be discovered at runtime.
`registerTeamProvider` gets the fullest treatment because it is the first
registration where core calls the MODULE and waits for an answer. The envelope,
the 10-second budget and the refusal semantics are all contract, not
implementation: they are how a module says "I cannot answer" without core hearing
"there is nothing". `ctx.teams` is documented as push-only, with the reason there
is no reader — a module answers questions about Teams, it does not ask them.
## BACKEND_DESIGN.md
The six Team tables, the rename rule, the active-only uniqueness encoding, the
per-column account-deletion decisions, and all eighteen routes across the three
tier tables.
Two entries there exist to stop a future reader "fixing" them: why
`team_forum_grants` does not use the obvious generated column, and why the two
columns TEAMS.md never mentioned have to exist.
## TEAMS.md — five amendments, marked as amendments with their date
- **§2.5's SQL and §2.10's decision cannot both hold.** MariaDB refuses ON
DELETE SET NULL on a base column of a stored generated column (1901), so
§2.5's `active_user` forces the CASCADE that §2.10 exists to prevent. §2.10
wins; the marker is re-encoded for identical semantics.
- **`team_forum_grants` lands in phase 2**, so the four-path resolver is written
once and its non-contamination tests are real.
- **Two columns the document did not contemplate**, both serving §2.4's gates:
`roster_synced_at`, because sync state is per MODULE and gate 3 leaves one
Team behind while the others sync; and `members_empty_since`, gate 4's
per-Team quarantine.
- **`leader` on the member shape is not path 2.** Taking §2.3 and §2.5 both
literally gives one column two writers, and the roster writes first — so a
refused `getTeamLeaders()` silently demoted everyone. Found by its own test.
- **§2.8.2's matcher needed two narrow widenings**, both real impersonation
vectors the whole-word rule missed: a term matches a name word's singular
("Guild of Moderators"), and a run of single-letter words is compared joined
("G.M."). Neither re-admits substring matching.
Pairs with website (Teams phase 2) and Module-uo (the provider).
Refs docs/website/TEAMS.md Part 12 phase 2
Co-Authored-By: Claude <noreply@anthropic.com>
|
|||
| b369e728c2 |
docs(link): protocol 4 — guild membership on the wire
Adds v4.md as the spec of record for `guild.roster` and `guild.leave`, and corrects the two older documents that Protocol 4 makes wrong. PROTOCOL_2.md §10.1 already described this design — hold a member-serial set, diff it each sweep, emit join/leave — and 2.0 then shipped only the half needing no new state, folding membership into the board signature as a serial *sum*. The section has read ever since as though the whole thing were built. It now says which half shipped, and carries the correction that doing it produced: a sum is not a safe stand-in for a set, because one member joining and another leaving between two sweeps offset each other and the guild reads as unchanged. INTEGRATION.md gains both kinds in the event catalogue, the `roster` key on GET /guilds, and the three things an integrator gets wrong otherwise — that `guild.leave`'s `who` is a bare serial rather than an actor object (the mobile has already left, so there is nothing to attribute), that `acct` is genuinely optional on a member, and that a guild with no `roster` key is not the same as one with an empty roster. v4.md documents what the phase found as well as what it built: the missing store migration and the user_version decision, why the roster lives in its own column rather than inside the guild.update snapshot, why a split roster is reassembled in memory rather than appended to the column, and why guild.leave gets no board projection at all. §6 records that the reassembly bug was invisible to every unit test — they all exercised single-frame rosters — and only the live rig caught it. TEAMS.md is amended where this phase disagreed with it: Phase 1 spans five repos, not four, because installer/backup.rs justifies skipping the sidecar database on reasoning the migration falsifies. The user_version decision is recorded there too, since the design of record did not contemplate a migration mechanism at all. Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| 6f8b722acf |
docs(teams): use the correct presentation of the project name, and match both
"Runic Gateway" (two words) is the correct presentation; "RunicGateway" is accepted only where the name has to condense to a single token — the Gitea org, a package name, a URL segment. Three prose uses in TEAMS.md had the condensed form for no reason, including the upload warning text, and are corrected. The `RunicGateway/<repo>` slugs throughout android/PLAN.md are condensed by necessity and are left alone. This turned out to matter beyond presentation. The reserved-name matcher (2.8) specified whole-word matching over a normalisation that case-folds, strips punctuation and collapses whitespace — under which "RunicGateway" is a SINGLE word and would never have matched the two-word term "Runic Gateway". The condensed form is the one an impersonator reaches for, precisely because it is what the org and every URL already use, so the check would have missed its most likely input. Multi-word terms are now additionally compared with whitespace removed on both sides, so `Runic Gateway` matches `RunicGateway`, `runic-gateway`, `Runic_Gateway` and `RUNIC GATEWAY` alike. The widening applies only to terms CONTAINING whitespace, which keeps it clear of the single-word terms where whole-word matching is doing the false-positive work: `admin` is still compared as a word and still does not fire on "Badminton". Listing the condensed form as a separate reserved term was the alternative and was rejected — it is a second thing to keep in sync and would still miss the hyphenated and underscored variants. The same reasoning applies to a deployment's own BRAND_NAME, which is free text and may well be spaced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz |
|||
| f944a89660 |
docs(teams): replace the draft upload warning with the org lead's wording
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 |
|||
| 111b412fd5 |
docs(teams): design of record for platform Teams & community integration
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 |