A crowd that does not agree with whoever spoke last
Three fixes to how a round behaves, found by reading one real round on the live instance rather than by testing it. A member asked "what would you have done differently" answered the person's original question again instead of critiquing what was already there. Fine on a question with one answer; on a request to *make* something it is an invitation. `crowd.turn` now says to respond to what is above and not to re-answer. The model that opened the round, told to write the final answer and take what the others got right, abandoned its own good answer and adopted the newcomer's position with no argument anywhere for why. Both closing fragments now say that an answer is not the worse one for having been written first, and that agreement with no argument behind it is not a reason to change. That second one is not cosmetic: all three answers from the observed round were compiled. The original and the critic's alternative both build; the merged answer that was actually delivered does not. A crowd's failure mode is not looping -- the caps handle that -- it is converging on the last thing said. Third, the reply that opens a round now carries a chip like every other one. It is the single contribution the crowd does not start, so there was nothing to stamp it with until the round began, and a two-model round rendered as an unmarked reply followed by one saying "2 of 2". The stamp is display state and never scheduling state: `crowd.scheduling_state` hides it from everything that decides what happens next, because fed to the scheduler it would inherit the round's clock -- regenerating the opening an hour later would end the round with "out of time" before anybody spoke -- and would hand that reply a member's tools and a member's instruction. And the chip was never translated. It is now, with the count as placeholders rather than three t() calls around one sentence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -177,6 +177,73 @@ def test_the_round_is_recorded_on_every_row(db, started):
|
||||
assert len(anchors) == 1
|
||||
|
||||
|
||||
def test_the_reply_that_opened_the_round_is_stamped_too(db, started):
|
||||
"""The opening bubble says `1 of 3` like every other one.
|
||||
|
||||
It is the one contribution the crowd does not start -- the composer does --
|
||||
so until the round begins there is nothing to stamp it with. Before this, a
|
||||
two-model round rendered as an unmarked reply followed by one saying `2 of 2`,
|
||||
with no 1 anywhere.
|
||||
"""
|
||||
chat = _crowd_chat(db)
|
||||
opening = _opening_reply(db, chat)
|
||||
assert crowd_service.state_of(opening) is None, "nothing to say before it finishes"
|
||||
|
||||
assert _advance(db, chat, opening)
|
||||
db.expire_all()
|
||||
|
||||
state = crowd_service.state_of(opening)
|
||||
assert state is not None
|
||||
assert (state.phase, state.index) == (crowd_service.PHASE_OUT, 0)
|
||||
assert state.of == 3
|
||||
|
||||
|
||||
def test_the_opening_stamp_belongs_to_the_same_round(db, started):
|
||||
chat = _crowd_chat(db)
|
||||
opening = _opening_reply(db, chat)
|
||||
order = []
|
||||
assert _advance(db, chat, opening)
|
||||
db.expire_all()
|
||||
order = _incomplete(db, chat)
|
||||
|
||||
opened = crowd_service.state_of(opening)
|
||||
first = crowd_service.state_of(order[0])
|
||||
# Same question, same clock -- or the chips group two bubbles of one round
|
||||
# under two different rounds.
|
||||
assert opened.turn == first.turn
|
||||
assert opened.started_at == first.started_at
|
||||
assert opened.round == first.round == 1
|
||||
|
||||
|
||||
def test_the_opening_stamp_is_not_scheduling_state(db, started):
|
||||
"""It must read as "no round yet" everywhere that decides what happens next.
|
||||
|
||||
Fed to the scheduler it would be a member at index 0, which inherits the old
|
||||
`started_at` -- so regenerating the opening an hour later would end the round
|
||||
with "out of time" before anybody spoke -- and it would hand that reply a
|
||||
member's tools and a member's instruction instead of an ordinary first answer.
|
||||
"""
|
||||
chat = _crowd_chat(db)
|
||||
opening = _opening_reply(db, chat)
|
||||
assert _advance(db, chat, opening)
|
||||
db.expire_all()
|
||||
|
||||
assert crowd_service.state_of(opening) is not None
|
||||
assert crowd_service.scheduling_state(opening) is None
|
||||
assert crowd_service.is_opening(crowd_service.state_of(opening))
|
||||
assert generation_service._opens_the_turn(opening)
|
||||
|
||||
|
||||
def test_a_later_speaker_is_not_mistaken_for_the_opening(db, started):
|
||||
chat = _crowd_chat(db)
|
||||
order = _run_round(db, chat, started)
|
||||
for message in order:
|
||||
state = crowd_service.state_of(message)
|
||||
assert not crowd_service.is_opening(state)
|
||||
# `==` and not `is`: `state_of` builds a fresh Turn on every call.
|
||||
assert crowd_service.scheduling_state(message) == state
|
||||
|
||||
|
||||
def test_each_speaker_carries_its_own_connection(db, started):
|
||||
"""So `speaker_for` resolves the pair rather than guessing at the id."""
|
||||
chat = _crowd_chat(db)
|
||||
|
||||
Reference in New Issue
Block a user