Skip to content

feature: branded highlight outro (orchestration) - #315

Open
Flegma wants to merge 8 commits into
mainfrom
feature/branded-outro
Open

Flegma wants to merge 8 commits into
mainfrom
feature/branded-outro

Conversation

@Flegma

@Flegma Flegma commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Part 2 of 2: api (merge AFTER the game-streamer PR). Renderer: the feature/branded-outro PR in 5stackgg/game-streamer. Full design, both PRs, and merge order: 5stackgg/5stack-panel#514.

What

The orchestration half of the branded highlight outro: the api decides the outro cache hit/miss per render and passes branding env to the game-streamer render pod. The renderer PR consumes that env.

Changes

  • src/matches/game-streamer/outro-branding.ts (new): pure helpers. computeOutroVersion = sha1(brandName|accent|logoEtag).slice(0,12), outroCacheKey = branding/outro_<version>_<dims>_<fps>.mp4, buildOutroEnv, outroAccentFromSetting (the setting when it is an HSL triple like 33 94% 58% or 224.3 76.3% 48%, trimmed; otherwise the default 33 94% 58%), and clipOutputFromSpec / sharedClipOutput (the dims/fps a spec renders at, mirroring game-streamer's job-fields, and the output every spec of a batch shares, or null). outro-branding.spec.ts has 28 unit tests.

  • game-streamer.service.ts: injects S3Service; resolveOutroBranding(dims, fps):

    • active only when a custom logo (public.logo_url) is set;
    • accent = public.color_dark_tactical_amber when it is an HSL triple, otherwise the default 33 94% 58% (via outroAccentFromSetting, the same pattern the renderer checks CLIP_BRAND_ACCENT against), so the version hash covers the accent that is really rendered;
    • seeds the cache version from the logo's S3 ETag (so a same-path logo re-upload invalidates);
    • presigns a cache GET on hit, or a PUT plus branding props on miss;
    • returns {} (which means baked stock outro) when no logo is set or on any error, so it never breaks a render.

    Wired into the batch-highlights pod env (pod-level) and the dispatchClipRenderToPod payload. The batch pod gets one outro env while each job renders at its own spec output, so the outro is keyed on the output all the jobs share (sharedClipOutput); when they differ, no outro env is sent (stock outro) and a warning is logged.

    The demo-session pod env also gets S3_PUBLIC_ORIGIN, the origin render-clip.mjs accepts the outro URLs from. resolveS3PublicOrigin reads it off a real presigned URL (S3Service.getPresignedUrlOrigin): the demos domain for the in-cluster store, the store's own host (bucket subdomain included under virtual-host style) for a remote store, which is what gets remote S3 stores the branded outro on the on-demand path. On any error it warns and falls back to the demos domain so the pod still starts.

  • src/s3/s3.service.ts: getPresignedUrlOrigin(bucket = this.bucket) presigns a probe key through getPresignedUrl (type get, no useLocal, the routing the outro URLs take) and returns new URL(url).origin. Signing is local, so the probe key is never requested; GET and PUT URLs come from the same client and share the origin.

  • src/matches/clips/clips.service.ts: passes outro_env in the on-demand dispatch payload.

No DB migration, no web change (reuses the existing branding settings).

Env contract (shared with the renderer PR)

Same six keys: CLIP_OUTRO_URL (hit), or CLIP_OUTRO_RENDER=1 + CLIP_OUTRO_PUT_URL + CLIP_BRAND_LOGO_URL + CLIP_BRAND_NAME + CLIP_BRAND_ACCENT (miss). CLIP_BRAND_NAME may be empty (keeps the stock wordmark); CLIP_BRAND_ACCENT is always an HSL triple. Plus S3_PUBLIC_ORIGIN on the demo-session pod: on the on-demand path the renderer drops the whole outro env when a URL is not at that origin or the accent is not an HSL triple, and renders the stock outro.

Merge order

Merge AFTER the game-streamer PR (which must ship :latest first). Safe either way thanks to the stock fallback, but the renderer must be updated before the api turns the feature on.

Verification

  • jest 80/80 across the five touched spec files: 28 in outro-branding.spec.ts; the new S3Service origin test (the probe is signed for the default bucket without useLocal); two resolveS3PublicOrigin tests (the presigned origin, and the warning plus demos-domain fallback) and one resolveOutroBranding orchestration test (an injection-string accent setting renders and hashes the stock amber) in game-streamer.service.spec.ts; the other two specs only gained the constructor argument. tsc -p tsconfig.build.json --noEmit clean; the full tsconfig.json run has 9 pre-existing errors in 7 untouched spec files, none in the changed files.
  • A whole-branch review verified the S3Service signatures, DI wiring, failure isolation, the exact env-name contract, and dims/fps consistency between the batch and on-demand paths. This round's change was reviewed again with no blocking findings.

Non-blocking follow-ups

  • Parallelize the independent settings reads (Promise.all) on the cold path.
  • Add the remaining resolver-orchestration unit tests (no-logo returns {}, hit/miss env, stat-throws degrades). The pure helpers and the invalid-accent orchestration case are tested; the rest of the orchestration was verified by review.

@Flegma

Flegma commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Code + security review (multi-agent), addressed

Independent code review (Sonnet) and security review (Opus).

Security review: Ship (no Critical/High)

The trust boundary holds: no path injection (dims is a hardcoded enum, fps validated, the cache version is a sha1 digest, so nothing attacker-controlled reaches the S3 key), no SSRF reflection (the api only ever presigns its own bucket's admin-set logo), no secret leakage in logs, and the presigned PUT is scoped to a single content-addressed key.

Code review, fixed

  • Moved resolveOutroBranding inside the dispatch try in clips.service.ts, so a hypothetical unexpected throw still marks the job error (consistent with the other failure paths).
  • Added S3_PUBLIC_ORIGIN to the demo-session pod env (startDemoPlayback) so the renderer can origin-allowlist the outro URLs against the real S3 presign origin, independent of the demo source. This keeps branded outros working for faceit/external demos (whose DEMO_URL origin differs).

Non-blocking follow-ups

  • Parallelize the independent settings reads (Promise.all) on the cold path.
  • Add a resolver-orchestration unit test (no-logo returns {}, hit/miss env, stat throws degrades). The pure helpers are unit-tested; the orchestration was verified by review.
  • Defense-in-depth: authenticate or NetworkPolicy the render-pod spec-server endpoint (tracked in game-streamer#29 and [FEATURE] Branding for Highlights Outro 5stack-panel#514).

@Flegma
Flegma force-pushed the feature/branded-outro branch from e3c8251 to 1c7651f Compare July 10, 2026 15:49
@Flegma

Flegma commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (771265f) and re-verified; no functional changes.

Since this branch was cut, main touched the same files five times (highlight settings #325, stream security #327, game-server mono repo #343, demo improvements #346, playback perf #349), so this was worth re-checking rather than trusting a textually clean merge:

  • Rebase applied 7/7 commits with no conflicts.
  • Integration points re-inspected post-rebase: resolveOutroBranding still runs inside the dispatch try on the on-demand path and lands in outro_env; the batch path now derives dims/fps from the configurable clip settings (resolveClipResolution / resolveClipFps) introduced by feature: highlight settings #325, which is exactly what the outro cache key wants; S3_PUBLIC_ORIGIN is still set on the demo-session pod for the renderer's URL allowlist.
  • outro-branding.spec.ts: 6/6 passing.
  • tsc --noEmit: no new errors from this branch. Note that main itself currently has 19 pre-existing errors in src/matchmaking/matchmake.service.spec.ts (reproduced on untouched 771265f); unrelated to this PR, will be tracked separately.

@Flegma
Flegma force-pushed the feature/branded-outro branch from 1c7651f to 11ca622 Compare October 2, 2026 02:17
@lukepolo

lukepolo commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

still sure where i sit with this

Flegma added 8 commits October 3, 2026 05:05
The demo-session pod got S3_PUBLIC_ORIGIN=https://DEMOS_DOMAIN, but
S3Service.getPresignedUrl signs against the demos domain only for the
in-cluster store (rustfs or minio). A remote store is signed against
its own endpoint, path or virtual-host style, so on those installs
every outro URL failed render-clip.mjs's allowlist (it accepts the
outro URLs only when their origin equals S3_PUBLIC_ORIGIN) and every
on-demand clip fell back to the stock outro. Batch highlights were not
affected: their pod gets the outro env directly, without the
allowlist.

S3Service.getPresignedUrlOrigin presigns a probe key for the default
bucket the way resolveOutroBranding does and returns the URL's origin,
so the value follows whatever addressing files-sdk picks instead of
re-implementing it. Checked against files-sdk 2.3.0 and the AWS
presigner: signing is local (the presign middleware returns before the
HTTP handler) and the GET and PUT URLs come from the same client, so
one probe covers the cache GET, the cache PUT and the logo GET. On any
error the pod still gets the demos domain, with a warning.

resolveOutroBranding also sent public.color_dark_tactical_amber raw as
CLIP_BRAND_ACCENT, which game-streamer interpolates into CSS in
headless Chromium. outroAccentFromSetting keeps the setting only when
it is an HSL triple as the web saves it ("33 94% 58%" from its color
picker, "224.3 76.3% 48%" from its defaults) and falls back to
DEFAULT_OUTRO_ACCENT otherwise, so the version hash covers the accent
that is really rendered and game-streamer, which now drops the whole
outro env for an invalid accent, never gets one it must reject. The
pattern is the one game-streamer's outro-env.mjs uses.
@Flegma
Flegma force-pushed the feature/branded-outro branch from d58adf7 to 6a88527 Compare October 3, 2026 03:42
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.

2 participants