Prototype: NewMessageQueue() *MessageQueue; Publish(id,payload string) bool accepts a lifetime-unique ID; duplicate false preserving payload even after consumption. Consume() *Message destructively returns oldest fresh snapshot, nil empty. Empty IDs and payloads valid.
Final: NewMessageBroker(maxAttempts int) (*MessageBroker,error) positive limit counts initial delivery. Publish appends lifetime-unique immutable messages. Subscribe(group string) bool starts at earliest retained message, duplicate false preserving progress; empty group valid. Poll(group) *Delivery nil unknown/caught up; initial attempt 1, repeated in-flight poll same attempt. After retryable Nack, next poll redelivers same position with next attempt before later items. Ack(group,id) bool succeeds only matching current in-flight, clears and advances; unknown/wrong/duplicate/between-nack-and-poll false unchanged. Nack(group,id) *string nil invalid; valid clears in-flight and returns retry if below max, else appends group-specific DeadLetter with Attempts, advances, returns dead-letter. DeadLetters(group) []DeadLetter fresh failure-order snapshots, unknown empty, never drains. New message resets attempts. No clocks/capacity/deletion/replay.
All identifiers and strings compare exactly and case-sensitively. Numeric inputs are typed int; booleans cannot be passed as amounts. Arithmetic and logical timestamps fit int. All supplied collection inputs that are retained must be copied. Every collection or pointer snapshot must be detached; separate service instances own independent state. A nil pointer or nil optional slice denotes absence, while an ordinary empty result collection is nonnil. Invalid calls leave state unchanged unless the contract explicitly accepts time and expires state on a valid unsuccessful call. The exported signatures in exercise.go are the API specification.
Edit exercise.go; retain the public types and signatures. prototype and final are separate stages. The executable tests recover panics as failures, report named checks, and exit nonzero on any failure. A successful run must execute at least one check. Tests use logical time or explicit synchronization where relevant.
Complete the starter code, then run the checks.
Results and errors will appear here.