mod: Phase 1 — report schema + policy engine on Prolog, 31/31
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m5s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m5s
Reports → Prolog facts (report/3, classification/2, report_count/2); ordered policy rules compile to policy_action/3 clauses, first match wins via pl-query-one. Decisions carry their proof (matching rule + conditions + evidence). Spam/abuse keyword classification, repeated-report escalation via Prolog join+arithmetic, no-rule→keep default. Registry api + conformance harness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
215
lib/mod/tests/decide.sx
Normal file
215
lib/mod/tests/decide.sx
Normal file
@@ -0,0 +1,215 @@
|
||||
;; lib/mod/tests/decide.sx — Phase 1: report representation + simple policy.
|
||||
|
||||
(define mod-dec-count 0)
|
||||
(define mod-dec-pass 0)
|
||||
(define mod-dec-fail 0)
|
||||
(define mod-dec-failures (list))
|
||||
|
||||
(define
|
||||
mod-dec-test!
|
||||
(fn
|
||||
(name got expected)
|
||||
(begin
|
||||
(set! mod-dec-count (+ mod-dec-count 1))
|
||||
(if
|
||||
(= got expected)
|
||||
(set! mod-dec-pass (+ mod-dec-pass 1))
|
||||
(begin
|
||||
(set! mod-dec-fail (+ mod-dec-fail 1))
|
||||
(append!
|
||||
mod-dec-failures
|
||||
(str name "\n expected: " expected "\n got: " got)))))))
|
||||
|
||||
;; decide a single report (count over a 1-element registry)
|
||||
(define
|
||||
mod-dec-one
|
||||
(fn
|
||||
(reason)
|
||||
(let
|
||||
((r (mod/mk-report "r1" "alice" "bob" reason)))
|
||||
(mod/decide-report r (list r) mod/default-rules))))
|
||||
|
||||
(define mod-dec-action (fn (reason) (get (mod-dec-one reason) :action)))
|
||||
|
||||
;; ── spam keyword → :hide ──
|
||||
|
||||
(mod-dec-test!
|
||||
"spam keyword 'spam' → hide"
|
||||
(mod-dec-action "this is spam")
|
||||
"hide")
|
||||
(mod-dec-test!
|
||||
"spam keyword 'buy now' → hide"
|
||||
(mod-dec-action "buy now while stocks last")
|
||||
"hide")
|
||||
(mod-dec-test!
|
||||
"spam keyword case-insensitive 'CLICK HERE' → hide"
|
||||
(mod-dec-action "CLICK HERE now")
|
||||
"hide")
|
||||
(mod-dec-test!
|
||||
"spam keyword 'free money' → hide"
|
||||
(mod-dec-action "win free money fast")
|
||||
"hide")
|
||||
|
||||
;; ── abuse keyword → :remove ──
|
||||
|
||||
(mod-dec-test!
|
||||
"abuse keyword 'harassment' → remove"
|
||||
(mod-dec-action "ongoing harassment of users")
|
||||
"remove")
|
||||
(mod-dec-test!
|
||||
"abuse keyword 'threat' → remove"
|
||||
(mod-dec-action "this is a threat")
|
||||
"remove")
|
||||
(mod-dec-test!
|
||||
"abuse keyword 'slur' → remove"
|
||||
(mod-dec-action "contains a slur")
|
||||
"remove")
|
||||
|
||||
;; ── no rule → :keep ──
|
||||
|
||||
(mod-dec-test!
|
||||
"neutral reason → keep"
|
||||
(mod-dec-action "I disagree with this post")
|
||||
"keep")
|
||||
(mod-dec-test! "empty reason → keep" (mod-dec-action "") "keep")
|
||||
|
||||
;; ── decision carries the matching rule (proof, not bare keyword) ──
|
||||
|
||||
(mod-dec-test!
|
||||
"spam decision rule name"
|
||||
(get (mod-dec-one "this is spam") :rule)
|
||||
"spam-hide")
|
||||
(mod-dec-test!
|
||||
"keep decision rule name"
|
||||
(get (mod-dec-one "fine post") :rule)
|
||||
"default-keep")
|
||||
(mod-dec-test!
|
||||
"abuse decision rule name"
|
||||
(get (mod-dec-one "harassment here") :rule)
|
||||
"abuse-remove")
|
||||
(mod-dec-test!
|
||||
"spam proof :rule"
|
||||
(get (get (mod-dec-one "spam!") :proof) :rule)
|
||||
"spam-hide")
|
||||
(mod-dec-test!
|
||||
"spam proof :evidence"
|
||||
(get (get (mod-dec-one "spam!") :proof) :evidence)
|
||||
(list "spam"))
|
||||
(mod-dec-test!
|
||||
"spam proof :count"
|
||||
(get (get (mod-dec-one "spam!") :proof) :count)
|
||||
1)
|
||||
|
||||
;; ── classification (evidence derivation) ──
|
||||
|
||||
(mod-dec-test!
|
||||
"classify spam"
|
||||
(mod/classify-keywords (mod/mk-report "r1" "a" "b" "spam!"))
|
||||
(list "spam"))
|
||||
(mod-dec-test!
|
||||
"classify abuse"
|
||||
(mod/classify-keywords (mod/mk-report "r1" "a" "b" "abuse"))
|
||||
(list "abuse"))
|
||||
(mod-dec-test!
|
||||
"classify neutral → empty"
|
||||
(mod/classify-keywords (mod/mk-report "r1" "a" "b" "hello"))
|
||||
(list))
|
||||
(mod-dec-test!
|
||||
"classify both spam+abuse"
|
||||
(mod/classify-keywords (mod/mk-report "r1" "a" "b" "spam and abuse"))
|
||||
(list "spam" "abuse"))
|
||||
|
||||
;; ── report-count + repeated → :escalate ──
|
||||
|
||||
(define
|
||||
mod-dec-three
|
||||
(list
|
||||
(mod/mk-report "r1" "a" "bob" "x")
|
||||
(mod/mk-report "r2" "c" "bob" "y")
|
||||
(mod/mk-report "r3" "d" "bob" "z")))
|
||||
|
||||
(mod-dec-test!
|
||||
"report-count counts subject"
|
||||
(mod/report-count "bob" mod-dec-three)
|
||||
3)
|
||||
(mod-dec-test!
|
||||
"3 reports about subject → escalate"
|
||||
(get
|
||||
(mod/decide-report (first mod-dec-three) mod-dec-three mod/default-rules)
|
||||
:action)
|
||||
"escalate")
|
||||
(mod-dec-test!
|
||||
"escalate rule name"
|
||||
(get
|
||||
(mod/decide-report (first mod-dec-three) mod-dec-three mod/default-rules)
|
||||
:rule)
|
||||
"repeated-escalate")
|
||||
|
||||
(define
|
||||
mod-dec-two
|
||||
(list
|
||||
(mod/mk-report "r1" "a" "carol" "x")
|
||||
(mod/mk-report "r2" "c" "carol" "y")))
|
||||
|
||||
(mod-dec-test!
|
||||
"2 reports about subject → keep (below threshold)"
|
||||
(get
|
||||
(mod/decide-report (first mod-dec-two) mod-dec-two mod/default-rules)
|
||||
:action)
|
||||
"keep")
|
||||
|
||||
;; ── precedence: spam beats repeated ──
|
||||
|
||||
(define
|
||||
mod-dec-spam-among-many
|
||||
(list
|
||||
(mod/mk-report "r1" "a" "dave" "buy now spam")
|
||||
(mod/mk-report "r2" "c" "dave" "y")
|
||||
(mod/mk-report "r3" "d" "dave" "z")))
|
||||
|
||||
(mod-dec-test!
|
||||
"spam wins over repeated (precedence)"
|
||||
(get
|
||||
(mod/decide-report
|
||||
(first mod-dec-spam-among-many)
|
||||
mod-dec-spam-among-many
|
||||
mod/default-rules)
|
||||
:action)
|
||||
"hide")
|
||||
|
||||
;; ── accessors ──
|
||||
|
||||
(mod-dec-test!
|
||||
"report-about accessor"
|
||||
(mod/report-about (mod/mk-report "r1" "a" "bob" "x"))
|
||||
"bob")
|
||||
(mod-dec-test!
|
||||
"report-by accessor"
|
||||
(mod/report-by (mod/mk-report "r1" "alice" "bob" "x"))
|
||||
"alice")
|
||||
|
||||
;; ── api registry ──
|
||||
|
||||
(mod/reset!)
|
||||
(define mod-dec-r1 (mod/report "alice" "bob" "this is spam"))
|
||||
(define mod-dec-r2 (mod/report "carol" "eve" "fine post"))
|
||||
|
||||
(mod-dec-test!
|
||||
"mod/report assigns sequential id r1"
|
||||
(mod/report-id mod-dec-r1)
|
||||
"r1")
|
||||
(mod-dec-test!
|
||||
"mod/report assigns sequential id r2"
|
||||
(mod/report-id mod-dec-r2)
|
||||
"r2")
|
||||
(mod-dec-test!
|
||||
"mod/decide via registry → hide"
|
||||
(get (mod/decide "r1") :action)
|
||||
"hide")
|
||||
(mod-dec-test!
|
||||
"mod/decide via registry → keep"
|
||||
(get (mod/decide "r2") :action)
|
||||
"keep")
|
||||
(mod-dec-test! "mod/decide unknown id → nil" (mod/decide "r99") nil)
|
||||
|
||||
(define mod-decide-tests-run! (fn () {:failures mod-dec-failures :total mod-dec-count :passed mod-dec-pass :failed mod-dec-fail}))
|
||||
Reference in New Issue
Block a user