Specify dynamic (pattern) advertisements in moq-lite, plus a moqt extension draft - #3005
Specify dynamic (pattern) advertisements in moq-lite, plus a moqt extension draft#3005kixelated wants to merge 7 commits into
Conversation
A publisher can advertise a (prefix, suffix) pattern of paths it could serve on demand instead of enumerating them: WILDCARD_START (0x3) and WILDCARD_UPDATE (0x4) on the Announce Stream, sharing the Announce ID space and retracted by ANNOUNCE_END. A wildcard is a capability rather than an inventory, carries a hop list and one Route Cost (no Epoch, never warm), and forwards under the existing routing rules. Resolution for an unadvertised path consults only the most specific matching tier, orders it by cost, and distributes by a hash of the requested path; a new CAPACITY stream error (0x20) permits one re-resolution excluding the refuser, while every other reset stays terminal. Mirrored in moq-cluster as the WILDCARD_SUFFIX parameter. The Ended flag is removed from ANNOUNCE_REQUEST, ANNOUNCE_START, and ANNOUNCE_UPDATE: announcing recordings is per-recording announce state, the growth wildcards exist to stop, and a recording is discovered out of band and read via FETCH with the wildcard routing the request. Path matching is respecified as segment-aware, matching the implementations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c529de518f
ℹ️ 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".
| The original publisher seeds the value with its production cost: 0 for content it is already producing, higher for content it would have to spin up on demand, such as a standby transcoder advertising everything it *could* serve. | ||
|
|
||
| ## WILDCARD_SUFFIX Parameter {#wildcard-suffix} | ||
| A publisher MAY advertise a pattern of namespaces rather than an exact one: a PUBLISH_NAMESPACE or NAMESPACE carrying WILDCARD_SUFFIX matches every namespace that starts with the message's namespace and ends with the parameter's fields, both possibly empty and matching whole tuple fields. |
There was a problem hiding this comment.
Rebase NAMESPACE wildcard prefixes against the subscription
When a wildcard is carried in a NAMESPACE response, the message's namespace field is a suffix relative to the enclosing SUBSCRIBE_NAMESPACE prefix, not an absolute namespace. For example, under a subscription to tenant, a NAMESPACE suffix of worker must describe the prefix tenant/worker; treating worker as the complete pattern prefix matches the wrong namespaces and validates authorization against the wrong scope. Specify reconstruction against the subscribed prefix while keeping PUBLISH_NAMESPACE absolute. (Written by GPT-5.6 Sol)
Useful? React with 👍 / 👎.
| |:--------|:----------------|:-----------------------------|:--------------| | ||
| | 0x40B57 | HOP_PATH | PUBLISH_NAMESPACE, NAMESPACE | This Document | | ||
| | 0x40B58 | ROUTE_COST | PUBLISH_NAMESPACE, NAMESPACE | This Document | | ||
| | 0x40B59 | WILDCARD_SUFFIX | PUBLISH_NAMESPACE, NAMESPACE | This Document | |
There was a problem hiding this comment.
Negotiate WILDCARD_SUFFIX separately
During a rolling upgrade, an older peer can successfully negotiate RELAY_HOPS and then receive this newly registered parameter without supporting it. The existing moq-transport message-parameter decoder (decode_params! in rs/moq-net/src/ietf/parameters.rs) deliberately rejects unknown message parameters, so the advertisement or session fails instead of degrading to non-wildcard behavior. Add a separately negotiated SETUP capability or a protocol-version boundary, and send WILDCARD_SUFFIX only after the peer advertises support. (Written by GPT-5.6 Sol)
AGENTS.md reference: AGENTS.md:L204-L204
Useful? React with 👍 / 👎.
|
|
||
| A request for a namespace nothing advertises MAY resolve against pattern advertisements ({{wildcard-suffix}}), after the same origin exclusion. | ||
| Only the most specific matching tier is consulted (the longest literal match, prefix plus suffix in tuple fields; equal-specificity patterns form one pool ordered by ROUTE_COST, then distributed by a deterministic hash of the requested namespace against each advertiser). | ||
| A reset with a capacity error permits one re-resolution within the tier, excluding the refuser; every other reset is terminal, so probing unserved namespaces costs one round trip each. |
There was a problem hiding this comment.
Define a transport-level capacity refusal
In moq-transport, requests are refused with REQUEST_ERROR, while a stream reset does not provide the typed application response this rule needs; this draft also assigns no numeric capacity code or REQUEST_ERROR mapping. Consequently, a receiver cannot distinguish retryable capacity exhaustion from a terminal refusal, making the specified one-time re-resolution non-interoperable. Define and register the exact REQUEST_ERROR code and its carriage rather than referring to an unspecified reset. (Written by GPT-5.6 Sol)
Useful? React with 👍 / 👎.
| A receiver MAY resolve a SUBSCRIBE, FETCH, or TRACK request for an unadvertised path against its wildcards. | ||
| Only the most specific matching tier is consulted: the longest literal match, prefix plus suffix in segments, with equal-specificity patterns forming one pool. | ||
| Within the tier, order by lowest accumulated Route Cost, then distribute by a deterministic hash of the requested path against each advertiser, so a set of paths spreads across a pool of advertisers while one path always resolves the same way. | ||
| A resolved path competes on that same cost against a concrete advertisement of it; nothing ranks the two kinds differently. |
There was a problem hiding this comment.
Make wildcard eligibility consistent with concrete advertisements
The first sentence limits wildcard resolution to an unadvertised path, but the following rule requires the resolved wildcard path to compete with a concrete advertisement. When both an exact publisher and a matching standby exist, conforming implementations can therefore either ignore the wildcard or compare their costs, selecting different publishers and potentially starting duplicate production. State one eligibility rule and cross-reference it consistently. (Written by GPT-5.6 Sol)
AGENTS.md reference: drafts/AGENTS.md:L69-L72
Useful? React with 👍 / 👎.
A wildcard is routing capability, not content discovery, so it no longer rides the Announce Stream: WILDCARD_REQUEST/WILDCARD_OK then WILDCARD_START/END/UPDATE mirror the announce mechanics with their own Wildcard ID space. The separation also buys the deployment story for free: a peer without support resets the unknown stream type, which the spec already defines as the non-fatal extension fallback, so broadcast announcements are never at risk and no shared implicit-id counter forces an ignoring peer to count messages it does not understand. The doc test pinning the stream table's row count learns the new row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reworked in cdbafd7 per @kixelated's review: wildcards now live on their own Wildcard Stream (0x7) instead of riding the Announce Stream. Beyond the API-shape argument (routing capability vs content discovery), the shared design had two concrete problems the separation removes. Announce IDs are implicit — derived from message order — so a peer that merely tolerated unknown wildcard messages would still have had to count them to keep broadcast ids aligned; each stream now has its own counter. And a new message type on the shared stream would have killed an older peer's announce stream outright, forcing the accept-first fleet staging; the spec already defines unknown stream types as a non-fatal reset ("the fallback when an extension stream is opened against a peer that did not negotiate it"), so the new stream degrades gracefully and announcements are never at risk. The cluster mirror is unchanged (parameter-based). The cross-kind rules the shared design needed (ANNOUNCE_UPDATE-on-a-wildcard-id violations, the Active Count contortion) are gone with it. (written by Fable 5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cdbafd7b63
ℹ️ 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".
| The remainder of the pattern's prefix after the stream's requested prefix, like ANNOUNCE_START's Broadcast Path Suffix. | ||
| A pattern belongs on a stream when it can match a path under the requested prefix: its prefix extends the requested one (encode the remainder) or the requested one extends its prefix (encode empty, since every path on the stream already satisfies it). | ||
| Empty means every path under the requested prefix. |
There was a problem hiding this comment.
Preserve suffix overlap when rebasing wildcard prefixes
When the requested prefix extends the wildcard prefix, encoding an empty remainder is not equivalent if the requested prefix already contains the wildcard suffix. For example, pattern (foo, bar) matches foo/bar because its halves are adjacent, but on a stream requesting foo/bar this rule reconstructs (foo/bar, bar), which cannot match foo/bar under the non-overlap rule. A valid scoped subscriber therefore silently loses the exact path; define an encoding or reconstruction rule that preserves this overlap. (Written by GPT-5.6 Sol)
Useful? React with 👍 / 👎.
The mechanism routes dynamically produced broadcasts and namespaces; "wildcard" named the matching shape and implied glob syntax the design does not have. The lite stream becomes the Dynamic Stream (0x7) with DYNAMIC_REQUEST/OK/START/END/UPDATE and Dynamic IDs. The moq-transport mirror moves out of moq-cluster into a standalone extension, draft-lcurley-moq-dynamic: a DYNAMIC setup option (0x40B5B) negotiates support, the DYNAMIC_SUFFIX parameter (0x40B59) marks a PUBLISH_NAMESPACE or extended NAMESPACE as a pattern, and the capability, specificity-tier selection, capacity-refusal retry, and authorization semantics travel with it. Cost ordering references moq-cluster as an optional integration, so the extension stands without the mesh machinery; moq-cluster reverts to untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Two more review-driven changes in 307422a: Renamed wildcard → dynamic throughout, per @kixelated: the mechanism routes dynamically produced broadcasts/namespaces, while "wildcard" named the matching shape and implied glob syntax the design deliberately lacks (the message carries two literal halves, no wildcard character). The lite stream is now the Dynamic Stream (0x7) with DYNAMIC_REQUEST/OK/START/END/UPDATE and Dynamic IDs; prose says "dynamic advertisement". Split the moqt half into a standalone extension draft,
(written by Fable 5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 307422ac85
ℹ️ 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".
| When the stream is closed, the subscriber MUST assume that all broadcasts are now unavailable. | ||
|
|
||
| Path prefix matching and equality is done on a byte-by-byte basis. | ||
| Paths are sequences of `/`-delimited segments, and all matching is segment-aware: a prefix (or suffix) matches only whole segments, so `foo` is a prefix of `foo/bar` but not of `foobar`. |
There was a problem hiding this comment.
Define canonical slash handling for segment matching
When an untrusted path contains leading, trailing, or repeated slashes, this rule does not say whether empty segments are retained, collapsed, or rejected. The Rust and JS decoders currently normalize those forms (rs/moq-net/src/path.rs and js/net/src/path.ts), while another implementation can reasonably preserve the empty segments, causing peers to disagree on advertisement matching and authorization scope. Specify canonicalization or reject noncanonical paths before applying the new segment-aware comparisons. (Written by GPT-5.6 Sol)
AGENTS.md reference: drafts/AGENTS.md:L10-L13
Useful? React with 👍 / 👎.
| # Acknowledgments | ||
| {:numbered="false"} | ||
|
|
||
| This document was drafted with the assistance of Claude, an AI assistant by Anthropic. |
There was a problem hiding this comment.
Remove the model attribution from the rendered draft
This source is translated into a /doc/draft/ page, so the acknowledgment publishes a source-level AI attribution that repository guidance explicitly prohibits because those markers rot. Remove the attribution from the draft. (Written by GPT-5.6 Sol)
AGENTS.md reference: AGENTS.md:L111-L111
Useful? React with 👍 / 👎.
The pattern prefix now travels whole - the requested prefix filters which patterns a stream carries but never rebases the encoding, since rebasing was lossy and corrupted specificity, re-forwarding, and pattern identity. The ended-broadcast lifecycle is stated: a broadcast that ends but remains readable retracts with ANNOUNCE_END (which never disturbs in-flight subscriptions) and serves stored groups over FETCH, discovered out of band. CAPACITY moves to 0x30, clear of every value current implementations emit as placeholders, the reserved range admits assigned exceptions so the session space keeps 0x20 reserved, and a receiver that spent its one re-resolution refuses downstream with a terminal code so the retry cannot compound per hop. Selection pins the rendezvous FNV-1a hash (seed, input, advertiser identity), states that spreading among cost-equals is the design, and defines the mixed dynamic-versus-concrete comparison (single cost as both halves, kind carries no rank, concrete tie-break chain applies). Authorization is receiver-side in both documents with discard as the action. Path grammar and equality are defined. moq-dynamic becomes implementable: cluster is a normative reference whose HOP_PATH/ROUTE_COST rules apply unchanged, multi-hop resolution requires it, DYNAMIC_CAPACITY is named and registered, and the codepoints move to 0x40B5C/D/F, clear of moq-broadcast's 0x40B59/0x40B5B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Ran a high-effort review over this PR (8 finder angles, adversarially verified); ten findings, nine confirmed, all fixed in eec6991:
Implementation of the Dynamic Stream in rs/moq-net is underway on a stacked branch. (written by Fable 5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eec69912fb
ℹ️ 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".
| Only the most specific matching tier is consulted: the longest literal match, prefix plus suffix in segments, with equal-specificity patterns forming one pool. | ||
| Within the tier, the lowest accumulated Route Cost wins, and a deterministic hash distributes among advertisements tied at that lowest cost. | ||
| Spreading among equals is the design: a co-located worker pool ties on cost and shares the work, while a costlier advertiser is deliberately overflow rather than an equal peer. | ||
| The hash is rendezvous-style FNV-1a: for each candidate, hash the requested path's bytes followed by the advertiser identity as a 64-bit little-endian value, seeded with 0x420C0DECB00B, and select the highest result, so one path always resolves the same way and a pool member arriving or leaving moves only its own share. |
There was a problem hiding this comment.
Hash the normalized path representation
When equivalent noncanonical spellings such as /foo//bar and foo/bar/ reach a dynamic pool, the comparison rule normalizes them into one path, but this line hashes the received bytes and therefore permits them to select different advertisers and start duplicate production for the same broadcast. The fresh evidence beyond the earlier slash-handling comment is that normalization is limited to comparisons while the newly specified rendezvous input remains raw bytes; require the normalized bytes here as well. (Written by GPT-5.6 Sol)
AGENTS.md reference: AGENTS.md:L111-L111
Useful? React with 👍 / 👎.
| When several patterns match, only the most specific tier is consulted: the longest literal match, prefix plus suffix in tuple fields, with equal-specificity patterns forming one pool. | ||
| A refusal from that tier is the answer; it never falls through to a less specific pattern, so a request the winning tier will not serve costs one round trip rather than a walk down the candidates. | ||
|
|
||
| Within the tier, the lowest accumulated ROUTE_COST wins, and a deterministic hash distributes among advertisements tied at that lowest cost, so a set of namespaces spreads across a co-located pool while a costlier advertiser is deliberately overflow. |
There was a problem hiding this comment.
Define selection without Relay Hops
When peers negotiate DYNAMIC but not the cluster extension, direct single-hop resolution remains permitted by lines 79-80, yet no advertisement carries ROUTE_COST and this rule still requires the lowest accumulated value to win. With multiple matching direct advertisers, implementations cannot determine whether missing costs are zero or whether local policy should choose; define the no-cluster ordering/default or require Relay Hops for all resolution. (Written by GPT-5.6 Sol)
AGENTS.md reference: AGENTS.md:L111-L111
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77855a91dc
ℹ️ 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".
| - Added an `Epoch` to ANNOUNCE_START and ANNOUNCE_UPDATE: a per-path content generation minted by the original publisher and forwarded unchanged, 0 meaning unspecified. The highest Epoch wins (non-zero outranks 0) and replacement is decided by value rather than arrival order; equal non-zero Epochs splice, and the first entry of the path remains the identity only when both are 0. That fallback identity requires a non-zero first entry: 0 identifies nothing, so it never proves continuity, and publishers SHOULD assign themselves a Hop ID (a random per-session value suffices). | ||
| - Added an `Ended` flag to ANNOUNCE_START and ANNOUNCE_UPDATE, and as an opt-in filter on ANNOUNCE_REQUEST: ended broadcasts reject SUBSCRIBE, are read via FETCH, and are only announced to subscribers that asked for them. | ||
| - Added the Coverage Stream (0x7): COVERAGE_REQUEST/COVERAGE_OK then COVERAGE_START, COVERAGE_END, and COVERAGE_UPDATE advertise a (prefix, suffix) pattern of paths the publisher could serve on demand, mirroring the Announce Stream's mechanics with its own Coverage ID space. An advertisement carries its pattern halves in full (the requested prefix filters, never rebases), a hop list, and one Route Cost (no Epoch, never warm), and forwards under the routing rules unchanged. Resolution of an unadvertised path recurses per receiver: most specific tier, then lowest cost, then a seeded rendezvous FNV-1a hash of the normalized path against the advertiser identity, with the winning tier the whole answer rather than the head of a fallback list. A COVERAGE_CAPACITY reset permits one re-resolution within the tier excluding the refusing advertiser, and is converted to a terminal code rather than propagated; every other reset is terminal. What a resolution produces is an ordinary broadcast, announced concretely so the next request does not resolve a second producer. | ||
| - Split the reserved stream error range: 32-47 stays reserved for the placeholders implementations emit today, and 48-63 becomes moq-lite's own space for conditions moq-transport has no code for. Assigned 0x30 COVERAGE_CAPACITY there. This is the one range a bridge must map rather than forward, since it has no moq-transport counterpart by construction. |
There was a problem hiding this comment.
Reconcile the error-range changelog entries
For readers checking the moq-lite-06 changes, this bullet assigns 48 through 63 to moq-lite, but the existing bullet at line 1532 still says that 32 through 63 are reserved and MUST NOT be interpreted. Because both bullets describe the same in-progress version, implementations can derive contradictory handling for 0x30; update or consolidate the older entry so the changelog states the final rule once.
AGENTS.md reference: drafts/AGENTS.md:L69-L72
Useful? React with 👍 / 👎.
| # Acknowledgments | ||
| {:numbered="false"} | ||
|
|
||
| This document was drafted with the assistance of Claude, an AI assistant by Anthropic. |
There was a problem hiding this comment.
Remove the AI attribution from the coverage draft
The fresh evidence beyond the earlier comment is that the deleted dynamic draft has been replaced by this newly added coverage draft while retaining the same source-level AI attribution. This draft is rendered into the public /doc/draft/ site, where repository guidance prohibits such attribution markers because they rot.
AGENTS.md reference: AGENTS.md:L111-L111
Useful? React with 👍 / 👎.
Keeps the DYNAMIC name. It is what origin::Dynamic in moq-net already
calls this: a fallback router that picks up requests for paths nobody
announced and serves them without announcing the result. The wire feature
is that concept scoped to a pattern and made advertisable, so the name is
the same because the thing is.
- DYNAMIC_CAPACITY at 0x30 punctured the reserved 32-63 stream error range
with an exception clause, landing in the space implementations use for
provisional placeholders. Split the range instead: 32-47 stays reserved
for placeholders, 48-63 becomes moq-lite's own assigned space, and
DYNAMIC_CAPACITY sits at its base. Named the bridge mapping to the
extension's own code rather than leaving the Error Codes section's
no-translation claim quietly false.
- "Suffix" meant two different things in one document: ANNOUNCE_START's is
a whole path with the requested prefix elided, the pattern's is a tail
the path must end with. The pattern halves are now Pattern Prefix and
Pattern Suffix, with the distinction spelled out.
- The rendezvous hash input was underspecified ("the requested path's
bytes" / "the requested namespace's encoded fields"). Receivers must
hash identical bytes or the distribution is not deterministic, so both
documents now pin the normalized input, the offset basis, the prime, and
the 64-bit selection.
- The two documents disagreed on re-resolution: the extension said the
winning tier never falls through and re-resolution stays inside it,
moq-lite said neither. Both say both now.
- The standby cost floor was a MUST against a quantity no publisher can
know and no receiver can check ("above the largest accumulated topology
cost its deployment can produce"). It is now a SHOULD against a
RECOMMENDED constant floor of 2^32, plus a MUST NOT on the saturation
ceiling, which already marks a draining path.
- Nothing said what happens to the content a resolution produces. It is an
ordinary broadcast, announced concretely so the next request for that
path does not resolve a second producer, which is the duplicated work
the mechanism exists to prevent. A dynamic advertisement is not consumed
by resolution.
- Authorization did not say which prefix it checks. It is the prefix as
sent, since that is the claim being re-advertised; the request prefix
filters which patterns a stream carries and does not scope them.
Minor: DYNAMIC_REQUEST's field matches ANNOUNCE_REQUEST's name, and both
documents state that specificity counts matched segments without caring
how they split between the halves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77855a9 to
fa1d9ec
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fa1d9ecd03
ℹ️ 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".
|
|
||
| | Value | Name | Reference | | ||
| |:--------|:--------|:--------------| | ||
| | 0x40B5D | DYNAMIC | This Document | |
There was a problem hiding this comment.
Remove the duplicate dynamic extension draft
This commit also adds draft-lcurley-moq-coverage.md, which specifies the same protocol while assigning these three values to COVERAGE, COVERAGE_SUFFIX, and COVERAGE_CAPACITY; retaining this draft assigns the identical values to different names, so the registrations cannot coexist and implementations cannot determine which negotiated extension the values represent. The stale draft also maps DYNAMIC_CAPACITY to a moq-lite code that is named only COVERAGE_CAPACITY. Keep one extension draft, or give genuinely distinct extensions non-conflicting registrations. (Written by GPT-5.6 Sol)
AGENTS.md reference: drafts/AGENTS.md:L69-L72
Useful? React with 👍 / 👎.
Segment-aware matching already covered both halves, but the only worked example was a prefix, which left the suffix case to inference in the one spot where it is easy to get wrong: a suffix like `transcode.bar` contains a `.`, and reading that as a boundary would match `foo.transcode.bar`. `/` is the only delimiter. `transcode.bar` is a suffix of `foo/transcode.bar` and not of `foo.transcode.bar`, and a `.` inside a segment is ordinary content. Says so in the path grammar, on the Pattern Suffix field, and in the moqt extension, where the tuple boundary plays the same role. Also states the overlap rule in segments rather than leaving the unit implicit, and that an empty half constrains nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4566363551
ℹ️ 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".
| Codes 48 through 63 are moq-lite's own, for conditions moq-transport has no code for, and are assigned by the tables below. | ||
| This is the one range that does not survive a bridge unchanged: an endpoint speaking both protocols MUST map a code here to the moq-transport code with the same meaning rather than forwarding the value, and MUST map an unmapped one to an unspecified error. | ||
| Placeholders stay below 48, so a value in this range is always an assignment. |
There was a problem hiding this comment.
Limit the new private range to stream errors
Because session and stream error codes are explicitly separate spaces, this blanket assignment also classifies session-close values 0x30 through 0x3f as assigned, even though only the Stream Error Codes table assigns 0x30. When a bridge receives session error 0x30, implementations can therefore disagree between treating it as an assigned moq-lite error and treating it as the unregistered error that it actually is. Scope this range and its mapping rule to stream errors, or separately define the session range.
Useful? React with 👍 / 👎.
…atterns
Reworks the feature after a design review. The wire structure is unchanged:
the Dynamic Stream keeps its own request, OK, END and id space, because it
answers a different question from Announce and is consumed differently.
The rationale was wrong. The draft justified patterns by volume ("too large
to enumerate"), which does not survive contact: if enumerating were the only
problem the answer would be to enumerate nothing and forward blind. The real
argument is that without an advertisement there is no route at all, since an
endpoint holds sessions rather than a directory, and the set that would fix
that cannot be enumerated in advance at any price. A transcoder cannot
announce outputs whose inputs do not exist, and a service cannot announce a
customer who has not signed up.
Requests become patterns. ANNOUNCE_REQUEST and DYNAMIC_REQUEST both carry a
head and a tail, so one vocabulary covers asking and advertising. The tail
filters on the wire; a peer whose version cannot carry it is filtered by the
receiver instead, which is also how a relay serves several requests from one
looser upstream subscription. Filtering a pattern against a pattern is a
different test from filtering a path against one, so DYNAMIC_REQUEST states
it as three explicit conditions rather than leaving "could match" to
interpretation.
ANNOUNCE_START now carries a Path Middle, eliding the requested tail as well
as the requested head. A matching path cannot overlap its own halves, so the
middle is always well defined, and empty when the path is exactly head
followed by tail.
Renamed the halves from prefix/suffix to head/tail, because neither is a
substring operation and the tail is where that bites: a tail of `dash` does
not match `room.dash`. The consequence is now stated outright, that anything
a path will be matched on has to be a segment of its own, with
`customer/room.dash` named as the layout that cannot be selected by tail.
`head/**/tail` is documented as an API spelling of the two fields, so callers
can write the glob they expect without one reaching the wire.
New correctness rule: a receiver MUST NOT resolve a path until the initial
announcement set covering it has arrived. The two streams carry no ordering
between them, so a handful of patterns arrives long before thousands of
announcements, and resolving in that window starts a producer for a broadcast
that already exists.
Patterns carry no Epoch, and the draft now says why rather than only that
they don't: two advertisers of one pattern are a pool rather than a contest,
so there is no generation to arbitrate, and a fleet handing off prices its
cost above the incumbent and drains instead of suppressing it.
Also documents that a moqt receiver takes both kinds and separates them
itself, since moq-transport has no request that could carry the distinction,
and that pattern support is baseline within lite-06 rather than something a
peer may quietly not implement.
The `Ended` removal that was bundled here is restored; it stands alone and
goes in its own change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bde574ddda
ℹ️ 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".
| #### Resolution {#dynamic-resolution} | ||
| A receiver MAY resolve a SUBSCRIBE, FETCH, or TRACK request for an unadvertised path against its dynamic advertisements, and resolution recurses: a resolved request forwarded upstream is still unadvertised there, so each receiver along the way selects among its own advertisements. | ||
|
|
||
| A receiver MUST NOT resolve a path until the initial announcement set covering it has arrived: the `Active Count` batch of an Announce Stream whose request matches that path has been received in full (see [ANNOUNCE_OK](#announce-ok)). |
There was a problem hiding this comment.
Wait for every covering announcement batch
When multiple Announce Streams have overlapping patterns, completing the Active Count batch of only “an” matching stream does not establish that the path is unadvertised. For example, an empty batch on the first stream permits immediate dynamic resolution while a second covering stream is still delivering the concrete broadcast, starting a duplicate producer. Require all covering initial batches to complete before resolving. (Written by GPT-5.6 Sol)
Useful? React with 👍 / 👎.
A publisher can advertise a pattern of paths it could serve on demand, a head and a tail with either half empty, instead of enumerating every broadcast.
Why
An announcement says a broadcast exists and, by its route, where to reach it. Without one there is no route at all: an endpoint holds sessions rather than a directory, so a path nobody announced has no session to send a request down, however willing some peer would have been to serve it.
The set that would fix this cannot be enumerated, and not because it is large. A transcode worker cannot announce outputs before the inputs exist. A service cannot announce a customer who has not signed up yet.
origin::Dynamicalready solves this locally formoq-mux; this is the same idea given a wire representation and a pattern, so a relay can pick which session to route an unadvertised path down.Three workloads motivate it (planned in moq.pro's questline): a transcode worker advertising
("", "transcode.pro")once for a whole fleet, a chat backend advertising("<pid>/chat", "")for rooms that exist independently of any broadcast, and an archive advertising("", "").moq-lite
Path Headand aPath Tail, so one vocabulary covers asking and advertising. The tail filters on the wire; a peer whose version cannot carry it is filtered by the receiver instead, which is also how a relay serves several requests from one looser upstream subscription. Filtering a pattern against a pattern is a different test from filtering a path against one, so DYNAMIC_REQUEST states it as three explicit conditions rather than leaving "could match" to interpretation.Path Middle, eliding the requested tail as well as the requested head. A matching path cannot overlap its own halves, so the middle is always well defined, and empty when the path is exactly head followed by tail.dashdoes not matchroom.dash. The consequence is stated outright, that anything a path will be matched on has to be a segment of its own, withcustomer/room.dashnamed as the layout that cannot be selected by tail.head/**/tailis documented as an API spelling of the two fields; no glob reaches the wire.2^32.moq-dynamic
The moq-transport half is its own extension draft, implementable standalone: cluster is a normative reference whose HOP_PATH/ROUTE_COST rules apply unchanged, and multi-hop resolution requires it because HOP_PATH is the only loop defense. A dynamic advertisement is a PUBLISH_NAMESPACE or NAMESPACE carrying the DYNAMIC_SUFFIX parameter (0x40B5F), with the DYNAMIC setup option (0x40B5D) declaring support and DYNAMIC_CAPACITY (0x40B5C) the typed refusal.
It keeps prefix/suffix vocabulary, deliberately: a namespace is a tuple of discrete fields, so the substring trap that motivates head/tail in lite does not exist there. It also documents that a moqt receiver takes both kinds and separates them itself, since moq-transport has no request that could carry the distinction.
Not in this PR
Endedremoval. It was bundled here and is now restored; it stands alone and gets its own PR.just drafts checkpasses on all ten drafts and the doc-site table test passes.🤖 Generated with Claude Code
(written by Claude Opus 5)