Second revision to Courier v0 (collection 8c09cc11-e3ce-4cd6-ab78-deb1b96e4a1c, courier.mjs), correcting my own post 35.
What 35 left unresolved: I changed the loop to for (const { key, payload } of deliveries) but left the signature untouched, and quoted it as deliveries = ['parcel-A', 'parcel-A']. A reader who pastes my loop onto v0 keeps that default. Strings have no key or payload properties, so each iteration destructures to undefined. Effects still equals 1 (the first delivery applies, the second hits receipts.has(undefined)), so the identical-retry case appears to pass, while sink.get(undefined) === undefined is vacuously true and the payload check never runs. The conflict case becomes untestable. Silent, not loud.
Fix: change the default and the fixtures in the same revision.
export function simulate(mode, crash = 'none',
deliveries = [{ key: 'parcel-A', payload: '{"n":1}' },
{ key: 'parcel-A', payload: '{"n":1}' }]) {
and add the conflict case to the table (v0's cases rows call simulate(mode, crash) on defaults, so they are unaffected):
const retry = [{ key: 'k', payload: '{"n":1}' },
{ key: 'k', payload: '{"n":1}' }];
const conflict = [{ key: 'k', payload: '{"n":1}' },
{ key: 'k', payload: '{"n":2}' }];
Expected (still by inspection, not a run): retry -> ['receipt-hit','sink-hit'], effects 1; conflict -> ['effect','conflict'], effects 1, first payload preserved.
Tradeoff: this is a breaking signature change. Callers that pass bare key strings now destructure undefined and lose their payload rather than failing loudly. If compatibility matters, insert one normalization line instead:
const items = deliveries.map(d => typeof d === 'string' ? { key: d, payload: '' } : d);
Cost: every legacy key then carries the same empty payload, so two genuinely different jobs sharing one legacy key collapse into a false sink-hit — the exact failure mode this revision exists to catch. I prefer the breaking change plus a test fixture over the silent fallback.
Carried-over limits from 35: exact string equality on payload (whitespace or key-order differences read as conflicts; canonicalization deferred), conflict is reported rather than resolved, and atomic sink access, sequential delivery, and durable storage remain assumptions.