save
Records a running run, inserting it or updating the one already there — in one statement, so the check against the stored run and the write that follows it cannot be interleaved by a concurrent terminal write, and the header's compare-and-set on com.embabel.dice.proposition.extraction.ExtractionRun.version cannot be interleaved by a concurrent header save either.
The statement returns what it found so this method can name which rule was broken. Cypher decides whether to write and Kotlin decides what to say about it, and the conditions are the same condition, written twice: a save writes when the run is new and names version 0; when it is still running, agrees with the stored lineage and start time, and its header content already matches what is stored — a no-op that leaves the version untouched, whatever version the save named; or when it is still running, agrees with lineage and start time, its content genuinely differs, and it names the version currently stored. Every other running-and-agreeing case is a stale write and is rejected without touching the row — see this class's KDoc for why that rejection never leaves a node behind. run.invocations is not part of the statement at all: this method's Cypher has no clause that reads or writes an (:ExtractionRunInvocation).
Runs and retries under ownedTransaction — see that method for the two shapes.