feat(beehive): connect authenticated owner-local compute gateways

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-10 12:04:58 -04:00
parent 419370d38c
commit 0dfe959f3a
5 changed files with 112 additions and 6 deletions

View File

@@ -0,0 +1,56 @@
# Connect an already provisioned local compute gateway
This is connection setup, **not mesh/backend provisioning**. Beehive neither
installs nor starts a compute service, opens a public listener, nor manages its
credentials. Use a gateway you have separately provisioned as the host service
operator, with a real required bearer token and OpenAI Chat Completions support.
1. On the host, arrange for that gateway to listen on literal `127.0.0.1` or
`[::1]`. Its API base must be `/v1`. Keep its service configuration local.
2. Store its actual required bearer token in an absolute, canonical owner-only
(0600) file owned by the Beehive service user. Do not put credentials in a URL,
argv, profile, remote TUI or shared inventory.
3. In the existing local setup wizard select Buzz Agent provider setup, the
installed buzz-agent executable, provider `openai-compat`, that key file,
`http://127.0.0.1:PORT/v1`, wire `chat`, and exact served wire model IDs.
Existing `local-setup` → `add-buzz-provider` also creates a new immutable binding;
do not mutate a binding used by an existing run. HTTPS services remain supported.
4. Complete normal conversation setup with separate conversation relay and
installed buzz-acp/Buzz CLI. The model HTTP URL is **not** a management or
conversation relay. Existing workspace, identity and profile controls apply.
5. Select the advertised binding/model in a public named launch configuration.
Save changes selected-next only. Explicit Start/Restart gates prompts on the
exact session model/profile. Failed preflight preserves the previous actual
run. Endpoint availability or an approved model list alone is not readiness.
## Source contract and unsupported direct mesh mode
At Buzz source `051c3a270be9c73da9ab06700bcab7d5552fceaa`,
`desktop/src-tauri/src/managed_agents/relay_mesh.rs` derives OpenAI-compatible
Chat Completions at `http://127.0.0.1:9337/v1`, maps Desktop `auto` to wire model
`mesh`, and inserts the **placeholder** `buzz-mesh-local`. The frontend is not
thereby authenticated. `managed_agents/runtime.rs:778–789` removes ambient
`OPENAI_API_KEY`. `crates/buzz-agent/src/config.rs:631–691` requires nonempty
`OPENAI_COMPAT_API_KEY`; `llm.rs` appends `/chat/completions` and uses bearer auth.
Beehive retains this closed provider environment and does not insert a placeholder
or fall back to ambient credentials. **Direct unauthenticated Desktop mesh is not
supported by this slice.** Native optional-auth support would require separately
authorized consumer changes; do not make a fake key to appear configured.
Use exact wire IDs, not the Desktop `auto` alias. `mesh` may route/degrade among
workers, so exact wire-model confirmation does not attest physical model/committee
membership. No mesh-specific long deadline, generation defaults or capacity promise
is imported. `commands/agent_providers.rs` describes discovered `buzz-backend-*`
JSON info invocations for compute deployment, not another LLM provider type; those
backend operations are not implemented here.
## Evidence scope
The reused installed normal OpenAI-compatible journey drives the actual local
wizard, host CLI subprocess and remote TUI; real installed buzz-acp and Buzz CLI
publish four verified same-agent signed replies. A source-shaped TypeScript ACP
consumer calls a bearer-protected loopback Chat Completions fixture. It checks
exact model/native profile, fresh Restart, selected-next versus actual, sibling and
history preservation, HTTP wrong-model/malformed-protocol/unreachable failures,
missing required credentials and no private inputs in inventory. This is not real
Buzz Agent inference, a deployed mesh network or production provider attestation.

View File

@@ -16,7 +16,9 @@ export function validateBuzzProvider(value: BuzzProvider): void {
if (value.auth === 'external-oauth' ? value.apiKeyFile !== undefined : typeof value.apiKeyFile !== 'string' || !isAbsolute(value.apiKeyFile)) throw Error('Provider requires owner-only key file; external OAuth forbids token fallback');
let url: URL;
try { url = new URL(value.baseUrl); } catch { throw Error('Invalid local provider endpoint'); }
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) throw Error('Provider endpoint requires HTTPS without credentials, query or fragment');
// Literal loopback only: no DNS resolution, LAN exposure or alternative URL spellings.
const loopback = value.provider === 'openai-compat' && url.protocol === 'http:' && /^http:\/\/(127\.0\.0\.1|\[::1\])(?::[0-9]+)?\/v1\/?$/.test(value.baseUrl);
if ((!loopback && url.protocol !== 'https:') || url.username || url.password || url.search || url.hash) throw Error('Provider endpoint requires HTTPS or OpenAI-compatible literal loopback HTTP /v1, without credentials, query or fragment');
if (value.provider === 'databricks_v2' && (url.pathname !== '/' || url.port)) throw Error('Databricks workspace requires an HTTPS origin without path or custom port');
if (value.provider === 'openai-compat' ? !['auto', 'chat', 'responses'].includes(value.wire ?? '') : value.wire !== undefined) throw Error('Unsupported provider wire combination; only OpenAI-compatible accepts auto/chat/responses');
}
@@ -38,7 +40,7 @@ export function buzzProviderEnvironment(value: BuzzProvider, home: string, confi
return { ...base, [contract.key]: key, [contract.url]: value.provider === 'databricks_v2' ? new URL(value.baseUrl).origin : value.baseUrl, ...(value.wire ? { OPENAI_COMPAT_API: value.wire } : {}) };
}
/** Operator guidance; no automatic install, login, refresh or cache harvesting. */
export const buzzProviderGuidance = 'Buzz Agent provider binding: install buzz-agent locally as the dedicated service user. Anthropic uses ANTHROPIC_API_KEY; OpenAI-compatible uses OPENAI_COMPAT_API_KEY (NOT OPENAI_API_KEY), with explicit auto/chat/responses wire mode; OpenRouter uses OPENROUTER_API_KEY and Chat Completions only. Endpoint and owner-only key file stay local. Approved models are not an authenticated catalog. Start must prove the exact session model; Save never restarts. Databricks v2 uses DATABRICKS_HOST and either an owner-only DATABRICKS_TOKEN file or explicitly external native OAuth (no token fallback). Local configuration and key presence are not verified login, refresh or model access. No login or endpoint probe initiated.';
export const buzzProviderGuidance = 'Buzz Agent provider binding: install buzz-agent locally as the dedicated service user. Anthropic uses ANTHROPIC_API_KEY; OpenAI-compatible uses OPENAI_COMPAT_API_KEY (NOT OPENAI_API_KEY), with explicit auto/chat/responses wire mode; OpenRouter uses OPENROUTER_API_KEY and Chat Completions only. Endpoint and owner-only key file stay local. Approved models are not an authenticated catalog. Start must prove the exact session model; Save never restarts. Databricks v2 uses DATABRICKS_HOST and either an owner-only DATABRICKS_TOKEN file or explicitly external native OAuth (no token fallback). Local configuration and key presence are not verified login, refresh or model access. No login or endpoint probe initiated. For an already provisioned owner-local compatible compute gateway, choose openai-compat, its literal loopback HTTP /v1 endpoint, chat wire and exact served model ID. Provision its real required bearer token in the owner-only key file. No service is downloaded, launched or exposed. This is connection setup, not backend provisioning. The pinned Desktop mesh uses a placeholder key for its unauthenticated frontend; native Buzz Agent still requires a nonempty key. That unauthenticated mesh contract is NOT supported here: do not create a fake key. Use a genuinely authenticated compatible local gateway or await native optional-auth support. Desktop auto maps to mesh; Beehive requires the exact wire model, with no auto alias or committee-capacity attestation. Management/conversation relay URLs are separate from this model endpoint.';
/** Shared normal/management wizard, not a generic remote environment form. */
export async function buzzProviderInput(ui: { question(prompt: string): Promise<string> }): Promise<BuzzProvider> {
console.log(buzzProviderGuidance);
@@ -47,7 +49,7 @@ export async function buzzProviderInput(ui: { question(prompt: string): Promise<
const auth = provider === 'databricks_v2' ? await ui.question('Databricks authentication [token / external-oauth]: ') as BuzzProvider['auth'] : undefined;
if (provider === 'databricks_v2' && !['token', 'external-oauth'].includes(auth ?? '')) throw Error('Choose explicit Databricks auth; no fallback');
const apiKeyFile = auth === 'external-oauth' ? undefined : await ui.question(`Absolute owner-only local ${contracts[provider].key} file (contents never relayed): `);
const baseUrl = await ui.question('Provider HTTPS base URL (local only): ');
const baseUrl = await ui.question('Provider HTTPS base URL (or OpenAI-compatible http://127.0.0.1:PORT/v1; local only): ');
const wire = provider === 'openai-compat' ? await ui.question('OpenAI-compatible wire [auto / chat / responses]: ') as BuzzProvider['wire'] : undefined;
const models = (await ui.question('Operator-approved compatible exact Buzz Agent model IDs (comma-separated): ')).split(',').map(m => m.trim());
const result = { provider, ...(apiKeyFile === undefined ? {} : { apiKeyFile }), ...(auth ? { auth } : {}), baseUrl, ...(wire ? { wire } : {}), models };

View File

@@ -51,3 +51,11 @@ for (const auth of ['token', 'external-oauth'] as const) test(`Databricks v2 ${a
if (auth === 'token') assert.throws(() => prepareAgent(launch), /API key prerequisite/);
else assert.doesNotThrow(() => prepareAgent(launch), 'external OAuth does not inspect credential/cache files, including absent refresh stores');
});
test('owner-local compatible compute URL boundary remains credential-required', () => {
const binding: BuzzProvider = { provider: 'openai-compat', apiKeyFile: '/missing-local-key', baseUrl: 'http://127.0.0.1:9337/v1', wire: 'chat', models: ['mesh'] };
for (const baseUrl of ['http://127.0.0.1:9337/v1', 'http://[::1]:9337/v1/']) assert.doesNotThrow(() => validateBuzzProvider({ ...binding, baseUrl }));
for (const baseUrl of ['http://localhost:9337/v1', 'http://127.1:9337/v1', 'http://2130706433:9337/v1', 'http://127.0.0.1:9337', 'http://192.168.1.2:9337/v1', 'http://127.0.0.1:9337/v1?key=secret', 'http://user:pass@127.0.0.1:9337/v1']) assert.throws(() => validateBuzzProvider({ ...binding, baseUrl }));
assert.throws(() => validateBuzzProvider({ ...binding, apiKeyFile: undefined }));
assert.throws(() => validateBuzzProvider({ ...binding, provider: 'anthropic', wire: undefined }));
});

View File

@@ -6,11 +6,12 @@ import { once } from 'node:events';
import { publicKey } from '../src/protocol.ts';
// Native profile evidence is recorded from session/new input, never the env fallback.
const buzzProvider = process.env.BUZZ_AGENT_PROVIDER;
const localCompute = buzzProvider === 'openai-compat' && /^http:\/\/127\.0\.0\.1:[0-9]+\/v1$/.test(process.env.OPENAI_COMPAT_BASE_URL ?? '');
if (['anthropic', 'openai-compat', 'openrouter'].includes(buzzProvider ?? '')) {
const p = buzzProvider!;
const key = p === 'anthropic' ? 'ANTHROPIC_API_KEY' : p === 'openai-compat' ? 'OPENAI_COMPAT_API_KEY' : 'OPENROUTER_API_KEY';
const endpoint = p === 'anthropic' ? 'ANTHROPIC_BASE_URL' : p === 'openai-compat' ? 'OPENAI_COMPAT_BASE_URL' : 'OPENROUTER_BASE_URL';
if (process.env[key] !== `fixture-${p}-private-key` || process.env[endpoint] !== `https://${p}.fixture.invalid/v1` || process.env.OPENAI_API_KEY || process.env.DATABRICKS_HOST || process.env.GOOSE_PROVIDER || process.env.ANTHROPIC_MODEL || !process.env.HOME?.endsWith('service-home') || !process.env.BUZZ_AGENT_CONFIG_DIR?.endsWith('agent-config') || (p === 'openai-compat' ? process.env.OPENAI_COMPAT_API !== 'responses' : process.env.OPENAI_COMPAT_API !== undefined)) throw Error('Incorrect Buzz provider contract');
if (process.env[key] !== `fixture-${p}-private-key` || (!localCompute && process.env[endpoint] !== `https://${p}.fixture.invalid/v1`) || process.env.OPENAI_API_KEY || process.env.DATABRICKS_HOST || process.env.GOOSE_PROVIDER || process.env.ANTHROPIC_MODEL || !process.env.HOME?.endsWith('service-home') || !process.env.BUZZ_AGENT_CONFIG_DIR?.endsWith('agent-config') || (p === 'openai-compat' ? process.env.OPENAI_COMPAT_API !== (localCompute ? 'chat' : 'responses') : process.env.OPENAI_COMPAT_API !== undefined)) throw Error('Incorrect Buzz provider contract');
for (const other of ['ANTHROPIC_API_KEY', 'OPENAI_COMPAT_API_KEY', 'OPENROUTER_API_KEY']) if (other !== key && process.env[other]) throw Error('Mixed provider credentials');
writeFileSync(`${p}-env-checked`, 'closed provider-specific contract');
}
@@ -121,6 +122,16 @@ for await (const line of createInterface({ input: process.stdin })) {
if ((goose || claude || codex || buzzProvider) && gooseSessions.has(m.params.sessionId)) sessionId = m.params.sessionId;
appendFileSync('received-prompts.jsonl', JSON.stringify(m.params) + '\n');
if (selected !== model || m.params.sessionId !== sessionId) process.exit(8);
// Source-shaped OpenAI consumer: the loopback fixture is not a mesh network.
if (localCompute) {
const response = await fetch(`${process.env.OPENAI_COMPAT_BASE_URL}/chat/completions`, {
method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.OPENAI_COMPAT_API_KEY}` },
body: JSON.stringify({ model: selected, stream: false, messages: [{ role: 'user', content: 'isolated fixture prompt' }] }),
signal: AbortSignal.timeout(2000),
});
const body = await response.json() as any;
if (!response.ok || body.model !== selected || typeof body.choices?.[0]?.message?.content !== 'string') throw Error('Local compute protocol/model failure');
}
writeFileSync('prompt-started', selected); prompt = m.id;
const drift = { jsonrpc: '2.0', method: 'session/update', params: { sessionId, update: mode === 'config-drift' ? { sessionUpdate: 'config_option_update', configOptions: [{ category: 'model', currentValue: 'other' }] } : { sessionUpdate: 'current_model_update', currentModelId: 'other' } } };
if (['current-drift', 'config-drift'].includes(mode)) send(drift);

View File

@@ -25,7 +25,7 @@ for (const kind of ['goose', 'claude', 'codex', 'custom', 'anthropic', 'openai-c
const databricks = kind.startsWith('databricks'), oauth = kind === 'databricks-oauth';
const buzz = databricks || ['anthropic', 'openai-compat', 'openrouter'].includes(kind);
const provider = databricks ? 'databricks_v2' : kind;
const endpoint = databricks ? 'https://databricks.fixture.invalid' : `https://${kind}.fixture.invalid/v1`;
let endpoint = databricks ? 'https://databricks.fixture.invalid' : `https://${kind}.fixture.invalid/v1`;
const keyName = databricks ? 'DATABRICKS_TOKEN' : kind === 'anthropic' ? 'ANTHROPIC_API_KEY' : kind === 'openai-compat' ? 'OPENAI_COMPAT_API_KEY' : 'OPENROUTER_API_KEY';
const title = codex ? 'Codex' : 'Claude';
const model = goose ? 'goose-model-a' : `${kind}-model-a`;
@@ -47,6 +47,25 @@ for (const kind of ['goose', 'claude', 'codex', 'custom', 'anthropic', 'openai-c
args: ['acp', literal], env: { CUSTOM_PRIVATE: 'owner-only-custom-value' }, installHint: 'Fixture only', installInstructionsUrl: '', contract: 'goose-native' });
writeFileSync(runner, `#!${realpathSync(process.execPath)}\nif (process.argv.length !== 4 || process.argv[2] !== 'acp' || process.argv[3] !== ${JSON.stringify(literal)} || process.env.CUSTOM_PRIVATE !== 'owner-only-custom-value') throw Error('custom argv/env mismatch');\nawait import(${JSON.stringify(pathToFileURL(resolve('test/conversation-harness-fixture.ts')).href)});\n`, { mode: 0o700 });
}
const computeRequests: string[] = [];
let computeFailure = '';
const compute = kind === 'openai-compat' ? createServer(async (req, res) => {
let bytes = ''; for await (const chunk of req) bytes += chunk;
assert.equal(req.url, '/v1/chat/completions'); assert.equal(req.method, 'POST');
assert.equal(req.headers.authorization, 'Bearer fixture-openai-compat-private-key');
const body = JSON.parse(bytes); assert.equal(body.model, model);
assert.equal(body.stream, false); assert.ok(Array.isArray(body.messages));
computeRequests.push(body.model);
res.setHeader('Content-Type', 'application/json');
if (computeFailure === 'protocol') { res.end('{}'); return; }
res.end(JSON.stringify({ model: computeFailure === 'model' ? 'wrong' : model, choices: [{ message: { role: 'assistant', content: 'local fixture response' }, finish_reason: 'stop' }] }));
}) : undefined;
if (compute) {
await new Promise<void>(resolve => compute.listen(0, '127.0.0.1', resolve));
const address = compute.address(); assert.ok(address && typeof address !== 'string');
endpoint = `http://127.0.0.1:${address.port}/v1`;
t.after(async () => { compute.closeAllConnections(); if (compute.listening) await new Promise<void>(resolve => compute.close(() => resolve())); });
}
const http = createServer();
t.after(async () => { http.closeAllConnections(); if (http.listening) await new Promise<void>(resolve => http.close(() => resolve())); rmSync(dir, { recursive: true, force: true }); });
await new Promise<void>(resolve => http.listen(0, '127.0.0.1', resolve));
@@ -64,7 +83,7 @@ for (const kind of ['goose', 'claude', 'codex', 'custom', 'anthropic', 'openai-c
...(databricks ? [{ prompt: 'Databricks authentication [', answer: oauth ? 'external-oauth' : 'token' }] : []),
...(!oauth ? [{ prompt: `Absolute owner-only local ${converting ? 'ANTHROPIC_API_KEY' : keyName} file`, answer: converting ? initialKey : keyFile }] : []),
{ prompt: 'Provider HTTPS base URL', answer: converting ? 'https://anthropic.fixture.invalid/v1' : endpoint },
...(kind === 'openai-compat' ? [{ prompt: 'OpenAI-compatible wire [', answer: 'responses' }] : []),
...(kind === 'openai-compat' ? [{ prompt: 'OpenAI-compatible wire [', answer: 'chat' }] : []),
{ prompt: 'Operator-approved compatible exact Buzz Agent model IDs', answer: model },
] : []),
{ prompt: 'Allowed workspace (absolute directory): ', answer: dir },
@@ -367,6 +386,7 @@ for (const kind of ['goose', 'claude', 'codex', 'custom', 'anthropic', 'openai-c
assert.deepEqual(readFileSync(join(installation, 'setup.json')), convertedManifest);
assert.equal(state().actual.selection.behavior.revision, restartBehavior.revision);
assert.equal(replies.length, 4, 'Restart must produce a fresh installed signed reply');
if (compute) assert.ok(computeRequests.length >= 4, 'source-shaped local HTTP Chat Completions consumer crossed on actual prompts');
assert.match(readFileSync(join(dir, 'received-system-instructions'), 'utf8'), /NORMAL-PROFILE-BOUNDARY/);
if (buzz) {
assert.equal(readFileSync(join(dir, 'received-system-instructions'), 'utf8'), restartInstructions);
@@ -399,6 +419,15 @@ for (const kind of ['goose', 'claude', 'codex', 'custom', 'anthropic', 'openai-c
assert.deepEqual(state().actual, actual);
}
}
if (compute) {
writeFileSync(join(dir, 'mode'), 'ok');
for (const failure of ['model', 'protocol', 'unreachable']) {
computeFailure = failure;
if (failure === 'unreachable') { compute.closeAllConnections(); await new Promise<void>(resolve => compute.close(() => resolve())); }
assert.notEqual((await request(message('restart', 'journey', agent, state().revision, {}))).body.result, 'accepted');
assert.deepEqual(state().actual, actual, `${failure} local service preflight preserves actual`);
}
}
if (buzz && !oauth) {
writeFileSync(join(dir, 'mode'), 'ok');
writeFileSync(keyFile, 'wrong-fixture-credential');