;; lib/mod/tests/audit.sx — Phase 2: evidence accumulation + proof tree + audit.

(define mod-aud-count 0)
(define mod-aud-pass 0)
(define mod-aud-fail 0)
(define mod-aud-failures (list))

(define
  mod-aud-test!
  (fn
    (name got expected)
    (begin
      (set! mod-aud-count (+ mod-aud-count 1))
      (if
        (= got expected)
        (set! mod-aud-pass (+ mod-aud-pass 1))
        (begin
          (set! mod-aud-fail (+ mod-aud-fail 1))
          (append!
            mod-aud-failures
            (str name "\n    expected: " expected "\n    got:      " got)))))))

(define
  mod-aud-decide1
  (fn (r) (mod/decide-report r (list r) mod/default-rules)))

;; ── proof tree: keyword classification ──

(define
  mod-aud-spam
  (mod-aud-decide1 (mod/mk-report "r1" "alice" "bob" "this is spam")))
(define mod-aud-spam-goals (get (get mod-aud-spam :proof) :goals))

(mod-aud-test! "spam proof has one goal" (len mod-aud-spam-goals) 1)
(mod-aud-test!
  "spam proof goal text"
  (get (first mod-aud-spam-goals) :goal)
  "classification(r1, spam)")
(mod-aud-test!
  "spam proof goal solved"
  (get (first mod-aud-spam-goals) :solved)
  true)

;; ── proof tree: count rule with real bindings ──

(define mod-aud-rep-r (mod/mk-report "r3" "ann" "dave" "x"))
(define
  mod-aud-rep
  (mod/decide-report
    mod-aud-rep-r
    (list mod-aud-rep-r mod-aud-rep-r mod-aud-rep-r)
    mod/default-rules))
(define mod-aud-rep-goals (get (get mod-aud-rep :proof) :goals))
(define mod-aud-rep-binds (get (first mod-aud-rep-goals) :bindings))

(mod-aud-test!
  "count proof goal solved"
  (get (first mod-aud-rep-goals) :solved)
  true)
(mod-aud-test! "count proof binding N" (dict-get mod-aud-rep-binds "N") "3")
(mod-aud-test!
  "count proof binding S (subject)"
  (dict-get mod-aud-rep-binds "S")
  "dave")

;; ── proof tree: default keep has a 'true' goal ──

(define
  mod-aud-keep
  (mod-aud-decide1 (mod/mk-report "rk" "a" "b" "a fine post")))
(define mod-aud-keep-goals (get (get mod-aud-keep :proof) :goals))

(mod-aud-test!
  "keep proof goal text true"
  (get (first mod-aud-keep-goals) :goal)
  "true")
(mod-aud-test!
  "keep proof goal solved"
  (get (first mod-aud-keep-goals) :solved)
  true)

;; ── evidence accumulation drives a rule ──

(define
  mod-aud-rev-r
  (mod/attach-evidence
    (mod/mk-report "re" "a" "carol" "neutral")
    (mod/mk-evidence "confirmed-abuse" "human")))
(define mod-aud-rev (mod-aud-decide1 mod-aud-rev-r))

(mod-aud-test!
  "evidence has length 1"
  (len (mod/report-evidence mod-aud-rev-r))
  1)
(mod-aud-test!
  "evidence reviewer-remove → remove"
  (get mod-aud-rev :action)
  "remove")
(mod-aud-test!
  "evidence reviewer-remove rule"
  (get mod-aud-rev :rule)
  "reviewer-remove")
(mod-aud-test!
  "evidence proof goal solved"
  (get (first (get (get mod-aud-rev :proof) :goals)) :solved)
  true)
(mod-aud-test!
  "no evidence → not reviewer-remove"
  (get (mod-aud-decide1 (mod/mk-report "rn" "a" "b" "neutral")) :rule)
  "default-keep")

;; ── append-only audit log via the api ──

(mod/reset!)
(mod/report "alice" "bob" "this is spam")
(mod/report "carol" "eve" "fine post")
(define mod-aud-d1 (mod/decide "r1"))
(define mod-aud-d2 (mod/decide "r2"))

(mod-aud-test! "two decisions logged" (mod/audit-count) 2)
(mod-aud-test!
  "first entry seq 1"
  (get (first (mod/audit-all)) :seq)
  1)
(mod-aud-test!
  "audit r1 returns one entry"
  (len (mod/audit "r1"))
  1)
(mod-aud-test!
  "audit r1 action matches decision"
  (get (first (mod/audit "r1")) :action)
  (get mod-aud-d1 :action))
(mod-aud-test!
  "audit r1 rule matches decision"
  (get (first (mod/audit "r1")) :rule)
  "spam-hide")
(mod-aud-test!
  "audit r1 entry carries proof goals"
  (len (get (get (first (mod/audit "r1")) :proof) :goals))
  1)
(mod-aud-test!
  "audit r2 keep"
  (get (first (mod/audit "r2")) :action)
  "keep")
(mod-aud-test! "audit unknown report → empty" (mod/audit "r99") (list))

;; ── append-only: re-deciding appends, never mutates ──

(define mod-aud-d1b (mod/decide "r1"))

(mod-aud-test! "re-decide appends (count 3)" (mod/audit-count) 3)
(mod-aud-test!
  "audit r1 now has 2 entries"
  (len (mod/audit "r1"))
  2)
(mod-aud-test!
  "audit r1 seqs monotonic"
  (get (nth (mod/audit "r1") 1) :seq)
  3)
(mod-aud-test!
  "audit-latest r1 is seq 3"
  (get (mod/audit-latest "r1") :seq)
  3)
(mod-aud-test!
  "first r1 entry unchanged (still seq 1)"
  (get (first (mod/audit "r1")) :seq)
  1)

;; ── evidence snapshot captured at decision time ──

(mod/add-evidence "r2" "confirmed-abuse" "human")
(define mod-aud-d2b (mod/decide "r2"))

(mod-aud-test!
  "post-evidence decision flips to remove"
  (get mod-aud-d2b :action)
  "remove")
(mod-aud-test!
  "audit snapshot records evidence kind"
  (mod/evidence-kind (first (get (mod/audit-latest "r2") :evidence)))
  "confirmed-abuse")
(mod-aud-test!
  "earlier r2 entry had empty evidence snapshot"
  (len (get (first (mod/audit "r2")) :evidence))
  0)

(define mod-audit-tests-run! (fn () {:failures mod-aud-failures :total mod-aud-count :passed mod-aud-pass :failed mod-aud-fail}))
