Skip to content

fix(buzz): harden dynamic channel lifecycle - #1

Closed
riyaazd29 wants to merge 2 commits into
Peakhunter:fix/buzz-dynamic-channelsfrom
riyaazd29:fix/buzz-dynamic-channel-hardening-pr74823
Closed

riyaazd29 wants to merge 2 commits into
Peakhunter:fix/buzz-dynamic-channelsfrom
riyaazd29:fix/buzz-dynamic-channel-hardening-pr74823

Conversation

@riyaazd29

Copy link
Copy Markdown

What does this PR do?

Adds lifecycle hardening on top of the exact head of NousResearch#74823 (8570eae). It keeps Reinhold's dynamic-channel implementation and contributor credit intact while covering races found during live canary testing.

Changes made

  • Reject malformed successful channels list --member output instead of treating it as an empty membership snapshot.
  • Track both membership-add and membership-remove events without rewinding the membership cursor on stale relay events.
  • Seed pre-join history before opening a live subscription, set a subscription floor, and avoid replaying old messages.
  • Reconcile joined channels on WebSocket reconnect, prune channels that were left, and close stale subscriptions.
  • Periodically rediscover joined channels in polling fallback mode.
  • Keep forum event kinds out of stream-message dispatch.
  • Preserve explicit channel allowlists, DM state, mention gating, and deduplication.

How to test

scripts/run_tests.sh tests/gateway/test_buzz_adapter.py tests/gateway/test_buzz_websocket.py -q

Results on macOS:

Live proof:

  • Deployed on two independent macOS Hermes gateways.
  • On each gateway, created a channel after startup, added the running identity, then immediately sent an addressed message.
  • Both gateways subscribed dynamically and replied from the same post-deployment PID without another restart.

Scope audit

  • Adapter-local change plus behavioral tests only.
  • No core files, dependencies, configuration, credentials, or public API changes.
  • Live canaries covered WebSocket transport; polling fallback is covered by tests.

AI assistance

The implementation and tests were developed with AI assistance, then reviewed through focused/full automated suites and two live gateway canaries.

@riyaazd29

Copy link
Copy Markdown
Author

Independent lifecycle review found three blocking race/replay paths after publication. Please do not merge this revision yet. I am adding deterministic regressions and will push a corrected commit with fresh verification.

reinhold-ph pushed a commit that referenced this pull request Aug 3, 2026
…e-review #1)

revoke_commit_admission() used to invoke the holder-qualified lease
release unconditionally — including while an admitted commit was still
mutating SessionDB — letting a second compressor acquire the durable
lock mid-commit and interleave with the first commit's writes.

The admission_revoked flag store stays lock-free, but the lease-release
decision now coordinates with the fence lock:
- revoke acquires the fence lock non-blocking; on success no commit can
  be in flight (an admitted commit retains the lock until finish_commit)
  and the release runs immediately, still under the lock so a racing
  begin_commit cannot slip between the check and the release.
- on failure the release is deferred: finish_commit() re-checks
  _admission_revoked and performs it AFTER the commit completes (prompt
  even if the worker thread is later parked), and the begin_commit
  refusal path does the same for a revoke that lost the race to a
  transient lock-setup/cancel boundary. All paths are idempotent with
  the worker's own outer cleanup (DB release is holder-qualified).

Invariant encoded + tested: no second compressor can acquire the durable
lock while an admitted commit is still mutating; after a post-revoke
commit finishes the lease is released promptly. Both regressions
(revoke-during-commit deferral, revoke-before-commit immediate release +
refused begin_commit) are sabotage-verified.
reinhold-ph pushed a commit that referenced this pull request Aug 11, 2026
…rst run

The first-run provider picker showed Fireworks AI alongside Nous Portal
before the user opened the 'Other providers' disclosure. Only Nous Portal
should be visible up front; Fireworks now lives inside the expanded list
but keeps its #1 position there (Nous -> Fireworks ordering preserved).
@reinhold-ph
reinhold-ph force-pushed the fix/buzz-dynamic-channels branch from e2e3797 to b16e7a5 Compare August 14, 2026 15:42
@cmyk cmyk closed this Sep 5, 2026
reinhold-ph pushed a commit that referenced this pull request Sep 16, 2026
reinhold-ph pushed a commit that referenced this pull request Sep 16, 2026
…, with or without the multiplex flag

Two authority gaps in served_profile_child_env (NousResearch#111617 review, andrexibiza P1 #1/#2,
kvnloo finding 1):

- The base was hermes_subprocess_env(inherit_credentials=True) = the launch environ's
  provider credentials; strip_launch_profile_env only knows names with .env/source
  provenance, so a key systemd/Compose/the shell injected into the launch process
  survived into profile B's child whenever B did not define the same name. Now a ROUTED
  target scrubs every Tier-1/Tier-2 credential from the base regardless of provenance
  before B's own scope is overlaid (the child boundary gets get_secret's contract: a
  scoped miss is no credential, never ambient fallback). The launch profile's own child
  keeps its env. bot_relay's base=os.environ goes through the same scrub.
- strip_launch_profile_env / the scrub keyed on is_multiplex_active(); the Desktop and
  dashboard backends serve ?profile=B by installing the HERMES_HOME override without
  that flag, so B's slash worker / helper children kept A's .env and settings. The
  authority test is now "is the target a routed home" (target != process home).
- _build_browser_env resolved the passthrough keys via get_secret, which falls through
  to os.environ on a scoped miss while multiplexing is inactive: a routed B with no
  Firecrawl key got A's. Under serves_routed_profile() the bound scope is the only source.
- served_profile_child_env(inherit_credentials=True) with no target and no scope bound
  under multiplex minted with the launch credentials (key_cmd TTL refresh on a worker
  thread); it now raises UnscopedSecretError like get_secret.

tests/tui_gateway/test_served_profile_child_env_authority.py: ambient-only A key + B
missing it (mux on), flag-off routed B (helper child + browser), real child observation.
3/3 red on base.
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.

3 participants