Skip to content

Commit fcdd0e2

Browse files
tmoostingclaude
andcommitted
0.64.0: is_to_everyone, the one call that tells a message to everyone from a malformed recipients list
Asked by the Deck's reader. Red on a mutant that read a list of no valid id as everyone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent cdd9eda commit fcdd0e2

5 files changed

Lines changed: 23 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ The package version runs on its own semver; the schema it is generated from is `
44

55
## Unreleased
66

7+
## 0.64.0 (2026-10-26)
8+
9+
- **`is_to_everyone(message)`**: true when a message is addressed to every member (its `characters` absent, `None` or empty). A list that names no valid id, or a value that is not a list, is addressed to nobody and False here, though `recipients_of` gives `[]` for both; this is the one call that tells them apart.
10+
711
## 0.63.0 (2026-10-26)
812

913
- **The message profile's readers.** A message between a world's members is a Narrative with supertype `Message`, from its `narrator` to its `characters`. `is_message(element)` reads the pair (`MESSAGE_PAIR`), trimmed and folded as `is_room_element` folds the room's pairs; a parlor's line and a world's own message are both messages. `recipients_of(message)` lists the `characters`, and `is_addressed_to(message, character_id)` is true when the id is among them or the list is empty, absent or `None`, which addresses everyone; a list that names no valid id, or a value that is not a list, addresses nobody, never everyone; nothing else addresses a message, not the `narrator`, not a name in the `story`, not `protagonist`. `is_sent_by(message, character_id)` reads the `narrator`, the claimed sender. `sender_status(message, members)` checks the claim against who wrote the element: its `created_by` on the world's members list (`None` is the owner), and answers a `SenderRead` with `status` `verified`, `mismatch` (with `written_by`, the author's Character) or `unverifiable` (no `narrator`, no `created_by` at all, no members list, the author not on it or playing no Character), and `claimed`, the `narrator`. `read_mark_of(relations, character_id, members)` finds a reader's read mark, a Relation with supertype `Read` (`READ_MARK_PAIR`) whose `actor` is the reader: only a mark written by the reader's own hand counts (a relation with no `created_by` at all, from a folder or an export, has no hand to check and counts), the newest `updated_at` when several do. `unread_for(messages, character_id, read_mark)` is the messages addressed to the reader, not sent by it and not on its mark. `thread_of(messages, root_id)`, which the profile's page does not list, is every message whose chain of `parent_narrative` reaches the root, the root included, a loop of parents ending. Every list comes back in `created_at` order, two equal times keeping the order given, never by id. The profile's fixture (43 cases) runs as a test.

‎src/onlyworlds/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
is_addressed_to,
5858
is_message,
5959
is_sent_by,
60+
is_to_everyone,
6061
read_mark_of,
6162
recipients_of,
6263
sender_status,
@@ -129,6 +130,7 @@
129130
"is_message",
130131
"is_room_element",
131132
"is_sent_by",
133+
"is_to_everyone",
132134
"parse_retry_after",
133135
"plan_push",
134136
"push",

‎src/onlyworlds/_version.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.63.0"
1+
__version__ = "0.64.0"

‎src/onlyworlds/message.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,20 @@ def is_addressed_to(message: Mapping[str, Any], character_id: str) -> bool:
106106
a name or an id in ``story``, not ``protagonist`` or any other link."""
107107
if not isinstance(character_id, str) or not character_id:
108108
return False
109-
value = message.get("characters")
110-
if value is None or (isinstance(value, list | tuple) and len(value) == 0):
109+
if is_to_everyone(message):
111110
return True
111+
value = message.get("characters")
112112
return isinstance(value, list | tuple) and character_id in value
113113

114114

115+
def is_to_everyone(message: Mapping[str, Any]) -> bool:
116+
"""True when the message is addressed to every member: its ``characters`` is absent, ``None`` or
117+
empty. A list that names no valid id (``[""]``) or a value that is not a list is addressed to nobody,
118+
so it is False here, though ``recipients_of`` gives ``[]`` for both."""
119+
value = message.get("characters")
120+
return value is None or (isinstance(value, list | tuple) and len(value) == 0)
121+
122+
115123
def is_sent_by(message: Mapping[str, Any], character_id: str) -> bool:
116124
"""True when the message's ``narrator`` is ``character_id``: the claim, whether or not
117125
``sender_status`` proves it."""

‎tests/test_message.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
is_addressed_to,
2020
is_message,
2121
is_sent_by,
22+
is_to_everyone,
2223
read_mark_of,
2324
recipients_of,
2425
sender_status,
@@ -124,6 +125,11 @@ def test_recipients_keep_their_order_and_a_malformed_list_is_not_everyone() -> N
124125
assert is_addressed_to({"characters": everyone}, ADA) is True, everyone
125126
assert is_addressed_to({}, ADA) is True
126127
assert is_addressed_to({"characters": []}, "") is False
128+
for not_everyone in (ADA, [""], [5], [ADA]):
129+
assert is_to_everyone({"characters": not_everyone}) is False, not_everyone
130+
everyone_bodies: tuple[dict[str, Any], ...] = ({}, {"characters": None}, {"characters": []})
131+
for m in everyone_bodies:
132+
assert is_to_everyone(m) is True, m
127133

128134

129135
def test_the_read_mark_supertype_is_folded_and_a_mark_with_no_members_list_has_no_hand() -> None:

0 commit comments

Comments
 (0)