Skip to content

fix(mabr-route-proxy): resolve DS URLs against the DS root, scope the cache to the manifest - #29

Merged
kkroo merged 4 commits into
mainfrom
fix/mabr-ds-root-base-scope
Oct 29, 2026
Merged

kkroo merged 4 commits into
mainfrom
fix/mabr-ds-root-base-scope

Conversation

@kkroo

@kkroo kkroo commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #27. On main the README pairs the DS manifest URL with baseUrl = new URL('.', manifestUrl). The sender labels ROUTE objects with DS-root paths (live/manifest.mpd, live/seg-1.m4s), so with that base every object resolves to live/live/... and nothing is served from the ROUTE cache; playback silently stays on unicast. On main with the fake route.wasm object contract: base = manifest folder gives manifest MISS and segment MISS; base = DS root gives HIT and HIT.

Changes

  • MabrRouteProxy gets a scope option, separate from baseUrl. Content-Locations resolve against baseUrl; only URLs under scope are cached and answered. scope defaults to baseUrl's directory, so existing callers behave the same.
  • With neither option set, RouteMabrSession.start() derives the base (DS origin root) and the scope (the manifest's directory) from the discovered manifest, and exposes session.manifestUrl as the URL to load. A manifest at the DS origin root makes start() throw, because that scope would let multicast answer any DS URL. Explicit baseUrl/scope are used as given, and session.manifestUrl is then the manifest path resolved against baseUrl (for example a page mount).
  • Discovery reads only the Blockcast PresentationManifest (urn:blockcast:route:1), which is what the sender serializes; SvcInetUrl is no longer read. The https-only filter is dropped; the same-origin check stays.
  • The unpublished dsUrl alias is removed; sltUrl takes a DS origin or an /slt/<group> URL.
  • stop() aborts discovery in flight, including concurrent start() calls, and discoverSlsEndpoint takes a signal.
  • Discovery fetches use cache: 'no-store', and the proxy sends no-store/reload requests to the network, so objects injected over multicast at /slt/ cannot steer the next join.
  • README: the main example uses sltUrl and session.manifestUrl; explicit wiring discovers once and passes slsEndpoint, baseUrl (DS root) and scope (manifest directory).

Release
The mabr-route-proxy-v0.2.0 tag is pushed, but its publish job failed (npm trusted publishing: package not found on the registry), so 0.2.0 is not on npm and nothing public depends on dsUrl.

Tests

  • npm test: 51/51 (42 before). New: derived HIT with an out-of-scope object missing; root-scope refusal; explicit /mabr/ mount; legacy default; abort during discovery with two concurrent starts; discoverSlsEndpoint signal; injected /slt/ objects skipped on restart; SvcInetUrl ignored; http DS manifest; proxy scope, rescope and the no-store/reload bypass. Each new test fails with its fix reverted.
  • npm run test:e2e (real route.wasm, RaptorQ sidecar, Shaka debug and production builds, Chrome): all passes green, plus a new derived pass. The DS /slt/7 document is now generated from multicast's SltType, encoded the way the sender encodes it, with a PresentationManifest; the session gets no baseUrl; Shaka plays session.manifestUrl (/dash/dash.mpd) and no /dash/ request reaches HTTP. Control: the same pass with main's README pairing (baseUrl = manifest folder) fails, because the manifest request reaches HTTP.
  • tsc --noEmit clean; .github/privacy-patterns.txt clean on the changed files.

@kkroo

kkroo commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@ally please review at head cbe322e: follow-up to #27 that separates the cache scope from baseUrl, derives both from the DS manifest, reads PresentationManifest only, drops dsUrl, aborts discovery on stop() and keeps discovery off the cache.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex (applied directly; no nested CLI in this runtime).
Reviewed head: cbe322e

Looks good. Splitting baseUrl (resolution) from scope (what the cache answers) fixes the live/live/... miss without widening the cache, and the derived path refuses a DS-root scope.

Critical Issues (0)

Important Issues (0)

Suggestions (2)

  • [native-codex] packages/mabr-route-proxy/src/route.ts:957 — the root-scope refusal only covers the derived path. With explicit options, baseUrl: <DS root> and no scope falls back to scope = baseUrl's directory, which is the origin root, so multicast can answer any DS URL. That is exactly the setup the README now tells callers to use for baseUrl. The README warns about it (README.md:40), but applying the same scope === new URL('/', scope).href refusal in the MabrRouteProxy constructor / rescope would make the footgun fail loudly instead of silently. Caveat: that also changes behaviour for anyone who passes an origin-root baseUrl today, so it may need its own PR.
  • [pr-review-toolkit:comments] packages/mabr-route-proxy/src/route.ts:945 — in derived mode an endpoint.manifestUrl that is relative (only possible through a caller-supplied slsEndpoint/endpoints; discovery always returns an absolute same-origin URL) resolves against the page, not the DS. A one-line doc note on RouteEndpoint.manifestUrl saying it must be absolute would cover it.

Strengths

  • Discovery uses cache: 'no-store' and the proxy sends no-store/reload straight to the network. That closes the path where objects injected at /slt/ could steer the next join, and there's a test for it.
  • stop() aborts discovery in flight, and throwIfAborted() after the await covers the window where the response arrives just before stop. The test runs two concurrent start() calls.
  • rescope is a non-exported symbol, so the public MabrRouteProxy surface stays the same.
  • I ran npm test locally at this head: 51/51 pass.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

@kkroo

kkroo commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@ally please review at head ab77dbb: adds the derived DS-URL pass to the route.wasm + Shaka e2e (SLT generated from multicast SltType) on top of cbe322e.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex (applied directly; no nested CLI in this runtime).
Reviewed head: ab77dbb

Looks good. The only change since the previous reviewed head is one test commit (compare reports 1 ahead, 0 behind): the SLS fixture now builds the DS /slt/7 document from multicast's models.SltType with a PresentationManifest, and the e2e gets a derived pass that gives the session no baseUrl, checks session.manifestUrl === origin + '/dash/dash.mpd', plays it through Shaka, and asserts no /dash/ request reaches HTTP. The library code is unchanged from the head I reviewed before. The two suggestions from that review still apply as written, and neither blocks merge.

Critical Issues (0)

Important Issues (0)

Suggestions (1)

  • [pr-review-toolkit:tests] packages/mabr-route-proxy/tests/route-wasm-shaka-e2e.mjs:397 — the derived pass runs once (first Shaka variant, native ingress). That covers the URL-derivation contract, which does not depend on ingress or Shaka build. If the AMT path ever starts touching manifestUrl, it would be cheap to run the derived pass inside the existing ingress loop.

Strengths

  • The /slt/7 fixture now goes through the sender's real model types instead of a hand-written XML string, so the derived pass checks the encoding the sender actually produces, not a guess at it.
  • The requests.slice(derivedFrom) window keeps the new /dash/ assertion from being satisfied or broken by the earlier passes, and the existing /mabr/ escape assertion still runs over every pass.
  • The openPage refactor keeps the per-page error and done assertions identical for the old passes.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

kkroo added 3 commits October 29, 2026 00:37
… cache to the manifest

The sender labels ROUTE objects with DS-root paths (live/seg-1.m4s). The
README paired the DS manifest URL with baseUrl = the manifest's directory,
so every object resolved to live/live/... and missed: playback stayed on
unicast.

- MabrRouteProxy gets a scope option separate from baseUrl:
  Content-Locations resolve against baseUrl, and only URLs under scope are
  cached. scope defaults to baseUrl's directory, so existing callers are
  unchanged.
- With neither option set, RouteMabrSession.start() derives the base (DS
  origin root) and scope (manifest directory) from the discovered manifest
  and exposes session.manifestUrl, the URL to load. A manifest at the DS
  root is refused. Explicit options are used as given, and
  session.manifestUrl is the manifest path resolved against baseUrl.
- Discovery reads only the Blockcast PresentationManifest, not SvcInetUrl,
  and no longer requires https. The same-origin check stays.
- Drop the unpublished dsUrl alias; sltUrl takes a DS origin or /slt/ URL.
- stop() aborts discovery in flight; discoverSlsEndpoint takes a signal.
- Discovery fetches send cache: 'no-store', and the proxy sends no-store
  and reload requests to the network, so multicast-injected objects cannot
  answer /slt/ and steer the next join.
…haka

The SLS fixture now emits the DS /slt/ document from multicast's SltType,
encoded the way the sender encodes it, with a PresentationManifest. A new
pass gives the session no baseUrl, loads session.manifestUrl and requires
that no /dash/ request reaches HTTP.
no-store and reload requests go to the network without touching the
hit/miss counters or onCacheEvent, as the README says.
@kkroo
kkroo force-pushed the fix/mabr-ds-root-base-scope branch from ab77dbb to 7330834 Compare October 29, 2026 00:40
@kkroo

kkroo commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@ally please review at head 7330834: rebased onto main f6370d1 after #30 (cache hit/miss telemetry). Conflicts resolved by keeping both: no-store/reload and non-GET requests bypass the cache before the hit/miss counters, so they reach neither stats nor onCacheEvent; README keeps #30's telemetry example with sltUrl/serviceId instead of the removed baseUrl const. New commit 7330834 asserts bypassed requests leave the counters unchanged (mutation-checked). Unit 53/53, tsc clean, route.wasm + Shaka e2e PASS including the derived pass.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex (applied directly; no nested CLI in this runtime).
Reviewed head: 7330834

Looks good. This head is a rebase onto main @ f6370d1 (#30, cache hit/miss telemetry) plus one new test commit. I re-read the conflict resolution in proxy.ts: the non-GET / no-store / reload bypass at packages/mabr-route-proxy/src/proxy.ts:191-192 returns to the network before the miss counter and #report at :195-201, so bypassed requests touch neither stats nor onCacheEvent. That matches the updated onCacheEvent doc in proxy.ts. Commit 7330834 pins it: after one hit and two bypassed fetches, stats is {hits: 1, misses: 0}. The README keeps #30's telemetry example, now with sltUrl/serviceId instead of the removed baseUrl const. The DS-root base / manifest-directory scope logic is the same as the head I reviewed before.

Critical Issues (0)

Important Issues (0)

Suggestions (1)

  • [pr-review-toolkit:comments] packages/mabr-route-proxy/src/route.ts:196 — the session-level onCacheEvent doc still says it is "Called when a player request is served by ROUTE or the HTTP fallback". After this change, no-store/reload requests go to the network without an event, so a reader of RouteMabrOptions alone would expect events this hook no longer emits. Copying the proxy.ts wording ("except no-store and reload") would keep the two in sync.

Strengths

  • Bypass sits ahead of the counters, so DS discovery traffic (cache: 'no-store') cannot inflate the miss rate that #30 exposes for fallback monitoring.
  • The new assertion runs against the same proxy right after a real hit, so it would catch a counter placed before the bypass check.
  • The README conflict was resolved without leaving a reference to the removed baseUrl const.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

@kkroo

kkroo commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@ally please review at head 6fcdd61: applies your suggestion on 7330834. The RouteMabrOptions.onCacheEvent doc (route.ts:196) now says no-store and reload requests bypass the cache unreported, matching the proxy-level doc. Doc-only; unit 53/53, tsc clean.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex (applied directly; no nested CLI in this runtime).
Reviewed head: 6fcdd61

Looks good. The only change since 7330834 is the one-line doc on RouteMabrOptions.onCacheEvent at packages/mabr-route-proxy/src/route.ts:196, which applies my previous suggestion. I checked it against the code. The bypass at packages/mabr-route-proxy/src/proxy.ts:191-192 sends non-GET/HEAD, no-store and reload requests to the network before either #report call at :197 and :201. That makes the new wording ("GET/HEAD served by ROUTE or the HTTP fallback; no-store and reload requests bypass the cache unreported") accurate, and it now matches the proxy-level doc at proxy.ts:68. There are no code changes, so nothing from the previous review needs re-reading.

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The session-level and proxy-level docs now describe the same event contract, so a reader of either interface sees the bypass.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

@kkroo
kkroo merged commit 2675b30 into main Oct 29, 2026
7 checks passed
kkroo added a commit that referenced this pull request Oct 29, 2026
Bumps `@blockcast/mabr-route-proxy` 0.2.0 -> 0.2.1 to release #30 (route cache hit and miss telemetry) and #29 (resolve DS URLs against the DS root, scope the cache to the manifest).
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