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
7 changes: 7 additions & 0 deletions trios/.trinity/events/akashic-log.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@
{"ts":"2026-09-01T13:07:35Z","event":"loop.handoff","agent":"codex-root","handoff_id":"d676c4ce-b960-438c-8f0c-05aa1f6f7396","task_id":"GH-1300","resume_point":"Watch live dispatches #1301 and #1302 through Queen review; production is on coding_plan with two active unique credentials.","next_options":["Add two remaining unique credentials securely and prove four-way capacity.","Implement provider quota-state and reset-time visibility without exposing response bodies.","Reconcile the seven legacy review cards against the runtime ledger that reports zero unreviewed dispatches."]}
{"ts":"2026-09-01T17:42:12Z","event":"task.intent","agent":"codex-root","task_id":"GH-1313","spec_path":".trinity/specs/queen-public-hardware-registry.md","graph_node":"queen_public_hardware_registry","priority":"P1"}
{"ts":"2026-09-01T17:42:12Z","event":"claim.acquire","agent":"codex-root","claim_id":"5A13B719-47C0-4490-B68F-8A1D328A8401","resource":".trinity/specs/queen-public-hardware-registry.md","ttl_sec":3600}
{"ts":"2026-10-01T21:48:30.035Z","event":"task.intent","agent":"codex-contributor-keys","task_id":"contributor-keys","spec_path":"agent-server/specs/automation/queen-contributor-keys.t27","graph_node":"queen_contributor_keys"}
{"ts":"2026-10-01T22:06:32.008Z","event":"claim.heartbeat","agent":"codex-contributor-keys","claim_id":"2d5b4461-adaa-4135-9b39-24296a6456a5"}
{"ts":"2026-10-01T22:09:25.786Z","event":"claim.release","agent":"codex-contributor-keys","claim_id":"2d5b4461-adaa-4135-9b39-24296a6456a5","result":"clean","validation":"268 Bun tests and 9 native spec tests passed; deployment pending"}
{"ts":"2026-10-01T22:10:35.338Z","event":"task.intent","agent":"codex-contributor-keys","task_id":"contributor-keys-generation","spec_path":"agent-server/specs/automation/queen-contributor-keys.t27","detail":"Preserve byte-identical t27c output through the existing formatting hook"}
{"ts":"2026-10-01T22:11:04.287Z","event":"claim.release","agent":"codex-contributor-keys","claim_id":"b00e2190-4289-4c02-bb2b-04fa398f29e4","result":"clean","validation":"Generated output remains byte-identical after formatter; 12 host tests pass"}
{"ts":"2026-10-01T22:12:34.391Z","event":"task.intent","agent":"codex-contributor-keys","task_id":"contributor-keys-ci","spec_path":"agent-server/specs/automation/queen-contributor-keys.t27","detail":"Connect live contributor tests to the existing disposable CI PostgreSQL"}
{"ts":"2026-10-01T22:14:26.087Z","event":"claim.release","agent":"codex-contributor-keys","claim_id":"c15a0cee-a9c3-4d66-ac7b-bc4cde3a67fc","result":"clean","validation":"Existing CI pglive command: 12 tests pass, 75 assertions, no skips, with TRIOS_PG_TEST_URL"}
27 changes: 27 additions & 0 deletions trios/.trinity/experience/2026-10-01_contributor-keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"timestamp": "2026-10-01T22:12:00Z",
"task_id": "CONTRIBUTOR-KEYS-001",
"tracking_issues": [
"https://github.com/gHashTag/999-multibots-telegraf/issues/3251",
"https://github.com/gHashTag/t27/issues/5472"
],
"contract": "Own-account Queen key management preserves immutable credential ownership, existing environment indices and dispatch-derived XP. Management authentication never overrides persisted consent.",
"red": [
"Removing SQL owner filtering exposed a foreign credential to the actual route test.",
"Removing allocation filtering selected a disabled environment key.",
"Removing owner AAD made the wrong-owner decrypt test fail."
],
"green": {
"bun_regression": "268 tests passed, 0 failed, 0 skipped, 1015 assertions across 11 files; includes 9 isolated PostgreSQL tests and compiled Swift core",
"typescript": "tsc --noEmit passed",
"spec_runtime": "9 native t27 tests passed with Zig 0.16.0",
"spec_negative_controls": "9 controls killed; verified by the canonical spec agent",
"generated_bindings": "Real t27c gen-ts output; every emitted constant compared with compiler AST in host test",
"review_fixes": "Disable does not decrypt; corrupt managed entries do not stop other credentials; removing the management capability retains consent and attribution; Ollama and legacy reviewers retain their paths"
},
"external_gates": [
"Deployment, live provider probes and XP visibility are not proven by these local checks.",
"Generated policy contains declarations; protocol, SQL and cryptographic operations remain explicit TypeScript host adapters."
],
"learning": "A service capability authenticates management calls, while durable consent remains authoritative without that capability. Credential identity must be assigned before filtering, and disable must remain possible when decryption fails. Concurrent t27 test-report processes need separate TMPDIR directories because the compiler currently derives its work directory from the spec basename."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { Hono } from 'hono'
import type { Pool } from 'pg'
import { createQueenPool } from '../../lib/db/queen-pool'
import {
addContributorKey,
CONTRIBUTOR_PROVIDERS,
ContributorError,
changeContributorKey,
contributorGithub,
type EnvironmentKey,
listContributorKeys,
trustedContributor,
} from '../services/queen-contributor-keys'
import { CONTRIBUTOR_POLICY } from '../services/queen-contributor-policy'
import { environmentContributorKeys } from '../services/queen-dispatch'
import {
ACCEPTED_XP,
HOUR_XP,
keyWork,
parseOwners,
rank,
SPEC_XP,
} from '../services/queen-leaderboard'

export interface ContributorRouteDeps {
pool?: () => Pool
environment?: () => EnvironmentKey[]
owners?: () => Record<number, string>
fetcher?: typeof fetch
}
let productionPool: Pool | undefined
function poolForKeys(): Pool {
const url = process.env.DATABASE_URL
if (!url) throw new ContributorError('contributor_keys_unavailable', 503)
productionPool ??= createQueenPool(url)
return productionPool
}
const emptyContribution = () => ({
xp: 0,
accepted: 0,
specs: 0,
finished: 0,
hours: 0,
})
async function contributionOf(pool: Pool, id: number) {
const work = (await keyWork(pool)).filter((entry) => entry.keyIndex === id)
const total = rank(work, {})[0]
return total
? {
xp: total.xp,
accepted: total.accepted,
specs: total.specs ?? 0,
finished: total.finished,
hours: total.hours,
}
: emptyContribution()
}

/** Render attests a verified app subject. No browser can self-assert ownership. */
export function createQueenContributorKeysRoute(
deps: ContributorRouteDeps = {},
) {
const app = new Hono<{ Variables: { contributor: string; body: string } }>()
app.use('/*', async (c, next) => {
c.header('Cache-Control', 'no-store')
try {
c.set(
'contributor',
trustedContributor(
c.req.header('authorization'),
c.req.header('x-queen-contributor-id'),
),
)
return await next()
} catch (error) {
const known =
error instanceof ContributorError
? error
: new ContributorError('contributor_keys_unavailable', 503)
return c.json({ error: known.code }, known.status as 400)
}
})
app.use('/*', async (c, next) => {
if (c.req.method !== 'POST') return next()
const reader = c.req.raw.body?.getReader()
const chunks: Uint8Array[] = []
let bytes = 0
if (reader) {
try {
for (;;) {
const { done, value } = await reader.read()
if (done) break
bytes += value.byteLength
if (bytes > CONTRIBUTOR_POLICY.MAX_BODY_BYTES) {
await reader.cancel()
throw new ContributorError('request_too_large', 413)
}
chunks.push(value)
}
} finally {
reader.releaseLock()
}
}
c.set('body', Buffer.concat(chunks).toString('utf8'))
return await next()
})
app.get('/', async (c) => {
const pool = (deps.pool ?? poolForKeys)()
const subject = c.get('contributor')
const environment = (deps.environment ?? environmentContributorKeys)()
const owners = (
deps.owners ?? (() => parseOwners(process.env.TRIOS_KEY_OWNERS))
)()
const keys = await listContributorKeys(pool, subject, environment, owners)
const work = await keyWork(pool)
const byKey = new Map(work.map((item) => [item.keyIndex, item]))
const own = work.filter((item) =>
keys.some((key) => key.id === item.keyIndex),
)
const totals = rank(
own,
Object.fromEntries(keys.map((key) => [key.id, 'own'])),
)[0]
const contribution = (entry: typeof totals) =>
entry
? {
xp: entry.xp,
accepted: entry.accepted,
specs: entry.specs ?? 0,
finished: entry.finished,
hours: entry.hours,
}
: emptyContribution()
return c.json({
keys: keys.map((key) => {
const entry = byKey.get(key.id)
return {
...key,
contribution: contribution(rank(entry ? [entry] : [], {})[0]),
}
}),
providers: CONTRIBUTOR_PROVIDERS.map(({ id, label, model }) => ({
id,
label,
model,
})),
contribution: contribution(totals),
attribution: { subject, github: contributorGithub(subject) },
scoring: { acceptedXp: ACCEPTED_XP, specXp: SPEC_XP, hourXp: HOUR_XP },
})
})
app.post('/', async (c) => {
let body: unknown
try {
body = JSON.parse(c.get('body'))
} catch {
throw new ContributorError('invalid_json')
}
const pool = (deps.pool ?? poolForKeys)()
const key = await addContributorKey(
pool,
c.get('contributor'),
body,
(deps.environment ?? environmentContributorKeys)(),
deps.fetcher,
)
return c.json(
{ key: { ...key, contribution: await contributionOf(pool, key.id) } },
201,
)
})
app.post('/:id/:action', async (c) => {
const raw = c.req.param('id')
const id = Number(raw)
if (
raw !== String(id) ||
!/^-?(?:0|[1-9][0-9]*)$/.test(raw) ||
!Number.isInteger(id) ||
id < -2147483647 ||
id > 2147483647
) {
throw new ContributorError('invalid_key_id')
}
const action = c.req.param('action')
if (action !== 'probe' && action !== 'enable' && action !== 'disable')
throw new ContributorError('invalid_action')
const pool = (deps.pool ?? poolForKeys)()
const key = await changeContributorKey(
pool,
c.get('contributor'),
id,
action,
(deps.environment ?? environmentContributorKeys)(),
(deps.owners ?? (() => parseOwners(process.env.TRIOS_KEY_OWNERS)))(),
deps.fetcher,
)
return c.json({
key: { ...key, contribution: await contributionOf(pool, key.id) },
})
})
app.onError((error, c) => {
const known =
error instanceof ContributorError
? error
: new ContributorError('contributor_keys_unavailable', 503)
return c.json({ error: known.code }, known.status as 400)
})
return app
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@
*/

import { Hono } from 'hono'
import type { Pool } from 'pg'
import { createQueenPool } from '../../lib/db/queen-pool'
import { logger } from '../../lib/logger'
import {
liveWorkerCapacity,
type WorkerCapacityBreakdown,
workerCapacityBreakdown,
} from '../services/queen-dispatch'
Expand Down Expand Up @@ -560,7 +562,7 @@ async function build(
(lastTick.rows[0]?.decision as { refusal?: string } | undefined)
?.refusal ?? null,
roundSeconds: Number(process.env.TRIOS_QUEEN_TICK_SECONDS ?? '0') || null,
...boardWorkerCapacity(),
...boardWorkerCapacity(await liveWorkerCapacity(pool as Pool)),
},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
*/

import { Hono } from 'hono'
import type { Pool } from 'pg'
import { createQueenPool } from '../../lib/db/queen-pool'
import { logger } from '../../lib/logger'
import {
liveWorkerCapacity,
type WorkerCapacityBreakdown,
workerCapacityBreakdown,
} from '../services/queen-dispatch'
Expand Down Expand Up @@ -225,10 +227,15 @@ export function createQueenPublicResearchRoute(
const url = databaseUrl()
let runtime: { status: 'live' | 'offline' } = { status: 'offline' }
let busyIndices: number[] = []
let breakdown = capacityBreakdown()
let capacityAvailable = !!deps.workerCapacityBreakdown

if (url) {
const pool = createPool(url)
try {
if (!deps.workerCapacityBreakdown)
breakdown = await liveWorkerCapacity(pool as Pool)
capacityAvailable = true
const active = await pool.query(
`SELECT key_index
FROM queen_dispatch
Expand All @@ -239,6 +246,8 @@ export function createQueenPublicResearchRoute(
busyIndices = active.rows.map((row) => Number(row.key_index))
runtime = { status: 'live' }
} catch (error) {
if (!capacityAvailable)
return c.json({ error: 'Worker capacity is unavailable' }, 503)
logger.warn('Queen public research telemetry query failed', {
error: error instanceof Error ? error.message : String(error),
})
Expand All @@ -254,7 +263,6 @@ export function createQueenPublicResearchRoute(
// 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@
*/

import { Hono } from 'hono'
import type { Pool } from 'pg'
import { createQueenPool } from '../../lib/db/queen-pool'
import { logger } from '../../lib/logger'
import { workerModelRanking } from '../../lib/model-ranking'
import { configuredWorkerCapacity } from '../services/queen-dispatch'
import {
configuredWorkerCapacity,
liveWorkerCapacity,
} from '../services/queen-dispatch'

interface QueryResult {
rowCount: number | null
Expand Down Expand Up @@ -635,7 +639,9 @@ export function createQueenPublicStatusRoute(deps: QueenPublicStatusDeps = {}) {
// slot, and each reading keeps its own meaning.
const running = asCount(countRow.running)
const startedUnfinished = asCount(countRow.started_running)
const capacity = workerCapacity()
const capacity = deps.workerCapacity
? workerCapacity()
: (await liveWorkerCapacity(pool as Pool)).effectiveCapacity
const tickDecidedAtMs = decidedAtMs(tickRow?.decided_at)
// Read once, quoted twice: `lastTick.refusal` and the swarmState
// classification must be two readings of the same tick decision, or
Expand Down
3 changes: 3 additions & 0 deletions trios/agent-server/apps/server/src/api/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { createMonitoringRoutes } from './routes/monitoring'
import { createOAuthRoutes } from './routes/oauth'
import { createOpenClawRoutes } from './routes/openclaw'
import { createProviderRoutes } from './routes/provider'
import { createQueenContributorKeysRoute } from './routes/queen-contributor-keys'
import { createQueenDashboardRoute } from './routes/queen-dashboard'
import { createQueenExportRoute } from './routes/queen-export'
import {
Expand Down Expand Up @@ -390,6 +391,8 @@ export async function createHttpServer(config: HttpServerConfig) {
.route('/queen/public-board', createQueenPublicBoardRoute())
// Who lent a lane and what it did; no titles, no worker text, no key.
.route('/queen/public-leaderboard', createQueenPublicLeaderboardRoute())
// Separate server-to-server capability; never a public-read or operator-token route.
.route('/queen/contributor-keys', createQueenContributorKeysRoute())
.route('/queen/registry', queenRegistryRoutes)
// The shell only. It holds no state and no token; every byte of data it
// shows comes from /queen/lease, which stays guarded. See the route header
Expand Down
Loading
Loading