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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* GET /queen/public-credits - for each accepted issue, the GitHub login of the
* GET /queen/public-credits - for each issue, the GitHub login of the
* person whose lane carried it (queen-lane-credits.ts). Read by the
* spec-authors board in gHashTag/trinity.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* WHOSE LANE CARRIED EACH ACCEPTED ISSUE.
* WHOSE LANE CARRIED EACH ISSUE'S BRANCH.
*
* The spec-authors board (gHashTag/trinity, apps/website/scripts/spec-authors.mjs)
* credits a commit to its GitHub author. A swarm commit reaches master as the
* squash of a `queen-<issue>` pull request, authored by whoever merged it, so
* every bee's work read as the merger's own. The owner, 2026-10-03: credit it to
* the person whose provider key the bee ran on. This is the record that makes
* that possible: for each accepted issue, the lane that did the accepted turn
* and the GitHub login of the person who claimed that lane.
* that possible: for each issue, the lane of its latest turn - the turn whose
* work the `queen-<issue>` branch carries - and the GitHub login of the person
* who claimed that lane.
*
* NOT ONLY ACCEPTED TURNS. Measured 2026-10-03: of 514 queen-<issue> pull
* requests merged since 2026-09-17, 89 had an accepted turn; the rest were
* merged by the maintainer after a sendBack, escalate or wait verdict. The
* merge is what puts the work on master, so the lane that wrote the branch is
* the one credited, whatever the Queen's own review said.
*
* Public on the same terms as the leaderboard: issue numbers and logins that
* board already shows, no titles, no worker text, no credential.
Expand All @@ -29,11 +36,11 @@ import { githubLoginOf, parseOwners } from './queen-leaderboard'
export interface LaneCredit {
issue: number
github: string
/** When the accepted turn was dispatched, ISO. */
/** When the credited turn was dispatched, ISO. */
at: string
}

export interface AcceptedTurn {
export interface IssueTurn {
issue: number
keyIndex: number
dispatchedAt: Date
Expand All @@ -57,16 +64,16 @@ export function parseOwnersSince(
}

/**
* Pure: one credit per issue, from its latest accepted turn, only when that
* Pure: one credit per issue, from its latest turn, only when that
* lane is claimed by a GitHub login and the turn is not older than the login's
* start date. An issue accepted on an unclaimed lane is left out, not guessed.
* start date. An issue last worked on an unclaimed lane is left out, not guessed.
*/
export function creditIssues(
turns: AcceptedTurn[],
turns: IssueTurn[],
owners: Record<number, string>,
since: Record<string, Date>,
): LaneCredit[] {
const latest = new Map<number, AcceptedTurn>()
const latest = new Map<number, IssueTurn>()
for (const turn of turns) {
const seen = latest.get(turn.issue)
if (!seen || turn.dispatchedAt > seen.dispatchedAt)
Expand All @@ -88,17 +95,16 @@ export function creditIssues(
return credits.sort((a, b) => a.issue - b.issue)
}

export async function acceptedTurns(pool: Pool): Promise<AcceptedTurn[]> {
export async function issueTurns(pool: Pool): Promise<IssueTurn[]> {
const { rows } = await pool.query(
`SELECT issue, key_index, dispatched_at
FROM queen_dispatch
WHERE review_state = 'accept' AND key_index IS NOT NULL
WHERE key_index IS NOT NULL AND dispatched_at IS NOT NULL
UNION ALL
SELECT issue, (snapshot->>'key_index')::integer,
(snapshot->>'dispatched_at')::timestamptz
FROM queen_dispatch_history
WHERE snapshot->>'review_state' = 'accept'
AND snapshot->>'key_index' ~ '^-?[0-9]+$'
WHERE snapshot->>'key_index' ~ '^-?[0-9]+$'
AND snapshot->>'dispatched_at' IS NOT NULL`,
)
return rows.map((row) => ({
Expand All @@ -119,7 +125,7 @@ export async function laneCredits(
measuredAt: new Date().toISOString(),
repo: 'gHashTag/t27',
credits: creditIssues(
await acceptedTurns(pool),
await issueTurns(pool),
owners,
parseOwnersSince(process.env.TRIOS_KEY_OWNERS_SINCE),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { describe, expect, it } from 'bun:test'
import {
type AcceptedTurn,
creditIssues,
type IssueTurn,
parseOwnersSince,
} from '../../src/api/services/queen-lane-credits'

const turn = (issue: number, keyIndex: number, day: string): AcceptedTurn => ({
const turn = (issue: number, keyIndex: number, day: string): IssueTurn => ({
issue,
keyIndex,
dispatchedAt: new Date(`${day}T12:00:00Z`),
})

describe('whose lane carried an accepted issue', () => {
describe("whose lane carried an issue's branch", () => {
const owners = {
0: '@dmitrii-f-t27',
1: '@dmitrii-f-t27',
2: 'Trinity community',
10000: '@alex',
}

it('credits the GitHub login that claimed the lane of the latest accepted turn', () => {
it('credits the GitHub login that claimed the lane of the latest turn, whatever its verdict', () => {
expect(
creditIssues(
[
Expand Down
Loading