Extraction Gate Pipeline
A standalone runner that applies an ordered list of ExtractionGates to propositions and aggregates each gate's GateEvaluation into a GatedPropositionResult.
This is a consumer-invoked, pre-persistence stage. The consumer runs the extraction pipeline, then passes the resulting propositions through this runner BEFORE calling save() — it is not embedded inside the extraction pipeline, so the proposition remains the canonical source of truth and gates only route or annotate. Insertion point:
val results = pipeline.process(chunks, context)
val gated = gatePipeline.evaluateAll(results.allPropositions) { proposition ->
// Coerce numerically so a wrong-typed value does not silently read as missing.
GateContext(trustScore = (proposition.metadata["dice.trust.score"] as? Number)?.toDouble())
}
gated.forEach { result ->
when (result.finalDecision) {
is GateDecision.Persist -> repository.save(result.proposition)
is GateDecision.RouteToReview -> reviewQueue.add(result.proposition)
is GateDecision.Reject -> { /* drop */}
is GateDecision.SkipProjection -> repository.save(result.proposition) // persist, no projection
is GateDecision.Demote -> {
// Persist as-is; consumer applies the weaker predicate at projection time.
repository.save(result.proposition)
projector.project(result.proposition, demoteLabel = result.demoteTo)
}
}
}Convention ordering. Gates run in the order they are supplied; the runner does not reorder them. The recommended convention is to order from cheapest/most-decisive to most-permissive — confidence, then dedup/merge, then conflict, then trust, then projection-eligibility — so an early hard rejection short-circuits the rest.
First-non-Persist-wins. The final decision is the first non-GateDecision.Persist decision encountered; once set, it is never overwritten. This guarantees a later GateDecision.SkipProjection (or any other decision) cannot mask an earlier GateDecision.Reject.
Properties
The gates to run, in convention order. Snapshotted at construction so that mutating an aliased backing list a caller passed in cannot change the pipeline's iteration afterwards.
when true (the default), gate execution stops as soon as a gate returns GateDecision.Reject (after recording that evaluation); when false, every gate runs and every evaluation is recorded regardless of decision. Note that short-circuiting can truncate the recorded evaluation trail even when the GateDecision.Reject is not the winning decision — for example if an earlier gate already set the final decision to GateDecision.RouteToReview, a later GateDecision.Reject still stops execution, so any gates after it never run and are absent from GatedPropositionResult.evaluations. Set this to false if the consumer needs the complete trail for audit or observability.
Functions
Run every gate against a single proposition, in declared order, applying first-non-Persist-wins precedence and the configured short-circuit behaviour.
Evaluate a batch of propositions, producing one GatedPropositionResult per proposition.