Skip to content

fix(relay): soft-delete kind:30620 events row on workflow a-tag delete - #5132

Open
iroiro147 wants to merge 1 commit into
block:mainfrom
iroiro147:fix/5077-workflows-list-deleted
Open

fix(relay): soft-delete kind:30620 events row on workflow a-tag delete#5132
iroiro147 wants to merge 1 commit into
block:mainfrom
iroiro147:fix/5077-workflows-list-deleted

Conversation

@iroiro147

Copy link
Copy Markdown
Contributor

Summary

buzz workflows list shows deleted workflows indefinitely because the kind:30620 events row survives an otherwise-successful workflows delete.

Resolves #5077.

Root cause

handle_a_tag_deletion special-cases KIND_WORKFLOW_DEF ahead of the generic NIP-33 arm. The custom branch calls delete_workflow_for_owner (which removes the operational workflows table row and invalidates the schedule cache) and then returns without soft-deleting the (kind:30620, pubkey, d_tag) row in events. The comment there even calls this out — "which doesn't soft-delete the events row by design — that's a separate concern."

That design holds for relay authority (the workflow can no longer fire), but it leaks through the CLI: cmd_list_workflows (crates/buzz-cli/src/commands/workflows.rs) issues a Nostr REQ for kinds:[30620] + #h+ channel id directly against the event store. With the events row undeleted, the deleted workflow keeps showing up in list output long after delete succeeded.

The generic NIP-33 arm (k if is_parameterized_replaceable(k)) already soft-deletes the events row for every other addressable kind via soft_delete_by_coordinate. KIND_WORKFLOW_DEF wouldn't be matched by that arm anyway because the workflow match runs first — but the workflow arm simply never called the mirror soft-delete.

Fix

After the operational delete succeeds (UUID or name path — both reach the same tail), apply the same (kind, pubkey, d_tag) soft-delete for kind:30620, using the a-tag coordinate's pubkey (the real event author) and the tombstone's created_at as the soft-delete ceiling (same pattern as the generic arm — NIP-09 scoping so a stale replayed tombstone can never erase a newer replacement head).

The soft-delete is best-effort, never fatal: the operational workflows row is already gone at that point, so the scheduler cannot re-fire the workflow. If the events-row update fails (transient DB error, malformed a-tag pubkey), the worst case is the zombie entry persists — same behavior as today, but with a tracing::warn trail so operators can see and clean up. Reflected in the comment on the fix site.

What did NOT change

  • Delete authority checks — still enforced by delete_workflow_for_owner matching actor_bytes (the effective author incl. ACP agent override) against workflows.owner_pubkey. If the caller doesn't own the workflow, the branch returns before the soft-delete.
  • Scheduler or webhook behaviorinvalidate_channel_workflows still runs on success, call_webhook authority checks still gated by role in handle_workflow_def, triggers still read the workflows table only.
  • Other addressable kinds — the generic NIP-33 path is untouched; my addition is narrowly scoped to the workflow arm.
  • CLI list behavior past deletion — works correctly post-fix because it reads the same events store the tombstone now hides. No CLI diff required.

Tests

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings clean.
  • cargo test --lib -p buzz-relay — 849 pass, 9 pre-existing api::admin / api::media failures (verified same failures reproduce on the untouched base branch).

I did not add an integration test: the existing NIP-09 a-tag suites (e2e_team, e2e_project, e2e_managed_agent) cover the pattern but only for non-workflow kinds, and KIND_WORKFLOW_DEF ingest additionally requires h-tag channel + channel membership + valid YAML, so a regression test would need fresh channel-bootstrap scaffolding. Happy to add one if you prefer — flagged here so reviewers can decide.

Compatibility

Pure deletion-side behavioral fix. No schema, no API surface change, no migration. Existing deleted workflows keep their zombie events rows (same as today) — a separate cleanup migration can sweep them if desired, but this PR does not require it.

'workflows list' reads the kind:30620 event store directly. Before this
change, 'workflows delete' removed only the operational `workflows` row
(via `handle_a_tag_deletion`) but left the kind:30620 `events` row live,
so a successfully deleted workflow kept showing up as a zombie entry in
the list.

The generic NIP-33 path (`k if is_parameterized_replaceable(k)`) already
applies `soft_delete_by_coordinate` for every other addressable kind.
KIND_WORKFLOW_DEF was deliberately branched ahead of it — the comment
noted the `events` row was a separate concern — and the branch never
got the mirror soft-delete.

Apply the same `(kind, pubkey, d_tag)` soft-delete to the workflow
arm once the operational delete succeeds. Keep it best-effort: if the
`events`-row lookup fails the deletion is still complete (the scheduler
cannot fire the workflow), only `workflows list` keeps showing the
zombie — log a warn so operators can investigate.

Resolves block#5077.

Signed-off-by: iroiro147 <sarthak.singh@mastersunion.org>
@iroiro147
iroiro147 requested a review from a team as a code owner August 7, 2026 01:25
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.

workflows list shows deleted workflows indefinitely (a-tag delete doesn't soft-delete the kind:30620 events row)

1 participant