Inputs have the documented types. Message IDs, group IDs, and payloads are strings; empty strings are allowed. Payloads are immutable strings. All snapshots and returned lists must be fresh. Instances are independent. There is no clock, capacity limit, deletion, replay, or automatic delivery timeout.
Prototype: implement MessageQueue() with publish(message_id, payload) -> bool and consume() -> dict | None. Publish returns True once per ID across the queue's lifetime, including IDs already consumed; a duplicate returns False without replacing content. Consume destructively returns the oldest {"id": id, "payload": payload} or None when empty.
Final: implement MessageBroker(max_attempts=2):
max_attemptsis an integer; values below 1 raiseValueError. It counts deliveries, including the initial delivery.publish(message_id, payload) -> boolappends to a retained FIFO log. IDs remain unique for the broker's lifetime. Duplicate IDs returnFalseand preserve the original payload.subscribe(group_id) -> boolcreates a group starting at the earliest retained message, including messages published before subscription. ReturnFalseif already subscribed; preserve existing progress.poll(group_id) -> dict | None: unknown or caught-up groups returnNone. Otherwise return exactly{"id": id, "payload": payload, "attempt": n}. The first delivery at a position has attempt 1. An already in-flight delivery is returned again without incrementing its attempt. After a retryable rejection, poll redelivers the same message with the next attempt before any later message.ack(group_id, message_id) -> bool: returnTrueonly for that group's current in-flight ID, clear it, and advance to the next log position. Unknown groups, wrong IDs, duplicate acknowledgments, and acknowledgments between rejection and redelivery returnFalsewithout changing state.nack(group_id, message_id) -> str | None: only a matching in-flight delivery is valid. Invalid calls returnNoneunchanged. A valid call clears in-flight state. If its attempt is below the limit, return"retry"without advancing. At the limit, append{"id": id, "payload": payload, "attempts": n}to that group's dead letters, advance, reset attempts for the next message, and return"dead-letter".dead_letters(group_id) -> list[dict]: return detached entries in failure order, or[]for an unknown group. Reading does not drain the list.
A rejected publish or invalid acknowledgment must not affect any group. Run python main.py --stage prototype, then python main.py --stage final (the default). Edit only exercise.py.
Complete the starter code, then run the checks.
Results and errors will appear here.