Skip to content

feat(queen): manage contributor keys and preserve account XP - #522

Merged
dmitrii-f-t27 merged 3 commits into
feat/queen-supervisorfrom
codex/contributor-keys
Oct 1, 2026
Merged

dmitrii-f-t27 merged 3 commits into
feat/queen-supervisorfrom
codex/contributor-keys

Conversation

@dmitrii-f-t27

@dmitrii-f-t27 dmitrii-f-t27 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Personal accounts currently cannot manage their Queen provider keys or connect key-level dispatch XP to a verified account. This adds a private account-proxy API for own-only listing, adding, probing, enabling and disabling NVIDIA NIM and Z.ai keys, with the existing contribution formula and no wallet issuance.

Managed credentials use AES-256-GCM, immutable owner snapshots and negative database IDs. Existing environment credentials retain their indices and history; durable fingerprint-bound disable overrides apply to workers, reviews and model probes. Removing the management capability does not revoke the consent ledger. Invalid or unreadable credentials fail closed individually, and a later disable wins over an in-flight enable.

Validation: 268 Bun tests passed across 11 files, including 9 isolated PostgreSQL tests, with 1,015 assertions and no skips. The existing CI test:pglive command also passed all 12 tests and 75 assertions with its TRIOS_PG_TEST_URL, proving the new tests do not silently skip there. TypeScript passed; the Swift Queen core built successfully. Native spec execution passed 9 tests; canonical verification killed all 9 negative controls. Three host mutations were killed (SQL ownership, disabled-key allocation and owner-bound encryption). Independent security review findings were fixed and verified. Existing lint complexity warnings remain.

The policy is generated by real t27c gen-ts; commit formatting preserves its exact output, and host tests compare emitted constants against the compiler AST. Canonical spec: gHashTag/t27#5473. Implementation documentation: trios/agent-server/docs/contributor-keys.md.

Deployment is pending. Configure the separate trusted proxy capability, verified subject-to-GitHub map, encryption master and existing-key ownership map as documented. The PR targets feat/queen-supervisor, but the live Railway source was fix/queen-worker-provider-and-prompt-size at rollout preparation: deploy the reviewed revision explicitly; merging this base alone does not prove a live rollout. Coordinate with the personal-account proxy/UI change. No live provider health result is claimed by this PR.

Refs gHashTag/999-multibots-telegraf#3251 and gHashTag/t27#5472.

@dmitrii-f-t27
dmitrii-f-t27 marked this pull request as ready for review October 1, 2026 22:15
@dmitrii-f-t27
dmitrii-f-t27 merged commit e36fab3 into feat/queen-supervisor Oct 1, 2026
2 of 31 checks passed
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

⚠️ No test results were produced

View workflow run

dmitrii-f-t27 pushed a commit that referenced this pull request Oct 2, 2026
#522 mounted /queen/contributor-keys and left the route-guard audit
unchanged, so feat/queen-supervisor fails four route-guard tests: 46
mounts against a pin of 45, 23 /queen mounts against 22, and an
unguarded mount nobody allowlisted.

The route is a server-to-server door for the app render proxy and has
its own guard: a bearer equal to QUEEN_CONTRIBUTOR_PROXY_TOKEN (32+
bytes, timingSafeEqual) plus a verified contributor header, and it is
off while that token is unset. The trusted-origin check would refuse
its only caller, so it is allowlisted with that reason and the pins are
re-measured. No other number moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2
dmitrii-f-t27 pushed a commit that referenced this pull request Oct 2, 2026
Conflicts, both additive:
- server.ts: keep the runner mounts and add /queen/contributor-keys.
- queen-leaderboard.ts: rank() takes the operator map merged with
  contributorOwnerNames(), and the runner owners beside it.

Also ports the route-guard fix from #520: #522 left
/queen/contributor-keys out of the audit, which turns four route-guard
tests red on the base. It is allowlisted with its own capability guard
and the pins are re-measured (48 mounts; 25 /queen: 8/8/9).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2
dmitrii-f-t27 pushed a commit that referenced this pull request Oct 2, 2026
One conflict, in dispatchBee: the runner offer still comes first, and
the container path then picks its key with #522's contributor runtime
(resolveWorkerProvider(..., runtime)). Contributor keys use negative
indices and runner lanes sit above 100000000, so the runner filters
(NOT_A_RUNNER, isRunnerLane) leave contributor keys with the container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2
gHashTag added a commit that referenced this pull request Oct 2, 2026
Bring the PR up to date with its base branch (contributor keys and XP
account management, #522; public board verdict and judged head, #517).

Conflict in src/api/server.ts: both sides added a mount right after
/queen/public-leaderboard. Keep both - /queen/public-earnings from this
branch and /queen/contributor-keys from the base. Every other file merged
cleanly; the queen_tri_earnings DDL in pg-migrate.ts and the
recordEarnings call in queen-tick.ts are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag pushed a commit that referenced this pull request Oct 2, 2026
…dules symlink (#520)

* fix(ci): pin Bun to the workspace version and untrack a local node_modules symlink

Every `Tests / *` job on #517 and #518 was cancelled at the 20-minute
budget while still inside `bun ci`, before any test ran.

Two things combined:
- trios/agent-server/apps/server/node_modules was committed as a symlink
  to a local macOS path. `.gitignore` said `node_modules/`, which only
  matches directories, so the symlink slipped through.
- setup-bun ran without a version. The `packageManager: bun@1.3.6` pin
  lives in trios/agent-server/package.json, not at the repo root, so CI
  got the latest release (1.4.2), which hangs on that dangling symlink.
  1.3.x installs past it.

Untrack the symlink, make the ignore rule match files too, and read the
Bun version from the workspace package.json in test.yml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(tools): get_page_content reads a constructed page, not the live example.com

With installs no longer hanging, server-tools ran for the first time
since 2026-09-23 and failed one test: get_page_content read
https://example.com 57 ms after opening it and found no "Example
Domain". The test is about extracting text, so it now writes that text
into about:blank with evaluate_script, as get_page_links already does.

Locally (BrowserOS AppImage, headless, --no-sandbox): the old test
fails the same way; the new one passes 3/3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(helpers): killProcessOnPort kills listeners only, never the test process

server-tools still exited 1 after every test in observation.test.ts
passed: before navigation-newtab-guard.test.ts the helper ran
`lsof -ti :<cdp port>`, which also lists clients still connected to the
port. One of them was the bun test process itself (its CDP socket to the
previous file's browser), so the SIGTERM ended the whole run and no
junit report was written ("workflow > server-tools setup").

Use `lsof -ti tcp:<port> -sTCP:LISTEN` and drop process.pid.

Locally, input.test.ts + navigation-newtab-guard.test.ts in one process:
before, exit 143 right after "Terminating process(es) <own pid>, ...";
after, 18 pass / 0 fail. The whole test:tools group now runs to the end
(242 pass; the 2 local failures load https://example.com, which this
sandbox's browser cannot reach and CI can).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(tools): wait_for waits for text a data: page adds, not the live example.com

With the run no longer killing itself, server-tools finished in CI with
243 pass / 1 fail: `wait_for finds text on page` waited its full 10 s
for "Example Domain" on https://example.com and never saw it - the same
page get_page_content could not read either.

The page now adds that text itself 500 ms after load, so the test still
proves wait_for waits, with nothing outside the runner involved.

Locally: 2/2 wait_for tests pass on repeat; the whole test:tools group
is 243 pass, the one local failure being take_screenshot (a 60 s hang
in this sandbox only - it passes in CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(tools): the class-selector search_dom test retries the load race too

server-tools on 3d57649 ran clean except one test that had passed on
both earlier runs: `search_dom > finds multiple elements with CSS class
selector` (123 ms, fewer than 3 matches). It searches once, straight
after new_page - the race this file already names and fixes with
searchUntil for two sibling tests. Use the same helper here.

Locally: search_dom 13/13, three runs in a row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(queen): give the 205-file salvage rename test an explicit 30 s budget

`the salvage commit > never splits a rename across the path cap` runs
real git over 205 files and salvageWorktree. It takes ~2 s for the whole
file locally and passed on the two CI runs before, then hit bun's 5 s
default once on a loaded runner (job 110500921083) with nothing in the
change touching salvage. A git-heavy fixture test should not share the
budget of a pure unit test.

Locally: queen-salvage-guards.test.ts 13 pass / 0 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(queen): the route-guard audit knows /queen/contributor-keys

#522 mounted /queen/contributor-keys and left the route-guard audit
unchanged, so feat/queen-supervisor fails four route-guard tests: 46
mounts against a pin of 45, 23 /queen mounts against 22, and an
unguarded mount nobody allowlisted.

The route is a server-to-server door for the app render proxy and has
its own guard: a bearer equal to QUEEN_CONTRIBUTOR_PROXY_TOKEN (32+
bytes, timingSafeEqual) plus a verified contributor header, and it is
off while that token is unset. The trusted-origin check would refuse
its only caller, so it is allowlisted with that reason and the pins are
re-measured. No other number moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

---------

Co-authored-by: Claude <noreply@anthropic.com>
gHashTag added a commit that referenced this pull request Oct 2, 2026
…516)

* feat(queen): record what accepted .t27 work has earned, append-only

The first half of spec authors mining TRI: nothing can be minted from a
number nobody wrote down. Every Queen round now records each accepted turn
whose boundary names a .t27 file as one earning per (repository, issue,
judged commit), with work_id = sha256('t27-accept:v1|repo|issue|commit') so
anyone can recompute it from public data.

Why a table, when the leaderboard derives its score on read: a CI take-back
edits queen_dispatch in place, so an acceptance derived on read would vanish
instead of showing as taken back. Rows here are inserted and revoked, never
deleted. A later sendBack/escalate of the same commit revokes an earning, and
the revocation is final.

GET /queen/public-earnings serves the record (public-read, no titles, no
worker text, no notes) and says in its own body that nothing is withdrawable:
no token is deployed, TRI per spec is undecided, and an accept does not yet
require a merge.

Tests: unit (grouping, query parameters, 503 without a database) and a live
PostgreSQL test in tests/pglive covering idempotency, the work_id hash, the
non-.t27 exclusion, take-back revocation and a new commit after a send-back.
Route-guard census re-measured: 46 mounts, 9 public-read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): pin Bun to the workspace version and untrack a local node_modules symlink

Every `Tests / *` job on #517 and #518 was cancelled at the 20-minute
budget while still inside `bun ci`, before any test ran.

Two things combined:
- trios/agent-server/apps/server/node_modules was committed as a symlink
  to a local macOS path. `.gitignore` said `node_modules/`, which only
  matches directories, so the symlink slipped through.
- setup-bun ran without a version. The `packageManager: bun@1.3.6` pin
  lives in trios/agent-server/package.json, not at the repo root, so CI
  got the latest release (1.4.2), which hangs on that dangling symlink.
  1.3.x installs past it.

Untrack the symlink, make the ignore rule match files too, and read the
Bun version from the workspace package.json in test.yml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* feat(queen): one earning by work id, and the epoch-1 amount (27 TRI)

GET /queen/public-earnings/:workId returns one earning with its earner's
GitHub login, the scheme and TRI_PER_SPEC = 27 (owner decision O2,
2026-10-01). This is what each TRI signer reads before it signs; the merge
rule (O4) is checked by the signers on GitHub, not here. Status text says
what is true: mintable on TON testnet only, V1, signer quorum, NOT trustless.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(tools): get_page_content reads a constructed page, not the live example.com

With installs no longer hanging, server-tools ran for the first time
since 2026-09-23 and failed one test: get_page_content read
https://example.com 57 ms after opening it and found no "Example
Domain". The test is about extracting text, so it now writes that text
into about:blank with evaluate_script, as get_page_links already does.

Locally (BrowserOS AppImage, headless, --no-sandbox): the old test
fails the same way; the new one passes 3/3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* feat(queen): every earning credited to one GitHub login

GET /queen/public-earnings/by/:github lists the earnings of the keys lent
under that login, newest first: what the TRI wallet shows its owner as
claimable. The ledger's 'recent' is capped at 100 and cannot serve this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(helpers): killProcessOnPort kills listeners only, never the test process

server-tools still exited 1 after every test in observation.test.ts
passed: before navigation-newtab-guard.test.ts the helper ran
`lsof -ti :<cdp port>`, which also lists clients still connected to the
port. One of them was the bun test process itself (its CDP socket to the
previous file's browser), so the SIGTERM ended the whole run and no
junit report was written ("workflow > server-tools setup").

Use `lsof -ti tcp:<port> -sTCP:LISTEN` and drop process.pid.

Locally, input.test.ts + navigation-newtab-guard.test.ts in one process:
before, exit 143 right after "Terminating process(es) <own pid>, ...";
after, 18 pass / 0 fail. The whole test:tools group now runs to the end
(242 pass; the 2 local failures load https://example.com, which this
sandbox's browser cannot reach and CI can).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(tools): wait_for waits for text a data: page adds, not the live example.com

With the run no longer killing itself, server-tools finished in CI with
243 pass / 1 fail: `wait_for finds text on page` waited its full 10 s
for "Example Domain" on https://example.com and never saw it - the same
page get_page_content could not read either.

The page now adds that text itself 500 ms after load, so the test still
proves wait_for waits, with nothing outside the runner involved.

Locally: 2/2 wait_for tests pass on repeat; the whole test:tools group
is 243 pass, the one local failure being take_screenshot (a 60 s hang
in this sandbox only - it passes in CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(tools): the class-selector search_dom test retries the load race too

server-tools on 3d57649 ran clean except one test that had passed on
both earlier runs: `search_dom > finds multiple elements with CSS class
selector` (123 ms, fewer than 3 matches). It searches once, straight
after new_page - the race this file already names and fixes with
searchUntil for two sibling tests. Use the same helper here.

Locally: search_dom 13/13, three runs in a row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(queen): give the 205-file salvage rename test an explicit 30 s budget

`the salvage commit > never splits a rename across the path cap` runs
real git over 205 files and salvageWorktree. It takes ~2 s for the whole
file locally and passed on the two CI runs before, then hit bun's 5 s
default once on a loaded runner (job 110500921083) with nothing in the
change touching salvage. A git-heavy fixture test should not share the
budget of a pure unit test.

Locally: queen-salvage-guards.test.ts 13 pass / 0 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* test(queen): the route-guard audit knows /queen/contributor-keys

#522 mounted /queen/contributor-keys and left the route-guard audit
unchanged, so feat/queen-supervisor fails four route-guard tests: 46
mounts against a pin of 45, 23 /queen mounts against 22, and an
unguarded mount nobody allowlisted.

The route is a server-to-server door for the app render proxy and has
its own guard: a bearer equal to QUEEN_CONTRIBUTOR_PROXY_TOKEN (32+
bytes, timingSafeEqual) plus a verified contributor header, and it is
off while that token is unset. The trusted-origin check would refuse
its only caller, so it is allowlisted with that reason and the pins are
re-measured. No other number moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

* fix(queen): keep the earnings status literal in earningsOfLogin

The base object literal widened EARNINGS_STATUS to string, so the
function no longer matched EarningsOfLogin. Typing base as
Omit<EarningsOfLogin, 'earnings'> keeps the literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions
github-actions Bot deleted the codex/contributor-keys branch October 4, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant