# flowscope 0.6 — Plan of record
**Date:** 2026-05-22
**Source:** [`docs/feedback-2026-05-22-netring.md`](./feedback-2026-05-22-netring.md)
— integration feedback from netring 0.14.0.
**Backward compatibility:** breaking changes are *in scope*. Pre-1.0
policy ([`plans/INDEX.md`](../plans/INDEX.md)) allows it; `netring`
updates in lockstep and `CHANGELOG.md` carries migration recipes.
---
## 1. Verdict
The netring feedback is high-quality and almost entirely actionable.
The author scoped each item, ranked it, and was honest about
prioritization ("if I were prioritizing flowscope 0.5"). We adopt
most of it, with one substantive disagreement (item 6) and two
deferrals (items 2 and 10) explained below.
The 0.4 series prioritized *common-case ergonomics* (annotation-free
drivers, one-expression offline pipelines, time-aware parser traits)
and traded away an advanced-use surface — driver per-flow user state
— without realizing the main consumer relied on it. 0.6 corrects
that and rounds out the convenience layer.
---
## 2. What ships in 0.6 (eight plans)
| [`38`](../plans/38-driver-state-restore.md) | #6 (opt. A) | Restore `S` on the drivers via split constructors | yes | M |
| [`39`](../plans/39-tracker-convenience.md) | #3 + #1 | `FlowTracker::finish()` + `sweep_with_parsers` / `sweep_with_datagram_parsers` | no | S |
| [`43`](../plans/43-anomaly-event-split.md) | #4 | Split `Anomaly { key: Option<K> }` into `FlowAnomaly` + `TrackerAnomaly` on both `FlowEvent` and `SessionEvent` | yes | M |
| [`44`](../plans/44-reassembler-watermark-threshold.md) | #9 | `BufferedReassembler::with_high_watermark_threshold` + `AnomalyKind::ReassemblerHighWatermark` | no | S–M |
| [`50`](../plans/50-as-packet-view.md) | #5 | `AsPacketView` trait + blanket `From<&T>` for `PacketView` | minor | S |
| [`58`](../plans/58-driver-factory-ctor.md) | #7 | `FlowSessionDriver::with_factory` / `FlowDatagramDriver::with_factory` | no | S |
| [`59`](../plans/59-test-helpers-parsers.md) | #8 | `flowscope::test_helpers::{NoopSessionParser, NoopDatagramParser, EchoSessionParser}` | no | S |
| [`60`](../plans/60-l7-umbrella-and-doc-note.md) | #11 + #12 | `l7` umbrella feature; intra-doc-link note for re-exporters | no | trivial |
| [`61`](../plans/61-feature-matrix-ci.md) | (not in feedback) | CI matrix of partial-feature builds (catches latent cfg dead-code) | no | trivial |
Sequencing:
- **First batch (no inter-dependencies):** 38, 39, 50, 60, 61.
- **Second batch (depends on 38 for the `S` reintroduction):** 58
(driver `with_factory` must thread through the restored `S`).
- **Independent:** 43, 44, 59.
---
## 3. The one substantive disagreement — item #6
The netring author lays out two options for the dropped driver-`S`
parameter and "leans toward B + #1" (keep drivers lean, ship a
documented `FlowTracker` recipe + the `sweep_with_parsers` helper).
**We adopt option A instead** — restore `S` on the drivers with a
split-constructor design so the common `S = ()` path stays
annotation-free. Reasoning:
1. **The "rare combination" assumption was wrong.** Plan 32's removal
was justified as "a driver carrying both a per-flow parser AND
per-flow user state is a rare combination not worth taxing every
signature for." netring — the main consumer crate — is exactly
that combination. The cost/benefit math is upside-down: every
call site that *doesn't* need `S` already pays nothing under the
split-constructor approach (option A from plan 32's review), and
netring would drop ~300 LoC of custom driver clone.
2. **Option A doesn't reintroduce the original wart.** The original
problem with `S` was that Rust type-parameter defaults don't
participate in inference, so every `FlowDriver::new(...)` site
had to annotate `<_, _, ()>`. Option A pins the common
constructors on `impl<E, F> FlowDriver<E, F, ()>` — inference is
bounded by the impl block, no annotation needed. The fix that
plan 32 chose (option B, drop `S`) was one of two equally valid
ways to solve the inference problem; option A would have also
solved it without losing the advanced-use surface.
3. **The "documented workaround" path scales badly.** Option B
plus #1 leaves every advanced consumer to recreate
reassembler-dispatch, parser-clone bookkeeping, and on-tick
choreography. Even with a `sweep_with_parsers` helper, the
*track-time* feed-bytes-to-parser path stays manual. The drivers
exist precisely to absorb that boilerplate; making them
inaccessible to anyone who needs per-flow state is the wrong
trade.
We still ship #1 (`sweep_with_parsers`) — it's useful for genuine
direct-tracker use and it's small. But the *motivation* shifts from
"netring needs this because the drivers don't support `S`" to
"FlowTracker direct-use should be ergonomic in its own right."
This decision is a partial reversal of plan 32. The CHANGELOG entry
for plan 38 calls it out explicitly so the rationale survives in
the published record.
---
## 4. What we defer
- **#2 — `FlowTracker::with_auto_sweep(interval)`.** The packet-clock
parity argument (live vs offline runs producing different events
for identical inputs) is real and worth solving. The author
agrees this "deserves its own RFC rather than landing in a minor."
We defer to 0.6 once the design questions are settled: how
out-of-order timestamps interact with the auto-sweep clock, what
events are returned from an implicit sweep (merged into the
`track` return? a separate channel?), and whether `with_monotonic_
timestamps` should be a prerequisite.
- **#10 — `SessionParser::is_done()`.** "I'm satisfied, please close
this flow cleanly" — symmetric with `is_poisoned()`. The
motivating case (HTTP/1.0 `Connection: close`) is one where the
server sends a FIN anyway, so the flow closes naturally. A
genuine motivating case (a stream-parser that knows protocol
completion without a network-level FIN) hasn't surfaced. Decline
pending real demand. Easy to add later — the symmetric `is_poisoned`
pattern is the precedent.
---
## 5. Items added beyond the feedback
**#61 — Cargo feature-matrix CI.** Caught during the plan-37
implementation: `obs::trace_session_message` was emitting a
`dead_code` warning under `--no-default-features --features dns`,
because its callers (the drivers) are `reassembler`-gated but the
function itself was only `session`-gated. CI runs `--all-features`,
so the warning was latent. A small matrix step (e.g.
`{default, pcap-only, dns-only, all-features}`) catches this class
of latent cfg-warning before users hit it. Tiny workflow change;
included.
No other "while we're in there" additions — the API surface
churned enough in 0.4 + 0.6 (per this plan) that additional
optional features would dilute focus. Sister-crate work
(`flowscope-export`, `flowscope-cli`) stays out of scope per
[`docs/DPI_ARCHITECTURE.md`](./DPI_ARCHITECTURE.md).
---
## 6. Migration & release shape
Plans 38, 43, 50 are breaking. The migration is mechanical and
single-pass; netring updates in one commit per CHANGELOG recipe.
Release version: **0.6.0** (pre-1.0 minor bump per the existing
policy — breaking changes inside a 0.x minor are allowed). Tag
`0.6.0`, no `v` prefix (matches `0.1.0` … `0.5.0`).
**Acceptance bar for the 0.6 series:** netring 0.15 (the lockstep
update) can drop both its custom-driver path (~300 LoC, eliminated
by plan 38) and its hand-rolled noop/echo parsers across five test
files (eliminated by plan 59). The `if let Some(k) = key` boilerplate
on every anomaly-routing path disappears (plan 43). Offline-DPI
consumers can feed a foreign packet type with a 3-line trait impl
(plan 50).