The six core tables Team core is built on (docs/website/TEAMS.md §2.1, §2.5,
§2.5.1, §2.9), plus the §2.10 account-deletion decisions expressed as foreign
keys rather than left to whatever the defaults happened to be.
Every table is core-internal (§10.3): a module populates them through the team
provider and must never read or write one directly. They carry no <moduleId>_
prefix, correctly -- MODULE_API.md §2.6's prefix rule binds modules, and these
are core's.
team_forum_grants lands in this phase rather than in phase 4, so the four-path
resolver is written once and its non-contamination tests are real. Nothing
writes it yet; the grant/revoke flow, the per-Team cap and the leader UI are
phase 4's.
Two departures from the SQL as TEAMS.md sketched it, both recorded in the file:
- team_forum_grants.user_id is nullable with ON DELETE SET NULL, following
§2.10 (the audit trail of who granted whom must survive the account) rather
than §2.5's CASCADE.
- its uniqueness marker is derived from revoked_at alone, with user_id moved
into the unique KEY. §2.5's `active_user AS (IF(revoked_at IS NULL, user_id,
NULL))` cannot coexist with the line above: MariaDB refuses ON DELETE SET
NULL on a foreign key whose column is a base column of a STORED generated
column (error 1901). The semantics are identical -- at most one active grant
per (team, user), unlimited revoked rows.
Verified by running ensureSchema() against MariaDB 11: all six tables create,
both generated columns materialise, and every foreign key's delete rule matches
§2.10's table. The uniqueness encoding was checked directly -- a second active
grant for the same (team, user) is rejected 1062 while revoked rows accumulate
freely.
Refs docs/website/TEAMS.md Part 12 phase 2
Co-Authored-By: Claude <noreply@anthropic.com>