feat(rust): identity — a link code from the game, and the Steam id inside core's user page #6
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/phase-6-identity"
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?
R1's identity link, site-side, and R13's first extension slot. A player types
/linkin game, enters the six-character code here, and an operator sees the result on core's own/admin/users/:idpage.Criterion met for everything the site owns, and walked in a browser. The code-from-the-game half is written down as a walk to run rather than claimed — a code reaches a player and nobody else, so no console can read one.
The site is the author of record and the game holds nothing
There is no per-account store in Rust that survives a wipe, and phase 7 needs the site authoritative anyway — it pushes permissions into the game keyed by Steam id. A copy on the game host would be a second thing to reconcile every wipe, answering no question better.
D24 — a code is minted by ONE server, so every server is asked
Nothing in six characters says where it came from. The fleet is asked in turn and the first
link.okwins; the others answerunknownand nothing happens there, because a code is only spent at the server that actually holds it. Asking the player to pick was rejected: a wrong pick comes back indistinguishable from a wrong code, and that is the one refusal which must not be ambiguous."Every reachable server refused" is not the same answer as "a server was unreachable." Collapsing them tells a player whose server is down that their code is wrong — so they run
/linkagain on that same server and hear it again.unsureis that case, and it says to try again rather than to fetch a new code.D23 — a Steam id another account holds is refused, never moved
steam_idis the primary key, and it is load-bearing rather than tidy: phase 7 grants permissions against a link and phase 13 hangs entitlements off it, so a silent move is an account takeover performed by typing six characters. The refusal names the holder, because the advice is unusable without it. The INSERT is a plain INSERT for the same reason —ON DUPLICATE KEY UPDATEhere would BE that move — and the duplicate-key error is the refusal for the race the check above cannot close.The way out is
/unlinkin game, which reaches the site off the ingest feed rather than through a route (the plugin has no link to delete). D25 adds the other way out: staff sever a link from the admin panel, with an activity row.The slot, declared in three registries
admin.users.detailis inmodule.json'sextensions(validated against the server registry — naming a client slot there fails the load), registered inserver/index.js(withmergeParams, without whichreq.params.idis undefined and every statement scopes to nobody), and filled in the chunk. Core's users router isrequireRole('admin'), so editors and moderators never reach these routes.It found a hole in this repo's own OpenAPI generator
swaggerFragment.jswalkedrecord.routes— the three tiers. A slot router is not registered under a tier, so the two routes under/admin/users/:idwere generated by nothing: a fragment that was internally consistent and described two routes fewer than the module serves. A slot's mount is core's, so it is a fourth constant besideTIER_BASE, held to account by the frozen-manifest job — verified by deleting the two paths and watching that check fail.test/frozenManifest.test.js's mounts agree case was written in phase 1 with this phase named in its comment, and it failed on the first run after the slot was filled. It now also fails when a declared slot contributes no route.Three defects the browser walk found that 122 green tests did not
(data && data.message) || res.statusText— and this module has answered{ error: … }since phase 1. It got away with it because every failure until now landed inErrorStateon a page whose whole content was missing, where a generic sentence is honest. A form is different: the sentence IS the outcome. The link page showed Service Unavailable for all four refusals this phase exists to write. All 23 error bodies now answer inmessage— which is also theErrorschema these routes' own#swagger.responsesalready referenced — andtest/errorShape.test.jsdrives each outcome rather than grepping for the field./player/rustshowed the name recorded at link time while the admin panel showed the one the game last saw. Only the admin read joinedrust_players.What the walk proved
Against a core at the pinned ref with the module installed, two configured servers (one sidecar up with no game behind it, one address with nothing listening) and three logins:
/admin/users/2renders the Steam id, when it was linked and on which server, when they last played, and per-server all-time totals summed across wipes — under core's own security panel.rust.account.unlink.stafflands in the activity log.404as one that does not exist; a player reaching the slot route gets403from core's gate.429.Checks
122 server tests, 39 client tests,
check:imports,check:bundle,check:externals— all green.swagger-fragment.jsonandroutes.manifest.jsonregenerated against a real core at the pinned ref (16 routes, all documented).Base branch
edge, per D18.AI disclosure
🤖 Generated with Claude Code
https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM