Summary
Deleting a workflow (kind:5 with a-tag address) removes the workflows control-plane row (log: "Workflow deleted via NIP-09 a-tag (UUID)") but can leave the current kind:30620 definition event live in the event log. The Desktop workflows list is built from /query kinds:[30620], so it keeps showing "ghost" workflows that no longer exist.
Worse, the ghosts are interactive: editing/toggling a ghost entry submits a kind:30620 update for the deleted id, and handle_workflow_def treats an unknown id as a create — silently resurrecting the deleted workflow. Manual-trigger (46020) on a ghost returns "workflow not found", which is how users notice.
Observed sequence (single-node relay, ghcr.io/block/buzz:main)
- Create workflow W (definition event E1). Delete W → row deleted, E1 tombstoned ✅
- Ghost entry still visible in a stale Desktop list → user edits it → kind:30620 update with W's d-tag → workflow re-created with new definition event E2 ✅ (by design, but surprising from a ghost)
- Delete W again → relay logs "Workflow deleted via NIP-09 a-tag (UUID)", row deleted, but E2 stays live (
events.deleted_at IS NULL) ❌
/query kinds:[30620] returns E2 forever → Desktop lists a workflow that get_workflow/trigger reject as not found.
Step 1 vs step 3 suggests event tombstoning keys on specific event ids (e tags from the client's possibly-stale view) rather than the a-tag address: the second delete referenced the original event id, not E2.
Suggested fix
On NIP-09 a-tag workflow deletion, tombstone all live kind:30620 events whose d tag matches the deleted workflow id (address-based, same transaction as the row delete). Optionally: reject kind:30620 updates for ids with a deletion tombstone instead of silently re-creating.
Related UX: the workflows list should refetch after delete mutations resolve; a stale list is what makes the resurrection path reachable.
Summary
Deleting a workflow (kind:5 with
a-tag address) removes theworkflowscontrol-plane row (log: "Workflow deleted via NIP-09 a-tag (UUID)") but can leave the current kind:30620 definition event live in the event log. The Desktop workflows list is built from/query kinds:[30620], so it keeps showing "ghost" workflows that no longer exist.Worse, the ghosts are interactive: editing/toggling a ghost entry submits a kind:30620 update for the deleted id, and
handle_workflow_deftreats an unknown id as a create — silently resurrecting the deleted workflow. Manual-trigger (46020) on a ghost returns "workflow not found", which is how users notice.Observed sequence (single-node relay,
ghcr.io/block/buzz:main)events.deleted_at IS NULL) ❌/query kinds:[30620]returns E2 forever → Desktop lists a workflow thatget_workflow/trigger reject as not found.Step 1 vs step 3 suggests event tombstoning keys on specific event ids (
etags from the client's possibly-stale view) rather than the a-tag address: the second delete referenced the original event id, not E2.Suggested fix
On NIP-09 a-tag workflow deletion, tombstone all live kind:30620 events whose
dtag matches the deleted workflow id (address-based, same transaction as the row delete). Optionally: reject kind:30620 updates for ids with a deletion tombstone instead of silently re-creating.Related UX: the workflows list should refetch after delete mutations resolve; a stale list is what makes the resurrection path reachable.