// The operator's LOCAL control surface — recovery that cannot be locked out.
//
// ⛔ A UNIX SOCKET, BECAUSE THE FILESYSTEM IS THE GATE. This is the fleet's pattern, not a new one:
// pgv3 carries `ctx.transport` through hyper-svc and its handlers self-gate on it ("Block HTTP
// (anonymous callers). UDS (local operator) … allowed"), and hyper-proto-peer's own UDS surface says
// why — "a UDS connection is owner-gated by the filesystem (only a local process with access to the
// socket path can connect), so the local surface is TRUSTED WITHOUT the writer-allowlist".
//
// ⚠ AND IT IS STRICTLY STRONGER THAN THE LOOPBACK HTTP ROUTE IT REPLACES. That route gated on the
// socket's remote address, which is the right intent and a weaker gate: loopback is reachable by ANY
// process or user on the machine, while a unix socket is reachable only by one that can open its path.
// Both mean "you are on this box"; only one means "you are the operator".
//
// ⛔ WHAT IT EXISTS FOR. Losing every operator browser — cleared storage, a dead laptop — otherwise
// shuts the bridge for good: the bootstrap token gates the FIRST claim only, and browsers.js never
// reopens the instance, not even when the claimant is revoked. Without this the recovery is deleting
// the identity store, which throws away every operator grant in order to get one back.
//
// ⛔ IT ISSUES AN INVITE, NEVER RE-ARMS TOFU. Re-arming would reopen the instance to whoever reached
// the port first, on a surface bound to 0.0.0.0 — the hole the bootstrap token exists to close.
import { Service } from 'hyper-svc'
export function adminControlHandlers ({ browsers, log = () => {} }) {
return {
/** Is anyone operating this bridge yet, and who? The read a CLI needs before it acts. */
'admin-status': async () => ({
unclaimed: await browsers.unclaimed(),
operators: await browsers.list()
}),
/**
* Mint a single-use invite for another browser.
*
* ⚠ LOUD. An invite minted by a local process with nobody watching is the one action here worth an
* audit line, precisely because it needs no session.
*/
'admin-invite': async () => {
const { token, expiresAt } = await browsers.invite()
log({ msg: 'ADMIN RECOVERY — invite issued over the local control socket', expiresAt })
return { token, expiresAt }
},
/** Retire an operator browser. The counterpart to invite, for a key that should no longer hold. */
'admin-revoke': async (ctx, { publicKey } = {}) => {
if (!publicKey) throw new Error('admin-revoke: publicKey required')
await browsers.revoke(publicKey)
log({ msg: 'ADMIN RECOVERY — operator revoked over the local control socket', publicKey })
return { revoked: publicKey }
}
}
}
export async function serveAdminControl ({ browsers, uds, log = () => {} }) {
if (!uds) throw new Error('serveAdminControl: a uds path is required')
const svc = new Service({ name: 'ap-bridge-admin', uds })
const handlers = adminControlHandlers({ browsers, log })
for (const [name, fn] of Object.entries(handlers)) svc.handle(name, fn)
await svc.listen()
return { uds, close: async () => { try { await svc.close() } catch {} } }
}
export default { serveAdminControl, adminControlHandlers }