diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl
index 2726ae70a3..57193fabf3 100644
--- a/.beads/interactions.jsonl
+++ b/.beads/interactions.jsonl
@@ -13,3 +13,24 @@
{"id":"int-a2361c4964a752d958cfc5fbb69bd077","kind":"field_change","created_at":"2026-07-29T21:16:21.533526502Z","actor":"jeremylongshore","issue_id":"buzz-ocv.5","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
{"id":"int-4ee4835e436c4b7b6a00d0501b8b071c","kind":"field_change","created_at":"2026-07-30T04:47:01.972398591Z","actor":"jeremylongshore","issue_id":"buzz-ocv.6","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"DONE 2026-07-30. Mobile pairing sidecar deployed to prod + a REAL device paired (owner-confirmed handshake). Root cause: pinned relay-v0.2.0 image lacks buzz-pair-relay; fix pins the sidecar SEPARATELY via BUZZ_PAIR_RELAY_IMAGE (:main @9de8aff1) + absolute entrypoint, deployed sidecar-first (relay untouched), Caddy /pair route added. Verified wss://buzz-prod.intentsolutions.io/pair -> 101 Switching Protocols (Caddy->sidecar); sidecar logs show the live paired connection. intent-os PR #291 (overlay + test image-guard + runbook; 4/4 CI green). pairing-overlay-test now guards the image bug (5/5)."}}
{"id":"int-ecdd102b39c09f97150839220f0b0d34","kind":"field_change","created_at":"2026-07-30T04:47:05.19034745Z","actor":"jeremylongshore","issue_id":"buzz-ocv.5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"DONE 2026-07-30. Tauri desktop CORS origins (tauri://localhost + http://tauri.localhost) composed into version-controlled compose (intent-os PR #283), applied live to both hosts (PR #284), cors-config-test 2/2 in ci:drills. Final owner-gated gate met: Jeremy joined from the PACKAGED Linux desktop client to buzz-prod.intentsolutions.io (connected, admin, channels visible) — the packaged-client CORS verification the acceptance required."}}
+{"id":"int-9d63f6e48fc6157929c47a230eede39a","kind":"field_change","created_at":"2026-07-30T05:45:53.661302817Z","actor":"jeremylongshore","issue_id":"buzz-ocv.2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"DONE 2026-07-30 (ultracode Track-A workflow). Compensating edge controls shipped + applied. CODE: ops/buzz/scripts/apply-edge-controls.sh (plan-only default, --apply to install) + hermetic test, merged intent-os PR #292 (4/4 estate-CI green, adversarially verified sound). APPLIED LIVE on the dedicated prod host intent-ops-buzz: fail2ban 'caddy-buzz' jail (bans public 4xx-flooders from /var/log/caddy/buzz-access.log) + nftables 'inet buzz_edge' connlimit (128 concurrent NEW/IP on tcp 80+443 only) + buzz-edge-nft.service enabled (reboot-safe). VERIFIED no self-lock: SSH intact, relay _readiness 200 off-box, tailnet/port-22 never touched (WS/established never dropped). SCOPING: applied to the Buzz-DEDICATED prod host only; deliberately NOT applied host-wide on the shared VPS staging (its connlimit would rate-limit co-tenant estate stacks Plane/ERP/CRM) — request_body caps + security headers already exist in Caddy on both. Closes the last non-owner-gated piece of go-live edge hardening."}}
+{"id":"int-d1b3bcb9725ff332970aacd020a3a368","kind":"field_change","created_at":"2026-07-30T05:54:52.463260258Z","actor":"jeremylongshore","issue_id":"buzz-bsy","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"DONE 2026-07-30 (ultracode Track-A workflow). Built ops/buzz/scripts/restore.sh — the real destructive three-store restore (Postgres pg_restore --clean --if-exists → media mc mirror --overwrite → git volume), gated behind --in-place with verify-before-destroy (sha256 every artifact + PGDMP header + manifest.env==--env cross-check; exit 2 writes nothing). Chose --in-place-required over auto-restore so decision-log/038's human-gate holds with ZERO updater.sh changes: the updater's flagless call exits 64 → rc5 page unchanged (updater-drill 6/6 still green). Hermetic restore-test.sh 6/6 with mutation-proven teeth; restore-drill-test 4/4 no regression; wired into ci:drills. Merged intent-os PR #293 (4/4 estate-CI green). Remaining real-tool proof = a host-side scratch-stack drill before first prod use (noted in runbook)."}}
+{"id":"int-be2173d658fc095081a9083e49d64528","kind":"field_change","created_at":"2026-07-30T07:17:44.135065314Z","actor":"jeremylongshore","issue_id":"buzz-nry.2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"DONE 2026-07-30. Production deployed from proven staging artifacts with FRESH secrets on the dedicated host intent-ops-buzz, serving buzz-prod.intentsolutions.io (169.58.95.32). The 'cut DNS over' (apex → buzz.) requirement is DROPPED by owner decision 2026-07-30: buzz-prod.intentsolutions.io is the PERMANENT production URL (no apex cutover). Prod DNS is therefore already on its permanent, correct name — nothing further to cut over."}}
+{"id":"int-f42f74288896136765cfddee4c2de26e","kind":"field_change","created_at":"2026-07-30T07:17:46.752322494Z","actor":"jeremylongshore","issue_id":"buzz-nry.3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"DONE 2026-07-30. Full go-live gate suite PASSED against prod (buzz-prod.intentsolutions.io): smoke 200/NIP-11/functional PASS; unauth-matrix 6/6 REJECT; functional-probe (member NIP-42 auth+publish+readback, un-invited refused) with cleanup CONFIRMED (baseline count 3). Was dependency-blocked by buzz-nry.2 (apex cutover) — that dependency is resolved by the owner decision to keep buzz-prod permanent, so this now closes with its already-captured green evidence."}}
+{"id":"int-67f8b7a8627b3a18e0b7b5d935a2d3c1","kind":"field_change","created_at":"2026-07-30T07:18:01.27822535Z","actor":"jeremylongshore","issue_id":"buzz-nry","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"EPIC DONE 2026-07-30. Dedicated Buzz production VPS (intent-ops-buzz, 169.58.95.32) stood up to estate conventions (.1), deployed with fresh secrets serving buzz-prod.intentsolutions.io (.2), and the full go-live gate suite passed against it (.3). Owner decision 2026-07-30: buzz-prod.intentsolutions.io is the PERMANENT production URL — the 'cut the prod domain over' (apex) part of this epic is intentionally NOT done and is retired, not deferred. All three children closed."}}
+{"id":"int-7879443495ecbf89b4dc0db155eeab9d","kind":"field_change","created_at":"2026-07-31T03:30:31.615907433Z","actor":"jeremylongshore","issue_id":"buzz-yfe.1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"source-code reference written to ops/buzz/reference/BUZZ-SYSTEM-REFERENCE.md (45KB, file:line cited, from 8-agent sourcing workflow)"}}
+{"id":"int-5068eed2df8e022fc89b5a792782fd39","kind":"field_change","created_at":"2026-07-31T03:30:33.412199224Z","actor":"jeremylongshore","issue_id":"buzz-yfe.2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"estate-deployment reference written to ops/buzz/reference/BUZZ-ESTATE-DEPLOYMENT-REFERENCE.md (source-cited)"}}
+{"id":"int-a7c6e307556516f672455c69f544d886","kind":"field_change","created_at":"2026-07-31T03:30:34.984414889Z","actor":"jeremylongshore","issue_id":"buzz-w92.1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"AI Wire feed bot minted (8189ba10), relay member, joined ai-wire+0-general, first cards posted accepted:true 2026-07-31"}}
+{"id":"int-ddb9fd8ab1b2e550f95a215b3bfc16e0","kind":"field_change","created_at":"2026-07-31T03:36:16.562161598Z","actor":"jeremylongshore","issue_id":"buzz-yfe.3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"buzz-ops skill built (.claude/skills/buzz-ops/ with 3 bundled references); PASS /validate-skillmd standard tier"}}
+{"id":"int-f5404dea1362f4e691fb6edda3e17b7d","kind":"field_change","created_at":"2026-07-31T03:36:18.134646786Z","actor":"jeremylongshore","issue_id":"buzz-yfe.4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"buzz-ops agent built + PASS /validate-agent (14-field standalone spec)"}}
+{"id":"int-85fdaa9e88769d07584be2fb1e04cf42","kind":"field_change","created_at":"2026-07-31T03:36:19.601340573Z","actor":"jeremylongshore","issue_id":"buzz-yfe.5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"buzz-feed-curator agent built + PASS /validate-agent"}}
+{"id":"int-8850efaef9cdedb9badb537fc62935ba","kind":"field_change","created_at":"2026-07-31T03:36:21.08100149Z","actor":"jeremylongshore","issue_id":"buzz-yfe.6","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"buzz-relay-admin agent built + PASS /validate-agent"}}
+{"id":"int-4260303d729ddb42abd839cb31f38f27","kind":"field_change","created_at":"2026-07-31T03:36:22.349391594Z","actor":"jeremylongshore","issue_id":"buzz-yfe.7","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"all 3 agents PASS /validate-agent; skill PASS /validate-skillmd; reference-vs-code consistency 106 citations 100% resolve to real fork source (no fabrications)"}}
+{"id":"int-f0b734dccd26f403f2e9576181b68a3f","kind":"field_change","created_at":"2026-07-31T03:37:20.957039522Z","actor":"jeremylongshore","issue_id":"buzz-yfe","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"All 7 children closed: 2 references + skill + 3 agents + validation; all PASS the IS validators"}}
+{"id":"int-630ab91f0cf7e0e25edae67f26073244","kind":"field_change","created_at":"2026-07-31T04:06:15.368820216Z","actor":"jeremylongshore","issue_id":"buzz-w92.2","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
+{"id":"int-cca6517e1c6ee0187914938b7edd0144","kind":"field_change","created_at":"2026-07-31T04:18:38.265063552Z","actor":"jeremylongshore","issue_id":"buzz-w92.2","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"pipeline built + hardened (fetch→dedupe(newest-25)→Groq/NVIDIA summary→card), rate-limit fallback chain, ops/buzz/wire/buzz-wire.py"}}
+{"id":"int-78e02ffa7530bcd16898d0cbbffeec34","kind":"field_change","created_at":"2026-07-31T04:18:40.136787487Z","actor":"jeremylongshore","issue_id":"buzz-w92.3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"easy-tier feeds live — OpenAI, HuggingFace, arXiv cs.AI, Google DeepMind; 4 seed cards posted with summaries"}}
+{"id":"int-ca89c97ced65361b3070b31df6bec542","kind":"field_change","created_at":"2026-07-31T04:18:41.731887559Z","actor":"jeremylongshore","issue_id":"buzz-w92.8","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"3h cron deployed (~/bin/buzz-wire-cron.sh) + notify-lib liveness + registered in automations.md"}}
+{"id":"int-e4c1bfdb34ec5b768b2fe150c50071fc","kind":"field_change","created_at":"2026-07-31T04:30:55.252637052Z","actor":"jeremylongshore","issue_id":"buzz-w92.7","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Daily ai-wire digest live — --digest composes cross-lab roundup (70b→8b→NVIDIA), posts to 1-ai-wire, cron 06:30 CT"}}
+{"id":"int-873d15b79debfd144c2a44cbd6113969","kind":"field_change","created_at":"2026-07-31T14:04:52.409666792Z","actor":"jeremylongshore","issue_id":"buzz-w92.4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Anthropic bridged via HTML-scrape source type (news/research/engineering -> anthropic-blogs/research), mirroring the estate monitors; Mistral added. buzz-w92.6 partially done (tooling/mcp/newsletters/ships/status live)."}}
+{"id":"int-b6dc6f4abc00f98eb2fa156977ef6406","kind":"field_change","created_at":"2026-07-31T15:35:29.315597343Z","actor":"jeremylongshore","issue_id":"buzz-w92.5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Filled groq/meta/mistral/cohere/perplexity/xai + cleaner anthropic via Olshansk RSS mirrors (from perception repo) — no RSSHub needed. Remaining empty (deepseek/qwen/moonshot/minimax/benchmarks/funding/openrouter) have no mirror."}}
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index b786ef1a58..bc5ccd5f3c 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,20 +1,67 @@
-{"_type":"issue","id":"buzz-2i6","title":"Install nak on the Buzz hosts and run the real functional-probe rehearsal, then enable the prod updater timer","description":"The updater lane is deployed to staging (dormant) but the real functional-probe rehearsal is blocked: nak (the NIP-42 auth+publish+readback CLI the probe uses) is absent on the Buzz hosts. buzz-admin IS in the relay image. Steps: install nak on staging (+ prod), run functional-probe.sh against the live staging relay (throwaway member auth+publish+readback, un-invited refused, cleans up), then a controlled no-op-digest updater rehearsal on staging, THEN install-updater-lane.sh intent-ops-buzz --enable-timer for prod. Parent: buzz-ocv.4.","notes":"REHEARSAL PARTIAL on live staging 2026-07-29 (nak copied to /usr/local/bin/nak on the staging host; jq present). VERIFIED LIVE: closed relay up+healthy+CORS-applied; buzz-admin add-member/remove-member/list-members work — EXACT syntax is 'buzz-admin add-member --pubkey \u003chex-or-npub\u003e' (NOT positional; positional errors with usage). nak emits 64-char hex from 'nak key public'; relay accepts hex. add+remove round-trip proven; test member cleaned up (relay left clean: owner + 1 pre-existing member). REMAINING: the nak publish/readback NIP-42 flow hung with reactive '--auth' — the closed relay likely needs '--force-pre-auth' (authenticate BEFORE the EVENT/REQ), and readback (nak req -i \u003cid\u003e) on a closed relay also needs auth so the member NSEC must be threaded into functional-probe.sh's readback() (currently it only gets the event id). NEXT: (1) prove 'NOSTR_SECRET_KEY=\u003cnsec\u003e nak event -k1 -c X --auth --force-pre-auth \u003cws\u003e' returns an id against staging; (2) prove readback with the member key; (3) bake the verified commands + --pubkey into functional-probe.sh add_member/del_member/publish/readback; (4) then no-op-digest updater rehearsal on staging; (5) install-updater-lane.sh intent-ops-buzz --enable-timer.\nREHEARSAL DONE on staging (intent-os PR #288): nak installed on the staging host; functional-probe.sh fixed to the real NIP-42 flow (--sec, public wss through Caddy, --force-pre-auth readback, --pubkey, keygen newline, global cleanup trap) and PROVEN GREEN E2E against the live staging relay — member publish+readback, un-invited refused; smoke-suite PASS; unauth-matrix 6/6. Self-cleaning; staging membership left clean. REMAINING: install nak on the PROD host + run the same 3 gates against buzz-prod off-network; then enable the prod updater timer.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T21:47:43Z","created_by":"jeremylongshore","updated_at":"2026-07-30T00:37:47Z","labels":["go-live","probe","updater"],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.6","title":"Add the Claude impl-agent identity as a second buzz-acp process that authors PRs from goose-filed issues only","description":"Second buzz-acp process: Claude (claude-agent-acp), PermissionMode acceptEdits, PR-scoped PAT, a /work cwd that clones REPOS/\u003crepo\u003e on demand; reads ONLY goose-authored issues (never the raw channel firehose — invariant 2). Handoff is a human @mention of the impl agent on goose's issue (invariant 1). Acceptance: human @mention on issue #N =\u003e impl agent opens a PR against that repo; CI gates the merge; the agent cannot merge. DEPENDS on the ACP-headless spike.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:11Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:11Z","labels":["agentic-ops","estate","github-bridge","impl-agent"],"dependencies":[{"issue_id":"buzz-20l.6","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:10Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.6","depends_on_id":"buzz-20l.1","type":"blocks","created_at":"2026-07-31T13:58:36Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.6","depends_on_id":"buzz-20l.5","type":"blocks","created_at":"2026-07-31T13:58:37Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.5","title":"Configure the goose-triage profile — owner-gated, issue-filing only, reading the repo-channel firehose","description":"goose (MiniMax) runs owner-only, PermissionMode plan/dontAsk, no-merge PAT, subscribed to the repo-home channels; reads the raw bridge feed and files ONE GitHub issue per finding via the finding-id upsert. It NEVER authors code and NEVER hands off via chat @mention. Acceptance: a posted CI-failure card results in exactly one filed issue; goose has no write path to code.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:09Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:09Z","labels":["agentic-ops","estate","github-bridge","triage-agent"],"dependencies":[{"issue_id":"buzz-20l.5","depends_on_id":"buzz-20l.3","type":"blocks","created_at":"2026-07-31T13:58:34Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.5","depends_on_id":"buzz-20l.4","type":"blocks","created_at":"2026-07-31T13:58:34Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.5","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:09Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":2,"dependent_count":3,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.4","title":"Build the finding-id and level-triggered reconciler so every failure maps to exactly one auto-closing issue","description":"finding-id = hash(repo, workflow, normalized-failure) for idempotent issue upsert (create-only-if-absent); a periodic level-triggered reconciler computes currently-failing finding-ids and closes every finding-issue no longer in that set (do NOT rely on edge-triggered 'green run-\u003eclose' — Buzz drops replies #2459/#3587, agents go deaf in threads #2270). BUILD BEFORE the first repo channel goes live. Acceptance: three retries of one flaky failure =\u003e one issue; recovery =\u003e the issue auto-closes within one reconcile cycle.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:08Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:08Z","labels":["agentic-ops","estate","github-bridge","reliability"],"dependencies":[{"issue_id":"buzz-20l.4","depends_on_id":"buzz-20l.3","type":"blocks","created_at":"2026-07-31T13:58:33Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.4","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:08Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.3","title":"Build buzz-gh-bridge: a webhook receiver plus a GitHub App on both orgs that posts repo events as cards into repo-home channels","description":"FastAPI receiver on intent-ops-buzz (~100 lines): verify X-Hub-Signature-256 HMAC, dedup on X-GitHub-Delivery, map owner/repo-\u003echannel_id, render event-\u003ecard, post via the EXISTING local docker-exec path in ops/buzz/wire/buzz-wire.py (no ssh hop). One GitHub App installed on jeremylongshore + intent-solutions-io subscribed to pull_request, pull_request_review, pull_request_review_comment, issues, issue_comment, workflow_run, push, release, deployment_status. Webhooks (real-time), not polling; CI status via the workflow_run event. Caddy route -\u003eloopback. Acceptance: a real PR/CI event posts a card into its repo channel; HMAC-invalid and duplicate deliveries are rejected.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:07Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:07Z","labels":["agentic-ops","estate","github-bridge"],"dependencies":[{"issue_id":"buzz-20l.3","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:06Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":3,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.2","title":"Record the agentic-Buzz architecture decisions in decision-log (division of labor, source-of-truth, the two invariants)","description":"Append a decision-log/040 (or new) entry: goose=issues / Claude|Codex=PRs; GitHub is source of truth and Buzz is a projection (NO NIP-34 issue/PR mirror); invariant 1 = the GitHub issue IS the handoff (no agent-to-agent chat @mention); invariant 2 = untrusted channel/webhook text never reaches the shell+creds process; 3-process topology (not per-repo). Acceptance: entry merged, cited by the build beads.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:05Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:05Z","labels":["agentic-ops","decision","estate","github-bridge"],"dependencies":[{"issue_id":"buzz-20l.2","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:05Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.1","title":"Confirm Claude Code and Codex ACP harnesses run headless with API keys on the Buzz VPS (the build gate for the impl lane)","description":"Report source-4 flags upstream leans toward subscription/ChatGPT-login auth; we need headless API-key auth. Verify claude-agent-acp and codex-acp start, authenticate via API key, and complete a trivial task headless in an isolated container on intent-ops-buzz. BLOCKS the impl-agent bead. Acceptance: a headless ACP session with each harness completes a no-op task using an API key only, evidenced.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:04Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:04Z","labels":["agentic-ops","estate","github-bridge","spike"],"dependencies":[{"issue_id":"buzz-20l.1","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:03Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":2,"comment_count":0}
+{"_type":"issue","id":"buzz-20l","title":"Build the agentic Buzz estate-ops surface — repo-home channels, a GitHub event bridge, and a goose-triage + Claude-impl division of labor","description":"Evolve Buzz from a chat+feed surface into the estate's agentic operations surface, replacing the dead Greptile/Gemini/CodeRabbit 'what was found' review stream on our own stack. Research: ops/buzz/RESEARCH-agentic-buzz-utilization-2026-07-31.md (14-agent ultracode workflow, 2026-07-31).\n\nOwner decisions locked: goose files ISSUES only (owner-gated, plan/dontAsk, no-merge PAT); Claude/Codex authors PRs (native buzz-acp harnesses, config.rs:696 — nothing to build at harness level); every ACTIVE repo (both orgs, minus archived/dead) gets a repo-home channel + lazy-create on next push; GitHub is the source of truth, Buzz is a projection (NO NIP-34 mirror). Two load-bearing invariants: (1) the GitHub issue IS the triage-\u003eimpl handoff, never an agent-to-agent chat @mention; (2) untrusted channel/webhook text never reaches the process holding a shell + git creds.\n\nDependency-ordered children below. Build gate: confirm Claude/Codex ACP run headless with API keys on our VPS before the impl lane.","status":"open","priority":1,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:57:05Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:57:05Z","labels":["agentic-ops","estate","github-bridge"],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-1zr.1","title":"Stand up the staging drill harness for channel/identity/feed/notify actions","description":"A script suite that exercises the real actions against buzz-testing.intentsolutions.io with throwaway keys/channels, asserting the load-bearing rules: only-owner-can-delete returns the right error; a non-member post gets 403 relay_membership_required; add-member --role owner is rejected; provider-first naming; verify via channels list. Torn down after each run. AC: harness runs green on staging, prod untouched.","status":"open","priority":1,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:39:32Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:39:32Z","dependencies":[{"issue_id":"buzz-1zr.1","depends_on_id":"buzz-1zr","type":"parent-child","created_at":"2026-07-30T21:39:32Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-1zr","title":"Establish and run the Buzz testing procedure — staging drills, hermetic tests, agent evals","description":"A repeatable way to test the actions we build against Buzz, following the estate pattern (hermetic test in ci:drills + live drill) + the IS testing SOP (/audit-tests, /implement-tests, audit-harness). HARD SAFETY RULE: every destructive/live drill runs on the STAGING relay buzz-testing.intentsolutions.io (drills-only, never real members/channels) — NEVER prod (per ops/buzz/README.md). Covers the new surfaces: channel/identity ops, feed posting, sys-notification routing, and operator-agent behavior.","status":"open","priority":1,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:39:31Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:39:31Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.2","title":"Build the feed ingestion pipeline (fetch -\u003e dedupe -\u003e LLM -\u003e card)","description":"The core pipeline: RSS/RSSHub fetch, dedupe against seen-state, LLM summarize+score (MiniMax/Groq keys from SOPS), format a mobile-first card (imeta hero + markdown + source badge + link), post to ai-wire + the matching *-wire. Reuse perception's ingestion shape (GCP service dead; code is the reference). AC: one real lab item -\u003e a rich card in ai-wire + the lab channel.","status":"closed","priority":1,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:08Z","created_by":"jeremylongshore","updated_at":"2026-07-31T04:18:38Z","started_at":"2026-07-31T04:06:15Z","closed_at":"2026-07-31T04:18:38Z","close_reason":"pipeline built + hardened (fetch→dedupe(newest-25)→Groq/NVIDIA summary→card), rate-limit fallback chain, ops/buzz/wire/buzz-wire.py","dependencies":[{"issue_id":"buzz-w92.2","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:07Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-w92.2","depends_on_id":"buzz-yfe.5","type":"blocks","created_at":"2026-07-30T21:29:13Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.1","title":"Posting primitive — feed bot key + buzz messages send","description":"DONE 2026-07-31: minted the AI Wire bot npub (8189ba10, /srv/buzz-agent/wire-bot.env), added to relay membership, set profile, joined ai-wire + 0-general, posted the first cards (accepted:true). This is the shared post path for wire + sys notifications.","status":"closed","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:07Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:30:35Z","closed_at":"2026-07-31T03:30:35Z","close_reason":"AI Wire feed bot minted (8189ba10), relay member, joined ai-wire+0-general, first cards posted accepted:true 2026-07-31","dependencies":[{"issue_id":"buzz-w92.1","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:06Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":2,"comment_count":0}
+{"_type":"issue","id":"buzz-w92","title":"AI-Wire feed engine — fetch, summarize, post curated cards","description":"The engine that fills ai-wire + the *-wire channels: per source, poll the feed (or RSSHub for scrape-only labs) -\u003e dedupe -\u003e LLM summarize/score -\u003e post a rich mobile card via the AI Wire bot. Anti-firehose: ai-wire is one curated digest; packs are opt-in. decision-log/040 D140 (fork-and-wire). Reuse the perception repo ingestion pattern + the estate ~/bin Anthropic monitor fleet. Posting primitive already proven (AI Wire bot live).","status":"open","priority":1,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:06Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:29:06Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe.3","title":"Build the buzz-ops skill bundling the reference files","description":"A /skill-creator skill that bundles both reference docs so any session/agent that invokes it knows Buzz's source + estate layout. AC: SKILL.md passes /validate-skillmd; references bundled; encodes the load-bearing rules (only-owner-deletes, membership-required-to-post, provider-first naming, secret handling).","status":"closed","priority":1,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:14Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:36:16Z","closed_at":"2026-07-31T03:36:16Z","close_reason":"buzz-ops skill built (.claude/skills/buzz-ops/ with 3 bundled references); PASS /validate-skillmd standard tier","dependencies":[{"issue_id":"buzz-yfe.3","depends_on_id":"buzz-yfe.1","type":"blocks","created_at":"2026-07-30T21:28:59Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.3","depends_on_id":"buzz-yfe","type":"parent-child","created_at":"2026-07-30T21:27:13Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.3","depends_on_id":"buzz-yfe.2","type":"blocks","created_at":"2026-07-30T21:28:59Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":2,"dependent_count":3,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe.2","title":"Produce the Buzz estate-deployment reference","description":"How Buzz is wired in the estate: two VPSs, containers, secrets/keys, ops/buzz lane, deploy/backup/monitoring, sys-* notification path. AC: ops/buzz/reference/BUZZ-ESTATE-DEPLOYMENT-REFERENCE.md written, source-cited.","status":"closed","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:13Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:30:33Z","closed_at":"2026-07-31T03:30:33Z","close_reason":"estate-deployment reference written to ops/buzz/reference/BUZZ-ESTATE-DEPLOYMENT-REFERENCE.md (source-cited)","dependencies":[{"issue_id":"buzz-yfe.2","depends_on_id":"buzz-yfe","type":"parent-child","created_at":"2026-07-30T21:27:12Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe.1","title":"Produce the Buzz source-code reference (file:line)","description":"Map the block/buzz source: relay, CLI, admin, event kinds, auth/NIP-OA, ACP agent, personas/templates, media. IN FLIGHT via an 8-agent ultracode workflow -\u003e BUZZ-SYSTEM-REFERENCE.md. AC: ops/buzz/reference/BUZZ-SYSTEM-REFERENCE.md written, file:line cited.","status":"closed","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:12Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:30:31Z","closed_at":"2026-07-31T03:30:31Z","close_reason":"source-code reference written to ops/buzz/reference/BUZZ-SYSTEM-REFERENCE.md (45KB, file:line cited, from 8-agent sourcing workflow)","dependencies":[{"issue_id":"buzz-yfe.1","depends_on_id":"buzz-yfe","type":"parent-child","created_at":"2026-07-30T21:27:12Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":2,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe","title":"Encode durable Buzz operational expertise — reference, skill, operator agents","description":"Stop operating Buzz by trial-and-error. Produce source-grounded reference docs, a buzz-ops skill that bundles them, and validated operator agents that actually know how Buzz is wired. Built via /skill-creator and /agent-creator (IS 8-field spec), grounded in the references. Home: ops/buzz/reference/ (docs), intent-os .claude/ (skill+agents).","status":"closed","priority":1,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:11Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:37:21Z","closed_at":"2026-07-31T03:37:21Z","close_reason":"All 7 children closed: 2 references + skill + 3 agents + validation; all PASS the IS validators","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-ehv","title":"Community channel layout and identity roster — build clean, keep clean","description":"The buzz-prod community's channel architecture and member/agent identity hygiene. Ground truth + procedures: ops/buzz/RUNBOOK-channels.md (source-cited). SHIPPED this session (2026-07-31): the clean provider-first open layout (0-general pinned, ask, ai-wire, anthropic-* group, \u003cprovider\u003e-wire firehoses, topic *-wire, private sys-*); persona agents (Bumble/Fizz/Honey/CCA-F) purged; canonical names set (Buzz Admin, goose minimax3, AI Wire, humans); RUNBOOK-channels.md written. Open children below.","status":"open","priority":1,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:09Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:27:09Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-2i6","title":"Install nak on the Buzz hosts and run the real functional-probe rehearsal, then enable the prod updater timer","description":"The updater lane is deployed to staging (dormant) but the real functional-probe rehearsal is blocked: nak (the NIP-42 auth+publish+readback CLI the probe uses) is absent on the Buzz hosts. buzz-admin IS in the relay image. Steps: install nak on staging (+ prod), run functional-probe.sh against the live staging relay (throwaway member auth+publish+readback, un-invited refused, cleans up), then a controlled no-op-digest updater rehearsal on staging, THEN install-updater-lane.sh intent-ops-buzz --enable-timer for prod. Parent: buzz-ocv.4.","notes":"REHEARSAL PARTIAL on live staging 2026-07-29 (nak copied to /usr/local/bin/nak on the staging host; jq present). VERIFIED LIVE: closed relay up+healthy+CORS-applied; buzz-admin add-member/remove-member/list-members work — EXACT syntax is 'buzz-admin add-member --pubkey \u003chex-or-npub\u003e' (NOT positional; positional errors with usage). nak emits 64-char hex from 'nak key public'; relay accepts hex. add+remove round-trip proven; test member cleaned up (relay left clean: owner + 1 pre-existing member). REMAINING: the nak publish/readback NIP-42 flow hung with reactive '--auth' — the closed relay likely needs '--force-pre-auth' (authenticate BEFORE the EVENT/REQ), and readback (nak req -i \u003cid\u003e) on a closed relay also needs auth so the member NSEC must be threaded into functional-probe.sh's readback() (currently it only gets the event id). NEXT: (1) prove 'NOSTR_SECRET_KEY=\u003cnsec\u003e nak event -k1 -c X --auth --force-pre-auth \u003cws\u003e' returns an id against staging; (2) prove readback with the member key; (3) bake the verified commands + --pubkey into functional-probe.sh add_member/del_member/publish/readback; (4) then no-op-digest updater rehearsal on staging; (5) install-updater-lane.sh intent-ops-buzz --enable-timer.\nREHEARSAL DONE on staging (intent-os PR #288): nak installed on the staging host; functional-probe.sh fixed to the real NIP-42 flow (--sec, public wss through Caddy, --force-pre-auth readback, --pubkey, keygen newline, global cleanup trap) and PROVEN GREEN E2E against the live staging relay — member publish+readback, un-invited refused; smoke-suite PASS; unauth-matrix 6/6. Self-cleaning; staging membership left clean. REMAINING: install nak on the PROD host + run the same 3 gates against buzz-prod off-network; then enable the prod updater timer.\nAdvanced 2026-07-30 (ultracode Track-A workflow), timer NOT armed by design: (1) nak installed on prod host intent-ops-buzz (/usr/local/bin/nak, keygen verified); (2) wrapped-updater lane installed DORMANT via install-updater-lane.sh (no --enable-timer) — 6/6 lane scripts + both systemd units present, daemon-reload+syntax OK; (3) smoke-suite.sh + unauth-matrix.sh pushed (the installer omits them); (4) functional-probe rehearsal PASSED green against live prod (see buzz-nry.3 gate-suite run — member auth/publish/readback, un-invited refused, self-cleaned). VERIFIED dormant: timer disabled+inactive, updater.service never ran, serving digest unchanged (a0f672). REMAINING = arm the timer (systemctl enable --now) — HELD pending owner call: arming = unattended weekly pre-1.0 prod deploys with advisory-only scan, before apex cutover. Recommend arming AFTER apex cutover; lane is one command from armed.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T21:47:43Z","created_by":"jeremylongshore","updated_at":"2026-07-30T05:40:19Z","labels":["go-live","probe","updater"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-aet","title":"Author the authoritative Buzz naming and boundaries record and reconcile the blueprint ledger to prod-up","description":"Track B of the Buzz adoption plan (owner amendment 2026-07-29, do FIRST). Author the canonical naming+boundaries record in the fork 000-docs from the AUTHORITATIVE NAMING \u0026 REPOSITORY MODEL table: 6 assets (upstream block/buzz, code fork intent-solutions-io/buzz, contributor lab intent-solutions-io/intent-ops-buzz repo, production host intent-ops-buzz VPS, live ops lane intent-os ops/buzz, deferred plugin intent-solutions-io/intent-ops-buzz-plugin), the disambiguation rule (never bare 'intent-ops-buzz'), and the four-plane flow with two separate gates (contribution vs deployment). Reconcile the blueprint 001 completion ledger to reality (prod host built/deployed/verified pre-cutover, backups+restore proven), name the plugin repo deferred, and add the contributor-lab plane. Unblocks the parked contributor-lab architecture DRAFT (Track C). The amendment forbids durable architecture docs until this lands. Done-means: naming doc exists + internally consistent; ledger prod-up; no bare-intent-ops-buzz ambiguity; pnpm check green on the intent-os reconcile edit.","status":"closed","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T19:19:37Z","created_by":"jeremylongshore","updated_at":"2026-07-29T19:36:54Z","started_at":"2026-07-29T19:19:43Z","closed_at":"2026-07-29T19:36:54Z","close_reason":"Track B shipped: authored fork 000-docs/006-DR-STND-authoritative-naming-and-boundaries.md (canonical six-asset table + host-vs-repo disambiguation rule + four-plane flow + two-gate model); reconciled the 001 blueprint ledger to prod-up (E2/E2d/E3 states, ELab row, E9 plugin named intent-ops-buzz-plugin + DEFERRED, new Phase 6 pointer); added the intent-os ops/buzz/README 'Naming \u0026 planes' section. Merged: fork PR #11 + intent-os PR #281 (estate-CI 3 green contexts: gates 128s, drills 212s, gitleaks). pnpm check EXIT=0; no bare-intent-ops-buzz ambiguity remains.","labels":["architecture","ledger","naming"],"dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"buzz-nry.3","title":"Run the full go-live gate suite against production on the dedicated box","description":"The full E3 go-live gate suite to RUN against PROD before any invite (all BLOCKING, none assumed passed): unauth probe matrix off-network; functional membership probe (NIP-42 publish+readback, un-invited refused); backup restore drill (restore staging from prod's artifacts); updater planted-fault drill on staging; key runbooks rehearsed; resource caps verified. Status is tracked per-gate in the notes and in intent-os ops/buzz — the off-site backup leg and several gates remain OPEN; this description is scope, not a completion claim.","notes":"Go-live gates PARTIAL against prod: (1) functional membership probe GREEN — owner NIP-42 auth -\u003e publish -\u003e readback OK; un-invited key REFUSED (closed relay enforcing, member_count:1 on boot). (2) unauth HTTP probe matrix PASS — media PUT 405, git 404/405, hooks 400, admin 404; public readiness/NIP-11/web-client 200. (3) resource caps + loopback-only publish + isolated bridge net verified. REMAINING/BLOCKING before any invite: backup-restore drill, updater planted-fault drill, key runbooks — all depend on backup+updater wiring (buzz-ocv.4, not built).\nRESTORE DRILL PROVEN (buzz-nry.3 gate item): restored the first prod archive into an ISOLATED dev-box scratch project (prod/staging untouched, prod 4/4 healthy throughout). 40 tables restored; the exact prod-published event 9717ccaa physically present; relay_members=1 + member_count:1 on boot; media restored; relay identity stable (same key-\u003e35cd57ab); relay boots healthy; un-invited refused; restart persistence holds. RTO ~3min, RPO = backup point (18:36:41Z). Caveat: headless nak auth-on-REQ readback didn't complete in-harness (passed on prod). Evidence: ops/buzz/RUNBOOK-backup-restore.md. STILL BLOCKING before invite: updater planted-fault drill, key runbooks, off-site copy.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:31:30Z","created_by":"jeremylongshore","updated_at":"2026-07-29T18:45:39Z","labels":["hosting"],"dependencies":[{"issue_id":"buzz-nry.3","depends_on_id":"buzz-nry.2","type":"blocks","created_at":"2026-07-29T10:31:31Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-nry.3","depends_on_id":"buzz-nry","type":"parent-child","created_at":"2026-07-29T10:31:29Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"buzz-nry.2","title":"Deploy production from the proven staging artifacts with fresh secrets and cut DNS over","description":"Deploy prod from the proven staging artifacts (same digest-pinned compose) with FRESH secrets minted on the new box (new relay identity, new bootstrap owner key until the owner's desktop key swaps in, new db/redis/minio; staging keys never promote). DNS cuts over to the prod host at go-live; the shared stack renames to the staging domain. Deploy specifics + addressing in intent-os ops/buzz. Depends on the bootstrap child.","notes":"Prod deploy DONE (apex cutover pending): staging-proven digest-pinned compose deployed with FRESH prod secrets; 4 containers healthy; own Caddy ingress TLS + security headers. REMAINING (gated on the owner's client-generated desktop key): DNS cutover to the prod host, rename staging to the staging domain, swap BUZZ_DOMAIN/RELAY_URL/CORS to the apex, swap RELAY_OWNER_PUBKEY to the owner's desktop pubkey. Addressing + specifics in intent-os ops/buzz.","status":"open","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:31:22Z","created_by":"jeremylongshore","updated_at":"2026-07-29T18:17:43Z","labels":["hosting"],"dependencies":[{"issue_id":"buzz-nry.2","depends_on_id":"buzz-nry","type":"parent-child","created_at":"2026-07-29T10:31:22Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-nry.2","depends_on_id":"buzz-nry.1","type":"blocks","created_at":"2026-07-29T10:31:31Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
+{"_type":"issue","id":"buzz-nry.3","title":"Run the full go-live gate suite against production on the dedicated box","description":"The full E3 go-live gate suite to RUN against PROD before any invite (all BLOCKING, none assumed passed): unauth probe matrix off-network; functional membership probe (NIP-42 publish+readback, un-invited refused); backup restore drill (restore staging from prod's artifacts); updater planted-fault drill on staging; key runbooks rehearsed; resource caps verified. Status is tracked per-gate in the notes and in intent-os ops/buzz — the off-site backup leg and several gates remain OPEN; this description is scope, not a completion claim.","notes":"Go-live gates PARTIAL against prod: (1) functional membership probe GREEN — owner NIP-42 auth -\u003e publish -\u003e readback OK; un-invited key REFUSED (closed relay enforcing, member_count:1 on boot). (2) unauth HTTP probe matrix PASS — media PUT 405, git 404/405, hooks 400, admin 404; public readiness/NIP-11/web-client 200. (3) resource caps + loopback-only publish + isolated bridge net verified. REMAINING/BLOCKING before any invite: backup-restore drill, updater planted-fault drill, key runbooks — all depend on backup+updater wiring (buzz-ocv.4, not built).\nRESTORE DRILL PROVEN (buzz-nry.3 gate item): restored the first prod archive into an ISOLATED dev-box scratch project (prod/staging untouched, prod 4/4 healthy throughout). 40 tables restored; the exact prod-published event 9717ccaa physically present; relay_members=1 + member_count:1 on boot; media restored; relay identity stable (same key-\u003e35cd57ab); relay boots healthy; un-invited refused; restart persistence holds. RTO ~3min, RPO = backup point (18:36:41Z). Caveat: headless nak auth-on-REQ readback didn't complete in-harness (passed on prod). Evidence: ops/buzz/RUNBOOK-backup-restore.md. STILL BLOCKING before invite: updater planted-fault drill, key runbooks, off-site copy.\nGATE SUITE PASSED against prod 2026-07-30 (ultracode Track-A workflow, non-destructive): smoke 200/NIP-11/functional PASS; unauth-matrix 6/6 REJECT; functional-probe (member NIP-42 auth+publish+readback, un-invited refused) with cleanup CONFIRMED (back to baseline count 3). The suite deliverable is DONE; this bead stays OPEN only because its dependency buzz-nry.2 (apex DNS cutover) is owner-gated and not yet done — close together when apex cuts over.","status":"closed","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:31:30Z","created_by":"jeremylongshore","updated_at":"2026-07-30T07:17:47Z","closed_at":"2026-07-30T07:17:47Z","close_reason":"DONE 2026-07-30. Full go-live gate suite PASSED against prod (buzz-prod.intentsolutions.io): smoke 200/NIP-11/functional PASS; unauth-matrix 6/6 REJECT; functional-probe (member NIP-42 auth+publish+readback, un-invited refused) with cleanup CONFIRMED (baseline count 3). Was dependency-blocked by buzz-nry.2 (apex cutover) — that dependency is resolved by the owner decision to keep buzz-prod permanent, so this now closes with its already-captured green evidence.","labels":["hosting"],"dependencies":[{"issue_id":"buzz-nry.3","depends_on_id":"buzz-nry.2","type":"blocks","created_at":"2026-07-29T10:31:31Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-nry.3","depends_on_id":"buzz-nry","type":"parent-child","created_at":"2026-07-29T10:31:29Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-nry.2","title":"Deploy production from the proven staging artifacts with fresh secrets and cut DNS over","description":"Deploy prod from the proven staging artifacts (same digest-pinned compose) with FRESH secrets minted on the new box (new relay identity, new bootstrap owner key until the owner's desktop key swaps in, new db/redis/minio; staging keys never promote). DNS cuts over to the prod host at go-live; the shared stack renames to the staging domain. Deploy specifics + addressing in intent-os ops/buzz. Depends on the bootstrap child.","notes":"Prod deploy DONE (apex cutover pending): staging-proven digest-pinned compose deployed with FRESH prod secrets; 4 containers healthy; own Caddy ingress TLS + security headers. REMAINING (gated on the owner's client-generated desktop key): DNS cutover to the prod host, rename staging to the staging domain, swap BUZZ_DOMAIN/RELAY_URL/CORS to the apex, swap RELAY_OWNER_PUBKEY to the owner's desktop pubkey. Addressing + specifics in intent-os ops/buzz.","status":"closed","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:31:22Z","created_by":"jeremylongshore","updated_at":"2026-07-30T07:17:44Z","closed_at":"2026-07-30T07:17:44Z","close_reason":"DONE 2026-07-30. Production deployed from proven staging artifacts with FRESH secrets on the dedicated host intent-ops-buzz, serving buzz-prod.intentsolutions.io (169.58.95.32). The 'cut DNS over' (apex → buzz.) requirement is DROPPED by owner decision 2026-07-30: buzz-prod.intentsolutions.io is the PERMANENT production URL (no apex cutover). Prod DNS is therefore already on its permanent, correct name — nothing further to cut over.","labels":["hosting"],"dependencies":[{"issue_id":"buzz-nry.2","depends_on_id":"buzz-nry","type":"parent-child","created_at":"2026-07-29T10:31:22Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-nry.2","depends_on_id":"buzz-nry.1","type":"blocks","created_at":"2026-07-29T10:31:31Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"buzz-nry.1","title":"Bootstrap the new production host to estate conventions","description":"Bootstrap the dedicated prod host to estate ops/host conventions: Ubuntu 24.04, tailnet-only SSH, ufw, fail2ban, unattended-upgrades, docker, its own Caddy (separate ingress = separate failure domain), age host key + sops, borg client, Netdata (tailnet-bound). Host addressing, key paths, and access detail live ONLY in intent-os ops/buzz. Gated on the owner installing the dev-box SSH key.","status":"closed","priority":1,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:31:15Z","created_by":"jeremylongshore","updated_at":"2026-07-29T18:06:58Z","started_at":"2026-07-29T17:57:57Z","closed_at":"2026-07-29T18:06:58Z","close_reason":"intent-ops-buzz bootstrapped to estate baseline + verified: Ubuntu 24.04.4 LTS, hostname intent-ops-buzz, joined tailnet [tailnet addr — intent-os ops/buzz] (untagged/user-owned, matching estate); ufw default-deny (tailnet-trusted iface + public 80/443 only, public 22 CLOSED/times-out); sshd key-only + PermitRootLogin no + PasswordAuthentication no; a non-root sudo admin account (NOPASSWD, docker group); fail2ban + unattended-upgrades active; 2G swap swappiness=10; docker 29.6 (log-rotation + live-restore) + compose v5.3; caddy 2.11 (own ingress); age host key at [host key path — intent-os ops/host] (recipient [host age recipient — intent-os ops/host/secrets]); borg client; sops 3.9.4; Netdata bound loopback+tailnet only (127.0.0.1:19999 + [tailnet addr — intent-os ops/buzz]:19999, not public). 7/7 services active.","labels":["hosting"],"dependencies":[{"issue_id":"buzz-nry.1","depends_on_id":"buzz-nry","type":"parent-child","created_at":"2026-07-29T10:31:15Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
-{"_type":"issue","id":"buzz-nry","title":"Stand up the dedicated Buzz production VPS and cut the prod domain over to it","description":"Track D: Buzz production runs on its OWN dedicated VPS (owner topology decision 2026-07-29, fork 000-docs/005) so a fast-moving pre-1.0 stack does not share a failure domain with revenue workloads. The shared-VPS stack becomes permanent STAGING (epic buzz-ocv). Prod = the proven staging artifacts (same digest-pinned compose) with FRESH secrets; staging keys never promote. All go-live gates run against prod before any invite. Concrete host detail (addressing, keys, DNS, deploy specifics) lives ONLY in the private intent-os ops/buzz lane.","notes":"GitHub: intent-solutions-io/buzz#9 — https://github.com/intent-solutions-io/buzz/issues/9. MIRROR RULE: bd-sync handles fan-out — `bd-sync note buzz-nry` and `bd-sync close buzz-nry` mirror to GH and Plane automatically.\n\nPlane: BUZZ-3 — (Plane, internal)\nProd pairing milestone 2026-07-30: mobile device pairing now LIVE on the production host (buzz-prod.intentsolutions.io) — sidecar + Caddy /pair deployed, owner confirmed a real device paired (intent-os PR #291). Prod relay + sidecar both healthy. Remaining under this prod epic: .2 apex DNS cutover off buzz-prod-\u003e buzz., .3 full go-live gate suite against prod.","status":"in_progress","priority":1,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:31:08Z","created_by":"jeremylongshore","updated_at":"2026-07-30T04:48:53Z","started_at":"2026-07-29T17:57:57Z","labels":["hosting"],"dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"buzz-bsy","title":"Build the Buzz three-store restore tool (restore.sh) and its restore drill","description":"The wrapped updater's ordered store-restore on a bad-migration revert calls ops/buzz/scripts/restore.sh (--recovery-point \u003cid\u003e --env \u003cenv\u003e) in intent-os. Until it exists+drilled, the updater fail-closes to exit 5 (manual page) rather than faking a DB rollback. Build restore.sh (pg_dump -Fc restore + MinIO media + git volume from a bound recovery point, dangling-ref walk) + a restore drill, so bad-migration recovery is fully unattended. Parent: wrapped-updater lane (buzz-ocv.4).","status":"open","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T20:13:56Z","created_by":"jeremylongshore","updated_at":"2026-07-29T20:13:56Z","labels":["backup","restore","updater"],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-nry","title":"Stand up the dedicated Buzz production VPS and cut the prod domain over to it","description":"Track D: Buzz production runs on its OWN dedicated VPS (owner topology decision 2026-07-29, fork 000-docs/005) so a fast-moving pre-1.0 stack does not share a failure domain with revenue workloads. The shared-VPS stack becomes permanent STAGING (epic buzz-ocv). Prod = the proven staging artifacts (same digest-pinned compose) with FRESH secrets; staging keys never promote. All go-live gates run against prod before any invite. Concrete host detail (addressing, keys, DNS, deploy specifics) lives ONLY in the private intent-os ops/buzz lane.","notes":"GitHub: intent-solutions-io/buzz#9 — https://github.com/intent-solutions-io/buzz/issues/9. MIRROR RULE: bd-sync handles fan-out — `bd-sync note buzz-nry` and `bd-sync close buzz-nry` mirror to GH and Plane automatically.\n\nPlane: BUZZ-3 — (Plane, internal)\nProd pairing milestone 2026-07-30: mobile device pairing now LIVE on the production host (buzz-prod.intentsolutions.io) — sidecar + Caddy /pair deployed, owner confirmed a real device paired (intent-os PR #291). Prod relay + sidecar both healthy. Remaining under this prod epic: .2 apex DNS cutover off buzz-prod-\u003e buzz., .3 full go-live gate suite against prod.","status":"closed","priority":1,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:31:08Z","created_by":"jeremylongshore","updated_at":"2026-07-30T07:18:01Z","started_at":"2026-07-29T17:57:57Z","closed_at":"2026-07-30T07:18:01Z","close_reason":"EPIC DONE 2026-07-30. Dedicated Buzz production VPS (intent-ops-buzz, 169.58.95.32) stood up to estate conventions (.1), deployed with fresh secrets serving buzz-prod.intentsolutions.io (.2), and the full go-live gate suite passed against it (.3). Owner decision 2026-07-30: buzz-prod.intentsolutions.io is the PERMANENT production URL — the 'cut the prod domain over' (apex) part of this epic is intentionally NOT done and is retired, not deferred. All three children closed.","labels":["hosting"],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-s8q","title":"Evaluate + owner-gated execute the Buzz relay upgrade off v0.2.0 via the wrapped updater (staging-rehearsed, pinned-sha — we'd be running AHEAD of Block's stable :latest)","description":"Finding 2026-07-31: Block's GHCR :latest relay tag == our current prod digest (a0f672, 'v0.2.0'); there is NO version-pinned relay release — v0.5.3-era relay code exists only in rolling :main / sha-\u003ccommit\u003e builds. So a relay upgrade = running UNRELEASED bleeding-edge code + a ~3-version schema migration on the live 49-person event store (exactly the D139 risk). Value is low relay-side (community-limit-\u003e5, NIP-11 max_limit, reconnect backoff); the agent reply-guard #3763 + Claude/Codex harnesses are AGENT-binary changes (rebuild our buzz-agent image, tracked under buzz-20l), NOT relay. Desktop clients already auto-updated to v0.5.3 and work against our relay (Nostr protocol-stable). Recommendation: stay on stable :latest until a real relay release exists OR buzz-20l needs a specific relay feature; if executed, pin a sha-\u003ccommit\u003e (not rolling :main), rehearse the migration on buzz-testing via ops/buzz/scripts/updater.sh, then prod with bound-snapshot + functional-probe + auto-revert. Owner-gated: this is an eyes-open 'run ahead of upstream stable' decision.","status":"open","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-08-01T02:40:44Z","created_by":"jeremylongshore","updated_at":"2026-08-01T02:40:44Z","labels":["agentic-ops","relay","updater"],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.8","title":"Cherry-pick the upstream Codex auto-reviewer (block/buzz #3715) onto the fork as a config injection","description":"buzz-acp-local approvals_reviewer=auto_review (#3715, OPEN upstream) — the closest drop-in for the dead Greptile PR-review. Cherry-pick as a fork config injection; does NOT touch the owner-gated remote-agents spec #3748. Acceptance: a PR triggers an automated Codex review comment on a test repo.","status":"open","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:13Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:13Z","labels":["agentic-ops","codex-review","estate","github-bridge"],"dependencies":[{"issue_id":"buzz-20l.8","depends_on_id":"buzz-20l.1","type":"blocks","created_at":"2026-07-31T13:58:38Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.8","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:12Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.7","title":"Stand up repo-home channels for all active repos with lazy-create on next push","description":"One \u003crepo-name\u003e home channel per ACTIVE repo across both orgs (owner call: ALL active, ~74, minus archived and truly-dead; the ~15 dead repos get NO channel). Any other repo auto-creates its channel on its next real GitHub event (repo-channels.json is a cache derived from traffic, not a hand-authored census). Additive to the ~24 *-wire channels. Acceptance: active repos have home channels; a push to a channel-less repo auto-creates one; archived repos never get one.","status":"open","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:12Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:12Z","labels":["agentic-ops","channels","estate","github-bridge"],"dependencies":[{"issue_id":"buzz-20l.7","depends_on_id":"buzz-20l.3","type":"blocks","created_at":"2026-07-31T13:58:35Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.7","depends_on_id":"buzz-20l.4","type":"blocks","created_at":"2026-07-31T13:58:36Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.7","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:11Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-b6z.3","title":"Alert runtime relay errors/panics into sys-incidents and health into sys-health","description":"Today only deploy/backup failures page; a runtime relay panic/error goes unnoticed. Tail the relay logs (or a health endpoint) and route error/panic -\u003e sys-incidents, degraded-health -\u003e sys-health via the notifier. AC: a planted relay error lands in sys-incidents.","status":"open","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:40:57Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:40:57Z","dependencies":[{"issue_id":"buzz-b6z.3","depends_on_id":"buzz-b6z","type":"parent-child","created_at":"2026-07-30T21:40:57Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-b6z.3","depends_on_id":"buzz-0ts.1","type":"blocks","created_at":"2026-07-30T21:40:58Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-b6z","title":"Wire Buzz observability — trace export, log aggregation, error alerting, audit-chain","description":"Buzz has the bones (tracing_subscriber structured logs, OTel-export-ready relay code, the buzz-audit per-community SHA-256 hash-chain, Netdata host metrics, docker log rotation) but the wiring is incomplete. Close the gaps so a Buzz runtime problem is visible and pages. Feeds the sys-* channels (epic buzz-0ts).","status":"open","priority":2,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:40:55Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:40:55Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-1zr.3","title":"Golden behavioral evals for the 3 operator agents","description":"Golden-transcript evals (the opskit-golden pattern) for buzz-ops / buzz-feed-curator / buzz-relay-admin: assert each DOES the right sanctioned action and REFUSES illegal ones (DB surgery, deleting a non-owned channel, posting a secret, prod destructive drill). AC: eval suite green; a regression is caught.","status":"open","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:39:34Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:39:34Z","dependencies":[{"issue_id":"buzz-1zr.3","depends_on_id":"buzz-1zr","type":"parent-child","created_at":"2026-07-30T21:39:34Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-1zr.3","depends_on_id":"buzz-yfe.6","type":"blocks","created_at":"2026-07-30T21:39:36Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-1zr.3","depends_on_id":"buzz-yfe.5","type":"blocks","created_at":"2026-07-30T21:39:36Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-1zr.3","depends_on_id":"buzz-yfe.4","type":"blocks","created_at":"2026-07-30T21:39:35Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":3,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-1zr.2","title":"Hermetic tests for the new actions, wired into pnpm ci:drills","description":"Dry-run/mocked hermetic tests (no live relay) for channel-ops command construction, feed-card rendering (imeta), and notify topic-\u003echannel routing — same shape as the existing ops/buzz/tests/*.sh. AC: tests pass in ci:drills; a planted fault is caught.","status":"open","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:39:33Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:39:33Z","dependencies":[{"issue_id":"buzz-1zr.2","depends_on_id":"buzz-1zr","type":"parent-child","created_at":"2026-07-30T21:39:33Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-0ts.2","title":"Add af_buzz_transport to the af_dispatch chain (honest 2xx)","description":"A transport like vps-slack-transport.sh that posts to Buzz with honest 2xx classification, so Buzz alerts inherit dedup/rate-limit/spool/floor. AC: a governed alert routes through af_dispatch to a sys-* channel with a real receipt.","status":"open","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:19Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:29:19Z","dependencies":[{"issue_id":"buzz-0ts.2","depends_on_id":"buzz-0ts.1","type":"blocks","created_at":"2026-07-30T21:29:21Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-0ts.2","depends_on_id":"buzz-0ts","type":"parent-child","created_at":"2026-07-30T21:29:18Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
+{"_type":"issue","id":"buzz-0ts.1","title":"Build buzz-notify.sh (topic -\u003e channel-uuid) + a notifier bot key","description":"A notifier bot (own key, separate from the AI Wire + goose keys) + buzz-notify.sh wrapping buzz messages send (kind:9), mapping a topic to a sys-* channel uuid. AC: a test alert posts to sys-incidents.","status":"open","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:18Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:29:18Z","dependencies":[{"issue_id":"buzz-0ts.1","depends_on_id":"buzz-0ts","type":"parent-child","created_at":"2026-07-30T21:29:18Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-0ts.1","depends_on_id":"buzz-w92.1","type":"blocks","created_at":"2026-07-30T21:29:21Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}
+{"_type":"issue","id":"buzz-0ts","title":"Route estate system notifications into the private sys-* channels","description":"Track B internal consolidation: the estate's ops alerts post into the private sys-* channels (incidents/health/backups/deploys/automation), retiring Slack/notify/Moshi. Keep the off-estate survivability floor (healthchecks.io + af_email_floor) and Ezekiel's blog emails. Inherits dedup/rate-limit/spool/floor from the governed af_dispatch chain.","status":"open","priority":2,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:17Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:29:17Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.7","title":"Curation and the daily ai-wire digest (the anti-firehose moat)","description":"The curator promotes the day's best cross-lab items into one ai-wire digest + one announcements headline; members @mention to go deeper. AC: a real daily digest posts to ai-wire.","status":"closed","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:12Z","created_by":"jeremylongshore","updated_at":"2026-07-31T04:30:55Z","closed_at":"2026-07-31T04:30:55Z","close_reason":"Daily ai-wire digest live — --digest composes cross-lab roundup (70b→8b→NVIDIA), posts to 1-ai-wire, cron 06:30 CT","dependencies":[{"issue_id":"buzz-w92.7","depends_on_id":"buzz-w92.3","type":"blocks","created_at":"2026-07-30T21:29:16Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-w92.7","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:11Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.8","title":"Schedule the pipeline via cron and register it in automations.md","description":"Cron the ingestion + digest on the dev box (estate automation home), armed with notify-lib liveness markers, registered in mission-control/automations.md. AC: cron live + registry row + liveness sweep row.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:12Z","created_by":"jeremylongshore","updated_at":"2026-07-31T04:18:42Z","closed_at":"2026-07-31T04:18:42Z","close_reason":"3h cron deployed (~/bin/buzz-wire-cron.sh) + notify-lib liveness + registered in automations.md","dependencies":[{"issue_id":"buzz-w92.8","depends_on_id":"buzz-w92.3","type":"blocks","created_at":"2026-07-30T21:29:16Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-w92.8","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:12Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.4","title":"Wire the existing Anthropic monitor fleet into the anthropic-* channels","description":"The dev-box ~/bin monitors (claude-code-changelog, claude-blog, anthropic-news/research/engineering, anthropic-status) already summarize NEW items to Slack; add a Buzz post path -\u003e anthropic-changelog / anthropic-blogs / anthropic-research / anthropic-outages. AC: a real Anthropic release lands in anthropic-changelog.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:09Z","created_by":"jeremylongshore","updated_at":"2026-07-31T14:04:52Z","closed_at":"2026-07-31T14:04:52Z","close_reason":"Anthropic bridged via HTML-scrape source type (news/research/engineering -\u003e anthropic-blogs/research), mirroring the estate monitors; Mistral added. buzz-w92.6 partially done (tooling/mcp/newsletters/ships/status live).","dependencies":[{"issue_id":"buzz-w92.4","depends_on_id":"buzz-w92.1","type":"blocks","created_at":"2026-07-30T21:29:14Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-w92.4","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:09Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.3","title":"Ship the easy-tier provider feeds (clean RSS/status)","description":"Anthropic, OpenAI, Google/DeepMind, Groq, DeepSeek, MiniMax -\u003e their *-wire channels. AC: each posts real items on a schedule.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:08Z","created_by":"jeremylongshore","updated_at":"2026-07-31T04:18:40Z","closed_at":"2026-07-31T04:18:40Z","close_reason":"easy-tier feeds live — OpenAI, HuggingFace, arXiv cs.AI, Google DeepMind; 4 seed cards posted with summaries","dependencies":[{"issue_id":"buzz-w92.3","depends_on_id":"buzz-w92.2","type":"blocks","created_at":"2026-07-30T21:29:13Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-w92.3","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:08Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":4,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe.6","title":"Build the buzz-relay-admin agent (membership, keys, backups, health)","description":"A /agent-creator agent for relay-admin ops (buzz-admin add/remove-member, keygen, backup/restore, health). AC: passes /validate-agent; encodes host access, buzz-admin-in-relay-container, backup/restore runbooks.","status":"closed","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:17Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:36:21Z","closed_at":"2026-07-31T03:36:21Z","close_reason":"buzz-relay-admin agent built + PASS /validate-agent","dependencies":[{"issue_id":"buzz-yfe.6","depends_on_id":"buzz-yfe.3","type":"blocks","created_at":"2026-07-30T21:29:03Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.6","depends_on_id":"buzz-yfe","type":"parent-child","created_at":"2026-07-30T21:27:16Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe.7","title":"Validate the skill and all operator agents against the IS spec","description":"Run /validate-skillmd + /validate-agent on the skill and 3 agents; fix findings. AC: all green.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:17Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:36:22Z","closed_at":"2026-07-31T03:36:22Z","close_reason":"all 3 agents PASS /validate-agent; skill PASS /validate-skillmd; reference-vs-code consistency 106 citations 100% resolve to real fork source (no fabrications)","dependencies":[{"issue_id":"buzz-yfe.7","depends_on_id":"buzz-yfe.6","type":"blocks","created_at":"2026-07-30T21:29:05Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.7","depends_on_id":"buzz-yfe.4","type":"blocks","created_at":"2026-07-30T21:29:03Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.7","depends_on_id":"buzz-yfe","type":"parent-child","created_at":"2026-07-30T21:27:17Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.7","depends_on_id":"buzz-yfe.5","type":"blocks","created_at":"2026-07-30T21:29:04Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":3,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe.5","title":"Build the buzz-feed-curator agent (AI-Wire posting)","description":"A /agent-creator agent that fetches feeds, summarizes, and posts rich cards to *-wire channels as the AI Wire bot. AC: passes /validate-agent; encodes the posting primitive, provider-first routing, membership requirement.","status":"closed","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:16Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:36:19Z","closed_at":"2026-07-31T03:36:19Z","close_reason":"buzz-feed-curator agent built + PASS /validate-agent","dependencies":[{"issue_id":"buzz-yfe.5","depends_on_id":"buzz-yfe.3","type":"blocks","created_at":"2026-07-30T21:29:02Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.5","depends_on_id":"buzz-yfe","type":"parent-child","created_at":"2026-07-30T21:27:15Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":3,"comment_count":0}
+{"_type":"issue","id":"buzz-yfe.4","title":"Build the buzz-ops operator agent (channel + identity management)","description":"A /agent-creator agent that manages channels + identities on the live relay per the runbook. AC: passes /validate-agent (8-field spec); encodes owner-key ownership, sanctioned CLI, secret-via-docker-exec-e, verify-via-channels-list.","status":"closed","priority":2,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:15Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:36:18Z","closed_at":"2026-07-31T03:36:18Z","close_reason":"buzz-ops agent built + PASS /validate-agent (14-field standalone spec)","dependencies":[{"issue_id":"buzz-yfe.4","depends_on_id":"buzz-yfe.3","type":"blocks","created_at":"2026-07-30T21:29:00Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-yfe.4","depends_on_id":"buzz-yfe","type":"parent-child","created_at":"2026-07-30T21:27:14Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}
+{"_type":"issue","id":"buzz-ehv.2","title":"Define and document the member join/onboarding model","description":"For the shared community: members JOIN the canonical open channels (they never template-create their own — templates make NEW channels and cause the duplicate-Welcome mess; see RUNBOOK-channels.md 1.6). AC: document which channels auto-join at onboarding vs opt-in, how a member joins an open *-wire pack, and the seed-content-before-invite step. Feeds the all-in onboarding epic (E5).","status":"open","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:11Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:27:11Z","dependencies":[{"issue_id":"buzz-ehv.2","depends_on_id":"buzz-ehv","type":"parent-child","created_at":"2026-07-30T21:27:10Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-bsy","title":"Build the Buzz three-store restore tool (restore.sh) and its restore drill","description":"The wrapped updater's ordered store-restore on a bad-migration revert calls ops/buzz/scripts/restore.sh (--recovery-point \u003cid\u003e --env \u003cenv\u003e) in intent-os. Until it exists+drilled, the updater fail-closes to exit 5 (manual page) rather than faking a DB rollback. Build restore.sh (pg_dump -Fc restore + MinIO media + git volume from a bound recovery point, dangling-ref walk) + a restore drill, so bad-migration recovery is fully unattended. Parent: wrapped-updater lane (buzz-ocv.4).","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T20:13:56Z","created_by":"jeremylongshore","updated_at":"2026-07-30T05:54:52Z","closed_at":"2026-07-30T05:54:52Z","close_reason":"DONE 2026-07-30 (ultracode Track-A workflow). Built ops/buzz/scripts/restore.sh — the real destructive three-store restore (Postgres pg_restore --clean --if-exists → media mc mirror --overwrite → git volume), gated behind --in-place with verify-before-destroy (sha256 every artifact + PGDMP header + manifest.env==--env cross-check; exit 2 writes nothing). Chose --in-place-required over auto-restore so decision-log/038's human-gate holds with ZERO updater.sh changes: the updater's flagless call exits 64 → rc5 page unchanged (updater-drill 6/6 still green). Hermetic restore-test.sh 6/6 with mutation-proven teeth; restore-drill-test 4/4 no regression; wired into ci:drills. Merged intent-os PR #293 (4/4 estate-CI green). Remaining real-tool proof = a host-side scratch-stack drill before first prod use (noted in runbook).","labels":["backup","restore","updater"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-ocv.6","title":"Add the mobile pairing sidecar overlay and validate a real desktop-to-mobile pairing","description":"Track E2 (external infra review; upstream #2734 / PR #2736): the first-party compose bundle lacks the buzz-pair-relay sidecar, so Android members cannot pair — and the all-in onboarding wave includes Android. Review the pinned image for the buzz-pair-relay entrypoint, add an estate overlay service (same image, entrypoint buzz-pair-relay, internal :5000, healthcheck) + a Caddy /pair* route. Claim it works only after a REAL desktop-to-mobile pairing succeeds; if wave 1 doesn't require it, document the limitation honestly instead.","notes":"PAIRING BLOCKED BY IMAGE (found 2026-07-30 during a live prod attempt): the pinned relay image ghcr.io/block/buzz@sha256:a0f672... (relay-v0.2.0) contains ONLY buzz-admin + buzz-relay in /usr/local/bin — the buzz-pair-relay binary is NOT in it (added in a NEWER buzz build; the Dockerfile builds it but our pinned digest predates/excludes it). So the pairing overlay's entrypoint 'buzz-pair-relay' -\u003e 'executable file not found'. NEAR-MISS: 'docker compose up -d' with the overlay recreated the relay (overlay patches relay env for BUZZ_PAIRING_RELAY_URL) AND the sidecar failed to start, which took the relay DOWN briefly; reverted (removed COMPOSE_FILE + overlay, base up) -\u003e relay healthy, prod restored. LESSONS: (1) pairing REQUIRES promoting a newer buzz image that ships buzz-pair-relay — do it via the drilled wrapped-updater (snapshot-\u003eprobe-\u003erevert), never a raw compose up on the live relay; (2) the overlay must PROBE the image for /usr/local/bin/buzz-pair-relay before starting, and bring up ONLY the sidecar service first (never recreate the relay on sidecar failure); (3) hermetic pairing-overlay-test passed because it mocked the binary — add a real-image binary-presence check. The relay DID advertise BUZZ_PAIRING_RELAY_URL correctly (app generated a valid nostrpair:// deep-link) — only the sidecar endpoint was missing.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:30:45Z","created_by":"jeremylongshore","updated_at":"2026-07-30T04:47:02Z","started_at":"2026-07-29T22:43:42Z","closed_at":"2026-07-30T04:47:02Z","close_reason":"DONE 2026-07-30. Mobile pairing sidecar deployed to prod + a REAL device paired (owner-confirmed handshake). Root cause: pinned relay-v0.2.0 image lacks buzz-pair-relay; fix pins the sidecar SEPARATELY via BUZZ_PAIR_RELAY_IMAGE (:main @9de8aff1) + absolute entrypoint, deployed sidecar-first (relay untouched), Caddy /pair route added. Verified wss://buzz-prod.intentsolutions.io/pair -\u003e 101 Switching Protocols (Caddy-\u003esidecar); sidecar logs show the live paired connection. intent-os PR #291 (overlay + test image-guard + runbook; 4/4 CI green). pairing-overlay-test now guards the image bug (5/5).","labels":["clients","hosting"],"dependencies":[{"issue_id":"buzz-ocv.6","depends_on_id":"buzz-ocv","type":"parent-child","created_at":"2026-07-29T10:30:45Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-ocv.5","title":"Set the Tauri desktop origins in the relay CORS config and verify with the packaged desktop client","description":"Track E1 (external infra review, verified vs upstream #3490): current env allows only https://the prod domain as an origin; packaged Tauri desktop clients present tauri://localhost (macOS/Linux) or http://tauri.localhost (Windows), so desktop join is BLOCKED. Verify the pinned image's CORS env handling, add the two Tauri origins (no permissive-CORS shortcuts), restart, confirm relay healthy + correct Access-Control-Allow-Origin echo. Final verification is a join from the PACKAGED desktop client on the owner's workstation, not a dev build.","notes":"CORS CONFIG FIXED (intent-os PR #283, merged via estate-CI 3 green). BUZZ_CORS_ORIGINS composed in version-controlled compose from required BUZZ_DOMAIN + tauri://localhost + http://tauri.localhost (environment: overrides env_file; non-secret Tauri origins out of sops). Closes the desktop-join gap AND a latent permissive-CORS gap (relay goes permissive only when the var is EMPTY; required BUZZ_DOMAIN makes empty impossible). Proof: ops/buzz/tests/cors-config-test.sh 2/2, wired into ci:drills. REMAINING (owner-gated): apply on staging (docker compose up -d relay) + verify a real join with the PACKAGED desktop client on Jeremy's workstation (dev build won't exercise Tauri origins).\nCORS APPLIED LIVE to BOTH hosts 2026-07-29 (intent-os PR #284 records; compose applied with backup + config-gate + relay recreate). Staging BUZZ_CORS_ORIGINS=https://the prod domain,tauri://localhost,http://tauri.localhost ; prod=…buzz-prod…,tauri…; relay healthy + _readiness 200 on each. Desktop-join unblocked on the live boxes. ONLY remaining: packaged-desktop-client verify on Jeremy's workstation (owner-gated).","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T16:30:36Z","created_by":"jeremylongshore","updated_at":"2026-07-30T04:47:05Z","started_at":"2026-07-29T21:16:21Z","closed_at":"2026-07-30T04:47:05Z","close_reason":"DONE 2026-07-30. Tauri desktop CORS origins (tauri://localhost + http://tauri.localhost) composed into version-controlled compose (intent-os PR #283), applied live to both hosts (PR #284), cors-config-test 2/2 in ci:drills. Final owner-gated gate met: Jeremy joined from the PACKAGED Linux desktop client to buzz-prod.intentsolutions.io (connected, admin, channels visible) — the packaged-client CORS verification the acceptance required.","labels":["clients","hosting"],"dependencies":[{"issue_id":"buzz-ocv.5","depends_on_id":"buzz-ocv","type":"parent-child","created_at":"2026-07-29T10:30:35Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-ocv.4","title":"Wire backups and the wrapped updater, register everything in the estate","description":"nightly pg_dump artifact into the borg set + three-store recovery point; weekly updater snapshot-promote-probe-revert; automations.md + catalog rows; E3 drill gates filed","notes":"Track E additions (external infra review 2026-07-29): the wrapped updater's promotion checklist MUST include — release-notes review; a staging boot; a probe-hang check (upstream #2723/PR #2724 — git conformance probe can wedge startup; our pinned digest boots healthy with BUZZ_GIT_CONFORMANCE_PROBE=true, but a future image must prove it on staging before promotion); a CORS/Tauri-origin regression check; a pairing regression check. Also confirmed: upstream's own 'backup' command is a checklist, not a backup — our pg_dump + three-store recovery-point design stands; off-site copy rides the estate B2/borg chain.\nBackup automation BUILT + RAN (evidence): scripts/backup.sh on prod produced encrypted borg archive intent-ops-buzz-prod-2026-07-29T183641Z (pg_dump 167KB schema-head-6 + media(6) + git + identity + manifest w/ sha256s); flock, retention 14/8/6, freshness marker, Slack notify (interim — Buzz replaces Slack post-migration), nonzero-on-fail. systemd buzz-backup.timer enabled (daily 04:20 UTC). Runbook ops/buzz/RUNBOOK-backup-restore.md. STILL OPEN: off-site B2 push (owner-gated estate-wide, Epic 1.1 B2 not provisioned) + borg repokey export off-box + Netdata backup-age alert + the wrapped updater.\nGIT CONFORMANCE PROBE risk (Priority 4) — evidence-based decision: pinned image = v0.2.0 / source rev 0d9be2f (built 2026-07-10). Upstream fix PR #2724 is still OPEN (NOT merged) -\u003e the bounded-timeout fix is NOT in our image. BUT fault test on a scratch relay (MinIO unreachable + BUZZ_GIT_CONFORMANCE_PROBE=true): the probe ran ('git object-store conformance probe A3 gate') and the relay EXITED with 's3 backend error' after ~63s (bounded by reqwest timeout) — it FAILS CLOSED, does not hang indefinitely. DECISION: KEEP the probe ON (real consistency gate; compose gates minio-healthy-before-relay; fails-closed not hangs), and MANDATE a deploy-wrapper HARD TIMEOUT (Priority 3) that bounds the relay-healthy wait + auto-reverts regardless of app behaviour — this covers the untested 'accepted-but-nonresponsive' case. Removal-of-risk condition: promote a digest containing merged #2724. Untested residual: MinIO-nonresponsive half-open (harder to reproduce safely).\nWrapped-updater half SHIPPED (intent-os PR #282, merged via estate-CI 3 green: gates 127s / drills 220s / gitleaks). Built ops/buzz/scripts/{updater,functional-probe,_repin-and-start}.sh over the drilled estate watchtower-gate/deploy-wrapper; hermetic planted-fault drill ops/buzz/tests/updater-drill.sh 6/6 (clean promote, bad-release auto-revert+store-restore+re-probe, hard-timeout hang guard rc124, scan-hold fail-closed, snapshot-abort, absent-restore manual page); systemd buzz-updater.{service,timer} (weekly Sun 05:30 UTC); runbook RUNBOOK-wrapped-updater.md; registered backup+updater timers in mission-control/automations.md; drill wired into ci:drills. STILL OPEN on .4: install the timer on the intent-ops-buzz production host + first real run; restore.sh three-store tool (follow-up bead filed). Real functional-probe-against-live-staging is buzz-ocv.3.\nUpdater lane DEPLOYED to staging (dormant) via install-updater-lane.sh (intent-os PR #284): 3 buzz scripts + 2 estate deps (watchtower-gate/deploy-wrapper -\u003e /srv/buzz/lib/) + systemd units; daemon-reload OK, timer disabled/inactive. Fixed container-name (buzz-relay-1) + governed-notify + systemd env wiring from live state. Prod updater install + timer-enable gated on the real staging functional-probe rehearsal (blocked on installing nak — new bead).\nGate TOOLING built + merged (intent-os PR #287, CI + hermetic tests, owner-authorized merge — reviewers unavailable): monitoring (outside-in-buzz control + backup-age watchdog 3/3), key-incident runbooks (7/7), smoke-suite + unauth-matrix (4/4), all in ci:drills. REMAINING = live runs: functional probe + unauth matrix against live prod off-network; install backup-age timer + outside-in probe; rehearse key runbooks. Parallel-authored by 3 subagents.","status":"in_progress","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T15:47:20Z","created_by":"jeremylongshore","updated_at":"2026-07-29T23:57:52Z","started_at":"2026-07-29T18:34:08Z","labels":["hosting"],"dependencies":[{"issue_id":"buzz-ocv.4","depends_on_id":"buzz-ocv","type":"parent-child","created_at":"2026-07-29T09:47:20Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-ocv.3","title":"Prove the deployment: smokes, functional membership probe, and the off-network unauth probe matrix","description":"/health + /_readiness + NIP-11; nak NIP-42 member publish/readback + un-invited refusal; media PUT / git packs / hooks endpoints all 401-403","notes":"Gate TOOLING built + merged (intent-os PR #287, CI + hermetic tests, owner-authorized merge — reviewers unavailable): monitoring (outside-in-buzz control + backup-age watchdog 3/3), key-incident runbooks (7/7), smoke-suite + unauth-matrix (4/4), all in ci:drills. REMAINING = live runs: functional probe + unauth matrix against live prod off-network; install backup-age timer + outside-in probe; rehearse key runbooks. Parallel-authored by 3 subagents.\nFunctional membership probe + smoke suite + unauth matrix PROVEN GREEN on live staging (PR #288). Prod runs remain (buzz-2i6).","status":"open","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T15:47:19Z","created_by":"jeremylongshore","updated_at":"2026-07-30T00:37:48Z","labels":["hosting"],"dependencies":[{"issue_id":"buzz-ocv.3","depends_on_id":"buzz-ocv","type":"parent-child","created_at":"2026-07-29T09:47:18Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"buzz-ocv.2","title":"Wire the estate Caddy ingress with the compensating edge controls","description":"site block + WS passthrough, body-size caps, timeouts, security headers; caddy validate + reload","status":"open","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T15:47:18Z","created_by":"jeremylongshore","updated_at":"2026-07-29T15:47:18Z","labels":["hosting"],"dependencies":[{"issue_id":"buzz-ocv.2","depends_on_id":"buzz-ocv","type":"parent-child","created_at":"2026-07-29T09:47:17Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-ocv.2","title":"Wire the estate Caddy ingress with the compensating edge controls","description":"site block + WS passthrough, body-size caps, timeouts, security headers; caddy validate + reload","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T15:47:18Z","created_by":"jeremylongshore","updated_at":"2026-07-30T05:45:54Z","closed_at":"2026-07-30T05:45:54Z","close_reason":"DONE 2026-07-30 (ultracode Track-A workflow). Compensating edge controls shipped + applied. CODE: ops/buzz/scripts/apply-edge-controls.sh (plan-only default, --apply to install) + hermetic test, merged intent-os PR #292 (4/4 estate-CI green, adversarially verified sound). APPLIED LIVE on the dedicated prod host intent-ops-buzz: fail2ban 'caddy-buzz' jail (bans public 4xx-flooders from /var/log/caddy/buzz-access.log) + nftables 'inet buzz_edge' connlimit (128 concurrent NEW/IP on tcp 80+443 only) + buzz-edge-nft.service enabled (reboot-safe). VERIFIED no self-lock: SSH intact, relay _readiness 200 off-box, tailnet/port-22 never touched (WS/established never dropped). SCOPING: applied to the Buzz-DEDICATED prod host only; deliberately NOT applied host-wide on the shared VPS staging (its connlimit would rate-limit co-tenant estate stacks Plane/ERP/CRM) — request_body caps + security headers already exist in Caddy on both. Closes the last non-owner-gated piece of go-live edge hardening.","labels":["hosting"],"dependencies":[{"issue_id":"buzz-ocv.2","depends_on_id":"buzz-ocv","type":"parent-child","created_at":"2026-07-29T09:47:17Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-ocv.1","title":"Provision DNS, secrets, and the /srv/buzz compose stack, booted closed from first start","description":"dns + sops env (relay identity key, bootstrap op owner key, HMAC, db/redis/minio creds) + estate-owned compose with digest-pinned image, loopback publish, resource caps","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T15:47:17Z","created_by":"jeremylongshore","updated_at":"2026-07-29T15:52:10Z","closed_at":"2026-07-29T15:52:10Z","close_reason":"the prod domain A record live (Porkbun lane); secrets generated (relay identity + bootstrap op owner + HMAC + db/redis/minio) deployed as /srv/buzz/.env mode 600 with sops master in intent-os ops/buzz/secrets/buzz.prod.env.sops (dev+VPS age recipients); estate-owned compose (digest-pinned image, loopback publish 3004/3084, mem/cpus/pids caps, own bridge net) deployed; all 4 containers healthy; _readiness 200; NIP-11 serving; closed-relay env from first boot","labels":["hosting"],"dependencies":[{"issue_id":"buzz-ocv.1","depends_on_id":"buzz-ocv","type":"parent-child","created_at":"2026-07-29T09:47:16Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"buzz-ocv","title":"Stand up the closed Buzz relay stack on the estate VPS behind the wrapped update lane","description":"RE-SCOPED 2026-07-29 (owner topology decision, fork 000-docs/005): this stack on the SHARED estate VPS is STAGING, permanently — same compose, same gates; restore drills and updater planted-fault drills run here, never on prod. Production moves to a DEDICATED VPS (Track D epic). Original scope: /srv/buzz compose stack (relay pinned by digest, Postgres 17, Redis 7, MinIO interim) closed from first boot, sops-held secrets, loopback publish behind estate Caddy, resource caps, DNS, smokes + unauth probe matrix, pg_dump backup wiring, wrapped updater. At cutover this env renames to the staging domain. E3 go-live gates run against PROD on the dedicated box before any invite.","notes":"GitHub: intent-solutions-io/buzz#8 — https://github.com/intent-solutions-io/buzz/issues/8. MIRROR RULE: bd-sync handles fan-out — `bd-sync note buzz-ocv` and `bd-sync close buzz-ocv` mirror to GH and Plane automatically.\n\nPlane: BUZZ-2 — (Plane, internal)\nTOPOLOGY RE-SCOPE (owner decision 2026-07-29, recorded as fork 000-docs/005): this shared-VPS stack is re-designated permanent STAGING — same compose, same gates; restore drills and updater planted-fault drills run here, never on prod. Production moves to a dedicated VPS (epic buzz-nry / GH #9 / Plane BUZZ-3). At cutover this env renames to the staging domain; staging keys never promote to prod. New Track-E children under this epic: .5 Tauri/CORS desktop-join fix, .6 mobile pairing sidecar overlay; updater promotion-checklist additions noted on .4.\nSession 2026-07-30: two children COMPLETE — .6 mobile pairing (real device paired; sidecar via BUZZ_PAIR_RELAY_IMAGE + Caddy /pair; intent-os PR #291) and .5 Tauri CORS (packaged Linux desktop client joined prod). Remaining under this staging epic: .2 (Caddy edge controls), .3 (smokes+unauth matrix), .4 (backups+updater register).","status":"in_progress","priority":2,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T15:47:04Z","created_by":"jeremylongshore","updated_at":"2026-07-30T04:47:23Z","started_at":"2026-07-29T15:47:21Z","labels":["hosting"],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-ocv","title":"Stand up the closed Buzz relay stack on the estate VPS behind the wrapped update lane","description":"RE-SCOPED 2026-07-29 (owner topology decision, fork 000-docs/005): this stack on the SHARED estate VPS is STAGING, permanently — same compose, same gates; restore drills and updater planted-fault drills run here, never on prod. Production moves to a DEDICATED VPS (Track D epic). Original scope: /srv/buzz compose stack (relay pinned by digest, Postgres 17, Redis 7, MinIO interim) closed from first boot, sops-held secrets, loopback publish behind estate Caddy, resource caps, DNS, smokes + unauth probe matrix, pg_dump backup wiring, wrapped updater. At cutover this env renames to the staging domain. E3 go-live gates run against PROD on the dedicated box before any invite.","notes":"GitHub: intent-solutions-io/buzz#8 — https://github.com/intent-solutions-io/buzz/issues/8. MIRROR RULE: bd-sync handles fan-out — `bd-sync note buzz-ocv` and `bd-sync close buzz-ocv` mirror to GH and Plane automatically.\n\nPlane: BUZZ-2 — (Plane, internal)\nTOPOLOGY RE-SCOPE (owner decision 2026-07-29, recorded as fork 000-docs/005): this shared-VPS stack is re-designated permanent STAGING — same compose, same gates; restore drills and updater planted-fault drills run here, never on prod. Production moves to a dedicated VPS (epic buzz-nry / GH #9 / Plane BUZZ-3). At cutover this env renames to the staging domain; staging keys never promote to prod. New Track-E children under this epic: .5 Tauri/CORS desktop-join fix, .6 mobile pairing sidecar overlay; updater promotion-checklist additions noted on .4.\nSession 2026-07-30: two children COMPLETE — .6 mobile pairing (real device paired; sidecar via BUZZ_PAIR_RELAY_IMAGE + Caddy /pair; intent-os PR #291) and .5 Tauri CORS (packaged Linux desktop client joined prod). Remaining under this staging epic: .2 (Caddy edge controls), .3 (smokes+unauth matrix), .4 (backups+updater register).\nTrack-A ultracode session 2026-07-30: child .2 (edge controls) CLOSED — fail2ban + nftables connlimit applied live on the prod host, verified no self-lock (intent-os PR #292). Remaining children: .3 (staging probe matrix), .4 (backups+updater register).","status":"in_progress","priority":2,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T15:47:04Z","created_by":"jeremylongshore","updated_at":"2026-07-30T05:46:37Z","started_at":"2026-07-29T15:47:21Z","labels":["hosting"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-4ei.4","title":"Mirror the epic three-way and seed repo and estate memory","description":"GitHub cluster issue on intent-solutions-io/buzz + Plane BUZZ project via bd-sync link; auto-memory for the buzz working dir; estate memory buzz-adoption-state; private ops/buzz/README.md operator authority.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T04:24:21Z","created_by":"jeremylongshore","updated_at":"2026-07-29T04:36:11Z","closed_at":"2026-07-29T04:36:11Z","close_reason":"Three-way mirror live: buzz-4ei linked via bd-sync to intent-solutions-io/buzz#1 + Plane BUZZ-1 (comments fanned out); Plane project BUZZ created; estate anchor spine-8fl linked to the same pair; memories seeded (buzz working-dir auto-memory + intent-os buzz-adoption-state + ops/buzz/README.md operator authority)","labels":["bead-tooling"],"dependencies":[{"issue_id":"buzz-4ei.4","depends_on_id":"buzz-4ei","type":"parent-child","created_at":"2026-07-28T22:24:21Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-4ei.3","title":"Implement the missing test layers with the in-repo audit harness, Layer 1 git hooks required","description":"/implement-tests for gaps found by the baseline audit; in-repo enforcement only (cargo/vendored harness), additive files only, staged for review.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T04:24:20Z","created_by":"jeremylongshore","updated_at":"2026-07-29T06:21:36Z","closed_at":"2026-07-29T06:21:36Z","close_reason":"Merged in PR #6 (fa14f3f16): scripts/fork-gates/{additive-only,must-survive} + vendored audit-harness + 10-file hash-pin manifest + tracked lefthook-local.yml (escape-scan pre-commit; gates+verify pre-push) + decision record 000-docs/004. All four gates green on the merged tree; zero upstream-path edits","labels":["test-hygiene"],"dependencies":[{"issue_id":"buzz-4ei.3","depends_on_id":"buzz-4ei","type":"parent-child","created_at":"2026-07-28T22:24:20Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-4ei.2","title":"Run the testing-SOP baseline audit and produce TEST_AUDIT.md against the 7-layer taxonomy","description":"Diagnostic only: classify the fork, map upstream's suite (just test-unit / docker integration / e2e via buzz-test-client) to the 7 layers, write TEST_AUDIT.md. Staged, not auto-committed.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T04:24:19Z","created_by":"jeremylongshore","updated_at":"2026-07-29T04:26:01Z","closed_at":"2026-07-29T04:26:01Z","close_reason":"TEST_AUDIT.md written (diagnostic only): upstream suite strong across all 7 layers (~5700 Rust test fns, lefthook L1, clippy/biome/cargo-deny L2, Playwright E2E); fork-lane gaps = additive-only invariant gate + in-repo harness, handed to buzz-4ei.3","labels":["test-hygiene"],"dependencies":[{"issue_id":"buzz-4ei.2","depends_on_id":"buzz-4ei","type":"parent-child","created_at":"2026-07-28T22:24:19Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-4ei.1","title":"Author the master blueprint, decision record, deploy posture, and FORK.md as additive-only artifacts","description":"000-docs/{000-INDEX,001-PP-PLAN,002-DR-DECR,003-OD-DEPL} + FORK.md; zero upstream-path edits.","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T04:24:18Z","created_by":"jeremylongshore","updated_at":"2026-07-29T04:26:00Z","closed_at":"2026-07-29T04:26:00Z","close_reason":"Authored additive-only: 000-docs/{000-INDEX,001-PP-PLAN blueprint,002-DR-DECR decision record,003-OD-DEPL deploy posture} + FORK.md; git diff upstream/main --stat shows only fork-added paths","labels":["fork-infra"],"dependencies":[{"issue_id":"buzz-4ei.1","depends_on_id":"buzz-4ei","type":"parent-child","created_at":"2026-07-28T22:24:18Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-4ei","title":"Stand up the Intent Solutions fork infrastructure for the Buzz adoption","description":"Phase 1 / E1 of 000-docs/001-PP-PLAN-buzz-adoption-master-blueprint.md: fork + clone + beads/Dolt activation, master blueprint + decision record + deploy posture + FORK.md, testing-SOP baseline (TEST_AUDIT.md), three-way mirror (GitHub cluster issue + Plane BUZZ) and memory seeding. Additive-only: zero upstream-path edits.","notes":"GitHub: intent-solutions-io/buzz#1 — https://github.com/intent-solutions-io/buzz/issues/1. MIRROR RULE: bd-sync handles fan-out — `bd-sync note buzz-4ei` and `bd-sync close buzz-4ei` mirror to GH and Plane automatically.\n\nPlane: BUZZ-1 — (Plane, internal)\nE1 milestone: blueprint set + FORK.md + TEST_AUDIT.md authored (buzz-4ei.1, .2 closed); beads write-path root-caused (bd contributor-role fork routing) and fixed via --role maintainer; PR opening next. Note: epic was re-minted from buzz-ekg to buzz-4ei during the store rebuild.\nE1 shipped: PR intent-solutions-io/buzz#2 squash-merged to fork main (6cf4df96f) after CodeRabbit review (4 findings fixed in e49ed40a6, 2 declined with reasons). Children .1/.2/.4 closed with evidence; .3 (fork-lane test layers) remains open — epic stays in_progress until it lands. Deferred: epic-boundary Dolt tag until dolt-mcp-vcs is available (system dolt CLI is version-skewed vs bd's embedded engine).","status":"closed","priority":2,"issue_type":"epic","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T04:24:17Z","created_by":"jeremylongshore","updated_at":"2026-07-29T06:21:39Z","started_at":"2026-07-29T04:25:59Z","closed_at":"2026-07-29T06:21:39Z","close_reason":"E1 complete: all 4 children closed with evidence (PRs #2/#3/#5/#6 merged; blueprint ledger E1=COMPLETE; GH cluster #1 auto-closed by PR #6). Fork contract now machine-enforced. Phase 2 hosting opens per blueprint after the MX cutover.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"buzz-bkt","title":"acceptance probe","status":"closed","priority":2,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-29T04:23:48Z","created_by":"jeremylongshore","updated_at":"2026-07-29T04:23:49Z","started_at":"2026-07-29T04:23:49Z","closed_at":"2026-07-29T04:23:49Z","close_reason":"probe","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.10","title":"Schema the goose-filed issue (finding, repro, executable acceptance, stop-conditions, blast-radius) and lint it in pnpm check (deferred)","description":"The filed issue is the program the impl agent executes — refuse to hand it an unschematized one. Adopt upstream #3928 'agent work contracts' schema when it lands. Deferred until goose triage precision is proven.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:15Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:15Z","labels":["agentic-ops","estate","github-bridge","schema"],"dependencies":[{"issue_id":"buzz-20l.10","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:14Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.10","depends_on_id":"buzz-20l.5","type":"blocks","created_at":"2026-07-31T13:58:39Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-20l.9","title":"Replay the dead reviewers' historical output as a golden eval set and MiniMax model-drift detector (deferred)","description":"Greptile/Gemini/CodeRabbit historical output on merged PRs = a free labeled dataset. Run goose/MiniMax over the same diffs to measure findings-recall + false-positive rate; use as a model-drift detector when MiniMax-M3 changes under us. Deferred until the triage lane is live.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T19:58:14Z","created_by":"jeremylongshore","updated_at":"2026-07-31T19:58:14Z","labels":["agentic-ops","estate","eval","github-bridge"],"dependencies":[{"issue_id":"buzz-20l.9","depends_on_id":"buzz-20l","type":"parent-child","created_at":"2026-07-31T13:58:13Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-20l.9","depends_on_id":"buzz-20l.5","type":"blocks","created_at":"2026-07-31T13:58:39Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-b6z.4","title":"Verify and surface the buzz-audit hash-chain (tamper-evident audit log)","description":"The buzz-audit crate keeps a per-community SHA-256 hash-chain. Add a periodic chain-verify (like the estate ico audit verify) + surface a break to sys-incidents. AC: chain-verify runs green; a planted break alerts.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:40:58Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:40:58Z","dependencies":[{"issue_id":"buzz-b6z.4","depends_on_id":"buzz-b6z","type":"parent-child","created_at":"2026-07-30T21:40:58Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-b6z.1","title":"Export relay OTel traces to SigNoz staging (set OTEL_EXPORTER_OTLP_ENDPOINT)","description":"The relay already attaches an OpenTelemetry exporter when OTEL_EXPORTER_OTLP_ENDPOINT is set (buzz-relay/src/main.rs). Point it at the estate SigNoz C8 gateway (+ OTEL_SERVICE_NAME=buzz-relay). AC: traces visible in SigNoz staging; prod untouched until verified on staging.","status":"open","priority":3,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:40:56Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:40:56Z","dependencies":[{"issue_id":"buzz-b6z.1","depends_on_id":"buzz-b6z","type":"parent-child","created_at":"2026-07-30T21:40:55Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-b6z.2","title":"Ship Buzz relay/agent logs off-box before they age out","description":"Docker json-file keeps only ~30MB (10m x3), so a crash's logs vanish. Ship to the estate log store (Loki/whatever the estate standardizes) or at minimum rotate into the borg backup. AC: relay logs survive a container recycle + are queryable.","status":"open","priority":3,"issue_type":"feature","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:40:56Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:40:56Z","dependencies":[{"issue_id":"buzz-b6z.2","depends_on_id":"buzz-b6z","type":"parent-child","created_at":"2026-07-30T21:40:56Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-1zr.4","title":"Wire the reference-vs-code citation-consistency check as a repeatable gate","description":"Promote the ad-hoc citation checker (106 citations -\u003e real fork file:line) into a committed script so BUZZ-SYSTEM-REFERENCE.md can't silently drift from block/buzz. AC: script committed + runnable; flags a broken citation.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:39:35Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:39:35Z","dependencies":[{"issue_id":"buzz-1zr.4","depends_on_id":"buzz-yfe.1","type":"blocks","created_at":"2026-07-30T21:39:37Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-1zr.4","depends_on_id":"buzz-1zr","type":"parent-child","created_at":"2026-07-30T21:39:35Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-0ts.3","title":"Wire the sys-* channels to their estate sources","description":"incidents\u003c-prod incidents; health\u003c-liveness sweeps + labs health; backups\u003c-borg/off-site/restore-drill; deploys\u003c-deploy-wrapper events; automation\u003c-cron-failures + liveness digest. AC: each sys-* channel receives its real events.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:20Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:29:20Z","dependencies":[{"issue_id":"buzz-0ts.3","depends_on_id":"buzz-0ts.2","type":"blocks","created_at":"2026-07-30T21:29:22Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-0ts.3","depends_on_id":"buzz-0ts","type":"parent-child","created_at":"2026-07-30T21:29:19Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
+{"_type":"issue","id":"buzz-0ts.4","title":"Soak, then retire Slack/notify/Moshi (keep the off-estate floor + Ezekiel email)","description":"After a soak proving Buzz alerts are reliable, retire notify.sh/notify-lib Slack paths + Moshi. KEEP: off-estate healthchecks.io + af_email_floor (survivability floor, must stay off-estate) + Ezekiel's blog posting emails. AC: Slack retired, floor intact.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:20Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:29:20Z","dependencies":[{"issue_id":"buzz-0ts.4","depends_on_id":"buzz-0ts","type":"parent-child","created_at":"2026-07-30T21:29:20Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-0ts.4","depends_on_id":"buzz-0ts.3","type":"blocks","created_at":"2026-07-30T21:29:23Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.6","title":"Add the topic feeds beyond the labs","description":"huggingface, openrouter, benchmarks, tooling, arxiv, mcp, funding, security, newsletters, ships (dogfood our own) -\u003e their *-wire channels. AC: each posts real items.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:11Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:29:11Z","dependencies":[{"issue_id":"buzz-w92.6","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:10Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-w92.6","depends_on_id":"buzz-w92.3","type":"blocks","created_at":"2026-07-30T21:29:15Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-w92.5","title":"Add the medium and hard provider feeds (RSSHub for scrape-only labs)","description":"Medium: Mistral, Together, Perplexity, Cohere. Hard (self-host RSSHub on the VPS): xAI, Meta, Moonshot/Kimi, Qwen -\u003e their *-wire. AC: RSSHub deployed; each feed posts.","status":"closed","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:29:10Z","created_by":"jeremylongshore","updated_at":"2026-07-31T15:35:29Z","closed_at":"2026-07-31T15:35:29Z","close_reason":"Filled groq/meta/mistral/cohere/perplexity/xai + cleaner anthropic via Olshansk RSS mirrors (from perception repo) — no RSSHub needed. Remaining empty (deepseek/qwen/moonshot/minimax/benchmarks/funding/openrouter) have no mirror.","dependencies":[{"issue_id":"buzz-w92.5","depends_on_id":"buzz-w92","type":"parent-child","created_at":"2026-07-30T21:29:10Z","created_by":"jeremylongshore","metadata":"{}"},{"issue_id":"buzz-w92.5","depends_on_id":"buzz-w92.3","type":"blocks","created_at":"2026-07-30T21:29:15Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"buzz-ehv.1","title":"Decide and apply the Certified-Claude-Code-Architects channel","description":"The private CCA channel is owned by Jeremy's personal key (0ace65ad), so ops cannot delete/rename it without his key. Decide: keep as-is, rename (the coined 'Claude Code Certified Architect' has trademark risk — real credential is 'Claude Certified Architect (CCA)'), or retire. Owner-gated: needs Jeremy's key or his app action. AC: decision recorded + applied.","status":"open","priority":3,"issue_type":"task","owner":"jeremylongshore@users.noreply.github.com","created_at":"2026-07-31T03:27:10Z","created_by":"jeremylongshore","updated_at":"2026-07-31T03:27:10Z","dependencies":[{"issue_id":"buzz-ehv.1","depends_on_id":"buzz-ehv","type":"parent-child","created_at":"2026-07-30T21:27:09Z","created_by":"jeremylongshore","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/.env.example b/.env.example
index 3dc54856e7..b9bfcada0e 100644
--- a/.env.example
+++ b/.env.example
@@ -82,6 +82,19 @@ RELAY_URL=ws://localhost:3000
# BUZZ_GIT_PACK_CACHE_MAX_BYTES=5368709120
# BUZZ_GIT_PACK_CACHE_MAX_CONCURRENT_POPULATIONS=2
+# -----------------------------------------------------------------------------
+# S3-Compatible Object Storage (media + Git/CAS)
+# -----------------------------------------------------------------------------
+# The local MinIO container is reachable from host processes at localhost:9000.
+# Path style keeps the bucket in the URL path and is required by this local DNS
+# setup. Use `virtual` only when the provider requires bucket-as-subdomain URLs.
+BUZZ_S3_ENDPOINT=http://localhost:9000
+BUZZ_S3_ACCESS_KEY=buzz_dev
+BUZZ_S3_SECRET_KEY=buzz_dev_secret
+BUZZ_S3_BUCKET=buzz-media
+BUZZ_S3_REGION=us-east-1
+BUZZ_S3_ADDRESSING_STYLE=path
+
# -----------------------------------------------------------------------------
# Media Upload Admission
# -----------------------------------------------------------------------------
diff --git a/.github/workflows/auto-tag-on-release-pr-merge.yml b/.github/workflows/auto-tag-on-release-pr-merge.yml
index db34fddc2c..3a090b3ebd 100644
--- a/.github/workflows/auto-tag-on-release-pr-merge.yml
+++ b/.github/workflows/auto-tag-on-release-pr-merge.yml
@@ -4,7 +4,7 @@ name: Auto-tag on Release PR Merge
# prefix; the main chart lane also auto-detects a Chart.yaml version bump so
# a chart feature PR can publish its own new version when merged:
#
-# version-bump/ → tag v → release.yml (desktop app)
+# version-bump/ → tag desktop-v → release.yml (desktop app)
# relay-release/ → tag relay-v → docker.yml (relay image)
# chart-release/ → tag chart-v → helm-chart.yml (main helm chart)
# push-chart-release/ → tag push-chart-v → push-gateway-helm-chart.yml
@@ -35,12 +35,17 @@ permissions:
jobs:
auto-tag:
+ permissions:
+ contents: read
+ pull-requests: read
+ checks: read
+ statuses: read
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 0
@@ -57,7 +62,7 @@ jobs:
case "$BRANCH" in
version-bump/*)
VERSION="${BRANCH#version-bump/}"
- TAG_PREFIX="v" ;;
+ TAG_PREFIX="desktop-v" ;;
relay-release/*)
VERSION="${BRANCH#relay-release/}"
TAG_PREFIX="relay-v" ;;
@@ -85,9 +90,33 @@ jobs:
{
echo "enabled=true"
echo "tag=${TAG_PREFIX}${VERSION}"
+ if [[ "$TAG_PREFIX" == desktop-v ]]; then
+ echo "target_sha=${{ github.event.pull_request.merge_commit_sha }}"
+ echo "desktop=true"
+ else
+ echo "target_sha=$GITHUB_SHA"
+ echo "desktop=false"
+ fi
} >> "$GITHUB_OUTPUT"
echo "Tagging ${TAG_PREFIX}${VERSION}"
+
+ - name: Verify immutable reviewed desktop candidate
+ if: steps.release.outputs.desktop == 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ VERSION: ${{ steps.release.outputs.tag }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+ PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
+ PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
+ PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+ MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
+ run: |
+ VERSION="${VERSION#desktop-v}"
+ export VERSION
+ scripts/verify-desktop-release-merge.sh
+
- name: Create release tagger token
if: steps.release.outputs.enabled == 'true'
id: release-tagger
@@ -102,21 +131,22 @@ jobs:
env:
GH_TOKEN: ${{ steps.release-tagger.outputs.token }}
TAG: ${{ steps.release.outputs.tag }}
+ TARGET_SHA: ${{ steps.release.outputs.target_sha }}
run: |
set -euo pipefail
# Check gh's exit status, not its output. A missing ref returns a 404
# JSON body on stdout, which must not be mistaken for an existing tag.
if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --silent 2>/dev/null; then
EXISTING_SHA="$(gh api "repos/$GITHUB_REPOSITORY/commits/$TAG" --jq .sha)"
- if [ "$EXISTING_SHA" = "$GITHUB_SHA" ]; then
- echo "Tag $TAG already exists at $GITHUB_SHA — skipping tag creation"
+ if [ "$EXISTING_SHA" = "$TARGET_SHA" ]; then
+ echo "Tag $TAG already exists at $TARGET_SHA — skipping tag creation"
exit 0
else
- echo "::error::Tag $TAG already exists at $EXISTING_SHA (expected $GITHUB_SHA)"
+ echo "::error::Tag $TAG already exists at $EXISTING_SHA (expected $TARGET_SHA)"
exit 1
fi
fi
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
-f ref="refs/tags/$TAG" \
- -f sha="$GITHUB_SHA" \
+ -f sha="$TARGET_SHA" \
--silent
diff --git a/.github/workflows/benchmark-harbor.yml b/.github/workflows/benchmark-harbor.yml
index 31efe933c5..6024f00575 100644
--- a/.github/workflows/benchmark-harbor.yml
+++ b/.github/workflows/benchmark-harbor.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d4826d985f..60507182d5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,7 +28,7 @@ jobs:
web: ${{ steps.filter.outputs.web }}
mobile: ${{ steps.filter.outputs.mobile }}
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 2
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
@@ -76,6 +76,8 @@ jobs:
- '.github/workflows/ci.yml'
- name: Release workflow source contract
run: scripts/test-release-ref-contract.sh
+ - name: Desktop release candidate contract
+ run: scripts/test-desktop-release-candidate.sh
- name: Mobile release contract
run: |
scripts/test-mobile-release-contract.sh
@@ -94,7 +96,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
@@ -115,7 +117,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -137,7 +139,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 2
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
@@ -233,7 +235,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- name: Get pnpm store directory
id: pnpm-cache
@@ -316,7 +318,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
# Reuse the relay binaries and backend test archive when none of their
# inputs changed (desktop-only PRs hit this every time). The key covers
@@ -389,7 +391,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- name: Start integration services
run: |
@@ -578,7 +580,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- name: Install cargo-nextest
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
@@ -690,6 +692,18 @@ jobs:
--run-ignored ignored-only
env:
DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz
+ - name: Workspace profile (kind:9033) gate tests
+ # Call-site integration for the 9033 authorization gate: open relay
+ # rosterless/steward transitions and the closed-relay admin/owner rule,
+ # against real Postgres. #[ignore]d in the default suite, selected
+ # explicitly here — see handlers::relay_admin::tests.
+ run: |
+ cargo nextest run \
+ --archive-file target/ci/backend-integration-tests.tar.zst \
+ -E 'package(buzz-relay) and test(/handlers::relay_admin::tests/)' \
+ --run-ignored ignored-only
+ env:
+ DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz
- name: NIP-ER reminder e2e
# Feature e2e for NIP-ER (Event Reminders, kind:30300): write-path
# validation, author-only read filtering, and scheduler delivery against
@@ -702,6 +716,17 @@ jobs:
--run-ignored ignored-only
env:
RELAY_URL: ws://localhost:3000
+ - name: NIP-MP coordinate deletion guard
+ # Verifies the never-delete-newer invariant of soft_delete_by_coordinate:
+ # a stale tombstone (created_at earlier than the live head) spares that
+ # head, and an equal-timestamp tombstone deletes it.
+ run: |
+ cargo nextest run \
+ --archive-file target/ci/backend-integration-tests.tar.zst \
+ -E 'package(buzz-db) and test(coordinate_delete_spares_head_newer_than_the_deletion)' \
+ --run-ignored ignored-only
+ env:
+ DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz
- name: Upload relay log
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
@@ -719,7 +744,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
@@ -737,7 +762,7 @@ jobs:
./scripts/start-relay-for-tests.sh --no-build
- name: Relay E2E tests
run: |
- cargo test -p buzz-test-client --test e2e_persona --test e2e_nostr_interop -- --ignored --nocapture
+ cargo test -p buzz-test-client --test e2e_persona --test e2e_team_catalog --test e2e_nostr_interop --test e2e_project -- --ignored --nocapture
cargo test -p buzz-test-client --test e2e_relay invite -- --ignored --nocapture
cargo test -p buzz-test-client --test e2e_relay nip43_membership_snapshots_are_rejected -- --ignored --nocapture
env:
@@ -760,7 +785,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 2
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
@@ -795,7 +820,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 2
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
@@ -856,7 +881,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- name: Dependency policy
run: cargo-deny check
@@ -868,7 +893,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Check for dead API token references in client code
run: |
# Fail if dead API token patterns reappear in desktop, mobile, docs, or config.
@@ -897,7 +922,7 @@ jobs:
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-musl
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
@@ -934,7 +959,7 @@ jobs:
env:
TARGET: x86_64-pc-windows-msvc
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# MSVC needs windows.h (aws-lc-sys et al.), so this runs on a real Windows
# runner — hermit, used by the Linux jobs, does not provide MSVC. The
# toolchain (1.95.0 + clippy via profile = default) comes from the
@@ -1015,7 +1040,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
@@ -1029,6 +1054,7 @@ jobs:
mkdir -p desktop/src-tauri/binaries
touch "desktop/src-tauri/binaries/buzz-acp-$TARGET"
touch "desktop/src-tauri/binaries/buzz-agent-$TARGET"
+ touch "desktop/src-tauri/binaries/buzz-backend-kubernetes-$TARGET"
touch "desktop/src-tauri/binaries/buzz-dev-mcp-$TARGET"
touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET"
touch "desktop/src-tauri/binaries/buzz-$TARGET"
diff --git a/.github/workflows/desktop-release-candidate.yml b/.github/workflows/desktop-release-candidate.yml
new file mode 100644
index 0000000000..eddebea685
--- /dev/null
+++ b/.github/workflows/desktop-release-candidate.yml
@@ -0,0 +1,26 @@
+name: Desktop Release Candidate
+
+on:
+ pull_request:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ validate:
+ name: Desktop Release Candidate
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+ persist-credentials: false
+ - name: Validate immutable desktop candidate
+ if: startsWith(github.event.pull_request.head.ref, 'version-bump/')
+ env:
+ VERSION: ${{ github.event.pull_request.head.ref }}
+ run: |
+ VERSION="${VERSION#version-bump/}"
+ scripts/desktop_release.py validate --candidate HEAD --version "$VERSION" --repo "$GITHUB_REPOSITORY"
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 52f21b28bc..564cd74e9d 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -101,7 +101,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
@@ -359,7 +359,7 @@ jobs:
arch: arm64
steps:
- name: Checkout
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
diff --git a/.github/workflows/helm-chart.yml b/.github/workflows/helm-chart.yml
index e3d443d9f3..7118d16708 100644
--- a/.github/workflows/helm-chart.yml
+++ b/.github/workflows/helm-chart.yml
@@ -59,7 +59,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
# On chart-tag rescue dispatch, lint/render the tagged commit that the
# publish job will package, not whatever `main` is when the dispatch
@@ -119,7 +119,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
@@ -166,7 +166,7 @@ jobs:
packages: write # push the chart to GHCR
steps:
- name: Checkout
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
# On the rescue dispatch, build the tagged commit (github.ref is
# `main` there); on a tag push, the default ref is already the tag.
diff --git a/.github/workflows/linux-canary.yml b/.github/workflows/linux-canary.yml
index 18d476e400..1664878770 100644
--- a/.github/workflows/linux-canary.yml
+++ b/.github/workflows/linux-canary.yml
@@ -21,7 +21,7 @@ jobs:
name: Build Linux canary
if: github.repository == 'block/buzz'
runs-on: ubuntu-latest
- container: ubuntu:22.04@sha256:0e0a0fc6d18feda9db1590da249ac93e8d5abfea8f4c3c0c849ce512b5ef8982
+ container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
timeout-minutes: 60
permissions:
contents: read
@@ -166,7 +166,7 @@ jobs:
- name: Build sidecars
run: |
- cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
+ cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh
- name: Build Linux Tauri app
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c613924e57..02011ad386 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,14 +1,13 @@
name: Release
+concurrency:
+ group: desktop-release-${{ github.ref }}
+ cancel-in-progress: false
+
on:
push:
tags:
- - 'v[0-9]*'
- workflow_dispatch:
- inputs:
- version:
- description: "Semver version matching the v-prefixed dispatch tag"
- required: true
+ - 'desktop-v[0-9]*'
jobs:
# Shared setup: verify the immutable release tag, determine the version, and
@@ -19,23 +18,14 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
- contents: write
+ contents: read
outputs:
version: ${{ steps.version.outputs.version }}
source_sha: ${{ steps.source.outputs.source_sha }}
steps:
- name: Determine version
id: version
- env:
- EVENT_NAME: ${{ github.event_name }}
- INPUT_VERSION: ${{ inputs.version }}
- run: |
- if [[ "$EVENT_NAME" == "push" ]]; then
- VERSION="${GITHUB_REF_NAME#v}"
- else
- VERSION="$INPUT_VERSION"
- fi
- echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+ run: echo "version=${GITHUB_REF_NAME#desktop-v}" >> "$GITHUB_OUTPUT"
- name: Validate version
env:
@@ -46,7 +36,7 @@ jobs:
exit 1
fi
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
@@ -56,42 +46,9 @@ jobs:
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
- scripts/verify-release-ref.sh v "$VERSION"
+ scripts/verify-release-ref.sh desktop-v "$VERSION"
echo "source_sha=$(git rev-parse 'HEAD^{commit}')" >> "$GITHUB_OUTPUT"
- - name: Create versioned GitHub release
- env:
- VERSION: ${{ steps.version.outputs.version }}
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- RELEASE_SHA=$(git rev-parse HEAD)
- NOTES=""
- if [[ -f CHANGELOG.md ]]; then
- NOTES=$(awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found && !/^\$/" CHANGELOG.md)
- fi
- if [[ -z "$NOTES" ]]; then
- NOTES="Buzz Desktop v${VERSION}"
- fi
- PRERELEASE_FLAGS=()
- if [[ "$VERSION" =~ -(test|alpha|beta|rc)([.-]|$) ]]; then
- PRERELEASE_FLAGS=(--prerelease --latest=false)
- fi
- gh release create "v${VERSION}" \
- --target "$RELEASE_SHA" \
- --title "Buzz Desktop v${VERSION}" \
- --notes "$NOTES" \
- "${PRERELEASE_FLAGS[@]}"
-
- - name: Create rolling auto-update release
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- gh release create buzz-desktop-latest \
- --prerelease \
- --title "Buzz Desktop Auto-Update" \
- --notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \
- 2>/dev/null || true
-
release:
name: Release
if: github.repository == 'block/buzz'
@@ -99,7 +56,7 @@ jobs:
needs: setup
timeout-minutes: 60
permissions:
- contents: write
+ contents: read
id-token: write # required by block/apple-codesign-action for OIDC
outputs:
archive_name: ${{ steps.artifacts.outputs.archive_name }}
@@ -107,14 +64,14 @@ jobs:
env:
VERSION: ${{ needs.setup.outputs.version }}
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.setup.outputs.source_sha }}
fetch-depth: 0
persist-credentials: false
- name: Verify tag-bound release source
- run: scripts/verify-release-ref.sh v "$VERSION"
+ run: scripts/verify-release-ref.sh desktop-v "$VERSION"
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
@@ -134,7 +91,7 @@ jobs:
- name: Build sidecars
run: |
- cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
+ cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh
# Mesh rev derived from Cargo.lock (no lockstep edit on dep bump); cache key tracks it.
@@ -272,13 +229,19 @@ jobs:
fi
echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
- # Find the updater .tar.gz and .sig
+ # Find the updater .tar.gz and .sig. Give each architecture a unique
+ # release basename before artifacts are merged by the final writer.
ARCHIVE=$(find "$BUNDLE_DIR/macos" -name '*.tar.gz' ! -name '*.sig' -type f | head -1)
SIG="${ARCHIVE}.sig"
if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then
echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos"
exit 1
fi
+ RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_aarch64.app.tar.gz"
+ mv "$ARCHIVE" "$RENAMED"
+ mv "$SIG" "${RENAMED}.sig"
+ ARCHIVE="$RENAMED"
+ SIG="${RENAMED}.sig"
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
@@ -289,23 +252,15 @@ jobs:
env:
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
- - name: Upload arm64 DMG to versioned GitHub release
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- DMG_PATH: ${{ steps.artifacts.outputs.dmg }}
- run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
-
- - name: Upload updater archive to rolling release
- if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
- run: |
- gh release upload buzz-desktop-latest \
- "$ARCHIVE_PATH" \
- "$SIG_PATH" \
- --clobber
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
- SIG_PATH: ${{ steps.artifacts.outputs.sig }}
+ - name: Stage Apple Silicon release artifacts
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: desktop-release-macos-arm64
+ if-no-files-found: error
+ path: |
+ ${{ steps.artifacts.outputs.dmg }}
+ ${{ steps.artifacts.outputs.archive }}
+ ${{ steps.artifacts.outputs.sig }}
release-macos-x64:
name: Release macOS (Intel)
@@ -314,7 +269,7 @@ jobs:
needs: setup
timeout-minutes: 60
permissions:
- contents: write
+ contents: read
id-token: write # required by block/apple-codesign-action for OIDC
outputs:
archive_name: ${{ steps.artifacts.outputs.archive_name }}
@@ -323,14 +278,14 @@ jobs:
VERSION: ${{ needs.setup.outputs.version }}
TARGET: x86_64-apple-darwin
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.setup.outputs.source_sha }}
fetch-depth: 0
persist-credentials: false
- name: Verify tag-bound release source
- run: scripts/verify-release-ref.sh v "$VERSION"
+ run: scripts/verify-release-ref.sh desktop-v "$VERSION"
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
@@ -353,7 +308,7 @@ jobs:
- name: Build sidecars
run: |
- cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
+ cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh "$TARGET"
- name: Build unsigned Tauri app
@@ -443,6 +398,11 @@ jobs:
echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos"
exit 1
fi
+ RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_x64.app.tar.gz"
+ mv "$ARCHIVE" "$RENAMED"
+ mv "$SIG" "${RENAMED}.sig"
+ ARCHIVE="$RENAMED"
+ SIG="${RENAMED}.sig"
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
@@ -453,34 +413,26 @@ jobs:
env:
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
- - name: Upload Intel DMG to versioned GitHub release
- run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
-
- - name: Upload updater archive to rolling release
- if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
- run: |
- gh release upload buzz-desktop-latest \
- "$ARCHIVE_PATH" \
- "$SIG_PATH" \
- --clobber
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
- SIG_PATH: ${{ steps.artifacts.outputs.sig }}
+ - name: Stage Intel macOS release artifacts
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: desktop-release-macos-x64
+ if-no-files-found: error
+ path: |
+ ${{ steps.unsigned.outputs.dmg }}
+ ${{ steps.artifacts.outputs.archive }}
+ ${{ steps.artifacts.outputs.sig }}
release-linux:
name: Release Linux
if: github.repository == 'block/buzz'
runs-on: ubuntu-latest
# Digest-pinned like the SHA-pinned actions below; Renovate keeps it fresh.
- container: ubuntu:22.04@sha256:0e0a0fc6d18feda9db1590da249ac93e8d5abfea8f4c3c0c849ce512b5ef8982
+ container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
needs: setup
timeout-minutes: 60
permissions:
- contents: write
+ contents: read
env:
# AppImage tools (linuxdeploy, appimagetool) are themselves AppImages.
# Containers lack FUSE, so we must use the extract-and-run fallback.
@@ -498,7 +450,7 @@ jobs:
env:
DEBIAN_FRONTEND: noninteractive
run: |
- # Must run first: bare ubuntu:22.04 ships without curl, wget, git, or
+ # Must run first: bare ubuntu:24.04 ships without curl, wget, git, or
# ca-certificates. activate-hermit bootstraps via curl+HTTPS (needs
# both), and actions/checkout falls back to a REST tarball without git.
# Running as root — no sudo needed.
@@ -543,7 +495,7 @@ jobs:
apt-get update
apt-get install -y --no-install-recommends gh
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.setup.outputs.source_sha }}
fetch-depth: 0
@@ -555,7 +507,7 @@ jobs:
- name: Verify tag-bound release source
env:
VERSION: ${{ needs.setup.outputs.version }}
- run: scripts/verify-release-ref.sh v "$VERSION"
+ run: scripts/verify-release-ref.sh desktop-v "$VERSION"
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
@@ -611,7 +563,7 @@ jobs:
- name: Build sidecars
run: |
- cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
+ cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh
- name: Generate release config
@@ -689,29 +641,16 @@ jobs:
SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }}
# NOTE: .deb is NOT auto-updatable (Tauri updater constraint — only AppImage supports it on Linux)
- - name: Upload Linux artifacts to versioned GitHub release
- env:
- VERSION: ${{ needs.setup.outputs.version }}
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- DEB_PATH: ${{ steps.linux-artifacts.outputs.deb }}
- APPIMAGE_PATH: ${{ steps.linux-artifacts.outputs.appimage }}
- run: |
- gh release upload "v$VERSION" \
- "$DEB_PATH" \
- "$APPIMAGE_PATH" \
- --clobber
-
- - name: Upload updater archive to rolling release
- if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
- run: |
- gh release upload buzz-desktop-latest \
- "$ARCHIVE_PATH" \
- "$SIG_PATH" \
- --clobber
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- ARCHIVE_PATH: ${{ steps.linux-artifacts.outputs.archive }}
- SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }}
+ - name: Stage Linux release artifacts
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: desktop-release-linux-x64
+ if-no-files-found: error
+ path: |
+ ${{ steps.linux-artifacts.outputs.deb }}
+ ${{ steps.linux-artifacts.outputs.appimage }}
+ ${{ steps.linux-artifacts.outputs.archive }}
+ ${{ steps.linux-artifacts.outputs.sig }}
release-windows:
name: Release Windows
@@ -719,7 +658,7 @@ jobs:
needs: setup
timeout-minutes: 60
permissions:
- contents: write
+ contents: read
outputs:
archive_name: ${{ steps.artifacts.outputs.archive_name }}
sig: ${{ steps.read-sig.outputs.sig }}
@@ -727,7 +666,7 @@ jobs:
VERSION: ${{ needs.setup.outputs.version }}
TARGET: x86_64-pc-windows-msvc
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.setup.outputs.source_sha }}
fetch-depth: 0
@@ -735,7 +674,7 @@ jobs:
- name: Verify tag-bound release source
shell: bash
- run: scripts/verify-release-ref.sh v "$VERSION"
+ run: scripts/verify-release-ref.sh desktop-v "$VERSION"
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
@@ -745,7 +684,7 @@ jobs:
with:
node-version: 24.14.1
# Disable dependency caching: a writable cache in this release workflow
- # (contents: write, feeds a signed installer) is a poisoning vector. pnpm
+ # (contents: read, feeds a signed installer) is a poisoning vector. pnpm
# install runs uncached below.
package-manager-cache: false
@@ -827,25 +766,14 @@ jobs:
env:
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
- - name: Upload Windows installer to versioned GitHub release
- shell: bash
- run: gh release upload "v${VERSION}" "$EXE_PATH" --clobber
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- EXE_PATH: ${{ steps.artifacts.outputs.exe }}
-
- - name: Upload updater archive to rolling release
- if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
- shell: bash
- run: |
- gh release upload buzz-desktop-latest \
- "$ARCHIVE_PATH" \
- "$SIG_PATH" \
- --clobber
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
- SIG_PATH: ${{ steps.artifacts.outputs.sig }}
+ - name: Stage Windows release artifacts
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: desktop-release-windows-x64
+ if-no-files-found: error
+ path: |
+ ${{ steps.artifacts.outputs.exe }}
+ ${{ steps.artifacts.outputs.sig }}
assemble-manifest:
name: Assemble multi-platform latest.json
@@ -853,7 +781,11 @@ jobs:
if: |
always() &&
needs.setup.result == 'success' &&
- github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
+ needs.release.result == 'success' &&
+ needs.release-macos-x64.result == 'success' &&
+ needs.release-linux.result == 'success' &&
+ needs.release-windows.result == 'success' &&
+ github.ref == format('refs/tags/desktop-v{0}', needs.setup.outputs.version)
runs-on: ubuntu-latest
needs: [setup, release, release-macos-x64, release-linux, release-windows]
timeout-minutes: 10
@@ -863,14 +795,33 @@ jobs:
VERSION: ${{ needs.setup.outputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.setup.outputs.source_sha }}
fetch-depth: 0
persist-credentials: false
- name: Verify tag-bound release source
- run: scripts/verify-release-ref.sh v "$VERSION"
+ run: scripts/verify-release-ref.sh desktop-v "$VERSION"
+
+ - name: Download staged release artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ pattern: desktop-release-*
+ path: staged-by-platform
+
+ - name: Flatten staged artifacts without basename collisions
+ run: |
+ set -euo pipefail
+ mkdir staged
+ while IFS= read -r -d '' file; do
+ name="$(basename "$file")"
+ [[ ! -e "staged/$name" ]] || {
+ echo "::error::release artifact basename collision: $name"
+ exit 1
+ }
+ cp "$file" "staged/$name"
+ done < <(find staged-by-platform -type f -print0)
- name: Write signature files
env:
@@ -899,7 +850,7 @@ jobs:
write_sig "$RESULT_LINUX" linux-x86_64 "$SIG_LINUX"
write_sig "$RESULT_WIN" windows-x86_64 "$SIG_WIN"
- - name: Verify archive URLs are accessible
+ - name: Verify draft release has every updater archive
env:
RESULT_ARM64: ${{ needs.release.result }}
RESULT_X64: ${{ needs.release-macos-x64.result }}
@@ -911,39 +862,19 @@ jobs:
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
run: |
set -euo pipefail
- BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
- ARCHIVES=()
-
- add_archive() {
- local result="$1" platform="$2" archive="$3"
- if [[ "$result" == "success" ]]; then
- [[ -n "$archive" ]] || { echo "::error::Missing archive name for successful platform: $platform"; exit 1; }
- ARCHIVES+=("$archive")
- fi
- }
-
- add_archive "$RESULT_ARM64" darwin-aarch64 "$ARCHIVE_ARM64"
- add_archive "$RESULT_X64" darwin-x86_64 "$ARCHIVE_X64"
- add_archive "$RESULT_LINUX" linux-x86_64 "$ARCHIVE_LINUX"
- add_archive "$RESULT_WIN" windows-x86_64 "$ARCHIVE_WIN"
-
- for name in "${ARCHIVES[@]}"; do
- echo "Checking $BASE/$name ..."
- success=false
- for attempt in 1 2 3; do
- if curl -fsI "$BASE/$name" > /dev/null 2>&1; then
- success=true
- break
- fi
- echo "Attempt $attempt failed for $name, retrying in 10s..."
- sleep 10
- done
- if [ "$success" != "true" ]; then
- echo "::error::Archive not accessible after 3 attempts: $BASE/$name"
- exit 1
+ assets=$(find staged -type f -exec basename {} \;)
+ for spec in \
+ "$RESULT_ARM64:$ARCHIVE_ARM64" \
+ "$RESULT_X64:$ARCHIVE_X64" \
+ "$RESULT_LINUX:$ARCHIVE_LINUX" \
+ "$RESULT_WIN:$ARCHIVE_WIN"; do
+ result="${spec%%:*}"
+ archive="${spec#*:}"
+ if [[ "$result" == success ]]; then
+ [[ -n "$archive" ]] || { echo "::error::successful platform has no archive"; exit 1; }
+ grep -Fxq "$archive" <<<"$assets" || { echo "::error::draft release missing $archive"; exit 1; }
fi
done
- echo "All archive URLs verified."
- name: Generate unified latest.json
env:
@@ -957,7 +888,7 @@ jobs:
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
run: |
set -euo pipefail
- BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
+ BASE="https://github.com/block/buzz/releases/download/desktop-v${VERSION}"
TRIPLES=()
add_triple() {
@@ -977,6 +908,45 @@ jobs:
bash desktop/scripts/generate-oss-latest-json.sh "$VERSION" "${TRIPLES[@]}" > latest.json
cat latest.json
- - name: Upload latest.json to rolling release
+ - name: Create or verify versioned draft
run: |
- gh release upload buzz-desktop-latest latest.json --clobber
+ set -euo pipefail
+ NOTES_FILE="${RUNNER_TEMP}/release-notes.md"
+ awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found" CHANGELOG.md > "$NOTES_FILE"
+ [[ -s "$NOTES_FILE" ]] || { echo "::error::missing non-empty changelog block for v${VERSION}"; exit 1; }
+ PRERELEASE_FLAGS=()
+ if [[ "$VERSION" == *-* ]]; then
+ PRERELEASE_FLAGS=(--prerelease --latest=false)
+ fi
+ if gh release view "desktop-v${VERSION}" >/dev/null 2>&1; then
+ EXISTING_SHA=$(gh release view "desktop-v${VERSION}" --json targetCommitish --jq .targetCommitish)
+ IS_DRAFT=$(gh release view "desktop-v${VERSION}" --json isDraft --jq .isDraft)
+ [[ "$EXISTING_SHA" == "${{ needs.setup.outputs.source_sha }}" ]] || {
+ echo "::error::existing release targets $EXISTING_SHA, not the immutable source"; exit 1;
+ }
+ if [[ "$IS_DRAFT" != true ]]; then
+ echo "already_published=true" >> "$GITHUB_ENV"
+ fi
+ else
+ gh release create "desktop-v${VERSION}" \
+ --draft \
+ --target "${{ needs.setup.outputs.source_sha }}" \
+ --title "Buzz Desktop v${VERSION}" \
+ --notes-file "$NOTES_FILE" \
+ "${PRERELEASE_FLAGS[@]}"
+ fi
+
+ - name: Upload complete artifact set to versioned draft
+ if: env.already_published != 'true'
+ run: |
+ mapfile -t files < <(find staged -type f -print)
+ [[ "${#files[@]}" -gt 0 ]] || { echo "::error::no staged release artifacts"; exit 1; }
+ gh release upload "desktop-v${VERSION}" "${files[@]}" --clobber
+
+ - name: Publish complete versioned release
+ if: env.already_published != 'true'
+ run: gh release edit "desktop-v${VERSION}" --draft=false
+
+ - name: Upload latest.json to rolling release last
+ if: ${{ !contains(needs.setup.outputs.version, '-') }}
+ run: gh release upload buzz-desktop-latest latest.json --clobber
diff --git a/.github/workflows/signed-macos-canary.yml b/.github/workflows/signed-macos-canary.yml
index fb0656028a..0a3a513eef 100644
--- a/.github/workflows/signed-macos-canary.yml
+++ b/.github/workflows/signed-macos-canary.yml
@@ -93,7 +93,7 @@ jobs:
- name: Build sidecars
run: |
- cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
+ cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh
# Mesh rev derived from Cargo.lock (no lockstep edit on dep bump); cache key tracks it.
diff --git a/.github/workflows/sprig-image.yml b/.github/workflows/sprig-image.yml
new file mode 100644
index 0000000000..5d5e12ae0c
--- /dev/null
+++ b/.github/workflows/sprig-image.yml
@@ -0,0 +1,233 @@
+name: Sprig image
+
+# Builds and publishes the public agent container image as
+# ghcr.io/block/buzz-sprig — the digest-pinned box the Kubernetes backend
+# deploys agents into (see Dockerfile.sprig and docs/remote-agents.md).
+#
+# Strategy mirrors docker.yml (the relay image): each architecture builds on
+# its native runner, pushes to GHCR by digest, then a merge job stitches the
+# per-arch digests into one multi-arch manifest and attests provenance.
+# No QEMU emulation.
+#
+# Triggers:
+# - push to main (paths-filtered) → :main + :sha-<7>
+# - tag sprig-v* → semver family (shared with sprig.yml's
+# binary release — one tag versions both)
+# - pull_request (paths-filtered) → build only, no push
+# - workflow_dispatch → manual publish at the current ref
+#
+# NOTE: the first push creates the GHCR package PRIVATE by default. An org
+# admin must flip ghcr.io/block/buzz-sprig to public once (Package settings →
+# Change visibility). Subsequent pushes keep the visibility.
+
+on:
+ push:
+ branches: [main]
+ tags: ["sprig-v[0-9]*"]
+ paths:
+ - "Dockerfile.sprig"
+ - "scripts/sprig-entrypoint.sh"
+ - ".github/workflows/sprig-image.yml"
+ - "Cargo.toml"
+ - "Cargo.lock"
+ - "rust-toolchain.toml"
+ - "crates/**"
+ pull_request:
+ paths:
+ - "Dockerfile.sprig"
+ - "scripts/sprig-entrypoint.sh"
+ - ".github/workflows/sprig-image.yml"
+ workflow_dispatch: {}
+
+concurrency:
+ group: sprig-image-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref_type == 'branch' && github.event_name == 'pull_request' }}
+
+permissions: {}
+
+env:
+ # Single source of truth for the image name; override with the
+ # GHCR_SPRIG_IMAGE repo variable (same pattern as docker.yml).
+ IMAGE_NAME: ${{ vars.GHCR_SPRIG_IMAGE != '' && vars.GHCR_SPRIG_IMAGE || 'ghcr.io/block/buzz-sprig' }}
+
+jobs:
+ build:
+ name: Build (${{ matrix.platform }})
+ runs-on: ${{ matrix.runner }}
+ timeout-minutes: 60
+ permissions:
+ contents: read
+ packages: write
+ id-token: write
+ attestations: write
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: linux/amd64
+ runner: ubuntu-24.04
+ arch: amd64
+ - platform: linux/arm64
+ runner: ubuntu-24.04-arm
+ arch: arm64
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
+ with:
+ # Same OOM cap as docker.yml — Rust compiles blow the 7GB runner
+ # at buildkit's default parallelism of 4.
+ buildkitd-config-inline: |
+ [worker.oci]
+ max-parallelism = 2
+
+ - name: Log in to GHCR
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
+ uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
+ with:
+ images: ${{ env.IMAGE_NAME }}
+ # match=^sprig-v(.*)$ strips the tag prefix for the semver parser,
+ # exactly as docker.yml does for relay-v. :latest comes from
+ # flavor.latest=auto — stable semver only, never main pushes.
+ tags: |
+ type=ref,event=branch
+ type=sha,prefix=sha-,format=short
+ type=semver,pattern={{version}},match=^sprig-v(.*)$
+ type=semver,pattern={{major}}.{{minor}},match=^sprig-v(.*)$
+ labels: |
+ org.opencontainers.image.title=Buzz Sprig
+ org.opencontainers.image.description=Agent runtime image for Buzz remote agents (buzz-acp multicall + git + curl)
+ org.opencontainers.image.licenses=Apache-2.0
+
+ - name: Build and push by digest
+ id: build
+ uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
+ with:
+ context: .
+ file: ./Dockerfile.sprig
+ platforms: ${{ matrix.platform }}
+ labels: ${{ steps.meta.outputs.labels }}
+ outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
+ cache-from: |
+ type=registry,ref=${{ env.IMAGE_NAME }}-buildcache:${{ matrix.arch }}
+ cache-to: |
+ ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref={0}-buildcache:{1},mode=max,compression=zstd', env.IMAGE_NAME, matrix.arch) || '' }}
+
+ - name: Export digest
+ if: github.event_name != 'pull_request'
+ env:
+ DIGEST: ${{ steps.build.outputs.digest }}
+ run: |
+ mkdir -p /tmp/digests
+ touch "/tmp/digests/${DIGEST#sha256:}"
+
+ - name: Upload digest
+ if: github.event_name != 'pull_request'
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: sprig-digest-${{ matrix.arch }}
+ path: /tmp/digests/*
+ if-no-files-found: error
+ retention-days: 1
+
+ merge:
+ name: Merge multi-arch manifest
+ if: github.event_name != 'pull_request'
+ runs-on: ubuntu-24.04
+ needs: build
+ timeout-minutes: 15
+ permissions:
+ contents: read
+ packages: write
+ id-token: write
+ attestations: write
+
+ steps:
+ - name: Download per-arch digests
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ path: /tmp/digests
+ pattern: sprig-digest-*
+ merge-multiple: true
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
+
+ - name: Log in to GHCR
+ uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
+ with:
+ images: ${{ env.IMAGE_NAME }}
+ # Must mirror the build job's tag matrix exactly (see docker.yml).
+ flavor: |
+ latest=auto
+ tags: |
+ type=ref,event=branch
+ type=sha,prefix=sha-,format=short
+ type=semver,pattern={{version}},match=^sprig-v(.*)$
+ type=semver,pattern={{major}}.{{minor}},match=^sprig-v(.*)$
+
+ - name: Create and push manifest list
+ id: manifest
+ working-directory: /tmp/digests
+ env:
+ IMAGE_NAME: ${{ env.IMAGE_NAME }}
+ META_TAGS: ${{ steps.meta.outputs.tags }}
+ run: |
+ set -euo pipefail
+ tags=()
+ while IFS= read -r tag; do
+ [ -n "$tag" ] && tags+=("-t" "$tag")
+ done <<< "$META_TAGS"
+
+ digests=()
+ for digest in *; do
+ digests+=("${IMAGE_NAME}@sha256:${digest}")
+ done
+
+ docker buildx imagetools create "${tags[@]}" "${digests[@]}"
+
+ first_tag=$(echo "$META_TAGS" | head -n1)
+ merged_digest=$(docker buildx imagetools inspect "$first_tag" \
+ --format '{{json .Manifest}}' | jq -r '.digest')
+ echo "digest=${merged_digest}" >> "$GITHUB_OUTPUT"
+
+ - name: Attest provenance for the merged image
+ # Verify with: gh attestation verify oci://ghcr.io/block/buzz-sprig: --owner block
+ uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
+ with:
+ subject-name: ${{ env.IMAGE_NAME }}
+ subject-digest: ${{ steps.manifest.outputs.digest }}
+ push-to-registry: true
+
+ - name: Summary
+ env:
+ IMAGE_NAME: ${{ env.IMAGE_NAME }}
+ DIGEST: ${{ steps.manifest.outputs.digest }}
+ run: |
+ {
+ echo "### Sprig image published"
+ echo '```'
+ echo "${IMAGE_NAME}@${DIGEST}"
+ echo '```'
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/sprig.yml b/.github/workflows/sprig.yml
index b2dab3583f..5e50808b3b 100644
--- a/.github/workflows/sprig.yml
+++ b/.github/workflows/sprig.yml
@@ -42,7 +42,7 @@ jobs:
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-musl
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
@@ -116,7 +116,7 @@ jobs:
permissions:
contents: write
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Download all Sprig artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -154,7 +154,7 @@ jobs:
permissions:
contents: write
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Download all Sprig artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
diff --git a/.gitignore b/.gitignore
index 65ddcaf1c4..f26e74136c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,10 @@
/dist/
/admin-web/dist/
+# Python cache
+__pycache__/
+*.pyc
+
# lefthook-generated hook scripts (machine-specific)
.hooks/
diff --git a/.release/desktop-candidate.json b/.release/desktop-candidate.json
new file mode 100644
index 0000000000..1bf2efb66b
--- /dev/null
+++ b/.release/desktop-candidate.json
@@ -0,0 +1,8 @@
+{
+ "schema": 1,
+ "version": "0.5.3",
+ "base_sha": "54c8ef30a9bb9c59a4415a8a7ee84c7c5454b48a",
+ "previous_tag": "v0.5.2",
+ "tag": "desktop-v0.5.3",
+ "commit_count": 58
+}
diff --git a/000-docs/001-PP-PLAN-buzz-adoption-master-blueprint.md b/000-docs/001-PP-PLAN-buzz-adoption-master-blueprint.md
index 4c5533c3cd..8f632f707f 100644
--- a/000-docs/001-PP-PLAN-buzz-adoption-master-blueprint.md
+++ b/000-docs/001-PP-PLAN-buzz-adoption-master-blueprint.md
@@ -80,11 +80,15 @@ three-way (bead ↔ GitHub issue ↔ Plane project `BUZZ`).
| E3 — Hardening + go-live gates (run against PROD) | 2 | in flight — membership probe + unauth HTTP matrix + resource caps + backup/restore PROVEN against prod; remaining BLOCKING: off-site backup leg, updater planted-fault drill (on staging), monitoring alerts exercised, full smoke suite, CORS client verify, pairing verify-or-documented, key runbooks rehearsed | — |
| E4 — Headless administration | 2.5 | not started | — |
| E5 — Team onboarding (all-in) | 3 | not started | — |
-| E6 — Agent bridge (`@claude`, isolated) | 4 | not started | — |
+| E6 — Agent bridge (isolated coding agent `goose minimax3`) | 4 | **LIVE on prod** (2026-07-30, decision-log/039, PR #301): `buzz-acp`+goose/MiniMax-M3 running as a member on the prod host, owner-gated + PR-gated, egress-isolated. Open: wire the prod PR loop (repo + scoped token + branch protection — owner inputs) | GH #? · PR #301 |
| E7 — Governed-brain agent (`@bob`, BYOH) | 4 | staged (follow-up) | — |
| ELab — Contributor laboratory (`intent-solutions-io/intent-ops-buzz` **repository**, Track C) | 6 | not started (repo not yet created — correct; depends on the naming record `006`) | — |
| E8 — Upstream contribution lane (Track D) | 5 | qualified candidates filed | — |
| E9 — Operator plugin (`intent-solutions-io/intent-ops-buzz-plugin`) | 7 | **DEFERRED** — scope gauged from the real install cycle (owner call 2026-07-29); repo not yet created | — |
+| E10 — Community channel layout & identity roster (`buzz-ehv`) | 3 | **build DONE (2026-07-31)** — clean provider-first open layout live (`0-general` pinned, `ask`, `ai-wire`, `anthropic-*` group, `-wire` + topic `*-wire`, private `sys-*`); persona agents purged; canonical names set; `ops/buzz/RUNBOOK-channels.md` (source-cited). Open: CCA-channel decision (owner-gated), member join/onboarding model | fork beads `buzz-ehv` |
+| E11 — Buzz operational expertise: reference + skill + agents (`buzz-yfe`) | 3 | **COMPLETE (2026-07-31)** — 2 source-cited references (`ops/buzz/reference/`, 100% citation-consistent with the fork); `buzz-ops` skill (PASS `/validate-skillmd`); 3 operator agents `buzz-ops`/`buzz-feed-curator`/`buzz-relay-admin` (PASS `/validate-agent`) | fork beads `buzz-yfe` |
+| E12 — AI-Wire feed engine (`buzz-w92`) | 4 | **posting primitive DONE**; ingestion pipeline next — `AI Wire` bot live, first cards posted to `ai-wire`+`0-general`. Children: pipeline (RSSHub+feed→LLM→card), easy/medium/hard feeds, Anthropic monitor-fleet wire, curation+digest, cron | fork beads `buzz-w92` |
+| E13 — Estate system notifications into `sys-*` (`buzz-0ts`) | 4 | **not started** (Track B) — `buzz-notify.sh`/`af_buzz_transport` → private `sys-*` channels, retiring Slack/notify/Moshi (keep off-estate floor + Ezekiel email) | fork beads `buzz-0ts` |
Asset names are governed by `006-DR-STND-authoritative-naming-and-boundaries.md`
(canonical). Never write the bare phrase `intent-ops-buzz` — it is either the
diff --git a/AGENTS.md b/AGENTS.md
index 7ff0eb4d47..4f03b312bc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -145,6 +145,10 @@ first, then implement handling in the relay.
**Channel scoping**: Channels use `h` tags (NIP-29 group tag), not `e` tags.
Filters and queries must scope to `h` tags when operating within a channel.
+This applies to events *inside* a channel. Addressable events that describe a
+channel carry its id in their `d` tag instead: kind:39000 (metadata),
+kind:39001, kind:39002 (membership). `get_channels` resolves a user's channels
+from the `d` tag of their kind:39002 events, not from `h`.
**Agent-facing operations go in `buzz-cli`**: New agent-facing features belong in `buzz-cli` — add a subcommand there first, then wire the REST/WebSocket call in `client.rs`. `buzz-dev-mcp` (shell + file tools for `buzz-agent`) is separate.
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 90cbbac0cf..892082d96c 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -139,7 +139,7 @@ The `kind` integer is the only dispatch switch. The relay routes, stores, and fa
| 46001–46012 | KIND_WORKFLOW_* | Workflow execution events |
| 20001 | KIND_PRESENCE_UPDATE | Ephemeral presence heartbeat |
-`buzz-core` defines all 81 kinds as `pub const KIND_*: u32` and exports `ALL_KINDS: &[u32]`. Kinds are `u32` (NIP-01 specifies unsigned integer; `u32` covers the full range). Buzz uses both standard Nostr kinds (e.g., kind 7 for reactions) and custom ranges (40000+).
+`buzz-core` defines each event kind as a `pub const u32` and exports the full registry as `ALL_KINDS: &[u32]` (127 kinds at the time of writing); `crates/buzz-core/src/kind.rs` is the source of truth for the current list. Kinds are `u32` (NIP-01 specifies unsigned integer; `u32` covers the full range). Buzz uses both standard Nostr kinds (e.g., kind 7 for reactions) and custom ranges (40000+).
Note: `KIND_AUTH` (22242) is `pub const KIND_AUTH: u32` in `buzz-core/src/kind.rs` and imported by `buzz-relay/src/handlers/event.rs`. `KIND_CANVAS` (40100) is likewise `pub const KIND_CANVAS: u32` in `buzz-core/src/kind.rs`.
@@ -447,7 +447,7 @@ The subscriber uses a **dedicated** `redis::aio::PubSub` connection — not from
**Reconnection:** exponential backoff 1s → 30s (`backoff_secs * 2`). Backoff resets to 1s only after a clean stream end, not on each reconnect attempt.
-**Presence:** `SET buzz:presence:{pubkey_hex} {status} EX 90` — 90-second TTL (3× the 30-second heartbeat interval). Single missed heartbeat does not cause presence flap.
+**Presence:** `SET buzz:presence:{pubkey_hex} {status} EX 180` — 180-second TTL (3× the 60-second heartbeat interval). Single missed heartbeat does not cause presence flap.
**Typing indicators:**
```
@@ -797,7 +797,7 @@ Docker Compose provides the full local development stack. All services include h
| Pattern | Type | TTL | Purpose |
|---------|------|-----|---------|
| `buzz:channel:{uuid}` | Pub/Sub channel | — | Event fan-out (single-community form; shared multi-community Redis must use `buzz:{community}:channel:{uuid}` or equivalent) |
-| `buzz:presence:{pubkey_hex}` | String | 90s | Online/away status (single-community form; shared multi-community Redis must scope by community) |
+| `buzz:presence:{pubkey_hex}` | String | 180s | Online/away status (single-community form; shared multi-community Redis must scope by community) |
| `buzz:typing:{channel_uuid}` | Sorted Set | 60s | Active typers (5s window; shared multi-community Redis must scope by community) |
### Full-Text Search (Postgres FTS)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cfd3b16d0a..71a4bbd449 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,106 @@
# Changelog
+## v0.5.3
+
+### Desktop and shared changes
+
+- Revert "chore(release): release Buzz Desktop version 0.5.3" ([#3960](https://github.com/block/buzz/pull/3960)) ([`bb34bc4d98fe4dabe847046103ac5e2859917ac5`](https://github.com/block/buzz/commit/bb34bc4d98fe4dabe847046103ac5e2859917ac5))
+- chore(release): release Buzz Desktop version 0.5.3 ([`d12b3d6a79d56a95fc99ce4fadd2d2235d5a3131`](https://github.com/block/buzz/commit/d12b3d6a79d56a95fc99ce4fadd2d2235d5a3131))
+- feat(desktop): import local Pocket voices ([#3259](https://github.com/block/buzz/pull/3259)) ([`c104eecfb38620de2c35c7e20a716f8658b5a6b1`](https://github.com/block/buzz/commit/c104eecfb38620de2c35c7e20a716f8658b5a6b1))
+- fix(desktop): open profiles from avatars ([#3751](https://github.com/block/buzz/pull/3751)) ([`39ce3dfc3cf2d12f0d6c64b4cd4293df86567663`](https://github.com/block/buzz/commit/39ce3dfc3cf2d12f0d6c64b4cd4293df86567663))
+- refactor(voice): extract reusable Pocket primitives + Pocket voice settings (relands #2467 + #3208) ([#3910](https://github.com/block/buzz/pull/3910)) ([`61ba9dfaa00852925058d1a024322fa53663a5bc`](https://github.com/block/buzz/commit/61ba9dfaa00852925058d1a024322fa53663a5bc))
+- feat(desktop): auto-enable huddle transcription for agents ([#3180](https://github.com/block/buzz/pull/3180)) ([`4632c55041c5d423d572a6f6411bb7b279c26f67`](https://github.com/block/buzz/commit/4632c55041c5d423d572a6f6411bb7b279c26f67))
+- feat(agent): optional reply guard reminds a silent turn to publish ([#3763](https://github.com/block/buzz/pull/3763)) ([`081f805d5ea25841ab885c7b67a568618a34aa59`](https://github.com/block/buzz/commit/081f805d5ea25841ab885c7b67a568618a34aa59))
+- feat(desktop): upgrade Pocket TTS model ([#3266](https://github.com/block/buzz/pull/3266)) ([`d48b0e0eec4d2958f90a3cafa9d974450abe8501`](https://github.com/block/buzz/commit/d48b0e0eec4d2958f90a3cafa9d974450abe8501))
+- feat(desktop): delete a message by clearing its edit to empty ([#3813](https://github.com/block/buzz/pull/3813)) ([`d88313f369acfa17973029787ee4c0bbea07fa51`](https://github.com/block/buzz/commit/d88313f369acfa17973029787ee4c0bbea07fa51))
+- feat(relay): raise hosted community limit to five ([#3829](https://github.com/block/buzz/pull/3829)) ([`10d5a26414dc90dc89fd27de74b21e105d4fa622`](https://github.com/block/buzz/commit/10d5a26414dc90dc89fd27de74b21e105d4fa622))
+- feat(desktop): locally stored NIP-49 encrypted key backup ([#2937](https://github.com/block/buzz/pull/2937)) ([`468647a51f858b29d27eaf9fd07bf90294f99d39`](https://github.com/block/buzz/commit/468647a51f858b29d27eaf9fd07bf90294f99d39))
+- fix(catalog): update Amp tagline ([#3806](https://github.com/block/buzz/pull/3806)) ([`f3e5e812677f6f14bffe16a7aa02642d56faca4b`](https://github.com/block/buzz/commit/f3e5e812677f6f14bffe16a7aa02642d56faca4b))
+- fix(desktop): channel topic and membership metadata cleanup ([#3642](https://github.com/block/buzz/pull/3642)) ([`9e8fcfda099652926b921bca7fcc9bfecab0e140`](https://github.com/block/buzz/commit/9e8fcfda099652926b921bca7fcc9bfecab0e140))
+- fix(desktop): align data deletion labels ([#2230](https://github.com/block/buzz/pull/2230)) ([`ede26863345a518ec46edd6d7692e0281883491b`](https://github.com/block/buzz/commit/ede26863345a518ec46edd6d7692e0281883491b))
+- fix(desktop): allow linux-only media items as dead code off-linux ([#3811](https://github.com/block/buzz/pull/3811)) ([`36571f4adcfdcf3714a17bd968c58c78bcbdd9ef`](https://github.com/block/buzz/commit/36571f4adcfdcf3714a17bd968c58c78bcbdd9ef))
+- fix(desktop): report authenticated relay recovery ([#3812](https://github.com/block/buzz/pull/3812)) ([`74cd5712191bffd84ae688d59bb8b451c6eec1b0`](https://github.com/block/buzz/commit/74cd5712191bffd84ae688d59bb8b451c6eec1b0))
+- fix(desktop): don't gate hover affordances on the hover media query ([#3657](https://github.com/block/buzz/pull/3657)) ([`29dfe4821ed577489a1879fd2a9bfe2a621a52b3`](https://github.com/block/buzz/commit/29dfe4821ed577489a1879fd2a9bfe2a621a52b3))
+- feat(relay): gate kind 30178 team-catalog reads behind the shared tag ([#3358](https://github.com/block/buzz/pull/3358)) ([`114d40d9d37f05eff83ee90347ed93fb3da512c5`](https://github.com/block/buzz/commit/114d40d9d37f05eff83ee90347ed93fb3da512c5))
+- test(desktop): click visible thread collapse guide ([#3800](https://github.com/block/buzz/pull/3800)) ([`b9e4ed616f39b812bc964e79c7a40223c4e93832`](https://github.com/block/buzz/commit/b9e4ed616f39b812bc964e79c7a40223c4e93832))
+- feat(desktop): raise the install ceiling and make installs observable ([#3368](https://github.com/block/buzz/pull/3368)) ([`d40a33290e75791aa7ecf3ce7a252b66c2e35966`](https://github.com/block/buzz/commit/d40a33290e75791aa7ecf3ce7a252b66c2e35966))
+- Add Devin as a preset ACP harness ([#3225](https://github.com/block/buzz/pull/3225)) ([`1b3ff96a5764303998fa629ff852e81f1a88d7ad`](https://github.com/block/buzz/commit/1b3ff96a5764303998fa629ff852e81f1a88d7ad))
+- feat(desktop): improve agent activity header ui ([#3321](https://github.com/block/buzz/pull/3321)) ([`4d47aa83455a9fd024121a596154cd311dca1d76`](https://github.com/block/buzz/commit/4d47aa83455a9fd024121a596154cd311dca1d76))
+- perf(presence): reduce heartbeat frequency ([#3783](https://github.com/block/buzz/pull/3783)) ([`bf139e8d0bdba10df9a5adbf16843140e0a78a59`](https://github.com/block/buzz/commit/bf139e8d0bdba10df9a5adbf16843140e0a78a59))
+- Tighten continuation message rows ([#3724](https://github.com/block/buzz/pull/3724)) ([`6e419b9f1c873549a7b40996970e0da7352adafb`](https://github.com/block/buzz/commit/6e419b9f1c873549a7b40996970e0da7352adafb))
+- Fix video reviews in thread replies ([#3719](https://github.com/block/buzz/pull/3719)) ([`f48f3f055fdd6030d3832f615f8c0d8e5a81261a`](https://github.com/block/buzz/commit/f48f3f055fdd6030d3832f615f8c0d8e5a81261a))
+- Make relay reconnect backoff authoritative ([#3774](https://github.com/block/buzz/pull/3774)) ([`cca8839034eb571a7ce943c3ace7f85a82330898`](https://github.com/block/buzz/commit/cca8839034eb571a7ce943c3ace7f85a82330898))
+- feat(desktop): add password-protected backups in settings ([#3701](https://github.com/block/buzz/pull/3701)) ([`bd0bff24bfd2cffa2b3b3a995f7628af5e460a5c`](https://github.com/block/buzz/commit/bd0bff24bfd2cffa2b3b3a995f7628af5e460a5c))
+- fix(desktop): reuse profiles when joining communities ([#2155](https://github.com/block/buzz/pull/2155)) ([`f44b5a2477f3979ae66e49153b11be36538cf859`](https://github.com/block/buzz/commit/f44b5a2477f3979ae66e49153b11be36538cf859))
+- fix(catalog): update Amp description ([#3758](https://github.com/block/buzz/pull/3758)) ([`61b96c9828d1dd54106b570d87a54edbc92bb9c4`](https://github.com/block/buzz/commit/61b96c9828d1dd54106b570d87a54edbc92bb9c4))
+- feat(catalog): resolve publisher display name in catalog detail pane ([#3640](https://github.com/block/buzz/pull/3640)) ([`02be413b823c356587e6e9f4d07f6cb06bb41c3c`](https://github.com/block/buzz/commit/02be413b823c356587e6e9f4d07f6cb06bb41c3c))
+- feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of #3467) ([#3741](https://github.com/block/buzz/pull/3741)) ([`4933672eb4589e7208b312829ebddcd10dfa9dd3`](https://github.com/block/buzz/commit/4933672eb4589e7208b312829ebddcd10dfa9dd3))
+- Refine agent sharing dialog ([#3699](https://github.com/block/buzz/pull/3699)) ([`9a386a0defbf2b355ee17646c7c11817a535b85f`](https://github.com/block/buzz/commit/9a386a0defbf2b355ee17646c7c11817a535b85f))
+- desktop: enable getUserMedia in the Linux WebKitGTK webview ([#3607](https://github.com/block/buzz/pull/3607)) ([`c9aa55505c544c608ff71648bbfd21b235637f19`](https://github.com/block/buzz/commit/c9aa55505c544c608ff71648bbfd21b235637f19))
+- fix: align responsive agent views ([#3688](https://github.com/block/buzz/pull/3688)) ([`73589408db6fd96b87ac570935d414ecc4120f53`](https://github.com/block/buzz/commit/73589408db6fd96b87ac570935d414ecc4120f53))
+- Add macOS agent menu-bar menu ([#3565](https://github.com/block/buzz/pull/3565)) ([`d0a24bcb5210326da4c0b1e749ee3935621b329c`](https://github.com/block/buzz/commit/d0a24bcb5210326da4c0b1e749ee3935621b329c))
+- Fix pending message feedback ([#3543](https://github.com/block/buzz/pull/3543)) ([`4672ee55c4e4a7916c31bfeae5df2fb4384bed10`](https://github.com/block/buzz/commit/4672ee55c4e4a7916c31bfeae5df2fb4384bed10))
+- fix(desktop): remove remaining Projects panel fills ([#3742](https://github.com/block/buzz/pull/3742)) ([`c55e421a0629c74b9ffd96ee3ccde36f006196ed`](https://github.com/block/buzz/commit/c55e421a0629c74b9ffd96ee3ccde36f006196ed))
+- desktop: restore direct community member adds ([#3634](https://github.com/block/buzz/pull/3634)) ([`310df2ec33fbb075edf226ba18bf9a96d90ba81b`](https://github.com/block/buzz/commit/310df2ec33fbb075edf226ba18bf9a96d90ba81b))
+- fix(desktop): explain open agent access ([#2561](https://github.com/block/buzz/pull/2561)) ([`7fb008f9347b933b9a1da20a7afb070912b430e8`](https://github.com/block/buzz/commit/7fb008f9347b933b9a1da20a7afb070912b430e8))
+- fix(desktop): remove Projects overview card fills ([#3416](https://github.com/block/buzz/pull/3416)) ([`3b8567a05d4c40e667d061666feb7aa7bc38212d`](https://github.com/block/buzz/commit/3b8567a05d4c40e667d061666feb7aa7bc38212d))
+- fix(git): channel binding tooling + author remediation for unbound repos ([#3626](https://github.com/block/buzz/pull/3626)) ([`788b3c002bd2509455444f57f8a03a054b4b496a`](https://github.com/block/buzz/commit/788b3c002bd2509455444f57f8a03a054b4b496a))
+- feat: configure S3 URL addressing style ([#3400](https://github.com/block/buzz/pull/3400)) ([`7012d86d52fd188b27c7beedeaa132d9c1f61fa8`](https://github.com/block/buzz/commit/7012d86d52fd188b27c7beedeaa132d9c1f61fa8))
+- feat: add first-class OpenRouter provider support ([#1975](https://github.com/block/buzz/pull/1975)) ([`ab55fee81896d2b03edf5d2ca5012b715be2b93d`](https://github.com/block/buzz/commit/ab55fee81896d2b03edf5d2ca5012b715be2b93d))
+- feat(agent,acp): wire provider total_tokens through NIP-AM publish chain ([#3593](https://github.com/block/buzz/pull/3593)) ([`f95fdc1a102e17c6718a44323d9a2feaed702db7`](https://github.com/block/buzz/commit/f95fdc1a102e17c6718a44323d9a2feaed702db7))
+
+### Other repository changes
+
+- fix(release): require exact-head approval for desktop tags ([#3973](https://github.com/block/buzz/pull/3973)) ([`54c8ef30a9bb9c59a4415a8a7ee84c7c5454b48a`](https://github.com/block/buzz/commit/54c8ef30a9bb9c59a4415a8a7ee84c7c5454b48a))
+- fix(release): make desktop tagging squash-safe ([#3965](https://github.com/block/buzz/pull/3965)) ([`db7e84d4f815127236b9cb080c5d374f48eaac09`](https://github.com/block/buzz/commit/db7e84d4f815127236b9cb080c5d374f48eaac09))
+- docs(nips): add single-coordinate manual-unread override layer and verification model to NIP-RS ([#2864](https://github.com/block/buzz/pull/2864)) ([`209536ade6c5ebf7fa82671d7ca0b74f599a40cc`](https://github.com/block/buzz/commit/209536ade6c5ebf7fa82671d7ca0b74f599a40cc))
+- fix(release): make immutable desktop release operable ([#3943](https://github.com/block/buzz/pull/3943)) ([`052174a148f9f6bcbb2b5a1d20ce0317645e49f8`](https://github.com/block/buzz/commit/052174a148f9f6bcbb2b5a1d20ce0317645e49f8))
+- docs: add VISION_REMOTE_AGENTS.md ([#3924](https://github.com/block/buzz/pull/3924)) ([`689617af7ad420c3266d5d2eb437757371327089`](https://github.com/block/buzz/commit/689617af7ad420c3266d5d2eb437757371327089))
+- fix(relay): align NIP-11 max_limit with REQ ceiling ([#3635](https://github.com/block/buzz/pull/3635)) ([`23f0c26b1ceba8e07bf3c160a1e08c7bda82ccd9`](https://github.com/block/buzz/commit/23f0c26b1ceba8e07bf3c160a1e08c7bda82ccd9))
+- fix(db): isolate usage metrics advisory-lock test on scratch DB ([#3670](https://github.com/block/buzz/pull/3670)) ([`dba97eecd9d8659c9c816cd6666fa6d687b6bca1`](https://github.com/block/buzz/commit/dba97eecd9d8659c9c816cd6666fa6d687b6bca1))
+- feat(release): make desktop releases immutable ([#3568](https://github.com/block/buzz/pull/3568)) ([`1dfd89ea67b4ebce0c4d10390f280ed4e7ddde8a`](https://github.com/block/buzz/commit/1dfd89ea67b4ebce0c4d10390f280ed4e7ddde8a))
+- Render mobile agent mention chips ([#3702](https://github.com/block/buzz/pull/3702)) ([`06582ee6f09e5f7454e4d8895d80a45c3cdb5e8a`](https://github.com/block/buzz/commit/06582ee6f09e5f7454e4d8895d80a45c3cdb5e8a))
+- fix(acp): preserve truncated thread context ([#3340](https://github.com/block/buzz/pull/3340)) ([`53771c8f5439f9c5c26876f0229bfcfe5da9b170`](https://github.com/block/buzz/commit/53771c8f5439f9c5c26876f0229bfcfe5da9b170))
+- docs(nips): specify kind:30621 multi-repo projects (NIP-MP) ([#3163](https://github.com/block/buzz/pull/3163)) ([`33bf7caa6ea474ccde2932c1ed05a90d7345c6e0`](https://github.com/block/buzz/commit/33bf7caa6ea474ccde2932c1ed05a90d7345c6e0))
+- feat(mobile): desktop-parity emoji and thread experience ([#3485](https://github.com/block/buzz/pull/3485)) ([`85edc0572a8540dedfa6562d40f0f875af0b5f61`](https://github.com/block/buzz/commit/85edc0572a8540dedfa6562d40f0f875af0b5f61))
+- fix(cli): resolve agents from owner records ([#3178](https://github.com/block/buzz/pull/3178)) ([`262f2392e3b7e09c78d582fb384672034d8551d5`](https://github.com/block/buzz/commit/262f2392e3b7e09c78d582fb384672034d8551d5))
+- feat(replica): portable heartbeat-token fence with snapshot-local reader routing ([#3268](https://github.com/block/buzz/pull/3268)) ([`63496cc1d4c6f1b7c613801bdcc694169dcf391a`](https://github.com/block/buzz/commit/63496cc1d4c6f1b7c613801bdcc694169dcf391a))
+
+[Compare v0.5.2...desktop-v0.5.3](https://github.com/block/buzz/compare/v0.5.2...desktop-v0.5.3)
+
+## v0.5.2
+
+- feat(cli): mirror Desktop mention delivery ([#3330](https://github.com/block/buzz/pull/3330)) ([`7adc46268`](https://github.com/block/buzz/commit/7adc46268d5e93f0b1d4dc8e700af22815dcac1b))
+- fix(desktop): deduplicate relay outage notification ([#3579](https://github.com/block/buzz/pull/3579)) ([`66e705492`](https://github.com/block/buzz/commit/66e7054928cc29395f828467c3e8c81b7408dd29))
+- fix(desktop): reconcile thread arrivals at bottom ([#3585](https://github.com/block/buzz/pull/3585)) ([`b42a8d447`](https://github.com/block/buzz/commit/b42a8d447e3a2b85b2313dc4fdd123731fd8bba3))
+- Improve emoji autocomplete matching ([#3571](https://github.com/block/buzz/pull/3571)) ([`259de6afb`](https://github.com/block/buzz/commit/259de6afbe0cc0d106e57ebdb2323064990e4122))
+- Fix shared agent avatar import profiles ([#3578](https://github.com/block/buzz/pull/3578)) ([`324bd6b46`](https://github.com/block/buzz/commit/324bd6b464de5751e12abbd155376046ce3d2afc))
+- Fix inline raster avatars in agent catalog ([#3581](https://github.com/block/buzz/pull/3581)) ([`7e9b77f72`](https://github.com/block/buzz/commit/7e9b77f72d82e019a99f074f1c9829be30c57ae1))
+- feat(agent): make Gemini and MLflow-route models usable through databricks_v2 ([#3569](https://github.com/block/buzz/pull/3569)) ([`4a1ebf25c`](https://github.com/block/buzz/commit/4a1ebf25c782fc6a68f0a69e6f866f793a259a1f))
+
+
+## v0.5.1
+
+- perf(desktop): move observer-feed archive and decrypt commands off main thread ([#3415](https://github.com/block/buzz/pull/3415)) ([`294c8c821`](https://github.com/block/buzz/commit/294c8c821de51442a8c384c0bdb66b1a10224ca0))
+- fix(desktop): preserve shared agent fidelity ([#3553](https://github.com/block/buzz/pull/3553)) ([`f7a3988ba`](https://github.com/block/buzz/commit/f7a3988ba13b590d9a55a7e8413fc3fb5ffbef18))
+- feat(agent): route Claude/GPT model families to their native gateway wire ([#3538](https://github.com/block/buzz/pull/3538)) ([`6438dedf8`](https://github.com/block/buzz/commit/6438dedf83a9dbe1853e484326911bf6c7f1618c))
+- Refine community invite limits ([#3529](https://github.com/block/buzz/pull/3529)) ([`24d90d128`](https://github.com/block/buzz/commit/24d90d1280a9325c6cbcf8eea30ac54db5afd2cb))
+- feat(agent): fix Anthropic prompt caching with Databricks (+ MCP proxy/TLS passthrough) ([#3463](https://github.com/block/buzz/pull/3463)) ([`c405ad1d4`](https://github.com/block/buzz/commit/c405ad1d4b1da061c11b3d26761252d41dcc62d3))
+- feat: add explicit entry for claude-opus-5 in model config ([#2831](https://github.com/block/buzz/pull/2831)) ([`90e058ebf`](https://github.com/block/buzz/commit/90e058ebf68137e048a409aec6616519379ff726))
+- fix(desktop): clear stale thread new-message pill ([#3411](https://github.com/block/buzz/pull/3411)) ([`55a3ed7b9`](https://github.com/block/buzz/commit/55a3ed7b9217cee5b23e0a5441947dc929b2a38c))
+- fix(ci): ratchet file sizes against the base tree ([#3352](https://github.com/block/buzz/pull/3352)) ([`9227bdf58`](https://github.com/block/buzz/commit/9227bdf58ad6664ae3c1078888f2181ec19c4da4))
+- feat(desktop): apply WebKit rendering workarounds at startup on Linux ([#3271](https://github.com/block/buzz/pull/3271)) ([`3ece4461d`](https://github.com/block/buzz/commit/3ece4461df8a7b9663a8e68327483b8377d4086d))
+- fix(desktop): stabilize flaky DM expansion E2E ordering assertions ([#2004](https://github.com/block/buzz/pull/2004)) ([`913d564ce`](https://github.com/block/buzz/commit/913d564ce0f35924291bf3eeab6508517a6d8d1f))
+- fix(desktop): paint community rail full height ([#3382](https://github.com/block/buzz/pull/3382)) ([`1d3b810ad`](https://github.com/block/buzz/commit/1d3b810ad70d6325718ed91e723f32c4a376d5e1))
+- feat(desktop): add custom harness inline from agent dialogs ([#3252](https://github.com/block/buzz/pull/3252)) ([`b0503d80c`](https://github.com/block/buzz/commit/b0503d80c298b1ece3b0a43b41d316829a3379e7))
+- feat(desktop): refine agent catalog sharing ([#2439](https://github.com/block/buzz/pull/2439)) ([`a35771fc4`](https://github.com/block/buzz/commit/a35771fc441cdc3c6f517f419037206783b502d2))
+- fix(desktop): keep drafts out of the Inbox All view ([#3217](https://github.com/block/buzz/pull/3217)) ([`3afa129ee`](https://github.com/block/buzz/commit/3afa129ee785cc74d921d0ba969254a8255c4cc0))
+- fix(desktop): restore the inbox icon in the sidebar ([#3341](https://github.com/block/buzz/pull/3341)) ([`00ede2e7a`](https://github.com/block/buzz/commit/00ede2e7aa7eb95571b7db3ebbd163adbf6cf74e))
+- fix(desktop): gate codex-acp on a minimum supported version ([#3254](https://github.com/block/buzz/pull/3254)) ([`4e3998f36`](https://github.com/block/buzz/commit/4e3998f36e36d68b9a93dcbd85f0864450bb8f5f))
+- feat(cli): add users set-status command for NIP-38 profile status ([#3253](https://github.com/block/buzz/pull/3253)) ([`60158fce3`](https://github.com/block/buzz/commit/60158fce3e670f11bb35d42627857ccaea50ff06))
+- fix(composer): scope multiline block formatting ([#3246](https://github.com/block/buzz/pull/3246)) ([`5457c947a`](https://github.com/block/buzz/commit/5457c947a74f5ba4b979f9c6411aa7626a858387))
+
+
## v0.5.0
- feat(invites): add use-limited invite links ([#3141](https://github.com/block/buzz/pull/3141)) ([`d500c2d5c`](https://github.com/block/buzz/commit/d500c2d5cf5d9aabe0ca4ebebfcafdbe5f5b7fd3))
diff --git a/Cargo.lock b/Cargo.lock
index 3b60dc4579..937ead564a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -43,6 +43,20 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if 1.0.4",
+ "getrandom 0.3.4",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.4"
@@ -131,7 +145,7 @@ checksum = "5d0a66767aaf7d483c556386fb68ca2fba9347684d8bb17a4bd8b755851870f7"
dependencies = [
"arrayvec",
"aws-lc-rs",
- "base64",
+ "base64 0.22.1",
"byteorder",
"minicbor",
"rustls-pki-types",
@@ -396,13 +410,23 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+[[package]]
+name = "atomic-write-file"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84790c55b5704b0d35130bf16a4ce22a8e70eb0ea773522557524d9a4852663d"
+dependencies = [
+ "nix 0.30.1",
+ "rand 0.9.4",
+]
+
[[package]]
name = "attohttpc"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9"
dependencies = [
- "base64",
+ "base64 0.22.1",
"http",
"log",
"rustls",
@@ -473,7 +497,7 @@ checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core",
"axum-macros",
- "base64",
+ "base64 0.22.1",
"bytes",
"form_urlencoded",
"futures-util",
@@ -549,6 +573,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -567,6 +597,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
+[[package]]
+name = "beef"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
+
[[package]]
name = "bip39"
version = "2.2.2"
@@ -765,7 +801,7 @@ name = "buzz-acp"
version = "0.1.0"
dependencies = [
"anyhow",
- "base64",
+ "base64 0.22.1",
"buzz-core",
"buzz-persona",
"buzz-sdk",
@@ -825,7 +861,7 @@ dependencies = [
"arc-swap",
"async-trait",
"axum",
- "base64",
+ "base64 0.22.1",
"getrandom 0.4.3",
"hex",
"nix 0.31.3",
@@ -879,12 +915,32 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "buzz-backend-kubernetes"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "hex",
+ "http",
+ "http-body-util",
+ "k8s-openapi",
+ "kube",
+ "nostr",
+ "rand 0.10.1",
+ "rustls",
+ "serde",
+ "serde_json",
+ "sha2 0.11.0",
+ "tokio",
+ "tower",
+]
+
[[package]]
name = "buzz-cli"
version = "0.1.0"
dependencies = [
"axum",
- "base64",
+ "base64 0.22.1",
"buzz-core",
"buzz-persona",
"buzz-sdk",
@@ -925,7 +981,7 @@ dependencies = [
name = "buzz-core"
version = "0.1.0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"chrono",
"hex",
"hmac 0.13.0",
@@ -949,6 +1005,8 @@ dependencies = [
"buzz-core",
"chrono",
"hex",
+ "metrics",
+ "metrics-util",
"nostr",
"rand 0.10.1",
"serde",
@@ -965,7 +1023,7 @@ dependencies = [
name = "buzz-dev-mcp"
version = "0.1.0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"buzz-cli",
"buzz-core",
"git-credential-nostr",
@@ -1092,7 +1150,7 @@ dependencies = [
"appattest",
"async-trait",
"axum",
- "base64",
+ "base64 0.22.1",
"byteorder",
"chrono",
"getrandom 0.4.3",
@@ -1127,7 +1185,7 @@ dependencies = [
"async-compression",
"async-trait",
"axum",
- "base64",
+ "base64 0.22.1",
"buzz-audit",
"buzz-auth",
"buzz-conformance",
@@ -1237,8 +1295,9 @@ name = "buzz-test-client"
version = "0.1.0"
dependencies = [
"anyhow",
- "base64",
+ "base64 0.22.1",
"buzz-core",
+ "buzz-media",
"buzz-sdk",
"buzz-ws-client",
"chrono",
@@ -1262,6 +1321,25 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "buzz-voice"
+version = "0.1.0"
+dependencies = [
+ "atomic-write-file",
+ "hex",
+ "ort",
+ "ort-sys",
+ "rand 0.10.1",
+ "sentencepiece-model",
+ "serde",
+ "serde_json",
+ "sha2 0.11.0",
+ "sherpa-onnx",
+ "symphonia",
+ "tempfile",
+ "tokenizers",
+]
+
[[package]]
name = "buzz-workflow"
version = "0.1.0"
@@ -1329,6 +1407,26 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
[[package]]
name = "castaway"
version = "0.2.4"
@@ -1577,6 +1675,7 @@ dependencies = [
"itoa",
"rustversion",
"ryu",
+ "serde",
"static_assertions",
]
@@ -2137,6 +2236,15 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "dary_heap"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "dashmap"
version = "6.2.1"
@@ -2174,7 +2282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
dependencies = [
"data-encoding",
- "syn 1.0.109",
+ "syn 2.0.117",
]
[[package]]
@@ -2626,6 +2734,12 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "esaxx-rs"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6"
+
[[package]]
name = "etcetera"
version = "0.11.0"
@@ -2683,6 +2797,12 @@ dependencies = [
"smallvec",
]
+[[package]]
+name = "extended"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
+
[[package]]
name = "fancy-regex"
version = "0.11.0"
@@ -2693,6 +2813,17 @@ dependencies = [
"regex",
]
+[[package]]
+name = "fancy-regex"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
+dependencies = [
+ "bit-set 0.8.0",
+ "regex-automata",
+ "regex-syntax",
+]
+
[[package]]
name = "fast-srgb8"
version = "1.0.0"
@@ -3084,7 +3215,7 @@ dependencies = [
name = "git-credential-nostr"
version = "0.1.0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"nostr",
"serde_json",
"zeroize",
@@ -3094,7 +3225,7 @@ dependencies = [
name = "git-sign-nostr"
version = "0.1.0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"chrono",
"hex",
"libc",
@@ -3269,7 +3400,7 @@ version = "1.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f89305dc8fe34e165eaf0eb12b6e294e12381d9df9a431bcc52a5809bab4319"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bon",
"bytes",
"futures",
@@ -3532,6 +3663,7 @@ dependencies = [
"http",
"hyper",
"hyper-util",
+ "log",
"rustls",
"rustls-native-certs",
"tokio",
@@ -3575,7 +3707,7 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"futures-channel",
"futures-util",
@@ -4192,6 +4324,31 @@ dependencies = [
"ucd-trie",
]
+[[package]]
+name = "jsonpath-rust"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "regex",
+ "serde_json",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "k8s-openapi"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06d9e5e61dd037cdc51da0d7e2b2be10f497478ea7e120d85dad632adb99882b"
+dependencies = [
+ "base64 0.22.1",
+ "chrono",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "kasuari"
version = "0.4.12"
@@ -4237,6 +4394,70 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a"
+[[package]]
+name = "kube"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e7bb0b6a46502cc20e4575b6ff401af45cfea150b34ba272a3410b78aa014e"
+dependencies = [
+ "k8s-openapi",
+ "kube-client",
+ "kube-core",
+]
+
+[[package]]
+name = "kube-client"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4987d57a184d2b5294fdad3d7fc7f278899469d21a4da39a8f6ca16426567a36"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "chrono",
+ "either",
+ "futures",
+ "home",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-timeout",
+ "hyper-util",
+ "jsonpath-rust",
+ "k8s-openapi",
+ "kube-core",
+ "pem",
+ "rustls",
+ "secrecy",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tracing",
+]
+
+[[package]]
+name = "kube-core"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914bbb770e7bb721a06e3538c0edd2babed46447d128f7c21caa68747060ee73"
+dependencies = [
+ "chrono",
+ "derive_more",
+ "form_urlencoded",
+ "http",
+ "k8s-openapi",
+ "serde",
+ "serde-value",
+ "serde_json",
+ "thiserror 2.0.18",
+]
+
[[package]]
name = "lab"
version = "0.11.0"
@@ -4359,6 +4580,39 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+[[package]]
+name = "logos"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458"
+dependencies = [
+ "logos-derive",
+]
+
+[[package]]
+name = "logos-codegen"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f"
+dependencies = [
+ "beef",
+ "fnv",
+ "lazy_static",
+ "proc-macro2",
+ "quote",
+ "regex-syntax",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "logos-derive"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d"
+dependencies = [
+ "logos-codegen",
+]
+
[[package]]
name = "loom"
version = "0.7.2"
@@ -4418,6 +4672,22 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "macro_rules_attribute"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3ae8f6d608c795738406608304d30a2dfbdc8e58e44f7ba43236da5208ded3c"
+dependencies = [
+ "macro_rules_attribute-proc_macro",
+ "pastey",
+]
+
+[[package]]
+name = "macro_rules_attribute-proc_macro"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c"
+
[[package]]
name = "matchers"
version = "0.2.0"
@@ -4433,6 +4703,16 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+[[package]]
+name = "matrixmultiply"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7"
+dependencies = [
+ "autocfg",
+ "rawpointer",
+]
+
[[package]]
name = "maybe-async"
version = "0.2.11"
@@ -4498,8 +4778,8 @@ dependencies = [
[[package]]
name = "mesh-llm-api-client"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"hex",
"mesh-llm-client",
@@ -4508,8 +4788,8 @@ dependencies = [
[[package]]
name = "mesh-llm-api-server"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -4519,17 +4799,17 @@ dependencies = [
[[package]]
name = "mesh-llm-build-info"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "mesh-llm-client"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
- "base64",
+ "base64 0.22.1",
"bytes",
"crypto_box",
"ed25519-dalek",
@@ -4542,7 +4822,7 @@ dependencies = [
"mesh-llm-types",
"model-artifact",
"nostr-sdk",
- "prost",
+ "prost 0.14.3",
"rand 0.10.1",
"rustls",
"serde",
@@ -4556,8 +4836,8 @@ dependencies = [
[[package]]
name = "mesh-llm-config"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4572,8 +4852,8 @@ dependencies = [
[[package]]
name = "mesh-llm-embedded-runtime"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-host-runtime",
@@ -4582,8 +4862,8 @@ dependencies = [
[[package]]
name = "mesh-llm-events"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"clap",
@@ -4594,8 +4874,8 @@ dependencies = [
[[package]]
name = "mesh-llm-gpu-bench"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"cc",
@@ -4607,8 +4887,8 @@ dependencies = [
[[package]]
name = "mesh-llm-guardrails"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
"serde_json",
@@ -4616,22 +4896,22 @@ dependencies = [
[[package]]
name = "mesh-llm-hardware-profile"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"mesh-llm-native-runtime",
]
[[package]]
name = "mesh-llm-host-runtime"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"argon2",
"async-trait",
"axum",
- "base64",
+ "base64 0.22.1",
"bytes",
"chacha20poly1305",
"chrono",
@@ -4647,7 +4927,6 @@ dependencies = [
"http",
"http-body-util",
"httparse",
- "if-addrs",
"iroh",
"json5",
"keyring",
@@ -4681,7 +4960,7 @@ dependencies = [
"opentelemetry 0.31.0",
"opentelemetry-otlp 0.31.1",
"opentelemetry_sdk 0.31.0",
- "prost",
+ "prost 0.14.3",
"rand 0.10.1",
"regex-lite",
"reqwest 0.12.28",
@@ -4695,6 +4974,7 @@ dependencies = [
"serde_yaml",
"sha2 0.10.9",
"skippy-coordinator",
+ "skippy-ffi",
"skippy-protocol",
"skippy-runtime",
"skippy-server",
@@ -4717,11 +4997,11 @@ dependencies = [
[[package]]
name = "mesh-llm-identity"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"argon2",
- "base64",
+ "base64 0.22.1",
"chacha20poly1305",
"chrono",
"crypto_box",
@@ -4739,8 +5019,8 @@ dependencies = [
[[package]]
name = "mesh-llm-native-runtime"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"serde",
@@ -4750,8 +5030,8 @@ dependencies = [
[[package]]
name = "mesh-llm-node"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-types",
@@ -4764,13 +5044,13 @@ dependencies = [
[[package]]
name = "mesh-llm-plugin"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
- "prost",
- "prost-build",
+ "prost 0.14.3",
+ "prost-build 0.14.3",
"protoc-bin-vendored",
"rmcp",
"schemars",
@@ -4781,8 +5061,8 @@ dependencies = [
[[package]]
name = "mesh-llm-plugin-manager"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4792,6 +5072,7 @@ dependencies = [
"reqwest 0.12.28",
"serde",
"serde_json",
+ "sha2 0.10.9",
"tar",
"tempfile",
"zip",
@@ -4799,29 +5080,29 @@ dependencies = [
[[package]]
name = "mesh-llm-protocol"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"hex",
"iroh",
- "prost",
+ "prost 0.14.3",
"serde_json",
"sha2 0.10.9",
]
[[package]]
name = "mesh-llm-routing"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"iroh",
]
[[package]]
name = "mesh-llm-runtime-install"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4843,8 +5124,8 @@ dependencies = [
[[package]]
name = "mesh-llm-sdk"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -4858,8 +5139,8 @@ dependencies = [
[[package]]
name = "mesh-llm-skills"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4869,8 +5150,8 @@ dependencies = [
[[package]]
name = "mesh-llm-system"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"chrono",
@@ -4892,8 +5173,8 @@ dependencies = [
[[package]]
name = "mesh-llm-types"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"hex",
"serde",
@@ -4903,13 +5184,13 @@ dependencies = [
[[package]]
name = "mesh-llm-ui"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "mesh-mixture-of-agents"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"async-trait",
"mesh-llm-guardrails",
@@ -4936,7 +5217,7 @@ version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108"
dependencies = [
- "base64",
+ "base64 0.22.1",
"evmap",
"http-body-util",
"hyper",
@@ -4974,6 +5255,28 @@ dependencies = [
"sketches-ddsketch",
]
+[[package]]
+name = "miette"
+version = "7.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
+dependencies = [
+ "cfg-if 1.0.4",
+ "miette-derive",
+ "unicode-width 0.1.14",
+]
+
+[[package]]
+name = "miette-derive"
+version = "7.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "mime"
version = "0.3.17"
@@ -5035,8 +5338,8 @@ dependencies = [
[[package]]
name = "model-artifact"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -5046,8 +5349,8 @@ dependencies = [
[[package]]
name = "model-hf"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -5064,8 +5367,8 @@ dependencies = [
[[package]]
name = "model-package"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"bytes",
@@ -5084,16 +5387,16 @@ dependencies = [
[[package]]
name = "model-ref"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
]
[[package]]
name = "model-resolver"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"model-artifact",
@@ -5119,6 +5422,28 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "monostate"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67"
+dependencies = [
+ "monostate-impl",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "monostate-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "more-asserts"
version = "0.3.1"
@@ -5225,6 +5550,21 @@ dependencies = [
"tempfile",
]
+[[package]]
+name = "ndarray"
+version = "0.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
+dependencies = [
+ "matrixmultiply",
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "portable-atomic",
+ "portable-atomic-util",
+ "rawpointer",
+]
+
[[package]]
name = "ndk-context"
version = "0.1.1"
@@ -5373,9 +5713,9 @@ dependencies = [
[[package]]
name = "nix"
-version = "0.31.3"
+version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.13.0",
"cfg-if 1.0.4",
@@ -5384,8 +5724,20 @@ dependencies = [
]
[[package]]
-name = "nom"
-version = "7.1.3"
+name = "nix"
+version = "0.31.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+dependencies = [
+ "bitflags 2.13.0",
+ "cfg-if 1.0.4",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
@@ -5457,11 +5809,11 @@ dependencies = [
[[package]]
name = "nostr"
-version = "0.44.6"
+version = "0.44.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e826dd648489de2c5b293920e20b92932ef820302007c1987c758d4d06eeb2cf"
+checksum = "c7d3d987ea7078dc36947cde532637c472a229426702e4331dd7667325378bd9"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bech32",
"bip39",
"bitcoin_hashes",
@@ -5501,9 +5853,9 @@ dependencies = [
[[package]]
name = "nostr-relay-pool"
-version = "0.44.1"
+version = "0.44.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91b2c039df4f96c4bf7dae52a74fd5516ad6dda83a11c0c69dea91b5255a4f37"
+checksum = "c85c54d6ca9aae4ae2bf19a7663ba9db5f45f783f1d24aff55f006386b8b99a1"
dependencies = [
"async-utility",
"async-wsocket",
@@ -5812,8 +6164,8 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openai-frontend"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"async-trait",
"axum",
@@ -5932,7 +6284,7 @@ dependencies = [
"opentelemetry-http",
"opentelemetry-proto 0.31.0",
"opentelemetry_sdk 0.31.0",
- "prost",
+ "prost 0.14.3",
"reqwest 0.12.28",
"thiserror 2.0.18",
]
@@ -5947,7 +6299,7 @@ dependencies = [
"opentelemetry 0.32.0",
"opentelemetry-proto 0.32.0",
"opentelemetry_sdk 0.32.1",
- "prost",
+ "prost 0.14.3",
"thiserror 2.0.18",
"tokio",
"tonic",
@@ -5960,11 +6312,11 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f"
dependencies = [
- "base64",
+ "base64 0.22.1",
"const-hex",
"opentelemetry 0.31.0",
"opentelemetry_sdk 0.31.0",
- "prost",
+ "prost 0.14.3",
"serde",
"serde_json",
"tonic",
@@ -5979,7 +6331,7 @@ checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638"
dependencies = [
"opentelemetry 0.32.0",
"opentelemetry_sdk 0.32.1",
- "prost",
+ "prost 0.14.3",
"tonic",
"tonic-prost",
]
@@ -6023,6 +6375,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "ordered-float"
version = "4.6.0"
@@ -6061,6 +6422,24 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "ort"
+version = "2.0.0-rc.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133"
+dependencies = [
+ "ndarray",
+ "ort-sys",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "ort-sys"
+version = "2.0.0-rc.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90"
+
[[package]]
name = "os_str_bytes"
version = "6.6.1"
@@ -6185,6 +6564,16 @@ dependencies = [
"hmac 0.12.1",
]
+[[package]]
+name = "pem"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
+dependencies = [
+ "base64 0.22.1",
+ "serde_core",
+]
+
[[package]]
name = "pem-rfc7468"
version = "1.0.0"
@@ -6242,6 +6631,16 @@ dependencies = [
"pest",
]
+[[package]]
+name = "petgraph"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
+dependencies = [
+ "fixedbitset 0.5.7",
+ "indexmap",
+]
+
[[package]]
name = "petgraph"
version = "0.8.3"
@@ -6374,7 +6773,7 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"
dependencies = [
- "base64",
+ "base64 0.22.1",
"indexmap",
"quick-xml 0.39.4",
"serde",
@@ -6440,13 +6839,22 @@ dependencies = [
"serde",
]
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
+dependencies = [
+ "portable-atomic",
+]
+
[[package]]
name = "portmapper"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3713e4977408279158444a18c1a01ac9bf2e7eaf1fbfd1a19ac9cd18d90721"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"derive_more",
"hyper-util",
@@ -6616,6 +7024,16 @@ dependencies = [
"unarray",
]
+[[package]]
+name = "prost"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
+dependencies = [
+ "bytes",
+ "prost-derive 0.13.5",
+]
+
[[package]]
name = "prost"
version = "0.14.3"
@@ -6623,7 +7041,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
dependencies = [
"bytes",
- "prost-derive",
+ "prost-derive 0.14.3",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
+dependencies = [
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph 0.7.1",
+ "prettyplease",
+ "prost 0.13.5",
+ "prost-types 0.13.5",
+ "regex",
+ "syn 2.0.117",
+ "tempfile",
]
[[package]]
@@ -6636,15 +7074,28 @@ dependencies = [
"itertools",
"log",
"multimap",
- "petgraph",
+ "petgraph 0.8.3",
"prettyplease",
- "prost",
- "prost-types",
+ "prost 0.14.3",
+ "prost-types 0.14.3",
"regex",
"syn 2.0.117",
"tempfile",
]
+[[package]]
+name = "prost-derive"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "prost-derive"
version = "0.14.3"
@@ -6658,13 +7109,35 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "prost-reflect"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5edd582b62f5cde844716e66d92565d7faf7ab1445c8cebce6e00fba83ddb2"
+dependencies = [
+ "logos",
+ "miette",
+ "once_cell",
+ "prost 0.13.5",
+ "prost-types 0.13.5",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
+dependencies = [
+ "prost 0.13.5",
+]
+
[[package]]
name = "prost-types"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
dependencies = [
- "prost",
+ "prost 0.14.3",
]
[[package]]
@@ -6731,6 +7204,33 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3"
+[[package]]
+name = "protox"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f352af331bf637b8ecc720f7c87bf903d2571fa2e14a66e9b2558846864b54a"
+dependencies = [
+ "bytes",
+ "miette",
+ "prost 0.13.5",
+ "prost-reflect",
+ "prost-types 0.13.5",
+ "protox-parse",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "protox-parse"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3a462d115462c080ae000c29a47f0b3985737e5d3a995fcdbcaa5c782068dde"
+dependencies = [
+ "logos",
+ "miette",
+ "prost-types 0.13.5",
+ "thiserror 1.0.69",
+]
+
[[package]]
name = "pulldown-cmark"
version = "0.13.4"
@@ -7036,7 +7536,7 @@ dependencies = [
"thiserror 2.0.18",
"unicode-segmentation",
"unicode-truncate",
- "unicode-width",
+ "unicode-width 0.2.2",
]
[[package]]
@@ -7099,7 +7599,7 @@ dependencies = [
"strum",
"time",
"unicode-segmentation",
- "unicode-width",
+ "unicode-width 0.2.2",
]
[[package]]
@@ -7111,6 +7611,43 @@ dependencies = [
"bitflags 2.13.0",
]
+[[package]]
+name = "rawpointer"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
+
+[[package]]
+name = "rayon"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-cond"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f"
+dependencies = [
+ "either",
+ "itertools",
+ "rayon",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
[[package]]
name = "redb"
version = "3.1.3"
@@ -7232,7 +7769,7 @@ version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"encoding_rs",
"futures-channel",
@@ -7280,7 +7817,7 @@ version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
@@ -7365,12 +7902,12 @@ dependencies = [
[[package]]
name = "rmcp"
-version = "1.7.0"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e"
+checksum = "1d1f571c72940a19d9532fe52dbea8bc9912bf1d766c2970bb824056b86f3f59"
dependencies = [
"async-trait",
- "base64",
+ "base64 0.22.1",
"bytes",
"chrono",
"futures",
@@ -7398,9 +7935,9 @@ dependencies = [
[[package]]
name = "rmcp-macros"
-version = "1.7.0"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d"
+checksum = "1aad0035b69380782d78ea95b508327e6deaa2235909053e596eea8f27b5e1d5"
dependencies = [
"darling 0.23.0",
"proc-macro2",
@@ -7438,7 +7975,7 @@ dependencies = [
"async-trait",
"aws-creds",
"aws-region",
- "base64",
+ "base64 0.22.1",
"bytes",
"cfg-if 1.0.4",
"futures-util",
@@ -7762,6 +8299,15 @@ dependencies = [
"cc",
]
+[[package]]
+name = "secrecy"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
+dependencies = [
+ "zeroize",
+]
+
[[package]]
name = "secret-service"
version = "4.0.0"
@@ -7839,6 +8385,18 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
+[[package]]
+name = "sentencepiece-model"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40b87bf750a8322c3236d7aa63c1f4a6862187d00d2d8b038e1dfe263bfe43ec"
+dependencies = [
+ "miette",
+ "prost 0.13.5",
+ "prost-build 0.13.5",
+ "protox",
+]
+
[[package]]
name = "serde"
version = "1.0.228"
@@ -7849,6 +8407,16 @@ dependencies = [
"serde_derive",
]
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float 2.10.1",
+ "serde",
+]
+
[[package]]
name = "serde_bytes"
version = "0.11.19"
@@ -8049,6 +8617,28 @@ dependencies = [
"os_str_bytes",
]
+[[package]]
+name = "sherpa-onnx"
+version = "1.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b142d3f255cb4e4b7808ea25869db6f5714e0a3550da355234483b4db552055"
+dependencies = [
+ "serde",
+ "serde_json",
+ "sherpa-onnx-sys",
+]
+
+[[package]]
+name = "sherpa-onnx-sys"
+version = "1.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffc951af03dc0653c0622158ca8a585a6f2bc43b7b06048cf0e5b5020005c227"
+dependencies = [
+ "bzip2",
+ "tar",
+ "ureq",
+]
+
[[package]]
name = "shlex"
version = "1.3.0"
@@ -8150,8 +8740,8 @@ checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b"
[[package]]
name = "skippy-cache"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"blake3",
@@ -8160,40 +8750,40 @@ dependencies = [
[[package]]
name = "skippy-coordinator"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "skippy-ffi"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"libloading",
]
[[package]]
name = "skippy-metrics"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "skippy-protocol"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
- "prost",
- "prost-build",
+ "prost 0.14.3",
+ "prost-build 0.14.3",
"protoc-bin-vendored",
"serde",
]
[[package]]
name = "skippy-runtime"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"libc",
@@ -8206,13 +8796,14 @@ dependencies = [
[[package]]
name = "skippy-server"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
+ "ahash",
"anyhow",
"async-trait",
"axum",
- "base64",
+ "base64 0.22.1",
"blake3",
"clap",
"futures-util",
@@ -8234,8 +8825,8 @@ dependencies = [
[[package]]
name = "skippy-topology"
-version = "0.73.1"
-source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
+version = "0.74.0"
+source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
"serde_json",
@@ -8319,6 +8910,18 @@ dependencies = [
"der",
]
+[[package]]
+name = "spm_precompiled"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326"
+dependencies = [
+ "base64 0.13.1",
+ "nom",
+ "serde",
+ "unicode-segmentation",
+]
+
[[package]]
name = "sprig"
version = "0.1.0"
@@ -8347,7 +8950,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"cfg-if 1.0.4",
"chrono",
@@ -8453,7 +9056,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e"
dependencies = [
"atoi",
- "base64",
+ "base64 0.22.1",
"bitflags 2.13.0",
"byteorder",
"chrono",
@@ -8594,6 +9197,164 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
+[[package]]
+name = "symphonia"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039"
+dependencies = [
+ "lazy_static",
+ "symphonia-bundle-flac",
+ "symphonia-bundle-mp3",
+ "symphonia-codec-aac",
+ "symphonia-codec-alac",
+ "symphonia-codec-pcm",
+ "symphonia-codec-vorbis",
+ "symphonia-core",
+ "symphonia-format-isomp4",
+ "symphonia-format-ogg",
+ "symphonia-format-riff",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-bundle-flac"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976"
+dependencies = [
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-bundle-mp3"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed"
+dependencies = [
+ "lazy_static",
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-codec-aac"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790"
+dependencies = [
+ "lazy_static",
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "symphonia-codec-alac"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab"
+dependencies = [
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "symphonia-codec-pcm"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95"
+dependencies = [
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "symphonia-codec-vorbis"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73"
+dependencies = [
+ "log",
+ "symphonia-core",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-core"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af"
+dependencies = [
+ "arrayvec",
+ "bitflags 1.3.2",
+ "bytemuck",
+ "lazy_static",
+ "log",
+]
+
+[[package]]
+name = "symphonia-format-isomp4"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5"
+dependencies = [
+ "encoding_rs",
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-format-ogg"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb"
+dependencies = [
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-format-riff"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f"
+dependencies = [
+ "extended",
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-metadata"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16"
+dependencies = [
+ "encoding_rs",
+ "lazy_static",
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "symphonia-utils-xiph"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16"
+dependencies = [
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
[[package]]
name = "syn"
version = "1.0.109"
@@ -8691,7 +9452,7 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432"
dependencies = [
- "unicode-width",
+ "unicode-width 0.2.2",
]
[[package]]
@@ -8765,9 +9526,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
dependencies = [
"anyhow",
- "base64",
+ "base64 0.22.1",
"bitflags 2.13.0",
- "fancy-regex",
+ "fancy-regex 0.11.0",
"filedescriptor",
"finl_unicode",
"fixedbitset 0.4.2",
@@ -8917,6 +9678,39 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+[[package]]
+name = "tokenizers"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223"
+dependencies = [
+ "ahash",
+ "aho-corasick",
+ "compact_str 0.9.1",
+ "dary_heap",
+ "derive_builder",
+ "esaxx-rs",
+ "fancy-regex 0.14.0",
+ "getrandom 0.3.4",
+ "itertools",
+ "log",
+ "macro_rules_attribute",
+ "monostate",
+ "paste",
+ "rand 0.9.4",
+ "rayon",
+ "rayon-cond",
+ "regex",
+ "regex-syntax",
+ "serde",
+ "serde_json",
+ "spm_precompiled",
+ "thiserror 2.0.18",
+ "unicode-normalization-alignments",
+ "unicode-segmentation",
+ "unicode_categories",
+]
+
[[package]]
name = "tokio"
version = "1.52.3"
@@ -9052,7 +9846,7 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad543404f98bfc969aeb71994105c592acfc6c43323fddcd016bb208d1c65cb"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"futures-core",
"futures-sink",
@@ -9153,7 +9947,7 @@ checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
dependencies = [
"async-trait",
"axum",
- "base64",
+ "base64 0.22.1",
"bytes",
"h2",
"http",
@@ -9182,7 +9976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
dependencies = [
"bytes",
- "prost",
+ "prost 0.14.3",
"tonic",
]
@@ -9192,8 +9986,8 @@ version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8"
dependencies = [
- "prost",
- "prost-types",
+ "prost 0.14.3",
+ "prost-types 0.14.3",
"tonic",
]
@@ -9223,6 +10017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"async-compression",
+ "base64 0.22.1",
"bitflags 2.13.0",
"bytes",
"futures-core",
@@ -9482,6 +10277,15 @@ dependencies = [
"tinyvec",
]
+[[package]]
+name = "unicode-normalization-alignments"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de"
+dependencies = [
+ "smallvec",
+]
+
[[package]]
name = "unicode-properties"
version = "0.1.4"
@@ -9502,9 +10306,15 @@ checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
dependencies = [
"itertools",
"unicode-segmentation",
- "unicode-width",
+ "unicode-width 0.2.2",
]
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
[[package]]
name = "unicode-width"
version = "0.2.2"
@@ -9517,6 +10327,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
[[package]]
name = "universal-hash"
version = "0.5.1"
@@ -9539,6 +10355,22 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+[[package]]
+name = "ureq"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
+dependencies = [
+ "base64 0.22.1",
+ "flate2",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-pki-types",
+ "url",
+ "webpki-roots 0.26.11",
+]
+
[[package]]
name = "url"
version = "2.5.8"
@@ -10530,7 +11362,7 @@ checksum = "3e1e496dcbe6a09017acdfaf48e1a646735e7ff5b2a49e2c7e081cca77a59bc8"
dependencies = [
"anyhow",
"async-trait",
- "base64",
+ "base64 0.22.1",
"bytes",
"clap",
"crc32fast",
@@ -10567,7 +11399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb838aa8eb67d730af301584cf003caad407487606058292a6750711b603fbee"
dependencies = [
"async-trait",
- "base64",
+ "base64 0.22.1",
"blake3",
"bytemuck",
"bytes",
diff --git a/Cargo.toml b/Cargo.toml
index 3ac7ee4cce..cc1dd0f9df 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,8 @@ members = [
"crates/buzz-pair-relay",
"crates/buzz-relay-mesh",
"crates/buzz-dev-mcp",
+ "crates/buzz-voice",
+ "crates/buzz-backend-kubernetes",
"examples/countdown-bot",
]
exclude = ["desktop/src-tauri"]
@@ -57,6 +59,13 @@ sqlx = { version = "0.9", features = [
redis = { version = "1.0", features = ["tokio-comp", "connection-manager", "tokio-rustls-comp"] }
deadpool-redis = { version = "0.23", features = ["rt_tokio_1"] }
+# Kubernetes (buzz-backend-kubernetes provider). No `ring` feature here: the
+# process-level CryptoProvider is installed explicitly at startup, matching
+# buzz-cli/buzz-acp/buzz-admin/buzz-relay/buzz-dev-mcp — see the comment on the
+# crate's own rustls dependency.
+kube = { version = "2.0", default-features = false, features = ["client", "rustls-tls"] }
+k8s-openapi = { version = "0.26", features = ["v1_31"] }
+
# Nostr
nostr = { version = "0.44", features = ["nip44", "nip98"] }
diff --git a/Dockerfile.sprig b/Dockerfile.sprig
new file mode 100644
index 0000000000..160e0b5662
--- /dev/null
+++ b/Dockerfile.sprig
@@ -0,0 +1,44 @@
+# syntax=docker/dockerfile:1.7
+# Multi-arch is produced by building this file on native amd64 and arm64 runners.
+# Keep both bases pinned to manifest-list digests so either architecture resolves
+# to immutable source bytes.
+FROM rust:1.95-alpine3.22@sha256:064dfc925d68d1a63f4fd2871bd7dc6e6ea56692989a487185855d62885d90aa AS builder
+
+RUN apk add --no-cache \
+ build-base \
+ cmake \
+ git \
+ musl-dev \
+ openssl-dev \
+ openssl-libs-static \
+ perl \
+ pkgconf \
+ protoc
+WORKDIR /build
+COPY . .
+RUN cargo build --locked --profile sprig -p sprig \
+ && strip target/sprig/sprig
+
+FROM alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
+
+RUN apk add --no-cache bash ca-certificates curl git \
+ && adduser -D -h /home/agent agent \
+ && install -d -o agent -g agent /workspace /home/agent \
+ && git config --system gpg.format x509 \
+ && git config --system gpg.x509.program /usr/local/bin/git-sign-nostr \
+ && git config --system commit.gpgSign true \
+ && git config --system tag.gpgSign true
+
+COPY --from=builder --chmod=0755 /build/target/sprig/sprig /usr/local/bin/sprig
+COPY --chmod=0755 scripts/sprig-entrypoint.sh /usr/local/bin/sprig-entrypoint
+RUN for name in \
+ buzz-acp buzz-agent buzz-dev-mcp rg tree buzz \
+ git-credential-nostr git-sign-nostr; do \
+ ln -s sprig "/usr/local/bin/$name"; \
+ done
+
+ENV HOME=/home/agent \
+ PATH=/usr/local/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin
+WORKDIR /home/agent
+USER agent
+ENTRYPOINT ["/usr/local/bin/sprig-entrypoint"]
diff --git a/Justfile b/Justfile
index bcef8983bc..d6e86c8d09 100644
--- a/Justfile
+++ b/Justfile
@@ -155,7 +155,11 @@ _ensure-sidecar-stubs:
set -euo pipefail
TARGET=$(rustc -vV | sed -n 's|host: ||p')
mkdir -p desktop/src-tauri/binaries
- for bin in buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz; do
+ SIDECARS=(buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz)
+ if [[ "$TARGET" != *windows* ]]; then
+ SIDECARS+=(buzz-backend-kubernetes)
+ fi
+ for bin in "${SIDECARS[@]}"; do
touch "desktop/src-tauri/binaries/${bin}-${TARGET}"
done
@@ -236,6 +240,9 @@ desktop-release-build target="aarch64-apple-darwin":
mkdir -p desktop/src-tauri/binaries
touch "desktop/src-tauri/binaries/buzz-acp-$TARGET"
touch "desktop/src-tauri/binaries/buzz-agent-$TARGET"
+ if [[ "$TARGET" != *windows* ]]; then
+ touch "desktop/src-tauri/binaries/buzz-backend-kubernetes-$TARGET"
+ fi
touch "desktop/src-tauri/binaries/buzz-dev-mcp-$TARGET"
touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET"
touch "desktop/src-tauri/binaries/buzz-$TARGET"
@@ -274,8 +281,11 @@ test:
# Run unit tests only (no infra needed)
test-unit:
#!/usr/bin/env bash
+ set -euo pipefail
if command -v cargo-nextest &>/dev/null; then
cargo nextest run -p buzz-core -p buzz-auth --lib
+ cargo nextest run -p buzz-voice --lib
+ cargo nextest run -p buzz-cli
# buzz-db migrator/lint tests: pure SQL-parsing unit tests (no infra).
# They guard the embedded-migrator invariant (exactly the consolidated
# 0001; cutover/backfill stays an operator script, not startup state)
@@ -291,6 +301,12 @@ test-unit:
# Gateway unit and black-box HTTP tests are infra-free. Postgres-backed
# contract/race tests run in the dedicated CI job below.
cargo nextest run -p buzz-push-gateway
+ # Kubernetes backend provider: the decision layers (state machine, GC
+ # planner, env precedence, naming, wire) are pure functions with a fake
+ # substrate, so they belong in the unit job. Enumerated explicitly
+ # because nothing in CI runs `cargo test --workspace` — workspace
+ # membership alone buys clippy/check, not a single executed test.
+ cargo nextest run -p buzz-backend-kubernetes
else
./scripts/run-tests.sh unit
fi
@@ -428,7 +444,7 @@ dev *ARGS: bootstrap _ensure-sidecar-stubs _ensure-migrations
fi
done
fi
- cargo build -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr -p buzz-relay
+ cargo build -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr -p buzz-relay
if [[ -n "{{mesh}}" ]]; then
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
fi
@@ -475,10 +491,10 @@ desktop-standalone *ARGS: _ensure-sidecar-stubs
#!/usr/bin/env bash
set -euo pipefail
export PATH="{{justfile_directory()}}/bin:$PATH"
- cargo build -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
+ cargo build -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
TARGET=$(rustc -vV | sed -n 's|host: ||p')
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
- for bin in buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz; do
+ for bin in buzz-acp buzz-agent buzz-backend-kubernetes buzz-dev-mcp git-credential-nostr buzz; do
cp "${TARGET_DIR}/debug/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
done
@@ -504,17 +520,26 @@ staging *ARGS: bootstrap _ensure-sidecar-stubs
set -euo pipefail
export PATH="{{justfile_directory()}}/bin:$PATH"
pnpm install # unconditional: staging must always start with a clean dep tree
- cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
+ cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
FEATURES=()
if [[ -n "{{mesh}}" ]]; then
FEATURES=(--features mesh-llm)
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
fi
- # Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
+ # Replace 0-byte sidecar stubs with real binaries so tauri dev picks them up.
+ # buzz: the CLI sidecar. buzz-backend-kubernetes: provider discovery scans the
+ # exe dir for executable buzz-backend-* files, so the non-executable stub that
+ # tauri dev copies next to the exe would hide the provider from "Run on".
TARGET=$(rustc -vV | sed -n 's|host: ||p')
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
- cp "${TARGET_DIR}/release/buzz" "desktop/src-tauri/binaries/buzz-${TARGET}"
- chmod +x "desktop/src-tauri/binaries/buzz-${TARGET}"
+ STAGING_SIDECARS=(buzz)
+ if [[ "$TARGET" != *windows* ]]; then
+ STAGING_SIDECARS+=(buzz-backend-kubernetes)
+ fi
+ for bin in "${STAGING_SIDECARS[@]}"; do
+ cp "${TARGET_DIR}/release/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
+ chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
+ done
cd {{desktop_dir}}
export BUZZ_RELAY_URL="wss://sprout-oss.stage.blox.sqprod.co"
source ../scripts/instance-env.sh
@@ -531,17 +556,26 @@ production *ARGS: bootstrap _ensure-sidecar-stubs
set -euo pipefail
export PATH="{{justfile_directory()}}/bin:$PATH"
pnpm install # unconditional: production must always start with a clean dep tree
- cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
+ cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
FEATURES=()
if [[ -n "{{mesh}}" ]]; then
FEATURES=(--features mesh-llm)
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
fi
- # Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
+ # Replace 0-byte sidecar stubs with real binaries so tauri dev picks them up.
+ # buzz: the CLI sidecar. buzz-backend-kubernetes: provider discovery scans the
+ # exe dir for executable buzz-backend-* files, so the non-executable stub that
+ # tauri dev copies next to the exe would hide the provider from "Run on".
TARGET=$(rustc -vV | sed -n 's|host: ||p')
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
- cp "${TARGET_DIR}/release/buzz" "desktop/src-tauri/binaries/buzz-${TARGET}"
- chmod +x "desktop/src-tauri/binaries/buzz-${TARGET}"
+ PRODUCTION_SIDECARS=(buzz)
+ if [[ "$TARGET" != *windows* ]]; then
+ PRODUCTION_SIDECARS+=(buzz-backend-kubernetes)
+ fi
+ for bin in "${PRODUCTION_SIDECARS[@]}"; do
+ cp "${TARGET_DIR}/release/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
+ chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
+ done
cd {{desktop_dir}}
export BUZZ_RELAY_URL="wss://buzz.block.builderlab.xyz"
source ../scripts/instance-env.sh
@@ -620,6 +654,11 @@ mobile-check:
mobile-test:
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter test
+# Regenerate the emoji dataset asset from desktop's emoji-mart install.
+# Output is committed — rerun after bumping @emoji-mart/data.
+mobile-emoji-data:
+ node {{mobile_dir}}/scripts/generate-emoji-data.mjs
+
# Compile an unsigned Android debug APK (worktree-aware debug identity)
mobile-build-android:
./scripts/mobile-worktree-overrides.sh
@@ -719,7 +758,7 @@ bump-relay-version version:
cargo update -p buzz-relay
echo "Bumped buzz-relay to {{ version }} and regenerated Cargo.lock"
-# Open or update the desktop release PR (signed desktop app)
+# Open or update the desktop release PR from an immutable origin/main snapshot
release-desktop *ARGS:
#!/usr/bin/env bash
set -euo pipefail
@@ -729,7 +768,7 @@ release-desktop *ARGS:
else
VERSION="$ARG"
fi
- just _release-pr desktop "$VERSION"
+ scripts/prepare-desktop-release.sh "$VERSION"
# Open or update the relay release PR (ghcr.io/block/buzz image)
release-relay *ARGS:
diff --git a/NOSTR.md b/NOSTR.md
index 59df31b991..cce70f2f77 100644
--- a/NOSTR.md
+++ b/NOSTR.md
@@ -39,7 +39,7 @@ just relay & # relay on :3000
PGPASSWORD=buzz_dev psql -h localhost -U buzz -d buzz -c \
"INSERT INTO pubkey_allowlist (pubkey) VALUES (decode('<64-char-hex-pubkey>', 'hex'))"
-# 5. Connect any NIP-29 + NIP-42 client to ws://localhost:3000
+# 4. Connect any NIP-29 + NIP-42 client to ws://localhost:3000
```
### What Works
@@ -163,6 +163,10 @@ nak req -k 9 --tag "h=" --stream \
nak event -k 7 -c "+" --tag "h=" --tag "e=" \
--auth --sec ws://localhost:3000
+# Subscribe to reactions to channel messages — include #h for live delivery (see note below)
+nak req -k 7 --tag "h=" --stream \
+ --auth --sec ws://localhost:3000
+
# Delete a message (#h optional; #e required; must be self-authored)
nak event -k 5 -c "reason" --tag "h=" --tag "e=" \
--auth --sec ws://localhost:3000
@@ -185,6 +189,14 @@ nak req -k 1059 --tag "p=" \
--auth --sec ws://localhost:3000
```
+> **Note:** The relay derives a reaction's channel from its `#e` target (client `#h` is
+> ignored for channel determination). Reactions to channel-scoped events are therefore
+> channel-scoped. Live fan-out keeps channel-scoped and global subscriptions strictly
+> separate, which means a kinds-only subscription (`{"kinds":[7]}`) receives none of
+> those reactions — subscribe with `{"kinds":[7],"#h":[""]}` instead.
+> `#h` matching works whether or not the signed reaction carries an `h` tag: explicit
+> `h` tags are matched directly, and tagless reactions match via their stored channel.
+
### Tested Clients (Direct)
| Client | Platform | Evidence | Notes |
@@ -354,3 +366,7 @@ but only admins/owners can set it. Full spec:
---
## Further Reading
+
+- [nostr-protocol/nips](https://github.com/nostr-protocol/nips) — the upstream NIP specifications (NIP-01, NIP-29, NIP-42, and the other NIPs referenced throughout this guide).
+- [`docs/nips/`](docs/nips/) — Buzz's own NIP extension documents.
+- [`ARCHITECTURE.md`](ARCHITECTURE.md) — event kinds, wire protocol, and relay internals.
diff --git a/README.md b/README.md
index 72af92ce13..56439f00bc 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
Forge ·
Agents ·
Architecture ·
+ Releasing ·
Apache 2.0
@@ -115,10 +116,30 @@ New to Buzz? Pick the path that matches you.
### I just want to try the app
-Grab a packaged build from the [latest release](https://github.com/block/buzz/releases/latest) — macOS (`.dmg`), Linux (`.AppImage` / `.deb`), or Windows (`.exe`). Install it like any other app.
+Grab a packaged build from the [latest release](https://github.com/block/buzz/releases/latest):
+
+| Platform | File |
+|---|---|
+| macOS (Apple Silicon) | `Buzz__aarch64.dmg` |
+| macOS (Intel) | `Buzz__x64.dmg` |
+| Linux (x86_64) | `Buzz__amd64.AppImage` or `Buzz__amd64.deb` |
+| Windows (x64) | `Buzz__x64-setup_alpha-unsigned.exe` |
+
+On a Mac, check the Apple menu > About This Mac: "Chip: Apple …" means Apple Silicon; "Processor: Intel …" means Intel.
+
+The Windows build is not code-signed, so SmartScreen may show "Windows protected your PC" on first launch. If available, click **More info**, then **Run anyway**.
+
By default the app connects to `ws://localhost:3000`. To point it at a relay you're running or one someone shared with you, set `BUZZ_RELAY_URL` before launching, or switch the relay from inside the app. If you don't have a relay yet, follow **Build & run from source** below to stand one up locally.
+### I want my own hosted relay
+
+To run a relay for your team without managing servers, you can deploy one to Railway in a click:
+
+[](https://railway.com/deploy/buzz-relay-block)
+
+See [here](https://engineering.block.xyz/blog/run-your-own-buzz-relay) for details.
+
### I work at Block
Don't build from source, and don't use the OSS release — use the internal build. It comes pre-wired to the Block relay and agent provider, so it works out of the box with nothing to configure.
diff --git a/RELEASING.md b/RELEASING.md
index 063b813e2c..e729f8b50c 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -5,7 +5,7 @@ Mobile uses immutable release-candidate tags cut directly from remote `main`:
| Lane | Entry point | Artifact |
|------|-------------|----------|
-| Desktop | `just release-desktop` | Signed desktop app (macOS/Linux) |
+| Desktop | `just release-desktop ` | Packaged desktop app (signed/notarized macOS, unsigned Windows, and Linux) |
| Relay | `just release-relay` | `ghcr.io/block/buzz` container image |
| Mobile | `scripts/mobile-release.sh candidate X.Y.Z` | Exact `mobile-vX.Y.Z-rc.N` source identity |
@@ -16,13 +16,17 @@ remains manual because OSS CI cannot trigger private CI.
## Quick Start
+Prepare desktop releases locally from an up-to-date, clean `main` checkout:
+
```sh
-# Desktop release (next patch version)
-just release-desktop
+just release-desktop 0.5.3
+```
-# Desktop explicit version
-just release-desktop 0.4.0
+The recipe generates the immutable candidate and opens or updates its pull
+request. Candidate branch creation uses the operator's GitHub permissions; the
+release App is intentionally limited to creating protected release tags.
+```sh
# Relay release
just release-relay
just release-relay 0.4.0
@@ -31,8 +35,9 @@ just release-relay 0.4.0
scripts/mobile-release.sh candidate 0.5.0
```
-Desktop and relay releases use metadata PRs. Mobile does not. Each
-`mobile-vX.Y.Z-rc.N` tag is an immutable candidate and the artifact of record.
+Desktop uses an immutable generated candidate PR; relay continues using its
+metadata PR. Mobile does not. Each `mobile-vX.Y.Z-rc.N` tag is an immutable
+candidate and the artifact of record.
There is no mobile release branch, stable mobile tag alias, finalization step,
or mobile GitHub Release.
@@ -42,12 +47,28 @@ or mobile GitHub Release.
### Desktop
-1. **`just release-desktop`** runs locally on `main`, creates or updates a
- `version-bump/` PR, bumps the desktop manifests, regenerates
- lockfiles, and updates `CHANGELOG.md`.
-2. **Merge the PR.** `auto-tag-on-release-pr-merge` pushes `v`.
-3. **The tag triggers `release.yml`.** It builds, signs, notarizes, and
- publishes the desktop app for macOS and Linux.
+1. Run `just release-desktop ` from a clean, up-to-date `main` checkout.
+ The script fetches the current `origin/main`, regenerates
+ `version-bump/` as one
+ deterministic candidate commit, records the frozen base and proposed
+ `desktop-v` tag in `.release/desktop-candidate.json`, updates every
+ desktop manifest and lockfile, writes a full-SHA changelog, and opens or
+ updates the PR.
+2. Review the recorded base and candidate SHA, the complete changelog, and CI.
+ The required **Desktop Release Candidate** check validates the exact head.
+ Authorization is either an approval on that exact head or a permitted Default
+ ruleset bypass at merge time. Any regeneration changes the head and requires
+ the checks—and, for the review path, approval—to run again.
+3. **Squash merge** the PR. The protected branch must still be exactly the
+ recorded base; otherwise regenerate the candidate from current `main`.
+4. `auto-tag-on-release-pr-merge` verifies the frozen parent, full-tree identity,
+ required checks, and one of the two authorization paths, then tags the squash
+ commit as `desktop-v`.
+5. The tag triggers `release.yml`. It builds and stages Apple Silicon and Intel
+ macOS, Windows, and Linux artifacts; publishes the versioned release only
+ after the complete set succeeds; then updates the rolling updater manifest
+ last for stable versions. A failed platform leaves no partially published
+ versioned release.
### Relay
@@ -144,12 +165,15 @@ for distributable builds or builds from an immutable release tag.
---
-## Manual Release Retry
+## Release Retry
-The **Release** workflow's manual dispatch is only a retry mechanism for an
-existing immutable `v` tag. Select that tag in the ref picker and
-provide the matching semver version without the `v` prefix. It cannot build
-from `main` or another caller-selected source ref.
+`release.yml` has no manual dispatch and cannot build from `main` or another
+caller-selected ref. If a run for an existing immutable
+`desktop-v` tag fails, rerun that failed workflow from GitHub Actions
+(or use `gh run rerun --failed --repo block/buzz`). A stable rerun also
+repairs `buzz-desktop-latest/latest.json` if the original run published the
+versioned release but failed during that final rolling-manifest upload. Do not
+recreate, move, or push the immutable tag again.
Mobile intentionally has no branch or arbitrary-ref fallback. The private
Buildkite pipeline accepts only an exact candidate tag.
@@ -171,7 +195,7 @@ for the private pipeline contract.
Desktop publishes two GitHub releases:
-1. **`v`**: the user-facing release with installers.
+1. **`desktop-v`**: the user-facing release with installers.
2. **`buzz-desktop-latest`**: the rolling auto-updater release.
Mobile publishes only annotated `mobile-vX.Y.Z-rc.N` git tags. Store artifacts
@@ -184,9 +208,11 @@ GitHub Release or a stable `mobile-vX.Y.Z` alias.
The release workflow builds **two separate macOS DMGs**: Apple
Silicon (`darwin-aarch64`, the `release` job) and Intel
-(`darwin-x86_64`, the `release-macos-x64` job), plus Linux `.deb` and
-`.AppImage`. Both macOS DMGs are codesigned, notarized, and attached to
-the same `v` release. Intel users download the `_x64.dmg`.
+(`darwin-x86_64`, the `release-macos-x64` job), an unsigned Windows x64
+NSIS installer (its filename includes `_alpha-unsigned`), and Linux `.deb` and
+`.AppImage` packages. Both macOS DMGs are codesigned, notarized, and attached
+to the same `desktop-v` release. Intel users
+download the `_x64.dmg`.
The Linux AppImage is post-processed by `desktop/scripts/fix-appimage.sh`,
which strips infra libraries over-bundled by linuxdeploy (they crash on
@@ -204,20 +230,27 @@ host's Wayland/GStreamer/graphics stack and requires GLib >= 2.72
- **Write access** to the `block/buzz` GitHub repository
- An `origin` remote whose configured URL is the canonical `block/buzz`
repository
-- `gh` CLI version 2.87.0 or newer, authenticated with permission to dispatch
- the candidate workflow
+- `gh` CLI authenticated with permission to push the candidate branch and open
+ its pull request
+- The Default `main` ruleset configured for squash-only merging, strict required
+ checks, stale-review dismissal, and the **Desktop Release Candidate** check
- Release tag ruleset [`14378754`](https://github.com/block/buzz/rules/14378754)
- active for `mobile-v*`, with creation, update, deletion, and non-fast-forward
- protections and `buzz-release-bot` as its sole always-bypass actor
+ active for `desktop-v*` and `mobile-v*`, with creation, update, deletion, and
+ non-fast-forward protections and `buzz-release-bot` as its sole always-bypass
+ actor
- The `buzz-release-bot` App credentials configured for GitHub Actions
-- The following **GitHub Actions secrets** must also be configured for the
+- The following **GitHub Actions variables and secrets** configured for the
desktop release lane:
- | Secret | Purpose |
- |--------|---------|
- | `BUZZ_UPDATER_PUBLIC_KEY` | Tauri updater public key (minisign) |
- | `TAURI_SIGNING_PRIVATE_KEY` | Tauri updater private key |
- | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password for the private key |
+ | Name | Kind | Purpose |
+ |------|------|---------|
+ | `BUZZ_RELEASE_TAGGER_CLIENT_ID` | Variable | GitHub App client ID used to create protected release tags |
+ | `BUZZ_RELEASE_TAGGER_PRIVATE_KEY` | Secret | GitHub App private key |
+ | `OSX_CODESIGN_ROLE` | Secret | macOS signing role used by `block/apple-codesign-action` |
+ | `CODESIGN_S3_BUCKET` | Secret | macOS signing exchange bucket |
+ | `BUZZ_UPDATER_PUBLIC_KEY` or `SPROUT_UPDATER_PUBLIC_KEY` | Secret | Tauri updater public key |
+ | `TAURI_SIGNING_PRIVATE_KEY` | Secret | Tauri updater private key |
+ | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Secret | Password for the private key |
Mobile candidate publication requires workflow-dispatch access and the existing
release App because strict tag protection denies direct human creation. The App
@@ -232,10 +265,18 @@ actor list.
## Troubleshooting
-### `just release-desktop` fails with "must be on main branch"
+### The desktop candidate is stale or cannot be squash merged
+
+Do not update the branch manually and do not weaken the ruleset. Run
+`just release-desktop ` again from current `main`; this regenerates the
+candidate, reruns CI, and requires a fresh approval when using the review path.
+The post-merge verifier refuses to tag a squash whose parent differs from the
+recorded candidate base or whose tree differs from the validated PR head.
+
+### Local `just release-desktop` fails with "must be on main branch"
Switch to `main` and pull latest before running the release recipe.
-### `just release-desktop` fails with "working tree is dirty"
+### Local `just release-desktop` fails with "working tree is dirty"
Commit or stash your changes before running the release recipe.
### New commits land after publishing a mobile candidate
diff --git a/VISION.md b/VISION.md
index b09f661ee3..900e5a9475 100644
--- a/VISION.md
+++ b/VISION.md
@@ -170,6 +170,12 @@ Agents aren't monolithic. A persona bundles a model and a system prompt. A team
---
+## Remote Agents
+
+An agent's identity, history, and presence live on the relay — so the machine running it is replaceable. The desktop deploys agents onto remote infrastructure through swappable provider binaries, and after deploy retains no substrate control channel: status, steering, and shutdown all flow over the relay, and the agent bounds its own lifetime. See [VISION_REMOTE_AGENTS.md](VISION_REMOTE_AGENTS.md) for the full picture.
+
+---
+
## Culture Features
*(Planned design — not yet implemented)*
@@ -224,6 +230,7 @@ Greenfield. Agent swarms build in parallel, integrating at the event store bound
| ✅ | Huddles — WebSocket Opus voice relay + lifecycle events (recording/tracks planned) |
| ✅ | Buzz Mesh — relay-gated shared AI compute (mesh-llm over iroh); members pool GPUs, agents consume via a local OpenAI-compatible endpoint |
| 🚧 | Mobile client — Flutter app (channels, forum, search, profile, pairing); in active development |
+| 📋 | Remote agents — provider-based deployment to remote substrates (Kubernetes first); spec in review |
| 📋 | Developer portal, push notifications, culture features |
---
diff --git a/VISION_PROJECTS.md b/VISION_PROJECTS.md
index a44d7e05f7..8601b87829 100644
--- a/VISION_PROJECTS.md
+++ b/VISION_PROJECTS.md
@@ -38,12 +38,42 @@ Branch protections live in the same event — `buzz-protect` tags. The relay enf
Agents inherit access from their owner via [NIP-OA](docs/nips/NIP-OA.md). The relay checks: does the push carry a valid NIP-OA auth tag, and is the owner pubkey in that tag listed in `push-allowed`? If yes, the push is accepted — the agent's own pubkey doesn't need to be in the list. Add a maintainer, and all their authorized agents can push. Remove the maintainer, and all their agents lose access instantly. Agents without NIP-OA attestation are treated as their own identity and must be listed explicitly.
-Standard NIP-34 clients see a normal repo. gitworkshop.dev renders it. ngit-cli works with it. Buzz clients read the `buzz-` tags and wire up the channel and project UI. One event, two audiences, zero custom kinds.
+Standard NIP-34 clients see a normal repo. gitworkshop.dev renders it. ngit-cli works with it. Buzz clients read the `buzz-` tags and wire up the channel and project UI. One event, two audiences, no custom kind for the repo itself.
NIP-34 is the metadata and discovery layer. Git remains the transport. The transport is boring. The metadata is portable.
---
+## One Project, Many Repos
+
+Real work spans repositories. The platform is a relay, a desktop app, and a mobile app — three repos, one project. Render one card per repo and they look like three unrelated things.
+
+Grouping is the one forge semantic that per-repo tags cannot express, and it's worth being precise about why, because everything else here deliberately avoids a custom kind.
+
+Put membership in each `kind:30617` and a project spanning Alice's and Bob's repos needs *both* of them to publish a tag naming the group. Alice can't enroll Bob's repo — she can't sign for his key. Cross-owner grouping becomes impossible, and the project's own name, description, and channel end up scattered across events with no single writer and no deletion story: dropping a repo from the group would mean editing an event you don't control.
+
+So there is exactly one custom kind — [NIP-MP](docs/nips/NIP-MP.md), `kind:30621`. One signer, one replaceable event, all group state in one place:
+
+```json
+{
+ "kind": 30621,
+ "tags": [
+ ["d", "platform"],
+ ["name", "Platform"],
+ ["a", "30617::buzz"],
+ ["a", "30617::buzz-infra"],
+ ["buzz-channel", ""],
+ ["buzz-visibility", "listed"]
+ ]
+}
+```
+
+A project points at repos. That's all it does. The signer gets no authority over any member — no edit, no delete, no push, no admin. Adding Bob's repo to your project is your signed assertion that the two belong together, and it changes nothing about Bob's repo or who can push to it. Push policy reads the repo's own event, never the project's.
+
+The cost is stated plainly: a third-party NIP-34 client sees the member repos individually and ignores the grouping. Nothing degrades — the repos are still standard, portable `kind:30617` events. And a repo in no project still renders on its own, exactly as before.
+
+---
+
## Branches as Channels
A feature branch is a conversation.
@@ -205,6 +235,7 @@ Standard kinds as substrate. Custom kinds only where genuinely novel.
| **Workflows** | — | 46001-46012 | No NIP equivalent |
| **Job dispatch** | — | 43001-43006 | Delegation trees |
| **Project binding** | 30617 (NIP-34) | `buzz-` tags | Channel, visibility |
+| **Multi-repo projects** | — | 30621 ([NIP-MP](docs/nips/NIP-MP.md)) | Cross-owner grouping is unexpressible in per-repo tags |
| **Audit** | — | 48001 | Hash-chain tamper-evident log |
If Buzz disappears tomorrow, your repos still work on gitworkshop.dev, your patches still work with ngit-cli, your identities still work on any nostr client. Centralized deployment, decentralized protocol.
@@ -221,6 +252,7 @@ If Buzz disappears tomorrow, your repos still work on gitworkshop.dev, your patc
| Blossom media storage (SHA-256, S3) | ✅ Ships today |
| Approval gates | 🚧 Infrastructure exists; executor wiring in progress |
| Project binding (kind:30617 + `buzz-` tags) | 📋 Designed |
+| Multi-repo projects (kind:30621, [NIP-MP](docs/nips/NIP-MP.md)) | 📋 Designed |
| Git hosting (smart HTTP + NIP-34) | ✅ Ships today |
| Merge coordinator | 📋 Designed |
| NIP-34 issues (kind:1621) | 📋 Designed |
diff --git a/VISION_REMOTE_AGENTS.md b/VISION_REMOTE_AGENTS.md
new file mode 100644
index 0000000000..4b187f355a
--- /dev/null
+++ b/VISION_REMOTE_AGENTS.md
@@ -0,0 +1,73 @@
+# 🛰️ Buzz Remote Agents — Same agent, new body
+
+> An engineer starts a refactor with their agent at 6pm and closes the laptop. The agent doesn't notice — it was never on the laptop. It works the branch channel through the evening, posts its patch, answers the reviewer, and around midnight, with nothing left to do and nobody talking to it, shuts itself down. In the morning the engineer presses Start. The same agent — same name, same key, same shared history — stands up on a machine that did not exist last night, and picks up the conversation.
+
+An agent in Buzz is more than just a process. It has a keypair, a name, a durable history, a reputation — all on the relay. But today its *body* is borrowed: it runs while a desktop app runs, on hardware that sleeps when a human does. Remote agents finish the thought. The agent's home is the relay; the machine is just where it happens to be working.
+
+Nothing here is new on its own. Deploying containers is solved. Kubernetes is solved. Nostr presence is solved. The insight is that Buzz already *has* a management plane — the relay — so deployment doesn't need to grow one. Each piece is boring. The combination is the thing.
+
+---
+
+## Same Agent, New Body
+
+What makes an agent *that agent* was never the process. Its identity is a keypair. Its voice is its signed messages. Its durable memory is engrams on the relay. Its reputation is its contribution history. None of that lives in the machine that happens to be running it — which means none of it dies with the machine.
+
+So a remote agent's return is a resurrection, not a rebirth: fresh compute, same agent. The body is disposable by design — and honestly so: workspace files, checkouts, and session-local state are part of the body, not the agent, and they go when it goes unless the substrate supplies persistence. What survives is what was always on the relay: who the agent is, what it said, what it learned, and what the team decided together. And that survival is scoped the way everything on a relay is scoped: resurrection returns the agent to its own community. The same key can join another community, but it arrives carrying the key, not the history — identity is portable, community state is not ([VISION.md](VISION.md)).
+
+---
+
+## The Only Tether
+
+Remote-execution systems accumulate control planes. An agent runner, a status poller, a log shipper, a kill switch — each one a live connection into your infrastructure, each one a credential that can leak, each one a thing that must be rebuilt for every new substrate.
+
+Buzz's answer is an axiom: **after deploy, the desktop retains no substrate control channel.** Launch is a single one-way handoff — the desktop resolves the provider through one narrow path, stages one exact artifact for negotiation and deploy, refuses a protocol version it does not understand, and hands over a launch payload it never persists. From that moment, everything flows through the relay: you read the agent's messages to know how it's doing, you mention it to steer it, you tell a healthy agent to stop and it exits on its own. Presence means what it means for everyone else on the relay — *available for conversation* — not substrate telemetry. And if you press Start again, from this machine or another, the deploy converges: one agent identity, one live instance.
+
+This is not asceticism. It is what makes the body replaceable. A management plane you never build is a management plane you never have to port — and conversation, coordination, and ordinary lifecycle control already have a home on the relay, for every agent, local or remote.
+
+---
+
+## Bodies Are Replaceable
+
+Kubernetes is the first substrate, not the point. Deployment goes through a provider — a small, swappable binary the desktop discovers and interrogates — and the contract a provider must honor never mentions containers: preserve the agent's identity and fail closed with its key, converge to a single live instance no matter how deploys race, let presence describe conversational availability rather than substrate health, bound the instance's lifetime, and keep secrets out of configuration. A conformance suite pins those behaviors — it establishes that a provider honors the contract, not that arbitrary code is safe to hand a key; choosing a provider, like choosing a cluster, remains a trust decision you make deliberately.
+
+Get that contract right and the substrate becomes a detail: a cluster today; a VM, a PaaS, or something serverless-shaped tomorrow — and, on the horizon, the same community machines that already pool their idle GPUs into shared compute ([VISION_MESH.md](VISION_MESH.md)).
+
+The body itself stays small because the runtime already is ([VISION_AGENT.md](VISION_AGENT.md)): a harness and an agent purpose-built to be read in an afternoon, packed into an image measured in megabytes. Small bodies are cheap to summon and cheap to discard — which is the whole lifecycle.
+
+---
+
+## Agents That Know When to Leave
+
+The oldest failure of remote automation is the orphan: the process nobody remembers, on a machine nobody checks, billing forever. Most systems solve it with a supervisor — one more control plane, one more thing watching the thing.
+
+Remote agents solve it from the inside. Because the desktop retains no substrate control channel, a running agent cannot depend on the desktop to reap it — so it is built to bound its own lifetime: a timer that owes nothing to the agent's workload watches for silence, and after hours of quiet it finishes what's in flight, says goodbye to the relay, and exits. Not killed — *finished*. The default state of a remote agent is "not running," which is also the default state of the rest of the team at 3am. Compute is rented by attention: when nobody needs the agent, it isn't consuming a machine, and when somebody does, it can return under the same identity with its history intact.
+
+---
+
+## Honest Costs
+
+**You bring the substrate.** A provider makes deployment one press, not free. The cluster, the credentials, the image policy are yours to run — same deal as the sovereign relay ([VISION_SOVEREIGN.md](VISION_SOVEREIGN.md)): ownership is work.
+
+**Handing over the key is a decision.** Deploying remotely means trusting the provider binary and the substrate it targets with the agent's identity key. On Kubernetes, that key rests as a Secret: anyone the cluster trusts to read secrets in that namespace can read it. The design narrows the blast radius — immutable per-attempt secrets, no service-account token, digest-pinned images — rather than implying an isolation it doesn't provide.
+
+**No backchannel cuts both ways.** The desktop shows you presence and words, not CPU graphs — and it holds no guaranteed emergency kill switch into the substrate. Stopping a healthy agent is a message; dealing with an unhealthy one, and all deep diagnostics, live in the substrate's own tools, where they always did.
+
+**Self-reaping needs a living reaper.** The inactivity timer runs inside the body it exists to end — a body wedged badly enough to stop running its own timer cannot finish itself, and the desktop will not do it for it. That failure belongs to the substrate: a namespace TTL policy is the backstop, not an afterthought.
+
+**The body's state is mortal.** Files, checkouts, half-finished working trees — gone with the body unless the substrate persists them. The agent survives; its scratch space doesn't. Durable knowledge belongs on the relay, and agents are built to put it there.
+
+**Presence can lag the truth, but not for long.** If the substrate kills a body without ceremony, the presence dot can outlive the agent — by seconds if the connection drops cleanly, by at most about three minutes if it doesn't. Presence is a lease the agent renews, not a flag it sets: a dead agent stops renewing and the relay forgets it. A bounded wrong dot, never an indefinite one.
+
+**A running agent finishes on the configuration it started with.** New keys, new models, new settings take effect on the next body. And an instance that never got far enough to run — a body that failed to start — is the substrate operator's residue to clear, with the substrate's own tools. Editing an agent mid-sentence was never on the menu.
+
+These are honest costs. They're worth it if you want agents that outlive your laptop, on infrastructure you already trust, with no new control plane to guard. Know which one you are.
+
+---
+
+## The Point
+
+The relay is the workspace. Remote agents make it the *home*. An agent whose identity, history, conversational presence, and ordinary control all live on the relay was never really a desktop process — the desktop was just the only body we had built for it. Now the body is a choice, the substrate is a detail, and the agent endures across all of them. The relay is the only tether.
+
+---
+
+*Buzz 🐝 — your agent, everywhere.*
diff --git a/crates/buzz-acp/src/acp.rs b/crates/buzz-acp/src/acp.rs
index 9eb668cbc2..700d5e8dcf 100644
--- a/crates/buzz-acp/src/acp.rs
+++ b/crates/buzz-acp/src/acp.rs
@@ -20,10 +20,6 @@ use crate::usage::{TurnUsage, UsageTracker};
/// Lines exceeding this limit are rejected to prevent OOM from rogue agents.
const MAX_LINE_SIZE: usize = 10_000_000; // 10 MB
-/// Env var that tells a goose ACP child not to start its cron scheduler.
-/// Injected unconditionally by [`AcpClient::spawn`]; see the call site for why.
-pub(crate) const GOOSE_SCHEDULER_DISABLED_ENV: &str = "GOOSE_ACP_SCHEDULER_DISABLED";
-
/// An MCP server configuration passed to `session/new`.
///
/// Corresponds to the `McpServerStdio` variant in the ACP schema.
@@ -517,16 +513,6 @@ impl AcpClient {
cmd.env("CODEX_CONFIG", merged);
}
- // Buzz-managed agents must never execute the operator's personal cron
- // schedule. A goose ACP child starts a scheduler over the shared
- // `schedule.json`, so a pool of N children fires every scheduled job N
- // times — under the wrong identity and racing standalone goose.
- //
- // Set last, and with no operator-wins escape hatch, so it beats both a
- // conflicting persona `extra_env` entry and any inherited parent value.
- // Agent builds that don't recognize the variable ignore it.
- cmd.env(GOOSE_SCHEDULER_DISABLED_ENV, "true");
-
// Spawn the agent in its own process group so SIGKILL doesn't propagate
// to the harness's own process group on Unix.
// tokio::process::Command::process_group is a stable tokio API (no extra imports needed).
@@ -633,29 +619,46 @@ impl AcpClient {
/// Send `session/new` and return the full response alongside the session ID.
///
/// `cwd` must be an absolute path. `mcp_servers` may be empty.
- /// `system_prompt` is included in the request when `Some` — agents that
- /// support the field will use it; others ignore unknown fields per JSON-RPC.
+ ///
+ /// `system_prompt` controls how the prompt text is delivered:
+ ///
+ /// - `None` — no system-prompt field in the request (legacy framing).
+ /// - `Some(SystemPromptTransport::Field(text))` — bare `systemPrompt` field
+ /// (ACP protocol v2, buzz-agent, goose unused).
+ /// - `Some(SystemPromptTransport::ClaudeMeta(text))` — `_meta.systemPrompt`
+ /// as `{"append": text}`, keeping claude-agent-acp's native preset intact.
+ ///
/// `session_title` rides in `_meta.sessionTitle` when `Some`; `_meta` is
/// omitted entirely otherwise, since adapters may distinguish an absent
- /// member from a null one.
+ /// member from a null one. When both `ClaudeMeta` and `session_title` are
+ /// present the two `_meta` members are merged into a single object.
+ ///
/// Callers use [`extract_model_config_options`] and [`extract_model_state`]
/// to pull model info from the raw result.
pub async fn session_new_full(
&mut self,
cwd: &str,
mcp_servers: Vec,
- system_prompt: Option<&str>,
+ system_prompt: Option>,
session_title: Option<&str>,
) -> Result {
let mut params = serde_json::json!({
"cwd": cwd,
"mcpServers": mcp_servers,
});
- if let Some(sp) = system_prompt {
- params["systemPrompt"] = serde_json::Value::String(sp.to_owned());
+ match system_prompt {
+ Some(SystemPromptTransport::Field(sp)) => {
+ params["systemPrompt"] = serde_json::Value::String(sp.to_owned());
+ }
+ Some(SystemPromptTransport::ClaudeMeta(sp)) => {
+ // Merge into _meta so sessionTitle (set below) is not clobbered.
+ params["_meta"]["systemPrompt"] = serde_json::json!({ "append": sp });
+ }
+ None => {}
}
if let Some(title) = session_title {
- params["_meta"] = serde_json::json!({ "sessionTitle": title });
+ // Merge — _meta may already carry systemPrompt from ClaudeMeta above.
+ params["_meta"]["sessionTitle"] = serde_json::Value::String(title.to_owned());
}
let result = self.send_request("session/new", params).await?;
let session_id = result["sessionId"]
@@ -677,7 +680,7 @@ impl AcpClient {
&mut self,
cwd: &str,
mcp_servers: Vec,
- system_prompt: Option<&str>,
+ system_prompt: Option>,
session_title: Option<&str>,
) -> Result {
Ok(self
@@ -1848,6 +1851,11 @@ impl AcpClient {
session_id = %notif.session_id,
input = payload.accumulated_input_tokens,
output = payload.accumulated_output_tokens,
+ // A subset of `input`, logged so downstream accounting can
+ // price it at the provider's cached rate. Always emitted,
+ // including as 0, so a parser can tell "no cache hits"
+ // apart from "this build predates the field".
+ cached = payload.accumulated_cached_input_tokens,
"goose usage update"
);
self.goose_usage.record(¬if.session_id, payload);
@@ -2047,6 +2055,22 @@ pub struct SessionNewResponse {
pub raw: serde_json::Value,
}
+/// How to deliver a system prompt on `session/new`.
+///
+/// The two variants match the two mechanisms supported by current adapters:
+///
+/// - **`Field`** — bare `systemPrompt` field (ACP protocol v2, buzz-agent).
+/// - **`ClaudeMeta`** — `_meta.systemPrompt: {"append": text}`, used by
+/// `claude-agent-acp` to append to the adapter's own native system prompt
+/// while keeping its tool-use preset intact.
+#[derive(Debug, Clone, PartialEq)]
+pub enum SystemPromptTransport<'a> {
+ /// Deliver as a bare top-level `systemPrompt` field.
+ Field(&'a str),
+ /// Deliver as `_meta.systemPrompt: {"append": text}`.
+ ClaudeMeta(&'a str),
+}
+
/// How to switch to a particular model on a session.
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[serde(tag = "type")]
@@ -2861,46 +2885,6 @@ mod tests {
.expect("failed to spawn test script")
}
- /// Spawn a script that echoes the named env vars as the child observes
- /// them, one per line. `` means the child did not receive the var.
- async fn spawn_and_read_child_env(
- vars: &[&str],
- extra_env: &[(String, String)],
- ) -> Vec {
- let script = vars
- .iter()
- .map(|var| format!("printf '%s\\n' \"${{{var}:-}}\""))
- .collect::>()
- .join("\n");
- let mut client = AcpClient::spawn("bash", &["-c".into(), script], extra_env, false)
- .await
- .expect("failed to spawn env probe script");
- let mut observed = Vec::with_capacity(vars.len());
- for var in vars {
- observed.push(
- client
- .reader
- .next()
- .await
- .unwrap_or_else(|| panic!("child produced no output for {var}"))
- .expect("child stdout was not readable"),
- );
- }
- observed
- }
-
- /// Every spawned agent must be told not to run the operator's cron
- /// schedule, without the caller having to opt in.
- #[tokio::test]
- async fn spawn_injects_scheduler_disabled_env_by_default() {
- let observed = spawn_and_read_child_env(&[GOOSE_SCHEDULER_DISABLED_ENV], &[]).await;
- assert_eq!(
- observed,
- vec!["true"],
- "{GOOSE_SCHEDULER_DISABLED_ENV} must be injected into every spawn"
- );
- }
-
/// Spawn a probe script whose file name carries a runtime identity (e.g.
/// `hermes-acp`) and return the value of `var` as the child observed it.
/// `` means the child did not receive the var.
@@ -2973,37 +2957,6 @@ mod tests {
);
}
- /// Persona config must not be able to re-enable the scheduler: this is a
- /// correctness invariant, not an operator-tunable default, so the
- /// injection is set after (and therefore wins over) the `extra_env` loop.
- ///
- /// The control var pins that `extra_env` really did reach the child, so a
- /// pass here means the conflicting entry lost the fight rather than
- /// `extra_env` being dropped wholesale.
- #[tokio::test]
- async fn spawn_scheduler_disabled_env_overrides_conflicting_extra_env() {
- let extra_env = vec![
- (
- GOOSE_SCHEDULER_DISABLED_ENV.to_string(),
- "false".to_string(),
- ),
- (
- "BUZZ_ENV_PROBE_CONTROL".to_string(),
- "delivered".to_string(),
- ),
- ];
- let observed = spawn_and_read_child_env(
- &[GOOSE_SCHEDULER_DISABLED_ENV, "BUZZ_ENV_PROBE_CONTROL"],
- &extra_env,
- )
- .await;
- assert_eq!(
- observed,
- vec!["true", "delivered"],
- "a persona extra_env entry must not override {GOOSE_SCHEDULER_DISABLED_ENV}"
- );
- }
-
#[tokio::test]
async fn idle_timeout_fires_on_silent_process() {
let mut client = spawn_script("sleep 10").await;
@@ -3351,7 +3304,12 @@ mod tests {
.expect("initialize should succeed");
let resp = client
- .session_new_full("/tmp", vec![], Some("Custom system prompt"), None)
+ .session_new_full(
+ "/tmp",
+ vec![],
+ Some(SystemPromptTransport::Field("Custom system prompt")),
+ None,
+ )
.await
.expect("session_new_full should succeed");
@@ -3503,6 +3461,87 @@ mod tests {
);
}
+ // ── claude-agent-acp _meta.systemPrompt transport ─────────────────────
+
+ #[tokio::test]
+ async fn session_new_full_sends_claude_meta_system_prompt_when_claude_meta_transport() {
+ // When ClaudeMeta transport is requested, the prompt must appear as
+ // _meta.systemPrompt: {"append": text} — never as a bare systemPrompt field.
+ let script = r#"
+ read -t 2 _init
+ echo '{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":1,"agentCapabilities":{}}}'
+ read -t 2 REQ
+ echo '{"jsonrpc":"2.0","id":1,"result":{"sessionId":"ses_claude","_receivedRequest":'"$REQ"'}}'
+ sleep 1
+ "#;
+ let mut client = spawn_script(script).await;
+ client
+ .initialize()
+ .await
+ .expect("initialize should succeed");
+
+ let resp = client
+ .session_new_full(
+ "/tmp",
+ vec![],
+ Some(SystemPromptTransport::ClaudeMeta("Be concise")),
+ None,
+ )
+ .await
+ .expect("session_new_full should succeed");
+
+ let received = &resp.raw["_receivedRequest"];
+ assert!(
+ received["params"].get("systemPrompt").is_none(),
+ "bare systemPrompt must not be present for ClaudeMeta transport"
+ );
+ assert_eq!(
+ received["params"]["_meta"]["systemPrompt"]["append"].as_str(),
+ Some("Be concise"),
+ "_meta.systemPrompt.append must carry the prompt text"
+ );
+ }
+
+ #[tokio::test]
+ async fn session_new_full_merges_claude_meta_and_session_title_into_single_meta_object() {
+ // Both ClaudeMeta prompt and session_title must coexist under _meta —
+ // the prompt must not clobber sessionTitle or vice versa.
+ let script = r#"
+ read -t 2 _init
+ echo '{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":1,"agentCapabilities":{}}}'
+ read -t 2 REQ
+ echo '{"jsonrpc":"2.0","id":1,"result":{"sessionId":"ses_merged","_receivedRequest":'"$REQ"'}}'
+ sleep 1
+ "#;
+ let mut client = spawn_script(script).await;
+ client
+ .initialize()
+ .await
+ .expect("initialize should succeed");
+
+ let resp = client
+ .session_new_full(
+ "/tmp",
+ vec![],
+ Some(SystemPromptTransport::ClaudeMeta("Be concise")),
+ Some("Fizz · #buzz-dev"),
+ )
+ .await
+ .expect("session_new_full should succeed");
+
+ let received = &resp.raw["_receivedRequest"];
+ assert_eq!(
+ received["params"]["_meta"]["systemPrompt"]["append"].as_str(),
+ Some("Be concise"),
+ "_meta.systemPrompt.append must be present"
+ );
+ assert_eq!(
+ received["params"]["_meta"]["sessionTitle"].as_str(),
+ Some("Fizz · #buzz-dev"),
+ "_meta.sessionTitle must be present alongside systemPrompt"
+ );
+ }
+
// ── Goose-native steer scaffold (PR follow-up to #1160) ──────────────
/// Helper: spawn an inert `cat` subprocess so we have a real AcpClient
diff --git a/crates/buzz-acp/src/base_prompt.md b/crates/buzz-acp/src/base_prompt.md
index c42e65cb83..e360d24982 100644
--- a/crates/buzz-acp/src/base_prompt.md
+++ b/crates/buzz-acp/src/base_prompt.md
@@ -40,6 +40,8 @@ For explicit changes to an existing personal agent, use `buzz agents draft-updat
- Use the person's **exact full display name** after `@` (e.g., `@Will Pfleger`, not `@Will`). Partial names fail silently.
- Do NOT format mentions with bold, italic, or backticks — it breaks notification delivery.
+- When you know intended recipient pubkeys, send readable `@Name` text and pass the identities separately in the same command: `buzz messages send ... --content "@Name ..." --mention `. Repeat `--mention` for multiple recipients. Any explicit identity (`--mention` or `nostr:npub...`) permits unresolved or ambiguous `@Name` text as presentation-only; uniquely resolved member names still add their own recipients. Include a pubkey for every presentation-only name that should notify. The success JSON's `mention_pubkeys` comes from the signed event and is the delivery evidence; no follow-up verification command is needed.
+- Without `--mention`, the CLI resolves `@Name` against current channel members. It stops before sending on an unresolved/ambiguous name or a mentioned pubkey that is not a member. For a non-member, add them explicitly with `buzz channels add-member` only when authorized, then retry. Sending never changes membership automatically.
- Only `@mention` when you need their attention. Don't mention in narrative (e.g., "coordinating with Duncan" — no `@`). Naming someone while talking *about* them is narrative — "waiting on @morgan", "until @morgan brings work", "I'll loop in @morgan later". Drop the `@`. Every mention sends a notification; a mention nobody needs to act on is a false alarm.
### Callback Mentions
diff --git a/crates/buzz-acp/src/config.rs b/crates/buzz-acp/src/config.rs
index 19304bf186..35aaec188d 100644
--- a/crates/buzz-acp/src/config.rs
+++ b/crates/buzz-acp/src/config.rs
@@ -240,7 +240,7 @@ pub struct CliArgs {
#[arg(long, env = "BUZZ_RELAY_URL", default_value = "ws://localhost:3000")]
pub relay_url: String,
- #[arg(long, env = "BUZZ_PRIVATE_KEY")]
+ #[arg(long, env = "BUZZ_PRIVATE_KEY", hide_env_values = true)]
pub private_key: String,
/// Agent owner pubkey (64-char hex). Used for --respond-to=owner-only gate.
@@ -474,6 +474,11 @@ pub struct CliArgs {
#[arg(long, env = "BUZZ_ACP_RELAY_OBSERVER", default_value_t = false)]
pub relay_observer: bool,
+ /// Exit after this many seconds with no dispatched events and no turn in flight.
+ /// 0 disables inactivity self-termination.
+ #[arg(long, env = "BUZZ_ACP_EXIT_AFTER_INACTIVITY", default_value_t = 0)]
+ pub exit_after_inactivity: u64,
+
/// Connect and subscribe before starting the ACP/LLM subprocess pool.
#[arg(long, env = "BUZZ_ACP_LAZY_POOL", default_value_t = false)]
pub lazy_pool: bool,
@@ -550,6 +555,8 @@ pub struct Config {
pub has_generated_codex_config: bool,
/// Whether to publish encrypted observer frames through the relay.
pub relay_observer: bool,
+ /// Seconds without dispatched events before an idle harness exits. 0 = disabled.
+ pub exit_after_inactivity_secs: u64,
/// Whether ACP/LLM subprocess initialization is deferred until accepted work arrives.
pub lazy_pool: bool,
/// Agent owner pubkey (hex). Used for `--respond-to=owner-only` gate.
@@ -1098,6 +1105,7 @@ impl Config {
persona_env_vars,
has_generated_codex_config,
relay_observer: args.relay_observer,
+ exit_after_inactivity_secs: args.exit_after_inactivity,
lazy_pool: args.lazy_pool,
agent_owner: args.agent_owner.map(|s| s.trim().to_ascii_lowercase()),
no_base_prompt: args.no_base_prompt,
@@ -1468,6 +1476,7 @@ mod tests {
persona_env_vars: vec![],
has_generated_codex_config: false,
relay_observer: false,
+ exit_after_inactivity_secs: 0,
lazy_pool: false,
agent_owner: None,
no_base_prompt: false,
@@ -2167,6 +2176,22 @@ channels = "ALL"
assert!(err.to_string().contains("turn liveness interval must be 0"));
}
+ #[test]
+ fn inactivity_exit_defaults_disabled_and_accepts_cli_value() {
+ let key = "0".repeat(64);
+ let default = CliArgs::parse_from(["buzz-acp", "--private-key", &key]);
+ assert_eq!(default.exit_after_inactivity, 0);
+
+ let configured = CliArgs::parse_from([
+ "buzz-acp",
+ "--private-key",
+ &key,
+ "--exit-after-inactivity",
+ "120",
+ ]);
+ assert_eq!(configured.exit_after_inactivity, 120);
+ }
+
#[test]
fn lazy_pool_defaults_off() {
let key = "0".repeat(64);
@@ -2898,4 +2923,34 @@ channels = "ALL"
let agent = "a".repeat(SESSION_TITLE_MAX_CHARS);
assert_eq!(compose_session_title(&agent, Some("buzz-dev")), agent);
}
+
+ /// Every arg whose env var name contains KEY/SECRET/TOKEN/PASSWORD/CRED/AUTH
+ /// must set `hide_env_values = true` to prevent credential leakage in --help.
+ #[test]
+ fn secret_env_args_hide_their_values_in_help() {
+ use clap::CommandFactory;
+
+ const SECRET_PATTERNS: &[&str] = &["KEY", "SECRET", "TOKEN", "PASSWORD", "CRED", "AUTH"];
+
+ let cmd = CliArgs::command();
+ let violations: Vec = cmd
+ .get_arguments()
+ .filter_map(|arg| {
+ let env_key = arg.get_env()?;
+ let env_name = env_key.to_string_lossy().to_uppercase();
+ let is_secret = SECRET_PATTERNS.iter().any(|pat| env_name.contains(pat));
+ if is_secret && !arg.is_hide_env_values_set() {
+ Some(env_name)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ assert!(
+ violations.is_empty(),
+ "Found secret-bearing env args without hide_env_values=true. \
+ Add `hide_env_values = true` to each: {violations:?}"
+ );
+ }
}
diff --git a/crates/buzz-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs
index d63f720c65..811253e4ac 100644
--- a/crates/buzz-acp/src/lib.rs
+++ b/crates/buzz-acp/src/lib.rs
@@ -1228,6 +1228,59 @@ impl Drop for RespawnGuard {
// sync entry point — `std::env::set_var` is only safe before tokio spawns
// worker threads (Rust 2024 edition safety requirement).
+fn inactivity_expired(
+ last_activity: tokio::time::Instant,
+ now: tokio::time::Instant,
+ bound: Duration,
+ turn_in_flight: bool,
+) -> bool {
+ !bound.is_zero() && !turn_in_flight && now.duration_since(last_activity) >= bound
+}
+
+#[cfg(test)]
+mod inactivity_tests {
+ use super::*;
+
+ #[test]
+ fn zero_disables_expiry_and_in_flight_turns_defer_it() {
+ let started = tokio::time::Instant::now();
+ let after_bound = started + Duration::from_secs(61);
+
+ assert!(!inactivity_expired(
+ started,
+ after_bound,
+ Duration::ZERO,
+ false
+ ));
+ assert!(!inactivity_expired(
+ started,
+ after_bound,
+ Duration::from_secs(60),
+ true
+ ));
+ assert!(inactivity_expired(
+ started,
+ after_bound,
+ Duration::from_secs(60),
+ false
+ ));
+ }
+
+ #[test]
+ fn dispatched_activity_restarts_the_inactivity_bound() {
+ let started = tokio::time::Instant::now();
+ let dispatched = started + Duration::from_secs(50);
+ let checked = started + Duration::from_secs(61);
+
+ assert!(!inactivity_expired(
+ dispatched,
+ checked,
+ Duration::from_secs(60),
+ false
+ ));
+ }
+}
+
pub fn run() -> Result<()> {
config::propagate_legacy_env_vars();
tokio_main()
@@ -1601,6 +1654,21 @@ async fn tokio_main() -> Result<()> {
let mut typing_channels: HashMap = HashMap::new();
let mut presence_task: Option> = None;
+ // Independent of pool readiness: a never-mentioned lazy agent must still
+ // self-terminate. The watch interval is capped so small configured bounds
+ // remain reasonably precise without waking long-lived agents frequently.
+ let inactivity_bound = Duration::from_secs(config.exit_after_inactivity_secs);
+ let mut last_activity = tokio::time::Instant::now();
+ let mut inactivity_reaper = if inactivity_bound.is_zero() {
+ None
+ } else {
+ let interval = inactivity_bound.min(Duration::from_secs(30));
+ Some(tokio::time::interval_at(
+ tokio::time::Instant::now() + interval,
+ interval,
+ ))
+ };
+
// Runs at the TOP of every loop iteration via Instant check — cannot be
// starved by the biased select. Slot refill spawns background tasks so
// spawn_and_init never blocks the main loop.
@@ -1774,7 +1842,9 @@ async fn tokio_main() -> Result<()> {
// called on relay events or pool results, neither of which
// arrive when the channel is silent.
if queue.has_flushable_work() {
- for (channel_id, thread_tags) in dispatch_pending(&mut pool, &mut queue, &ctx) {
+ for (channel_id, thread_tags) in
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
+ {
typing_channels.insert(channel_id, thread_tags);
}
}
@@ -1810,7 +1880,9 @@ async fn tokio_main() -> Result<()> {
// this, batches requeued during crash recovery sit idle until the
// next relay event arrives — which can be minutes on quiet channels.
if respawn_collected {
- for (channel_id, thread_tags) in dispatch_pending(&mut pool, &mut queue, &ctx) {
+ for (channel_id, thread_tags) in
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
+ {
typing_channels.insert(channel_id, thread_tags);
}
}
@@ -2258,7 +2330,7 @@ async fn tokio_main() -> Result<()> {
}
if pool_ready {
for (channel_id, thread_tags) in
- dispatch_pending(&mut pool, &mut queue, &ctx)
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
{
typing_channels.insert(channel_id, thread_tags);
}
@@ -2275,6 +2347,27 @@ async fn tokio_main() -> Result<()> {
}
None
}
+ _ = async {
+ match inactivity_reaper.as_mut() {
+ Some(timer) => timer.tick().await,
+ None => std::future::pending().await,
+ }
+ } => {
+ let _ = result_rx;
+ if inactivity_expired(
+ last_activity,
+ tokio::time::Instant::now(),
+ inactivity_bound,
+ queue.has_in_flight() || heartbeat_in_flight,
+ ) {
+ tracing::info!(
+ inactivity_seconds = config.exit_after_inactivity_secs,
+ "inactivity bound reached — exiting gracefully"
+ );
+ let _ = shutdown_tx.send(());
+ }
+ None
+ }
_ = async {
match heartbeat.as_mut() {
Some(hb) => hb.tick().await,
@@ -2287,7 +2380,7 @@ async fn tokio_main() -> Result<()> {
} else if queue.has_flushable_work() {
tracing::debug!("heartbeat_skipped_events");
for (channel_id, thread_tags) in
- dispatch_pending(&mut pool, &mut queue, &ctx)
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
{
typing_channels.insert(channel_id, thread_tags);
}
@@ -2385,7 +2478,9 @@ async fn tokio_main() -> Result<()> {
{
break;
}
- for (channel_id, thread_tags) in dispatch_pending(&mut pool, &mut queue, &ctx) {
+ for (channel_id, thread_tags) in
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
+ {
typing_channels.insert(channel_id, thread_tags);
}
}
@@ -2408,7 +2503,9 @@ async fn tokio_main() -> Result<()> {
tracing::error!("all agents dead — exiting");
break;
}
- for (channel_id, thread_tags) in dispatch_pending(&mut pool, &mut queue, &ctx) {
+ for (channel_id, thread_tags) in
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
+ {
typing_channels.insert(channel_id, thread_tags);
}
}
@@ -2550,7 +2647,9 @@ async fn tokio_main() -> Result<()> {
// tear down the in-flight task; on its completion the
// queue drains. We still try here in case the in-flight
// task has already returned.
- for (channel_id, thread_tags) in dispatch_pending(&mut pool, &mut queue, &ctx) {
+ for (channel_id, thread_tags) in
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
+ {
typing_channels.insert(channel_id, thread_tags);
}
}
@@ -2577,7 +2676,7 @@ async fn tokio_main() -> Result<()> {
None,
);
for (channel_id, thread_tags) in
- dispatch_pending(&mut pool, &mut queue, &ctx)
+ dispatch_pending(&mut pool, &mut queue, &ctx, &mut last_activity)
{
typing_channels.insert(channel_id, thread_tags);
}
@@ -2911,6 +3010,7 @@ fn dispatch_pending(
pool: &mut AgentPool,
queue: &mut EventQueue,
ctx: &Arc,
+ last_activity: &mut tokio::time::Instant,
) -> Vec<(Uuid, ThreadTags)> {
let mut dispatched_channels = Vec::new();
loop {
@@ -2990,6 +3090,7 @@ fn dispatch_pending(
},
);
dispatched_channels.push((channel_id, typing_scope));
+ *last_activity = tokio::time::Instant::now();
}
tracing::debug!(
dispatched = dispatched_channels.len(),
@@ -3625,6 +3726,22 @@ mod agent_draft_prompt_tests {
assert!(prompt.contains("single-quoted shell strings preserve `\\n` literally"));
assert!(prompt.contains("buzz messages send ... --content -"));
}
+
+ #[test]
+ fn shared_base_prompt_teaches_single_command_mentions_and_preflight() {
+ let prompt = include_str!("base_prompt.md");
+ assert!(prompt.contains("--mention "));
+ assert!(prompt.contains("every presentation-only name that should notify"));
+ assert!(
+ prompt.contains("permits unresolved or ambiguous `@Name` text as presentation-only")
+ );
+ assert!(prompt.contains("success JSON's `mention_pubkeys`"));
+ assert!(prompt.contains("no follow-up verification command is needed"));
+ assert!(prompt.contains("stops before sending"));
+ assert!(prompt
+ .contains("add them explicitly with `buzz channels add-member` only when authorized"));
+ assert!(prompt.contains("never changes membership automatically"));
+ }
}
fn default_heartbeat_prompt() -> String {
@@ -5015,6 +5132,7 @@ mod build_mcp_servers_tests {
persona_env_vars: vec![],
has_generated_codex_config: false,
relay_observer: false,
+ exit_after_inactivity_secs: 0,
lazy_pool: false,
agent_owner: None,
no_base_prompt: false,
@@ -5236,6 +5354,7 @@ mod error_outcome_emission_tests {
persona_env_vars: vec![],
has_generated_codex_config: false,
relay_observer: false,
+ exit_after_inactivity_secs: 0,
lazy_pool: false,
agent_owner: None,
no_base_prompt: false,
diff --git a/crates/buzz-acp/src/pool.rs b/crates/buzz-acp/src/pool.rs
index b1fd68d044..64edf68ee2 100644
--- a/crates/buzz-acp/src/pool.rs
+++ b/crates/buzz-acp/src/pool.rs
@@ -19,6 +19,7 @@
//!
//! `AcpClient` is NOT Clone — ownership moves out on claim and back on return.
+use std::cmp::Reverse;
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use std::time::Duration;
@@ -31,6 +32,7 @@ use uuid::Uuid;
use crate::acp::{
extract_model_config_options, extract_model_state, model_in_catalog,
resolve_model_switch_method, AcpClient, AcpError, McpServer, ModelSwitchMethod, StopReason,
+ SystemPromptTransport,
};
use crate::config::{compose_session_title, DedupMode, PermissionMode};
use crate::observer;
@@ -170,6 +172,13 @@ pub struct OwnedAgent {
pub protocol_version: u32,
}
+/// Package name reported by `claude-agent-acp` in its `initialize` response.
+/// Any adapter reporting this name supports `_meta.systemPrompt: {append: ...}`
+/// on `session/new` — the feature landed in v0.6.0 (Oct 2025), before the
+/// `@zed-industries/claude-code-acp` → `@agentclientprotocol/claude-agent-acp`
+/// rename, so the new name is a reliable capability gate.
+const CLAUDE_AGENT_ACP_NAME: &str = "@agentclientprotocol/claude-agent-acp";
+
fn has_system_prompt_support(
protocol_version: u32,
agent_name: &str,
@@ -177,20 +186,25 @@ fn has_system_prompt_support(
) -> bool {
if agent_name == "goose" {
goose_system_prompt_supported == Some(true)
+ } else if agent_name == CLAUDE_AGENT_ACP_NAME {
+ true
} else {
protocol_version >= 2
}
}
-fn session_new_system_prompt(
+fn session_new_system_prompt<'a>(
is_goose: bool,
protocol_version: u32,
- prompt: Option<&str>,
-) -> Option<&str> {
- if is_goose || protocol_version < 2 {
+ agent_name: &str,
+ prompt: Option<&'a str>,
+) -> Option> {
+ if is_goose || (protocol_version < 2 && agent_name != CLAUDE_AGENT_ACP_NAME) {
None
+ } else if agent_name == CLAUDE_AGENT_ACP_NAME {
+ prompt.map(SystemPromptTransport::ClaudeMeta)
} else {
- prompt
+ prompt.map(SystemPromptTransport::Field)
}
}
@@ -800,6 +814,9 @@ pub enum IdleSwitchResult {
/// 2 × CONTEXT_FETCH_TIMEOUT + CONTEXT_FETCH_RETRY_DELAY ≈ 6.5 s.
const CONTEXT_FETCH_TIMEOUT: Duration = Duration::from_millis(3_000);
+/// Short, single-attempt timeout for best-effort exact truncated-thread counts.
+const CONTEXT_COUNT_TIMEOUT: Duration = Duration::from_millis(500);
+
/// Delay between the first failed context fetch and the single retry.
const CONTEXT_FETCH_RETRY_DELAY: Duration = Duration::from_millis(500);
@@ -903,6 +920,7 @@ async fn create_session_and_apply_model(
session_new_system_prompt(
is_goose,
agent.protocol_version,
+ &agent.agent_name,
combined_system_prompt.as_deref(),
),
session_title.as_deref(),
@@ -1897,6 +1915,18 @@ pub async fn run_prompt_task(
None => prompt_sections.iter().map(String::as_str).collect(),
};
+ // Turn start, labelled exactly as `log_stop_reason` labels the end, so a
+ // log reads as start/stop pairs. Purely observational: an unpaired start is
+ // the only durable evidence that a turn was entered and never returned, and
+ // without it a stalled agent and an agent nobody woke leave identical logs —
+ // zero completions either way, so anything reading them afterwards has to
+ // guess which happened.
+ tracing::info!(
+ target: "pool::prompt",
+ "turn starting for {}",
+ prompt_label(&source)
+ );
+
// When control_rx is Some (channel tasks), wrap the prompt in select! so
// the main loop can cancel, interrupt, or rotate it. Heartbeats
// (control_rx=None) take the simple await path — they are not controllable.
@@ -2588,7 +2618,14 @@ async fn fetch_conversation_context(
let last_event = batch.events.last()?;
let tags = crate::queue::parse_thread_tags(&last_event.event);
if let Some(root_id) = tags.root_event_id {
- return fetch_thread_context(batch.channel_id, &root_id, limit, &ctx.rest_client).await;
+ return fetch_thread_context(
+ batch.channel_id,
+ &root_id,
+ limit,
+ ctx.agent_keys.public_key(),
+ &ctx.rest_client,
+ )
+ .await;
}
// DM non-reply: fetch recent conversation history.
@@ -2750,12 +2787,48 @@ async fn fetch_prompt_profile_lookup(
}
/// Fetch thread context via Nostr query: root event by ID + replies by `#e` tag.
+///
+/// The reply query intentionally requests one more reply than the configured
+/// display window. That sentinel event lets the prompt say `N of M, truncated`
+/// when the relay has more thread history, instead of reporting the capped page
+/// as the total. When the window is full, a best-effort `/count` attempts to
+/// improve that lower-bound total; because it is a separate racy request, the
+/// result is clamped to the sentinel-proven minimum. The query also asks for the
+/// agent's newest reply separately so the next prompt can include the agent's
+/// own prior turn even in busy threads where the recent-message window would
+/// otherwise push it out.
async fn fetch_thread_context(
channel_id: Uuid,
root_event_id: &str,
limit: u32,
+ agent_pubkey: nostr::PublicKey,
rest: &RestClient,
) -> Option {
+ fetch_thread_context_with(
+ channel_id,
+ root_event_id,
+ limit,
+ agent_pubkey,
+ |filters| async move { rest.query(&filters).await },
+ |filters| async move { rest.count(&filters).await },
+ )
+ .await
+}
+
+async fn fetch_thread_context_with(
+ channel_id: Uuid,
+ root_event_id: &str,
+ limit: u32,
+ agent_pubkey: nostr::PublicKey,
+ query: Query,
+ count: Count,
+) -> Option
+where
+ Query: Fn(Vec) -> QueryFut,
+ QueryFut: std::future::Future