mod: Ext 1 — negation-as-failure conditions (:not / :attr), 146/146
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s

Report attributes (:attrs) project to attr(Id, name) facts; policy gains (:attr x)
and (:not <cond>) conditions. The Prolog substrate exposes negation as a functor
not(Goal) (the prefix \+ operator doesn't parse here). Closed-world example:
hide spam unless author verified. Default policy untouched — feature proven via
custom rule sets, so all 132 base tests stay green. +14 extension tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 17:59:01 +00:00
parent ee9851c063
commit 2ea87796a1
7 changed files with 218 additions and 15 deletions

153
lib/mod/tests/extensions.sx Normal file
View File

@@ -0,0 +1,153 @@
;; lib/mod/tests/extensions.sx — beyond-roadmap extensions.
;;
;; Ext 1: negation-as-failure conditions (:not / :attr) + report attributes.
;; These exercise closed-world reasoning: "hide spam UNLESS the author is
;; verified". Demonstrated with custom rule sets so the default policy (and its
;; 132 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)))))))
;; ── 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)
;; ── 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")
;; ── 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)
;; ── 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))")
;; ── 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")
(define mod-extensions-tests-run! (fn () {:failures mod-ext-failures :total mod-ext-count :passed mod-ext-pass :failed mod-ext-fail}))