feat(desktop): publish signed launch bundles for buzz-waker - #28
Merged
Merged
Conversation
sign_launch_bundle existed and was tested but had zero production callers, so a real wake attempt still ended in DeployFailed because no bundle ever reached the daemon. This wires the desktop side up: - ManagedAgentRecord gains an opt-in `waker_enabled` flag (default off) — a bundle carries the agent's private_key_nsec, so publishing it by default for every Provider-backend agent is the wrong default for a security-sensitive value. - A new set_managed_agent_waker_enabled command flips it on only for a Provider backend; that flip is the enrolment moment. - retain_managed_agent_pending (agents.rs) — the existing chokepoint that already fires on every real config change, never on runtime- only churn (G3) — now also issues and retains a fresh signed kind:30180 bundle when the flag is set. It's retained through the same pending_sync row the existing 30s flush loop already drains for persona/team/managed-agent writers, reusing that retry path instead of adding a second one. - The new logic lives in agents_waker.rs, split out because agents.rs is already past the file-size ratchet and may not grow. Out of scope: the generation-nonce substitution contract (daemon-side execution logic, tracked separately) and a UI toggle for the new flag. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Junchao Yan <yjc801@gmail.com>
Addresses Alex's PR #28 round-1 findings: - [P1] Disabling waker, or migrating an enabled agent off a Provider backend, now enqueues a NIP-09 tombstone for the retained kind:30180 launch bundle instead of only stopping future issuance. A daemon that reconnects or restarts after this can no longer recover the previous authorization from the relay. Does not reach a daemon that already holds the bundle in its live BundleState -- that needs daemon-side revocation delivery, which PLANS/BUZZ_WAKER_DESIGN.md SS3 names as a still-unbuilt implementation gate, documented as a known follow-up. - [P2] set_managed_agent_waker_enabled's false->true (enrolment) transition now propagates a bundle-issuance failure as a command error and rolls waker_enabled back to false, instead of silently swallowing it and reporting success with no bundle to show for it. Signed-off-by: Junchao Yan <yjc801@gmail.com>
Addresses Alex's round-2 finding on PR #28: the prior NIP-09 tombstone only closed relay recovery for a daemon that reconnects after the disable transition. An already-connected daemon never subscribes to kind:5 and kept its admitted bundle in BundleState, usable for up to the 90-day validity window -- exactly the duplicate-harness state the backend migration guard exists to prevent. Replace the tombstone with a `revoked` flag on LaunchBundleBody, published at the *same* kind:30180 NIP-33 coordinate a config-change reissue already uses. Because the daemon's bundle tap holds that coordinate's filter open live for real-time reissues, a revocation reaches an already-connected daemon the same way a reissue does -- no new relay change, no new event kind. - crates/buzz-waker/src/bundle.rs: `revoked: bool` field, inside the signature like every other clamp. - crates/buzz-waker/src/bundle_feed.rs: decrypt_verify_and_admit now returns a BundleOutcome (Delivered/Revoked); a revoked delivery raises FloorStore's revocation floor and BundleState::clear() drops the cached bundle. - desktop/src-tauri/src/commands/agents_waker.rs: tombstone_waker_bundle_pending replaced by revoke_waker_bundle_pending, which republishes a revoked bundle at the same coordinate instead of a kind:5 delete. Signed-off-by: Junchao Yan <yjc801@gmail.com>
Addresses Alex's round-3 (escalated) findings on PR #28: - revoke_waker_bundle_pending now returns Result instead of logging and swallowing its own failure. Both callers (waker_enabled(false) and a Provider->Local backend migration) now call it BEFORE mutating or saving the record, and propagate Err on failure -- so a caller can no longer report success while the old bundle stays authorized for up to 90 days. Failing before any mutation needs no rollback, since nothing was persisted yet. - sign_and_retain_waker_bundle_at now bumps the outer Nostr event's created_at past the previously retained head via the existing monotonic_created_at helper (same rule reconcile::retain_agent_record already applies to the sibling kind:30177 record), instead of using raw wall-clock seconds. NIP-33 replacement breaks same-created_at ties by lowest event id, so a same-second issue-then-revoke could otherwise leave the relay serving the older, non-revoked bundle even though local retention had already moved on. Signed-off-by: Junchao Yan <yjc801@gmail.com>
yjc801
pushed a commit
that referenced
this pull request
Aug 26, 2026
## Why Database pressure currently collapses several distinct delays into one symptom. This adds the evidence layer needed to distinguish pool acquisition wait, logical database operation time, advisory-lock wait, and selected transaction duration before changing timeout or retry policy. This is the phase 2 Lane A observability bundle for [#26](TheSentinel454#26), [#28](TheSentinel454#28), and [#33](TheSentinel454#33). It is stacked on block#6668. ## What - Record explicit reader/writer checkout wait and acquisition outcomes with `buzz_db_pool_acquire_wait_seconds` and `buzz_db_pool_acquisitions_total`. - Extend the compile-time `#[datastore_span(name = "...")]` seam with `buzz_db_operation_duration_seconds`, so operation labels remain static source literals instead of request data. - Route correctness-critical replacement, membership, push-gate, deletion, and migration/schema-safety advisory locks through one observer without changing their SQL, order, scope, or blocking behavior. - Measure six internally owned transaction lifetimes with `buzz_db_transaction_duration_seconds`, starting after `BEGIN` succeeds and ending after explicit commit/rollback or scope exit. - Emit root slow-operation warnings at 500 ms, logging the first slow completion and then 1/100 per call site with only `operation`, `outcome`, and `elapsed_ms`. - Document names, units, fixed label vocabularies, measurement boundaries, and blind spots in this PR description. Fixed labels are deliberately small: - `pool_role`: `writer`, `reader` - `lock_type`: `replacement`, `membership`, `push_gate`, `deletion`, `migration_schema_safety` - `outcome`: `success`, `error`, `timeout` where SQLx/PostgreSQL can distinguish it accurately - `operation`: compile-time datastore names plus the six closed transaction operation names documented in the runbook No metric or slow warning contains community IDs, event IDs, event kinds, coordinates, d-tags, SQL/query text, query IDs, returned errors, or event content. ## Coverage boundaries - Operation duration is the complete annotated logical function body, not pure SQL execution; it may include implicit checkout, lock wait, nested operations, and application work. Cancelled futures do not reach its completion hook. - Pool timing covers explicit helper checkouts, including proved-reader routing and selected writer-owned transactions. Implicit SQLx checkout through `&PgPool` remains folded into operation duration. - Lock timing covers application-side blocking locks in the five named families. Trigger/stored-procedure locks, channel-TTL locking, the usage try-lock, and the audit service session lock remain outside this slice. - Transaction timing covers only the six wholly owned boundaries documented in the runbook. It excludes pool wait, `BEGIN`, asynchronous rollback cleanup after an early return, and caller-owned `Db::begin_transaction` lifetime. ## Relationship to block#6229 block#6229 is the incident-driven timeout precursor. This PR does not add or change `statement_timeout`, `lock_timeout`, `idle_in_transaction_session_timeout`, retries, audit durability, or client-visible conflicts. It provides the missing distributions needed to evaluate those policies later and intentionally leaves block#6229's open audit retry/durability finding untouched. The branches overlap in `crates/buzz-db/src/lib.rs` and `crates/buzz-db/src/migration.rs`, so a later rebase may need textual conflict resolution, but the behavior is complementary rather than duplicated. ## Risk assessment Moderate-low. The primary risk is instrumentation overhead and added static series. Cardinality is source-bounded, slow logs are sampled/redacted root events, and the lock/transaction changes wrap existing awaits without changing policy or ordering. ## Verification Author workstation: `buzz-tornquist-db-pressure-observability` (`2010927`), exact head `d7cf833e26c528adfcde3917ded80daf6f4ddac9`, parent `6f50e6b2b2a996349149af61d35bdd6a355f77fd`. - `cargo fmt --all --check` — passed - `cargo clippy -p buzz-datastore-tracing -p buzz-db -p buzz-audit -p buzz-search -p buzz-relay --all-targets -- -D warnings` — passed - `cargo test -p buzz-datastore-tracing --quiet` — 4 passed - `cargo test -p buzz-db --quiet` — 109 passed, 200 ignored - `cargo test -p buzz-audit -p buzz-search --quiet` — 16 passed, 25 ignored - `cargo test -p buzz-relay --lib --quiet -- --test-threads=1` — 906 passed, 48 ignored - Native PostgreSQL focused tests for pool success/timeout/error, lock success/contention/timeout/error, replacement, membership serialization, push ordering, deletion fencing, migration/schema exclusion, and reader fallback — 8 passed The default-parallel relay run passed once; subsequent runs exposed the existing load-sensitive `api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` 504 at the end of the suite. That test passes in isolation and the full relay suite passes serially. Independent exact-head review workstation: `buzz-tornquist-db-pressure-observability-review` (`2013067`). Formatting, the same all-target clippy command, datastore instrumentation tests, DB unit tests, source privacy guards, and diff/non-goal audits passed; no review findings. Generated with Codex --------- Signed-off-by: tornquist <tornquist@squareup.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
sign_launch_bundle(desktop/src-tauri/src/managed_agents/waker_bundle.rs) has existed since PR #22/#24/#25 landed the daemon-side bundle transport, but had zero production callers — so a real wake attempt still ended inDeployFailed, just because no bundle ever reached the daemon rather than because the daemon couldn't act on one. This PR wires the desktop side up so a signed bundle actually gets published.ManagedAgentRecordgains an opt-inwaker_enabled: bool(defaultfalse). A bundle carries the agent'sprivate_key_nsec(NIP-44-encrypted to the agent, but published under the owner's real key to a public relay), so publishing it unconditionally for every Provider-backend agent was the wrong default — flagged this decision in#buzz-devbefore implementing, no objection.set_managed_agent_waker_enabledcommand flips the flag, refusing aLocalbackend. Turning it on is the enrolment moment (PLANS/BUZZ_WAKER_DESIGN.md§11).retain_managed_agent_pending— the existing chokepoint every real config-change command already calls, and that already skips runtime-only churn (start/stop) — now also issues and retains a fresh signed kind:30180 bundle whenwaker_enabledis set. It's retained through the samepending_syncrow the existing 30s flush loop (persona_events::flush_active_pending_events) already drains for persona/team/managed-agent writers, reusing that retry path instead of adding a second one.agents_waker.rs—agents.rsis already pastdesktop/scripts/check-file-sizes.mjs's ratchet and may not grow, soretain_managed_agent_pendingmoved there wholesale and is re-exported for its existing call sites.Out of scope, called out explicitly rather than guessed at:
crates/buzz-waker/src/effects.rs's own doc note) — daemon-side execution logic, not desktop's signing/publish path.set_managed_agent_waker_enabledis ready for a settings-panel toggle to call.Test plan
cargo fmt --manifest-path desktop/src-tauri/Cargo.toml -- --checkcargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warningscargo test --workspacefromdesktop/src-tauri— 2453 passed, 0 failed (plus sub-crate suites)node desktop/scripts/check-file-sizes.mjs— ratchet passespnpm check(desktop TS/lint) — passes (pre-existing unrelated warnings only)agents_waker.rsround-trip a retained bundle through the exact decrypt+verify pathbuzz-waker's own bundle tap runs, and assert version reissue never repeatsKnown pre-existing failure, unrelated to this change:
just gate'sdesktop-teststep fails onRootErrorBoundary.test.mjsexpecting the string "Buzz failed to start" against the app's actual rendered "Waggle failed to start" — fallout from the app-rename commit (PR #23), reproduces onmainwith zero changes from this branch. Confirmed viagit logthat PR #23 touchedRootErrorBoundary.tsxwithout updating its test's expected copy. Left untouched as out of scope for this PR.🤖 Generated with Claude Code