PropositionRunLinkStore

Which extraction runs produced which claims.

This is the relation that closes DICE #67's gap: a stored claim could not be traced to the run that produced it. ProvenanceEntry says which source a claim was read from and CollectorTraceStore says why a collapse happened; neither carries a run.

Many-to-many, and why it has to be

One run produces many propositions. One proposition is produced by many runs — that is not an edge case, it is the normal outcome of re-extraction. Run a second extraction over the same material and the deduplicating store answers with the proposition that already exists, so the second run produced nothing new and still produced that claim. Both runs are true answers to "what produced this?", and a relation that could hold only one would have to pick, silently. That is why this is its own relation rather than a field on the proposition.

Only canonical ids go in

A link is written against the id the store holds, never the id extraction minted. Those differ whenever a backend deduplicates, and a link against the minted id would point at nothing. com.embabel.dice.proposition.PropositionPersistenceResult is what carries the canonical ids back from a save, and it is what a caller links from.

Run identity stays out of source provenance

Nothing here touches ProvenanceEntry or SourceLocator. Two claims read from the same source under two different runs have equal provenance and always will: source identity answers "where did this come from", run identity answers "which execution wrote it down", and folding one into the other would make evidence from two runs over one document look like evidence from two documents. The lineage lives in this relation instead.

Tenant-guarded, on both ends

A link is between a proposition and a run in one tenant. link resolves both ends inside key.contextId and rejects the write outright if either does not resolve there — a proposition that lives in another tenant is not "missing", it is out of scope, and joining it to this tenant's run would put a neighbour's claim into this tenant's audit. Nothing partial is written: one out-of-scope id rejects the whole batch.

Both reads fail closed the same way. A read in the wrong tenant returns nothing rather than crossing.

Bounded, and ordered by id

Every read takes a positive limit. Both are ordered by id ascending, which makes a page repeatable without joining anything: ordering runs newest-first would mean reading each run's header for its start time, and a caller who wants that already has ExtractionRunStore and the refs this returns.

EXPERIMENTAL. The shape may still change while extraction runs (DICE #67) land.

Inheritors

Functions

Link copied to clipboard
open fun link(key: ExtractionRunKey, propositionId: String): Int

link for a single proposition.

abstract fun link(key: ExtractionRunKey, propositionIds: Collection<String>): Int

Records that key's run produced each of propositionIds, in that run's tenant.

Link copied to clipboard
abstract fun propositionsOf(key: ExtractionRunKey, limit: Int): List<String>

The propositions a run produced — the inverse read — by proposition id ascending.

Link copied to clipboard
open fun runsOf(contextId: <Error class: unknown class>, propositionId: String, limit: Int): List<ExtractionRunRef>

runsOf for Kotlin callers holding a typed tenant.

abstract fun runsOf(contextIdValue: String, propositionId: String, limit: Int): List<ExtractionRunRef>

The runs that produced the proposition, in one tenant, by run id ascending.