fix(guilds): do not offer linking where linking cannot reach
Some checks failed
PR Checks / server-tests (pull_request) Successful in 28s
PR Checks / client-build (pull_request) Successful in 17s
PR Checks / frozen-manifest (pull_request) Failing after 35s

Found on the live rig, with the shard's guild feature gated to staff: the
refusal still read "link your account — this shard shows guild information to
linked players". Signing in reaches `logged_in` and linking a game account
reaches `player`; `staff` and `admin` are roles an operator grants, and no
amount of linking earns them. Inviting someone to do something that changes
nothing is worse than plainly saying no.

Also drops the host name from the list embed's title. `ctx.site` carries a base
URL and no brand name, so naming the deployment there could only ever mean
printing its hostname into a title on the shard's own Discord server.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-18 19:08:22 -05:00
parent 2d1d91e372
commit 466842c6f2
2 changed files with 27 additions and 6 deletions

View File

@@ -42,13 +42,22 @@ async function levelFor(actor) {
return visibility.viewerLevel({ user: { id: actor.userId, role: actor.role } })
}
// The nudge §9 answer 5 asks for, and only when it is TRUE. An unlinked caller
// who was told nothing because the shard publishes nothing is not helped by
// being invited to link; the prompt appears when linking is what would actually
// change the answer.
// The nudge §9 answer 5 asks for, and only when it is TRUE.
//
// **Linking reaches exactly two rungs and no further.** Signing in gets a caller
// to `logged_in` and linking a game account to `player`; `staff` and `admin` are
// roles an operator grants and no amount of linking will earn. So a shard that
// gates guilds to staff refuses an unlinked caller WITHOUT the invitation —
// telling them to link would be telling them to do something that changes
// nothing, which is worse than saying no.
//
// The live walk found this: gated to `staff`, the refusal still read "this shard
// shows guild information to linked players".
const LINKING_REACHES = new Set(['logged_in', 'player'])
function linkPrompt(actor, audience) {
if (actor.isLinked) return null
if (audience === 'anonymous') return null
if (!LINKING_REACHES.has(audience)) return null
return 'Link your account on the site to see more — this shard shows guild information to linked players.'
}
@@ -154,7 +163,10 @@ async function handler({ options, actor }) {
if (!wanted) {
const top = [...rows].sort((a, b) => (b.members || 0) - (a.members || 0)).slice(0, LIST_LIMIT)
return {
title: `Guilds on ${core.baseUrl.replace(/^https?:\/\//, '')}`,
// Not "Guilds on <host>": `ctx.site` carries a base URL and no brand name,
// so naming the deployment here can only mean printing its hostname into
// an embed title, which is noise on a shard's own Discord server.
title: 'Guilds on this shard',
fields: top.map((g) => ({
name: g.abbr ? `${g.name} [${g.abbr}]` : g.name,
value: `${g.members || 0} members · ${g.online || 0} online`,

View File

@@ -88,6 +88,14 @@ test('an unlinked caller is invited to link — but only when linking would chan
stub({ audience: 'anonymous', level: 'anonymous' })
const open = await command.handler({ options: {}, actor: anonymous })
assert.equal(open.notice, null)
// Gated to staff: linking reaches `player` and stops there, so the invitation
// would be an instruction to do something that changes nothing. Found on the
// live rig, where a staff-gated shard still offered it.
stub({ audience: 'staff', level: 'anonymous' })
const unreachable = await command.handler({ options: {}, actor: anonymous })
assert.match(unreachable.text, /not shown to your account/)
assert.equal(unreachable.notice, null)
})
test('a stale board answers offline rather than reporting what it still holds', async () => {
@@ -99,6 +107,7 @@ test('a stale board answers offline rather than reporting what it still holds',
test('no argument lists the largest guilds', async () => {
stub()
const res = await command.handler({ options: {}, actor: anonymous })
assert.equal(res.title, 'Guilds on this shard')
assert.equal(res.fields.length, 2)
assert.match(res.fields[0].name, /Knights of the Codex/)
assert.match(res.fields[0].value, /12 members · 3 online/)