Files
Module-Rust/routes.manifest.json
wtclaude 43147b796a feat(rust): site-owned permissions — the site is the author, the game is the cache
R2, and the first phase where this module WRITES to a game. Groups and grants are
authored on the website and pushed into each server's own permission store, so
every plugin that already calls `UserHasPermission` honours them with no adapter,
and a wipe stops being a data-loss event.

**Seven org-lead decisions (D28-D34).** A grant is keyed to the website USER and
resolved to every Steam id they have linked at push time (D28); every authored row
carries a scope — a server or `*` (D29); groups are mirrored as real groups rather
than flattened (D30); a holder the site did not author is REPORTED, never undone,
with adopt and revoke offered (D31); one verb, with the plugin diffing locally
(D32); a permission no server has registered is reported unresolved and never
self-registered (D33); authoring is people and groups by hand, with rules deferred
(D34).

**Three sets, and every interesting question is a difference between two.**
`desired − pushed` is what to apply; `pushed − desired` is what to RETIRE, because
the site put it there and has since withdrawn it; `present − desired` is drift. The
middle one is why `rust_perm_pushed` exists: a name in the store that is not in the
desired set is either something the site retired or something a human granted, and
those two have opposite correct answers.

**What lands is not what was sent.** A grant naming a permission the server has not
registered did not land — `GrantUserPermission` no-ops silently — and a member the
store has never seen could not be placed. Neither is recorded as pushed, so the
site never believes it gave a privilege it did not.

The loop asks a cheap question every thirty seconds — does the digest of the
desired set still equal what this server last confirmed — and syncs on a change, a
restart, a wipe, a drift hook, a failed attempt past its backoff, or the
fifteen-minute audit that finds drift on a server nobody has touched.

**This module's first admin page**, because a permission model is the first thing
here that has to be composed rather than configured. What is on it is decided by
what an operator can get wrong: four states are invisible from the game and from a
list of grants, and each is a sentence rather than a number.

Walked end to end against a real core at the pinned ref, the real sidecar, and a
stand-in speaking protocol 4 — including a restart that emptied the store and was
fully re-pushed. Four defects the browser found that 133 green tests did not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
2026-09-21 18:28:32 -05:00

156 lines
3.8 KiB
JSON

{
"$comment": "Generated inventory of the URLs module-rust serves - the module half of the freeze core keeps in server/routes.manifest.json. DERIVED as the difference between a core without this module and the same core with it, both at the pinned ref in ci/core-ref.json. Regenerate with the frozen-manifest job in .gitea/workflows/pr-checks.yml; see server/scripts/frozenManifest.js.",
"routes": [
{
"method": "DELETE",
"path": "/api/v1/admin/rust/permissions/grants/:id",
"tier": "public"
},
{
"method": "DELETE",
"path": "/api/v1/admin/rust/permissions/groups/:name",
"tier": "public"
},
{
"method": "DELETE",
"path": "/api/v1/admin/rust/permissions/groups/:name/members/:userId",
"tier": "public"
},
{
"method": "DELETE",
"path": "/api/v1/admin/rust/servers/:id",
"tier": "public"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id/rust/links/:steamId",
"tier": "public"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id/rust/permissions/grants/:grantId",
"tier": "public"
},
{
"method": "DELETE",
"path": "/api/v1/player/rust/links/:steamId",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/admin/rust/permissions",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/admin/rust/permissions/catalogue",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/admin/rust/servers",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/rust/links",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/rust/permissions",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/player/rust/links",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/player/rust/servers",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/public/rust/servers",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/public/rust/servers/:id",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/public/rust/servers/:id/events",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/public/rust/servers/:id/leaderboard",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/public/rust/servers/:id/online",
"tier": "public"
},
{
"method": "GET",
"path": "/api/v1/public/rust/servers/:id/wipes",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/admin/rust/permissions/drift/:id/adopt",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/admin/rust/permissions/drift/:id/revoke",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/admin/rust/permissions/grants",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/admin/rust/permissions/groups/:name/members",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/admin/rust/permissions/sync",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/admin/rust/servers/:id/test",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/admin/users/:id/rust/permissions/grants",
"tier": "public"
},
{
"method": "POST",
"path": "/api/v1/player/rust/link",
"tier": "public"
},
{
"method": "PUT",
"path": "/api/v1/admin/rust/permissions/groups/:name",
"tier": "public"
},
{
"method": "PUT",
"path": "/api/v1/admin/rust/servers/:id",
"tier": "public"
}
]
}