Skip to content

feat(net)!: carry Live apart from the subscription floor - #5085

Draft
kixelated wants to merge 3 commits into
mainfrom
quest/m1/lite-live
Draft

kixelated wants to merge 3 commits into
mainfrom
quest/m1/lite-live

Conversation

@kixelated

@kixelated kixelated commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A floor and "no floor" were one field. lite-06 Group Start 0 meant no floor, and the model kept a separate None meaning "the latest group". Merging an explicit floor with None either dropped the late group the floor asked for (main) or starved the floorless subscriber until the floor's group existed (#5000), which on a quiet catalog track is never.

Completes quest/m1/lite-live.md (deleted here). #5000 (quest/m1/lite-late-lower-group) rebases onto this and drops its own floor-combining change.

Approach

  • Model. track::Subscription has live: bool (default true) and floor: Option<Position>. Merging takes the OR of live and the minimum floor, where None is neutral. live lowers the floor to frame 0 of the oldest unexpired group, or to the latest group on an untimed track. With neither field set, the subscription asks for nothing, the same as an empty range. with_groups(2..) sets a floor only, and with_groups(..) sets live only.
  • lite-07 wire. SUBSCRIBE and SUBSCRIBE_UPDATE carry Live (8) and Floor (8). [Group Start (i)] and [Frame Start (i)] follow only when Floor is 1. With neither set, the message is a protocol violation.
  • Older wires are mapped in the codec only. On lite-06, (0, 0) is live and any other pair is a floor. Before lite-06, an absent Group Start is live. A merged {live, floor} is sent as an explicit group 0 (lite-03 to 06), or as an Absolute start at {0, 0} on moq-transport, and the relay filters locally. Pre-06 now encodes an explicit group 0 as 1 instead of folding it to absent.
  • Publishers (Rust and JS). When an update gains live, the cursor drops back to the live edge. The "start past largest" SUBSCRIBE_OK answer applies only to a floor without live. A floor's Frame Start applies only while the floor is where delivery starts.
  • moq-transport publisher. A resolved draft-20 start is a floor. Relative(1) floors at frame 0 of the current group (Largest.Group). Unfiltered stays a floor at {0, 0}. A relative filter before any group exists has no start, so it stays the live edge. The objects this publisher sends for Relative(1) are still the current group. The subscribe_join FILL of Relative(1) is unchanged.
  • Origin. The subscription a routed query opens on the source follows later changes to the readers' aggregate, and drops when the last reader leaves. A later narrowing, including Relative(1), reaches the source instead of leaving the query-time snapshot in the aggregate.
  • JS track. Arrival-order reads keep groups below the floor buffered instead of dropping them, until the cache evicts them. Otherwise a serving cursor could not lower its floor.
  • Relay resume copies (model/resume.rs) ask only for what follows the newest group they handed out. They keep live when the reader's own floor sits above that group.

Tests with mocked time:

  • rs/moq-net/tests/live_floor.rs covers the starvation case, a buffered live beside a floor, frame floors, older lite encodings, and moq-transport {live, floor}.
  • relative_one_floors_demand_at_the_current_group and the JS relative 1 floors demand at the current group test assert live: false and a floor at the current group. The unfiltered tests assert a floor at group 0.
  • Codec tests pin every version's mapping in both languages. The lite-07 bytes are pinned identically in Rust and JS.

Impact

This round

  • Public API: no new Rust, JS, or FFI exports. Subscription.live and floor keep the shapes in the whole-PR list below.
  • Wire: no format change. A received draft-20 Relative(1) sets live false and a floor at frame 0 of the current group, so a relay's upstream demand is that floor. Unfiltered stays a floor at {0, 0} with live false. lite-07 Live / Floor and the older-draft codecs are unchanged from the rest of this PR.

Whole PR

  • Rust API (breaking):
    • Subscription::start is renamed floor, and with_start is renamed with_floor.
    • New: the Subscription::live field and with_live.
    • with_groups now sets live too.
    • A floor alone needs .with_live(false). Every in-repo caller is updated.
  • JS API: Subscription.live?: boolean, which defaults to true without groups.start and to false with one, so existing floors keep their meaning.
  • FFI / C: no surface change. A present group_start is a floor alone, and an absent one is live.
  • Wire, lite-07-wip: SUBSCRIBE and SUBSCRIBE_UPDATE gain Live (8) and Floor (8). Group Start and Frame Start become optional and move ahead of Group End.
  • Wire, lite-03 to 05: an explicit floor at group 0 is now sent as 1 (the draft's sequence + 1) instead of absent.
  • Wire, moq-transport draft-20+: an Unfiltered SUBSCRIBE is a floor at {0, 0}. A Relative(1) SUBSCRIBE is a floor at frame 0 of the current group. Encoding of both filters is unchanged. Earlier drafts are unchanged.
  • Draft: the lite draft documents Live and Floor, how SUBSCRIBE_UPDATE clears either, and the lite-07 changelog entry. It also fixes the stale "Group Start is offset by 1" note.
  • Docs: doc/concept/moq-lite.md covers live and a floor, and drops the retired Order knob.

Alternatives

  • One mode byte for the lite-07 start (floor, live, or both), which would make "neither" unrepresentable. The quest says the floor and Live are separate and that "neither" is refused, and two bytes match Hidden (8).
  • Keep start as the field name. live can start delivery below the floor, so floor names the role. The quest used floor too.
  • A TRACK_STATUS round trip to find the largest group before a moq-transport subscribe. Rejected in the quest (2026-10-08).

Settled decisions

  1. draft-20 Unfiltered.
    • Floor at {0, 0}. ✅
    • Treat Unfiltered as live.
  2. draft-20 Relative(1). The quest sentence that called it live is overridden. live with the serving budget is the retained window.
    • Floor at frame 0 of the current group. ✅
    • Map Relative(1) to live.
  3. JS groups below the floor.
    • Keep them buffered until cache eviction. ✅
    • Re-mirror cached groups from the producer when a serving cursor lowers.

Follow-ups

  • On pre-draft-20 moq-transport, the {live, floor} fold is an absolute joining FETCH from group 0. The publisher refuses it (one group per FETCH), so only the live edge arrives. The floor's backlog is lost. That is outside this PR.
  • Expose live through moq-ffi, moq-c, and the bindings, or leave it to subscribe ranges.
  • The JS Subscription has no frame floor (groups.start is group-level), unlike Rust.

just check against origin/main passes on this head.

(Written by Grok 4.7)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome of /quest-start quest/m1/lite-live.md: implemented in Rust and JS, including the draft, docs, and tests. Left as a draft because three decisions are open (see the PR body):

  1. draft-20 Unfiltered is served from {0, 0}. Recommend keeping it.
  2. Relative(1) maps to live, which widens a relay's upstream request to the retained window. Recommend switching to a floor at the current group.
  3. The JS track keeps groups below the floor buffered. Recommend keeping it.

(Written by Claude Opus 5.5)

kixelated and others added 3 commits October 9, 2026 13:12
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lite-07 SUBSCRIBE and SUBSCRIBE_UPDATE carry a Live flag beside an optional floor. The model mirrors it: Subscription { live, floor } in Rust and @moq/net, merged as OR and min. Older wires fold live into the floor at the codec, and a relay filters locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A resolved draft-20 start is a floor, including Relative(1) at frame 0 of the current group. It is not live: live with the serving budget asks a relay for the retained window. Unfiltered stays a floor at {0, 0}.

The origin's query subscription was a snapshot of demand at splice time, so a later narrowing left live set on the source. It now follows the readers' aggregate.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant