fix(beehive): sign in with Buzz key on selection alone

Selecting Sign in with Buzz key now dispatches the read of the saved Buzz
owner key immediately. The extra Beehive confirmation before it is removed:
selection itself is the explicit user gesture. While the read is in flight
the status line keeps the custody disclosure (Keychain read only, session
only, no local copy) and states that an OS keychain permission dialog is
separate and never bypassed or promised away.

Saved-key and matching-key import sign-in keep their confirmation and
input paths; helper source, owner matching, session-only custody,
cancellation and all backend behavior are unchanged. The test-only fixture
gains an inert env-gated pacing knob so a PTY driver can observe the
pending state and exercise Esc cancellation deterministically.

Verified with the synthetic source-UI PTY driver in a fresh
beehive-pairing-cli- HOME with explicit BEEHIVE_TEST_CREDENTIAL_FILE,
installed loader and service fences; production Keychain never read: one
selection reaches signed-in controls with no further Beehive input, and
Esc still cancels the in-flight read. Focused 12/12 backend and 8/8
renderer suites and package typecheck re-passed.

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-16 13:07:11 -04:00
parent eecde35de7
commit e9d5a0d9f9
3 changed files with 24 additions and 6 deletions

View File

@@ -1,7 +1,10 @@
# Explicit Buzz owner sign-in
Agents offers **Sign in with Buzz key** alongside saved-key and matching-key import.
The confirmation says Keychain read only, session only, no local copy. Merely
Selecting the action signs in immediately: selection itself is the explicit
gesture, and no extra Beehive confirmation is shown. While the key is read the
status line states Keychain read only, session only, no local copy, and that an
OS keychain permission dialog is separate and not bypassed. Merely
opening Beehive, selecting Agents, configuring routing, or choosing another
sign-in method never invokes this reader. It is not provider sharing/discovery.
@@ -79,6 +82,13 @@ Final semantic candidate, pinned Node **24.15.0**, package-local TypeScript:
A separate fresh traversal of the **unchanged installed launcher** exercised
saved-key sign-in (cancel/confirm), with the same explicit isolation. This is
compatibility evidence, not installation of this feature or real relay proof.
- 2025-09-16 prompt removal: the extra Beehive confirmation before the Buzz-key
read was deleted; one selection dispatches the request. The source-UI PTY
driver (`ui-single-gesture.py`) replays screen state and verified Agents →
select Buzz key once → signed-in controls with **no** Beehive confirm or other
input, Esc still cancelling the in-flight read; same fresh-HOME,
fixture-credential and service fences, production Keychain untouched. The
12/12 backend and 8/8 renderer suites and package typecheck re-passed.
- **One** default concurrent full-package run was attempted on the final semantic
candidate. It exceeded the outer tool's **400 second** bound, so no final suite
summary exists. Retained output reports failures in assignment, broker, and

View File

@@ -22,7 +22,7 @@ function request(action: string, values?: Record<string,string>, target?: string
const id = ++sequence;
const completion = new Promise<ManagerResult>(resolve => { pending = { id, resolve }; });
const message: ManagerRequest = { id, action, values, target, revision };
screen.setPending(true); const retire = screen.temporaryNotice(action === 'add-databricks' ? 'Signing in through your browser… Esc cancels the native helper. An OS credential write or browser window may remain; cancelled sign-in will not save a provider.' : 'Working… Esc stops waiting. Remote work and saved changes are not cancelled. Inspect before you try again.');
screen.setPending(true); const retire = screen.temporaryNotice(action === 'add-databricks' ? 'Signing in through your browser… Esc cancels the native helper. An OS credential write or browser window may remain; cancelled sign-in will not save a provider.' : action === 'signin-buzz' ? 'Reading the saved Buzz owner key… Keychain read only · session only · no local copy. Esc stops waiting. An OS keychain permission dialog may appear; it is separate and is not bypassed.' : 'Working… Esc stops waiting. Remote work and saved changes are not cancelled. Inspect before you try again.');
output.write(JSON.stringify(message) + '\n');
return completion.finally(retire);
}
@@ -46,10 +46,14 @@ async function routing(action: string, secret = false) {
if (key === undefined) return;
values.secret = key;
}
const confirmation = action === 'signin-buzz'
? `Sign in with Buzz key\nKeychain read only · session only · no local copy\nOwner: ${shortIdentity(owner)}\nRelay: ${relay}`
: `${action === 'configure' ? 'Configure this computer' : 'Sign in'}\nOwner: ${shortIdentity(owner)}\nRelay: ${relay}\n${action === 'configure' ? 'Create host identity in the OS credential store?' : 'Access owner key in the OS credential store?'}`;
if (!await screen.confirm(confirmation)) { delete values.secret; return; }
// Buzz-key sign-in carries no extra Beehive prompt: selecting the action is
// itself the explicit gesture (an OS keychain permission dialog is separate
// and still applies), so the request dispatches immediately. The saved-key
// and import paths keep their confirmation.
if (action !== 'signin-buzz') {
const confirmation = `${action === 'configure' ? 'Configure this computer' : 'Sign in'}\nOwner: ${shortIdentity(owner)}\nRelay: ${relay}\n${action === 'configure' ? 'Create host identity in the OS credential store?' : 'Access owner key in the OS credential store?'}`;
if (!await screen.confirm(confirmation)) { delete values.secret; return; }
}
await request(action, values); delete values.secret;
}
async function registerForm(continuation?: string, agent?: string) {

View File

@@ -21,6 +21,10 @@ export async function fixtureCredential(input: any, signal: AbortSignal) {
if (input.action === 'add-provider') { addProvider(input.directory,input.name,input.secret,{read:r=>backend.read(r as unknown as CredentialReference),create:(r,s)=>backend.create(r as unknown as CredentialReference,s)},input.type,input.endpoint,input.wire); return {ok:true}; }
if (input.action === 'models') return { ok: true, models: ['fixture-model'] };
if (input.action === 'signin-buzz') {
// Test-only pacing so a PTY driver can observe the pending state and exercise
// Esc cancellation deterministically; inert unless the driver sets it.
const paced = Number(process.env.BEEHIVE_TEST_SIGNIN_DELAY_MS ?? '0');
if (paced > 0) await new Promise((resolve, reject) => { const timer = setTimeout(resolve, paced); signal.addEventListener('abort', () => { clearTimeout(timer); reject(signal.reason); }, { once: true }); });
const key = backend.read(credentialReference('owner', input.owner));
const result = readBuzzOwnerKey(input.owner, () => key === null ? null : JSON.stringify({identity:nip19.nsecEncode(Buffer.from(key,'hex'))}));
if (!result.ok) throw Error(buzzOwnerKeyErrors[result.reason]);