Time
A batch query has no clock: it reads everything, computes, and finishes. A streaming application runs for months, and when an event happened is different from when it arrived. Streamform names both and lets you choose which one a model runs on.
Processing time is the default
A source without event_time runs on processing time: events are processed in arrival order, and a timestamp column is just a column. explain says so on the Source: time=processing. Nothing is late, nothing expires, and a fixture’s given is simply the order of delivery.
Event time gives the plan a clock
Declare event_time: ordered_at on a source and three things follow:
- The time attribute. Every model downstream carries
ordered_atas its time attribute for as long as it keeps the column — through a filter, through a projection that selects it (under its alias), and not through an aggregate, which groups it away.explainprintstime attribute=…on each node that has one. - The watermark. The source’s watermark is the largest event time seen so far minus the declared
watermarkdelay, and it never moves backwards. It is the plan’s statement of “everything before this instant has arrived”.watermark: 0ssays events are in order;watermark: 5mallows five minutes of disorder. - Lateness. An event behind the watermark when it is delivered is late.
late_events: drop(the default) discards and counts it;keepdelivers it as if on time. Dropping is a choice a fixture must acknowledge, so no test loses an event silently.
Retention lets state end
retention: 30d on a model says a key’s state is dropped once the watermark reaches its last event plus thirty days — silently, with no retraction, so the next event for the key starts its aggregate over and an upsert sink replaces the stale row rather than deleting it. Retention is enforced in event time, which is why it needs an event-time source upstream. It fills the retention= of the state fingerprint, so declaring it changes the model’s state digest, and streamform plan classifies the change as STATE MIGRATION REQUIRED.
The simulator is the definition
One watermark per source; late detection at the moment of delivery; expiry after every advancement; no wall clock anywhere. Fixtures can advance the watermark explicitly with - watermark: <timestamp> and must list dropped events under late:. Flink gets the same semantics where Flink SQL can express them — a WATERMARK FOR clause, a late-dropping view, a STATE_TTL hint — and build warns where it can only approximate: retention on Flink is wall-clock time, which is wrong under replay.
The walkthrough with three runnable fixtures is Event time.