Skip to content

Commit be21096

Browse files
committed
feat: Surface self-loop in command failures
1 parent 0c0a750 commit be21096

3 files changed

Lines changed: 63 additions & 7 deletions

File tree

src/bark/commands.clj

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -657,14 +657,26 @@
657657
source-type target-type))}))
658658

659659
(defn- record-target-failures!
660-
[failure-ctx syntax target-mid target-eid valid?]
660+
"Record a failure when a relation command's target lookup or
661+
validation didn't pan out. Distinguishes three modes:
662+
- :unknown-target -- mid was given but no report matches.
663+
- :self-loop -- target resolves to the current report itself
664+
(a no-op self-reference, common when a new
665+
bug filed as a reply names its own thread root).
666+
- :type-mismatch -- target exists but the type constraint fails."
667+
[failure-ctx syntax target-mid target-eid valid? report-eid]
661668
(when failure-ctx
662669
(cond
663670
(and target-mid (nil? target-eid))
664671
(record-failure! (assoc failure-ctx
665672
:reason :unknown-target
666673
:audience :author
667674
:command (str syntax ": " target-mid)))
675+
(and target-eid (= target-eid report-eid))
676+
(record-failure! (assoc failure-ctx
677+
:reason :self-loop
678+
:audience :author
679+
:command (str syntax ": " target-mid)))
668680
(and target-eid (not valid?))
669681
(record-failure! (assoc failure-ctx
670682
:reason :type-mismatch
@@ -674,8 +686,8 @@
674686
(defn- apply-related-to!
675687
"Pose / retract :related-to relations from a resolved commands map.
676688
Independent of report closure state; called from both `apply-lines!`
677-
(open path) and `try-unclosed!` (closed path). Emits failures for
678-
unknown targets, ignores self-loops silently.
689+
(open path) and `try-unclosed!` (closed path). Records failures
690+
for unknown targets and self-loops.
679691
680692
Target mids resolve via `report-eid-by-mid` -- root or descendant --
681693
so a mid pointing at any email in a report's thread reaches that
@@ -700,7 +712,13 @@
700712
:audience :author
701713
:command (str "Related-to: " mid))))
702714
(= report-eid target-eid)
703-
(log/warn "Related-to: self-loop ignored" mid)
715+
(do (log/warn (str "Related-to: " mid
716+
" -- targets the same report (self-loop) -- ignored"))
717+
(when failure-ctx
718+
(record-failure! (assoc failure-ctx
719+
:reason :self-loop
720+
:audience :author
721+
:command (str "Related-to: " mid)))))
704722
:else
705723
(do (rel/pose-if-absent! conn {:from-eid report-eid
706724
:to-eid target-eid
@@ -985,10 +1003,13 @@
9851003
(doseq [{:keys [resolved syntax target-mid]} rows
9861004
:let [tgt-eid (:target-eid resolved)
9871005
valid? (:valid? resolved)]]
988-
(record-target-failures! failure-ctx syntax target-mid tgt-eid valid?)
1006+
(record-target-failures! failure-ctx syntax target-mid tgt-eid valid? report-eid)
9891007
(when (and tgt-eid (not valid?))
990-
(log/warn (str syntax ": type mismatch -- source")
991-
source-type "vs target" (:target-type resolved))))
1008+
(if (= tgt-eid report-eid)
1009+
(log/warn (str syntax ": " target-mid
1010+
" -- targets the same report (self-loop) -- ignored"))
1011+
(log/warn (str syntax ": type mismatch -- source")
1012+
source-type "vs target" (:target-type resolved)))))
9921013
(apply-related-to! conn report-eid resolved email-eid from-addr failure-ctx)))))
9931014

9941015
(def ^:private unclose-relation-rows

src/bark/common.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
(def reason-labels
5353
"Human-readable labels for command-failure :reason keys."
5454
{:unknown-target "unknown target"
55+
:type-mismatch "type mismatch between source and target"
56+
:self-loop "target is the same report as the source"
5557
:insufficient-scope "insufficient permissions"})
5658

5759
;; ---------------------------------------------------------------------------

test/bark/qualified_links_test.clj

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,39 @@
293293
":supersedes relation still active (not retracted)")))
294294
(finally (close-and-cleanup! setup))))))
295295

296+
(deftest superseded-by-self-loop-is-recorded
297+
(testing "Superseded-by: <own-mid> records a :self-loop failure and
298+
leaves the report open. This catches the common pitfall
299+
where a new [BUG] reply names its own thread root as the
300+
superseder, which would otherwise no-op silently."
301+
(let [{:keys [conn] :as setup} (fresh-conn)]
302+
(try
303+
(let [bug-mid "<bug-self@x>"
304+
bug-email (mk-email! conn bug-mid "alice@x" #inst "2026-04-01")
305+
bug-eid (mk-report! conn bug-mid bug-email :bug)
306+
cmd-eid (mk-email! conn "<cmd-self@x>" "alice@x" #inst "2026-04-02")
307+
cmd-email {:db/id cmd-eid
308+
:email/author-address "alice@x"
309+
:email/date-sent #inst "2026-04-02"
310+
:email/body-text (str "Superseded-by: " bug-mid "\n")}
311+
recorded (atom [])]
312+
(with-redefs [commands/record-failure! (fn [entry] (swap! recorded conj entry))]
313+
(commands/apply-commands! conn bug-eid :bug cmd-email
314+
{} {} :direct))
315+
(let [after (d/pull (d/db conn)
316+
[:report/closed :report/close-reason] bug-eid)]
317+
(is (nil? (:report/closed after))
318+
"report stays open -- self-loop is rejected")
319+
(is (nil? (:report/close-reason after))
320+
"no close-reason")
321+
(is (some #(= :self-loop (:reason %)) @recorded)
322+
"a :self-loop failure was recorded")
323+
(let [entry (some #(when (= :self-loop (:reason %)) %) @recorded)]
324+
(is (= :author (:audience entry))
325+
"self-loop is routed to the author (the typo culprit)")
326+
(is (= (str "Superseded-by: " bug-mid) (:command entry))))))
327+
(finally (close-and-cleanup! setup))))))
328+
296329
;; ---------------------------------------------------------------------------
297330
;; Phase 4 (partial): Duplicate-of directive
298331
;; ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)