Skip to content

feat(net): scope origin announcement interest - #4234

Merged
kixelated merged 6 commits into
mainfrom
quest/m1/js-origin-scope
Sep 26, 2026
Merged

kixelated merged 6 commits into
mainfrom
quest/m1/js-origin-scope

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Problem

JS origins always requested the empty announcement prefix, even when an application only needed a specific namespace. They also lacked Rust's rooted permission scopes.

Approach

Add shared rooted origin views that intersect nested pattern scopes and enforce them for publishing, routing, requests, and wire-facing views. Subscribe once per distinct literal head, omit heads already covered by another subscription, and filter the received routes locally. Keep hidden routes on the transport subscriptions and leave visibility to each reader.

Impact

  • Add Origin.Producer.scope(root, patterns), returning a handle on the same origin with root-relative paths and narrowed permissions; consume() retains that view.
  • Wire encoding is unchanged. Scoped origins now request only the necessary namespace prefixes on lite and IETF; unscoped origins still request every namespace.

Alternatives

Filtering announcement output alone would keep requesting the empty prefix and would leave publishing and blind requests outside the requested scope. Scope the shared routing boundaries instead.

Validation

  • Literal-head regression fails before the forwarding change. Unit coverage includes rooted/nested scopes, denied paths, hidden routes, partial-interest failure, and session cleanup. Real mock-transport sessions cover lite-07 and IETF-19.
  • nix develop --command just check passed, including all 912 net tests.
  • just test interop --all: 27/32 cells passed. Four C cells could not build because the external mbx cache omitted target/include/moq.h; Python-to-browser stalled. Both blockers are also recorded in fix(test): isolate concurrent interop harness runs #4228.
  • Swept 8/32/128 routes and 1/8/32 observers for unscoped, filtered, scoped-producer, and rooted-producer readers. At 128 routes/32 observers, unscoped updates measured 54.5 µs before and 60.1 µs after; filtered readers 18.9 ms before and 22.9 ms after; new scoped/rooted readers 24.5/23.9 ms. These readers retain the existing table-scan cost; the unscoped shared snapshot remains intact. The extended benchmark runs in existing nightly CI.

Follow-ups

The external C-header cache defect and Python/browser stall remain outside this change; #4228 records the same failures and investigation.

(Written by GPT-6)

@kixelated
kixelated marked this pull request as ready for review September 26, 2026 02:42
@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 28 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: daf73590-f723-495c-983b-b4668cdcd004

📥 Commits

Reviewing files that changed from the base of the PR and between 0f61bda and 7609212.

📒 Files selected for processing (10)
  • doc/concept/moq-lite.md
  • js/net/bench/broadcasts.ts
  • js/net/src/connection/forward.test.ts
  • js/net/src/connection/forward.ts
  • js/net/src/connection/scope.test.ts
  • js/net/src/origin.test.ts
  • js/net/src/origin.ts
  • js/net/src/wire.ts
  • quest/m1/README.md
  • quest/m1/js-origin-scope.md

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 23acc332-d661-4d27-880c-fd89d1067c2d

📥 Commits

Reviewing files that changed from the base of the PR and between d2d5d78 and 0f61bda.

📒 Files selected for processing (10)
  • doc/concept/moq-lite.md
  • js/net/bench/broadcasts.ts
  • js/net/src/connection/forward.test.ts
  • js/net/src/connection/forward.ts
  • js/net/src/connection/scope.test.ts
  • js/net/src/origin.test.ts
  • js/net/src/origin.ts
  • js/net/src/wire.ts
  • quest/m1/README.md
  • quest/m1/js-origin-scope.md
💤 Files with no reviewable changes (2)
  • quest/m1/README.md
  • quest/m1/js-origin-scope.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.


Walkthrough

The change adds rooted, pattern-limited producer and consumer views that translate paths between relative and origin-rooted forms. Wire advertisements carry scoped claims, and announcement forwarding subscribes to namespace prefixes derived from origin interests. The update also adds tests and benchmark cases for scoped routing and discovery, updates scope and announcement documentation, and removes the JS origin scope quest entry and page.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 0f61b

Scoped paths and announcement forwarding have no identified issue requiring a fix before merge.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 0f61b

Scoped views narrow publishing and discovery, but a scoped producer can still close the entire shared origin. That matters if an application gives a scoped producer to code that should not control other namespaces.

Retained concerns

  • Medium · security · inferred: A derived, permission-limited producer shares origin state, but closing that producer terminates the whole origin, including sibling namespaces. Delegating the handle therefore delegates broader availability authority than its path scope suggests.
Security review details

Security Blast Radius

  • inferred — If a scoped producer is delegated to less-trusted application code, its close method can withdraw publications and requests across every namespace sharing that origin. The evidence does not establish remote reachability or a tenant boundary.

Security Findings and Attack Paths

  • inferred — The conditional attack path is receipt of a scoped producer followed by close(), which terminates shared origin state without a path-scope check. No verified remote exploit or observed delegation is supplied.

Trust Boundaries and Controls

  • observed — Path operations root and validate scoped inputs, request forwarding uses projected slots, and route selection checks the creating scope. These controls counter a direct out-of-scope publish or request claim, but do not restrict origin-wide closure.

Resilience and Maintainability Implications

  • observed — When an interest stream ends, forwarding closes the routes it inserted. If the connection remains live, it marks discovery unavailable so announcement-gated requests can fall back to blind requests.

Hardening Proposals

  • proposed — If scoped producers are intended for delegation, separate view disposal from origin-wide shutdown, or make shutdown authority explicit in the contract.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding scoped announcement interest for network origins.
Description check ✅ Passed The description directly explains the problem, implementation, impact, validation, and follow-ups for the origin scope and announcement-interest changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 7 files. (1 skipped: 1 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch quest/m1/js-origin-scope
✨ Simplify code
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 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-09-26T15:07:42.458411Z 7609212 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.

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

ℹ️ 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/net/src/origin.ts
Comment on lines +1261 to +1264
const scope = scopes.find(
({ pattern, head }) =>
(exact ? pattern.matches(path) : scopeOverlaps(pattern, path)) &&
(hidden || !hiddenBelow(head, path)),

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 Filter advertisements by their originating scope

When a broad dynamic route is created through a narrow producer, such as scope("", ["*/chat"]).dynamic(""), a disjoint scoped reader such as room/video still sees that route because this predicate checks only the reader's patterns against the advertised prefix. snapshot() and originated have already discarded the route's RouteEntry.scope, so the same invalid route is also forwarded when that disjoint consumer is used as a connection's publish origin, even though bestEntry later refuses every request under it. Preserve the originating scope in the derived advertisement and require it to overlap the reader scope to avoid false local and wire announcements.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 0f61bda: routes now carry their producer scope's claim, and both local readers and the wire advertised view require it to overlap the reader scope, matching moq-net's RouteEntry::overlaps. Regression test added.

(Written by Claude Opus 5.5)

Comment thread js/net/src/origin.ts Outdated
Comment on lines +80 to +82
const relative = Path.stripPrefix(this.root, path);
if (relative !== null) out.set(relative, value);
else if (!exact && Path.hasPrefix(path, this.root)) out.set(Path.empty(), value);

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 Choose the most-specific route when rebasing ancestors

When more than one advertised prefix is above a rooted view, every ancestor is projected to the empty relative path and the last map entry silently wins. For example, if room/alice is inserted before room, a view rooted at room/alice advertises the broader room route's cost and hops even though requests resolve through the more-specific room/alice route; insertion order therefore changes routing metadata exposed to peers. Select the most-specific covering prefix when multiple entries collapse to the same relative key rather than overwriting unconditionally.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 0f61bda: prefixes that collapse onto a rooted view's root keep the most specific one, in both broadcasts/announced and the wire view, matching moq-net's scoped_cursor_advertises_most_specific_covering_route. Regression test added.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits September 26, 2026 07:35
…ost specific cover

A route inserted through a scoped producer now carries its claim, so readers
whose scope it cannot serve no longer see it, locally or on the wire. Prefixes
collapsing onto a rooted view's root present the most specific one, matching
the route requests resolve through. Both mirror moq-net.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge prep:

  • Merged latest main (no conflicts).
  • Fixed both Codex findings: scoped routes are only announced to readers their producer scope can serve, and rooted views present the most specific covering prefix. Both mirror existing moq-net behavior; regression tests fail without the fix.
  • Origin.Producer.scope(root, patterns) mirrors Rust origin::Producer::scope(root, &Patterns) (throws where Rust returns Unauthorized).
  • just check passes (918 net tests).

(Written by Claude Opus 5.5)

@kixelated
kixelated enabled auto-merge (squash) September 26, 2026 14:42

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

ℹ️ 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".

for (const prefix of originWire.interests()) {
// Hidden routes are mirrored too; each local reader opts in on its own.
const announced = conn.announced(Path.Pattern.subtree(prefix), { hidden: true });
const inserted = new Map<Path.Valid, Dynamic>();

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 Deduplicate broad routes across interest streams

With multiple disjoint heads and a peer advertising a prefix above them, such as "" for a/** and b/**, every conn.announced stream emits the same advertisement, but this per-stream map creates a separate Dynamic and drive task for each copy. The IETF subscriber explicitly sends one broad advertisement to every matching consumer in #attachAnnounce, so route storage and repricing work grow with heads times routes; if one stream ends, closing the selected duplicate can also rematerialize active requests through an identical entry. Share or reference-count received prefixes across these streams, and benchmark interest heads against routes. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L35-L35

Useful? React with 👍 / 👎.

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

ℹ️ 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/net/src/origin.ts
const entry = preferredEntry(routes);
if (!entry) continue;
const value = { identity: entry.identity, route: entry.route.peek() };
const value = { identity: entry.identity, route: entry.route.peek(), claim: claimOf(entry, path) };

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 Select scoped routes before collapsing each prefix

When two scoped dynamics advertise the same prefix but cover disjoint patterns, snapshot() chooses the globally preferred entry before #listed applies the reader's scope. For example, if cheap */chat and costlier */video routes both advertise "", a video-scoped reader sees no announcement even though bestEntry correctly routes room/video through the video entry. rebuildOriginated() makes the same early selection, so a connection publishing that scoped view also omits the usable route. Keep the candidates until scope filtering, then choose the preferred overlapping entry. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L28-L28

Useful? React with 👍 / 👎.

@kixelated
kixelated merged commit a5af309 into main Sep 26, 2026
4 checks passed
@kixelated
kixelated deleted the quest/m1/js-origin-scope branch September 26, 2026 15:14
kixelated added a commit that referenced this pull request Sep 27, 2026
forwardAnnounced now opens one announce stream per origin interest (#4234),
so each stream takes its own replay hold and the origin's live marker waits
for every interest's initial set. The live event is handled before the prefix
filter, since it carries no prefix.

Co-Authored-By: Claude Opus 5.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