feat(queen): manage contributor keys and preserve account XP - #522
Merged
Merged
Conversation
Refs gHashTag/999-multibots-telegraf#3251 and gHashTag/t27#5472. Add trusted account key management, encrypted managed credentials, persistent consent, and stable allocation with dispatch-derived attribution.
|
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:pglivecommand also passed all 12 tests and 75 assertions with itsTRIOS_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 wasfix/queen-worker-provider-and-prompt-sizeat 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.