From 5ff83d82f852edb50a96cab4cb67a13baa8bf830 Mon Sep 17 00:00:00 2001 From: chiller Date: Wed, 7 Oct 2026 08:24:15 +1100 Subject: [PATCH] fix(desktop): address workflow deletions at the workflow's actual owner (#2669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #2671. ## Problem Deleting an agent-created workflow from the desktop app appears to succeed, but the workflow keeps running and stays in the list. Since workflows are commonly created by agents, this makes most workflows undeletable from the UI. ## Root Cause `delete_workflow` builds the NIP-09 `a`-tag coordinate with the **caller's** pubkey: ```rust let builder = events::build_workflow_delete(&workflow_id, ¤t_pubkey_hex(&state)?)?; ``` kind:30620 is addressable by `(author, d-tag)`. For an agent-created workflow, `30620::` names a record that does not exist. The relay accepts the kind:5 (a self-addressed delete always passes validation) and deletes nothing; the UI reports success. Observed live: a delete of an agent-owned workflow produced `["a","30620::"]` while the kind:30620 event is authored by the agent — the coordinate can never match. ## Fix 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. Relay-side validation already authorizes the owning human for agent-authored coordinates via `is_agent_owner`. Deleting an unknown workflow id now surfaces `workflow not found` instead of silently publishing an unmatchable delete. This is the client half of the fix. The relay half is #2242: today the relay executes a-tag deletions scoped by the delete's signer, so a human-signed delete of an agent workflow still no-ops server-side. Without this change, #2242 alone won't help desktop users, because the client addresses a record that doesn't exist. Together they make delete work end to end. Related but deliberately not included: deleted workflows leaving their kind:30620 event queryable (ghost list entries) is #2390, with a relay-side fix open in #1340. ## Validation - `cargo test` in `desktop/src-tauri`: 1617 passed, 0 failed, 14 ignored. - `cargo clippy --all-targets` and `cargo fmt --check`: clean. - Live repro against the hosted relay: the stock client's kind:5 for an agent-owned workflow named the signer in the coordinate; the relay returned `accepted: true` and the workflow remained live and kept firing. - With the fix: the delete event carries `30620::`; an owner-signed deletion of that exact coordinate was verified to remove the workflow record. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- desktop/src-tauri/src/commands/workflows.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/desktop/src-tauri/src/commands/workflows.rs b/desktop/src-tauri/src/commands/workflows.rs index c4e5d38c8b..251dbeb515 100644 --- a/desktop/src-tauri/src/commands/workflows.rs +++ b/desktop/src-tauri/src/commands/workflows.rs @@ -313,7 +313,26 @@ pub async fn delete_workflow( workflow_id: String, state: State<'_, AppState>, ) -> Result<(), String> { - let builder = events::build_workflow_delete(&workflow_id, ¤t_pubkey_hex(&state)?)?; + // The NIP-09 `a` coordinate must name the workflow's actual author, not + // the caller: kind:30620 is addressable by (author, d-tag), and workflows + // are commonly agent-created. Addressing the delete at the caller's own + // pubkey targets a record that doesn't exist — the relay accepts the + // kind:5 and deletes nothing. + let prior = query_relay( + &state, + &[serde_json::json!({ + "kinds": [30620], + "#d": [workflow_id.clone()], + "limit": 1 + })], + ) + .await?; + let owner_pubkey = prior + .first() + .map(|ev| ev.pubkey.to_hex()) + .ok_or_else(|| "workflow not found".to_string())?; + + let builder = events::build_workflow_delete(&workflow_id, &owner_pubkey)?; submit_event(builder, &state).await?; Ok(()) }