Model rules: who a chat's model may bring into a conversation
Rules read from the main model decide who it is offered as a crowd member, a friend and on its roster; any-to-any with denies by default, or none-to-none with allows. The crowd picker names what it holds back and why, and a model held back only by a person's own rule -- or by anything, with the new rules.override -- can still be added by hand. Another data group is now a deny that an explicit rule opens. Admin -> Model rules and a card in Settings, each with a matrix drawn by the enforcing function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"""`talk.decide`, the one function behind every talk rule, as a table.
|
||||
|
||||
Pure -- plain values in, a verdict out -- so every combination is asserted here
|
||||
with no database, and the admin matrix, which calls the same function, cannot
|
||||
disagree with what is enforced.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from lembas.services import talk
|
||||
from lembas.services.talk import Rule, decide, match
|
||||
|
||||
ALLOW = Rule("main", "target", "allow")
|
||||
DENY = Rule("main", "target", "deny")
|
||||
|
||||
|
||||
def _v(**kwargs):
|
||||
kwargs.setdefault("instance_mode", talk.MODE_OPEN)
|
||||
kwargs.setdefault("instance_rule", None)
|
||||
return decide(**kwargs)
|
||||
|
||||
|
||||
# --- The instance's layer ------------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
("mode", "rule", "offered"),
|
||||
[
|
||||
(talk.MODE_OPEN, None, True),
|
||||
(talk.MODE_OPEN, DENY, False),
|
||||
(talk.MODE_OPEN, ALLOW, True),
|
||||
(talk.MODE_CLOSED, None, False),
|
||||
(talk.MODE_CLOSED, ALLOW, True),
|
||||
(talk.MODE_CLOSED, DENY, False),
|
||||
],
|
||||
)
|
||||
def test_the_instance_mode_and_its_rules(mode, rule, offered):
|
||||
verdict = _v(instance_mode=mode, instance_rule=rule)
|
||||
assert verdict.offered is offered
|
||||
assert verdict.addable is offered
|
||||
|
||||
|
||||
def test_another_data_group_is_a_deny_only_an_explicit_allow_opens():
|
||||
assert _v(same_group=False).offered is False
|
||||
assert _v(same_group=False).why == talk.WHY_GROUP
|
||||
assert _v(same_group=False, instance_rule=ALLOW).offered is True
|
||||
# An open mode is not an explicit allow.
|
||||
assert _v(same_group=False, instance_mode=talk.MODE_OPEN).offered is False
|
||||
|
||||
|
||||
# --- A person without the override can only narrow ---------------------------------
|
||||
def test_a_persons_own_deny_takes_it_off_what_is_offered_but_not_off_what_is_addable():
|
||||
verdict = _v(user_rule=DENY)
|
||||
assert verdict.offered is False
|
||||
assert verdict.addable is True
|
||||
assert verdict.why == talk.WHY_YOUR_RULE
|
||||
|
||||
|
||||
def test_a_persons_closed_mode_narrows_too():
|
||||
verdict = _v(user_mode=talk.MODE_CLOSED)
|
||||
assert (verdict.offered, verdict.addable, verdict.why) == (False, True, talk.WHY_YOUR_CLOSED)
|
||||
|
||||
|
||||
def test_without_the_override_a_persons_allow_cannot_widen():
|
||||
verdict = _v(instance_rule=DENY, user_rule=ALLOW)
|
||||
assert (verdict.offered, verdict.addable) == (False, False)
|
||||
assert verdict.why == talk.WHY_INSTANCE_RULE
|
||||
|
||||
|
||||
def test_without_the_override_open_mode_cannot_widen_a_closed_instance():
|
||||
verdict = _v(instance_mode=talk.MODE_CLOSED, user_mode=talk.MODE_OPEN)
|
||||
assert (verdict.offered, verdict.addable) == (False, False)
|
||||
|
||||
|
||||
# --- With the override, the person's layer wins ---------------------------------------
|
||||
def test_with_the_override_a_persons_allow_beats_the_instances_deny():
|
||||
verdict = _v(instance_rule=DENY, user_rule=ALLOW, override=True)
|
||||
assert (verdict.offered, verdict.addable) == (True, True)
|
||||
|
||||
|
||||
def test_with_the_override_a_persons_rule_opens_another_group():
|
||||
assert _v(same_group=False, user_rule=ALLOW, override=True).offered is True
|
||||
|
||||
|
||||
def test_with_the_override_open_mode_widens_a_closed_instance_but_not_across_groups():
|
||||
assert _v(instance_mode=talk.MODE_CLOSED, user_mode=talk.MODE_OPEN, override=True).offered
|
||||
across = _v(same_group=False, user_mode=talk.MODE_OPEN, override=True)
|
||||
assert across.offered is False and across.why == talk.WHY_GROUP
|
||||
# ...unless some rule explicitly allows it.
|
||||
assert _v(
|
||||
same_group=False, user_mode=talk.MODE_OPEN, override=True, instance_rule=ALLOW
|
||||
).offered
|
||||
|
||||
|
||||
def test_with_the_override_and_no_layer_of_their_own_the_instance_decides():
|
||||
assert _v(instance_rule=DENY, override=True).offered is False
|
||||
|
||||
|
||||
def test_with_the_override_anything_may_be_added_by_hand():
|
||||
assert _v(instance_rule=DENY, override=True).addable is True
|
||||
assert _v(same_group=False, override=True).addable is True
|
||||
|
||||
|
||||
# --- Specificity -------------------------------------------------------------------------
|
||||
def test_the_most_specific_rule_wins():
|
||||
rules = [
|
||||
Rule("*", "*", "deny"),
|
||||
Rule("*", "b", "allow"),
|
||||
Rule("a", "*", "deny"),
|
||||
Rule("a", "b", "allow"),
|
||||
]
|
||||
assert match(rules, "a", "b").effect == "allow"
|
||||
assert match(rules, "a", "c") == Rule("a", "*", "deny")
|
||||
assert match(rules, "x", "b") == Rule("*", "b", "allow")
|
||||
assert match(rules, "x", "y") == Rule("*", "*", "deny")
|
||||
assert match([], "a", "b") is None
|
||||
|
||||
|
||||
def test_a_verdict_says_why_in_english_for_a_model():
|
||||
assert "forbids" in _v(instance_rule=DENY).reason
|
||||
assert _v().reason == ""
|
||||
Reference in New Issue
Block a user