Skip to content

Specify dynamic (pattern) advertisements in moq-lite, plus a moqt extension draft - #3005

Draft
kixelated wants to merge 7 commits into
devfrom
claude/wildcard-advertisement-draft
Draft

Specify dynamic (pattern) advertisements in moq-lite, plus a moqt extension draft#3005
kixelated wants to merge 7 commits into
devfrom
claude/wildcard-advertisement-draft

Conversation

@kixelated

@kixelated kixelated commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

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::Dynamic already solves this locally for moq-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

  • A new Dynamic Stream (0x7): DYNAMIC_REQUEST/DYNAMIC_OK, then DYNAMIC_START, DYNAMIC_END, and DYNAMIC_UPDATE, mirroring the Announce Stream's mechanics with their own Dynamic ID space. Kept separate from Announce because the two answer different questions and are consumed differently: one enumerates content, the other describes capability, and a receiver that wants capability without inventory asks for exactly that.
  • Requests are patterns. ANNOUNCE_REQUEST and DYNAMIC_REQUEST both carry a Path Head and a Path 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 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.
  • Head/tail, not prefix/suffix. Neither is a substring operation, and the tail is where that bites: a tail of dash does not match room.dash. The consequence is 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; no glob reaches the wire.
  • A dynamic advertisement is a capability, not an inventory: it never implies a matching path exists, and refusal denies one path. Pattern halves travel whole rather than rebased, since an advertisement is routinely broader than the request that surfaced it. No Epoch, and now the draft says why: 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.
  • Resolution: only the most specific matching tier is consulted, and that tier is the whole answer rather than the head of a fallback list. Cost orders it and a pinned rendezvous FNV-1a of the normalized path against the advertiser identity spreads across the cost-equals. A standby seed SHOULD clear a RECOMMENDED constant floor of 2^32.
  • 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.
  • Resolved content is an ordinary broadcast, announced concretely by whoever holds it so the next request is answered from the announcement. The advertisement is not consumed by resolution.
  • Typed refusal: stream error code 0x30 DYNAMIC_CAPACITY permits exactly one re-resolution, within the tier and excluding the refuser. Every other code is terminal. The reserved stream error range is split to make room without puncturing it: 32-47 stays reserved for the provisional placeholders implementations emit today, 48-63 becomes moq-lite's own assigned space. It is the one range that cannot forward across a bridge unchanged, so both documents name the mapping.
  • Pattern support is baseline in lite-06, not something a peer may quietly not implement.

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

  • Ended removal. It was bundled here and is now restored; it stands alone and gets its own PR.
  • The request tail as a moqt SUBSCRIBE_NAMESPACE parameter. Suffix filtering is useful with no patterns anywhere, so coupling it to negotiating pattern support is wrong. Its own small extension draft, in its own PR.

just drafts check passes on all ten drafts and the doc-site table test passes.

🤖 Generated with Claude Code

(written by Claude Opus 5)

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>

@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: 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".

Comment thread drafts/draft-lcurley-moq-cluster.md Outdated
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.

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 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 👍 / 👎.

Comment thread drafts/draft-lcurley-moq-cluster.md Outdated
|:--------|:----------------|:-----------------------------|:--------------|
| 0x40B57 | HOP_PATH | PUBLISH_NAMESPACE, NAMESPACE | This Document |
| 0x40B58 | ROUTE_COST | PUBLISH_NAMESPACE, NAMESPACE | This Document |
| 0x40B59 | WILDCARD_SUFFIX | PUBLISH_NAMESPACE, NAMESPACE | This Document |

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 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 👍 / 👎.

Comment thread drafts/draft-lcurley-moq-cluster.md Outdated

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.

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 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 👍 / 👎.

Comment thread drafts/draft-lcurley-moq-lite.md Outdated
Comment on lines +397 to +400
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.

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 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>
@kixelated

Copy link
Copy Markdown
Collaborator Author

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)

@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: 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".

Comment thread drafts/draft-lcurley-moq-lite.md Outdated
Comment on lines +1010 to +1012
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.

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 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>
@kixelated kixelated changed the title Specify wildcard (pattern) advertisements in moq-lite and moq-cluster Specify dynamic (pattern) advertisements in moq-lite, plus a moqt extension draft Aug 22, 2026
@kixelated

kixelated commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

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, draft-lcurley-moq-dynamic, instead of a parameter buried in moq-cluster: a DYNAMIC setup option (0x40B5B, empty value) negotiates support — required, because an un-negotiated receiver would read the advertisement as a concrete namespace — and the DYNAMIC_SUFFIX parameter (0x40B59) carries the pattern's suffix on PUBLISH_NAMESPACE and, when cluster is also negotiated, the extended NAMESPACE. Selection, capacity-refusal retry, and authorization travel with it; ROUTE_COST ordering is an optional integration referencing moq-cluster informatively, so the extension is usable without the mesh machinery. moq-cluster is reverted to untouched.

just drafts check and bun run --cwd doc check pass; the new draft is picked up by the doc site automatically.

(written by Fable 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: 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".

Comment thread drafts/draft-lcurley-moq-lite.md Outdated
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`.

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 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.

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 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>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Ran a high-effort review over this PR (8 finder angles, adversarially verified); ten findings, nine confirmed, all fixed in eec6991:

  • Prefix rebasing was lossy — a pattern forwarded onto a deeper-scoped stream lost its original prefix, inflating specificity per-relay and shrinking coverage on re-forwarding. The prefix now travels whole; the request prefix filters, never rebases.
  • The ended-broadcast lifecycle was undefined after the Ended removal — keep-advertised and retract were both conformant and could not interop. Now: retraction claims nothing about content and never disturbs in-flight subscriptions; a recording retracts and serves stored groups over FETCH, discovered out of band.
  • CAPACITY sat at 0x20, which deployed moq-net already emits as its NotFound placeholder, and the range narrowing left session-space 0x20 uncovered. Moved to 0x30 with an assigned-exception clause on the reserved range.
  • Selection: strict cost-then-hash made the hash unreachable for unequal costs; the draft now states spreading among cost-equals is the design, pins the rendezvous FNV-1a hash (seed, input, advertiser identity = first hop entry), states per-receiver recursion, and defines the mixed dynamic-vs-concrete comparison (single cost as both halves, kind carries no rank).
  • CAPACITY semantics: scoped to dynamically resolved requests, exclusion keys on the advertiser identity, and a receiver that spent its one re-resolution refuses downstream with a terminal code so the retry cannot compound per hop.
  • Authorization now binds the receiver in both documents (the two drafts previously put the MUST on opposite actors) with discard as the action.
  • moq-dynamic is now implementable standalone: 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 moved to 0x40B5C/D/F — the old ones were moq-broadcast's two values swapped.
  • Path grammar and equality are defined (normalized non-empty segments, byte equality).

Implementation of the Dynamic Stream in rs/moq-net is underway on a stacked branch.

(written by Fable 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: 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".

Comment thread drafts/draft-lcurley-moq-lite.md Outdated
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.

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 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.

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 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 👍 / 👎.

@kixelated kixelated changed the title Specify dynamic (pattern) advertisements in moq-lite, plus a moqt extension draft Specify coverage (pattern) advertisements in moq-lite, plus a moqt extension draft Aug 24, 2026

@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: 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".

Comment thread drafts/draft-lcurley-moq-lite.md Outdated
- 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.

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 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.

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 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>
@kixelated
kixelated force-pushed the claude/wildcard-advertisement-draft branch from 77855a9 to fa1d9ec Compare August 24, 2026 23:10
@kixelated kixelated changed the title Specify coverage (pattern) advertisements in moq-lite, plus a moqt extension draft Specify dynamic (pattern) advertisements in moq-lite, plus a moqt extension draft Aug 24, 2026

@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: 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 |

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 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>

@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: 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".

Comment on lines +255 to +257
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.

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 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>

@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: 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)).

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 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 👍 / 👎.

@kixelated
kixelated marked this pull request as draft August 25, 2026 16:27
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