One depot, its scanners, and the services that listen
A parcel depot’s scanners report every scan: sorted, out for delivery, delivered, or failed. A customer text notifier reacts to them, an audit service logs them all, and later a proof-of-delivery archive joins. Like the lesson’s editor, the depot should announce each scan once and let each consumer read on its own schedule. Each level starts from the previous level’s finished code, so you can begin at any of them.
Last week the SMS gateway was down for an hour during the evening rush. The notifier’s inbox kept every copy, thousands of them, and when the gateway came back customers got a burst of texts about deliveries an hour old. An inbox that waits needs an end. Each subscription now asks for a capacity, and the team agreed what a full inbox does: the oldest unread copy makes room for the new one, and the broker counts it, so the notifier can report how many texts it missed.
Without a bound, one stopped consumer holds every scan in memory for as long as it is stopped, and when it starts again it acts on stale news. Counting what was dropped is how the operations screen tells a slow consumer from a quiet day.
Reproduce Scan three deliveries or failures before the notifier’s next catch-up, as in a busy minute or a gateway outage: d.scan("p1", "delivered"); d.scan("p2", "delivered"); d.scan("p3", "failed"); d.catchUp(). The notifier’s inbox holds 2, so it should send the newest two and report missed=1; today it sends all three and missed stays 0.
Acceptance criteria No inbox holds more unread copies than its subscription’s capacity. When an inbox is full, its oldest unread copy makes room for the new one, and the broker counts the drop for that subscription. A full inbox changes nothing for any other subscription: other subscribers still get every copy. After a catch-up with the gateway up, the notifier’s missed says how many of its copies have been dropped so far. Everything from PUBSUB-101 still holds. Notes Each case prints failed=… texts=[…] missed=<n>, and audit=[…] when the checker subscribes; the checker’s own subscription asks for 10. Keep the names the checker uses (scan, catchUp, notifier.gatewayUp, notifier.sent, notifier.missed, the broker’s subscribe with a capacity, and drain). missed is updated when the notifier catches up with the gateway up; while it is down, the notifier reads nothing.