# pipe-io v0.5.0 - Windowing

**Date:** 2026-05-19
**Compare:** `v0.4.0...v0.5.0`

## Headline

Lands the `window` module: `Tumbling`, `Sliding`, and `Session`
policies behind a pluggable `Clock` trait. Closes one of the three
deferrals from the `0.3.0` design lock (the other two are the
unified `Driver` trait and the dead-letter sink wiring).

The locked surface from `REPS.md` section 4.6 ships verbatim
(`Clock`, `SystemClock`, `WindowPolicy`, `Window<T>`,
`PipelineBuilder::window`, `PipelineBuilder::window_with`). One
practical constraint emerged during implementation: both builder
methods require `T: Clone` because the sliding policy duplicates
items across overlapping windows. That constraint and the
no-background-timer semantics are documented in the module rustdoc
and in `REPS.md`.

## What changed since `v0.4.0`

### New - `pipe_io::window` module

```rust
pub trait Clock: Send {
    fn now(&self) -> Instant;
}

pub struct SystemClock;  // wraps Instant::now

pub enum WindowPolicy {
    Tumbling { size: Duration },
    Sliding  { size: Duration, slide: Duration },
    Session  { idle: Duration },
}

pub struct Window<T> {
    pub fn items(&self) -> &[T];
    pub fn len(&self) -> usize;
    pub fn is_empty(&self) -> bool;
    pub fn start(&self) -> Instant;
    pub fn end(&self) -> Instant;
    pub fn into_inner(self) -> Vec<T>;
}
// IntoIterator for Window<T> and &Window<T>.
```

Available under `feature = "std"` (default-on); not part of the
no_std surface.

### New - Builder methods

```rust
impl<T, S, Acc> PipelineBuilder<T, S, Acc> where T: Clone {
    pub fn window(self, policy: WindowPolicy) -> PipelineBuilder<Window<T>, S, _>;
    pub fn window_with<C: Clock>(self, policy: WindowPolicy, clock: C)
        -> PipelineBuilder<Window<T>, S, _>;
}
```

The carrier type changes from `T` to `Window<T>` after the call.
`window` uses the default `SystemClock`; `window_with` accepts any
user-supplied `Clock` (for deterministic tests, custom monotonic
sources, or fake-time scenarios).

### New - Tests

- 5 unit tests in `src/window.rs` with a deterministic in-memory
  clock: tumbling boundary emission, session idle close, sliding
  overlap, tumbling flush of partial window, `Window::into_inner`.
- 4 integration tests in `tests/window.rs`: a metric rollup with
  tumbling, a session boundary scenario, a sliding overlap scenario,
  and an empty-source no-emit verification.

### Changed - Documentation

- `REPS.md` section 4.6 un-defers the module and documents the
  `T: Clone` requirement and the no-background-timer semantics.
- `docs/API.md` gains a `pipe_io::window` section and lists
  `Window`, `WindowPolicy`, `Clock`, `SystemClock` in the crate-
  root table.

## Public surface

Crate root now re-exports (under `feature = "std"`):

```text
pipe_io::Clock
pipe_io::SystemClock
pipe_io::Window<T>
pipe_io::WindowPolicy
```

Plus the module itself at `pipe_io::window`. See
[`docs/API.md`](../API.md).

### MSRV

`1.75`, unchanged.

### Runtime dependencies

Zero, unchanged.

## Semantics

### Tumbling

Fixed-width, non-overlapping windows. The first window starts when
the first item arrives. When an item arrives at or after
`start + size`, the current window is emitted and the next window
starts at `start + size`. Long gaps with no items are skipped (no
empty windows are emitted between gaps).

### Sliding

Overlapping windows of width `size`, advancing by `slide` each
tick. When an item arrives, the stage:

1. Spawns any new windows whose start is at or before `now`.
2. Emits any windows whose end is at or before `now`.
3. Adds the new item to every active window via `clone`.

`size` does not need to be a multiple of `slide`.

### Session

Adaptive window that stays open as long as new items arrive within
`idle` of the previous item. When an item arrives after `idle` has
elapsed since the last item, the current session closes (its end
instant is the last item's instant), and a fresh session begins
with the new item.

### Flush behaviour

At end-of-stream, `Pipeline::run` calls `Stage::flush` on the
windowing stage. Tumbling and session flush their current
non-empty window with `end = now()` (tumbling) or
`end = last_seen` (session). Sliding flushes every pending window
that holds at least one item.

### No background timer

The pure synchronous core does not run a background timer. A
session window that has gone idle without further items will not
close until `Pipeline::run` reaches end-of-stream. This is a
deliberate trade-off for the runtime-agnostic, dependency-free
design. A `WindowDriver` with an internal timer is a possible
future extension; it sits outside the locked `1.0.0` surface.

## Verification

Local matrix (Windows x86_64-pc-windows-msvc, stable 1.95):

- `cargo build` (default features) [OK]
- `cargo build --no-default-features` [OK]
- `cargo build --all-features` [OK]
- `cargo +1.75 build --all-features` (MSRV) [OK]
- `cargo fmt --all -- --check` [OK]
- `cargo clippy --all-targets --no-default-features -- -D warnings` [OK]
- `cargo clippy --all-targets --all-features -- -D warnings` [OK]
- `cargo test --all-features` - **60 tests pass, 0 ignored**
  (32 unit + 19 integration + 9 doctest)
- `RUSTDOCFLAGS="-D warnings -D rustdoc::broken-intra-doc-links" cargo doc --all-features --no-deps` [OK]
- Banned-word and em-dash scan across `src/`, `tests/`, `benches/`,
  `docs/`, `README.md`, `CHANGELOG.md`, `REPS.md`: 0 hits

CI matrix continues to run on ubuntu-latest, macos-latest,
windows-latest.

## Migration

### From `v0.4.0`

No breaking changes. The `window` and `window_with` builder methods
are pure additions; existing pipelines need no source modification
to upgrade.

To start using windows:

```rust
use core::time::Duration;
use pipe_io::{Pipeline, WindowPolicy, Window, sink::VecSink};

let sink = VecSink::<Vec<u64>>::new();
let handle = sink.handle();

Pipeline::from_iter(metric_stream)
    .window(WindowPolicy::Tumbling { size: Duration::from_secs(60) })
    .map(|w: Window<u64>| w.into_inner())
    .sink(sink)
    .run()?;
```

## Stability commitment

Unchanged. `0.x.y` releases are not API-stable; API freeze begins
at `1.0.0`. After `0.5.0` the two remaining locked-but-not-shipped
slices from the `0.3.0` design lock are:

1. Unified `Driver` trait (extensibility hook).
2. Dead-letter sink wiring (`ErrorPolicy::DeadLetter` is reserved
   in the enum but currently behaves like `Continue`).

## Release ceremony

```bash
git tag -a v0.5.0 -m "Release v0.5.0 - Windowing"
git push origin main
git push origin v0.5.0

cargo publish

cargo search pipe-io | head -3
```

GitHub release title: `v0.5.0 - Windowing`. Not tagged as
pre-release.

## Acknowledgements

`pipe-io 0.5.0` lands the windowing surface defined in the
`0.3.0` design lock. Implementation came in clean: the `Clock`
trait is one method, the three policies share a single
`WindowStage` type, and the deterministic test clock pattern made
boundary-condition tests precise rather than flaky.

Next: roadmap calls for the `0.6.0` dead-letter routing or
`0.7.0` Driver-trait release, in either order. Then `0.9.x`
pre-1.0 stabilization.

---

**Full Changelog:** https://github.com/jamesgober/pipe-io/compare/v0.4.0...v0.5.0
