From bda8c6ee09683c52a02c9af3ce9a625af59db3b0 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sat, 5 Sep 2026 02:17:25 +0700 Subject: [PATCH] feat(queen): carry #1308 closed worker-capacity breakdown onto the supervisor base Carry-forward of origin/queen-1308 onto origin/feat/queen-supervisor. The bee branch was cut before the tree-loader hardening landed, so the two edits met inside queen-public-research.ts. Applied by hand, hunk by hunk, so the base's newer work is not reverted by a merge. CARRIED, because the base has none of it: - queen-dispatch.ts: `WorkerCapacityBreakdown` and `workerCapacityBreakdown()`, the one authority that answers WHICH capacity a total is made of - connected credentials, lanes per credential, effective capacity - three integers and nothing that can be inverted into a secret. `configuredWorkerCapacity()` now delegates to it, so the number dispatch allocates against and the number the public endpoint explains cannot diverge. - queen-dispatch.ts: `keysFor` trims before it judges. ' key' and 'key' in two boxes were counted as two credentials on one rate limit; a whitespace-only value was counted as configured. The trimmed value is also the stored one, so the count and the selection read the same list. - queen-public-research.ts: the route reads the breakdown instead of the bare count and spreads the anonymous factors into `workers`. - Both test files, including the closure assertions that no planted value, no provider variable name and no key_index-shaped field leaves the endpoint. ADAPTED to the base, which the bee branch could not have seen: - tests/api/queen-tree-load.test.ts pins the route with `workerCapacity: () => 0`. That file does not exist on queen-1308. Renaming the dep without it would have left that route reading the REAL environment. Repointed at the breakdown. - The carried factorisation table configures OPENAI_API_KEY_2, a suffixed name absent from both cleanup lists. Bun runs the api tests in one process, so it survived into queen-public-research.test.ts and made "nothing is connected" read one connected credential. Added to both KEYS lists. DROPPED as superseded: nothing. Every hunk's substance was absent from the base. Not reverted: the branch predates `isTreeLoadFailure` / `TreeLoadFailure`, which the base added to turn a wrong-shape tech-tree.json into a 503 instead of a 500 on a wildcard-CORS public endpoint. That handling is untouched here. Closes #1308 Co-Authored-By: Claude Opus 5 --- .../src/api/routes/queen-public-research.ts | 32 ++- .../server/src/api/services/queen-dispatch.ts | 72 ++++++- .../server/tests/api/queen-dispatch.test.ts | 181 ++++++++++++++++ .../tests/api/queen-public-research.test.ts | 197 +++++++++++++++++- .../server/tests/api/queen-tree-load.test.ts | 6 +- 5 files changed, 466 insertions(+), 22 deletions(-) diff --git a/trios/agent-server/apps/server/src/api/routes/queen-public-research.ts b/trios/agent-server/apps/server/src/api/routes/queen-public-research.ts index d9c8b1fb3f..151328d8ec 100644 --- a/trios/agent-server/apps/server/src/api/routes/queen-public-research.ts +++ b/trios/agent-server/apps/server/src/api/routes/queen-public-research.ts @@ -2,16 +2,22 @@ * Public native research graph for t27.ai. * * The canonical graph is the evidence-backed file used by /queen/tree. This - * 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 + * route adds directionally-correct prerequisites/unlocks, a secret-free + * view of paid worker-slot utilisation, and - since #1308 - the anonymous + * capacity factors behind that utilisation: how many credentials are + * connected and how many lanes each carries, read from the same dispatch + * authority that allocates against them. 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. */ import { Hono } from 'hono' import { Pool } from 'pg' import { logger } from '../../lib/logger' -import { configuredWorkerCapacity } from '../services/queen-dispatch' +import { + type WorkerCapacityBreakdown, + workerCapacityBreakdown, +} from '../services/queen-dispatch' import { isTreeLoadFailure, loadTree as loadCanonicalTree, @@ -33,7 +39,8 @@ interface QueenPublicResearchDeps { loadTree?: () => Promise databaseUrl?: () => string | undefined createPool?: (url: string) => ResearchPool - workerCapacity?: () => number + /** The closed capacity authority; defaults to dispatch's own breakdown. */ + workerCapacityBreakdown?: () => WorkerCapacityBreakdown publicOrigin?: (requestUrl: string) => string } @@ -119,6 +126,9 @@ function projectTree(tree: Tree) { } function workerProjection(capacity: number, busyIndices: number[]) { + // The capacity arrives from the closed breakdown authority (#1308); the + // anonymous factor fields join it in the response without changing any of + // the contracts below. const safeCapacity = Math.max(0, Math.floor(capacity)) // key_index identifies a credential, not a logical lane. With an explicit // multi-lane plan two rows may legitimately carry the same index; counting @@ -149,7 +159,8 @@ export function createQueenPublicResearchRoute( const createPool = deps.createPool ?? ((url: string) => new Pool({ connectionString: url }) as ResearchPool) - const workerCapacity = deps.workerCapacity ?? configuredWorkerCapacity + const capacityBreakdown = + deps.workerCapacityBreakdown ?? workerCapacityBreakdown const publicOrigin = deps.publicOrigin ?? configuredPublicOrigin return new Hono().get('/', async (c) => { @@ -194,10 +205,17 @@ export function createQueenPublicResearchRoute( // container. Build copyable A2A links from Railway's trusted public domain // rather than leaking the internal scheme into the bootstrap contract. const origin = publicOrigin(c.req.url) + // One authority, one number: the projection's capacity IS the breakdown's + // effective capacity, so an operator reading "4" and the factors below it + // can never see two totals that disagree about the same configuration. + const breakdown = capacityBreakdown() return c.json({ ...graph, runtime, - workers: workerProjection(workerCapacity(), busyIndices), + workers: { + ...workerProjection(breakdown.effectiveCapacity, busyIndices), + ...breakdown, + }, agentBootstrap: { version: 'trinity-research-a2a/v1', mode: 'public-read-only', diff --git a/trios/agent-server/apps/server/src/api/services/queen-dispatch.ts b/trios/agent-server/apps/server/src/api/services/queen-dispatch.ts index b28cbeda20..472207d04d 100644 --- a/trios/agent-server/apps/server/src/api/services/queen-dispatch.ts +++ b/trios/agent-server/apps/server/src/api/services/queen-dispatch.ts @@ -203,14 +203,22 @@ export interface WorkerProvider { * one function - the count a dashboard shows and the index a bee takes are the * same list, never two different stories about one secret. First occurrence * wins, so the unsuffixed variable stays index 0 in every ordering. + * + * Values are TRIMMED before they are judged (#1308). ' key' and 'key' pasted + * into two boxes are one credential wearing its whitespace differently, and a + * value that is nothing but whitespace is the empty box one paste later. The + * trimmed value is also the one stored: a key that authenticates never needed + * its padding, and handing the trimmed form out keeps the count and the + * selection - which both read this list - from ever disagreeing. */ function keysFor(envVar: string): string[] { const keys: string[] = [] const seen = new Set() const admit = (value: string | undefined) => { - if (!value || value.length === 0 || seen.has(value)) return - seen.add(value) - keys.push(value) + const trimmed = (value ?? '').trim() + if (trimmed.length === 0 || seen.has(trimmed)) return + seen.add(trimmed) + keys.push(trimmed) } admit(process.env[envVar]) for (let i = 2; i <= 16; i++) { @@ -241,16 +249,60 @@ function workerLanesFor(provider: string): number { } /** - * Number of genuinely independent worker credentials available to the first - * configured provider. The values never leave this module; the public research - * projection uses only the count to show whether paid capacity is idle. + * The closed, anonymous capacity breakdown every capacity number is made of + * (#1308). + * + * `workers.capacity` answering 4 does not say WHICH 4: two subscriptions at a + * lane each and one subscription at two lanes each are the same total with + * completely different operator implications - the first hides a disconnected + * paid subscription, the second promises parallelism a single rate limit + * cannot back. This is the ONE authority both `configuredWorkerCapacity` and + * the public research telemetry read, so the factorisation a dashboard shows + * and the ceiling dispatch allocates against are the same statement, never two + * different stories about one configuration. + * + * CLOSED means three integers and nothing else. No hashes, no key suffixes, no + * slot indexes, no provider variable names, no values: anything shaped like a + * credential is a disclosure, and a count cannot be inverted into one. */ -export function configuredWorkerCapacity(): number { +export interface WorkerCapacityBreakdown { + connectedCredentials: number + lanesPerCredential: number + effectiveCapacity: number +} + +export function workerCapacityBreakdown(): WorkerCapacityBreakdown { for (const candidate of WORKER_PROVIDERS) { - const count = keysFor(candidate.envVar).length - if (count > 0) return count * workerLanesFor(candidate.provider) + const keys = keysFor(candidate.envVar) + if (keys.length > 0) { + const lanesPerCredential = workerLanesFor(candidate.provider) + return { + connectedCredentials: keys.length, + lanesPerCredential, + effectiveCapacity: keys.length * lanesPerCredential, + } + } } - return 0 + // Nothing is connected. The lanes factor keeps its safe default rather than + // zeroing, because it describes the bound the NEXT connected credential + // would run under; the total is still zero, from zero credentials alone. + return { + connectedCredentials: 0, + lanesPerCredential: configuredWorkerLanesPerCredential(), + effectiveCapacity: 0, + } +} + +/** + * Number of genuinely independent worker credentials available to the first + * configured provider, multiplied by that provider's lanes. The values never + * leave this module; the public research projection uses only the count to + * show whether paid capacity is idle. Delegates to the breakdown authority so + * the number allocated against and the number explained publicly can never + * diverge (#1308). + */ +export function configuredWorkerCapacity(): number { + return workerCapacityBreakdown().effectiveCapacity } /** diff --git a/trios/agent-server/apps/server/tests/api/queen-dispatch.test.ts b/trios/agent-server/apps/server/tests/api/queen-dispatch.test.ts index 642e6ea822..9207136669 100644 --- a/trios/agent-server/apps/server/tests/api/queen-dispatch.test.ts +++ b/trios/agent-server/apps/server/tests/api/queen-dispatch.test.ts @@ -17,6 +17,7 @@ import { recordDispatch, resolveWorkerProvider, setDurableCloseListener, + workerCapacityBreakdown, workspaceRoot, } from '../../src/api/services/queen-dispatch' import { logger } from '../../src/lib/logger' @@ -30,6 +31,11 @@ const KEYS = [ 'OPENROUTER_API_KEY', 'MOONSHOT_API_KEY', 'OPENAI_API_KEY', + // #1308's factorisation table configures a second OpenAI slot. Bun runs the + // api test files in ONE process, so a suffixed name missing from this list + // is not a tidiness problem: it survives into the next FILE and makes a + // "nothing is connected" case read one connected credential. + 'OPENAI_API_KEY_2', 'TRIOS_QUEEN_WORKER_MODEL', 'TRIOS_ZAI_CONCURRENCY_PER_KEY', ] @@ -253,6 +259,181 @@ describe('queen dispatch precheck', () => { }) }) +/** + * #1308. `workers.capacity` answers a number; this breakdown answers what the + * number is MADE of. An operator seeing capacity 4 cannot act on it without + * knowing whether it is two subscriptions at a lane each - one of which may be + * quietly disconnected - or one subscription at two lanes each, and a total + * alone keeps that a guess. + */ +describe('worker capacity breakdown', () => { + // Scenario 1 of the issue: two distinct configured Z.ai credentials and two + // lanes per credential. The response is closed - three integers, no trace of + // WHICH credentials produced them. + it('factors capacity into connected credentials and lanes per credential', () => { + process.env.ZAI_API_KEY = 'planted-secret-a' + process.env.ZAI_API_KEY_2 = 'planted-secret-b' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '2' + expect(workerCapacityBreakdown()).toEqual({ + connectedCredentials: 2, + lanesPerCredential: 2, + effectiveCapacity: 4, + }) + // The same authority dispatch allocates against, not a second story. + expect(configuredWorkerCapacity()).toBe(4) + expect(resolveWorkerProvider([0, 1, 0, 1])?.exhausted).toBe(4) + }) + + // FR-002/FR-003: closed and anonymous. Anything beyond these three fields - + // a hash, a suffix, an index, a variable name, a value - is a disclosure. + it('is three numeric fields and nothing else', () => { + process.env.ZAI_API_KEY = 'planted-secret-a' + process.env.ZAI_API_KEY_2 = 'planted-secret-b' + const breakdown = workerCapacityBreakdown() as unknown as Record< + string, + unknown + > + expect(Object.keys(breakdown).sort()).toEqual([ + 'connectedCredentials', + 'effectiveCapacity', + 'lanesPerCredential', + ]) + for (const value of Object.values(breakdown)) { + expect(typeof value).toBe('number') + expect(Number.isInteger(value)).toBe(true) + } + const serialized = JSON.stringify(breakdown) + expect(serialized).not.toContain('planted-secret') + expect(serialized).not.toContain('ZAI_API_KEY') + expect(serialized).not.toContain('ANTHROPIC_API_KEY') + }) + + // Scenario 2: a credential duplicated across slots is one account with one + // rate limit (#1293). Neither factor may be inflated by it. + it('counts a duplicated credential once so nothing is inflated', () => { + process.env.ZAI_API_KEY = 'planted-secret-a' + process.env.ZAI_API_KEY_2 = 'planted-secret-a' + process.env.ZAI_API_KEY_3 = 'planted-secret-b' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '2' + expect(workerCapacityBreakdown()).toEqual({ + connectedCredentials: 2, + lanesPerCredential: 2, + effectiveCapacity: 4, + }) + expect(configuredWorkerCapacity()).toBe(4) + }) + + // FR-003: counted after TRIMMING. ' key' and 'key' in two boxes are one + // credential wearing its whitespace differently, and the count must say so + // before a second slot is handed a secret the first is already spending. + it('trims values before counting, so padded duplicates are one credential', () => { + process.env.ZAI_API_KEY = ' planted-secret-a ' + process.env.ZAI_API_KEY_2 = 'planted-secret-a' + process.env.ZAI_API_KEY_3 = ' planted-secret-b ' + expect(workerCapacityBreakdown().connectedCredentials).toBe(2) + // Selection reads the same trimmed list, so the two can never disagree. + expect(resolveWorkerProvider([])?.keyCount).toBe(2) + }) + + it('treats a whitespace-only value as the empty box it supplies nothing from', () => { + process.env.ZAI_API_KEY = ' ' + expect(workerCapacityBreakdown().connectedCredentials).toBe(0) + expect(configuredWorkerCapacity()).toBe(0) + }) + + // Scenario 3: no supported provider credentials. Every factor is zero or + // its safe default, and nothing about a secret leaves with it. + it('reports zeros and the safe lane default when nothing is connected', () => { + expect(workerCapacityBreakdown()).toEqual({ + connectedCredentials: 0, + lanesPerCredential: 1, + effectiveCapacity: 0, + }) + expect(configuredWorkerCapacity()).toBe(0) + }) + + // FR-005: the lane factor keeps its existing safe default and bound. + it('keeps the safe default of one lane and the bound of four', () => { + process.env.ZAI_API_KEY = 'planted-secret-a' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '0' + expect(workerCapacityBreakdown()).toEqual({ + connectedCredentials: 1, + lanesPerCredential: 1, + effectiveCapacity: 1, + }) + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '99' + expect(workerCapacityBreakdown()).toEqual({ + connectedCredentials: 1, + lanesPerCredential: 4, + effectiveCapacity: 4, + }) + }) + + // The lane override belongs to Z.ai's tiered plans; another provider's + // capacity stays one credential times one lane, exactly as before. + it('does not apply the Z.ai lane factor to another provider', () => { + process.env.ANTHROPIC_API_KEY = 'planted-anthropic-secret' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '3' + expect(workerCapacityBreakdown()).toEqual({ + connectedCredentials: 1, + lanesPerCredential: 1, + effectiveCapacity: 1, + }) + expect(configuredWorkerCapacity()).toBe(1) + }) + + it('factors only the first configured provider, in preference order', () => { + process.env.ZAI_API_KEY = 'planted-secret-a' + process.env.ANTHROPIC_API_KEY = 'planted-anthropic-secret' + process.env.OPENAI_API_KEY = 'planted-openai-secret' + expect(workerCapacityBreakdown().connectedCredentials).toBe(1) + }) + + // FR-004: effective capacity is the number dispatch allocates against, so + // the two must be one number in every configuration, not two that happen to + // agree today. Each fixture starts from a cleared environment. + it('equals configuredWorkerCapacity in every tested configuration', () => { + const configurations: Array<() => void> = [ + () => undefined, + () => { + process.env.ZAI_API_KEY = 'a' + }, + () => { + process.env.ZAI_API_KEY = 'a' + process.env.ZAI_API_KEY_2 = 'b' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '2' + }, + () => { + process.env.ZAI_API_KEY = 'a' + process.env.ZAI_API_KEY_2 = 'a' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '4' + }, + () => { + process.env.ANTHROPIC_API_KEY = 'anthropic-a' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '2' + }, + () => { + process.env.OPENAI_API_KEY = 'openai-a' + process.env.OPENAI_API_KEY_2 = 'openai-b' + }, + () => { + process.env.ZAI_API_KEY = ' a ' + process.env.ZAI_API_KEY_2 = 'a' + process.env.ZAI_API_KEY_3 = ' b ' + }, + ] + for (const configure of configurations) { + for (const key of KEYS) delete process.env[key] + configure() + const breakdown = workerCapacityBreakdown() + expect(breakdown.effectiveCapacity).toBe(configuredWorkerCapacity()) + expect(breakdown.effectiveCapacity).toBe( + breakdown.connectedCredentials * breakdown.lanesPerCredential, + ) + } + }) +}) + /** Every statement a call made, with the values it bound. */ function recordingPool( answer: (sql: string, attempt: number) => unknown = () => ({ diff --git a/trios/agent-server/apps/server/tests/api/queen-public-research.test.ts b/trios/agent-server/apps/server/tests/api/queen-public-research.test.ts index 8a2829bcc2..de9f69579d 100644 --- a/trios/agent-server/apps/server/tests/api/queen-public-research.test.ts +++ b/trios/agent-server/apps/server/tests/api/queen-public-research.test.ts @@ -1,5 +1,6 @@ -import { describe, expect, it } from 'bun:test' +import { afterEach, beforeAll, describe, expect, it } from 'bun:test' import { createQueenPublicResearchRoute } from '../../src/api/routes/queen-public-research' +import type { WorkerCapacityBreakdown } from '../../src/api/services/queen-dispatch' const tree = { nodes: [ @@ -33,6 +34,54 @@ const tree = { staleSkills: [], } +// #1308 plants environment-shaped credential slots and reads the DEFAULT +// capacity authority, so a real secret sitting in the runner's environment +// must be cleared before the first case and after every case: a failure that +// printed one would be worse than the failure, and a leftover one would +// silently change which provider the first case factors. +const KEYS = [ + 'ZAI_API_KEY', + 'ZAI_API_KEY_2', + 'ZAI_API_KEY_3', + 'ZAI_API_KEY_4', + 'ANTHROPIC_API_KEY', + 'OPENROUTER_API_KEY', + 'MOONSHOT_API_KEY', + 'OPENAI_API_KEY', + // Suffixed slots too: these cases read the DEFAULT authority, so a name any + // earlier file in the same bun process left behind would be counted here. + 'OPENAI_API_KEY_2', + 'TRIOS_ZAI_CONCURRENCY_PER_KEY', +] + +beforeAll(() => { + for (const key of KEYS) delete process.env[key] +}) + +afterEach(() => { + for (const key of KEYS) delete process.env[key] +}) + +/** The breakdown of the capacity-4 fixtures above: two credentials, two lanes. */ +const twoCredentialsTwoLanes = (): WorkerCapacityBreakdown => ({ + connectedCredentials: 2, + lanesPerCredential: 2, + effectiveCapacity: 4, +}) + +/** Every property name anywhere in a parsed response. */ +const propertyNames = (value: unknown, into: string[] = []): string[] => { + if (Array.isArray(value)) { + for (const item of value) propertyNames(item, into) + } else if (value && typeof value === 'object') { + for (const [name, inner] of Object.entries(value)) { + into.push(name) + propertyNames(inner, into) + } + } + return into +} + describe('GET /queen/public-research', () => { it('projects the evidence graph, unlocks and four worker slots without secrets', async () => { let ended = false @@ -48,7 +97,7 @@ describe('GET /queen/public-research', () => { ended = true }, }), - workerCapacity: () => 4, + workerCapacityBreakdown: twoCredentialsTwoLanes, publicOrigin: () => 'https://research.t27.test', }).request('/') @@ -68,6 +117,9 @@ describe('GET /queen/public-research', () => { active: 2, idle: 2, utilization: 50, + connectedCredentials: 2, + lanesPerCredential: 2, + effectiveCapacity: 4, slots: [ { slot: 1, state: 'busy' }, { slot: 2, state: 'busy' }, @@ -99,7 +151,7 @@ describe('GET /queen/public-research', () => { }), end: async () => {}, }), - workerCapacity: () => 4, + workerCapacityBreakdown: twoCredentialsTwoLanes, }).request('/') const body = await response.json() @@ -108,6 +160,9 @@ describe('GET /queen/public-research', () => { active: 2, idle: 2, utilization: 50, + connectedCredentials: 2, + lanesPerCredential: 2, + effectiveCapacity: 4, slots: [ { slot: 1, state: 'busy' }, { slot: 2, state: 'busy' }, @@ -121,7 +176,7 @@ describe('GET /queen/public-research', () => { const response = await createQueenPublicResearchRoute({ loadTree: async () => tree, databaseUrl: () => undefined, - workerCapacity: () => 4, + workerCapacityBreakdown: twoCredentialsTwoLanes, }).request('/') expect(response.status).toBe(200) @@ -138,3 +193,137 @@ describe('GET /queen/public-research', () => { expect(response.status).toBe(503) }) }) + +/** + * #1308. `workers.capacity` answering 4 does not say WHICH 4. These cases + * plant environment-shaped credential slots and read the DEFAULT capacity + * authority - the same one dispatch allocates against - then assert the + * response explains the total as anonymous factors while carrying none of the + * material that produced them: no planted value, no provider variable name, + * no credential slot index. + */ +describe('worker capacity breakdown on /queen/public-research', () => { + const assertClosed = (body: unknown) => { + const serialized = JSON.stringify(body) + // None of the planted key values, in full or in part. + for (const planted of [ + 'planted-zai-alpha-7f3a', + 'planted-zai-beta-9c2d', + 'planted-anthropic-gamma-5e1f', + 'planted-duplicate-zeta-4b8e', + 'planted-distinct-eta-1a6b', + ]) { + expect(serialized).not.toContain(planted) + } + // No provider variable name, unsuffixed or suffixed. + for (const name of [ + 'ZAI_API_KEY', + 'ANTHROPIC_API_KEY', + 'OPENROUTER_API_KEY', + 'MOONSHOT_API_KEY', + 'OPENAI_API_KEY', + 'TRIOS_ZAI_CONCURRENCY_PER_KEY', + ]) { + expect(serialized).not.toContain(name) + } + // No credential slot index field: the anonymous `slot` numbers of the + // public slots contract are lane slots, and key_index must never be one + // of them by another name. + const names = propertyNames(body) + expect(names.some((name) => /key_?index/i.test(name))).toBe(false) + expect(names.filter((name) => /credential/i.test(name)).sort()).toEqual([ + 'connectedCredentials', + 'lanesPerCredential', + ]) + } + + // Scenario 1: two distinct configured Z.ai credentials, two lanes each. + // ANTHROPIC is planted too, to prove a variable name that IS configured + // still never appears in what leaves. + it('explains capacity 4 as two credentials at two lanes', async () => { + process.env.ZAI_API_KEY = 'planted-zai-alpha-7f3a' + process.env.ZAI_API_KEY_2 = 'planted-zai-beta-9c2d' + process.env.TRIOS_ZAI_CONCURRENCY_PER_KEY = '2' + process.env.ANTHROPIC_API_KEY = 'planted-anthropic-gamma-5e1f' + const response = await createQueenPublicResearchRoute({ + loadTree: async () => tree, + databaseUrl: () => 'postgres://configured', + createPool: () => ({ + query: async () => ({ + rowCount: 2, + rows: [{ key_index: 0 }, { key_index: 1 }], + }), + end: async () => {}, + }), + }).request('/') + + expect(response.status).toBe(200) + const body = await response.json() + expect(body.workers).toEqual({ + capacity: 4, + active: 2, + idle: 2, + utilization: 50, + connectedCredentials: 2, + lanesPerCredential: 2, + effectiveCapacity: 4, + slots: [ + { slot: 1, state: 'busy' }, + { slot: 2, state: 'busy' }, + { slot: 3, state: 'idle' }, + { slot: 4, state: 'idle' }, + ], + }) + assertClosed(body) + }) + + // Scenario 2: the same credential duplicated across slots is one account + // with one rate limit; neither connected credentials nor capacity inflates. + it('does not inflate when duplicate credentials sit in several slots', async () => { + process.env.ZAI_API_KEY = 'planted-duplicate-zeta-4b8e' + process.env.ZAI_API_KEY_2 = 'planted-duplicate-zeta-4b8e' + process.env.ZAI_API_KEY_3 = 'planted-distinct-eta-1a6b' + const response = await createQueenPublicResearchRoute({ + loadTree: async () => tree, + databaseUrl: () => undefined, + }).request('/') + + const body = await response.json() + expect(body.workers).toEqual({ + capacity: 2, + active: 0, + idle: 2, + utilization: 0, + connectedCredentials: 2, + lanesPerCredential: 1, + effectiveCapacity: 2, + slots: [ + { slot: 1, state: 'idle' }, + { slot: 2, state: 'idle' }, + ], + }) + assertClosed(body) + }) + + // Scenario 3: no supported provider credentials anywhere. Every factor is + // zero or its safe default, and no secret metadata leaves with them. + it('reports zeros and safe defaults when no provider is connected', async () => { + const response = await createQueenPublicResearchRoute({ + loadTree: async () => tree, + databaseUrl: () => undefined, + }).request('/') + + const body = await response.json() + expect(body.workers).toEqual({ + capacity: 0, + active: 0, + idle: 0, + utilization: 0, + connectedCredentials: 0, + lanesPerCredential: 1, + effectiveCapacity: 0, + slots: [], + }) + assertClosed(body) + }) +}) diff --git a/trios/agent-server/apps/server/tests/api/queen-tree-load.test.ts b/trios/agent-server/apps/server/tests/api/queen-tree-load.test.ts index e636159276..5322563e26 100644 --- a/trios/agent-server/apps/server/tests/api/queen-tree-load.test.ts +++ b/trios/agent-server/apps/server/tests/api/queen-tree-load.test.ts @@ -54,7 +54,11 @@ const treeRoute = createQueenTreeRoute() // are pinned so the answer depends on the tree file and nothing else. const researchRoute = createQueenPublicResearchRoute({ databaseUrl: () => undefined, - workerCapacity: () => 0, + workerCapacityBreakdown: () => ({ + connectedCredentials: 0, + lanesPerCredential: 1, + effectiveCapacity: 0, + }), }) const VALID_TREE = JSON.stringify(