Relay-canonical managed agents - #4940
Conversation
Push the authenticated reader into event query and count predicates before pagination, including mixed-kind and kindless filters. Add the brownfield FTS exclusion for private managed-agent aggregates without changing prior migration checksums. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Add Postgres-backed coverage for pre-limit owner visibility, kindless ID privacy, and explicit, mixed, and kindless COUNT behavior. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Document the encrypted relay boundary, Desktop verification gate, shared definition revision semantics, runtime capability gate, and bounded deployment phases for the relay-authority follow-up. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Lock explicit, mixed-kind, and kindless private managed-agent filters to the author-only SQL pushdown path. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Introduce an authenticated aggregate endpoint and serializable authority transaction for encrypted private managed-agent heads and their exact public projections. Preserve immutable signed recovery bytes, enforce generation and definition revision CAS, reactivate submitted projections, retire prior heads, and return a writer-consistent signed aggregate snapshot. Keep kind 30179 author-only and out of full-text search, reject generic writes, and advertise the aggregate capability through NIP-11. Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Introduce the Desktop safety foundation for the relay-canonical managed-agent
migration. State + classification only — no migration enablement, no
destructive local mutation. Every existing record defaults to LegacyOnly, so an
upgrade that predates any migration deserializes unchanged and keeps local JSON
+ keyring authoritative.
- Add `managed_agents::authority` with a single internally-tagged
`RelayAuthority` enum (`LegacyOnly` | `RelayAuthoritative { evidence }`), so
impossible states are unrepresentable: authoritative structurally carries a
verified head, legacy structurally cannot. No `{"state":{"state":...}}`
nesting — persisted shape is `{"authority":"legacy_only"}` /
`{"authority":"relay_authoritative","evidence":{...}}`, asserted exactly.
- Add `#[serde(default)] relay_authority: RelayAuthority` to
`ManagedAgentRecord`; a store predating the field deserializes as LegacyOnly.
- Add an exhaustive field→codec-home classification table
(`FIELD_CLASSIFICATIONS`) as a single source of truth, verified against the
ACTUAL PersonaEventContent (30175) / ManagedAgentEventContent (30177)
builders and the 30177 exclusion asserts — not intent:
* start_on_app_launch, auto_restart_on_config_change, provider_binary_path,
persona_team_dir carry through NO codec slot -> TransientLocal.
* avatar_url IS carried by 30175 -> stays DefinitionProjected.
- Tighten the exhaustiveness test to exact set equality: every record field has
a class AND every class key still names a live field (no stale drift).
- Populate `relay_authority` explicitly (`RelayAuthority::legacy()`) at every
production/test constructor rather than relying on a struct-wide default.
Foundation-only classification/authority API is consumed by the not-yet-landed
reconcile/migration lanes; suppression is per-item with a named consumer, not
module-wide.
Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Extend the migrated-Postgres aggregate regression through deletion. Prove a private tombstone advances the CAS generation, returns no public bindings, retires the instance projection, retains the ordinary-ingest authority fence, and rejects a same-generation stale active resurrection. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Add the migration-readiness contract on top of the authority foundation (FIX #2, FIX #5). Types + pure gate only — no migration enablement, no destructive local mutation. The migration driver (sibling lane) is the consumer. - Reclassify `auth_tag` PrivateCanonical -> DerivedNotCarried: it is re-minted at spawn from owner keys + agent pubkey (compute_auth_tag), never carried in a projection and never diffed at verification. Carrying a stale tag would pin an agent to legacy forever. - Add a `VersionedBackend` envelope with `BACKEND_PAYLOAD_VERSION = 1` and a stable serialized shape; deny-unknown-fields so a newer envelope is rejected rather than silently truncated. Round-trip + reject-unknown fixtures. - Add the `MigrationBlock` failure taxonomy and the pure `assess_migration_readiness` gate (FIX #5): capability FIRST, then payload size, then per-value size. Capability is the exact NIP-11 aggregate token `nip-pma-aggregate-v1` (NIP_PMA_AGGREGATE_TOKEN) matched over the advertised token set — not a boolean "kind accepted". Size boundaries are inclusive-safe; exactly-at-limit passes, strictly-over blocks. On any block the caller MUST stay LegacyOnly and NOT retry until an input changes (no timer/retry loop). - `MIGRATION_MAX_VALUE_BYTES = 32_768` tracks the relay per-`Value` codec limit (`private_managed_agent::MAX_VALUE_BYTES`); it is NOT a keyring floor or a combined recovery blob. `MigrationBlock::reason()` renders a diagnostic string for logging/surfacing — the variant is the signal; durable persistence is owned by the migration driver record, not this gate. These are foundation contract types; the next slice wires them into a real payload builder / verification consumer so they prevent drift rather than sit beside `PrivateConfig.backend: Value` decoratively. Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Skip kind:30177 boot reconciliation for records whose verified kind:30179 head is canonical. This prevents stale local compatibility JSON from republishing a legacy projection after promotion or deletion. Add a regression proving an authoritative record creates no retained or pending kind:30177 row. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Persist each exact signed kind:30179 aggregate request by generation so retries reuse immutable bytes and newer tombstones do not erase older in-flight attempts. Enforce contiguous generations and exact per-attempt compare-and-clear/error updates. Add focused coverage for immutable generations, stale acknowledgements, and diagnostic scoping. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Point the FTS integration fixture at the folded migration filename so the private kind:30179 exclusion coverage compiles and can run. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Add `managed_agents::migration`, the first real consumer of the slice-1/2 authority contract. It converts one hydrated `ManagedAgentRecord` + owner/agent keys + exact signed kind:30175/30177 projections + CAS coordinate into the shared `buzz_core::private_managed_agent::Payload` and a signed, inert kind:30179 aggregate candidate, and verifies the relay read-back byte-exact before yielding promotion evidence. Pure by construction: no HTTP, no persistence, no authority mutation. The transport/driver lane (sibling) owns submission, retry, durable state, and the actual RelayAuthoritative write. This module is the gate those lanes pass through, so every "is the relay head a faithful owner-signed copy?" check lives here behind an adversarial fixture matrix. Builder (`build_migration_candidate`): - `PrivateConfig.backend` carries the versioned `VersionedBackend` envelope, not a bare `BackendKind`, so a shape change is an explicit version bump. - `auth_tag` is RE-MINTED unconditionally via `compute_auth_tag(owner, agent, "")`; the stored record string is never copied or trusted (a re-mint cannot byte-match it, and the codec requires an unconditional attestation). - Readiness gating derives the ACTUAL largest inserted codec `Value` (the two projection recovery envelopes dominate) plus the encrypted payload size and feeds them to `assess_migration_readiness` with the relay's NIP-11 tokens — never a caller-supplied number. - Fails fast on inconsistent keys/projections; `build_event` re-runs full codec validation so any semantic slip surfaces locally, not at the relay. Verifier (`verify_promotion`): treats the aggregate response as a LOCAL typed fixture matching the documented route JSON, not a buzz-db type. Checks, in order: `validate_and_decrypt` of the returned head; owner/agent/generation/ predecessor/state equality; binding event-id + content-hash + recovery equality against the returned signed projections (and that the served standalone projection matches its binding, so a swapped public event is caught); definition revision equality; and source-vs-roundtrip equality for CARRIED classes only (the payload type structurally carries only PrivateConfig + identity, i.e. the carried classes — derived/transient/bookkeeping fields never enter it). Returns `PromotionEvidence` only after every check passes. Adversarial fixtures (14, all green): happy exact round-trip; re-mint-not-copy; versioned backend envelope; agent-key/record-pubkey mismatch; projection not owner-signed; wrong-kind projection; capability-absent block; oversize codec value block; tampered head ciphertext; head signed by wrong owner; swapped definition projection; swapped instance projection; wrong definition revision; stale-generation head. Uses shared codec kind/size constants directly. Foundation API is `#[allow(dead_code)]` with a named sibling consumer (the transport lane); prod `cargo check --lib` is warning-free. `managed_agents::` suite: 1014 passed, 0 failed. Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Deserialize the exact aggregate route shape, require its acceptance, head ID, generation, state, optional active projections, and definition revision to match the signed payload, and compare the complete served projection events to their recovery copies. Add route-shape and response-metadata regression coverage. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Serialize aggregate instance projections with ordinary NIP-33 writes, recheck PMA authority inside the replacement transaction, and prevent both NIP-09 deletion paths from mutating bound projections. Add real-Postgres coverage for the first-aggregate ingest race and kind:5 a/e-tag fencing. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Define the last-binding behavior for shared definition coordinates and call out migration 0028's ACCESS EXCLUSIVE full-table rewrite before capability rollout. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Add a pure owner-signed deleted aggregate builder that advances verified relay authority evidence without carrying active projections, with codec round-trip and invalid-input coverage. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Preserve authority evidence through single-agent and persona-cascade deletion, enqueue a minimal next-generation aggregate tombstone for relay-authoritative agents, and retain legacy kind:5 deletion only for LegacyOnly records. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Require deleted aggregate responses to return the exact retained tombstone, echo its generation and event ID, and omit all active projections before an attempt may be acknowledged. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Snapshot the latest pending retained aggregate per agent within the captured owner scope so the flush driver can drain immutable retry inputs without crossing identities or replaying superseded generations. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Add `managed_agents::migration::driver`, the transport half of the relay-canonical migration seam. It is the only code that moves a retained aggregate across the wire: it consumes one immutable row from `managed_agent_aggregates`, POSTs the row's `request_json` verbatim to `/api/managed-agents/aggregate` with fresh owner-signed NIP-98 auth, strict-deserializes the corrected `migration::AggregateResponse`, verifies it through `verify_promotion`, and flips the durable row to synced only after a fully verified read-back. Design invariants: - Exact bytes on the wire. The body is `request_json` verbatim; the driver never re-serializes it, so the stored request and the request the relay authenticates are the same bytes. The verification source (`MigrationCandidate`) is reconstructed from those same bytes — parse the three signed events, decrypt the head under the owner key — so the candidate and the wire request cannot diverge and no build-side crypto is duplicated. - Fresh owner NIP-98 per attempt via `build_nip98_auth_header_for_keys` (unique nonce), so a retry never replays a stale token. Mirrors the `/events` submit path: egress guard, rate-limit wait, `classify_request_error` / `relay_error_message` / `parse_json_response`. - Only the exact retained (generation, private_event_id) is marked synced. Beyond `verify_promotion`'s response-vs-candidate checks, the driver binds the *durable row's* coordinate to the verified evidence, so a row whose columns disagree with its own request_json (and thus the verified head) is never confirmed. - Errors preserve retry. Every failure path — transport, non-2xx, malformed body, reconstruction, verification, coordinate mismatch — persists an exact-attempt diagnostic via `record_managed_agent_aggregate_error` and leaves `pending_sync = 1`. Nothing here clears retry except a verified read-back. Transport tests use a loopback axum stub in the shape of the aggregate route (6, all green): faithful read-back marks the exact generation synced and posts verbatim; conflict status preserves retry; malformed success body preserves retry; tampered read-back fails verification and preserves retry; row coordinate disagreeing with the verified head is not synced; no pending row is a no-op with no network submission. Pure transport confined to the new module + one `mod` line; no delete/tombstone, docs, or relay-crate changes. `managed_agents::` suite: 1026 passed, 0 failed; `cargo check --lib` clean. Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Subscribe to owner-private PMA heads, strictly decrypt and validate their bound projections, and reconstruct the portable managed-agent record on a fresh device without allowing unsolicited heads to overwrite LegacyOnly state. Carry the relay-mesh marker that the field classification already declares private-canonical. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Reject both a-tag and e-tag kind:5 attempts to soft-delete kind:30179 at the handler and database boundaries. Extend the managed-agent integration assertion to cover the private head alongside its bound projections. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Make the owner-scoped persona sync backfill awaitable and run it before Welcome onboarding inspects local managed agents. This lets a second installation reconstruct relay-canonical identities before deciding whether to mint replacements. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Build and decrypt a real private managed-agent aggregate in the inbound reconciliation test module, then assert portable secrets, backend configuration, relay mesh, and authoritative head evidence survive reconstruction. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Retain authoritative tombstones before local destruction, preserve terminal deletion evidence, submit and verify deleted aggregates, and apply local erase with a marker-before-erase replay protocol. Keep legacy deletion immediate and make persona cascades resumable across partial enqueue/save failures. Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Add an owner- and community-bound durable tombstone predicate and enforce it across shared transport admission, ingest, and live WebSocket commands. Await cluster-wide disconnect publication after tombstone commits so exact retries repair failed propagation. Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Build generation-advancing aggregates from the retained definition binding, submit and verify before advancing local authority evidence, suppress fenced legacy projection writes, and roll back local edits when relay confirmation fails. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
The migration builder deliberately accepts the complete cryptographic candidate inputs; suppress the argument-count lint at that boundary rather than obscuring them behind an unrelated bag type. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Adversarial review of the integrated authoritative-edit path at head B1 — BLOCKER: one failed authoritative edit permanently wedges the agent's edit pathThe failure flow contradicts itself:
Net: a single relay blip or one genuine conflict makes the agent permanently uneditable without manual SQLite surgery. Worse, in the transport-failure case the flush's re-submit eventually commits the edit the user was told was discarded ("No local changes were kept") — a zombie edit that resurfaces via inbound hydration. Fix shape (consistent with the thread agreement): thread the HTTP status into B2 — BLOCKER: fresh-device reconstructed agents cannot be edited at all
Fix shape: the head payload already carries everything the edit needs — seed a confirmed ( B3 — HIGH: inbound 30179 application wholesale-replaces existing records, resetting device-local policy
Follow-up flag (documented as out of scope, but it has live teeth)Persona/definition fan-out is absent as documented — but note the interaction: What holds up (attacked, didn't break)Enqueue-before-save under the store lock; evidence↔retained-row equality gate ( B1 and B2 are one focused commit each against seams that already exist. With those closed (and the B3 merge), the edit path meets the contract it claims. |
Retire proven conflicts while preserving transport retries, seed reconstructed relay heads into retention, preserve device-local policy during inbound aggregate advances, and suppress fenced legacy persona publication. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Delete an exact pending edit attempt after a proven relay conflict so the next edit can advance from newly hydrated authority instead of being blocked by a dead retained generation. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Format the Tauri changes, use transaction auto-deref accepted by Windows Clippy, and split oversized desktop modules along existing responsibility boundaries so the file-size ratchet remains enforceable. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Refresh the PR branch onto the current integration baseline after resolving overlapping desktop authority files without conflicts. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Initialize the relay authority field in the managed-agent parallelism test fixture so all-target Clippy and the Tauri test suite compile after the authority model landed. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Resolve the newly conflicting migration number by keeping main's reaction-width migration at 0028 and advancing the private managed-agent foundation to 0029. Preserve both desired-schema assertions. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
0ee888d to
a0f08d3
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Combined review from two independent passes (Paul + Thufir), deduped and severity-reconciled, at head 0ee888dd53976c5d6efb788e0c193163fb0ddc08.
Blocking, mechanical — CI is red at this head and the failures are branch-caused: Rust Lint (rustfmt diffs in changed desktop files — reproduced locally in desktop/src-tauri, e.g. commands/agent_models.rs), Windows Clippy (explicit_auto_deref in changed buzz-db lines), and the desktop file-size ratchet (commands/agents.rs +252, managed_agents/retention.rs +619). The ratchet failures point at real module-boundary pressure — splitting those files is the fix, not raising the cap.
Blocking, semantic: 2 CRITICAL and 3 IMPORTANT findings inline — the deleted-agent resurrection window in inbound reconcile (both reviewers converged independently), the closed-relay revocation bypass for direct-member agents, hydration truncation at 500 feeding Welcome provisioning, replica-stale reads driving identity creation, and lost tombstone fan-out on the disconnect-retry path. Plus 3 MINOR notes.
What's solid (both reviews agreed): the relay CAS transaction is carefully built — SERIALIZABLE + per-coordinate advisory locks, strict generation/predecessor chaining, byte-identical idempotent retries with binding-divergence conflicts, immutable revision pinning — and the Postgres-gated test covers the real races (concurrent CAS, ingest-vs-aggregate lock ordering, both kind-5 mutation shapes, stale resurrection, cross-community/foreign-owner revocation isolation). Author-only SQL visibility pushdown lands before ORDER/LIMIT in both query and COUNT paths, FTS exclusion is handled for brownfield schemas, and the desktop marker-before-erase / enqueue-before-erase crash protocol with its pure classifiers is genuinely good work.
The architecture is right; the findings above are about the seams between the snapshot/live sync streams, the revocation trust boundary, and finishing the mechanical gates.
Route mutation-gating PMA hydration through the writer, page all sync history without mixed-kind starvation, replay durable heads after retrying post-commit propagation, and clarify the deployment and revision trust boundaries. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Verify signed NIP-OA owner evidence independently from the closed-relay delegation feature flag. Preserve and materialize that evidence for directly enrolled agents so AUTH binds subsequent REQ and EVENT revalidation to the durable PMA tombstone. Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
An inbound private managed-agent (PMA) tombstone could be resurrected locally. The deleted branch of reconcile_inbound_private_managed_agent persisted nothing when no local record existed, and the generation guards only run when a record IS present. So a delayed stale active head at the tombstone's generation (or lower) — a fresh-device hydration/live race, or a post-erase replay — found no record, skipped every guard, and hit the no-match insert path, reconstructing the deleted agent including its nsec. Fix: give every coordinate a DURABLE generation floor, keyed by the retained managed_agent_aggregates rows (the MAX generation across active AND deleted rows). The deleted branch now seeds a confirmed tombstone row even when no local record exists, and both branches gate on the floor before touching the store: an inbound head at <= the floor generation is dropped, while a genuine re-creation at a strictly higher generation still passes. - seed_confirmed_managed_agent_tombstone: idempotent (INSERT OR IGNORE) confirmed deleted-row seed; never downgrades a flush-authored row. - reconcile_inbound_private_managed_agent: open retention once for both branches, read the floor, reject stale heads, seed the tombstone floor before erasing the local record. Regression: the exact active-N / deleted-N+1 / replay-active-N interleaving stays floored (agent absent), a re-created N+2 clears the floor, plus the seed's idempotency and synced-deleted-head contract. Full desktop/src-tauri workspace: 2310 passed / 0 failed / 13 ignored; fmt and clippy -D warnings clean. Addresses PR #4940 review thread discussion_r3724146156. Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Destructure every managed-agent field at the relay-to-device boundary so new fields cannot silently inherit the wrong authority class. Apply only portable state while preserving device policy, derived commands, paths, timestamps, and runtime receipts. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Only deletion retries need to repair the post-commit disconnect/fan-out gap; leave byte-identical active retries as no-op deliveries. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Bind each aggregate-controlled agent to its owner in the same serializable transaction as the canonical head, including idempotent retries that repair brownfield rows. This makes tombstones revoke direct members even when they never supplied NIP-OA evidence, while rejecting conflicting owner mappings. Keep automatic legacy-agent promotion dark unless the desktop build opts in with BUZZ_DESKTOP_BUILD_PMA_MIGRATION=1. Existing authoritative edit and deletion retries continue to flush so the rollout gate cannot strand them. Also return the final serialization failure normally instead of retaining a panic-shaped retry tail. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Bring the PR onto the exact main state used by CI after overlapping relay connection and event-path changes landed. The automatic merge was clean. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Re-review at head 63563d4 (Paul + Thufir, combined). CI is fully green at this head, and 7 of the 8 original findings are verified addressed at source:
| Finding | Fixing commit | Status |
|---|---|---|
CRITICAL — tombstone resurrection window (inbound.rs) |
2d10774 | ✅ durable generation floor gates both branches; regression test covers active-N → deleted-N+1 → replayed-N and N+2 re-creation (retention/tests.rs) |
| CRITICAL — closed-relay direct-member revocation bypass | d22d587 | ✅ owner evidence verified independently of allow_nip_oa_auth; AUTH materializes owner; REQ/persistent/ephemeral EVENT revalidation covered |
| IMPORTANT — hydration truncation (LIMIT competition) | 2fcf4b9 | |
| IMPORTANT — replica-routed pre-provision reads | 2fcf4b9 | ✅ pure kind-30179 REQ routes to writer (req.rs); PMA heads in a dedicated query |
| IMPORTANT — lost tombstone fan-out on idempotent retry | 2fcf4b9 + 63563d4 | ✅ retry redispatches reconstructed tombstone; active byte-identical retries stay eventless |
| IMPORTANT — red CI (fmt, clippy, file-size ratchet) | 21a4f03 | ✅ all checks green at this head |
| MINOR — non-exhaustive merge | 125f3d7 | ✅ exhaustive destructure |
MINOR — doc fixes (expected_definition_revision, migration downtime note) |
2fcf4b9 | ✅ |
The one remaining blocker is the inline comment on usePersonaSync.ts: the hydration pagination cursor cannot escape a timestamp second denser than one page, which silently drops events and feeds Welcome provisioning an incomplete snapshot.
Non-blocking notes (no action required to merge, but worth follow-up):
- No endpoint-level test spans the closed-relay (
allow_nip_oa_auth=false) revocation path across AUTH + REQ + COUNT + persistent/ephemeral EVENT in one flow — unit/DB coverage exists per piece. - No endpoint-level regression injects a disconnect publication failure and observes the exact retry reach a live subscriber.
| oldestCreatedAt = Math.min(oldestCreatedAt, event.created_at); | ||
| } | ||
| if (page.length < HYDRATION_PAGE_SIZE || byId.size === sizeBefore) break; | ||
| until = oldestCreatedAt; |
There was a problem hiding this comment.
🤖 IMPORTANT — Correctness: this cursor is a bare inclusive timestamp, and it cannot advance past a second denser than one page.
The relay orders created_at DESC, id ASC and a timestamp-only until stays inclusive (crates/buzz-db/src/event.rs). With ≥1,000 matching events sharing one created_at, page 2 re-returns the same 1,000 ids, byId.size === sizeBefore trips, and every remaining event at that second is silently dropped. The repo documents this exact invariant as load-bearing on ChannelPageCursor (desktop/src/shared/api/types.ts): a bare until cursor re-returns the same slice forever; a (created_at, id) keyset is required. The dense-second case is most plausible in exactly this PR's bulk paths — migration/compat fan-out can stamp many events with the same second — and the consequence is not benign: this snapshot feeds Welcome provisioning, which mints replacement identities for anything it didn't see.
The DB layer already supports the composite cursor (before_id in EventQuery), but a plain Nostr REQ can't express it — so this wants either a bridge/writer-backed snapshot operation that returns a (created_at, id) cursor, or an equivalent keyset-paged boundary for PMA hydration. Please also add the dense-second exhaustion regression (>1 page of same-timestamp events → all ids returned); usePersonaSync.test.mjs currently only asserts the two initial request shapes.
Summary
Review context
This follows the review of #4593. Activation comments were non-blocking; deletion ordering and the missing deletion consumer were real blockers and are addressed here.
Validation
cargo check --manifest-path desktop/src-tauri/Cargo.tomlcargo check -p buzz-db -p buzz-relay --testspnpm typecheck--no-verifyonly because the branch-wide Desktop file-size ratchet rejects pre-existing oversized files plus the intentionally expanded PMA filesKnown follow-up