Skip to content

feat(queen): runner cabinet - lend a lane without lending a key - #518

Open
dmitrii-f-t27 wants to merge 9 commits into
fix/queen-worker-provider-and-prompt-sizefrom
claude/gallant-bardeen-h2hgaa
Open

dmitrii-f-t27 wants to merge 9 commits into
fix/queen-worker-provider-and-prompt-sizefrom
claude/gallant-bardeen-h2hgaa

Conversation

@dmitrii-f-t27

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

Copy link
Copy Markdown
Collaborator

What and why

The leaderboard asks people to "lend the swarm a key", but most providers forbid handing a key to a third party (see how-to-join-the-swarm). The design that asks nobody to breach their terms is the one where the key never moves: the runner runs on the lender's machine, under the lender's account.

This PR is the registry half of that design.

  • /queen/me/runners (GET/POST/DELETE): the cabinet for a person signed in on app.t27.ai. The server sends the person's bearer to the issuer (vibee-render /mcp whoami) and gets back their telegram_id.
    • Lists, creates and revokes that person's runner tokens.
    • At most 5 live runners per person.
    • A token is shown once. Only its SHA-256 and last 4 characters are stored.
  • /queen/runner/heartbeat (POST, runner token): marks the runner online and returns its lane. It answers work: null and says honestly that handing out tasks is the next stage.
  • queen_runner table in MIGRATION_SQL. A runner's lane is 100000000 + id, a key_index block that no operator pool reaches. Dispatch rows on that lane are therefore credited by the existing leaderboard arithmetic.
  • Leaderboard: runner lanes are grouped by person (telegram_id), never by name. A runner cannot merge into an operator's row, and it never gets a link to an unverified GitHub login.
  • CORS for /queen/me/*: only https://app.t27.ai, Authorization + Content-Type, no credentials. It is mounted before trustedCorsMiddleware, like the public routes.
  • route-guard audit: both new mounts are on the allowlist with an "own bearer" reason. The pins are re-measured: 45→47 mounts, 22→24 /queen. No other count changed.

No route accepts, stores or returns a provider key.

Website counterpart: gHashTag/trinity#1205 — the MY RUNNERS panel on the LEADERBOARD tab. Deploy this PR first.

Verification

  • bun run test:api: 1315 pass, 0 fail, run after rebasing onto 6bf7efe (feat(queen): public board carries the Queen's verdict and judged head #517).
    • Earlier, the route-guard test failed on the new mounts; it passed once the allowlist and pins were updated.
  • tests/api/queen-runners.test.ts (22 tests) covers:
    • parsing whoami, and telling a refused token (401) apart from an unreachable issuer (503);
    • the cache;
    • the token format, and the token never appearing in the list response;
    • the per-person limit and that a revoked runner frees its slot;
    • isolation between people;
    • heartbeat before and after revoke;
    • CORS;
    • grouping on the leaderboard.
  • bun run typecheck is clean, and biome check is clean on the changed files.
  • bun run test:pglive with TRIOS_PG_TEST_URL pointing at a local Postgres 16: 3 pass.
  • I also ran the real SQL against that database:
    • the limit (the 6th create returned limit), the heartbeat by hash, and revoke, including that someone else's revoke returns false;
    • a dispatch on lane 100000002 appeared on the leaderboard as {name: "Alice", runner: true, xp: 320}.

Limitations / next stage

  • Runners cannot take work yet. Still to do: claim/complete, review of a branch pushed from GitHub, and a runner CLI.
    • The review currently diffs queen-<issue> inside the container.
    • reapDispatchesFromPreviousBoot reaps every unfinished row, so remote rows need to be excluded.
  • The cabinet forwards the app's full session token to the issuer's whoami for verification. It is never logged or stored; the cache is keyed by its SHA-256. A narrowly scoped token from vibee-render would be better.
  • Deployment: the migration creates queen_runner at boot. TRIOS_APP_IDENTITY_URL is optional; the default is the production vibee-render.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2

A signed-in person (app.t27.ai session, verified by asking its issuer's
whoami for telegram_id) can mint, list and revoke runner tokens at
/queen/me/runners. A runner process on the lender's own machine presents
that token at /queen/runner/heartbeat and learns its lane. The provider
key never reaches the Queen: no route accepts, stores or returns one.

- queen_runner table: only the token's SHA-256 and last four characters
  are kept; at most 5 live runners per person.
- A runner's lane is 100000000 + id, a key_index block no operator pool
  reaches, so dispatch rows on it are credited by the existing
  leaderboard arithmetic.
- Leaderboard gathers runner lanes by person (telegram_id), never by
  name, so a runner cannot merge into an operator's row, and never links
  a runner to an unverified GitHub login.
- CORS for /queen/me/* is exactly https://app.t27.ai, bearer only, no
  credentials; both new mounts are on the route-guard allowlist with an
  own-bearer reason, and the audit pins are re-measured.

Taking tasks and handing work back (claim/complete, remote review,
runner CLI) is the next stage; the heartbeat says so instead of
offering work.

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

Copy link
Copy Markdown
Collaborator Author

cla is red because of how the workflow is configured, not because of this PR. The CLA action stops before it checks any signature: Please add a personal access token as an environment variable for writing signatures in a remote repository/organization and then Could not retrieve repository contents (log). PERSONAL_ACCESS_TOKEN is empty in this repository, and signatures are stored in the remote browseros-ai/cla-signatures.

The same failure appears on #517 (two runs), which was merged.

Nothing in this PR can fix it. It needs the token secret to be set, or the CLA workflow to be disabled for this fork. A re-run would fail the same way, so I have not re-run it.


Generated by Claude Code

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

✅ Tests passed — 2457/2517

Suite Passed Failed Skipped
✅ agent 87/87 0 0
✅ build 9/9 0 0
✅ cdp-protocol 5/5 0 0
✅ eval 93/93 0 0
✅ server-agent 272/272 0 0
✅ server-api 1327/1386 0 59
✅ server-browser 6/6 0 0
✅ server-integration 10/11 0 1
✅ server-lib 279/279 0 0
✅ server-pglive 12/12 0 0
✅ server-root 68/68 0 0
✅ server-skills 31/31 0 0
✅ server-tools 244/244 0 0
✅ shared 14/14 0 0

View workflow run

Copy link
Copy Markdown
Collaborator Author

All Tests / * jobs were cancelled, but the cause is a problem on the base branch, not in this PR.

Every job hangs in Install dependencies (bun ci) until the 20-minute timeout-minutes cancels it, before any test runs (server-api log). #517 had the same cancellations.

Reproduced locally on git archive of this head (trios/agent-server):

Bun committed apps/server/node_modules symlink bun ci
1.4.2 (what CI installs) present hangs, killed by timeout 120
1.4.2 removed 2325 packages installed in 11.97 s
1.3.14 present installs fine

Two things on feat/queen-supervisor combine to cause this:

  1. A dangling symlink is committed: trios/agent-server/apps/server/node_modules -> /Users/playom/queen-patches/work/browseros-deploy/trios/agent-server/apps/server/node_modules. It was last touched in ce13b7e (feat(queen): a lane can be signed with a GitHub login #511) and looks like a local deploy path that was committed by accident.
  2. CI runs the latest Bun instead of the pinned one. oven-sh/setup-bun@v2 is called without bun-version, and the packageManager: bun@1.3.6 pin lives in trios/agent-server/package.json, not at the repo root. CI therefore gets Bun 1.4.2, which hangs on that symlink.

This PR does not touch either file, so I have not widened it.

Proposed patch (either part alone unblocks CI; both are better):

git rm --cached trios/agent-server/apps/server/node_modules   # drop the Mac-local symlink
# .github/workflows/test.yml, "Setup Bun"
      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version-file: trios/agent-server/package.json

Once that lands on feat/queen-supervisor, I'll merge it into this branch and CI should run the suites. Locally this head passes bun run test:api 1315/0, typecheck, and test:pglive 3/0.


Generated by Claude Code

claude added 6 commits October 1, 2026 16:53
…dules 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
…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
… 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
…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
… 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
…udget

`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
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
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>
Retargets the runner cabinet at the branch production builds from.
Conflicts, all additive:
- server.ts: runner mounts beside /queen/public-credits and
  /queen/public-earnings.
- pg-migrate.ts: queen_runner beside queen_tri_earnings.
- route-guard-audit.mjs: the runner allowlist entries beside the
  production branch's /queen/contributor-keys entry (same reason text).
- route-guard.test.ts: the production branch's pins plus the two runner
  mounts: 51 mounts; 28 /queen mounts as 10 public-read, 9 wrapper,
  9 allowlisted; eleven unguarded without the allowlist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ8KjRoGNBoHBoDR92fAo2
@dmitrii-f-t27
dmitrii-f-t27 changed the base branch from feat/queen-supervisor to fix/queen-worker-provider-and-prompt-size October 3, 2026 20:43

Copy link
Copy Markdown
Collaborator Author

I've retargeted this PR to fix/queen-worker-provider-and-prompt-size, the branch production builds from. To do that I merged that branch in (d98971d).

The conflicts were all cases where both sides added something:

  • Mounts: the runner mounts now sit beside /queen/public-credits and /queen/public-earnings.
  • Database: the queen_runner table sits beside queen_tri_earnings.
  • Route-guard audit: both sides' entries are kept. The test pins are now 51 mounts; of the 28 /queen mounts, 10 are public-read, 9 go through a wrapper and 9 are allowlisted.

The diff against the new base is the runner cabinet alone, 10 files. The #520 CI fixes are already on the base.

Local results: test:api 1399 passed, 0 failed; test:pglive 25 passed; test:root 68 passed.

#521 has been retargeted to the same branch. It contains this PR, so merge this one first.


Generated by Claude Code

dmitrii-f-t27 pushed a commit to gHashTag/trinity that referenced this pull request Oct 3, 2026
The setup lines fetched queen-runner.mjs from feat/queen-supervisor,
where it does not exist (a 404, as the review found). The runner PRs
(gHashTag/BrowserOS#518, #521) now target
fix/queen-worker-provider-and-prompt-size, the branch the deployed
Queen is built from, so the script and its README are linked there,
through one RUNNER_BRANCH constant.

The contract's "no provider key" check matched the bare word
"provider", which that branch name contains. It now matches a key in
any spelling (API_KEY, sk-, provider key) and proves it still catches
each of them.

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

Copy link
Copy Markdown
Collaborator Author

The Tests workflow never started for this PR's current head (d98971d). Only PR Conventional Commit Validation and CLA Assistant ran. The push changes trios/agent-server/**, so the workflow's pull_request trigger and path filter both cover it. The event looks dropped. I can't re-run it or dispatch it (the dispatch call returned 403), and I won't push an empty commit to kick it.

Here is the evidence for this head in the meantime:

A maintainer can start the run with Re-run or Run workflow (test.yml on claude/gallant-bardeen-h2hgaa). Otherwise it will run on the next code push to this branch.


Generated by Claude Code

dmitrii-f-t27 pushed a commit to gHashTag/trinity that referenced this pull request Oct 4, 2026
…has it

The MY RUNNERS panel is live on app.t27.ai, and the server half
(gHashTag/BrowserOS#518) is not deployed yet: the production Queen
answers /queen/me/runners with 404. The panel read every non-OK answer
as an outage and told each signed-in person "The Queen did not answer.
Try again in a minute." while she was answering.

A server that has the cabinet answers that path 200, 401 or 503, never
404, so a 404 on list or create now means "not here yet": the panel
says runners are on their way and switches on by itself once the server
offers them. No create form is shown in that state. 503 and network
failures still read "did not answer".

Measured before the change: the production server answers the path 404
with Access-Control-Allow-Origin: https://app.t27.ai, so the browser
does see the status.

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

This branch has not been deployed

No deployments
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.

2 participants