Skip to content

Commit 1d6de04

Browse files
committed
fix: Refuse contradictory Superseded-by/Duplicate-of combinations
1 parent 2c54765 commit 1d6de04

3 files changed

Lines changed: 54 additions & 0 deletions

File tree

docs/bone-manual.org

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,19 @@ the last path segment of an archive URL such as
206206
=https://list.orgmode.org/orgmode/id@host/=. Both reports must be of the
207207
same type: bug to bug, patch to patch, request to request.
208208

209+
A report has at most one superseder: when an email carries several
210+
=Superseded-by:= lines (or several =Supersedes:= or =Duplicate-of:=
211+
lines), only the last one applies; earlier lines are silently
212+
ignored.
213+
214+
=Superseded-by:= and =Duplicate-of:= in the same email contradict
215+
each other: neither is applied.
216+
217+
These commands act on open reports only. On a closed report,
218+
=Superseded-by:=, =Supersedes:= and =Duplicate-of:= are silently
219+
ignored; reopen the report first with =Not closed.= and resend the
220+
command.
221+
209222
To undo, reply with =Not superseded-by: <new-message-id@example.com>=
210223
from the closed report's thread, or with =Not supersedes:
211224
<old-message-id@example.com>= from the replacement's thread.

src/bone/commands.clj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,15 @@
994994
(when (seq permitted)
995995
(let [current @current-d
996996
resolved (resolve-commands permitted)
997+
;; Superseded-by: and Duplicate-of: in one email contradict
998+
;; each other -- both close this report, and the winning
999+
;; close-reason would be arbitrated by closure-relation-rows
1000+
;; order, not by the email. Apply neither.
1001+
resolved (if (and (:superseded-by resolved) (:duplicate-of resolved))
1002+
(do (log/warn "Superseded-by: and Duplicate-of: in the same"
1003+
"email -- contradictory, neither applied")
1004+
(dissoc resolved :superseded-by :duplicate-of))
1005+
resolved)
9971006
source-type (:report/type current)
9981007
rows (compute-closure-rows db report-eid source-type resolved)
9991008
valid-rows (filterv (comp :valid? :resolved) rows)

test/bone/qualified_links_test.clj

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,38 @@
329329
(is (= (str "Superseded-by: " bug-mid) (:command entry))))))
330330
(finally (close-and-cleanup! setup))))))
331331

332+
(deftest conflicting-closure-commands-apply-neither
333+
(testing "Superseded-by: and Duplicate-of: in one email contradict
334+
each other: neither applies (the winning close-reason would
335+
otherwise be arbitrated by closure-relation-rows order, an
336+
implementation detail the email does not control)."
337+
(let [{:keys [conn] :as setup} (fresh-conn)]
338+
(try
339+
(let [old-mid "<patch-old-c@x>"
340+
sup-mid "<patch-sup@x>"
341+
dup-mid "<patch-dup-c@x>"
342+
old-email (mk-email! conn old-mid "alice@x" #inst "2026-04-01")
343+
old-eid (mk-report! conn old-mid old-email :patch)
344+
sup-email (mk-email! conn sup-mid "alice@x" #inst "2026-04-02")
345+
_sup-eid (mk-report! conn sup-mid sup-email :patch)
346+
dup-email (mk-email! conn dup-mid "alice@x" #inst "2026-04-03")
347+
_dup-eid (mk-report! conn dup-mid dup-email :patch)
348+
cmd-eid (mk-email! conn "<cmd-conflict@x>" "alice@x" #inst "2026-04-04")
349+
cmd-email {:db/id cmd-eid
350+
:email/author-address "alice@x"
351+
:email/date-sent #inst "2026-04-04"
352+
:email/body-text (str "Superseded-by: " sup-mid "\n"
353+
"Duplicate-of: " dup-mid "\n")}]
354+
(commands/apply-commands! conn old-eid :patch cmd-email
355+
{} {} :direct nil)
356+
(let [after (d/pull (d/db conn)
357+
[:report/closed :report/close-reason] old-eid)
358+
rels (get-relations (d/db conn) old-eid)]
359+
(is (nil? (:report/closed after)) "report stays open")
360+
(is (nil? (:report/close-reason after)) "no close-reason")
361+
(is (empty? rels) "no relation posed")))
362+
(finally (close-and-cleanup! setup))))))
363+
332364
;; ---------------------------------------------------------------------------
333365
;; Phase 4 (partial): Duplicate-of directive
334366
;; ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)