A recreate of a workspace whose Mnemosyne Library was orphaned (left behind
by a failed Daedalus delete-propagate) collides on the global Library.name
unique constraint. neomodel raised UniqueProperty unguarded, so workspace_create
500'd and ingest then 404'd forever — the queue froze silently.
Guard lib.save() and return a structured 409 with a machine code so Daedalus
can classify the failure without string-matching:
- name_conflict — the new name-collision case
- owner_conflict, library_type_immutable — codes added to the two existing 409s
Cypher-touching paths stay covered by the manual end-to-end plan, per the
test module's stated convention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Admin/HTML library delete previously hard-blocked workspace-scoped
(Daedalus-managed) libraries, leaving no way to clear an orphaned Library
node — e.g. one left behind when a Daedalus workspace delete failed to
propagate. A recreate of that workspace then collides on the global
Library.name unique constraint and 500s, freezing ingest.
Allow the delete behind the existing confirm warning (low risk: source
content lives in Daedalus and is recreated + re-embedded on next sync),
and route both the API and HTML delete paths through one shared cascade.
- Add library/services/library_delete.delete_library_cascade(lib), keyed on
Library uid so it covers global and workspace-scoped libraries. It removes
Chunks, Images/ImageEmbeddings, Items, Collections, the Library, then GCs
orphan-only Concepts (verbatim from the API view, re-keyed workspace_id->uid).
- workspace_detail_or_delete (API) now calls the shared helper.
- library_delete (HTML) no longer blocks workspace_id libraries; it calls the
cascade instead of a bare lib.delete() (which leaked child nodes — also a
latent bug for global libraries with content).
- Confirm-delete template shows a caution banner for Daedalus-managed libraries.
No migration: Mnemosyne library data is in Neo4j (neomodel); no schema change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>