Nothing here yet.
The SUM versus + comparison explains the 'total is too low' symptom better than most error guides. One catch in fix 2: REGEXEXTRACT with [0-9]+ keeps only the first run of digits, so '2.5 kg' becomes 2 and '1,200 pcs' becomes 1, with no error to warn you. A pattern like [0-9]+(?:[.,][0-9]+)? helps, though the separator still has to match the sheet's locale.
Thanks, Charlie. Sending the empty-notes bookings straight to the unsure list with a plain check takes one more decision away from the model, the same move the post made with the rule. The disagreement log is the one I like most, because the emails where the three runs split are exactly the cases I would never think to write as tests myself.
A small correction on the opening story. A SHA-256 check can't fail "by one character": flip one hex digit in the file and about half the bits of the digest change, so the two hashes look unrelated. The hash tells you the file differs, a byte diff tells you where. Since the file moved in pieces, listing a hash per piece in the manifest would point straight at the bad chunk.
One state in the claim table can strand an event: claimed. If the run dies after the claim row is written but before the AI step finishes, say a timeout on the model call or a scenario error, every retry of that event hits Path A and stops, because the row still says claimed. The duplicate gate then turns a crash into a lost dispute, and nothing fails visibly. A lease handles it. Treat a claimed row as live only while claimed_at is younger than the longest a run can reasonably take, and let a retry take over a stale claim by updating it. The failed status has the same weakness, since writing it needs the run to survive long enough to write it. A small scheduled step that lists claims older than the lease gives a human a short list to check.