Skip to content

fix(relay): execute a-tag workflow deletions against the a-tag owner, delete all name matches, surface no-op deletions#2242

Open
Orland00 wants to merge 1 commit into
block:mainfrom
Orland00:fix/1593-workflow-deletion-owner-scope
Open

fix(relay): execute a-tag workflow deletions against the a-tag owner, delete all name matches, surface no-op deletions#2242
Orland00 wants to merge 1 commit into
block:mainfrom
Orland00:fix/1593-workflow-deletion-owner-scope

Conversation

@Orland00

@Orland00 Orland00 commented Jul 21, 2026

Copy link
Copy Markdown

Partially addresses #1593 (the going-forward half; existing-orphan reconciliation is a separate discussion — see my comment on the issue).

Problem

Workflows whose owner can no longer delete them keep executing on their cron schedules indefinitely — recurring compute and API spend with no accountable owner and no off switch. Three execution bugs make that possible:

  1. Validation/execution owner mismatch. validate_standard_deletion_event authorizes an a-tag deletion against the a-tag's pubkey (including the is_agent_owner allowance that lets a human owner delete their agent's content), but handle_a_tag_deletion parses that pubkey and never uses it — both the UUID delete and the name fallback are scoped by the deletion signer. A human owner deleting an agent-owned workflow passes validation, then silently deletes nothing.
  2. Name-based deletion removes at most one of N. The name fallback used find_by_owner_and_name (LIMIT 1, no ORDER BY) + a single-row delete; with several same-named rows (there's no unique constraint on owner+name) an arbitrary one is removed and the rest keep firing.
  3. No-op deletions look successful. All miss paths only tracing::warn! server-side; the client sees accepted: true with an empty message — which misled the issue reporter into declaring victory twice.

Implementation

  • handle_a_tag_deletion (KIND_WORKFLOW_DEF): hex-decode the a-tag pubkey and scope both the UUID delete and the name-based delete by it. Authorization is unchanged — it already ran in validate_standard_deletion_event; this aligns execution with validation.
  • New delete_workflows_for_owner_by_name in buzz-db: single DELETE … RETURNING id, channel_id removing every match; each affected channel's workflows are invalidated (deduped). find_by_owner_and_name is untouched (its only caller was this handler).
  • No-op surfacing follows the existing "duplicate:" / "info:" OK-message convention in ingest.rs: the kind:5 event is stored/accepted as before, but the OK message now carries info: no matching workflow found for deletion. Plumbing: handle_side_effects returns anyhow::Result<Option<String>>; kind:5 routes separately and every other kind keeps its exact previous behavior (Ok(None)).

Behavioral note for reviewers: cross-owner deletions that previously passed validation and then silently no-op'd now actually execute — that is the intended fix, but worth a conscious look at the authz implications (validation already gated these on is_agent_owner).

Testing

Regression tests (Postgres-gated, #[ignore = "requires Postgres"], following the existing setup_pool / workflow_sink::integration_tests patterns):

  • delete_workflows_for_owner_by_name_removes_all_same_named_rows (3 same-named rows → all deleted)
  • delete_workflows_for_owner_by_name_leaves_other_owners_untouched
  • a_tag_deletion_by_human_owner_deletes_agent_owned_workflow — the exact reported bug: agent-owned workflow, deletion signed by the human owner, a-tag naming the agent → row actually deleted. Red/green verified (temporarily reintroducing the old actor scoping makes it fail).

Ran locally: cargo fmt --all -- --check · cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings · cargo test -p buzz-db -p buzz-relay (82 + 715 passed; PG-gated tests run against a real Postgres 17) · cargo check --workspace. Relying on upstream CI for the full matrix (desktop/web/mobile untouched).

Deferred

  • Reconciliation for already-orphaned rows (pre-Harden relay attack surfaces #1369 rows are unreachable by any client-signable event, per the issue's 2026-07-09 update) — awaiting maintainer preference on shape (migration vs startup sweep vs scripts/maintenance/), see the issue thread.

  • CLI buzz workflows delete --name follow-up (the relay path now supports it correctly).

  • No-op surfacing is deliberately scoped to the workflow branch; the generic NIP-33 soft-delete no-match case still no-ops silently — happy to extend the same message there if you want the consistency.

@Orland00
Orland00 requested a review from a team as a code owner July 21, 2026 17:37
… delete all name matches, surface no-op deletions

Issue block#1593: NIP-09 a-tag deletion for kind:30620 (workflow) parsed the
a-tag's owner pubkey but never used it. Both the UUID and name-based delete
paths scoped the DELETE by `actor_bytes` (the deletion signer) instead.
`validate_standard_deletion_event` already authorizes the request against
the a-tag pubkey, including the `is_agent_owner` allowance that lets a human
owner delete their agent's workflows -- so a human owner's deletion of an
agent-owned workflow passed validation, then silently deleted nothing, while
the relay still replied `accepted:true`.

Three fixes, one change:

- handlers/side_effects.rs: `handle_a_tag_deletion`'s KIND_WORKFLOW_DEF
  branch now hex-decodes the a-tag pubkey and scopes both the UUID delete
  and the name-based delete by that owner, not the signer.
- buzz-db/workflow.rs: add `delete_workflows_for_owner_by_name`, which
  removes every workflow matching (community, owner, name) in one
  statement. `find_by_owner_and_name`'s bare `LIMIT 1` (no unique
  constraint on the pair) meant name-based deletion previously removed at
  most one of N same-named rows; it has no remaining callers but stays in place as public API.
- No-op deletions (no matching workflow) now surface as an `OK` message
  ("info: no matching workflow found for deletion") instead of a silent
  tracing::warn, following this codebase's existing convention of
  accepted:true + an informational message prefix (see the "duplicate:"
  and "info: you have left this relay" messages in ingest.rs). This
  required widening `handle_side_effects`'s return type to
  `Result<Option<String>>`, which ingest_event_inner now folds into the
  final IngestResult.message; every other kind is unaffected and always
  yields `Ok(None)`.

Tests:
- buzz-db: delete_workflows_for_owner_by_name_removes_all_same_named_rows,
  delete_workflows_for_owner_by_name_leaves_other_owners_untouched
  (Postgres-gated, existing repo pattern).
- buzz-relay: a_tag_deletion_by_human_owner_deletes_agent_owned_workflow --
  exact bug regression (agent-owned workflow, deletion signed by the human
  owner) using the same real-AppState-over-lazy-PgPool pattern as
  workflow_sink::integration_tests.

All three verified red (fail without the owner-scope fix) and green (pass
with it) against a local Postgres instance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Orlando Lopez <orlando.lf19@gmail.com>
@Orland00
Orland00 force-pushed the fix/1593-workflow-deletion-owner-scope branch from d94cd43 to 103b368 Compare July 21, 2026 17:55
chillerno1 added a commit to chillerno1/buzz that referenced this pull request Jul 24, 2026
delete_workflow built the NIP-09 a-tag coordinate with the caller's
pubkey, but kind:30620 is addressable by (author, d-tag). For an
agent-created workflow the coordinate named a record that doesn't
exist — the relay accepted the kind:5 and deleted nothing while the
UI reported success.

Fetch the workflow's kind:30620 event first (the same #d query
update_workflow already uses) and build the coordinate from the
event's author. Deleting an unknown workflow id now surfaces
"workflow not found" instead of silently publishing an unmatchable
delete.

Client half of the end-to-end fix; the relay half (execute a-tag
deletions against the a-tag owner rather than the signer) is block#2242.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: chillerno1 <gh.chiller@pm.me>
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.

1 participant