Extraction Run
The durable record of one extraction execution.
A run answers "what produced this claim, under what, and how did it go?" — the profile and prompt and schema versions in force, which revisions of which sources were read, which model was asked for and what the provider reported back, how far it got, and what went wrong. It is the header; the propositions it produced are attributed to it by a separate relation, and source grounding stays what it was.
Two rules shape the type.
The first is that requested and observed are different types. What a run asked a model for is requestedModel, one ExtractionRequestedModelConfig on the header. What actually happened is an ExtractionInvocationRecord per attempt, holding usage, timing, the service it went to, and whatever the provider reported. An invocation record has no field that can hold a requested value, so nothing can quietly present a setting as an observation.
The second is that the run holds no content. No prompts, no source text, no responses, no user or session objects, no provider SDK payloads, no extension maps. What a host would have needed those for is covered by digests it can compare (fingerprints) and by bounded pseudonymous tokens it can group by (subjectRefs). Failures are said in a closed vocabulary of codes, stages and numbers, so there is no text field on a run for an exception message to land in.
What is not decided here. Which status transitions are legal, which are compare-and-set, and what a store does with a repeated terminal write belong to the run store contract in the next slice. This type checks that a finish does not precede a start and stops there; it does not require, for instance, that a ExtractionRunStatus.COMPLETED run has a finishedAt, so the state machine defines that once instead of twice.
Collections are copied on the way in, always, including empty ones — an empty mutable list a caller keeps a handle on is the same aliasing bug as a full one, and it fails later and stranger. The copies are unmodifiable, so the run a caller reads back cannot be edited through the list either.
This is a plain class rather than a data class on purpose. A data class has to declare its collection parameters as properties, which means the field is the caller's list and there is nowhere to copy it; its generated copy and componentN methods would also pin an ABI across eighteen fields while #67 is still moving. Equality and hash are written out over every component instead.
EXPERIMENTAL. The shape may still change while extraction runs (DICE #67) land.
Constructors
Properties
The arm within that experiment
How much the run got through
The experiment this run belongs to
What went wrong, bounded and said in the failure vocabulary.
Digests of the prompt, schema and metamodel in force
When it reached a terminal state, or null while it has not
One record per attempt at each planned call, always in plan order: call 0 before call 1, and within a call, first attempt before second.
This run's reference, its root, its parent, what it supersedes, and its pass
The run this one continues from, or null.
The content profile version in force
This run's reference.
How much of this run someone could set up again from what it recorded
What the run asked a model for
The oldest run in this run's lineage, which is ref itself when this run has no parent.
What code ran it, and where
Which revisions of which sources this run read, in order.
Where the run stands
Pseudonymous references to whose work this was
The compare-and-set generation ExtractionRunStore.save checks this header against. A run that has never been saved, and the run its first accepted save produces, both carry 0 — the first save inserts the row, and there is no earlier generation for it to raise past. A store rejects a first save naming any other value. Every later save save accepts that actually changes the header raises it by one; a save whose content already matches what is stored is accepted too, as a no-op replay, and leaves the generation exactly where it stood. ExtractionRunStore.recordInvocation never changes it, since an invocation record writes a child row of its own — and ExtractionRunTransition.applyTo carries whatever value the run already has, because a terminal run takes no more saves and nothing compares its version again
Functions
Every attempt at the call at invocationIndex, earliest attempt first.
The tenant id as a plain string, for Java callers, since ContextId is a value class.
The invocation records ordered by the plan: call 0 before call 1, and within a call, first attempt before second.
The tenant-qualified identity a store keys this run on.