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
5 changes: 5 additions & 0 deletions .github/workflows/website-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ jobs:
# only our own /live/ is framed. Pure, no browser.
- name: The BROWSER view
run: npm run check:queen-browser
# The runner cabinet: the session token rides only in a header with
# credentials omitted, a runner token is shown once and kept nowhere, and
# the page has no field for a provider key. Pure, no browser.
- name: The runner cabinet
run: npm run check:queen-runners
# What that identity is FOR. The owner's rule, 2026-09-20: only registered
# people write to the Queen. The gate that holds it is the proxy's, not
# this bundle's -- a rule shipped in a public bundle is a suggestion -- and
Expand Down
1 change: 1 addition & 0 deletions apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"check:queen-redirect": "node --experimental-strip-types qa/queen-redirect-contract.mjs",
"check:app-session-identity": "node --experimental-strip-types qa/app-session-identity-contract.mjs",
"check:queen-browser": "node --experimental-strip-types qa/queen-browser-contract.mjs",
"check:queen-runners": "node --experimental-strip-types qa/queen-runners-contract.mjs",
"check:queen-chat-gate": "node --experimental-strip-types qa/queen-chat-gate-contract.mjs",
"check:queen-chat-tabs": "node --experimental-strip-types qa/queen-chat-tabs-contract.mjs",
"check:queen-contrast": "node --experimental-strip-types qa/queen-contrast-contract.mjs",
Expand Down
3 changes: 3 additions & 0 deletions apps/website/qa/queen-contrast-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ const REACHED = {
/* PEOPLE, the contributors half of that tab: the fourth sheet the list has
caught on its first run, which is four for four. */
'src/components/QueenPeople.css',
/* MY RUNNERS, the cabinet inside that tab: a veil over the hive, blurred,
like the leaderboard rows it sits beside. */
'src/components/QueenRunners.css',
/* LEVEL II, the comb on the ROADMAP view: five for five. It renders inside
.rm, whose opaque gradient already grounds it, and its own panel is
opaque on top of that. */
Expand Down
148 changes: 148 additions & 0 deletions apps/website/qa/queen-runners-contract.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// MY RUNNERS: the cabinet that mints runner tokens for the person signed in.
//
// Calls the decisions in src/lib/queenRunners.ts with real inputs and a fetch
// that records what it was asked. Each rule below is one a wrong edit would
// break quietly: a session token sent with cookies or an extra header, a
// runner token kept past the one answer that carries it, a provider key field
// slipping into a page that promises it has none.
//
// node --experimental-strip-types qa/queen-runners-contract.mjs

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import {
CABINET_HOME,
CABINET_PATH,
RUNNER_TOKEN,
cabinetOf,
callRunners,
runnerOf,
setupLines,
} from '../src/lib/queenRunners.ts'

const BASE = 'https://queen.invalid/'
const TOKEN = `qr_${'A'.repeat(43)}`
const RUNNER = {
id: 3,
label: 'laptop',
lane: 100000003,
tokenHint: 'AAAA',
createdAt: '2026-10-01T00:00:00Z',
lastSeenAt: null,
state: 'never-seen',
}

/** A fetch that answers from a script and records every request. */
function recorder(script) {
const asked = []
const fetch = async (url, init) => {
asked.push({ url, ...init })
const next = script.shift()
assert.ok(next, `unexpected request ${init.method} ${url}`)
return { ok: next.status >= 200 && next.status < 300, status: next.status, json: async () => next.body ?? {} }
}
return { asked, fetch }
}

const env = (fetch, token = 'session-token') => ({ base: BASE, fetch, token: () => token })

// 1. Nobody signed in: no request at all.
{
const { asked, fetch } = recorder([])
assert.deepEqual(await callRunners(env(fetch, null), { kind: 'list' }), { state: 'signin' })
assert.equal(asked.length, 0)
}

// 2. Every request: credentials omitted, exactly two headers, bearer is the session.
{
const { asked, fetch } = recorder([{ status: 200, body: { runners: [RUNNER], limit: 5 } }])
const view = await callRunners(env(fetch), { kind: 'list' })
assert.equal(view.state, 'ready')
assert.equal(view.cabinet.runners[0].label, 'laptop')
const [req] = asked
assert.equal(req.url, `https://queen.invalid${CABINET_PATH}`)
assert.equal(req.credentials, 'omit')
assert.deepEqual(Object.keys(req.headers).sort(), ['Authorization', 'Content-Type'])
assert.equal(req.headers.Authorization, 'Bearer session-token')
}

// 3. A refused session is "sign in", a server failure is "unavailable".
{
const a = recorder([{ status: 401 }])
assert.deepEqual(await callRunners(env(a.fetch), { kind: 'list' }), { state: 'signin' })
const b = recorder([{ status: 503 }])
assert.deepEqual(await callRunners(env(b.fetch), { kind: 'list' }), { state: 'unavailable' })
}

// 4. Create: the token is shown once, only in `minted`, and only when it is a
// runner token as minted; the list is re-read after.
{
const { asked, fetch } = recorder([
{ status: 201, body: { runner: RUNNER, token: TOKEN } },
{ status: 200, body: { runners: [RUNNER], limit: 5 } },
])
const view = await callRunners(env(fetch), { kind: 'create', label: ' my laptop ' })
assert.equal(view.state, 'minted')
assert.equal(view.token, TOKEN)
assert.equal(JSON.parse(asked[0].body).label, 'my laptop')
assert.equal(asked[1].method, 'GET')

const bad = recorder([{ status: 201, body: { runner: RUNNER, token: 'sk-live-something' } }])
assert.deepEqual(await callRunners(env(bad.fetch), { kind: 'create', label: 'x' }), { state: 'unavailable' })
}

// 5. Refusals keep the list on screen and send nothing when there is no name.
{
const kept = { runners: [runnerOf(RUNNER)], limit: 5 }
const empty = recorder([])
const noName = await callRunners(env(empty.fetch), { kind: 'create', label: ' ' }, kept)
assert.deepEqual(noName, { state: 'refused', cabinet: kept, reason: 'label' })
assert.equal(empty.asked.length, 0)
const full = recorder([{ status: 409 }])
const limit = await callRunners(env(full.fetch), { kind: 'create', label: 'one more' }, kept)
assert.deepEqual(limit, { state: 'refused', cabinet: kept, reason: 'limit' })
}

// 6. Revoke: DELETE by id, then the list; a nonsense id never reaches the wire.
{
const { asked, fetch } = recorder([{ status: 204 }, { status: 200, body: { runners: [], limit: 5 } }])
const view = await callRunners(env(fetch), { kind: 'revoke', id: 3 })
assert.equal(view.state, 'ready')
assert.equal(asked[0].method, 'DELETE')
assert.equal(asked[0].url, `https://queen.invalid${CABINET_PATH}/3`)
const n = recorder([{ status: 200, body: { runners: [], limit: 5 } }])
await callRunners(env(n.fetch), { kind: 'revoke', id: -1 })
assert.equal(n.asked.length, 1)
assert.equal(n.asked[0].method, 'GET')
}

// 7. The wire is data: malformed runners are dropped, labels are capped.
{
assert.equal(runnerOf({ ...RUNNER, state: 'pwned' }), null)
assert.equal(runnerOf({ ...RUNNER, tokenHint: '<img>' }), null)
assert.equal(runnerOf({ ...RUNNER, label: 'x'.repeat(200) }).label.length, 40)
assert.deepEqual(cabinetOf({ runners: [RUNNER, null, 7], limit: 'x' }).runners.length, 1)
assert.equal(cabinetOf(null).limit, 5)
assert.ok(RUNNER_TOKEN.test(TOKEN))
}

// 8. The setup lines name the provider key nowhere; they carry the runner
// token and the Queen's address and nothing else.
{
const lines = setupLines(TOKEN, 'https://queen.invalid').join('\n')
assert.ok(lines.includes(TOKEN))
assert.ok(!/API_KEY|sk-|provider/i.test(lines))
assert.ok(CABINET_HOME.startsWith('https://app.t27.ai/queen/'))
}

// 9. The page has no field for a provider key and never stores the token.
{
const page = readFileSync(new URL('../src/components/QueenRunners.tsx', import.meta.url), 'utf8')
const lib = readFileSync(new URL('../src/lib/queenRunners.ts', import.meta.url), 'utf8')
for (const forbidden of ['localStorage', 'sessionStorage', 'document.cookie', 'type="password"', 'apiKey', 'api_key']) {
assert.ok(!page.includes(forbidden) && !lib.includes(forbidden), `runners cabinet must not use ${forbidden}`)
}
assert.ok(!lib.includes("credentials: 'include'"))
}

console.log('queen-runners contract: ok')
9 changes: 8 additions & 1 deletion apps/website/src/components/QueenLeaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
import { useEffect, useState } from 'react'
import { QUEEN_API } from '../lib/queenApi'
import QueenPeople from './QueenPeople'
import QueenRunners from './QueenRunners'
import './QueenLeaderboard.css'

interface Contributor {
name: string
claimed: boolean
/** Their GitHub login, when the operator signed the lane as `@login`. */
github?: string
/** The lanes ran on the lender's own machine: a runner, named by Telegram. */
runner?: boolean
keys: number[]
accepted: number
/** Accepted issues whose boundary named a .t27 file: the game's own goal. */
Expand Down Expand Up @@ -163,6 +166,10 @@ export default function QueenLeaderboard({ lang }: { lang: 'en' | 'ru' }) {
open repositories. */}
<QueenPeople lang={lang === 'ru' ? 'ru' : 'en'} />

{/* The door this tab used to only point at: lend a lane by running it
yourself, with your key on your own machine. */}
<QueenRunners lang={lang === 'ru' ? 'ru' : 'en'} />

<h3 className="ql-lanes-title">{c.lanesTitle}</h3>
<p className="ql-lanes-lead">{c.lanesLead}</p>

Expand All @@ -173,7 +180,7 @@ export default function QueenLeaderboard({ lang }: { lang: 'en' | 'ru' }) {
{board.contributors.map((row, i) => {
const login = loginOf(row)
return (
<li key={row.name} className={`ql-row${row.claimed ? '' : ' is-unclaimed'}`}>
<li key={`${row.runner ? 'r' : 'k'}${row.keys[0]}`} className={`ql-row${row.claimed ? '' : ' is-unclaimed'}`}>
<span className="ql-rank">{i + 1}</span>
{/* The avatar comes from github.com/<login>.png, a public
redirect: no API call, no token, and a leaderboard that does
Expand Down
133 changes: 133 additions & 0 deletions apps/website/src/components/QueenRunners.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* MY RUNNERS: same opaque/blurred grounds as the leaderboard rows, because the
panel carries text over the lit hive (qa/queen-contrast-contract.mjs). */
.qr {
margin: 18px 0 8px;
padding: 14px 16px;
border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
border-radius: 10px;
background: var(--hud-veil, rgba(2, 8, 6, 0.72));
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
}
.qr-head h3 {
margin: 0 0 6px;
font-size: 15px;
letter-spacing: 0.08em;
color: var(--hud-green, #00ff88);
}
.qr-head p,
.qr-note,
.qr-small {
margin: 0 0 6px;
line-height: 1.5;
color: var(--hud-muted, rgba(255, 255, 255, 0.78));
}
.qr-key {
color: var(--hud-gold, #ffd700) !important;
font-size: 13px;
}
.qr-small {
font-size: 12px;
}
.qr-note a {
color: var(--hud-green, #00ff88);
}
.qr-list {
list-style: none;
margin: 10px 0;
padding: 0;
display: grid;
gap: 6px;
}
.qr-row {
display: grid;
grid-template-columns: 10px minmax(0, 1fr) auto;
grid-template-areas: 'dot label button' 'dot meta button';
align-items: center;
column-gap: 10px;
padding: 8px 10px;
border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
border-radius: 8px;
background: #020806;
}
.qr-dot {
grid-area: dot;
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.35);
}
.qr-row.is-online .qr-dot {
background: var(--hud-green, #00ff88);
}
.qr-label {
grid-area: label;
overflow-wrap: anywhere;
color: #e8f5ee;
}
.qr-meta {
grid-area: meta;
font-size: 12px;
color: var(--hud-muted, rgba(255, 255, 255, 0.78));
overflow-wrap: anywhere;
}
.qr-row button {
grid-area: button;
}
.qr button {
padding: 6px 10px;
border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
border-radius: 6px;
background: #04170e;
color: var(--hud-green, #00ff88);
font: inherit;
font-size: 13px;
cursor: pointer;
}
.qr button:disabled {
opacity: 0.5;
cursor: default;
}
.qr-form {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 8px 0 4px;
}
.qr-form input {
flex: 1 1 180px;
min-width: 0;
padding: 6px 10px;
border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
border-radius: 6px;
background: #000;
color: #e8f5ee;
font: inherit;
}
.qr-minted {
margin: 10px 0;
padding: 10px 12px;
border: 1px solid var(--hud-gold, #ffd700);
border-radius: 8px;
background: #141000;
}
.qr-minted p {
margin: 4px 0 8px;
color: var(--hud-muted, rgba(255, 255, 255, 0.78));
}
.qr-setup {
margin: 0 0 8px;
padding: 8px;
max-width: 100%;
overflow-x: auto;
white-space: pre;
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 12px;
color: #e8f5ee;
background: #000;
border-radius: 6px;
}
.qr-actions {
display: flex;
gap: 8px;
}
Loading
Loading