feat(rust): what the site has given a player, as the player reads it #10

Merged
whitlocktech merged 2 commits from feat/phase-8-player-permissions into edge 2026-09-23 01:52:25 +00:00
Member

Phase 8's website half (docs/modules/rust/PLAN.md §22, D39). Phase 7 made the site the author of in-game privilege and gave an operator every view of it; this is the other side, and it is the first time a player can see what they hold without asking one.

One new route, GET /player/rust/permissions — self-scoped in SQL, read-only by construction (a grant a player could change would not be a grant), and rendered on this module's own /player/rust page as well as by the app's leg (Android-app#48), so the app never has a surface the website lacks.

It is a different shape from the admin read rather than a filtered one:

  • the scope is resolved here. A client handed scope: "*" would have to know what the fleet is to say anything, and then inScope exists twice. Each entry carries the servers it reaches, already resolved and already marked.
  • live is the pushed ledger, never the authored row. A grant is not a privilege in a game until a sync confirmed it, and phase 7 is careful never to record a push that silently did nothing — so waiting is honest, and the alternative is the site claiming to have given something it has not.
  • nothing says why it is waiting. An offline server, a permission no loaded plugin registered and a store that has never seen the account all look the same from here; telling them apart is an operator's diagnosis and an inventory of what is installed.

An entitlement that reaches nobody still lists, and the page says so — authored against the website account, it exists before a Steam id does, and hiding it until one turns up is the defect the admin user page shipped in phase 7 (§20.5).

Verified

169 server tests (7 new), 39 client tests, check:imports, check:bundle, check:externals, the regenerated OpenAPI fragment and the frozen route manifest (generated against core at the pinned sha, as CI does).

Walked in a browser against a core with this module installed and a live Oxide rig behind it — the first Rust phase whose backend talked to a real plugin rather than a stand-in. perm.sync created the group in Oxide's own store, applied the membership, and reported unresolved for a name nothing had registered; the page showed exactly that, one entry has it and one waiting.

The acceptance line needs a person in game — a /link code reaches a player and nobody else. The three minutes that close it are in docs/rust-link/PLAYER_WALK.md.

AI disclosure

Written with Claude Code (Opus 5).

🤖 Generated with Claude Code

https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM

Phase 8's website half (`docs/modules/rust/PLAN.md` §22, D39). Phase 7 made the site the author of in-game privilege and gave an operator every view of it; this is the other side, and it is the first time a player can see what they hold without asking one. **One new route**, `GET /player/rust/permissions` — self-scoped in SQL, read-only by construction (a grant a player could change would not be a grant), and rendered on this module's own `/player/rust` page as well as by the app's leg (Android-app#48), so the app never has a surface the website lacks. It is a different shape from the admin read rather than a filtered one: * **the scope is resolved here.** A client handed `scope: "*"` would have to know what the fleet is to say anything, and then `inScope` exists twice. Each entry carries the servers it reaches, already resolved and already marked. * **`live` is the pushed ledger, never the authored row.** A grant is not a privilege in a game until a sync confirmed it, and phase 7 is careful never to record a push that silently did nothing — so *waiting* is honest, and the alternative is the site claiming to have given something it has not. * **nothing says why it is waiting.** An offline server, a permission no loaded plugin registered and a store that has never seen the account all look the same from here; telling them apart is an operator's diagnosis and an inventory of what is installed. **An entitlement that reaches nobody still lists**, and the page says so — authored against the website account, it exists before a Steam id does, and hiding it until one turns up is the defect the admin user page shipped in phase 7 (§20.5). ### Verified 169 server tests (7 new), 39 client tests, `check:imports`, `check:bundle`, `check:externals`, the regenerated OpenAPI fragment and the frozen route manifest (generated against core at the pinned sha, as CI does). Walked in a browser against a core with this module installed and a **live Oxide rig** behind it — the first Rust phase whose backend talked to a real plugin rather than a stand-in. `perm.sync` created the group in Oxide's own store, applied the membership, and reported `unresolved` for a name nothing had registered; the page showed exactly that, one entry *has it* and one *waiting*. **The acceptance line needs a person in game** — a `/link` code reaches a player and nobody else. The three minutes that close it are in `docs/rust-link/PLAYER_WALK.md`. ### AI disclosure Written with Claude Code (Opus 5). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
wtclaude added 2 commits 2026-09-23 01:36:35 +00:00
Phase 8's website half. Phase 7 made the site the author of in-game
privilege and gave an operator every view of it; this is the other side,
and it is the first time a player can see what they hold without asking
one.

`GET /player/rust/permissions` is self-scoped in SQL and read-only by
construction — a grant a player could change would not be a grant. Three
things make it a different shape from the admin read rather than a
filtered one:

  * the scope arithmetic is answered on the server. A client handed `*`
    would have to know what the fleet is to say anything, and then
    `inScope` exists twice. Each entry carries the servers it reaches,
    already resolved and already marked.
  * `live` is the pushed ledger, never the authored row. A grant is not a
    privilege in a game until a sync confirmed it, and phase 7 is careful
    never to record a push that silently did nothing — so "waiting" is
    honest, and the alternative is the site claiming to have given
    something it has not.
  * nothing says WHY it is waiting. An offline server, a permission no
    loaded plugin registered and a store that has never seen the account
    all look the same from here; telling them apart is an operator's
    diagnosis and an inventory of what is installed.

An entitlement that reaches nobody still lists, and the page says so —
authored against the website account, it exists before a Steam id does,
and hiding it until one turns up is the defect the admin user page
shipped in phase 7 (PLAN.md §20.5).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
fix(rust): put the word on the pill, not only the dot
All checks were successful
PR Checks / client-build (pull_request) Successful in 24s
PR Checks / server-tests (pull_request) Successful in 25s
PR Checks / frozen-manifest (pull_request) Successful in 47s
c4dda5f85c
A filled circle beside a hollow one is the whole difference between "you
have this in game" and "you do not yet", which is more than a shape should
have to carry — and a reader who cannot tell the two apart gets no answer
at all. The pill now reads "<server> · has it" or "<server> · waiting",
which is also what the app's leg says, so the two surfaces describe the
same state in the same words.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
whitlocktech merged commit 480a99f661 into edge 2026-09-23 01:52:25 +00:00
whitlocktech deleted branch feat/phase-8-player-permissions 2026-09-23 01:52:25 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/Module-Rust#10
No description provided.