Skip to content
Open
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 @@ -5,20 +5,42 @@
* route adds directionally-correct prerequisites/unlocks and a secret-free
* view of paid worker-slot utilisation. It deliberately keeps graph state and
* worker activity separate: "partial" means the repository has incomplete
* evidence, not that a model is currently spending tokens on it.
* evidence, not that a model is currently spending tokens on it. The `billing`
* block explains, in the same closed vocabulary /queen/status publishes,
* which quota gate those paid workers answer to - without which an idle
* subscription swarm reads as broken capacity.
*/

import { Hono } from 'hono'
import { Pool } from 'pg'
import { logger } from '../../lib/logger'
import { configuredWorkerCapacity } from '../services/queen-dispatch'
import { configuredBillingMode } from './queen-public-status'
import {
isTreeLoadFailure,
loadTree as loadCanonicalTree,
type Tree,
type TreeLoadFailure,
} from './queen-tree'

/**
* The closed billing vocabulary, derived from the one resolver both public
* pages share. Deriving it instead of re-declaring it means this route can
* never drift from /queen/status even if the vocabulary grows.
*/
type BillingMode = ReturnType<typeof configuredBillingMode>

/**
* Every value `quotaAuthority` can carry.
*
* provider_quota a Coding Plan spends a provider-side subscription
* quota; its resets and refusals belong to the
* provider and are observed, never computed locally
* estimated_usd_gate metered API work is gated by the Queen's own
* estimated USD cap, which can refuse a new Bee
*/
type QuotaAuthority = 'provider_quota' | 'estimated_usd_gate'

interface QueryResult {
rowCount: number | null
rows: Array<Record<string, unknown>>
Expand All @@ -34,6 +56,7 @@ interface QueenPublicResearchDeps {
databaseUrl?: () => string | undefined
createPool?: (url: string) => ResearchPool
workerCapacity?: () => number
billingMode?: () => BillingMode
publicOrigin?: (requestUrl: string) => string
}

Expand Down Expand Up @@ -141,6 +164,28 @@ function workerProjection(capacity: number, busyIndices: number[]) {
}
}

/**
* The closed billing explanation behind the worker panel.
*
* This is an explanation, not a decision: queend owns whether a Bee starts,
* and no value here can make one run. The mode comes from the exact
* resolver /queen/status publishes, so the two pages can never disagree
* about the same swarm. `quotaAuthority` names which gate actually refuses
* work under that mode. Only these two closed words leave this file - never
* credentials, never provider response bodies, never balances or quota
* telemetry, all of which this route never reads in the first place.
*/
function billingProjection(mode: BillingMode): {
billingMode: BillingMode
quotaAuthority: QuotaAuthority
} {
return {
billingMode: mode,
quotaAuthority:
mode === 'coding_plan' ? 'provider_quota' : 'estimated_usd_gate',
}
}

export function createQueenPublicResearchRoute(
deps: QueenPublicResearchDeps = {},
) {
Expand All @@ -150,6 +195,7 @@ export function createQueenPublicResearchRoute(
deps.createPool ??
((url: string) => new Pool({ connectionString: url }) as ResearchPool)
const workerCapacity = deps.workerCapacity ?? configuredWorkerCapacity
const billingMode = deps.billingMode ?? configuredBillingMode
const publicOrigin = deps.publicOrigin ?? configuredPublicOrigin

return new Hono().get('/', async (c) => {
Expand Down Expand Up @@ -197,6 +243,7 @@ export function createQueenPublicResearchRoute(
return c.json({
...graph,
runtime,
billing: billingProjection(billingMode()),
workers: workerProjection(workerCapacity(), busyIndices),
agentBootstrap: {
version: 'trinity-research-a2a/v1',
Expand Down
157 changes: 157 additions & 0 deletions trios/agent-server/apps/server/tests/api/queen-public-research.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'bun:test'
import { createQueenPublicResearchRoute } from '../../src/api/routes/queen-public-research'
import { configuredBillingMode } from '../../src/api/routes/queen-public-status'

const tree = {
nodes: [
Expand Down Expand Up @@ -138,3 +139,159 @@ describe('GET /queen/public-research', () => {
expect(response.status).toBe(503)
})
})

describe('GET /queen/public-research billing projection', () => {
type ResearchDeps = Parameters<typeof createQueenPublicResearchRoute>[0]

const read = async (overrides: Partial<ResearchDeps> = {}) => {
const response = await createQueenPublicResearchRoute({
loadTree: async () => tree,
databaseUrl: () => undefined,
workerCapacity: () => 4,
...overrides,
}).request('/')
expect(response.status).toBe(200)
return (await response.json()) as {
billing: Record<string, unknown>
workers: Record<string, unknown>
}
}

it('reports Coding Plan with provider quota as the authority, and nothing else', async () => {
const body = await read({ billingMode: () => 'coding_plan' })
// Closed contract: exactly two fields, both closed words. No key, no
// balance, no provider response body has anywhere to hide in this shape.
expect(Object.keys(body.billing).sort()).toEqual([
'billingMode',
'quotaAuthority',
])
expect(body.billing).toEqual({
billingMode: 'coding_plan',
quotaAuthority: 'provider_quota',
})
})

it('resolves an explicit Coding Plan environment value by itself', async () => {
const previous = process.env.TRIOS_SWARM_BILLING_MODE
try {
process.env.TRIOS_SWARM_BILLING_MODE = 'coding_plan'
const body = await read()
expect(body.billing).toEqual({
billingMode: 'coding_plan',
quotaAuthority: 'provider_quota',
})
} finally {
if (previous === undefined) delete process.env.TRIOS_SWARM_BILLING_MODE
else process.env.TRIOS_SWARM_BILLING_MODE = previous
}
})

it('stays conservatively metered when the configuration is missing, empty, or unknown', async () => {
const previous = process.env.TRIOS_SWARM_BILLING_MODE
try {
for (const raw of [undefined, '', 'subscription', 'coding-plan']) {
if (raw === undefined) delete process.env.TRIOS_SWARM_BILLING_MODE
else process.env.TRIOS_SWARM_BILLING_MODE = raw

const body = await read()
expect(body.billing).toEqual({
billingMode: 'api_metered',
quotaAuthority: 'estimated_usd_gate',
})
}
} finally {
if (previous === undefined) delete process.env.TRIOS_SWARM_BILLING_MODE
else process.env.TRIOS_SWARM_BILLING_MODE = previous
}
})

it('agrees with the public status contract for every raw configuration', async () => {
const previous = process.env.TRIOS_SWARM_BILLING_MODE
try {
for (const raw of [
'coding_plan',
' CODING_PLAN ',
'api_metered',
undefined,
'',
'subscription',
]) {
if (raw === undefined) delete process.env.TRIOS_SWARM_BILLING_MODE
else process.env.TRIOS_SWARM_BILLING_MODE = raw

// /queen/status publishes this exact resolver's verdict. The worker
// panel must never tell a different story about the same swarm.
const statusMode = configuredBillingMode()
const body = await read()
expect(body.billing.billingMode).toBe(statusMode)
// The named authority and the status gate boolean are the same fact:
// the estimated USD gate refuses work exactly when it is the
// authority, and only then.
expect(body.billing.quotaAuthority).toBe(
statusMode === 'coding_plan'
? 'provider_quota'
: 'estimated_usd_gate',
)
}
} finally {
if (previous === undefined) delete process.env.TRIOS_SWARM_BILLING_MODE
else process.env.TRIOS_SWARM_BILLING_MODE = previous
}
})

it('cannot fabricate an active worker when capacity is zero or idle', async () => {
const readTelemetry = async (capacity: number, busyIndices: number[]) => {
const response = await createQueenPublicResearchRoute({
loadTree: async () => tree,
databaseUrl: () => 'postgres://configured',
createPool: () => ({
query: async () => ({
rowCount: busyIndices.length,
rows: busyIndices.map((key_index) => ({ key_index })),
}),
end: async () => {},
}),
workerCapacity: () => capacity,
// The most generous billing story: a Coding Plan that may run
// subscription workers. It still starts none.
billingMode: () => 'coding_plan',
}).request('/')
return (await response.json()) as {
billing: Record<string, unknown>
workers: {
capacity: number
active: number
idle: number
utilization: number
slots: Array<{ slot: number; state: string }>
}
}
}

const zero = await readTelemetry(0, [])
expect(zero.workers).toEqual({
capacity: 0,
active: 0,
idle: 0,
utilization: 0,
slots: [],
})
expect(zero.billing).toEqual({
billingMode: 'coding_plan',
quotaAuthority: 'provider_quota',
})

const idle = await readTelemetry(2, [])
expect(idle.workers.active).toBe(0)
expect(idle.workers.utilization).toBe(0)
expect(idle.workers.slots.every((slot) => slot.state === 'idle')).toBe(true)

// The billing words are labels about a gate, never an activity claim:
// neither closed field can be read as "a worker is running".
for (const body of [zero, idle]) {
expect(JSON.stringify(body.billing)).not.toMatch(
/busy|active|running|worker/i,
)
}
})
})
Loading