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>
131 lines
6.5 KiB
Markdown
131 lines
6.5 KiB
Markdown
# mod-on-sx: Moderation on Prolog
|
|
|
|
rose-ash needs moderation infrastructure: reports flagged by users, automated
|
|
classifications (spam, abuse), tiered escalation (auto → human → appeal), audit
|
|
trails. Each decision is the conclusion of a backtracking search over evidence and
|
|
policy rules — exactly what Prolog does.
|
|
|
|
Where acl-sx says "may this happen?", mod-sx says "should this stay?" The former is
|
|
a positive decision (proof of grant); the latter often a negative one (proof of
|
|
violation), and policy chains naturally backtrack: if the first rule doesn't apply,
|
|
try the next.
|
|
|
|
End-state: a Prolog-on-SX layer for moderation policy declaration and evaluation,
|
|
with persistent report lifecycle, audit log, escalation state machine, and
|
|
federation extension.
|
|
|
|
## Status (rolling)
|
|
|
|
`bash lib/mod/conformance.sh` → **31/31** (Phase 1 complete)
|
|
|
|
## Ground rules
|
|
|
|
- **Scope:** only touch `lib/mod/**` and `plans/mod-on-sx.md`. Do **not** edit
|
|
`spec/`, `hosts/`, `shared/`, `lib/prolog/**`, or other `lib/<lang>/`. You may
|
|
**import** from `lib/prolog/` (public API in `lib/prolog/prolog.sx`); do **not**
|
|
modify Prolog.
|
|
- **Shared-file issues** go under "Blockers" with a minimal repro; do not fix here.
|
|
- **SX files:** use `sx-tree` MCP tools only.
|
|
- **Architecture:** policies are Prolog rules over `report(...)` and `evidence(...)`
|
|
facts. Decisions are query results. Proof trees become audit records. The state
|
|
machine for report lifecycle is separate (an SX module on top).
|
|
- **Shared with acl-sx:** rule-engine plumbing may be liftable into `lib/guest/`.
|
|
Watch for it; flag in Progress log but do not extract until both subsystems are
|
|
past Phase 2.
|
|
- **Commits:** one feature per commit. Keep Progress log updated and tick boxes.
|
|
|
|
## Architecture sketch
|
|
|
|
```
|
|
Report Decision
|
|
{:by :about :reason :at} {:action :proof :next-state}
|
|
│ ▲
|
|
▼ │
|
|
lib/mod/schema.sx lib/mod/engine.sx
|
|
— report/4, evidence/2, — query Prolog with report fact
|
|
classification/3 predicates — extract proof tree
|
|
│ ▲
|
|
▼ │
|
|
lib/mod/policy.sx lib/mod/lifecycle.sx
|
|
— rule syntax → Prolog — state machine
|
|
— action heads: — open → triaged → decided
|
|
{:keep :hide :remove — appeal handling
|
|
:escalate :ban} │
|
|
│ ▼
|
|
▼ lib/mod/audit.sx
|
|
lib/mod/api.sx — append-only decision log
|
|
— (mod/report ...) — proof tree persistence
|
|
— (mod/decide report) — query API
|
|
— (mod/appeal id)
|
|
│
|
|
▼
|
|
lib/mod/fed.sx
|
|
— cross-instance reports via fed-sx
|
|
— decision sharing / trust model
|
|
```
|
|
|
|
## Phase 1 — Report representation + simple policy
|
|
|
|
- [x] `lib/mod/schema.sx` — `report(id, by, about)`, `classification(id, kind)`,
|
|
`report_count(subject, n)` Prolog facts; keyword classifier derives evidence
|
|
- [x] `lib/mod/policy.sx` — `mod/mk-rule` + ordered `mod/default-rules`; conditions
|
|
(`:classification`, `:count-at-least`) compile to Prolog goals; `policy_action/3`
|
|
clauses, last clause `true` so every report yields at least `:keep`
|
|
- [x] `lib/mod/engine.sx` — `(mod/decide-report r reports rules)` queries
|
|
`policy_action(Id, Action, Rule)` with `pl-query-one` (clause order = precedence);
|
|
returns a decision dict `{:action :rule :report-id :proof}` carrying the why
|
|
- [x] `lib/mod/api.sx` — registry + `(mod/report by about reason)`, `(mod/decide id)`
|
|
- [x] `lib/mod/tests/decide.sx` — 31 cases: spam/abuse keyword, repeated→escalate,
|
|
no-rule→keep, precedence (spam beats repeated), proof shape, registry ids
|
|
- [x] `lib/mod/scoreboard.{json,md}`
|
|
- [x] `lib/mod/conformance.sh`
|
|
|
|
## Phase 2 — Evidence + audit trail
|
|
|
|
- [ ] evidence accumulation — additional facts asserted before query
|
|
- [ ] proof tree from Prolog derivation tree
|
|
- [ ] `lib/mod/audit.sx` — append-only log (decision + proof + evidence snapshot)
|
|
- [ ] `(mod/audit id)` retrieval
|
|
- [ ] `lib/mod/tests/audit.sx` — proof correctness, trail completeness
|
|
|
|
## Phase 3 — Escalation + lifecycle state machine
|
|
|
|
- [ ] state machine: `:open → :triaged → :decided → :appealed → :final`
|
|
- [ ] auto-tier: first-pass rules decide quick cases
|
|
- [ ] human-tier: rules that emit `:escalate` move to next state
|
|
- [ ] appeal: re-runs with appeal evidence, may override prior decision
|
|
- [ ] `(mod/appeal id new-evidence)` API
|
|
- [ ] `lib/mod/tests/escalation.sx` — full lifecycle traversal cases
|
|
|
|
## Phase 4 — Federation
|
|
|
|
- [ ] cross-instance reports — peer raises report about local content (or vice versa)
|
|
- [ ] decision sharing — actions taken locally propagate to peers via fed-sx
|
|
- [ ] trust model — peer's decision is advisory unless `(trust peer :mod)` is granted
|
|
- [ ] revocation — undo applied moderation if proof was invalidated
|
|
- [ ] `lib/mod/tests/fed.sx` — federated decision chains (mock fed-sx in tests)
|
|
|
|
## Progress log
|
|
|
|
- **Phase 1 complete — 31/31.** Report schema, keyword classifier, policy DSL,
|
|
engine, registry api, conformance harness. Decisions are proofs: each carries
|
|
`:rule` (matching clause), `:proof {:rule :conditions :evidence :count}`.
|
|
Precedence is Prolog clause order resolved by `pl-query-one`; a trailing
|
|
`true`-bodied default rule makes "no rule matched" a real `:keep`, not a query
|
|
failure. Evidence (spam/abuse classification) derived in SX and asserted as
|
|
`classification/2` facts; repeated-report escalation uses a genuine Prolog
|
|
join + arithmetic (`report(Id,_,S), report_count(S,N), N >= 3`).
|
|
- **Gotcha (env):** loading the prolog libs strips `includes?` (and other
|
|
high-level string prims) from the eval env — only the set the prolog
|
|
tokenizer itself uses survives (`slice`, `len`, `nth`, `=`, `join`,
|
|
`downcase`, `map`, `reduce`, `append!`). Implemented `mod/str-contains?` over
|
|
`slice`/`len` rather than relying on `includes?`. Watch for this in later
|
|
phases — stick to the blessed primitive set.
|
|
- **Liftable (acl-sx watch):** `mod/join-with`, `mod/str-contains?`, `mod/any?`,
|
|
and the rule→clause compilation shape are generic rule-engine plumbing. Do not
|
|
extract to `lib/guest/` until both mod-sx and acl-sx are past Phase 2.
|
|
|
|
## Blockers
|
|
|
|
(none)
|