Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions trios/agent-server/apps/server/node_modules
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@
*
* TRIOS_KEY_OWNERS="0=Dmitrii,1=@alex,4=Trinity community"
*
* A NAME THAT STARTS WITH `@` IS A GITHUB LOGIN. This project lives on GitHub -
* the issues, the branches, the pull requests a bee opens are all there - so the
* person who lent the lane is nearly always someone with a GitHub account, and
* saying `1=@alex` gives the board a face and a profile to link instead of a
* bare string. The login is validated against GitHub's own rule (alphanumerics
* and single hyphens, up to 39 characters); anything else stays a plain name,
* so `4=Trinity community` and an `@` in a display name cannot become a link to
* a profile that is not theirs. Nothing is fetched from GitHub here: the handle
* travels as text and the page builds the avatar URL, so a leaderboard read
* never depends on GitHub being up or on a rate limit.
*
* A lane nobody claimed is shown as `key #N` rather than dropped - the work is
* real and the board must not imply the swarm ran on four keys when it ran on
* fourteen. Nothing here reads a key, and nothing here can: the value never
Expand All @@ -46,6 +57,8 @@ export interface Contributor {
name: string
/** Whether a person claimed this lane in TRIOS_KEY_OWNERS. */
claimed: boolean
/** Their GitHub login, when the name was written as `@login`. */
github?: string
keys: number[]
accepted: number
finished: number
Expand Down Expand Up @@ -74,6 +87,22 @@ export function xpFor(work: Pick<KeyWork, 'accepted' | 'hours'>): number {
return Math.round(work.accepted * ACCEPTED_XP + work.hours * HOUR_XP)
}

/**
* GitHub's own rule for a login: alphanumerics and single hyphens, never at
* either end, at most 39 characters. Kept strict on purpose - this string
* becomes a link to a person's profile and the URL of their avatar, so a name
* that merely contains an `@` must not be able to point the board at a
* stranger's account.
*/
const GITHUB_LOGIN = /^[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){0,38}$/

/** `@alex` -> 'alex'. Anything that is not a login is not one. */
export function githubLoginOf(name: string): string | undefined {
if (!name.startsWith('@')) return undefined
const login = name.slice(1)
return GITHUB_LOGIN.test(login) ? login : undefined
}

/**
* The work of each lane, gathered by lender and ranked. Pure: the rows are the
* database's, the ranking is this function's, and the test drives it directly.
Expand All @@ -90,6 +119,7 @@ export function rank(
const into: Contributor = seen ?? {
name,
claimed,
...(claimed ? { github: githubLoginOf(name) } : {}),
keys: [],
accepted: 0,
finished: 0,
Expand Down
44 changes: 44 additions & 0 deletions trios/agent-server/apps/server/tests/api/queen-leaderboard.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'bun:test'
import {
ACCEPTED_XP,
githubLoginOf,
HOUR_XP,
type KeyWork,
parseOwners,
Expand Down Expand Up @@ -74,4 +75,47 @@ describe('the score', () => {
)
expect(ranked.map((r) => r.name)).toEqual(['Zoe', 'Ann', 'Ada', 'Bob'])
})

it('reads a GitHub login out of an @name, and carries it to the row', () => {
expect(githubLoginOf('@alex')).toBe('alex')
expect(githubLoginOf('@torvalds')).toBe('torvalds')
expect(githubLoginOf('@gHashTag')).toBe('gHashTag')
expect(githubLoginOf('@a-b-c9')).toBe('a-b-c9')
const [row] = rank([lane(0, 1, 1, 0)], { 0: '@alex' })
expect(row.github).toBe('alex')
expect(row.name).toBe('@alex')
})

/**
* The handle becomes a link to a person's profile and the URL of their
* avatar, so anything that is not a login must not become one. A plain name
* is shown as itself; it is never pointed at somebody else's account.
*/
it('refuses a name that is not a GitHub login', () => {
for (const name of [
'Dmitrii',
'Trinity community',
'@',
'@-alex',
'@alex-',
'@al--ex',
'@alex/../torvalds',
'@alex bob',
'@alex.png',
'@' + 'a'.repeat(40),
'mail@example.com',
]) {
expect(githubLoginOf(name)).toBeUndefined()
}
const [row] = rank([lane(0, 1, 1, 0)], { 0: 'Trinity community' })
expect(row.github).toBeUndefined()
expect(row.claimed).toBe(true)
})

it('gives an unclaimed lane no handle at all', () => {
const [row] = rank([lane(7, 1, 1, 0)], {})
expect(row.name).toBe('key #7')
expect(row.github).toBeUndefined()
expect(row.claimed).toBe(false)
})
})
Loading