eval_runs/*.json report → eval_outcomes rows (the original eval-ingest
long format, widened with agent/suite/runtime/run_id). Idempotent by
file contents.
Eval reports are left in place, and eval_outcomes has no row key, so a
report rewritten after it was ingested would add its rows a second time.
New contents under a report name already ingested are therefore rejected
with {:error, {:changed_after_ingest, name}}, and an empty report with
{:error, :empty_file}. Reports have other readers, so a rejected report
stays where it is: the rejection is recorded in ingest_rejections by
content digest, and the same contents answer :already_rejected from
then on. New contents are judged afresh.