docs(teams): phase 5 — discussion, the edit window, and reports that route around leadership #158

Merged
whitlocktech merged 1 commits from docs/teams-phase5 into edge 2026-08-18 18:37:22 +00:00
Member

Docs half of Teams Phase 5, into edge. Code: RunicGateway/website#155.

TEAMS.md §5.4, §5.5.7 (new), §5.6 and the Part 12 phase entry; BACKEND_DESIGN.md's schema and admin route tables.


The largest change is a decision, not a description

§5.6's first rule said "a leader may also see and act on reports for their own Team, but staff always receive them". Org lead settled 2026-08-18 that the leader half is decided against, not deferred: the gap the section exists to close is that leaders moderate their own forum and a Team's leaders are exactly the people who will not report their own Team — a leader-visible queue hands a complaint about a leader back to them, and a read-only leader view still tells them who reported what.

Recorded as an amendment rather than by editing the sentence away, because the reasoning for the original is what makes the correction legible.

§5.6's DDL does not work as written, and the amendment says so

With status in the unique key, closed rows collide with each other: dismiss a report, let the behaviour recur, dismiss the second, and the UPDATE lands on a tuple that already exists — so the queue starts throwing duplicate-key errors on the first repeat reporter. The shipped table keys on a generated open_marker, the same encoding team_forum_grants.active_marker uses.

Left in place with the correction beside it rather than swapped out: it is the one spot in this document where the SQL and the prose next to it disagreed, and that is worth being able to find.

Three smaller departures recorded alongside: handled_note (the API took a note the table had nowhere to put), the two §2.10 username snapshots every other Team table already has, and a real CASCADE on team_id.

New §5.5.7 — teams_forum_edit_window_minutes

0–1440, default 15, staff unbounded, and the rule under it: the window is resolved on the server twice — the read path stamps canEdit/editableUntil so a client knows whether to draw the control, the write re-derives it from created_at before allowing anything. The read is advice and the write is enforcement, because a time-bounded permission must not take its clock from the party it bounds. That is also why the key is not published (§5.5.6): the client needing the number is the admin screen, and the client needing the decision already has it per post.

0 is a legitimate operator choice (posts permanent once written), not an off switch — and the read fails closed to zero rather than to the default, which is the opposite of what it looks like it should do.

§5.4 gains three notes its route table does not carry

  • Thread creation splits authority by TYPE rather than widening the leader gate, and reports it as two booleans — one would make a client guess which right it described.
  • Post moderation is its own route, whose validator accepts all eight actions so the model can answer "pin applies to a thread, not to a post". Restricting it turns a nameable mistake into a generic validation error, which is what the live rig found.
  • A reply's three refusal codes are chosen to be distinguishable — 404 absent, 400 announcement, 409 locked — and locked refuses staff too.

Part 12

What the phase disproved, its four acceptance criteria, that it spans one repo where phase 4 needed two, and the single defect the live rig found. Also notes that phase 4 shipped uploads with the default off, so §5.6's "pull reports forward if uploads is enabled anywhere" never triggered.

BACKEND_DESIGN.md

content_reports in the schema table — with the section heading corrected, since it is deliberately not one of the eleven Team tables — the four properties worth carrying (staff-only, not-a-moderation-action, one-open-per-reporter, every transition audited including dismissals), the edit-window rule, and the two new admin routes.

One gap noticed and not filled here, flagged for your call: the /player tier has no route table in BACKEND_DESIGN.md at all, and phase 4's three admin forum routes are missing from the admin one. Both predate this phase; TEAMS.md §5.4 and the OpenAPI spec are the canonical homes for those routes today.

  • AI-assisted — Claude Code (Claude Opus 5)

🤖 Generated with Claude Code

https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz

Docs half of Teams **Phase 5**, into `edge`. Code: RunicGateway/website#155. `TEAMS.md` §5.4, §5.5.7 (new), §5.6 and the Part 12 phase entry; `BACKEND_DESIGN.md`'s schema and admin route tables. --- ### The largest change is a decision, not a description §5.6's first rule said *"a leader may also see and act on reports for their own Team, but staff always receive them"*. Org lead settled 2026-08-18 that the leader half is **decided against, not deferred**: the gap the section exists to close is that leaders moderate their own forum and a Team's leaders are exactly the people who will not report their own Team — a leader-visible queue hands a complaint *about* a leader back to them, and a read-only leader view still tells them who reported what. Recorded as an amendment rather than by editing the sentence away, because the reasoning for the original is what makes the correction legible. ### §5.6's DDL does not work as written, and the amendment says so With `status` in the unique key, **closed rows collide with each other**: dismiss a report, let the behaviour recur, dismiss the second, and the `UPDATE` lands on a tuple that already exists — so the queue starts throwing duplicate-key errors on the first repeat reporter. The shipped table keys on a generated `open_marker`, the same encoding `team_forum_grants.active_marker` uses. Left in place with the correction beside it rather than swapped out: it is the one spot in this document where the SQL and the prose next to it disagreed, and that is worth being able to find. Three smaller departures recorded alongside: `handled_note` (the API took a `note` the table had nowhere to put), the two `§2.10` username snapshots every other Team table already has, and a real CASCADE on `team_id`. ### New §5.5.7 — `teams_forum_edit_window_minutes` 0–1440, default 15, staff unbounded, and the rule under it: the window is resolved on the server **twice** — the read path stamps `canEdit`/`editableUntil` so a client knows whether to draw the control, the write re-derives it from `created_at` before allowing anything. The read is advice and the write is enforcement, because **a time-bounded permission must not take its clock from the party it bounds**. That is also why the key is not published (§5.5.6): the client needing the *number* is the admin screen, and the client needing the *decision* already has it per post. `0` is a legitimate operator choice (posts permanent once written), not an off switch — and the read fails closed to zero rather than to the default, which is the opposite of what it looks like it should do. ### §5.4 gains three notes its route table does not carry - Thread creation splits authority **by TYPE** rather than widening the leader gate, and reports it as **two** booleans — one would make a client guess which right it described. - Post moderation is its own route, whose validator accepts all eight actions so the model can answer *"pin applies to a thread, not to a post"*. Restricting it turns a nameable mistake into a generic validation error, which is what the live rig found. - A reply's three refusal codes are chosen to be **distinguishable** — 404 absent, 400 announcement, 409 locked — and locked refuses staff too. ### Part 12 What the phase disproved, its four acceptance criteria, that it spans **one** repo where phase 4 needed two, and the single defect the live rig found. Also notes that phase 4 shipped `uploads` with the default off, so §5.6's *"pull reports forward if uploads is enabled anywhere"* never triggered. ### BACKEND_DESIGN.md `content_reports` in the schema table — with the section heading corrected, since it is deliberately **not** one of the eleven Team tables — the four properties worth carrying (staff-only, not-a-moderation-action, one-open-per-reporter, every transition audited **including dismissals**), the edit-window rule, and the two new admin routes. One gap noticed and **not** filled here, flagged for your call: the `/player` tier has no route table in BACKEND_DESIGN.md at all, and phase 4's three admin forum routes are missing from the admin one. Both predate this phase; TEAMS.md §5.4 and the OpenAPI spec are the canonical homes for those routes today. - [x] AI-assisted — Claude Code (Claude Opus 5) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz
wtclaude added 1 commit 2026-08-18 18:31:52 +00:00
TEAMS.md §5.4, §5.5.7 (new), §5.6 and the Part 12 phase entry; BACKEND_DESIGN.md's
schema and admin route tables.

**The largest change is a decision, not a description.** §5.6's first rule said
"a leader may also see and act on reports for their own Team, but staff always
receive them". The org lead settled on 2026-08-18 that the leader half is
**decided against, not deferred**: the gap the section exists to close is that
leaders moderate their own forum and a Team's leaders are exactly the people who
will not report their own Team, so a leader-visible queue hands a complaint about
a leader back to them — and a read-only leader view still tells them who reported
what. Recorded as an amendment rather than by editing the sentence away, because
the reasoning for the original is what makes the correction legible.

**§5.6's `content_reports` DDL does not work as written, and the amendment says so
rather than quietly swapping it.** With `status` in the unique key, CLOSED rows
collide with each other: dismiss a report, let the behaviour recur, dismiss the
second one, and the UPDATE lands on a tuple that already exists — so the queue
starts throwing duplicate-key errors on the first repeat reporter. The shipped
table keys on a generated `open_marker`, the same encoding
`team_forum_grants.active_marker` uses. Three smaller departures are recorded
beside it: `handled_note`, the two username snapshots §2.10 asks for everywhere
else, and a real CASCADE on `team_id`.

**New §5.5.7 for `teams_forum_edit_window_minutes`** (0–1440, default 15), and the
rule under it: the window is resolved on the server TWICE — the read path stamps
`canEdit`/`editableUntil` so a client knows whether to draw the control, the write
re-derives it from `created_at` before allowing anything. The read is advice and
the write is enforcement, because a time-bounded permission must not take its
clock from the party it bounds. That is also why the key is not published: the
client needing the number is the admin screen, and the client needing the decision
already has it per post.

**§5.4 gains three notes its route table does not carry**: thread creation splits
authority by TYPE rather than widening the leader gate (and reports it as two
booleans, since one would make a client guess which right it described); post
moderation is its own route whose validator accepts all eight actions so the model
can say "pin applies to a thread, not to a post"; and a reply's three refusal codes
are chosen to be distinguishable — 404 absent, 400 announcement, 409 locked — with
locked refusing staff too.

The Part 12 entry records what the phase disproved, its four acceptance criteria,
that it spans ONE repo where phase 4 needed two, and the single defect the live rig
found. It also notes that phase 4 shipped `uploads` with the default off, so §5.6's
"pull reports forward if uploads is enabled anywhere" never triggered.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 04eb859a48 into edge 2026-08-18 18:37:22 +00:00
whitlocktech deleted branch docs/teams-phase5 2026-08-18 18:37:23 +00:00
Sign in to join this conversation.
No description provided.