Tombstone-based workflow removal for Kiro Crew's saved workflow library
Discovered from README cross-links · today
Add a dashboard Remove action that tombstones workflows without breaking
What they did
The author implemented removal of saved workflows from Kiro Crew's dashboard library as a tombstone rather than a hard delete. In workflows/library.py, removal writes a tombstone (keeping every revision) under the library's cross-process lock, adds removed_at, and replaces every file holding a live copy so records saved under older redaction rules or placed by hand are removed too. The id and slug held at removal are reserved permanently: every read by ref refuses them (ids match before slugs), slug allocation never hands out an id or slug on disk, and records whose refs are shadowed are listed in computed shadowed_refs and not offered. In workflows/service.py, lineage resolution reads tombstoned definitions and checks every copy of the parent's id, so drafts and runs adapted from a removed workflow keep saving; a run of a removed workflow explains why it can't be saved. A single owner-only DELETE /api/workflows/definitions/{ref} route was added in dashboard/handlers/workflows.py and server_runtime/mcp_routes.py, with one audit event per removal, 404 for unknown or already-removed refs, and optional expected_id returning 409 when a ref now reaches another workflow. On the website, WorkflowLibraryTab.tsx and api/client/workflows.ts gained a right-aligned Remove action with a confirmation leading with a bold warning, a status line or ErrorNotice with collapsed ErrorDetails and a retry, re-reads after any removal settles, and read-only fields when no ref reaches a workflow. Docs, the workflows spec, feature map and docs/workflows.md were updated, and strings added to every catalog.
How it went
Shipped with extensive coverage: a dedicated test_workflows_library_remove.py suite plus handler, owner-gate, slash-command and WorkflowLibraryTab vitest tests, many with mutation checks. Backend pytest, frontend vitest, tsc, eslint, i18n checks and all 24 pre-push gates pass; every failing test also fails identically on main. Manual verification covered every state in light and dark via a capture script.
Worth knowing
Removal is dashboard-only: not exposed via agent tools, MCP tools or the CLI, and restoring a removed workflow is explicitly out of scope (the tombstone allows it later). Removing a single revision or deleting run history is not supported. A slug given up by an earlier rename is not covered by reservation (#23680). Unreadable tombstones release their refs and log a warning.
Wiring
Backend: workflows/library.py owns tombstone writes, the reserved id/slug set, ref resolution and shadowed_refs; workflows/service.py resolves lineage through tombstones; dashboard/handlers/workflows.py and server_runtime/mcp_routes.py expose the owner-gated DELETE route with audit events and expected_id conflict handling; chat_turn/slash_commands.py and mcp_tools/workflows.py filter removed and shadowed refs. Frontend: website/src/api/client/workflows.ts calls the DELETE route with the shown id, and WorkflowLibraryTab.tsx renders Remove, confirmation, status/ErrorNotice, retry and re-reads. Tests: test/test_workflows_library_remove.py, test_workflow_definition_handlers.py, test_r7_owner_gate_*, test_slash_command_no_channel.py and website/src/test/WorkflowLibraryTab.test.tsx. Run backend via scripts/run_scoped_tests.py plus ratchet and ws-event-scoping files; frontend via vitest with I18N_BASE_REF, tsc, eslint, i18n:check, lint:i18n and i18n:pseudo; then all 24 pre_push_gate.py gates.
Try it yourself
In this repo, add the ability to remove a saved workflow from the library in the dashboard without breaking anything adapted from it. Implement removal as a tombstone in workflows/library.py: keep every revision, add removed_at, write under the library's cross-process lock, and replace every file holding a live copy of the record. Reserve the removed id and slug permanently: every read by ref must refuse them (ids before slugs), slug allocation must never hand out an id or slug on disk, and records whose refs are shadowed should be listed in a computed shadowed_refs field and not offered. In workflows/service.py, make lineage resolution read tombstoned definitions and check every copy of the parent's id so adapted drafts and runs still save, and make a run of a removed workflow explain why it can't be saved. Add one owner-only DELETE /api/workflows/definitions/{ref} route behind the same owner guard as the sibling PATCH route, with one audit event per removal, 404 for unknown or already-removed refs, and an optional expected_id returning 409 when the ref now reaches another workflow. On the website, add a right-aligned Remove action apart from Save with a confirmation that leads with a bold irreversible warning and names the real slug, a status line or ErrorNotice with collapsed ErrorDetails and a retry, re-read the library after any removal settles, and show read-only fields when no ref reaches a workflow. Update the workflows spec, feature map and docs/workflows.md, and add strings to every catalog.