TransitionSink

§4.8.2. A write-only consumer with a per-experiment lifetime.

The shape, and why it is this one

A sink is a long-lived object that is attached to an element and told when a run starts and stopsDecisionElement.attachTransitionSink, then beginExperiment at the element's beforeExperiment() and endExperiment at its afterExperiment(). That is the same shape as ksl.animation.AnimationSink, which has onReplicationStart/onReplicationEnd/onExperimentEnd with no-op defaults for exactly this reason: a sink that only wants rows implements one method.

The handshake is not decoration. A sink needs RunProvenance to write anything a stranger can read, and two of provenance's five fields — the experiment name and the policy label — change between runs of the same model: §4.9's k-rule comparison assigns k policies to one element in a loop. Handing provenance to a sink's constructor would freeze those at the wrong moment, and the descriptor is computed at beforeExperiment() rather than stored so it cannot be stale (§4.1.5). So provenance arrives per experiment, at beginExperiment.

Who closes it

Whoever constructed it. endExperiment is the flush point and the element calls it; close is ownership and the element does not. This follows ksl.observers.ResponseTrace, which is AutoCloseable, is constructed by the user, and is closed by the user. The one sink the element does own is one it created itself, which is what RollingSink is for.

Inheritors

Functions

Link copied to clipboard
open fun beginExperiment(provenance: RunProvenance)

A run is beginning. Called once per experiment, per attachment, before any write.

Link copied to clipboard
open override fun close()
Link copied to clipboard
open fun endExperiment()

The run is over and no further write will arrive for it. Flush here. The sink may be told beginExperiment again for the next run — attachments outlive experiments.

Link copied to clipboard
abstract fun write(record: TransitionRecord)