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:
2026-09-29 17:20:40 +00:00
co-authored by Claude Opus 5.5
parent 9970bb43c6
commit c27fe47d4d
24 changed files with 1324 additions and 29 deletions
+6
View File
@@ -144,7 +144,11 @@ def test_a_chat_whose_model_moved_is_refused_rather_than_sent(db, owner, setup):
# --- Other models reaching the conversation ------------------------------------------
def test_a_crowd_member_from_another_group_is_refused(client, db, owner, setup):
"""For somebody without `rules.override`: a different group is a deny only a
rule opens. (An administrator holds every permission, so the owner is demoted.)"""
settings_store.update(db, {"enabled": True}, key=settings_store.CROWD)
owner.role = "user"
db.commit()
local, _ = setup
chat = _chat(db, owner, connection=local)
client.patch(f"/api/chats/{chat.id}", data={"crowd_model_ids": ["local-b", "cloud-model"]})
@@ -153,6 +157,8 @@ def test_a_crowd_member_from_another_group_is_refused(client, db, owner, setup):
def test_a_crowd_member_that_left_the_group_does_not_speak(db, owner, setup):
owner.role = "user"
db.commit()
local, cloud = setup
chat = _chat(db, owner, connection=local)
db.add(CrowdMember(chat_id=chat.id, model_id="cloud-model", connection_id=cloud.id))