In Memory Extraction Run Store
Reference ExtractionRunStore that keeps runs in a map.
It is the executable statement of what the contract means, so a durable backend can be held to the same suite of tests. It also lets a host record and read runs before it has a database, which is most of what the first tier of run lineage is for.
Compare-and-set is real here, not simulated. Every write and every read runs inside one monitor, so the read of a run's status and the write that changes it cannot interleave with another thread's. A durable store gets the same guarantee from its transaction. A reference implementation that read and then wrote without holding a lock would pass every single-threaded test and lie about the property the contract is named for.
A header save is itself compare-and-set, on ExtractionRun.version — and it owns header fields only. Two writers can hold the same running header at once, and whichever saves second must not silently put the other's write back the way it looked before. save accepts a write only when the version it names matches what is stored; a stale writer is told so, in an ExtractionRunConflictException, and has to read the run again and rebuild its save on what it holds now. See save's own KDoc for why this replaced an earlier, field-by-field merge of the running header. Whatever run's invocations field carries plays no part in that comparison or in what gets stored — see save's own KDoc for why.
recordInvocation is the only door onto invocation rows, and each row keeps its own concurrency control. Two attempts never contend on the header's version, because they are not writing the header; each is decided against the row already stored under its own (invocationIndex, attempt) key.
Scope is applied before the limit. Each page filters to the tenant, then orders, then takes the limit. That order is the whole point of the contract's rule, so the reference implementation does it in the order a query would rather than filtering a truncated list.
There is no unscoped read, not even for tests. One instance holds every tenant's runs, so a public "everything in the store" method would be a cross-tenant, unbounded read on a store whose contract is neither — and a host running the shipped in-memory backend would have one. The tests read through the contract like any other caller.
A run that ends announces itself once. transition hands an ExtractionRunTransitioned to listener for the call that ended the run, after the write has landed and outside the monitor, so a slow listener holds up no other writer. A replay and a rejected write announce nothing.
Nothing here survives the JVM, and two instances know nothing about each other.
This is the reference implementation, and it forgets. It holds at most maxRuns runs, and when a new run would push it past that cap it evicts the oldest ended runs, by ExtractionRun.startedAt, until it fits again. A run still RUNNING is never evicted for the cap, so a store where every stored run happens to be running can grow past maxRuns; when that happens it says so with a single warn log for the breach, not one per insert. A host running this store in production is accepting that a run older than the cap is gone for good: it cannot be found, paged, or walked as an ancestor once evicted. Retention is a real, durable policy on a database-backed store, kept for as long as an operator decides; this store exists so a host can record and read runs before it has one of those, and forgetting the oldest is the cost of holding every run in one JVM's memory.
EXPERIMENTAL. The shape may still change while extraction runs (DICE #67) land.
Functions
Walks the parent chain up from the run at key, nearest ancestor first. The run itself is not in the result.
childrenOf for Kotlin callers holding typed references.
The runs whose immediate parent is parentRunId — one hop down the parent axis, in one tenant.
The run stored under key, or null.
Every attempt recorded against the run, in plan order: call 0 before call 1, and within a call, first attempt before second.
Records one attempt at one model call against a running run. This is the only door onto invocation state — save 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.
One tenant's runs, newest first.
runsOfRoot for Kotlin callers holding typed references.
Every run in one lineage: those whose ExtractionRunLineage.rootRunRef is rootRunId, including the root itself.
Records a running run's header, inserting it under ExtractionRun.key or updating the one already there. This writes header fields only — it is not a door onto invocation state. recordInvocation is the only method that creates, updates or locks an invocation row; whatever ExtractionRun.invocations holds on run is not written anywhere and does not affect what a save accepts, rejects or replays as a no-op. A caller building run from a previous read does not need to strip that field, but nothing is lost either way if it does.
Ends a run: compare-and-set from RUNNING to the transition's terminal status.