Steps to reproduce
- Create or import two facts that share the same identifier value, and give both the property
tags with values ['old', 'keep'].
- Call
Fbe.delete_one(first_fact, 'tags', 'old', fb: fb).
- Inspect the factbase after the transaction.
Actual result
The lookup in Fbe.delete_one deletes every fact with the matching identifier, then inserts a replacement built only from the supplied fact handle. The second fact is silently lost. The sibling Fbe.delete and Fbe.overwrite helpers reject identifiers that match multiple facts, but Fbe.delete_one does not check the deletion count.
Expected result
Fbe.delete_one should detect that the identifier matched more than one fact and roll back with an error, instead of deleting unrelated facts.
Technical evidence
lib/fbe/delete_one.rb calls fbt.query("(eq #{id} #{i})").delete! and ignores the returned count before inserting a replacement. When two facts share that identifier, delete! removes both. The reconstruction copies values only from the single fact argument, so the other fact's properties are discarded.
Steps to reproduce
tagswith values['old', 'keep'].Fbe.delete_one(first_fact, 'tags', 'old', fb: fb).Actual result
The lookup in
Fbe.delete_onedeletes every fact with the matching identifier, then inserts a replacement built only from the supplied fact handle. The second fact is silently lost. The siblingFbe.deleteandFbe.overwritehelpers reject identifiers that match multiple facts, butFbe.delete_onedoes not check the deletion count.Expected result
Fbe.delete_oneshould detect that the identifier matched more than one fact and roll back with an error, instead of deleting unrelated facts.Technical evidence
lib/fbe/delete_one.rbcallsfbt.query("(eq #{id} #{i})").delete!and ignores the returned count before inserting a replacement. When two facts share that identifier,delete!removes both. The reconstruction copies values only from the singlefactargument, so the other fact's properties are discarded.