Skip to content

feat(json): let a snapshot publisher cut a group - #3146

Merged
kixelated merged 1 commit into
devfrom
quest/m1/archive/json-cut
Aug 29, 2026
Merged

feat(json): let a snapshot publisher cut a group#3146
kixelated merged 1 commit into
devfrom
quest/m1/archive/json-cut

Conversation

@kixelated

@kixelated kixelated commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

A moq-json snapshot publisher had no way to force a group boundary. The encoder rolls on its own delta budget and frame cap, so a caller that knows the deltas already written should stop being provisional — because it is about to store them, or because a joiner should not have to replay them — had no way to say so.

snapshot::Producer::cut() FINs the open group and opens no replacement. The next update does, emitting a full snapshot as frame 0 even when the value is unchanged, so a consumer joining there reads the whole value and none of the deltas that preceded the cut. Cutting with no group open is a no-op, so a caller can cut on its own schedule without tracking what it has published, and it is inert with deltaRatio at 0, where every frame already gets its own group.

Deliberately not included

This PR originally also carried an age-based bound (max_age) and the same method on stream. Both are dropped.

A cut cannot express a sliding window on an append-only log. Re-seeding a new group with the retained records is indistinguishable from new data on the wire — a stream consumer yields every frame in order across group boundaries — so a live consumer receives them twice. In this repo that is worse than a duplicate: moq-hls reads a non-increasing segment as "the publisher restarted" and clears its whole playlist window (export/segments.rs:137, export/renditions.rs:185).

Since the broadcast timeline is the only stream user in either language, a cut there would have shipped with no consumer and a real footgun. The timeline gets a windowed log with tagged reset/push/pop ops instead, tracked separately.

Public API changes

Additive; nothing renamed, removed, or signature-changed.

  • moq_json::snapshot::Producer::cut() (new)
  • @moq/json Snapshot.Producer.cut() (new)

Wire behavior changes

No encoding change, and no change at all unless a caller opts in by calling cut(). When one does, a peer sees a group boundary it would not otherwise see, and the next frame is a full snapshot rather than a merge patch. Both are shapes the format already produces whenever the encoder's own budget rolls a group, so nothing new has to be understood by a consumer. No draft update needed.

Test plan

  • just check and just test (1933 tests, 1 skipped) green.
  • 5 new Rust tests and 5 new TS tests: the forced roll, republishing an unchanged value, the no-empty-group guarantee, idempotency (including before anything is published), and inertness with deltas disabled.

(written by claude-opus-5)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e39e69d90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-json/src/stream/producer.rs Outdated
Comment on lines +64 to +66
/// The log continues: no replacement group opens until the next [`append`](Self::append), which
/// starts a cold DEFLATE window that a consumer joining there can decode on its own. That is what
/// bounds the log's history, since a consumer reads whole groups.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the public stream docs for rolled groups

When users follow this new cut API, the public documentation becomes internally contradictory: rs/moq-json/src/stream/producer.rs:13 still says every record is published "in a single group," while rs/moq-json/src/stream/consumer.rs:13-15 says this Producer writes the whole log into one group and attributes rolling only to lower-level Encoder users. Update those public type docs to describe the new producer-managed cuts and max_age behavior so consumers do not build around the obsolete single-group invariant.

AGENTS.md reference: AGENTS.md:L107-L109

Useful? React with 👍 / 👎.

@kixelated
kixelated force-pushed the quest/m1/archive/json-cut branch from 9e39e69 to 7adfdc8 Compare August 28, 2026 21:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7adfdc8e52

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-json/src/group.rs Outdated
Comment on lines +66 to +67
match moq_net::Timestamp::now().checked_sub(opened) {
Ok(age) => age.as_micros() >= max_age.as_micros(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track max_age with a monotonic instant

On a native host whose wall clock is before 2020 when the process starts, Timestamp::now() saturates to zero in rs/moq-net/src/model/time.rs, so this subtraction continues reporting an age of zero until the monotonic elapsed time catches up to 2020. In that environment, max_age never rolls groups in either JSON publishing mode, potentially for years; store a monotonic Instant for elapsed-age checks while retaining the timestamp separately for frame presentation. (Written by GPT-5.6 Sol)

Useful? React with 👍 / 👎.

@kixelated
kixelated force-pushed the quest/m1/archive/json-cut branch from 7adfdc8 to 9b78821 Compare August 28, 2026 21:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b78821bcd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/json/src/snapshot/encoder.ts Outdated
// The third `maxAge` bounding a group's age, and the only one that decides where a group *ends*:
// a track's `maxAge` is how long a publisher keeps a closed group, and a subscription's is how
// long a subscriber waits for one.
maxAge?: Time.Milli;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Convert maxAge documentation to JSDoc

The newly exported Config.maxAge member is preceded only by // comments, so its detailed behavior will not appear as API documentation for consumers of @moq/json. Convert the block to a /** ... */ doc comment, as required for exported TypeScript members. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L107-L107

Useful? React with 👍 / 👎.

A `moq-json` snapshot publisher had no way to force a group boundary. The
encoder rolls on its own delta budget and frame cap, so a caller that knows
the deltas already written should stop being provisional -- because it is
about to store them, or because a joiner should not have to replay them --
had no way to say so.

Add `snapshot::Producer::cut()`. It FINs the open group and opens no
replacement: the next `update` does, emitting a full snapshot as frame 0 even
when the value is unchanged, so a consumer joining there reads the whole
value and none of the deltas that preceded the cut. Cutting with no group
open is a no-op, so a caller can cut on its own schedule without tracking
what it has published, and it is inert with deltas disabled, where every
frame already gets its own group.

Deliberately not included: an age-based bound, and the same method on
`stream`. A cut cannot express a sliding window on an append-only log --
re-seeding a new group with the retained records is indistinguishable from
new data, so a live consumer receives them twice -- and that is what the
timeline actually needs. It gets a windowed log of its own instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kixelated
kixelated force-pushed the quest/m1/archive/json-cut branch from 9b78821 to fd6c14b Compare August 29, 2026 00:11
@kixelated kixelated changed the title feat(json): cut a group on demand or on age, in both modes feat(json): let a snapshot publisher cut a group Aug 29, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T00:14:47.500173Z fd6c14b New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated
kixelated merged commit bab72fc into dev Aug 29, 2026
2 checks passed
@kixelated
kixelated deleted the quest/m1/archive/json-cut branch August 29, 2026 03:45
kixelated added a commit that referenced this pull request Aug 29, 2026
The json-cut tests (#3146) and the ordered-handle rework (#3099) merged past
each other with cancelled CI, so dev's cut tests still call the pre-handle
structure(track.subscribe(...)) shape, which no longer typechecks or runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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