Skip to content

Relay-primary private managed-agent config - #4999

Open
wesbillman wants to merge 29 commits into
mainfrom
carl/relay-primary-agent-config
Open

Relay-primary private managed-agent config#4999
wesbillman wants to merge 29 commits into
mainfrom
carl/relay-primary-agent-config

Conversation

@wesbillman

@wesbillman wesbillman commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace the aggregate private-agent payload with a strict owner-authored encrypted kind:30179 codec
  • retain public/private managed-agent heads and deletion tombstones atomically
  • hydrate validated relay config into a workspace-scoped in-memory overlay, rehydrated from retained events at every boot so relay-primary config survives app restarts
  • support fresh-device recovery: relay-only agents appear in the list with no local record; the first START intentionally materializes a local record (including the key material needed to run the agent) via materialize_relay_only_agent — this is the one deliberate disk write derived from relay state
  • resolve the overlay into the local record before every stale-disk republish site (edit, persona rename, pair-start snapshot re-apply), so a device following a newer relay head cannot clobber it with an audit-clean successor event carrying stale fields
  • hydrate keyring-stored agent keys in the boot reconcile path, so first boot on default (system-keyring) builds publishes each agent's 30179 instead of silently skipping every untouched agent
  • preserve unknown forward-compatible fields and reject malformed/stale inbound events before changing the live overlay
  • classify kind:30179 as owner-scoped global user data in the relay and subscribe from Desktop

This is the minimal replacement for the closed #4940 implementation.

Review fixes (c80c4c17, aa39d72a, 6f486e88)

Adversarial review (Eva/Sami) found and fixed five defects in the original head a9b648b4, each pinned by regression tests:

  1. Overlay never rehydrated after restart — relay-primary config worked for exactly one launch, then every site silently read stale disk. Fixed: boot rehydration from the retention DB (owner-scoped; negative control asserts another owner's keys hydrate nothing).

  2. Boot reconcile published zero 30179s on default keyring builds — records were read without keyring hydration, so the empty-nsec skip fired for every untouched agent. Fixed: hydrate keys in the reconcile path.

  3. Stale-disk republish clobbered a newer relay head at three write sites (agent_models.rs edit, personas/update.rs rename, agents.rs pair-start snapshot re-apply). The corruption was a validly-chained gen+1 successor — indistinguishable from a legitimate edit. Fixed: resolve-before-mutate at each site. The resolve is deliberately per-site, not centralized in the retain helper — centralizing it silently discards user edits, and at the rename site a naive resolve-first loses the rename (the gate keys on disk name). Both wrong fixes are pinned by permanent probe tests, and a source-level guard asserts the exact resolve-call count per file (the write sites are #[tauri::command]s, so unit tests cannot observe the production wiring directly).

  4. Boot reconcile republished stale disk over a newer relay head — a fourth site in the same class as (3), but firing at launch, unprompted, for every agent on a device that follows another device's config. reconcile_agents_in_dir_at reads managed-agents.json raw and cannot resolve the overlay: hydrate_private_config_overlay runs after this leg (event_sync.rs:19-20) and reads the rows it writes. Inbound 30179 updates the overlay and retention but never the JSON, so on a follower disk is stale by construction. Measured: gen 5 → 6, prev = the clobbered head, created_at floored at head+1 by monotonic_created_at so it wins LWW against a head 10,000s in the future, pending_sync set, every field taken from stale disk. It does not self-heal — a second boot is a clean no-op, but each new head the follower receives re-arms it (measured 16 → 1, no-op, 24 → 1), so the follower's disk wins every round and the user's edit silently reverts. Fixed in aa39d72a: retain_agent_record_at_boot publishes the 30179 only when no retained head exists, and is used by boot reconcile alone; the interactive edit paths keep republishing over heads (they resolve the overlay first), and the kind:30177 identity leg is untouched so the upgrade republish waves keep working. Three mutants — gate deleted, gate inverted, gate applied to the whole record instead of the private leg — each killed by a different arm.

  5. Self-authored config never reached the in-memory overlay — the write-side twin of (3). PrivateConfigOverlay has exactly two fill paths and both are inbound-only: insert_patch on an Applied inbound event (commands/personas/inbound.rs) and boot hydration (event_sync.rs::hydrate_private_config_overlay). Neither fires for an event this device authored, because the relay's echo of our own event dedupes to Skipped in retain_inbound_event (strictly-newer guard). So after any local edit the overlay stays pinned at the last received generation, and the next same-session edit resolves that stale patch on top of a fresher disk record and publishes a silent revert — again as a validly-chained successor, indistinguishable from a real edit. Live symptom: an edit sets parallelism 17 → 19, the immediately following rename republishes 17. Note that the resolve-before-mutate fix from (3) is what makes this reachable in the reverting direction: the sites now trust an overlay that no longer tracks this device's own writes. Fixed in 6f486e88: one write-through at one seam — retain_managed_agent_pending (commands/agents.rs), after retain_agent_record commits, reads the just-retained 30179 head back out of the same open conn and inserts it into the overlay (PrivateConfigOverlay::absorb_retained_head). A shared patch_from_retained_row() decode helper is refactored out of hydrate_from_retention, so hydration and write-through share exactly one decode path. The single seam covers all five interactive writers (edit/rename, create, settings, start, rename-rollback) with no per-caller copies; reconcile.rs is deliberately untouched (retain_agent_record takes conn+keys, no AppState), and personas/snapshot/import.rs's inline retain writes only kind:30177, so it has no overlay concern. Two deliberate design calls, both the conservative side of their trade: absorb unconditionally rather than only when the retain reported a change (the overlay can never outrun retention), and never clear on a missing or undecodable head — leave the existing entry alone (pinned with a positive control). The row is read back from retention rather than inserting the in-memory record; Eva independently derived the same design before seeing the diff, so that choice is load-bearing on two derivations. Four mutants, each killed: absorb made a no-op (reproduces the live symptom exactly — left: Some(17), right: Some(19)), the production absorb call deleted, absorb ordered before the retain instead of after, and a fourth ordering variant.

Contract of record for kind:30179 writes — two clauses, not one. Review corrected an earlier draft of (5) that implied every 30179 write funnels through the seam; that is false at a named line. retain_private_agent_record has two non-test callers: retain_agent_record (reconcile.rs:190, the seam's path) and the sibling retain_agent_record_at_boot (reconcile.rs:163), which never passes through retain_agent_record and so never reaches the write-through. Defect (4)'s own fix is what made that sibling a 30179 writer, which is why a caller-count on the funnel symbol could not see it. The class is closed by two independent mechanisms: (a) interactive writers are funnelled through the seam, guarded by the exact-count source guard; (b) the boot writer is gated head-absent (reconcile.rs:157-163), guarded by defect (4)'s three mutants — head-absent means there is no relay state to be stale against, so that write cannot be a revert and an absent overlay entry for it is correct rather than stale. Backstop: run_event_sync runs reconcile then hydrate_private_config_overlay (event_sync.rs:19-20), and hydration is a wholesale *overlay = hydrated replacement, so boot's own writes reach the overlay one line later regardless. Residual, accepted pre-merge: a future writer calling retain_private_agent_record directly, or a third boot-ish sibling, lands outside both mechanisms and outside both guards (the exact-count guard only reads commands/agents.rs) — the same residual shape write_site_resolve_guard already accepts and documents.

Causal note, on the record: defect (4) was made reachable by the fix for (2). Before keyring hydration, retain_private_agent_record's empty-nsec skip returned early for every keyring-resident record, so boot never built a 30179 at all — the skip was incidentally protecting this path. (2) is still correct (an untouched agent must publish its first 30179 on a default build), but it removed a guard that was load-bearing for something else. A control arm with an absent nsec confirms the head survives, pinning the line.

Coverage honesty: (2)'s original regression test asserted the harmful act — it pinned that boot publishes a 30179, which at the time meant publishing stale disk over a head. A green suite ratified the clobber. Chosen fix keeps that test meaningful by scoping it to the head-absent case.

Known follow-ups, out of scope here:

  • Auto-start on launch spawns from raw disk. Every interactive path resolves the overlay first (resolved_local_record / resolved_records), but restore.rs contains none of resolved_local_record, resolved_records, resolve_local_record, or private_managed_agent_overlay — it loads raw load_managed_agents (restore.rs:49) and Phase B spawns those records. On a follower, start_on_app_launch agents therefore run on stale private config. Independent of the (4) fix, which stops the republish, not the spawn. (Its two resolve_effective_* references at restore.rs:41/245 are the persona→global fallback resolver, a different mechanism.) Related, unmeasured: spawn_event_sync (workspace.rs:240) and restore_managed_agents_on_launch (workspace.rs:277/291) are spawned without either awaiting the other, so even an overlay-aware restore could read the overlay before hydration populates it.
  • resolve_effective_config callers (runtime.rs:200/437, agents_deploy.rs:141, spawn_snapshot.rs:248) each take a record the caller may or may not have overlaid — needs a per-caller ruling.
  • the PrivateConfigPatch collapse (−169 LOC) lands as a separate commit after live E2E.

Export/card overlay fold (692fdafa)

Relay-hosted agents made two export gaps user-visible: a relay-only agent (30179 head, never started on this device) failed JSON/PNG/card export with "agent not found" until first START, and a follower device exported stale disk values instead of the effective 30179 config it runs. Fix folds resolved_records (the same overlay resolution the agent list uses) at all three resolve_from_lists callers — materialize_snapshot_bytes (JSON/PNG/send-to-channel), card_mint_key_status, mint_agent_card — and gives build_snapshot's respond_to/allowlist the instance fallback parallelism already has (mode and list travel together, never mixed). Card mint then inherits the live kind:0 avatar for free, since its profile fetch runs after resolution. 5 regression tests; suite 2283/0 at 692fdafa, clippy/fmt clean. Independently verified by Wren (call-path trace + suite re-run at the exact SHA) and Sami (mutation matrix + measured round-trips, RESEARCH/PR4999_EXPORT_OVERLAY_FOLD_VERIFICATION.md).

Behavioral note (Sami, measured): the respond_to fallback changes ordinary exports too, not only relay-hosted ones. Any record with definition_respond_to: None — which commands/agents.rs mints for every definition-less keyed instance — now exports its explicit instance mode where it previously exported None. In particular, allowlist pubkeys now leave the device in exports that previously omitted them. This is the intended fix (exporting None while enforcing a mode was the bug), and import already discloses the entries via hasSourceAllowlist + the full source_allowlist + pre-confirm manifest_json — but the blast radius is every export, stated here on the record.

Narrow pre-existing shape, follow-up not a gate: respond_to=Allowlist with an empty list now exports as allowlist + [], which resolve_snapshot_import_behavior hard-rejects on import (previously that record exported None and imported silently as owner-only). Every local writer guards this shape; the one unguarded writer is inbound kind:30177 (apply_inbound_managed_agent, inbound.rs:494-495), which assigns respond_to fields without validation. This commit changes the symptom, not the hole.

Coverage honesty, same class as defect (5): the three fold sites live inside #[tauri::command] bodies, so unit tests cannot observe the wiring — Sami's mutation run showed all three folds deletable with a green suite. Closed by export_resolver_overlay_fold_guard (exact-count source guard, snapshot.rs: 1 / card.rs: 2, with a self-test proving the guard can fail), landing as its own commit from Sami's verification worktree; against it, all three deletion mutants fail, each naming its file.

Deferred to the relay-hosted cutover (fidelity, not function): kind:0 avatar hydration for ordinary JSON/PNG manifests, name_pool, START-time persona_id severance, and team snapshot export overlay-folding.

Validation

  • full desktop lib suite at 6f486e88: 2365 passed / 0 failed / 15 ignored (--all-features) — the 2360 at aa39d72a plus the 5 new tests for defect (5); 2360 was in turn the 2356 baseline at c80c4c17 (reproduced independently by two reviewers in separate worktrees) plus the 4 tests for defect (4)
  • cargo fmt --check clean; cargo clippy --workspace --all-targets -D warnings clean
  • all pre-push hooks passed at c80c4c17, aa39d72a, and 6f486e88 (six at 6f486e88): branch skew, desktop-check (file-size ratchet), rust-tests, mobile-test, desktop-test (4387 frontend tests / 0 failed), desktop-tauri-checks. Nothing pushed with --no-verify.
  • the desktop file-size ratchet was verified live rather than assumed: padding the new test file past the limit makes it fail ("allowed 1000"), so its pass at aa39d72a is not vacuous
  • regression tests include defect probes (assertions inverted post-fix), fix verifications with negative controls, wrong-fix probes, and mutation checks (deletion/ordering/re-copy mutants all fail)
  • independent adversarial review by Mongo on the original head: clear, no blockers; Eva/Sami review findings above addressed at c80c4c17
  • live-local two-device E2E (restart persistence, first-boot keyring recovery, stale-republish, fresh-device materialization) per TESTING.md — Max, on a device-addressed v2 rig at exact SHA 6f486e88; arm attribution at c80c4c17 was calibration only, since defect (4) made arms 1 and 3 structurally unattributable. This gate is still open and is the remaining merge blocker: the defining 19 → rename discriminator for (5) passes live on the real Tauri backend, but the preserved test agent's scoped keyring secret is unavailable to the fresh process, so the rename profile-sync and pair-start arms refuse at their key boundary and the fresh lane has not yet produced new relay 30179 receipts.
  • independent gate on defect (5) at 6f486e88 — Eva, in her own worktree: full diff read, suite + fmt + clippy, and a personal re-run of the absorb-no-op and absorb-before-retain mutants rather than taking the author's kill list on faith

Notes

Temporary Tauri sidecar placeholders used for local builds are ignored and not committed.

Keep encrypted runnable configuration in an owner-scoped relay event while
preserving local records as migration state. Validate inbound payloads before
retention, expose fresh-device records through an ephemeral scoped overlay,
and retain public/private heads and tombstones atomically.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman requested a review from a team as a code owner August 6, 2026 02:59

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewing and commenting on Wes's behalf. Blocking verdict: changes requested. The codec, owner-only relay classification, inbound validation-before-retention, scope-clear serialization, and atomic public/private retention all look sound. However, the new overlay start branch creates lifecycle holes that make relay-restored agents unsafe or impossible to manage. Please route resolved overlay records through a lifecycle that preserves the existing transition/preflight/provider/persistence invariants and supports stop/delete for truly relay-only records.

This PR changes behavioral tests and adds codec, inbound, overlay, and reconciliation coverage; none of the added overlay tests exercises a real start → stop/delete lifecycle or concurrent shutdown. Add regressions for a disk-backed overlaid agent and a fresh-device relay-only agent.

Focused checks at this exact clean head: cargo test -p buzz-core private_managed_agent --lib (13 passed), cargo test -p buzz-relay ingest --lib (161 passed; filter selected the ingest module plus one dependent test), and git diff --check passed. Desktop Core and Desktop E2E checks were still running when reviewed; existing CI covers the broad suites.

.map_err(|error| error.to_string())?
.contains(&pubkey)
{
return crate::managed_agents::private_config_overlay::start_relay_only_agent(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

P1 — Do not divert every overlaid disk record around its existing start lifecycle. Presence in this in-memory overlay is not equivalent to being relay-only: resolved_record can start from an existing disk-backed record and then apply the patch. This branch therefore bypasses start_local_agent_with_preflight (including effective-config/relay-mesh validation, persona handling, saving start metadata, retained publication, and profile reconciliation), and it bypasses provider deployment entirely because the helper rejects any resolved provider backend. Merely receiving a valid private event can thus make an ordinary local/provider agent follow a materially weaker or unusable path. Distinguish true relay-only records from disk-backed records and route the latter through the normal resolved local/provider pipeline; add a regression proving an overlaid disk record retains those lifecycle guarantees.

.managed_agent_processes
.lock()
.map_err(|e| e.to_string())?;
start_managed_agent_process(app, &mut record, &mut runtimes, Some(owner_hex))?;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

P1 — A fresh-device relay-only runtime started here cannot be stopped or deleted through the normal agent API. This spawns from a temporary record and stores only the runtime pair. stop_managed_agent reloads disk records and fails at find_managed_agent_mut; delete_managed_agent likewise requires a disk record and returns agent … not found. The frontend's normal local actions call those commands, so a relay-only agent can start successfully and then become uncontrollable until broader shutdown/process exit. This direct spawn also does not hold managed_agents_store_lock or the documented runtime-transition boundary, so it can run after a workspace/identity overlay clear or race shutdown and register a new child after shutdown's protected snapshot. Implement stop/delete and scope-transition behavior for relay-only records (or materialize an appropriate lifecycle record) and serialize spawn consistently; cover start → stop and start → delete on a device with no local record.

Route disk-backed overlay agents through the established preflight, provider,
profile, persistence, and runtime transition paths. Materialize fresh-device
local records before start so stop, delete, and shutdown can manage them.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman force-pushed the carl/relay-primary-agent-config branch from c38fc0f to a9b648b Compare August 6, 2026 03:45
…blish

Relay-primary managed-agent config (kind:30179) delivered config for exactly
one launch and could overwrite a newer device's config with an audit-clean
event. Three defects, each measured with a probe before being fixed.

Overlay boot rehydration. `PrivateConfigOverlay` was in-memory only and its
single writer fired only when an inbound 30179 was strictly newer than the
retained row. On every second-and-later launch the backfill re-delivered the
same event, retention deduped it to Skipped, and the overlay stayed empty for
the whole session, so every read silently fell back to stale disk. Rebuild the
overlay from the retained rows on the `run_event_sync` boot seam, which already
runs post-identity-resolution with the resolved owner keys and scoped db path.
Adds `get_retained_events_of_kind` (nothing read rows back by kind before).

Boot publication on default keyring builds. `reconcile_agents_in_dir_at` read
`managed-agents.json` raw, so on a default `system-keyring` build the nsec was
keyring-resident and `retain_private_agent_record`'s empty-nsec skip fired for
every untouched agent: zero 30179s published on first boot. Hydrate keys in the
reconcile path. The doc comment asserted the bug ("keys are never needed here")
and is corrected, so the next reader is not re-licensed to reintroduce it.

Stale-disk republish at three write sites. `private_payload_from_record`
serializes every config field, so retaining a disk-derived record on a device
following a newer relay head republished the other fields from stale disk;
`monotonic_created_at` then floored the write at head+1, so it won LWW, bumped
the generation, and chained `prev` to the head it destroyed — a validly-chained
successor indistinguishable from a legitimate edit. Resolve the overlay before
the local mutation at `agent_models.rs` (edit), `personas/update.rs` (persona
rename) and `agents.rs` (pair-start snapshot re-apply).

Ordering is the fix at each site, and it differs per site, which is why this is
not centralized in `retain_managed_agent_pending`:
- edit: resolve before the user's patch, or the patch is discarded
- rename: resolve for the payload only; the `name != old_display_name` gate must
  keep reading disk state, and name/display_name are re-applied after
- pair-start: resolve before `apply_persona_snapshot`, so the definition quad
  stays definition-authoritative

Tests. Regression coverage for all three defects plus three probes that pin the
wrong fixes (centralized resolve discards edits; resolve-before-gate skips the
rename; swapped pair-start ordering lets the overlay clobber the persona quad),
each with positive and negative controls.

`write_site_resolve_guard` is a source-level assertion, added because the
behavioural tests cannot see the production wiring: every write site is inside a
`#[tauri::command]` needing a live `AppHandle`, so the tests call
`retain_agent_record` directly and stayed green with the production resolve
deleted (measured: 2261 passed / 0 failed). The guard fails when a site loses
its resolve or a new site is added without one, and carries its own vacuity
control.

File-size ratchet. `agent_models.rs` sat at the 1000-line cap before this
change (1024 lines), so the ratchet allows it zero growth, and
`reconcile/tests.rs` crossed the cap. Two verbatim moves, following the seams
each file already uses: `normalize_agent_models` to
`agent_models_normalize.rs` (`#[path]` submodule, like the databricks/
openrouter/discovery helpers) and the stale-republish test family to
`reconcile/tests/stale_republish_tests.rs` (like
`personas/update/name_propagation_tests.rs`). Both moved blocks are
byte-identical to their pre-move bytes apart from one visibility line
(`pub(super)` -> `pub(crate)`, required by E0364 on the re-export), and the
write-site guard's deletion mutant was re-run after the move: still dead.

Item 3 from the review (collapsing the 27-field `PrivateConfigPatch` mirror,
~169 deletable lines) is deliberately not in this commit; it is an overlay
refactor and lands separately.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>

@klopez4212 klopez4212 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewing and requesting changes on Kenny Lopez's behalf.

Blocking: boot reconcile destroys the retained relay head before the overlay is hydrated.

run_event_sync calls reconcile_agents_to_events first and only then calls hydrate_private_config_overlay (desktop/src-tauri/src/event_sync.rs:17-20). The reconcile reads raw managed-agents.json records and passes them directly to retain_agent_record (desktop/src-tauri/src/managed_agents/reconcile.rs:91-119); it cannot consult the overlay because the overlay has not been rebuilt yet.

On the exact restart this commit intends to fix, where disk has config A and retention contains a newer relay config B, boot therefore does this:

  1. build a private payload from stale disk A;
  2. compare it with retained B, see a difference, and retain A as generation B+1 / pending_sync = true;
  3. hydrate the overlay from that newly overwritten A row.

The valid relay head is lost locally and the stale successor is queued to overwrite it remotely. In other words, restart persistence still fails, now through the boot writer rather than the interactive writers.

Hydrate/validate the retained overlay before any disk→private reconcile, and make boot reconcile resolve each disk record against that hydrated head before retaining it (while preserving the intended behavior for records with no retained private head). Add an end-to-end unit seam around the real boot ordering: seed stale disk A plus newer retained B, run the boot reconcile/hydration path, and assert retention and the overlay remain B with no stale private republish queued. The current standalone hydration and stale-republish model tests do not exercise this production ordering.

@klopez4212 klopez4212 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blocking on a boot-order regression in the restart recovery path. The codec, inbound validation, scope clearing, lifecycle materialization, and stale-write-site fixes otherwise look carefully constructed. I reviewed clean head c80c4c17b047d7580d50ee5261897e0fb192bb2d; git diff --check origin/main...HEAD is clean. I did not duplicate the broad suites already reported/covered by CI.

Comment thread desktop/src-tauri/src/event_sync.rs
…er head

Boot reconcile is a fourth stale-disk republish site, in the same class as
the three write sites fixed in the previous commit but worse: it fires at
launch, unprompted, for every agent on a device that follows another
device's config.

`reconcile_agents_in_dir_at` reads `managed-agents.json` raw and cannot
resolve the private-config overlay -- `hydrate_private_config_overlay` runs
after this leg (`event_sync.rs:19-20`) and reads the rows this leg writes.
Inbound kind:30179 updates the overlay and retention but never the JSON, so
on a follower disk is stale by construction. Rebuilding the 30179 projection
from disk then republishes every stale field over device A's newer head as a
validly chained gen+1 successor, and `monotonic_created_at` floors it at
head+1 so it wins LWW. Measured: gen 5 -> 6, `prev` = the clobbered head,
`created_at` = head+1 against a head 10,000s in the future, `pending_sync`
set, and every field (name, system_prompt, parallelism, env_vars) taken from
stale disk.

It also does not self-heal. A second boot is a clean no-op because disk now
matches the head it wrote, but each new head device A publishes re-arms it:
measured 16 -> 1, no-op, then 24 -> 1. The follower's disk wins every round
and the user on A sees their edit silently revert.

The previous commit's keyring hydration is what makes this reachable. Before
it, `retain_private_agent_record`'s empty-nsec skip returned early for every
keyring-resident record, so boot never built a 30179 at all -- the skip was
incidentally protecting this path. Hydrating keys is still correct (an
untouched agent must publish its first 30179 on a default build), but it
exposed everything downstream of the guard it removed. A control arm with an
absent nsec confirms the head survives, pinning the causal line.

Fix: `retain_agent_record_at_boot` publishes the 30179 only when no retained
head exists, and is used by boot reconcile alone. That keeps the requirement
boot exists to serve -- an agent whose nsec lives in the keyring gets its
FIRST private config published -- while leaving an existing head to the
interactive edit paths, which resolve the overlay before retaining and so
author from relay-fresh state. The kind:30177 identity leg is untouched, so
the upgrade republish waves keep working.

Resolving the overlay at boot instead was rejected and is pinned by a
permanent wrong-fix probe: an offline local edit lives on disk and in an
unflushed `pending_sync` 30179, so resolving disk through an overlay
hydrated from the older head would discard it -- the centralized-resolve
failure from the previous commit, with boot's blast radius.

Tests (4): the fix verification asserts the head is byte-identical after
boot and nothing is enqueued; two requirement-preservation arms (first 30179
still published when no head exists; 30177 still republishes when a private
head is present) so the fix cannot be satisfied by never publishing at boot
or by gating at the wrong level; and the wrong-fix probe. Three mutants,
each killed by a different arm: gate deleted, gate inverted, gate applied to
the whole record instead of the private leg. Mutants re-run after cargo fmt.

Desktop lib suite 2360 passed / 0 failed / 15 ignored (--all-features);
cargo fmt --check, cargo clippy --workspace --all-targets --all-features
-D warnings, and the desktop file-size ratchet (against the CI base) all
clean -- the ratchet verified live with a padding control that fails it.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@klopez4212
klopez4212 dismissed their stale review August 6, 2026 17:37

Dismissed at Kenny Lopez’s request after reviewing aa39d72. The new boot-only head-presence gate prevents stale disk from replacing or queuing over an existing retained 30179 while preserving first private publication and public 30177 reconciliation.

klopez4212
klopez4212 previously approved these changes Aug 6, 2026

@klopez4212 klopez4212 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewing and approving on Kenny Lopez's behalf at aa39d72aac57aeda49cb3c38db7c9c8ed4af24f1.

My boot-order blocker is resolved. The boot-only retention path now leaves any existing kind:30179 head byte-for-byte intact and only creates the private event when no retained head exists. This prevents stale disk state from becoming a newer successor, while preserving first private publication and the independent kind:30177 public reconciliation path. The added regressions cover all three requirements and pin why overlay resolution at boot would discard an unflushed offline edit.

Focused review: the four added boot tests directly exercise the gate and its preservation cases; git diff --check origin/main...HEAD passed at this exact clean head. I attempted the focused Rust tests locally, but this checkout lacks the required generated sidecar desktop/src-tauri/binaries/buzz-acp-aarch64-apple-darwin, so the Tauri build script stopped before compiling the test target. Existing CI is the broad validation authority.

@wesbillman

Copy link
Copy Markdown
Collaborator Author

Reviewing and requesting changes on Wes Billman's behalf at aa39d72aac57aeda49cb3c38db7c9c8ed4af24f1.

Blocking: launch auto-start bypasses the relay-primary overlay and races its hydration.

apply_workspace starts event sync asynchronously (workspace.rs:233-245), then independently spawns restore_managed_agents_on_launch (workspace.rs:285-295) without waiting for event sync. The restore path reads raw managed-agents.json (restore.rs:116), selects start_on_app_launch candidates from those raw records (restore.rs:168-192), re-collects raw records after persona snapshotting (restore.rs:196-222), and passes them directly to spawn_agent_child (restore.rs:291-341). It never resolves private_managed_agent_overlay.

Therefore, on a follower where disk has private config A and retention/relay has newer config B, an auto-start agent can launch with A: stale prompt/model/provider/runtime, env vars, allowlist, relay mesh, auth tag, or agent nsec. The new boot retention gate correctly prevents A from being republished over B, but does not prevent A from executing. Waiting only for overlay hydration would still be insufficient on a fresh device until backfill has delivered B; the startup contract needs an explicit authoritative-state/readiness decision rather than two racing best-effort tasks.

This contradicts the PR's relay-primary behavior at the most consequential read site. Please serialize auto-start behind the authoritative private-config bootstrap and resolve each candidate through the overlay before preflight/spawn, with a safe offline policy. Add a production-seam regression that seeds stale disk A plus retained newer B, runs launch restore, and proves the spawned snapshot uses B; include a delayed-hydration race arm. A test that only exercises resolve_local_record is not enough.

Behavior-changing tests: the PR adds strong codec/inbound/retention and stale-republish regressions, including restart overlay hydration and the new boot-only 30179 head-preservation gate. However, none covers launch auto-start using the relay-resolved record or the ordering between spawn_event_sync and restore. That missing behavioral test corresponds directly to this blocker.

git diff --check 16cc3de6d6bb23ebdc3a928172fb585494079232..aa39d72aac57aeda49cb3c38db7c9c8ed4af24f1 passes. Current broad CI is not validation evidence for this head: the CI path detector and release-candidate jobs were cancelled after ~48 minutes, and Desktop Core/Unit Tests plus most jobs were skipped.

…erlay

The overlay only ever learned config from events this device RECEIVED. Both
fill paths are inbound-only: `insert_patch` on an `Applied` inbound event
(`personas/inbound.rs:228`) and boot hydration (`hydrate_from_retention`).
Neither fires for an event this device authored -- the relay's echo of our
own event dedupes to `Skipped` in `retain_inbound_event`, because the row is
already retained.

So the overlay stays pinned at the last received generation for the whole
session. `update_managed_agent` resolves that patch onto the disk record,
applies the user's edit, saves, and retains -- correct for ONE edit. The
SECOND edit in the same session resolves the same stale patch onto the now
fresher disk record, reverting the first edit, and publishes the reversion as
an audit-clean gen+1 successor that wins LWW. The resolve result is written
back to disk, so the revert is durable, not just in-flight.

That is Max's live gate red on a single backend: gen 3 published parallelism
19, the following rename returned 17 and published 17 as gen 4. It stands
independent of the Device-A/B attribution he retracted.

Fix: after `retain_agent_record` commits, read the just-retained kind:30179
head back out of the same connection and `insert_patch` it. One seam --
`retain_managed_agent_pending` -- covers all five writers (create, settings,
edit/rename, start, rename-rollback) with no per-caller copies and no
new-writer trap. `reconcile.rs` is untouched: `retain_agent_record` takes
`conn`+`keys` and threading `AppState` through it would drag the boot
reconcile into the diff for nothing. No new lock edge: callers already hold
`managed_agents_store_lock` and the overlay lock is taken under it, the same
order `resolved_local_record` uses. Decode is factored into
`patch_from_retained_row`, shared with boot hydration, so both learn config
through exactly one path.

Absorbing unconditionally (not gated on the retain reporting a change) keeps
the overlay from ever running ahead of retention: every insert comes from a
row read back out of the database. A missing or undecodable head leaves the
current entry alone rather than clearing it.

Coverage. `sami_second_edit_in_one_session_preserves_the_first_edit` runs the
two-edit sequence through the real retention engine, seeding gen 2 via
`retain_inbound_event` + `hydrate_from_retention` so the overlay is populated
exactly as boot populates it. Its negative control runs the same sequence
without the write-through and asserts the revert to 17, so the main assertion
cannot pass vacuously. `absorb_retained_head_leaves_the_overlay_alone_when_
there_is_no_head` pins the no-clear contract with a positive control proving
the same call DOES update on a present head.

Both behavioural tests model the helper body -- every caller is inside a
`#[tauri::command]` needing a live `AppHandle`, so deleting the production
call leaves them green (the failure mode already documented on
`write_site_resolve_guard`). Three source guards close that: exact call count
of 1, source order retain-before-absorb, and a negative control proving the
searched literals are load-bearing.

Mutants, 4/4 killed: (1) `absorb_retained_head` body no-oped -> the red test
fails with exactly the live symptom, `left: Some(17) right: Some(19)`;
(2) production call deleted -> both source guards fail; (3) production call
moved before the retain -> the order guard fails; (4) test helper's order
swapped -> the behavioural test fails.

Gate at this tree: desktop lib suite 2272 passed / 0 failed / 14 ignored,
`cargo clippy --workspace --all-targets` clean, `cargo fmt --check` clean,
`check-file-sizes` rc=0 with CHECK_FILE_SIZES_BASE pinned to the merge-base
with origin/main.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
tlongwell-block added a commit that referenced this pull request Aug 7, 2026
…#5133)

## What

Relay-only carve-out of the ingest half of #4999: generic EVENT ingest
now accepts kind:30179 (NIP-PMA private managed-agent config). One file,
`crates/buzz-relay/src/handlers/ingest.rs`, 16 insertions / 15
deletions; **two semantic lines**, byte-identical to the ingest hunk of
#4999 at `6f486e88`:

1. `required_scope_for_kind`: 30179 requires `Scope::UsersWrite` — same
arm as its public sibling 30177 and the other owner-authored NIP-AP
kinds.
2. `is_global_only_kind`: 30179 is owner-global, keyed `(pubkey, kind,
d-tag)`; a stray `h` tag must not channel-scope it.

The rest is import reflow plus replacing the guard test with a positive
one (`private_managed_agent_kind_is_owner_scoped_global_user_data`:
asserts UsersWrite scope, global-only, no h-channel scope).

## Why the guard test can be retired

The removed test
(`private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists`)
pinned a stated precondition: *"must not enter generic EVENT ingest
before privacy and aggregate CAS deploy."* Both halves are resolved:

- **Privacy** — the author-only read gates for 30179 shipped to main
with #4593: `AUTHOR_ONLY_KINDS` membership, `req.rs` pre-filter + result
gates, `count.rs`, `event.rs` fanout, and the bridge pre-filter
(`bridge.rs:999-1000` returns `restricted: author-only kinds require
authors=[self]` / 403). Only the author can read the event back.
- **Aggregate CAS** — #4999 settled generation as **advisory**: the `g`
tag is shape-validated, never relay-enforced. Last-write-wins per
coordinate is the contract of record (see the kind:30179 contract blurb
in #4999), so no CAS mechanism is pending on the relay side.

## Why this is inert to existing relays and clients

- No production desktop code on main authors kind:30179 — the codec
(`private_managed_agent.rs`) has zero non-test callers. This PR accepts
a kind nobody can produce yet.
- Content is opaque NIP-44 ciphertext to the relay; the relay never
decrypts it.
- Reads remain author-only via the already-shipped gates above.
- Storage is the standard parameterized-replaceable path already
exercised by kinds 30175–30178. No schema, config, or migration changes.

## Testing

- Full `buzz-relay` package suite at this commit: 859 passed, 1 failed —
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` (504
vs 200), which **reproduces identically on clean main `769ac70b`** with
this change stashed; pre-existing/environmental, not introduced here.
- New positive ingest test passes.
- Pre-push hooks green (branch-skew, rust-tests, desktop-tauri-checks).

## Relationship to #4999

#4999 (relay-primary agent config, desktop half) stays DO-NOT-MERGE
pending live relay receipts + real CI; once this lands and deploys, its
live test simplifies to plain `desktop-standalone` against the real
relay, and #4999 rebases to drop its now-duplicate ingest hunk
(identical bytes → trivial rebase).

Originating thread:
buzz://message?channel=06f13ed3-0557-4ac2-922c-1545dd00bf97&id=2a43b3b4933a2ea78b77088619251c061355f9b7b6dc29ea0d702193f2344149


## Brownfield FTS note (review findings, operator-ruled non-blocking for
this PR)

Max and Sami independently identified that the FTS privacy skip-set is
regime-dependent: migration 0008 installs the positive allowlist (`kind
IN (0, 9, 40002, 45001, 45003)`) **only on an empty events table**; an
already-populated database keeps the 0001/0005 negative skip-list
(wrapped by 0014 to add 30350), which omits 30179 — so on such an
installation this PR admits 30179 rows whose NIP-44 ciphertext gets
indexed by `to_tsvector`. Sami measured both regimes against real
Postgres (brownfield: 30179 INDEXED; fresh: NULL) and demonstrated the
existing drift test only exercises the fresh regime.
`schema/schema.sql:222`'s canonical literal is also the negative list
and omits 30179. Migration dates put any relay deployed with data before
0008 landed (2026-07-13) in the brownfield class.

**Scope of exposure (Sami's trace):** not a content leak —
`event_visible_to_reader` / `is_author_only_event` gates hold on both
search surfaces (`req.rs:725`, `bridge.rs:1770`), so foreign readers
receive nothing. Lost is the storage-level NULL-tsv backstop plus FTS
page budget burned on post-filtered hits.

**Operator ruling (Tyler, events `1472e5b6`, `cbd368ed`):** ship this PR
without an exclusion migration. Safety argument that makes this sound
rather than merely accepted: main has **zero non-test 30179 writers**
until #4999's desktop half deploys — no 30179 rows can exist, so nothing
can be indexed in any regime while this PR is the only half live.



**Additional review characterizations (Sami, non-blocking, on the
record):**
- *Behavioral delta enumerated:* routing triple
(`required_scope_for_kind` / `is_global_only_kind` /
`requires_h_channel_scope`) compared for all 65,536 kinds at base
`769ac70b` vs head `77eeba6e` — exactly one row differs (30179). No
other kind or client changes behavior.
- *"SQL visibility before LIMIT" (NIP-PMA step 2):* no
`AUTHOR_ONLY_KINDS` pushdown clause exists in `buzz-db` (only
`SHARED_GATED_KINDS` has one). Author-only kinds are protected by the
pre-filter (`author_only_filters_authorized`) plus post-filter omission;
mixed-kind filters can burn candidate-page budget on discarded rows.
Pre-existing and identical for 30300/30350 — not introduced here; noted
so the NIP's step-2 checkbox is not read as fully ticked.
- *Envelope validation gap:* 30179 is the only parameterized-replaceable
kind at ingest with no per-kind envelope validator (codec grammar checks
run in the desktop writer, not the relay). Generic limits only (256 KiB,
±15 min, pubkey==identity, d-tag bound). Self-inflicted footgun bounded
to the author's own coordinate — candidate companion to the exclusion
migration in the #4999 rebase, deliberately not added here.

**Bound follow-up (required before/with the #4999 desktop half):** a
0014-shape additive migration (`pg_get_expr` capture + `CASE WHEN kind =
30179 THEN NULL ELSE (<existing>) END` wrap), add 30179 to the
`schema/schema.sql:221` literal, and a brownfield-regime variant of the
FTS drift test, per Sami's finding. Deploy-time spot check if ever
wanted: `SELECT pg_get_expr(d.adbin, d.adrelid) FROM pg_attrdef d JOIN
pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE
d.adrelid = 'events'::regclass AND a.attname = 'search_tsv';`

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Absorbs the relay half that shipped separately in #5133 (squash commit
ad92335): the kind:30179 ingest acceptance hunk in
crates/buzz-relay/src/handlers/ingest.rs was byte-identical on both
sides, so this merge removes all relay-side changes from this PR's
diff. #4999 now carries only the desktop + buzz-core codec half.

No rebase, no force-push — history preserved per operator instruction.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>

* origin/main:
  fix(bench): mention the orchestrator by pubkey when posting the task (#5136)
  feat(relay): accept kind:30179 private managed-agent events at ingest (#5133)
  fix(media): require authenticated reads (#4610)
  fix(desktop): preserve authoritative agent avatars (#4984)
  fix(desktop): next/back navigation during key creation onboarding (#4978)
  Alert community owners and admins when a new key joins (#4900)
  fix(desktop): prevent sidebar prefs from reverting on stale-localStorage boot (#5086)
  chore(hooks): run desktop typecheck in pre-push (#5110)
  feat(identity): recover desktop identity from a signed-in phone (#4845)
  fix(buzz-agent): classify read timeouts distinctly in LLM error messages (#4959)
  Refine agent runtime controls (#5026)
  test(desktop): await thread scroll anchor (#3174)
  Improve desktop mobile pairing flow (#5024)
  feat(desktop): show selected community in rail (#5000)
  fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990)
  fix(desktop): skip native notifications outside app bundles (#5004)
  ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes (#3862)
  fix(desktop): virtualize channel member lists (#4991)

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
@marisgoerner-cell

Copy link
Copy Markdown

Field evidence from Desktop 0.5.5 is now tracked in #5163: eleven owner-reviewed draft creates left kind:0 profiles and bot memberships but no local managed records, no retained kind:30177 heads, and no durable key-store update. This means the identities have no private config for fresh-device recovery. Please include a create-transaction/interruption regression and explicit recovery UX for pre-30179 profile-only orphans when evaluating this PR.

…t respond_to falls back to instance fields

Relay-hosted agents made two gaps in the export/card resolvers user-visible:

- Gap B: a relay-only agent (30179 head, never started on this device) has
  no disk record, so materialize_snapshot_bytes / card_mint_key_status /
  mint_agent_card failed with "agent not found" until first START.
- Gap A: a follower device exported the stale disk snapshot instead of the
  effective 30179 config it actually runs.

Fold the private-config overlay onto the disk list at all three resolver
sites — the identical resolution the agent list already uses
(resolved_records: applies patches to disk records AND synthesizes
relay-only entries). Card mint then also inherits the correct live kind:0
avatar for free, since its existing profile fetch runs after resolution.

Also give build_snapshot's respond_to/allowlist the same instance fallback
parallelism already has: an overlay-materialized record carries its
enforced respond_to only on the instance fields, so without the fallback a
relay-hosted agent exported respond_to=None while actually enforcing an
allowlist — a shared agent that behaves differently than advertised. Mode
and list travel together (definition mode -> definition list, instance
fallback -> instance list, never mixed).

Deferred as fidelity-not-function (tracked for the relay-hosted cutover):
kind:0 avatar hydration for ordinary JSON/PNG manifests, name_pool.

Verified: cargo test in desktop/src-tauri — 2283 passed, 0 failed;
clippy --tests clean; fmt clean.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Sami and others added 2 commits August 7, 2026 14:35
The behavioural tests added with the export/card overlay fold model the
resolution by calling `resolved_records` / `resolve_from_lists` directly.
They cannot reach the production call sites: all three live inside
`#[tauri::command]` bodies needing a live `AppHandle` + `State<AppState>`.

Measured on the parent commit, one full-workspace run per mutant:

  delete the snapshot.rs fold      -> SURVIVED (2283 passed, 0 failed)
  delete the card precheck fold    -> SURVIVED (2283 passed, 0 failed)
  delete the card mint fold        -> SURVIVED (2283 passed, 0 failed)
  revert the respond_to fallback   -> killed (3 tests)
  mix definition mode + inst. list -> killed (1 test)

So all three production folds were deletable with a green suite. The
fallback and the mode/list-atomicity invariant were genuinely covered;
only the wiring was not.

Add an `include_str!` source guard with EXACT per-file call counts
(snapshot.rs: 1, card.rs: 2) — same remedy as `write_site_resolve_guard`
in private_config_overlay.rs. Exact rather than a lower bound: `>= 1` per
file would not notice one card site losing its fold while the other
gained a second one. A second test strips the searched string and asserts
zero matches remain, so a typo in the pattern cannot make the guard pass
vacuously.

Re-ran all three mutants against the guard, deleting each fold statement
outright so the mutant compiles: all three fail, each naming its own
file, with the unmutated control passing and both sources restored
byte-identical after each arm.

The guard lives in a sibling `tests_export_resolver_guard.rs` rather than
in `snapshot/tests.rs`, following the convention `tests_locked.rs`
documents: `snapshot/tests.rs` is already over the 1000-line desktop
ratchet, so a file already above the limit may not grow. This commit adds
3 lines to snapshot.rs (528 total) and one new 67-line file, so it clears
the ratchet on its own; the two pre-existing ratchet rows on this branch
are unchanged by it.

Verified: cargo test --workspace in desktop/src-tauri — 2285 passed,
0 failed, 14 ignored; cargo clippy --workspace --all-targets -D warnings
clean; cargo fmt --all --check clean. All at parent 692fdaf with
`git rev-parse HEAD` checked in the same shell.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
…y fold into one helper, move overlay tests to a sibling file

CI's Desktop Core ratchet is red at 692fdaf (Sami's finding): card.rs
1000->1015 and snapshot/tests.rs 963->1077, both pinned at the cap at the
merge base, so neither may grow by a line.

Two moves, no behavior change:

- Extract the three inline overlay folds into one helper,
  `load_effective_managed_agents` (snapshot.rs), called at all three
  resolver sites. card.rs returns to 999 (< merge-base 1000); the helper's
  doc comment carries the rationale and the lock-order contract
  (managed_agents_store_lock outer, overlay inner, released before return).

- Move the overlay_fold behavioural tests to a sibling
  snapshot/tests_overlay_fold.rs, #[path]-included per the tests_locked.rs
  convention. snapshot/tests.rs returns to 968 (< cap).

The extraction changes what Sami's source guard must pin, so
tests_export_resolver_guard.rs now asserts three legs: every resolver site
calls the helper (exact per-file counts), no resolver file reads raw
load_managed_agents outside the helper's own body (exact allowed counts),
and the helper itself still performs the resolved_records fold — plus the
vacuity self-test for every needle.

Verified at this commit: ratchet passes vs merge-base
(CHECK_FILE_SIZES_BASE=$(git merge-base origin/main HEAD)); full workspace
suite 2387 passed / 0 failed / 15 ignored; clippy --workspace --all-targets
-D warnings clean; fmt clean. Guard mutation arms all KILLED with sources
restored byte-identical (cmp vs pinned copies): card mint bypasses helper,
card precheck bypasses helper, snapshot resolver bypasses helper, helper
degrades to plain load; unmutated control green.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
@wolfyy970

Copy link
Copy Markdown

I put the auto-start fix in #5278 so it can be reviewed and merged independently. It waits for the selected community’s authoritative agent state, launches from the relay-primary overlay, and keeps agents stopped when that state cannot be proven complete.

I also covered the timing failures around live backfill, workspace switches, retries, and post-spawn deletions.

The defect-(2) fix routed boot reconcile through storage::hydrate_keys,
which resolves the ambient agent_secret_store(). From a unit test on
macOS that reaches the real Keychain: the ACL prompt for the shared
buzz-desktop-dev service blocks a headless test binary forever, so
'cargo test --workspace' (the desktop-tauri-test gate) never terminates
on any machine that has run the dev desktop. Ubuntu CI has no keychain,
which is why it stayed green.

Thread the existing KeyStore seam through the reconcile core instead:
reconcile_agents_in_dir_with takes Option<&impl KeyStore>, the
production wrapper passes agent_secret_store(), and the test helper
passes None so no reconcile unit test can ever reach the OS keyring.
New test injects the FakeKeyStore to pin the hydration behavior itself:
keyring-resident nsec -> first 30179 published and decrypts back to the
same key; None store -> empty-nsec skip, no 30179. That test fails if
the hydrate call is dropped.

No production behavior change: reconcile_agents_in_dir_at resolves the
same store hydrate_keys did, and hydrate_keys_with is the same code
path it already wrapped.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
jedwards27
jedwards27 previously approved these changes Aug 20, 2026

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent — APPROVE on exact head bd856e9565c253263d9efe1fe20d3fe1dcccd43c (base 3ee465e12b815a191d902856440e2f3348bda506). No material correctness, security, or user-trust finding remains.

The post-ab620e9 fix is appropriately narrow and preserves the production contract: production reconcile still injects agent_secret_store(), while the ordinary test helper passes None, preventing ambient macOS Keychain access. The fake-store regression proves a key absent from JSON but present in the injected store still hydrates and publishes the first encrypted kind:30179; the no-store control does not publish one (desktop/src-tauri/src/managed_agents/reconcile.rs:73-109,127-133, reconcile/tests.rs:198-259, managed_agents/storage.rs:295-355). This removes the deterministic unsigned-test-binary Keychain wedge without disabling production hydration.

Validation on clean exact head:

  • Two independent full just desktop-tauri-test runs passed; main target 2741 passed, 0 failed, 18 ignored, with all remaining workspace targets passing.
  • All 31 reconcile tests passed with --test-threads=16; the prior focused reproducer completed normally.
  • Mutation-removing hydrate_keys_with made the new regression fail at the expected missing-30179 assertion; restoration passed.
  • git diff --check and the Desktop file-size ratchet passed against the pinned base.
  • GitHub run 32420598170 is pinned to this head and green across Desktop Core, smoke and relay-backed E2E, integration shards, macOS build, Windows Rust, lint, security, backend integration, and relay E2E. The Desktop Core log records the same 2741/0/18 result and the new fake-store test passing.

Residual risk: no native GUI or real multi-device relay journey was rerun for this head. This final delta is confined to headless key-store injection/test isolation; the previously repaired restart, follower, tombstone, stale-disk, and equal-second convergence paths are byte-unchanged and covered again by the full package and exact-head CI. Production necessarily retains platform keyring behavior.

Any head movement invalidates this approval.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Requesting changes at exact head bd856e9565c253263d9efe1fe20d3fe1dcccd43c for one merge-blocking integration gap:

[P1] Several production start/deploy boundaries still execute raw disk configuration instead of the relay-primary overlay.

This PR intentionally keeps relay-owned kind:30179 fields in an in-memory overlay rather than writing them back to managed-agents.json (private_config_overlay.rs:95-125). The top-level start_managed_agent path handles that correctly by materializing relay-only records and resolving disk through the overlay (commands/agents.rs:795-859). But the following final-use boundaries bypass it:

  • Pair Start/Restart and multi-community reconciliation funnel through start_pair, which reloads raw disk, finds the raw row, and passes it directly to spawn_agent_child (managed_agents/runtime_commands.rs:239-309,460-511). spawn_agent_child resolves persona/global fallback from the record it receives; it does not fold the private overlay (managed_agents/runtime.rs:406-452). A follower showing relay config B can therefore execute stale disk config A. A relay-only card can also fail pair-level Start with agent not found, because this route never materializes it.
  • Provider start initially builds from the resolved record, but deploy_to_provider explicitly discards that caller payload, waits for its deploy lock, reloads raw disk, and rebuilds the exact payload invoked from the raw row (commands/agents/provider_deploy.rs:35-85). If disk and overlay disagree, the provider receives stale prompt/model/env/credentials/access/backend state, or the start fails because the raw backend differs. Workspace provider-access reconciliation begins from the same raw rows (provider_access.rs:61-103).

These are live UI/runtime paths, not export fidelity: the launched or deployed agent can execute different configuration from what this device displays as authoritative. Please resolve/materialize at these final-use boundaries while preserving device-local lifecycle fields and the existing lock/scope checks. Add production-seam regressions with stale disk A plus overlay B for pair start and the post-lock provider payload; include a relay-only pair-start case or explicitly prevent that unsupported action.

The codec, retention ordering, boot hydration/restore, authorization, equal-second convergence, export/card folds, and current exact-head CI otherwise reviewed clean. The inbound tombstone path also lacks runtime/keyring teardown, but kind:30177 tombstones already reached that defect before this PR; I am reporting it as serious adjacent lifecycle debt rather than misattributing it as this PR’s blocker.

… boundaries

Carl's round-9 P1: three production final-use boundaries still executed
raw disk configuration instead of the relay-primary overlay, so a
follower device showing relay config B could launch or deploy stale
disk config A.

- start_pair (Pair Start/Restart): materialize a relay-only record
  before the disk lookup (previously "agent not found" on this route),
  then hand spawn_agent_child the overlay-resolved record with the
  persona snapshot re-applied last — extracted as the pure
  resolve_pair_spawn_record fold (same seam strategy as
  finalize_restore_candidate). Lifecycle writes still land on the disk
  row; relay-owned config is never written back. restart_ also
  materializes before its stop half, which would otherwise fail first.
- reconcile_managed_agent_runtimes: fan-out candidates resolve through
  the overlay BEFORE the relay-access probe (which authenticates as the
  agent, so a rotated identity must come from the head); the backend
  gate reads the resolved record; the raw disk updated_at is carried
  alongside for start_pair's in-flight guard.
- deploy_to_provider: the post-lock rebuild — the exact payload the
  provider invokes — resolves through the overlay instead of the raw
  row, with the backend extraction split into the pure
  resolved_provider_backend so a head migrated back to local refuses by
  name. provider_binary_path stays the disk value (device-local, never
  patched).
- reconcile_on_workspace_apply: rows resolve before target selection,
  so both the predicate (backend/backend_agent_id/pending) and the
  redeploy payload read authoritative config.

Regression tests at the pure seams with stale-disk-A/overlay-B and
negative controls: pair start (incl. the relay-only case and a
provider-head refusal), post-lock provider rebuild (incl. migrated-to-
local refusal), and access reconciliation (selection + payload). The
write_site_resolve_guard gains rows for all three files with exact
resolve counts.

Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES

Reviewed base 3ee465e12b815a191d902856440e2f3348bda506 through exact head 8f75e90358ed6ededd902594ed5adb9c9c9322a5. This remains critical identity/secrets, multi-device persistence, and destructive remote-lifecycle code.

Blocking finding

[P1] A successful provider deployment is immediately hidden by the authoritative relay overlay, which can leave a real remote process looking undeployed and bypass deletion safeguards.

PrivateConfigPatch::apply treats backend_agent_id as relay-authoritative and overwrites the disk value during every resolve (desktop/src-tauri/src/managed_agents/private_config_overlay.rs:95-125). The repaired post-lock deploy path correctly builds the provider request from the resolved record (desktop/src-tauri/src/commands/agents/provider_deploy.rs:65-88), but success settlement then reloads raw disk and writes the provider-returned ID only to that disk row (:115-128,195-209). It neither retains a successor kind:30179 nor updates the live overlay.

The resulting state transition is:

  1. the pre-deploy retained head has backend_agent_id = None;
  2. the provider creates a real remote process and returns its ID;
  3. disk receives that ID;
  4. every subsequent resolved read reapplies the retained None.

This is not cosmetic. Provider-access reconciliation now selects from resolved records and requires backend_agent_id.is_some() (desktop/src-tauri/src/commands/agents/provider_access.rs:14-20,69-88), so it can skip the live deployment. The delete path also checks the resolved ID before requiring force_remote_delete (desktop/src-tauri/src/commands/agents.rs:1060-1081), so it can permit local deletion while the provider process remains orphaned.

Establish one authority model for this field. Either commit provider success through the retained kind:30179/write-through overlay seam with a fence against a newer concurrent relay edit, or deliberately make backend_agent_id device-local and remove it from relay-overlay semantics consistently. Add a production-seam regression covering successful deploy → resolved list/access reconciliation/delete guard, plus a delayed success settling after a newer head so stale completion cannot overwrite newer intent.

Re-review and validation

The new-head delta otherwise repairs the prior raw-disk final-use blocker: Pair Start/Restart materializes relay-only local records and resolves at the spawn boundary; provider deploy rebuilds beneath its per-agent lock from the resolved record; provider-access target selection uses resolved records. Focused final-use/materialization tests passed, the pair-start wiring guard was mutation-proved, the file-size ratchet passed, and the exact-head GitHub matrix is green across Desktop Core, smoke/relay/integration E2E, macOS build, Windows Rust, lint, security, backend integration, and relay E2E.

A local full Tauri package run reached 2,747 passed / 1 failed / 18 ignored; the sole failure was an untouched managed-node descendant-process timing test and its exact focused rerun passed. That transient does not drive this verdict. Current tests separately cover provider payload/backend extraction and disk result mutation, but do not compose success settlement with a subsequent overlay resolve, so green CI does not exercise the blocking lifecycle.

No native GUI or real two-device provider journey was run or claimed; no production keys were accessed. The source-level state transition is deterministic and already prevents clearance. Any head movement invalidates this review.

Wren added 2 commits August 21, 2026 10:22
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>

* origin/main:
  chore(deps): pin earshot below 1.2.0 pending a VAD threshold re-pick (#6392)
  polish(desktop): finish Projects navigation and context chrome (#6429)
  fix(desktop): clarify add agents channel action (#6374)
  Repair stale large channel roster snapshots (#6251)
  feat(desktop-messages): show compact Buzz link metadata (#6252)
  feat(workflows): reply in-thread from send_message action (#6178)
  perf(desktop): split discover_acp_providers into cheap and forced paths (#6330)
  fix(desktop): restore recent channel sorting (#6402)
  fix(desktop): isolate main timeline stacking context from focus drawer (#6398)
  fix(desktop): make reconnect repair lossless (#6415)
  fix(hooks): scope pre-push lanes to branch merge-base diff (#6423)

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>

* origin/carl/relay-primary-agent-config:
  fix(desktop): resolve relay overlay at pair-start and provider-deploy boundaries

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES

Reviewed base 2edacde4d4c01490834725774aa878dbc373c41d through exact head 25753b67061d2bfcc78187c6cf440c21507c1969. This remains critical identity/secrets, multi-device persistence, provider execution, and destructive remote-lifecycle code.

Blocking findings

[P1] Provider success is written to disk but immediately erased by the relay overlay, bypassing deployed-state and deletion safeguards

deploy_to_provider settles a successful provider call by reloading the raw disk row and writing backend_agent_id only there (desktop/src-tauri/src/commands/agents/provider_deploy.rs:115-128,195-209). It neither retains a successor kind:30179 nor updates the live overlay. But PrivateConfigPatch::apply treats that field as relay-authoritative and overwrites disk state with the retained value (desktop/src-tauri/src/managed_agents/private_config_overlay.rs:95-125); a pre-deploy/fresh head normally carries None (:128-186).

The start response then resolves through that stale overlay before summarizing (desktop/src-tauri/src/commands/agents.rs:899-919), and remote status is deployed only when the resolved ID exists (desktop/src-tauri/src/managed_agents/runtime.rs:151-172). The frontend warns and sends forceRemoteDelete: true only when its summary includes the ID (desktop/src/features/agents/lib/managedAgentControlActions.ts:157-209). The backend delete guard repeats the same resolved-ID check, then removes the record/key and tombstones it (desktop/src-tauri/src/commands/agents.rs:1055-1109). A real provider process can therefore report not_deployed and be silently orphaned by an ordinary delete.

Choose one authority for deployment lifecycle fields. Either commit provider success through the retained 30179/write-through overlay seam with a fence against newer concurrent relay/user edits, or consistently classify backend_agent_id as device-local and remove it from overlay/30179 semantics. Add biting regressions for deploy success → resolved summary deployed → delete without force rejected, and delayed deploy settlement after a newer head without reverting newer config.

[P1] Workspace apply reconciles providers after clearing the overlay but before hydrating the new scope, so it deploys stale disk config

apply_workspace serializes the scope transition and clears private_managed_agent_overlay before changing relay/identity (desktop/src-tauri/src/commands/workspace.rs:214-238). It then invokes provider reconciliation (:274-277). Only afterward does scoped event sync run (:287-317), and overlay hydration is the final event-sync step (desktop/src-tauri/src/event_sync.rs:28-32,42-60).

Consequently, both the new target-selection resolve (desktop/src-tauri/src/commands/agents/provider_access.rs:67-85) and the post-deploy-lock final-use resolve execute against a deliberately empty overlay on the production workspace-apply path. A follower with stale disk A and retained relay head B can redeploy A, including obsolete provider/backend, prompt, environment, credentials, or access policy, before B is hydrated. The new tests seed an overlay directly; they prove the fold exists, not this ordering.

Hydrate and validate the exact relay+owner scope before provider selection/deployment while preserving switch serialization and fail-closed behavior. Add a command-seam ordering regression for cleared overlay + retained B + disk A → provider receives B, including relay/identity switching and delayed-deploy fencing.

Validation and residual risk

  • Immediately before submission, GitHub still reported base/head 2edacde4...25753b67; the review checkout was clean at that head.
  • git diff --check 2edacde4...25753b67 passed.
  • The three critical files from the prior P1 are byte-unchanged between 8f75e903 and this head (git diff --quiet passed), and both blocking call chains were independently source-traced at the exact head.
  • GitHub's exact-head matrix is green across Desktop Core, smoke/relay/integration E2E, macOS build, Windows Rust, lint, security, backend integration, and relay E2E.
  • One independent clean full Tauri package run reached 2855 passed, 1 failed, 19 ignored; the failure was managed_agents::global_config::tests::inherited_shared_compute_translates_to_supported_agent_transport (Some("mesh") vs Some("auto")). Its PR-specificity could not be isolated because the parent comparison lacked the required sidecar, so it is reported as an unresolved local/CI discrepancy rather than used as either blocker.
  • No native GUI or real two-device provider journey was run or claimed; no production credentials were accessed. Source-level destructive and stale-execution chains already prevent clearance.

Any head movement invalidates this review.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES
Reviewed: 2edacde4d4c01490834725774aa878dbc373c41d..25753b67061d2bfcc78187c6cf440c21507c1969 (exact head 25753b67061d2bfcc78187c6cf440c21507c1969)
Risk: critical — relay/local authority, provider deployment, credentials/access configuration, workspace switching, and destructive deletion.

Blocking findings

  1. Successful provider deployment is hidden by stale relay overlay state, permitting an unconfirmed delete that can orphan live infrastructure. provider_deploy.rs:115-128,195-209 writes returned backend_agent_id only to raw disk; there is no retain/publish/write-through. PrivateConfigPatch::apply then overwrites disk with retained overlay state (private_config_overlay.rs:95-125), whose 30179 payload owns this field (reconcile.rs:381-406). Resolved status can report not_deployed; UI warning/force deletion depends on that resolved ID (managedAgentControlActions.ts:157-209), and the backend guard also only blocks when the resolved ID exists (commands/agents.rs:1055-1081). A normal delete can erase the record/key and tombstone it while leaving the provider process alive. Establish one authority for settlement fields, fence delayed settlement against newer relay edits, and test deploy success → resolved deployed summary → unforced delete rejection plus delayed-settlement concurrency/restart.

  2. Workspace apply runs provider reconciliation while the overlay is deliberately empty. workspace.rs:214-238 clears the private overlay before relay/identity change, then invokes provider reconciliation at :274-277; event sync/hydration only follows at :287-317 (event_sync.rs:28-32,42-60). Thus target selection and post-lock final-use resolves (provider_access.rs:67-85; provider_deploy.rs:57-92) deterministically see an empty overlay. A follower with disk A and retained relay head B redeploys stale A—including backend/config/prompt/env/credentials/access—before B is hydrated. Existing tests manually seed the overlay and source guards prove call presence, not production ordering. Hydrate the exact relay/owner scope before provider selection/deploy and add a command-seam ordering regression including identity/relay switch and delayed-deploy fencing.

Validation at matching clean HEAD: diff check PASS. Exact-head GitHub checks were green. Full Tauri --all-features reached assertions with 2,855 passed, 1 failed, 19 ignored; the failure was inherited_shared_compute_translates_to_supported_agent_transport (mesh vs auto) and was not isolated as PR-specific because a parent comparison lacked its sidecar. Another lane’s focused Rust build stopped before assertions on the missing sidecar. These results do not clear either deterministic authority/order chain.

Manual/native evidence: no GUI/two-device journey; source call ordering establishes both blockers. No production keys were used.

Residual risk: the full-suite failure remains unattributed, and packaged two-device recovery was not rerun. Neither weakens the blockers above.

kaalph pushed a commit to kaalph/buzz that referenced this pull request Aug 21, 2026
Absorbs the relay half that shipped separately in block#5133 (squash commit
4a8c67a): the kind:30179 ingest acceptance hunk in
crates/buzz-relay/src/handlers/ingest.rs was byte-identical on both
sides, so this merge removes all relay-side changes from this PR's
diff. block#4999 now carries only the desktop + buzz-core codec half.

No rebase, no force-push — history preserved per operator instruction.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>

* origin/main:
  fix(bench): mention the orchestrator by pubkey when posting the task (block#5136)
  feat(relay): accept kind:30179 private managed-agent events at ingest (block#5133)
  fix(media): require authenticated reads (block#4610)
  fix(desktop): preserve authoritative agent avatars (block#4984)
  fix(desktop): next/back navigation during key creation onboarding (block#4978)
  Alert community owners and admins when a new key joins (block#4900)
  fix(desktop): prevent sidebar prefs from reverting on stale-localStorage boot (block#5086)
  chore(hooks): run desktop typecheck in pre-push (block#5110)
  feat(identity): recover desktop identity from a signed-in phone (block#4845)
  fix(buzz-agent): classify read timeouts distinctly in LLM error messages (block#4959)
  Refine agent runtime controls (block#5026)
  test(desktop): await thread scroll anchor (block#3174)
  Improve desktop mobile pairing flow (block#5024)
  feat(desktop): show selected community in rail (block#5000)
  fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (block#4990)
  fix(desktop): skip native notifications outside app bundles (block#5004)
  ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes (block#3862)
  fix(desktop): virtualize channel member lists (block#4991)

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
kaalph pushed a commit to kaalph/buzz that referenced this pull request Aug 21, 2026
Staged follow-up for block#4999. Do NOT land this on
workerbee/agent-export-thinking-config before block#4999 merges: the overlay
API it calls does not exist on main.

block#4999 makes private managed-agent config relay-primary. Its encrypted
PrivateConfig carries env_vars, and PrivateConfigPatch::apply replaces
env_vars wholesale rather than merging, so on a device that follows
another device's relay head the env_vars in managed-agents.json are stale
by construction. block#4999 resolves the overlay at the edit, rename, and
pair-start sites, but not at the export sites, because those files are
not in its diff.

That matters specifically for this PR: the portable thinking-effort value
lives inside env_vars, so team export, agent-snapshot export, and card
mint could serialize a stale effort — or none at all, when the effort was
only ever set on the other device.

Resolved at:
- commands/team_snapshot.rs (materialize_team_snapshot_bytes)
- commands/personas/snapshot.rs (materialize_snapshot_bytes)
- commands/personas/card.rs (mint_agent_card, card_mint_key_status)

build_team_export_snapshot now takes a ResolvedRecords newtype that only
the overlay can construct, so passing raw load_managed_agents output to
the team export is a compile error rather than a silent wrong export.
The card-mint resolve also fixes the OPENAI key/base-URL layering, which
read the same stale instance env.

The regression test asserts on the exported snapshot rather than on
resolved_records output, and carries three controls: raw disk exports
"low" where the head says "high", head-only effort is exported when disk
has none, and the same record without the overlay exports no effort at
all. Testing each side against its own expectation is how a join like
this stays green while being broken.

Verified in a throwaway merge of pr4999 (47f77c7) and ce93328:
cargo test --lib 2281 passed / 0 failed / 14 ignored, clippy --all-targets
clean, cargo fmt applied.

Co-authored-by: Atish Patel <atish@squareup.com>
Signed-off-by: Atish Patel <atish@squareup.com>
kaalph pushed a commit to kaalph/buzz that referenced this pull request Aug 21, 2026
Staged follow-up for block#4999. Do NOT land this on
workerbee/agent-export-thinking-config before block#4999 merges: the overlay
API it calls does not exist on main.

block#4999 makes private managed-agent config relay-primary. Its encrypted
PrivateConfig carries env_vars, and PrivateConfigPatch::apply replaces
env_vars wholesale rather than merging, so on a device that follows
another device's relay head the env_vars in managed-agents.json are stale
by construction. block#4999 resolves the overlay at the edit, rename, and
pair-start sites, but not at the export sites, because those files are
not in its diff.

That matters specifically for this PR: the portable thinking-effort value
lives inside env_vars, so team export, agent-snapshot export, and card
mint could serialize a stale effort — or none at all, when the effort was
only ever set on the other device.

Resolved at:
- commands/team_snapshot.rs (materialize_team_snapshot_bytes)
- commands/personas/snapshot.rs (materialize_snapshot_bytes)
- commands/personas/card.rs (mint_agent_card, card_mint_key_status)

build_team_export_snapshot now takes a ResolvedRecords newtype that only
the overlay can construct, so passing raw load_managed_agents output to
the team export is a compile error rather than a silent wrong export.
The card-mint resolve also fixes the OPENAI key/base-URL layering, which
read the same stale instance env.

The regression test asserts on the exported snapshot rather than on
resolved_records output, and carries three controls: raw disk exports
"low" where the head says "high", head-only effort is exported when disk
has none, and the same record without the overlay exports no effort at
all. Testing each side against its own expectation is how a join like
this stays green while being broken.

Verified in a throwaway merge of pr4999 (aa39d72) and 2a116bf:
cargo test --lib 2281 passed / 0 failed / 14 ignored, clippy --all-targets
clean, cargo fmt applied.

Co-authored-by: Atish Patel <atish@squareup.com>
Signed-off-by: Atish Patel <atish@squareup.com>
BradGroux pushed a commit to BradGroux/buzz that referenced this pull request Aug 23, 2026
…block#5133)

## What

Relay-only carve-out of the ingest half of block#4999: generic EVENT ingest
now accepts kind:30179 (NIP-PMA private managed-agent config). One file,
`crates/buzz-relay/src/handlers/ingest.rs`, 16 insertions / 15
deletions; **two semantic lines**, byte-identical to the ingest hunk of
block#4999 at `6f486e88`:

1. `required_scope_for_kind`: 30179 requires `Scope::UsersWrite` — same
arm as its public sibling 30177 and the other owner-authored NIP-AP
kinds.
2. `is_global_only_kind`: 30179 is owner-global, keyed `(pubkey, kind,
d-tag)`; a stray `h` tag must not channel-scope it.

The rest is import reflow plus replacing the guard test with a positive
one (`private_managed_agent_kind_is_owner_scoped_global_user_data`:
asserts UsersWrite scope, global-only, no h-channel scope).

## Why the guard test can be retired

The removed test
(`private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists`)
pinned a stated precondition: *"must not enter generic EVENT ingest
before privacy and aggregate CAS deploy."* Both halves are resolved:

- **Privacy** — the author-only read gates for 30179 shipped to main
with block#4593: `AUTHOR_ONLY_KINDS` membership, `req.rs` pre-filter + result
gates, `count.rs`, `event.rs` fanout, and the bridge pre-filter
(`bridge.rs:999-1000` returns `restricted: author-only kinds require
authors=[self]` / 403). Only the author can read the event back.
- **Aggregate CAS** — block#4999 settled generation as **advisory**: the `g`
tag is shape-validated, never relay-enforced. Last-write-wins per
coordinate is the contract of record (see the kind:30179 contract blurb
in block#4999), so no CAS mechanism is pending on the relay side.

## Why this is inert to existing relays and clients

- No production desktop code on main authors kind:30179 — the codec
(`private_managed_agent.rs`) has zero non-test callers. This PR accepts
a kind nobody can produce yet.
- Content is opaque NIP-44 ciphertext to the relay; the relay never
decrypts it.
- Reads remain author-only via the already-shipped gates above.
- Storage is the standard parameterized-replaceable path already
exercised by kinds 30175–30178. No schema, config, or migration changes.

## Testing

- Full `buzz-relay` package suite at this commit: 859 passed, 1 failed —
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` (504
vs 200), which **reproduces identically on clean main `c400713e`** with
this change stashed; pre-existing/environmental, not introduced here.
- New positive ingest test passes.
- Pre-push hooks green (branch-skew, rust-tests, desktop-tauri-checks).

## Relationship to block#4999

block#4999 (relay-primary agent config, desktop half) stays DO-NOT-MERGE
pending live relay receipts + real CI; once this lands and deploys, its
live test simplifies to plain `desktop-standalone` against the real
relay, and block#4999 rebases to drop its now-duplicate ingest hunk
(identical bytes → trivial rebase).

Originating thread:
buzz://message?channel=06f13ed3-0557-4ac2-922c-1545dd00bf97&id=2a43b3b4933a2ea78b77088619251c061355f9b7b6dc29ea0d702193f2344149


## Brownfield FTS note (review findings, operator-ruled non-blocking for
this PR)

Max and Sami independently identified that the FTS privacy skip-set is
regime-dependent: migration 0008 installs the positive allowlist (`kind
IN (0, 9, 40002, 45001, 45003)`) **only on an empty events table**; an
already-populated database keeps the 0001/0005 negative skip-list
(wrapped by 0014 to add 30350), which omits 30179 — so on such an
installation this PR admits 30179 rows whose NIP-44 ciphertext gets
indexed by `to_tsvector`. Sami measured both regimes against real
Postgres (brownfield: 30179 INDEXED; fresh: NULL) and demonstrated the
existing drift test only exercises the fresh regime.
`schema/schema.sql:222`'s canonical literal is also the negative list
and omits 30179. Migration dates put any relay deployed with data before
0008 landed (2026-07-13) in the brownfield class.

**Scope of exposure (Sami's trace):** not a content leak —
`event_visible_to_reader` / `is_author_only_event` gates hold on both
search surfaces (`req.rs:725`, `bridge.rs:1770`), so foreign readers
receive nothing. Lost is the storage-level NULL-tsv backstop plus FTS
page budget burned on post-filtered hits.

**Operator ruling (Tyler, events `1472e5b6`, `cbd368ed`):** ship this PR
without an exclusion migration. Safety argument that makes this sound
rather than merely accepted: main has **zero non-test 30179 writers**
until block#4999's desktop half deploys — no 30179 rows can exist, so nothing
can be indexed in any regime while this PR is the only half live.



**Additional review characterizations (Sami, non-blocking, on the
record):**
- *Behavioral delta enumerated:* routing triple
(`required_scope_for_kind` / `is_global_only_kind` /
`requires_h_channel_scope`) compared for all 65,536 kinds at base
`c400713e` vs head `77eeba6e` — exactly one row differs (30179). No
other kind or client changes behavior.
- *"SQL visibility before LIMIT" (NIP-PMA step 2):* no
`AUTHOR_ONLY_KINDS` pushdown clause exists in `buzz-db` (only
`SHARED_GATED_KINDS` has one). Author-only kinds are protected by the
pre-filter (`author_only_filters_authorized`) plus post-filter omission;
mixed-kind filters can burn candidate-page budget on discarded rows.
Pre-existing and identical for 30300/30350 — not introduced here; noted
so the NIP's step-2 checkbox is not read as fully ticked.
- *Envelope validation gap:* 30179 is the only parameterized-replaceable
kind at ingest with no per-kind envelope validator (codec grammar checks
run in the desktop writer, not the relay). Generic limits only (256 KiB,
±15 min, pubkey==identity, d-tag bound). Self-inflicted footgun bounded
to the author's own coordinate — candidate companion to the exclusion
migration in the block#4999 rebase, deliberately not added here.

**Bound follow-up (required before/with the block#4999 desktop half):** a
0014-shape additive migration (`pg_get_expr` capture + `CASE WHEN kind =
30179 THEN NULL ELSE (<existing>) END` wrap), add 30179 to the
`schema/schema.sql:221` literal, and a brownfield-regime variant of the
FTS drift test, per Sami's finding. Deploy-time spot check if ever
wanted: `SELECT pg_get_expr(d.adbin, d.adrelid) FROM pg_attrdef d JOIN
pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE
d.adrelid = 'events'::regclass AND a.attname = 'search_tsv';`

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Meli added 2 commits August 24, 2026 12:55
Clean auto-merge: app_state.rs (archive_db / http pool on main vs
private_managed_agent_overlay here) and runtime_commands.rs
(list_managed_agent_runtimes went async on main; overlay hooks in
start_pair/reconcile here) touched disjoint hunks.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
…concile providers after overlay hydration

Jude's round-10 review, two blockers.

1. deploy_to_provider wrote the returned backend_agent_id onto the raw
   disk row only. The 30179 head owns that field, so every resolve site
   (summary, delete guard) kept reading the head's None: the summary
   reported not_deployed and an unforced delete could orphan the live
   deployment. Now, under the store lock, the reloaded disk row is
   resolved through the overlay AGAIN (not the pre-deploy snapshot, so a
   relay edit landing mid-flight is fenced), the deploy result is
   applied to that resolved record via the pure settle_deploy_result,
   only the settlement fields (backend_agent_id, provider_policy_pending,
   updated_at, lifecycle) are mirrored to disk, and on success the
   settled record is retained as the next 30179 head and written through
   to the overlay via retain_managed_agent_pending. Failures still land
   last_error on both records and publish nothing.

2. apply_workspace ran reconcile_on_workspace_apply before
   run_event_sync_blocking rehydrated the overlay that the apply
   transaction had just cleared, so target selection and the redeploy
   payload resolved to raw disk and a follower device redeployed stale
   config A over relay head B. The call now runs after the sync block.

Tests: settle_deploy_result fold (success mirrors only settlement fields
to disk; failure never sets an id); the full chain Jude asked for with a
negative control (disk-only settlement is invisible behind the head;
settled + retain_agent_record + absorb_retained_head resolves to the id
the delete guard checks); a positional guard that the settlement is the
record retained (mutation-found: dropping the retain left every
behavioural test green); a positional guard in workspace.rs pinning
clear < run_event_sync_blocking < reconcile_on_workspace_apply with
exactly one production call site. write_site_resolve_guard's
provider_deploy.rs resolve count bumps 1 -> 2.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
@tlongwell-block

Copy link
Copy Markdown
Collaborator

Re jedwards27's review at 25753b670 — both blockers confirmed against the code and addressed in 03a322852 (on top of the origin/main merge 5741bad97; no rebase, no force):

  1. Provider deploy settlementdeploy_to_provider now re-resolves the reloaded disk row through the overlay under the store lock, applies the deploy result to that record (settle_deploy_result, pure), mirrors only settlement fields (backend_agent_id, provider_policy_pending, updated_at, lifecycle) to disk, and on success retains the settled record as the next 30179 head + writes it through to the overlay via retain_managed_agent_pending. Re-resolving after the provider call (not the pre-deploy snapshot) fences delayed settlement against relay edits that landed mid-flight. Tests: the success/failure folds, the chain you asked for (deploy → overlay-resolved record carries the id → delete-guard predicate holds) with a negative control showing the old disk-only shape is invisible behind the head, and a positional guard that the settlement is what gets retained (mutation-found — dropping the retain left the behavioural tests green).
  2. Workspace apply orderingreconcile_on_workspace_apply moved after the run_event_sync_blocking block in apply_workspace, so provider target selection and the redeploy payload resolve against the hydrated overlay for this exact relay+owner scope. Positional guard pins .clear() < run_event_sync_blocking( < reconcile_on_workspace_apply( with exactly one production call site.

Local gate at 03a322852 bytes: cargo fmt --check, file-size ratchet, clippy --workspace --all-targets -D warnings all rc=0; cargo test --workspace (desktop/src-tauri, default features) 2820 passed / 0 failed / 18 ignored. Mutants M1 (old ordering), M2 (disk-only settle), M3 (drop retain) each killed by the new tests.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES

Reviewed base 26f4c3ed304db2c273f0bd4d2746aa9598f38366 through exact head 03a3228521936b11425e5017740165833be6e0f9 with independent systems/integration and adversarial product lanes. Three P1 fail-open paths remain in provider execution and destructive deletion.

Blocking findings

1. A delayed Provider A result is settled onto an unrelated newer backend

deploy_to_provider snapshots and invokes a specific provider/backend/payload at desktop/src-tauri/src/commands/agents/provider_deploy.rs:65-113. After the await it re-resolves the latest authority and unconditionally applies the old result at :120-164; settle_deploy_result compares neither provider/backend/config nor authoritative head identity/generation.

A concurrent Provider A → Local or Provider A → Provider B edit therefore receives A’s returned backend_agent_id. In the Local case, deletion ignores the stranded deployment because the native guard requires both backend != Local and an ID (desktop/src-tauri/src/commands/agents.rs:1072-1080); the UI warning uses the same provider/deployed interpretation. The management record can be deleted without confirmation while A’s remote compute continues running with the agent key.

Author action: carry an immutable invocation fence (owner/relay, authoritative head identity or generation, provider identity/config) through the call and settle only if it still matches. On mismatch, do not stamp A’s result onto the new config; durably preserve and surface an explicit “deployment may exist” conflict that status/delete cannot ignore. Add deterministic delayed A→Local and A→B production-seam regressions proving truthful status and forced-delete protection.

Verification owner: author for deterministic Rust regressions; :bot: Jude’s code review agent for source re-trace and mutation checks removing the fence/delete guard.

2. Provider deployment returns success when authoritative settlement fails

The successful result is saved to disk first (provider_deploy.rs:133-139), then retain_managed_agent_pending attempts the authoritative 30179/write-through update. That helper intentionally swallows every retention, signing, scope, and overlay-absorption error and only logs it (desktop/src-tauri/src/commands/agents_pending.rs:23-24,35-57).

On any such failure, the caller receives success and disk has the ID, but the old overlay still masks it with backend_agent_id: None. Status again says not_deployed, and an ordinary delete can orphan the live provider process. Restart does not repair this safely because the stale retained head remains authoritative.

Author action: make externally successful deployment settlement fail-safe and durable. Either establish the authoritative head/in-memory state before reporting success, or persist an explicit unsettled state that survives restart and makes status/delete conservatively treat the deployment as live. Add injected DB-open/retain/sign/overlay-absorb failure tests covering return value, status, delete, and restart recovery.

Verification owner: author for fault-injection tests; :bot: Jude’s code review agent for mutation-checking every injected failure and the restart/outbox chain.

3. Workspace provider reconciliation still runs after private-overlay hydration fails

Happy-path ordering is repaired: workspace apply clears the overlay, awaits event sync, then reconciles providers (desktop/src-tauri/src/commands/workspace.rs:286-336). But run_event_sync always returns success after calling hydration (desktop/src-tauri/src/event_sync.rs:28-32), while hydrate_private_config_overlay logs and swallows open/decode/overlay failures (:35-70). Reconciliation then runs against the deliberately empty overlay and falls back to stale disk authority—the prior stale provider redeploy behavior, merely moved onto an explicit error path.

Author action: make exact-scope private-overlay hydration a required, observable precondition for provider reconciliation; skip/fail reconciliation without invoking a provider when hydration fails. Add a workspace-apply production-seam regression with retained relay config B, stale disk A, and injected hydration failure proving zero provider invocation.

Verification owner: author for the regression; :bot: Jude’s code review agent for ordering and mutation review.

Validation and confidence gaps

  • Immediately before submission GitHub still reported exact head 03a3228521936b11425e5017740165833be6e0f9, base 26f4c3ed304db2c273f0bd4d2746aa9598f38366; the review checkout was clean and git diff --check passed.
  • Happy-path workspace hydration ordering and successful write-through settlement are materially repaired. No additional material ownership/tenancy, retention migration, restart/fresh-device, export-disclosure, or deletion defect was found in the searched paths.
  • Exact-head Desktop Core, macOS build, release candidate, smoke, relay, and integration E2E checks are green.
  • GitHub Unit Tests is red because buzz-voice cannot find native static library sherpa-onnx-c-api (job 97530265999). That remains an unattributed CI/tooling gate requiring rerun/ownership before merge; it is separate from the deterministic source blockers.
  • Independent local all-features Tauri attempts did not reach assertions because generated sidecar stubs were absent. That is reviewer-environment confidence loss, not author rework. No native GUI or live provider/two-device journey is claimed.

Any head movement invalidates this review.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES
Reviewed: 26f4c3ed304db2c273f0bd4d2746aa9598f38366..03a3228521936b11425e5017740165833be6e0f9 (exact head 03a3228521936b11425e5017740165833be6e0f9)
Risk: critical — relay-primary authority now controls provider deployment identity, settlement, status, and destructive deletion.

Behavior/contracts traced: workspace switch → scoped overlay hydration → provider reconciliation; provider invocation snapshot → delayed settlement → retained 30179/write-through; list/status/delete authority; restart and partial-failure paths.

Blocking findings:

  1. Delayed Provider A success can be attached to a different current backend and then orphaned. desktop/src-tauri/src/managed_agents/provider_deploy.rs:65-113 invokes from one provider/backend snapshot, but after the await :120-164 re-resolves the latest record and settles without comparing backend/provider/config or authoritative head identity. A concurrent Provider A → Local (or Provider B) edit receives A’s returned ID. In the Local case, desktop/src-tauri/src/commands/agents.rs:1072-1080 does not apply the provider delete guard, so normal deletion can orphan live infrastructure.

  2. A successful deployment can return success while authoritative settlement failed. provider_deploy.rs:133-139 saves disk and then calls retain_managed_agent_pending; desktop/src-tauri/src/managed_agents/agents_pending.rs:42-57 logs and swallows retain/overlay failures. The stale overlay can therefore mask the disk ID, report not deployed, and bypass the same destructive-delete warning/guard despite live infrastructure.

  3. Workspace provider reconciliation still proceeds when required private-overlay hydration fails. desktop/src-tauri/src/event_sync.rs:42-70 logs and swallows retention/decode/overlay failures, after which desktop/src-tauri/src/commands/workspace.rs:330-336 still reconciles providers against an empty overlay. The happy-path ordering is fixed, but failure returns to stale disk authority.

Author action: carry an immutable invocation fence (owner/relay, authoritative head generation/event ID, provider identity/config) and settle only on a match; preserve an explicit durable “deployment may exist/unsettled” state on mismatch or retain failure that status/delete cannot ignore; make exact-scope private-overlay hydration a required observable precondition for provider reconciliation. Add deterministic delayed A→Local/A→B, retain/open/sign/absorb failure + restart/status/delete, and hydration-failure + zero-provider-invocation regressions. Mutation-prove the fences and fail-closed gates.

Verification owner: author for the production-seam/fault-injection tests; reviewer/tooling for exact-new-head mutation and ordering review; CI owner to rerun/attribute the unrelated red Unit Tests job.

Validation: both independent lanes traced and corroborated the settlement/orphan chain at the exact clean head. GitHub Desktop Core, macOS build, smoke, and integration E2E were green. GitHub Unit Tests was red because buzz-voice could not find sherpa-onnx-c-api; causality was not established and it is not the basis for this verdict. Local Tauri attempts did not reach assertions because the generated sidecar was absent.

Manual/native evidence: no GUI, live two-device, or provider-substrate run claimed; deterministic source failure paths establish the blockers.

Residual risk: broader provider/restart behavior remains unverified until the authority and partial-failure defects are fixed. Any new head expires this review.

Wren and others added 4 commits August 25, 2026 10:34
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Brings in a121907 (buzz-agent request_permission gate, #5712) and
931747c (staging dev relay image workflow, #6709). Clean merge, no
conflicts; no files overlap with this branch.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Boot reconcile minted zero 30179s on a store with any record whose
legacy relay_url pin is "". Two defects compounded:

- buzz-core validate_identity_and_config rejected an empty relay_url.
  The per-record pin has been ignored at read time since #2122 (the
  agent resolves to the active workspace relay), so "" is a legal
  record and must encode. Keep only the 4096 upper bound.
- reconcile_agents_in_dir_with used `?` on each record, so the first
  build failure aborted the loop and every record after it in file
  order was never visited. Log the failing record and keep walking;
  each record is its own transaction, so partial progress is safe.

Tests: buzz-core `empty_relay_url_is_accepted`; desktop
`empty_relay_url_pin_does_not_abort_boot_reconcile` writes a pin-less
record followed by a normal one and asserts both get 30177 and a
decryptable 30179. The desktop test fails on f7c2477 with
"invalid relay_url length" and passes with this change.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
66995b1 made empty relay_url valid in the same commit that hardened
the boot loop, so its regression test never reaches the `Err` arm: both
records take `Ok(true)`, and restoring `?` would leave it green.

Add a three-record test with a genuinely unpublishable middle record
(nsec that does not derive its pubkey): reconcile must return Ok(2),
the first and last records get 30177 + 30179, and the bad coordinate
gets neither. Verified red with `?` restored in reconcile.rs, green
with the `match`.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Verdict: REQUEST CHANGES

Reviewed exact head a3f68c9bd67635dd74d7c5f83e549b6e1f3477ca against base a1219070fa6c3263c8a29637c70b7a317d4ecd9d. This feature makes encrypted kind:30179 the authority for executable agent configuration. That means provider settlement, deletion, replay, and startup must preserve one rule: local disk may be stale or mortal, but an operation must never report success while relay-authoritative state still describes the opposite outcome.

Blocking findings

1. [P1] Provider results are neither fenced to the invocation nor durably settled before success

deploy_to_provider invokes a provider/backend snapshot at desktop/src-tauri/src/commands/agents/provider_deploy.rs:65-113. After the await, it re-resolves the latest record and unconditionally applies the old result at :120-164; it never compares the current backend/provider/config or authoritative head identity/generation with the invocation. A delayed Provider A result can therefore be stamped onto a concurrent A→Local or A→Provider B edit. In the Local case the remote-delete guard does not apply (commands/agents.rs:1072-1081), so A's live infrastructure can be orphaned.

Even without a concurrent edit, successful settlement is not authoritative before the command returns. The disk mirror is saved, then retain_managed_agent_pending is called (provider_deploy.rs:135-141), but that helper logs and swallows every scope/open/sign/retain/overlay error (commands/agents_pending.rs:42-57). The command returns Ok(()) while the prior overlay still resolves backend_agent_id: None; status reads not_deployed, deletion is unguarded, and restart preserves the stale existing private head because boot reconcile deliberately does not replace it.

Carry an immutable invocation fence through the provider call, settle only when it still matches, and make successful authoritative retain/write-through a fallible prerequisite to reporting success. On mismatch or retention failure, durably preserve an explicit unresolved-deployment state that status/delete/restart cannot mistake for “not deployed.” Add delayed A→Local/A→B and injected retention/sign/overlay failure regressions.

2. [P1] An older client's public-only delete can resurrect the private agent on a fresh client

Startup backfill forwards relay results in their native newest-first order (desktop/src/features/agents/lib/usePersonaSync.ts:97-104; relay query ordering is created_at DESC). An older client emits only the kind:30177 tombstone while the older kind:30179 head remains live. The fresh client processes the newer tombstone first; resolve_inbound_tombstone finds no locally retained head and commits it (commands/personas/inbound.rs:536-593). The older 30179 then follows the ordinary inbound path (:355-390), which never checks a covering tombstone, and inserts a secret-bearing overlay entry. private_config_overlay.rs:279-293 exposes it as a relay-only agent.

Make every inbound public/private upsert consult covering tombstones for the managed-agent coordinate, independent of arrival order and sibling kind. Add an old-client 30177-only delete + surviving 30179 + fresh-device regression that proves the agent cannot rematerialize.

3. [P1] Both inbound and local deletion can finalize one side of the delete while permanently losing anti-resurrection state

For inbound deletion, the kind:5 row and retained-head purge commit first (commands/personas/inbound.rs:540-593); only afterward do the fallible JSON-store saves run (:639-657). If a save fails, replay dedupes at :555-556, so the surviving local record can never be deleted by that event.

The local command has the inverse hole: it saves the record removal, drops the overlay and key, then calls a tombstone helper returning () (commands/agents.rs:1091-1105). managed_agents/agent_events.rs:154-215 swallows every signing/SQLite/commit failure. The command reports success while the retained 30179 head survives; restart hydration then rematerializes the deleted agent.

Use a recoverable transaction/outbox protocol across JSON and retention: do not consume an inbound tombstone before the local mutation can complete, and do not finalize a local delete without durable tombstones/head suppression or durable retry intent. Propagate failure and add save/open/sign/commit fault-injection plus replay/restart tests.

4. [P2] Overlay hydration failure still falls through to stale provider reconciliation and restore

The happy-path ordering is fixed, but hydrate_private_config_overlay returns () and logs/swallow errors (desktop/src-tauri/src/event_sync.rs:42-70); run_event_sync therefore returns success (:16-32). apply_workspace then runs provider reconciliation and restore after an empty/partial overlay (commands/workspace.rs:286-336), causing stale disk config to execute on the exact failure path where relay authority is unavailable.

Return an observable hydration result and refuse provider reconcile/restore when the exact relay+owner overlay could not be established. Distinguish a valid empty overlay from decode/open/lock failure and cover zero-provider/zero-spawn behavior under injected failure.

Validation

The worktree was clean at the exact head above and git diff --check passed. Exact-head GitHub CI is green across Desktop Core, Desktop E2E, relay/integration suites, unit tests, lint, security, macOS, and Windows. Those checks do not exercise these mixed-version and injected partial-failure state transitions. A delegated focused Tauri test attempt was blocked before assertions because this checkout lacks desktop/src-tauri/binaries/buzz-acp-aarch64-apple-darwin; no local suite pass is claimed. The codec's signature/owner/nsec binding, malformed-input handling, and relay/community scoping reviewed clean in the searched paths.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Verdict: REQUEST CHANGES

Reviewed exact head a3f68c9bd67635dd74d7c5f83e549b6e1f3477ca against base a1219070fa6c3263c8a29637c70b7a317d4ecd9d using read-only GitHub metadata and source/diff inspection. The codec, owner/signature/nsec binding, scoped retention, equal-second convergence, happy-path overlay hydration ordering, and public/private atomic retention reviewed clean. Four lifecycle failures remain.

1. [P1] Provider success is not fenced or durably authoritative

deploy_to_provider invokes one provider/backend snapshot at desktop/src-tauri/src/commands/agents/provider_deploy.rs:65-113, then re-resolves current state and applies the old result at :120-164 without comparing backend/provider/config or authoritative-head identity. A delayed Provider A result can be stamped onto a concurrent A→Local or A→Provider B edit. Even without concurrency, settlement saves disk and calls retain_managed_agent_pending (:135-141), whose retention/signing/overlay failures are logged and swallowed (commands/agents_pending.rs:42-57). The command can return success while the authoritative overlay still reports no deployment, weakening status and delete safeguards around live provider infrastructure.

Carry an immutable invocation fence through the provider call. Settle only on a match, and make authoritative settlement a fallible prerequisite to success or durably record an unresolved deployment that status/delete/restart conservatively preserve. Add delayed A→Local/A→B and injected settlement-failure regressions.

2. [P1] A public-only old-client delete can resurrect the private agent

Backfill forwards newest-first relay results (desktop/src/features/agents/lib/usePersonaSync.ts:97-104). If an older client publishes only the kind:30177 tombstone, a fresh client can consume that newer tombstone first, then receive the surviving older kind:30179. resolve_inbound_tombstone records deletion and finds no local head (commands/personas/inbound.rs:536-593), but apply_inbound_private_managed_agent_event later consults only the now-absent 30179 head (:355-390), not covering tombstones, and inserts the patch. private_config_overlay.rs:279-293 exposes it as a relay-only agent.

Make managed-agent upserts consult covering public/private tombstones independent of delivery order and sibling kind. Add the mixed-version fresh-device regression.

3. [P1] Delete side effects and anti-resurrection state are not recoverably atomic

Inbound deletion commits the tombstone and purges retained heads before fallible JSON-store saves (commands/personas/inbound.rs:540-657). If a save fails, replay dedupes the tombstone, leaving the local record undeletable by that event. Local deletion has the inverse hole: it removes disk/overlay/key before tombstone_managed_agent_pending, while managed_agents/agent_events.rs:154-215 swallows signing/SQLite/commit failures. The command can report success while a retained private head survives and rematerializes on restart.

Use a recoverable transaction/outbox protocol across JSON/key state and retention. Propagate failures or persist durable retry intent. Add save/open/sign/commit fault-injection with replay/restart coverage.

4. [P2] Overlay hydration failure executes stale disk authority

hydrate_private_config_overlay logs and swallows open/decode/lock failures (desktop/src-tauri/src/event_sync.rs:42-70), so run_event_sync still succeeds (:16-32). Workspace apply then proceeds to provider reconciliation and restore against an empty or partial overlay (commands/workspace.rs:286-336). The happy-path ordering is correct; the failure path can still deploy or start stale disk configuration.

Return an observable hydration result and refuse provider reconciliation/restore unless the exact relay+owner overlay was established. Distinguish a valid empty overlay from failure and test zero provider/spawn invocation under injected failure.

Exact-head GitHub CI is green across the listed unit, Desktop Core/E2E, relay/integration, lint, security, macOS, and Windows jobs. Those checks do not exercise these mixed-version and partial-failure transitions. No PR code was checked out or executed for this review.

Brings the branch up to origin/main f177f49 (25 commits, including
prompt framing, feed CLI, and CI/workflow updates). Automatic merge,
no conflicts; no files overlap with this branch's changes since the
a121907 merge-base.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
@tlongwell-block

Copy link
Copy Markdown
Collaborator

Eva, an automated teammate, commenting via Tyler's GitHub account on his explicit instruction.

Branch updated: merged origin/main (f177f4909, 25 commits) into the branch as plain merge commit de33fd8bc — no rebase, no force-push. The merge was conflict-free and touches zero files this branch changes since the a1219070f merge-base (verified by comm over both git diff --name-only sets), so every file/line citation in review 5032539884 remains valid at the new head. Pre-push hooks (rust-tests, desktop-test, desktop-tauri-checks) passed locally before push.

On the four findings: we independently traced each against the new head and corroborate the mechanisms as written:

  1. settle_deploy_result re-resolves the record post-deploy but applies the stale provider result unconditionally, and retain_managed_agent_pending swallows every retention/overlay error while deploy_to_provider returns Ok (commands/agents_pending.rs:42-57).
  2. inbound_event_outcome (managed_agents/retention.rs:275-293) resolves only the same-coordinate retained row and never consults a covering kind:5; coalesceManagedAgentBackfill coalesces only kind:30177, so the newest-first backfill can consume the tombstone first and then apply the older 30179.
  3. Inbound deletion commits the kind:5 + head purge before the fallible JSON-store saves, and replay dedupes on the retained kind:5; the local delete's tombstone_managed_agent_pending returns () and swallows signing/SQLite/commit failure (managed_agents/agent_events.rs:167-216).
  4. hydrate_private_config_overlay returns () and swallows failure, so run_event_sync reports success and apply_workspace proceeds to provider reconcile/restore over an unestablished overlay.

This merge commit addresses none of the findings; fixes will land as separate commits.

@tlongwell-block tlongwell-block left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Wren review at exact head de33fd8bc8cc2301de66eafa0387f02ecd21361e.

Verdict: changes requested. I independently traced Carl's four blockers at the merged head and concur with all four:

  1. P1 — provider settlement is not fenced/durable. provider_deploy.rs:108-140 invokes one payload, then re-resolves current state and applies the old result without comparing provider/backend/config/head identity. A delayed Provider A result can settle onto a concurrent A→Local or A→B edit. The successful path then calls retain_managed_agent_pending, whose errors are swallowed at agents_pending.rs:42-57, so success can be returned without an authoritative retained settlement.
  2. P1 — old-client public-only delete can resurrect private config. inbound.rs:536-593 consumes a 30177 tombstone and removes both currently retained heads, but the later 30179 upsert path at :355-390 checks only its own retained head, not a covering public/private tombstone. Backfill preserves relay order (usePersonaSync.ts:97-104), and private_config_overlay.rs:279-293 materializes the surviving patch as relay-only state.
  3. P1 — deletion is not recoverably atomic across retention and local stores. Inbound commits retention/head removal before fallible JSON mutation (inbound.rs:540-657), and replay then skips at :555-556. Local deletion removes disk/overlay/key before a tombstone helper that returns () (agents.rs:1091-1105); signing/DB/commit failures are logged and swallowed (agent_events.rs:154-215). Both directions admit permanent partial completion and resurrection.
  4. P2 — hydration failure still authorizes stale disk execution. event_sync.rs:42-70 logs and swallows overlay open/decode/lock failure, allowing run_event_sync to return success at :16-32; workspace.rs:311-336 then proceeds into provider reconciliation and later restore despite an unestablished relay-authoritative overlay.

Ratings

  • Minimalness: 8/10 — the PR is broad but mostly cohesive; lifecycle guarantees are spread across best-effort seams rather than one explicit protocol.
  • Elegance: 7/10 — the overlay/retention direction is understandable, but split authority plus swallowed failures makes invariants hard to state and enforce.
  • Correctness: 4/10 — three P1 state-integrity/lifecycle holes and one P2 fail-open boot path remain. Per the ≥9 bar, this is not ready.

Merge assessment: the merge commit itself is clean: its combined diff is empty, branch-vs-main file overlap since merge-base is empty, and git diff --check f177f4909..de33fd8bc passes. I found no merge-introduced conflict resolution or change to any of the four findings.

I did not duplicate the delegated live/local workflow pass; Max owns that evidence separately.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Blocking review. GitHub records this as COMMENTED because it does not permit an author to request changes on their own PR. Please treat the following as changes requested.

  1. P1 — fail closed when private-overlay hydration fails. run_event_sync reconciles managed agents before hydration, and hydrate_private_config_overlay logs and swallows database, decode, and lock failures (desktop/src-tauri/src/event_sync.rs:16-70). Workspace startup can therefore continue into provider reconciliation and auto-restore with an empty or partial overlay, resolving stale disk prompt/model/env/credential/ACL/key material as executable state. Make hydration return a distinguishable error and block provider reconciliation and restore until relay-authoritative private state is established.

  2. P1 — fence and durably settle asynchronous provider results. The provider call is made from one relay-resolved payload, but after the await the code re-resolves current state and applies the old result without matching the invoked authoritative head, backend, provider, or config (commands/agents/provider_deploy.rs:58-164). A delayed Provider A result can settle onto a concurrent A→Local or A→Provider B edit. The success path then calls retain_managed_agent_pending, which returns () and swallows signing, SQLite, retain, and overlay errors (commands/agents_pending.rs:35-57), so the command can report success without authoritative settlement. Carry an immutable invocation fence, reject/reconcile mismatches, and make retain plus overlay write-through a prerequisite for success (or durably record an unresolved deployment state).

  3. P1 — make a public tombstone cover private backfill. A kind:30177 deletion removes the current public and private heads, but the later kind:30179 upsert path checks only same-kind retention (inbound.rs:355-390,540-593). An older private event arriving after a public-only deletion can therefore be retained and materialized again, resurrecting deleted private config. Evaluate inbound private events against the deletion boundary that covers the managed-agent coordinate, including public tombstones, and test out-of-order/backfill delivery.

  4. P1 — make deletion recoverable across retention and local stores. Inbound deletion commits tombstone/head removal before fallible JSON, overlay, and key-store mutation (inbound.rs:540-657); replay then skips the consumed event, leaving permanent partial deletion possible. The local direction likewise removes local state before best-effort tombstone publication. Introduce a transactional/retryable state transition whose progress survives failure, and test failures at every boundary in both directions.

  5. P2 — hydrate relay authority before publishing disk state at boot. Startup currently performs disk→relay managed-agent reconciliation before private-overlay hydration (event_sync.rs:16-32). The private event path is guarded, but stale disk public state can still publish a newer kind:30177 over the relay-visible public head. Hydrate first, then reconcile from the combined authoritative view, or otherwise fence stale public publication.

The private-config codec itself looks sound in the reviewed paths: owner/signature checks precede decryption, owner/agent/generation bindings are validated, and malformed or unauthenticated payloads fail closed. The blockers are lifecycle, ordering, and durability defects around that codec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants