feat(teams): phase 4 — the forum access model, announcements and the operator's controls #153
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/teams-phase4-forum-access"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
Teams phase 4 — Forum 5a: the access model, announcements, and the operator's
controls.
docs/website/TEAMS.mdPart 5, plus §2.5's grant flow. Targetsedgealongside Module-uo#12 and docs#157.
This ships the permission model everything downstream depends on, behind a small
surface: a single announcements stream per Team, plus the two switches an operator
needs to run it.
One structural correction, and it is why this PR is not website-only. TEAMS.md
§3.1 gave the forum a CORE page and phase 3 deleted every core Team page — Teams is
a contract primitive and core does not own the word for one. The routes were
unaffected (all
/playerand/admin), but the participant SURFACE had no home.Settled by the org lead the way phase 3 settled the activity feed:
module-uodeclares a second place on its guild page (
uo.guild.forum) and core fills it.What is in it
upload attribution — including the columns only phase 5's discussion threads use.
That is §5.1's split by LAYER: phase 5 opens paths rather than migrating data.
ordinary access on their own Team, capped and rate-limited, and cannot revoke a
staff-issued grant. Nothing on this path writes
team_members, in eitherdirection.
imgis never allowed. An authorwrites a URL; core's renderer decides at read time whether it becomes a picture.
uploadsmode, hardened for an uploader who is not an admin: magic-bytesniffing, a rolling per-account quota, an attribution row per file, and a nightly
sweep.
bypassed, and recorded rather than merely displayed.
Three things worth a reviewer's attention
The 404, not 403. Every forum route answers 404 while the switch is off, and
404 to a caller with no access. A 403 says "this exists and you may not have it",
which advertises both a feature the operator turned off and the existence of a
private room to someone outside it.
The stored HTML never changes with the image policy. That is the property the
renderer-owned design exists to give, and it is what makes flipping the setting
back to
disabledun-render every image on every existing post with no migration.Asserted directly (acceptance 3).
A stale acknowledgement does not disable uploads. It raises a banner and
freezes every other forum setting until re-given. Silently downgrading a live
feature because a legal text changed would strand users mid-conversation.
How it was tested
npm testinserver/— 972 pass, 0 fail, including 23 new tests intest/teamForum.test.jsand 5 new route-level tests intest/teamRoutes.test.js.npm testinclient/— 224 pass.npm run buildinclient/— clean.npm run swaggerandnpm run routes:manifestregenerated and committed; the12 new routes are all annotated and in the spec.
acceptance N:(asserted byte-identical across a grant), and is still refused external
eligibility;
on the way there;
disabledandremote;uploadswithout a matching acknowledgement is refused 400 server-side.Not yet run on the live rig — worth doing before the
edge→maincutover, sincethe panel is core content inside a module's page and that seam is exactly what unit
tests cannot see.
Checklist
AI-assisted contributions (required)
Claude Code. I have reviewed and understandevery change, and take responsibility for it. AI-authored commits are
marked with a
Co-Authored-Bytrailer.License
(GNU GPL v3.0 or later), and I have the right to contribute it.
Path 3's WRITE half. The resolver landed in phase 2; this is who may hand access out, to whom, and what stops a leader turning a Team forum into open hosting on the operator's site. Two authorities, and not one authority with different reach. Staff may act on any Team, uncapped, and may revoke anything. A leader may grant and revoke ordinary access on their own Team, is capped at `teams_max_grants_per_team` (default 50), is rate-limited, and may NOT revoke a staff-issued grant — which is what stops a leader undoing a moderation decision. The issuer's role is checked at revoke time rather than stored, so an account that has since lost its staff role stops protecting the grants it made. Nothing on this path writes team_members, in either direction. A grant may name any account, including one with no linked game identity — that is the point of it — and that account stays off the roster, out of every count, and ineligible for external platforms. Announcements are a degenerate thread rather than their own object, so phase 5 adds no migration. Moderation records WHICH authority was exercised: a staff action also writes activity_log, a leader's writes only the Team's own ledger. Merging the two would make a guild leader locking a thread an appealable Discord sanction. Every forum route answers 404 while the switch is off, and 404 — never 403 — to a caller with no access: in a private room the contents and the existence are the same secret. The grant routes deliberately answer even while the forum is OFF, because a toggle-off revokes no grant and the access list has to stay manageable. Under /player rather than /admin: a leader is a player, and the /admin tier gate is requireRole('admin','editor','moderator') — putting a leader endpoint behind it would mean widening that gate. Co-Authored-By: Claude <noreply@anthropic.com>