;; lib/mod/tests/extensions.sx — beyond-roadmap extensions. ;; ;; Ext 1: negation-as-failure conditions (:not / :attr) + report attributes. ;; "hide spam UNLESS the author is verified" (closed-world reasoning). ;; Ext 2: weighted/aggregate evidence scoring (:score-at-least) + report signals. ;; Many low-confidence signals accumulate past a threshold via Prolog ;; aggregate_all(sum(W), ...). ;; Demonstrated with custom rule sets so the default policy (and its conformance ;; tests) stays untouched. (define mod-ext-count 0) (define mod-ext-pass 0) (define mod-ext-fail 0) (define mod-ext-failures (list)) (define mod-ext-test! (fn (name got expected) (begin (set! mod-ext-count (+ mod-ext-count 1)) (if (= got expected) (set! mod-ext-pass (+ mod-ext-pass 1)) (begin (set! mod-ext-fail (+ mod-ext-fail 1)) (append! mod-ext-failures (str name "\n expected: " expected "\n got: " got))))))) ;; ── Ext 1: report attributes ── (define mod-ext-r0 (mod/mk-report "r1" "a" "b" "this is spam")) (mod-ext-test! "fresh report has no attrs" (len (mod/report-attrs mod-ext-r0)) 0) (define mod-ext-rv (mod/attach-attr mod-ext-r0 "verified")) (mod-ext-test! "attach-attr adds one attr" (len (mod/report-attrs mod-ext-rv)) 1) (mod-ext-test! "attach-attr preserves evidence field" (len (mod/report-evidence (mod/attach-evidence mod-ext-rv (mod/mk-evidence "x" "y")))) 1) (mod-ext-test! "attach-evidence preserves attrs" (len (mod/report-attrs (mod/attach-evidence mod-ext-rv (mod/mk-evidence "x" "y")))) 1) ;; ── Ext 1: negation-as-failure: spam hidden unless author verified ── (define mod-ext-rules (list (mod/mk-rule "spam-unverified-hide" :hide (list (list :classification "spam") (list :not (list :attr "verified")))) (mod/mk-rule "default-keep" :keep (list)))) (define mod-ext-spam-plain (mod/mk-report "p1" "a" "b" "this is spam")) (define mod-ext-spam-verified (mod/attach-attr (mod/mk-report "p2" "a" "b" "this is spam") "verified")) (define mod-ext-clean (mod/mk-report "p3" "a" "b" "a fine post")) (mod-ext-test! "unverified spam → hide" (get (mod/decide-report mod-ext-spam-plain (list mod-ext-spam-plain) mod-ext-rules) :action) "hide") (mod-ext-test! "verified author spam → keep (negation blocks)" (get (mod/decide-report mod-ext-spam-verified (list mod-ext-spam-verified) mod-ext-rules) :action) "keep") (mod-ext-test! "clean post → keep" (get (mod/decide-report mod-ext-clean (list mod-ext-clean) mod-ext-rules) :action) "keep") ;; ── Ext 1: negation appears in the goal text + proof ── (define mod-ext-dec (mod/decide-report mod-ext-spam-plain (list mod-ext-spam-plain) mod-ext-rules)) (define mod-ext-goals (get (get mod-ext-dec :proof) :goals)) (mod-ext-test! "rule that matched is spam-unverified-hide" (get mod-ext-dec :rule) "spam-unverified-hide") (mod-ext-test! "proof has two goals" (len mod-ext-goals) 2) (mod-ext-test! "negation goal text" (get (nth mod-ext-goals 1) :goal) "not(attr(p1, verified))") (mod-ext-test! "negation goal solved for unverified" (get (nth mod-ext-goals 1) :solved) true) ;; ── Ext 1: cond->goal compiles :attr and :not directly ── (mod-ext-test! "cond->goal :attr" (mod/cond->goal (list :attr "verified") "Id") "attr(Id, verified)") (mod-ext-test! "cond->goal :not wraps inner" (mod/cond->goal (list :not (list :classification "spam")) "Id") "not(classification(Id, spam))") ;; ── Ext 1: positive :attr condition (allowlist-style) ── (define mod-ext-allow-rules (list (mod/mk-rule "trusted-keep" :keep (list (list :attr "trusted"))) (mod/mk-rule "spam-hide" :hide (list (list :classification "spam"))) (mod/mk-rule "default-keep" :keep (list)))) (define mod-ext-trusted-spam (mod/attach-attr (mod/mk-report "t1" "a" "b" "this is spam") "trusted")) (mod-ext-test! "trusted attr exempts spam → keep" (get (mod/decide-report mod-ext-trusted-spam (list mod-ext-trusted-spam) mod-ext-allow-rules) :action) "keep") ;; ── Ext 2: weighted signals + aggregate scoring ── (define mod-ext-s0 (mod/mk-report "s1" "a" "b" "neutral")) (mod-ext-test! "fresh report has no signals" (len (mod/report-signals mod-ext-s0)) 0) (define mod-ext-s1 (mod/attach-signal mod-ext-s0 (mod/mk-signal "link" 2))) (mod-ext-test! "attach-signal adds one" (len (mod/report-signals mod-ext-s1)) 1) (mod-ext-test! "attach-signal preserves attrs" (len (mod/report-attrs (mod/attach-signal mod-ext-rv (mod/mk-signal "x" 1)))) 1) (define mod-ext-score-rules (list (mod/mk-rule "high-score-hide" :hide (list (list :score-at-least 5))) (mod/mk-rule "default-keep" :keep (list)))) ;; one weak signal (2) — below threshold (define mod-ext-weak (mod/attach-signal (mod/mk-report "w1" "a" "b" "neutral") (mod/mk-signal "link" 2))) (mod-ext-test! "single weak signal → keep (below threshold)" (get (mod/decide-report mod-ext-weak (list mod-ext-weak) mod-ext-score-rules) :action) "keep") ;; three signals summing to 6 — over threshold (define mod-ext-strong0 (mod/attach-signal (mod/mk-report "w2" "a" "b" "neutral") (mod/mk-signal "link" 2))) (define mod-ext-strong1 (mod/attach-signal mod-ext-strong0 (mod/mk-signal "newaccount" 2))) (define mod-ext-strong (mod/attach-signal mod-ext-strong1 (mod/mk-signal "burst" 2))) (mod-ext-test! "accumulated signals (2+2+2=6) → hide" (get (mod/decide-report mod-ext-strong (list mod-ext-strong) mod-ext-score-rules) :action) "hide") (mod-ext-test! "scoring rule named in decision" (get (mod/decide-report mod-ext-strong (list mod-ext-strong) mod-ext-score-rules) :rule) "high-score-hide") ;; exactly at threshold (5) fires (define mod-ext-exact0 (mod/attach-signal (mod/mk-report "w3" "a" "b" "neutral") (mod/mk-signal "link" 3))) (define mod-ext-exact (mod/attach-signal mod-ext-exact0 (mod/mk-signal "burst" 2))) (mod-ext-test! "exactly at threshold (5) → hide" (get (mod/decide-report mod-ext-exact (list mod-ext-exact) mod-ext-score-rules) :action) "hide") (mod-ext-test! "cond->goal :score-at-least" (mod/cond->goal (list :score-at-least 5) "Id") "aggregate_all(sum(W), signal(Id, _, W), T), T >= 5") (define mod-extensions-tests-run! (fn () {:failures mod-ext-failures :total mod-ext-count :passed mod-ext-pass :failed mod-ext-fail}))