recordInvocation

Records one attempt at one model call against a running run. This is the only door onto invocation statesave writes header fields only and never creates, updates or deletes an invocation row, however non-empty the invocation list on the run it is handed.

The record's ExtractionInvocationRecord.id is its key within the run, and every write here is insert-or-compare on that key alone: a row for an id not yet stored is inserted, and a write against a row another writer owns never overwrites it wholesale — see the terminal lock below for what "owns" means before an id's outcome settles. A caller does not have to hold the whole run header to add one, and two calls recording different ids never contend with each other or with a concurrent save.

While the attempt is ExtractionInvocationOutcome.IN_FLIGHT, a repeated write for the same id updates in place — that is how dispatch details and, eventually, the terminal outcome fill in as the attempt runs — and the next attempt lands on its own row. That in-place update replaces the whole record with whatever the latest write carries; it does not merge fields from the write it displaces. Two writers racing on the same id while it is still ExtractionInvocationOutcome.IN_FLIGHT, each carrying disjoint dispatch facts the other does not have, leave only the facts the later write named — the earlier write's facts are gone, with no conflict raised, because neither write disagrees about the outcome and the lock in the next paragraph applies only once the record is terminal. A caller that needs every writer's facts preserved has to carry the full accumulated record on each write itself; the store does not accumulate one for it.

Once an attempt is terminal, its record is locked. A write for an id already stored as ExtractionInvocationOutcome.SUCCEEDED, ExtractionInvocationOutcome.FAILED or ExtractionInvocationOutcome.CANCELLED is accepted only when it equals the stored record exactly — an identical retry replays as a no-op — and every other write for that id is rejected, whether it claims a different outcome or the same outcome with different timing, usage or provider facts. The case that motivates the rule is a dispatcher's own retry timer firing late and delivering an ExtractionInvocationOutcome.IN_FLIGHT write for an attempt that had already succeeded or failed, which would otherwise put the attempt back to outstanding and erase the record of how it actually ended. Locking the whole record closes a narrower version of the same problem too — a delayed write that repeats the correct outcome and omits the timing, usage or provider facts the terminal write actually carried. A rejection here matches transition's own choice for the run as a whole: a caller finding out that its message arrived too late is safer than a caller that cannot tell whether it did. Because save never touches this state, a stale header snapshot — however old, however different its invocation list — cannot be the write that puts a terminal record back to outstanding or erases the facts it carries; only another call here can.

Return

The run, with the record in place.

Parameters

key

The run to record against.

record

The attempt.

Throws

if no run is stored under key.

if the run has already ended (a finished run's invocation list is part of how it finished), or if record differs from an attempt the store already holds as terminal.