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.
Every delivered parcel’s proof of delivery (the recipient’s signature) must be stored: senders are shown it in disputes, and a delivery without one is paid back. A teammate’s pull request adds the archive as a subscriber to delivered, like the notifier, with an inbox of 2, and its catch-up stores the proofs. QA found proofs missing after a busy hour, so the pull request also makes the depot publish a scan a second time whenever an inbox was full. The proof store, like the gateway, is sometimes down. You are reviewing the pull request.
Lost proofs are refunds. And since the second publish, customers get the same text twice and the audit logs scans twice, while proofs still go missing.
Reproduce Take the proof store down and scan a delivery: the courier’s handheld should report the scan as failed, so they rescan once the store is back; with the pull request it succeeds, and the customer is told the parcel was delivered. Separately, scan four deliveries, then catch up: every proof should be stored once; with the pull request the oldest proofs are gone and some copies arrive twice.
Acceptance criteria Every delivered parcel’s proof is stored, once, however many deliveries come before the next catch-up. When a proof cannot be stored, that scan fails, and nothing about it reaches the customer or any subscriber; a rescan once the store is back succeeds. Each subscriber gets one copy of each scan. Everything from PUBSUB-101 and PUBSUB-102 still holds, the notifier’s capacity and missed count included. Notes Each case prints failed=… texts=[…] missed=<n> proofs=[…], and audit=[…] when the checker subscribes. Keep the names the checker uses, plus proofs.up and proofs.stored. The checker calls catchUp; whether the archive needs one is your call. A failed scan is one that throws (TypeScript) or returns an error (Go and Rust).