Transition Sink
§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 stops — DecisionElement.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
A run is beginning. Called once per experiment, per attachment, before any write.
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.