Skip to content

feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth - #5545

Merged
wpfleger96 merged 29 commits into
mainfrom
duncan/databricks-auth-coordinator
Sep 1, 2026
Merged

feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth#5545
wpfleger96 merged 29 commits into
mainfrom
duncan/databricks-auth-coordinator

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Aug 11, 2026

Copy link
Copy Markdown
Member

What

Consolidates all Databricks OAuth acquisition behind one coordinator on PkceOAuthTokenSource. Every entry point — the four TokenSource methods (bearer, bearer_no_browser, refresh_now, interactive_login) plus the public acquire_with_intent — routes through a single acquire()/acquire_locked() core that owns browser and cooldown policy.

Before this, acquisition logic was scattered across those methods with no coordination: concurrent callers (Desktop discovery, the saved-agent model picker, managed-runtime inference) could each pop their own browser, and a just-denied attempt would immediately re-prompt on the next passive read.

How

  • Intent policy. AuthIntent::{Auto, UserInitiated, Headless} decides whether a caller may open a browser and whether it honors the cooldown. Headless never browses; Auto browses but honors an unexpired cooldown; UserInitiated browses and bypasses+clears the cooldown.
  • Two-layer single-flight per cache key. An in-process registry (INFLIGHT) coalesces same-key, same-intent callers onto one leader's attempt before the file lock. The slot key is (lock_path, AuthIntent), so a UserInitiated sign-in never inherits an Auto leader's result. A joined result is revalidated against the waiter's own contract. Across processes, callers serialize on a flock-based advisory lock and share success through the on-disk cache. RAII Drop releases both lock and leader slot.
  • Joiner credential-state reconciliation. SlotPublish carries the full CachedToken on success. Each joiner reconciles its own independent state cell under state.lock().await before returning: adopt when absent, expired, or matching the joiner's rejected credential; preserve any distinct newer usable credential. On a matching shared failure, neutralize the joiner's in-memory rejected entry under lock via expire_rejected_memory — durable disk mutation is reserved for acquire_locked under the cross-process file lock. Without this, a joining source's state remains stale or empty and subsequent plain bearer() calls resurface the rejected or absent credential.
  • Validate-before-persist boundary. finish() is the candidate-token persistence boundary for refresh and browser results. Before a token is written to cache or the cooldown is cleared, a bearer equal to the caller's rejected bytes yields a typed failure.
  • Token neutralization. When acquire_locked enters with rejected = Some(bytes), it calls expire_rejected() under the state lock before any cache check.
  • Cross-process failure single-flight. An AttemptRecord sidecar records a monotonically-increasing generation, intent, result code, and SHA-256 digest of the completing caller's rejected token. Adoption is temporal (pre-queue snapshot predates current generation) and digest-matched.
  • Typed outcomes. AuthError with stable code()/from_code() replaces display-text matching.
  • Durable cooldown sidecar. Every failed browser attempt is recorded next to the cache key. 5-minute expiry.
  • Windows disk persistence disabled. On non-Unix platforms, persist() is a no-op. Lock, cooldown, and attempt sidecars are active on all platforms. Tests that seed or assert on the on-disk token cache are #[cfg(unix)]-gated.
  • Injected browser opener invoked while the localhost callback listener is live.

Tests

  • crates/buzz-agent/tests/databricks_auth_coordinator.rs: browser/cooldown/classification acceptance matrix with a scripted BrowserOpener and stub OIDC provider. P1 regressions exercise the full finish()acquire_locked()acquire_leader()LeaderGuard::complete() → joiner wiring:
    • test_inprocess_joiner_reconciles_stale_state_after_shared_success (Unix): two real sources both loaded locally-fresh-but-rejected X; after shared success Y, subsequent plain bearer() on both returns Y, not X.
    • test_inprocess_joiner_neutralizes_rejected_on_matching_shared_failure (Unix): B's matching rejected X is force-expired in memory after shared RefreshRejected; subsequent read cannot return X.
    • test_inprocess_joiner_populates_empty_state_no_second_acquisition (non-Unix): empty A/B join a browser success; B's subsequent headless read returns Y without a second browser (no disk fallback on non-Unix exposes the regression).
    • test_crossprocess_userinitiated_waiter_adopts_predecessor_denial (snapshot-marker barrier replacing an earlier sleep for deterministic generation ordering).
  • auth.rs in-crate tests: lock-primitive edges, disk-recheck on shared failure (#[cfg(unix)]), and:
    • test_joiner_reconciliation_blocked_until_state_lock_released: deterministic direct-poll proof that awaited reconciliation requires state.lock().await. The test task holds B's state mutex and manually polls a pinned real acquire() future — Poll 2 (slot published, mutex still held) must return Pending because lock().await blocks; with try_lock instead, Poll 2 returns Ready, failing the assertion.
    • test_joiner_preserve_distinct_newer_credential: deterministic direct polling parks B at slot.wait(), then writes Z directly into B's state in the same task, then publishes Y and awaits completion. B must return Y but leave state == Z. Mutation check: unconditional adoption overwrites Z with Y, failing the state assertion.
    • test_joiner_shared_failure_recovers_disk_replacement (Unix): the matching-failure joiner enters the recovery branch — after expire_rejected_memory (in-memory, empty state no-op) it reads a sibling-written disk replacement via usable_from_disk and returns it. Mutation check: removing the usable_from_disk recovery branch returns Err(RefreshRejected).
    • test_joiner_failure_does_not_write_disk (Unix): byte-for-byte disk-invariance regression — a matching-failure joiner calls expire_rejected_memory and must not touch the on-disk cache. An independent process C may write a valid replacement between A's failure and B's reconciliation; this guard ensures B's unfenced in-memory neutralization cannot overwrite C's concurrent disk write. Mutation check: reverting to expire_rejected rewrites the file (expires_at = 0), changing the bytes and failing the assertion.
    • test_lock_timeout_leaves_cooldown_sidecar_byte_for_byte_untouched: a waiter past its deadline returns LockTimeout before entering acquire_locked; the cooldown sidecar bytes are unchanged. Documents a pre-lock limitation: LockTimeout callers do not neutralize state or sidecars.

Scope / follow-ups

  • Runtime 401 handling is deferred. This PR owns acquisition single-flight and policy.
  • Desktop wiring is Phase 2 (not in this PR's boundary). Confined to crates/buzz-agent/.
  • Windows DACL is a follow-up once the windows-sys binding is available.

Stack

Built on #5534 (hayt/databricks-oauth-cache-hardening), now merged. Retargeted to main.

@wpfleger96
wpfleger96 requested a review from a team as a code owner August 11, 2026 05:09
Base automatically changed from hayt/databricks-oauth-cache-hardening to main August 11, 2026 13:58
@wpfleger96
wpfleger96 force-pushed the duncan/databricks-auth-coordinator branch from 90a4f0b to c581d2e Compare August 11, 2026 14:09
wpfleger96 added a commit that referenced this pull request Aug 12, 2026
…5607)

When a user's agent runtime is `buzz-agent` with no cached Databricks
OAuth token, the desktop app's passive model-discovery surfaces were
forbidden from launching interactive auth. Discovery failed silently, so
the model dropdown showed only built-in fallback models behind a vague
"Could not load live models for `databricks_v2`" note (reported
internally by Nick and Jose).

## What changed

Both discovery surfaces — the passive draft-form discovery and the
explicit saved-model picker — now launch the browser OAuth flow,
matching goose's behavior. The only behavioral difference between them
is cooldown handling:

- **Passive draft discovery** fires on every form-state change, so a
failed, cancelled, or timed-out sign-in records a per-host cooldown (5
min) that suppresses re-popping the browser on the next keystroke. While
the cooldown is active it returns the "sign-in required" guidance
instead of relaunching.
- **The explicit model picker** is a deliberate user action, so it
always launches and clears any stale cooldown first.

Safety rails:

- A 150s hard timeout (`AUTH_FLOW_TIMEOUT`) bounds the whole interactive
flow so an abandoned SSO tab fails discovery cleanly rather than wedging
the dropdown. Success clears the cooldown; failure and timeout both
record it.
- `AuthCooldown` recovers from a poisoned lock rather than wedging every
future sign-in on one panic.

The frontend maps the terminal Databricks sign-in states to typed,
actionable copy in `formatModelDiscoveryErrorStatus`: "sign-in required"
is a muted note pointing at the picker and `buzz-agent auth databricks`;
a failed or timed-out sign-in is a warning pointing at the explicit
retry. Other Databricks failures fall through to the existing generic
notice.

## Scope

Changes are confined to Databricks discovery and its frontend status
formatter — no `agent_models.rs` call sites are touched. The
interactive-auth helper takes an injected timeout so the
timeout/cooldown policy is unit-testable without a live browser.

## Deferred

Cooldown keys use the raw trimmed `DATABRICKS_HOST`, while the catalog
and OAuth cache normalize trailing slashes
(`crates/buzz-agent/src/catalog.rs:96`,
`crates/buzz-agent/src/llm.rs:2046`). So `https://workspace/` and
`https://workspace` share credentials but get separate cooldown entries
— an equivalent-spelling change to the host field mid-cooldown can
re-pop passive OAuth once within the 5-minute window. Self-limiting (one
extra browser launch, never auth corruption). Follow-up: a
`trim_end_matches('/')` on the cooldown key plus an equivalent-host
test, picked up with the coordinator migration if
[#5545](#5545) ever merges.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
shelman09 added a commit to Namleh-Studios/buzz that referenced this pull request Aug 13, 2026
* fix(buzz-agent): harden Databricks OAuth token cache and callback (block#5534)

Hardens the Databricks PKCE OAuth code in
`crates/buzz-agent/src/auth.rs`. Two fixes.

## Token cache is owner-only across its whole lifecycle, and race-safe

The PKCE cache holds both the access and refresh tokens, but `save()`
wrote it with a bare `fs::write` + `fs::rename`. Under a `022` umask the
file landed world-readable, and the fixed `*.json.tmp` temp name races
across concurrent savers sharing `$HOME` — one writer's `rename` can
fail on another's half-written temp.

**On write**, `write_private_cache()` creates a temp file with
owner-only permissions from the moment it exists — mode `0o600` on Unix
via `OpenOptions::mode` — writes and fsyncs it, then renames over the
destination. The rename swaps the inode wholesale, so a pre-existing
cache file with loose permissions is *replaced* by the new private inode
rather than inheriting its mode. `unique_suffix()` (getrandom, timestamp
fallback) gives each write a distinct temp name, and a drop guard
removes the temp on any failure path.

**On load**, owner-only is enforced as a cache lifecycle invariant, not
just a write-path property. A world-readable cache left by an older
buzz-agent was previously read straight into memory and returned on the
fresh cache-hit path without ever invoking `save()`, so a token file
with no advertised expiry could stay exposed indefinitely.
`read_cache()` now funnels every load — initial and cross-process
re-reads — through `read_private_cache()`, which on Unix opens with
`O_NOFOLLOW` (kernel-level symlink refusal, no stat/open TOCTOU),
requires a regular file, and `fchmod`s the pinned handle to `0o600` when
any group/other bit is set. A cache that cannot be secured is treated as
absent, so callers fail closed to a fresh flow rather than trusting an
exposed file.

## OAuth callback no longer reflects untrusted input

The localhost callback embedded the untrusted `error` query param
straight into the HTML response — an XSS sink on the redirect page — and
routed that same raw value into the error string that reaches the logs.

`callback_outcome()` is now a pure function returning `(result,
static_page)`: the browser always sees a fixed literal page that embeds
no request parameter, and failure detail travels only through the result
channel. `sanitize_callback_detail()` strips control characters (CR/LF
log-line injection) and caps length before that detail enters the error
string bound for the logs.

## Deferred: Windows owner-only ACLs

Windows owner-only protection is out of scope for this change. The
goose-parity route (`CreateFileW` with an owner-only SDDL
`D:P(A;;FA;;;OW)`) requires `unsafe` FFI, which this crate's
`#![forbid(unsafe_code)]` prohibits; reconciling that conflict is a
separate decision. Both platform seams — `create_private_temp_file`
(write) and `read_private_cache` (load) — have a `#[cfg(not(unix))]`
branch that relies on the default per-user ACLs and is the drop-in point
if Windows protection is added later. No new dependency and no `unsafe`
are introduced here.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
(cherry picked from commit 5e4d0fe)

* fix(desktop): launch Databricks OAuth from passive model discovery (block#5607)

When a user's agent runtime is `buzz-agent` with no cached Databricks
OAuth token, the desktop app's passive model-discovery surfaces were
forbidden from launching interactive auth. Discovery failed silently, so
the model dropdown showed only built-in fallback models behind a vague
"Could not load live models for `databricks_v2`" note (reported
internally by Nick and Jose).

## What changed

Both discovery surfaces — the passive draft-form discovery and the
explicit saved-model picker — now launch the browser OAuth flow,
matching goose's behavior. The only behavioral difference between them
is cooldown handling:

- **Passive draft discovery** fires on every form-state change, so a
failed, cancelled, or timed-out sign-in records a per-host cooldown (5
min) that suppresses re-popping the browser on the next keystroke. While
the cooldown is active it returns the "sign-in required" guidance
instead of relaunching.
- **The explicit model picker** is a deliberate user action, so it
always launches and clears any stale cooldown first.

Safety rails:

- A 150s hard timeout (`AUTH_FLOW_TIMEOUT`) bounds the whole interactive
flow so an abandoned SSO tab fails discovery cleanly rather than wedging
the dropdown. Success clears the cooldown; failure and timeout both
record it.
- `AuthCooldown` recovers from a poisoned lock rather than wedging every
future sign-in on one panic.

The frontend maps the terminal Databricks sign-in states to typed,
actionable copy in `formatModelDiscoveryErrorStatus`: "sign-in required"
is a muted note pointing at the picker and `buzz-agent auth databricks`;
a failed or timed-out sign-in is a warning pointing at the explicit
retry. Other Databricks failures fall through to the existing generic
notice.

## Scope

Changes are confined to Databricks discovery and its frontend status
formatter — no `agent_models.rs` call sites are touched. The
interactive-auth helper takes an injected timeout so the
timeout/cooldown policy is unit-testable without a live browser.

## Deferred

Cooldown keys use the raw trimmed `DATABRICKS_HOST`, while the catalog
and OAuth cache normalize trailing slashes
(`crates/buzz-agent/src/catalog.rs:96`,
`crates/buzz-agent/src/llm.rs:2046`). So `https://workspace/` and
`https://workspace` share credentials but get separate cooldown entries
— an equivalent-spelling change to the host field mid-cooldown can
re-pop passive OAuth once within the 5-minute window. Self-limiting (one
extra browser launch, never auth corruption). Follow-up: a
`trim_end_matches('/')` on the cooldown key plus an equivalent-host
test, picked up with the coordinator migration if
[block#5545](block#5545) ever merges.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
(cherry picked from commit 1ff98fa)

* fix(relay): stop panicking the ingest worker on reactions to project events (block#5294)

A NIP-25 reaction whose target is a project root or project comment
(kind
1621 issue, 1618 PR, or a kind-1 comment on one) carries no h tag, so
channel_id is None on the reaction write path. The conformance-trace
emission asserted a channel was always present:

channel: channel_label(channel_id.expect("reaction path has channel")),

so the worker panicked at ingest.rs:2824. The row was inserted before
the
panic, so the client saw a failed request for a persisted event and
retried,
and the duplicate branch carried the same expect, head-of-line blocking
a
durable publish queue forever.

Mirror the message write's three-way split at the same seam:
(Some, true) -> WriteInsert, (Some, false) -> WriteDuplicate, (None, _)
-> WriteInsertGlobal. The conformance vocabulary already models
channel-less
writes; only the reaction path was missing it.

Closes block#4936

Signed-off-by: Taksh <takshkothari09@gmail.com>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
(cherry picked from commit 16b7ae7)

* Harden shared agent instruction review (block#4220)

## Summary

- render shared-agent instructions as literal text so Markdown cannot
conceal spoiler contents, link destinations, or image sources
- reject non-reviewable Unicode controls at every agent-definition
boundary while preserving legitimate rendered emoji sequences
- verify shared catalog event IDs and signatures before trusting
authorship, coordinates, pagination, or executable content
- preserve the exact system-prompt bytes between review and execution
instead of silently stripping or normalizing content

## Security rationale

Shared system prompts are executable configuration. Previously, catalog
prompts were projected through the chat Markdown renderer, which could
hide text, replace link destinations with benign labels, and turn image
syntax into remote loads. Zero-width and bidirectional controls could
also make reviewed text differ from what the agent executes.

This change establishes a review invariant: the prompt a user sees is
the prompt the agent executes. Definitions that cannot be reviewed
faithfully are rejected rather than rewritten. Catalog events must also
pass Nostr ID/signature verification before they can claim a publisher,
coordinate, or cursor.

## What changed

- catalog instructions render as exact literal text rather than rich
Markdown
- catalog relay events are verified on a fresh wire-shaped object before
paging, coordinate selection, attribution, or projection
- forged content, pubkeys, signatures, and invalid newer heads are
ignored and cannot shadow a valid signed definition
- TypeScript catalog parsing rejects unsafe remote definitions before
they reach the UI
- shared Rust validation covers persona create/update/import, inbound
relay sync, definition-less managed-agent sync, and catalog publication
paths
- definition-less managed agents now fail closed on local create, local
update, and publication before persistence or relay retention
- linked managed agents validate their local name while treating the
persona definition as authoritative; their inert record-level prompt is
not executed or published
- names reject layout controls; prompts retain ordinary newlines and
tabs
- legitimate emoji composition is supported, including contextual VS16,
ZWJ, skin-tone, family, flag, and keycap sequences
- detached selectors/joiners, bidirectional controls, tag characters,
zero-width concealment, and other default-ignorables remain rejected
- names are bounded to 128 characters and prompts to 64 KiB
- contributor guidance documents the byte-for-byte review requirement
for future sharing paths

Validation reports the offending code point and never silently removes
it.

## E2E recording

[buzz-shared-agent-security-e2e.webm](https://github.com/user-attachments/assets/44d6b75f-0877-490f-bda4-a716fae3f700)

The recording demonstrates:

- a safe definition remains visible
- a prompt containing zero-width `U+200B` is rejected
- a name containing bidi override `U+202E` is rejected
- the prompt is preserved exactly
- spoiler, link, and image syntax remains literal and does not render or
load

## Verification

Passed locally:

- `just test`: all 10 unit and Docker-backed integration stages
- desktop frontend unit suite: 4,295 tests
- persona catalog relay unit suite: 32 tests, including forged-event and
cursor-shadowing cases
- focused Rust definition-validation coverage: 3 local create/update
tests and 6 publication-filtered tests
- complete desktop Tauri library suite after rebase: 2,263 passed, 14
ignored, 0 failed
- desktop Tauri clippy with warnings denied and Rust formatting
- complete agent Playwright spec: 34 tests
- the exact formerly failing `inbox-edit` immediate-attachment smoke
test after rebase: 1 test
- focused shared-agent publish, literal-review, hidden-control,
signature, and cross-member import Playwright coverage
- desktop E2E production build and TypeScript typecheck
- changed-file formatting/lint and file-size ratchet
- pre-commit secret scan and DCO signoff

The branch was rebased onto current `main`, which includes the upstream
attachment-button label fix. Fresh post-rebase GitHub CI is green for
every required and selected check: Desktop Core, all four Desktop Smoke
E2E shards, both Desktop E2E Integration shards and their aggregate,
Desktop E2E Relay, Desktop Build (macOS), Windows Rust, Rust Lint, DCO,
security scanners, and Desktop Release Candidate. The previously failing
`Desktop Smoke E2E (3)` shard now passes.

The repository-wide desktop check also reports existing CSS
formatting/`!important` findings in `components.css` and `terminal.css`;
neither file is changed by this PR. GitHub's Desktop Core lint and
format stage passes on the rebased branch.

---------

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
(cherry picked from commit a96af89)

* fix: reject non-reviewable Unicode formatting

* fix: close upstream security review gaps

* fix: authorize inbound agent sync events

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Taksh <takshkothari09@gmail.com>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Taksh Kothari <takshkothari09@gmail.com>
Co-authored-by: Alex Rosenzweig <64241648+shellz-n-stuff@users.noreply.github.com>
bhargavms pushed a commit to EWA-Services/buzz that referenced this pull request Aug 18, 2026
…lock#5607)

When a user's agent runtime is `buzz-agent` with no cached Databricks
OAuth token, the desktop app's passive model-discovery surfaces were
forbidden from launching interactive auth. Discovery failed silently, so
the model dropdown showed only built-in fallback models behind a vague
"Could not load live models for `databricks_v2`" note (reported
internally by Nick and Jose).

## What changed

Both discovery surfaces — the passive draft-form discovery and the
explicit saved-model picker — now launch the browser OAuth flow,
matching goose's behavior. The only behavioral difference between them
is cooldown handling:

- **Passive draft discovery** fires on every form-state change, so a
failed, cancelled, or timed-out sign-in records a per-host cooldown (5
min) that suppresses re-popping the browser on the next keystroke. While
the cooldown is active it returns the "sign-in required" guidance
instead of relaunching.
- **The explicit model picker** is a deliberate user action, so it
always launches and clears any stale cooldown first.

Safety rails:

- A 150s hard timeout (`AUTH_FLOW_TIMEOUT`) bounds the whole interactive
flow so an abandoned SSO tab fails discovery cleanly rather than wedging
the dropdown. Success clears the cooldown; failure and timeout both
record it.
- `AuthCooldown` recovers from a poisoned lock rather than wedging every
future sign-in on one panic.

The frontend maps the terminal Databricks sign-in states to typed,
actionable copy in `formatModelDiscoveryErrorStatus`: "sign-in required"
is a muted note pointing at the picker and `buzz-agent auth databricks`;
a failed or timed-out sign-in is a warning pointing at the explicit
retry. Other Databricks failures fall through to the existing generic
notice.

## Scope

Changes are confined to Databricks discovery and its frontend status
formatter — no `agent_models.rs` call sites are touched. The
interactive-auth helper takes an injected timeout so the
timeout/cooldown policy is unit-testable without a live browser.

## Deferred

Cooldown keys use the raw trimmed `DATABRICKS_HOST`, while the catalog
and OAuth cache normalize trailing slashes
(`crates/buzz-agent/src/catalog.rs:96`,
`crates/buzz-agent/src/llm.rs:2046`). So `https://workspace/` and
`https://workspace` share credentials but get separate cooldown entries
— an equivalent-spelling change to the host field mid-cooldown can
re-pop passive OAuth once within the 5-minute window. Self-limiting (one
extra browser launch, never auth corruption). Follow-up: a
`trim_end_matches('/')` on the cooldown key plus an equivalent-host
test, picked up with the coordinator migration if
[block#5545](block#5545) ever merges.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: bhargavms <bhargav.m@ewa-services.com>
BradGroux pushed a commit to BradGroux/buzz that referenced this pull request Aug 23, 2026
…lock#5607)

When a user's agent runtime is `buzz-agent` with no cached Databricks
OAuth token, the desktop app's passive model-discovery surfaces were
forbidden from launching interactive auth. Discovery failed silently, so
the model dropdown showed only built-in fallback models behind a vague
"Could not load live models for `databricks_v2`" note (reported
internally by Nick and Jose).

## What changed

Both discovery surfaces — the passive draft-form discovery and the
explicit saved-model picker — now launch the browser OAuth flow,
matching goose's behavior. The only behavioral difference between them
is cooldown handling:

- **Passive draft discovery** fires on every form-state change, so a
failed, cancelled, or timed-out sign-in records a per-host cooldown (5
min) that suppresses re-popping the browser on the next keystroke. While
the cooldown is active it returns the "sign-in required" guidance
instead of relaunching.
- **The explicit model picker** is a deliberate user action, so it
always launches and clears any stale cooldown first.

Safety rails:

- A 150s hard timeout (`AUTH_FLOW_TIMEOUT`) bounds the whole interactive
flow so an abandoned SSO tab fails discovery cleanly rather than wedging
the dropdown. Success clears the cooldown; failure and timeout both
record it.
- `AuthCooldown` recovers from a poisoned lock rather than wedging every
future sign-in on one panic.

The frontend maps the terminal Databricks sign-in states to typed,
actionable copy in `formatModelDiscoveryErrorStatus`: "sign-in required"
is a muted note pointing at the picker and `buzz-agent auth databricks`;
a failed or timed-out sign-in is a warning pointing at the explicit
retry. Other Databricks failures fall through to the existing generic
notice.

## Scope

Changes are confined to Databricks discovery and its frontend status
formatter — no `agent_models.rs` call sites are touched. The
interactive-auth helper takes an injected timeout so the
timeout/cooldown policy is unit-testable without a live browser.

## Deferred

Cooldown keys use the raw trimmed `DATABRICKS_HOST`, while the catalog
and OAuth cache normalize trailing slashes
(`crates/buzz-agent/src/catalog.rs:96`,
`crates/buzz-agent/src/llm.rs:2046`). So `https://workspace/` and
`https://workspace` share credentials but get separate cooldown entries
— an equivalent-spelling change to the host field mid-cooldown can
re-pop passive OAuth once within the 5-minute window. Self-limiting (one
extra browser launch, never auth corruption). Follow-up: a
`trim_end_matches('/')` on the cooldown key plus an equivalent-host
test, picked up with the coordinator migration if
[block#5545](block#5545) ever merges.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes required at e58204b88601af14460f17940d2bc3d008f78b49:

  1. [P1] Preserve UserInitiated retry semantics across in-process coalescing. Auto and UserInitiated use the same (lock_path, may_open_browser = true) slot, and a joiner returns the leader’s result without applying its own intent (auth.rs lines 675–699). If an explicit user action joins an Auto leader that returns an active cooldown at lines 802–807, the user receives the prior Denied/TimedOut result instead of clearing the cooldown and opening sign-in as UserInitiated promises. Key the slot by the relevant intent policy, or make a user-initiated joiner retry when it inherited an automatic cooldown result. Add the mixed-intent race test; the current tests cover same-intent coalescing and sequential cooldown bypass only.

  2. [P1] Do not accept an expired replacement after a 401. In cached_hit, rejected = Some(t) considers any token whose bytes differ from t usable (lines 603–623), without checking is_expired. An expired in-memory or on-disk token B can therefore be returned as the presumed sibling replacement for rejected token A, skipping refresh for refresh_now and every public rejected-token acquisition. A replacement must differ from the rejected token and still be unexpired.

  3. [P1] Separate authorization-code rejection from exchange infrastructure failure. The code-exchange path maps every non-success status, including 429 and 5xx, plus malformed 2xx JSON/token payloads, to ExchangeFailed (lines 1544–1556). That variant is terminal LlmAuth and cooldown-worthy, so a transient provider outage after callback is reported as a rejected code and suppresses automatic auth for five minutes. Classify transport/429/5xx and malformed success responses as NetworkUnavailable; reserve ExchangeFailed for an OAuth response that establishes the authorization grant was rejected. Mirror the refresh classifier’s status/body coverage in exchange tests.

I reviewed the public entry-point × intent × cache/refresh/browser/cooldown matrix, same-process and cross-process coordination, cancellation/deadline behavior, OAuth classification, and platform paths using pinned GitHub source only. I did not execute PR code.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes required at 4513d2385fcd4e09109c99756f83c309ea3346b9:

  1. [P1] Include rejected-token identity in same-process single-flight. cached_hit correctly makes the caller's rejected bearer part of cache validity, but InflightKey contains only (lock_path, AuthIntent) and a joiner returns the leader's result without applying its own rejected value (auth.rs lines 656–707 and 1061–1132). This breaks the public 401/403 retry contract when concurrent requests reject different bearer generations. For example, while refresh_now(A) is refreshing, refresh_now(B) joins the same Headless slot; if the leader publishes B, the second request receives the exact token it just reported rejected and retries its provider call with known-bad credentials. The inverse can also inherit a terminal refresh/network result even though that caller's cached replacement was already valid. Include a non-secret digest of rejected in the slot key, or revalidate a joined result against the waiter's rejected value and rerun its own cache/acquisition policy. Add a deterministic concurrent different-rejected-values test proving a waiter never receives its rejected bytes.

The three blockers from the previous head are fixed: full AuthIntent now separates mixed cooldown policy, rejected replacements must differ and be unexpired, and exchange infrastructure failures remain NetworkUnavailable. I reviewed public entry points, cache/refresh/browser/cooldown transitions, same/cross-process coordination, cancellation/deadlines, OAuth classification, platform cache paths, and the test matrix using pinned GitHub source only. Exact-head CI is otherwise green; I did not execute PR code.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

[P1] Do not persist a bearer after proving it equals the caller’s rejected token.

The new equality guard runs in acquire_leader only after acquire_locked returns (crates/buzz-agent/src/auth.rs:786-810). Both successful live-token paths call finish first (:845, :906), and finish atomically saves the token to disk and memory before returning it (:921-930). If a provider reissues the exact bearer just rejected with 401/403, this call correctly returns RefreshRejected or NetworkUnavailable, but leaves that known-bad bearer cached as fresh. The next ordinary bearer() calls acquire(Headless, None) and cached_hit accepts it because no rejected identity is supplied (:605-623, :943-946). The runtime can therefore restore and send credentials this flow already proved unusable; a fresh process does the same from disk.

Validate the candidate before committing it, or explicitly invalidate/remove the persisted candidate on equality without destroying a still-valid concurrent replacement. Add lifecycle regressions for both sticky refresh and sticky browser exchange: after the rejected-aware acquisition fails, a following bearer() and a newly constructed token source must not return the rejected bytes.

The prior different-rejected-joiner race itself is fixed: joiners revalidate the published result and perform a bounded rerun. This remaining blocker is the persistence boundary after that rerun.

Read-only exact-head source review; no PR code was checked out or executed.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested at exact head 5cdc56bae2ece14fdacce7f8c0de064eaab5ffa6:

  1. P1: rejected-token reissuance still leaves the original known-bad cache entry live. The new finish guard correctly refuses to persist a newly issued token equal to rejected, but it does not invalidate the existing cached token. In the real lifecycle, bearer() returns cached unexpired A, the provider rejects A, and refresh_now(A) receives sticky A from refresh. finish returns RefreshRejected before saving, leaving the original unexpired A in memory and on disk. The next plain bearer(None), or a fresh process, accepts A again. The new poison tests seed expired-seed while passing a different rejected value (sticky-token / sticky-browser), so they cannot detect this path. Add regressions that seed the same unexpired A passed as rejected, then prove the failure cannot be followed by a cache hit for A; invalidate conditionally so a concurrently persisted distinct replacement is preserved.

  2. P1: the file lock serializes cross-process failures but does not single-flight them for UserInitiated or Headless. After process A fails and releases the lock, an already-waiting process B enters acquire_locked. A UserInitiated waiter clears A's just-written cooldown and opens a second browser; a Headless 401 waiter repeats the failed refresh. The process-local INFLIGHT slot cannot publish A's failure across processes. The current denial test only uses an Auto waiter, whose cooldown policy masks the gap; success shares through the cache. Preserve an attempt generation/outcome so callers already queued behind that generation can adopt failures while later explicit user retries still bypass cooldown, and add real two-process denial and failed-refresh regressions.

  3. P1 security: Windows persistent OAuth cache files are not owner-only. The non-Unix read path accepts any cache ACL unchanged, and the non-Unix temp-file path creates cache/cooldown files with inherited default ACLs. These files hold access and refresh tokens; the implementation comments explicitly defer the owner-only DACL. On a permissive parent or pre-existing broad ACL, other local principals can read persisted credentials. Create and validate/repair an owner-only Windows DACL, or disable persistent OAuth caching there until that guarantee exists. The Unix path's O_NOFOLLOW, fd-based mode repair, 0600 creation, and atomic rename do not cover Windows.

The previous exact-head blocker about persisting the newly returned rejected token before validation is partly fixed by moving equality validation ahead of save; item 1 is the remaining full-lifecycle gap. CI's normal build/test/security gates are green; the Codex security-review job was cancelled. Review was read-only; no PR code was executed.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested on exact head 8af6afb3c8f9164dfd6cc765d5416f70c9d174b6.

  1. P1: Do not share rejected-token-relative failures across different rejected bearers. The in-process slot key contains only (lock_path, intent) (crates/buzz-agent/src/auth.rs:780-840). If Headless caller A rejects X and refresh reissues X, finish returns RefreshRejected; concurrent caller B rejecting Y inherits that error, although the successful refresh proved the grant remains live and X does not equal B’s rejected value. The cross-process attempt record has the same problem because it stores intent/result but not rejected identity (:958-963, 1253-1262). Existing tests cover the inverse shared-success collision where the leader returns the joiner’s rejected bytes, not a leader-relative failure. Key rejection-relative outcomes by a non-secret rejected-token digest, or rerun joiners when the shared result may not satisfy their rejected identity; cover both in-process and cross-process transitions.

  2. P1: An adopter must not create a new attempt generation. When a queued process adopts a predecessor’s terminal failure, acquire_locked immediately calls write_attempt again (:958-963). That records work which never ran. A caller arriving after the real attempt can snapshot generation 1, queue behind the adopter that advances it to 2, and incorrectly adopt the old failure; overlapping arrivals can relay it indefinitely. Return the adopted error without advancing the sidecar, and add a three-process regression where C arrives after A’s failure but while B is adopting it.

  3. P1: The non-Unix memory-only policy breaks the cross-process success contract and its newly required Windows CI lane. persist is a no-op and read_private_cache refuses/deletes disk tokens on non-Unix (:512-527, 1578-1595), so a Windows waiter serialized by LockFileEx cannot consume the winner’s bearer and performs another refresh/browser flow. Exact-head Windows CI proves this: test_crossprocess_two_coordinators_race_to_one_grant_and_cache gets browser-token-2 versus browser-token-1, and the added auth-coordinator step fails 20 of 33 tests. Either provide secure Windows success handoff, or explicitly scope the product contract and Windows tests to invariants that remain true without persistence. Do not leave a required platform lane structurally red.

  4. P2: Rejected-token neutralization is not fail-closed for a readable stale file. expire_rejected rewrites best-effort and then removes best-effort (:544-570). If both fail while the original token file remains readable, such as an owner-writable cache file in a now read-only directory where temp creation and unlink fail, a fresh process still serves the locally-unexpired bearer already proven dead. The regression substitutes a directory at the cache path, which the read path rejects independently, so it does not cover this case. Add a fallback that durably expires/removes the readable regular file or make subsequent loads reject it, with the actual double-failure regression.

The OAuth status classification, Unix no-follow/0600/atomic cache path, bounded lock/browser lifetimes, public intent routing, and prior rejected-token persistence fixes otherwise look coherent. The unrelated Desktop smoke layout failure is not attributed to this PR. Review was read-only; PR code was not executed.

Duncan and others added 2 commits August 31, 2026 21:21
…h-coordinator

* origin/main:
  feat(desktop): add isolated named demo builds (#6407)
  fix(model-capabilities): humanize databricks goose model names (#7135)
  feat(db): add NIP-FI identity and final-admission schema foundation (#6994)
  feat(buzz-acp): give each channel thread its own agent session (#6732)
  docs: add review-proven failure-path & async-state rules to AGENTS.md (#7061)
  fix(desktop): back split thread headers (#7137)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The test must exercise reconciliation, not the fast-path cache. With Z in
state and Z != rejected, cached_hit returns Z before the joiner path is
reached. Fix: hold state during acquire so the fast-path try_lock misses,
forcing the joiner branch. The reconciliation try_lock also fails (state
still held), which is the correct behavior — another task holds state,
so adoption is skipped and Z is preserved.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…place synthetic tests

Replace try_lock with lock().await in both joiner state transitions:
- Success: reconcile B's state under the lock to guarantee the write
  completes before returning. try_lock's skip-on-contention could leave
  stale or empty state and recreate the P1 regression on the next plain
  bearer() call.
- Matching failure: expire B's matching rejected state under the lock
  for the same reason.

The joiner holds neither the INFLIGHT registry mutex nor the cross-process
file lock at this point, so awaiting state cannot deadlock. Fix the
unnecessary_map_or Clippy diagnostic: use is_none_or.

Remove the ~310-line joiner_reconciliation_tests module (private-seam
synthetic scaffolding). Replace with real public-API coordinator tests
that drive two independently constructed sources through actual leader/joiner
acquisition and verify subsequent reads:
- test_inprocess_joiner_reconciles_stale_state_after_shared_success (Unix):
  B holds locally-fresh-but-rejected X; after shared success Y, subsequent
  plain bearer() on B returns Y, not X. Mutation: bearer-only publication
  leaves B.state=unexpired-X; next read returns X.
- test_inprocess_joiner_neutralizes_rejected_on_matching_shared_failure (Unix):
  B holds X; after matching shared RefreshRejected, next bearer() cannot
  return X. Mutation: no expire_rejected on Err path leaves B.state=X.
- test_inprocess_joiner_populates_empty_state_no_second_acquisition (non-Unix):
  empty A and B join a browser flow; B's subsequent headless read returns Y
  and no second browser opens. Mutation: bearer-only leaves B.state=None;
  headless returns NoCredential (no disk fallback on non-Unix).

Add test_joiner_preserve_distinct_newer_credential to auth::tests: real
concurrent write pattern — Z is written to B's state while B is in
slot.wait(); after waking, reconciliation predicate correctly preserves Z.
Mutation: unconditional adoption overwrites Z with Y.

Also update test_joiner_shared_failure_recovers_disk_replacement_under_state_contention:
rename and remove the now-wrong held-mutex framing (holding state from
outside and calling lock().await in the same task would deadlock).

Fix MINOR marker-comment overclaims: both snapshot-marker comments now
state that the marker proves B captured generation 0 before A records
generation 1, not that it proves B is queued/waiting behind A.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Adds test_joiner_reconciliation_blocked_until_state_lock_released, a
focused regression that holds B's state mutex across slot publication,
proving the joining future cannot return before reconciliation completes.

The test exercises the real acquire() joiner path: B's fast-path
try_lock fails (mutex held), B sprints to slot.wait(), Y is published
while the mutex is still held, and B's state.lock().await suspends.
is_finished() asserts B has not returned. Releasing the mutex lets B
complete; the subsequent public acquire() returns Y from the in-memory
state, not stale X.

Mutation check (lock().await → try_lock()): try_lock fails while the
mutex is held, the adopt block is skipped, B returns immediately
(is_finished() == true fails the assertion), and the subsequent read
returns stale X — the exact P1 regression from the Carl review.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested: one P2 regression

Reviewed head 81216c80c55772bee34ccf5f848ee913bb6b7a82 against base 4a9de1a3a121285ef475d630b2b5764044c02cde. This is a convergence-focused re-review of acquisition coordination. The prior full-credential joiner-retention/neutralization finding and process-denial snapshot-barrier finding are addressed. The new failure cleanup introduces the following durable-state regression.

P2: Keep joiner failure cleanup from writing the cache outside the auth-file lock

Location: crates/buzz-agent/src/auth.rs:937-940.

The matching-failure joiner holds only its own state mutex, but calls expire_rejected, which also reads and rewrites the shared disk cache (lines 587-605). The shared auth-file lock has already been released by the leader; it does not fence this joiner. The equality check and subsequent atomic rename are not a compare-and-swap.

Source-derived Unix interleaving:

  1. A and B are independent same-key sources in one process, both recovering rejected X. A leads, expires disk X under the file lock, then fails. B receives the matching shared failure.
  2. B reads the expired disk X inside expire_rejected; it still matches X, so B prepares another write. A later explicit acquisition C, in another process, holds the auth-file lock and successfully persists Y with its new refresh token.
  3. B's unfenced rename commits after C's rename, replacing Y with its expired-X snapshot and old refresh token. The in-place/remove fallbacks are unfenced as well.

Impact: a successful sign-in loses its durable credential. Fresh sources miss Y, retry the old refresh token, and can fail headlessly or require another interactive sign-in when that old token is invalid. B's disk recovery check also misses the replacement it just overwrote. This is introduced by the new joiner helper call, not an inherited concern or Windows persistence follow-up.

Smallest safe repair: separate conditional in-memory invalidation from durable invalidation. Keep the joiner's local-state reconciliation, but ensure every credential-content write/removal remains owned by the existing cross-process lock; do not reuse the combined disk-mutating helper from a lockless joiner. For normal completed attempts the leader already handles disk invalidation. Do not assume every shared error proves that step ran: lock-acquisition failures return before it. Add a deterministic witness for failed A/joining B versus successful C that asserts C's full persisted credential survives and B cannot serve rejected X.

Closed findings and nonblocking coverage note

  • Full CachedToken publication, awaited conditional adoption, matching rejected-memory invalidation, and preservation of distinct usable Z address the previous P1. Headless, Auto, and UserInitiated remain separated; non-Unix same-process retention is covered without requiring disk persistence.
  • The worker snapshot marker is emitted after capturing the attempt generation and observed before releasing the predecessor. The earlier process-denial timing issue is closed. Unix disk-test gates and the Windows CI crash-release description are corrected.
  • Nonblocking: test_joiner_shared_failure_recovers_disk_replacement (auth.rs:2316-2364) now installs a usable disk replacement before acquisition without state contention. acquire returns it at its initial fast path, so the test passes without exercising shared-failure recovery. Arrange the replacement after the caller has actually joined, and make removal of that recovery branch fail the test. The new lock/nonoverwrite witnesses also rely on yield_now() as a scheduling barrier (auth.rs:2923-2939,3032-3052); use explicit polling to Pending or a boundary acknowledgment instead of claiming one yield guarantees the child reached the intended await.

Coverage/limits: checked the public acquisition entrypoints through cache/refresh/browser producers, leader publication, independent joiner state, subsequent reads, rejected-identity and intent boundaries, process generation adoption, persistence ownership, cancellation, and changed test/CI wiring. Read exact-base governing/product documents; integrated all assigned review lanes. Desktop wiring, runtime 401 integration, and Windows secure persistence/cross-process success handoff remain excluded by the agreed contract. Source-only: no checkout, build, tests, or PR code execution; the race above is established by source ordering, not a claimed runtime reproduction. CI success is not asserted.

- Introduce `expire_rejected_memory`: in-memory-only variant of
  `expire_rejected`, used by the matching-failure joiner to neutralize
  its own state without touching the shared disk cache. The combined
  disk-mutating helper is reserved for `acquire_locked`, which runs
  under the cross-process file lock.

- Replace the spawned-task / yield_now / is_finished joiner test with
  direct manual polling of a pinned `acquire()` future using
  `Waker::noop()`. Poll 1 structurally proves B reached `slot.wait()`;
  poll 2 while the state mutex is held proves the production
  `lock().await` parks (Pending) while the rejected `try_lock`
  mutation returns Ready immediately, failing the assertion.

- Convert `test_joiner_preserve_distinct_newer_credential` to the same
  direct-poll approach, removing the `yield_now` / `tokio::spawn`
  scheduling assumption.

- Rewrite `test_joiner_shared_failure_recovers_disk_replacement` to
  force the joiner path (hold state guard, poll 1 proves joiner reached
  `state.lock().await`), then install the disk replacement before
  releasing, ensuring the test exercises the joiner recovery branch
  rather than the initial fast-path `cached_hit`.

- Add `test_joiner_failure_does_not_write_disk`: seeds X on disk, runs B
  as a matching-failure joiner, and asserts the disk file is byte-for-byte
  unchanged. Mutation (expire_rejected_memory -> expire_rejected) reads
  the disk file, overwrites with `expires_at=0`, and fails the
  byte-equality assertion -- proving the unfenced write would overwrite
  any concurrent process C write.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Fixes clippy::empty_line_after_doc_comments (-D warnings) on the
expire_rejected / expire_rejected_memory doc block boundary at
auth.rs:559. No behavior change.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…est comment

The "both layers" Rustdoc was attached to expire_rejected_memory while
expire_rejected had no doc. Move it above expire_rejected where it
belongs; leave only the memory-only contract and LockTimeout limitation
on expire_rejected_memory.

In test_joiner_preserve_distinct_newer_credential, replace "real
concurrent write" with "intervening write" to accurately describe the
same-task Z installation via direct polling (no spawn involved).

No behavior change; comments only.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Context::from_waker takes &Waker; Waker::noop() already returns &'static Waker,
so passing &waker was a double-reference caught by clippy::needless_borrow.
Remove the redundant & at all four test sites.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Review clear: prior blocker resolved

Reviewed exact head e1c89528e7526edabe8cf506fe7bf1cadd3ce35a against base 4a9de1a3a121285ef475d630b2b5764044c02cde. No actionable blocker remains in this corrective review of the agreed acquisition-only contract.

  • Lockless cache overwrite repaired. The matching-failure joiner now awaits its local state and calls memory-only invalidation before a filtered disk read (auth.rs:978–985). That path cannot rewrite/truncate/remove credential contents. Combined durable invalidation remains in acquire_locked, reached under the auth-file guard (1029–1085); refresh/browser persistence retains the same ownership. The sole P2 blocker in my previous review is closed.
  • Regression witnesses now reach production boundaries. The revised tests directly poll real acquire futures: disk recovery happens after joining, disk contents remain unchanged on shared failure, reconciliation cannot complete while state is locked, and distinct usable Z survives shared success Y (2364–2555, 3066–3263). The disk-invariance test is a no-content-write witness, not a three-process race execution. Independent source audit agreed; no mutation tests were executed.
  • Established boundaries preserved. Headless/Auto/UserInitiated separation, full-credential sharing, rejected-identity checks, conditional joiner adoption, generation matching, and cooldown ownership remain intact. A pre-lock LockTimeout does not guarantee durable invalidation; the documented possibility of a later plain read re-adopting disk X is not certified away by this repair.

Scope: re-reviewed the auth.rs delta from the last published head 81216c80, including the subsequent documentation and four waker-borrow corrections, using prior whole-acquisition review evidence for unchanged paths. Read exact-base governing/product guidance and integrated the independent test lane. Desktop Phase 2 wiring, runtime 401 integration, and Windows secure persistence/cross-process success handoff remain deferred; non-Unix same-process credential retention remains in scope.

Validation is source-only: no checkout, build, test, live workflow, or PR-code execution. No current CI success is asserted. This is a clear COMMENTED review, not an approval.

@wpfleger96
wpfleger96 merged commit 5aed49b into main Sep 1, 2026
75 of 78 checks passed
@wpfleger96
wpfleger96 deleted the duncan/databricks-auth-coordinator branch September 1, 2026 19:59
johnmatthewtennant added a commit that referenced this pull request Sep 1, 2026
* origin/main:
  feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
  fix(desktop): preserve keyring identity during recovery (#7203)
  feat(mobile): prepare `buzz-push-gateway` for deployment (#7158)
  ci: relax file-size ceilings by surface (#6485)
  fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)

Signed-off-by: John Tennant <jtennant@squareup.com>

# Conflicts:
#	crates/buzz-db/src/runtime/migration.rs
wpfleger96 pushed a commit that referenced this pull request Sep 1, 2026
…c-agent-commit-identity

* origin/main:
  feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
  fix(desktop): preserve keyring identity during recovery (#7203)
  feat(mobile): prepare `buzz-push-gateway` for deployment (#7158)
  ci: relax file-size ceilings by surface (#6485)
  fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>

# Conflicts:
#	.github/workflows/ci.yml
wpfleger96 pushed a commit that referenced this pull request Sep 1, 2026
* origin/main:
  feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
  fix(desktop): preserve keyring identity during recovery (#7203)
  feat(mobile): prepare `buzz-push-gateway` for deployment (#7158)
  ci: relax file-size ceilings by surface (#6485)
  fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)
  chore(ci): lower Codex security review effort (#7179)
  fix(dev-mcp): extend shell timeout cap to 20 minutes and align outer budgets (#7185)
  fix(dev): keep the canonical profile when launching from desktop/ (#7143)
  feat(buzz-auth): add production NIP-FI federated assertion runtime (#7109)
  Hide download action on voice notes (#7182)
  ci: run PostgreSQL tests in isolated lane (#6730)
  Add voice notes to desktop messages (#6978)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
delkc added a commit that referenced this pull request Sep 1, 2026
…rding-v3

* origin/main:
  feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
  fix(desktop): preserve keyring identity during recovery (#7203)
  feat(mobile): prepare `buzz-push-gateway` for deployment (#7158)
  ci: relax file-size ceilings by surface (#6485)
  fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)
  chore(ci): lower Codex security review effort (#7179)
  fix(dev-mcp): extend shell timeout cap to 20 minutes and align outer budgets (#7185)
  fix(dev): keep the canonical profile when launching from desktop/ (#7143)
  feat(buzz-auth): add production NIP-FI federated assertion runtime (#7109)
  Hide download action on voice notes (#7182)
  ci: run PostgreSQL tests in isolated lane (#6730)
  Add voice notes to desktop messages (#6978)

Signed-off-by: Clay Delk <clay.delk@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Sep 1, 2026
…agent-edit

* origin/main:
  feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
  fix(desktop): preserve keyring identity during recovery (#7203)
  feat(mobile): prepare `buzz-push-gateway` for deployment (#7158)
  ci: relax file-size ceilings by surface (#6485)
  fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)
  chore(ci): lower Codex security review effort (#7179)
  fix(dev-mcp): extend shell timeout cap to 20 minutes and align outer budgets (#7185)
  fix(dev): keep the canonical profile when launching from desktop/ (#7143)
  feat(buzz-auth): add production NIP-FI federated assertion runtime (#7109)
  Hide download action on voice notes (#7182)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>

# Conflicts:
#	desktop/src/features/agents/AGENTS.md
wpfleger96 pushed a commit that referenced this pull request Sep 1, 2026
…n-surface

* origin/main:
  feat(desktop): add Pi agent preset (#7208)
  feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
  fix(desktop): preserve keyring identity during recovery (#7203)
  feat(mobile): prepare `buzz-push-gateway` for deployment (#7158)
  ci: relax file-size ceilings by surface (#6485)
  fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)
  chore(ci): lower Codex security review effort (#7179)
  fix(dev-mcp): extend shell timeout cap to 20 minutes and align outer budgets (#7185)
  fix(dev): keep the canonical profile when launching from desktop/ (#7143)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Sep 2, 2026
…-history

* origin/main:
  fix(acp): replace real user name in base prompt mention example (#7250)
  ci: split CI into reusable workflows (#7168)
  fix(desktop): retain automatic mentions only in threads (#7144)
  feat: add databricks fable 5.1 model capabilities (#7213)
  docs(nip-fi): rewrite NIP-FI as stateless OSS Buzz spec v2 (#7214)
  feat(relay): add detailed readiness metrics (#7149)
  feat(desktop): add Pi agent preset (#7208)
  feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
  fix(desktop): preserve keyring identity during recovery (#7203)
  feat(mobile): prepare `buzz-push-gateway` for deployment (#7158)
  ci: relax file-size ceilings by surface (#6485)
  fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)
  chore(ci): lower Codex security review effort (#7179)
  fix(dev-mcp): extend shell timeout cap to 20 minutes and align outer budgets (#7185)
  fix(dev): keep the canonical profile when launching from desktop/ (#7143)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants