Skip to content

Let a streaming break through selection, and only a streaming break - #303

Merged
ralyodio merged 1 commit into
masterfrom
ads-decision
Sep 24, 2026
Merged

ralyodio merged 1 commit into
masterfrom
ads-decision

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

The break endpoint returned an empty fill for every request. fitAdFormat refuses streaming formats outright and it is the gate in front of serveAd — so the endpoint was calling straight into it and getting exactly what the guard promised.

That refusal is correct and deliberate: everything past that gate renders HTML and ASCII, and a video creative drawn as a banner is the leak the guard exists to prevent.

Selection is not duplicated to get around it

serveAd is where credits, paper spend, trend matching, the auction and the impression live. A second copy of that is a second set of numbers, and the one not wired to billing is the one that quietly gives inventory away.

Instead the context carries an explicit streaming flag that opens the gate and closes the renderer in the same move:

  • a streaming request is matched against the slot's own format list, with no width negotiation
  • the fill's html and text come back empty

So the only path allowed past the refusal is also the one path that cannot render markup — which is what the refusal was protecting.

A streaming break negotiates nothing on purpose: there's no width to fit and no smaller unit to fall back to, so the slot must offer the format outright. That keeps the publisher in control of what their break can carry.

Verification

fitAdFormat's refusal is now pinned by a test, including for a slot that lists the format — that's the configuration someone will eventually try, and it's the one where a regression would be invisible.

2597 passed / 1 failed repo-wide (pre-existing tracker-geo). Typechecks clean.

Live state: nixamp now has an active slot (7e0ea02c) carrying video_preroll_5s, and 182 of 185 video creatives have a published revision — so there is real inventory and real demand waiting on this.

The break endpoint returned an empty fill for every request, because
fitAdFormat refuses streaming formats outright and it is the gate in front of
serveAd. That refusal is correct and deliberate: everything past that gate
renders HTML and ASCII, and a video creative drawn as a banner is the leak the
guard exists to prevent. The endpoint was calling straight into it and getting
exactly what the guard promised.

Selection is not duplicated to get around it. serveAd is where credits, paper
spend, trend matching, the auction and the impression live, and a second copy
of that is a second set of numbers — with the one not wired to billing being
the one that quietly gives inventory away.

Instead the context carries an explicit `streaming` flag, and it opens the gate
and closes the renderer in the same move: a streaming request is matched
against the slot's own format list with no width negotiation, and the fill's
html and text come back empty. So the only path allowed past the refusal is
also the one path that cannot render markup, which is what the refusal was
protecting.

A streaming break negotiates nothing on purpose. There is no width to fit and
no smaller unit to fall back to, so the slot must offer the format outright,
which keeps the publisher in control of what their break can carry.

fitAdFormat's own refusal is now pinned by a test, including for a slot that
lists the format, since that is the configuration someone will eventually try.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

48 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 31 | LOW: 15

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM js-dynamic-code-execution lib/crawl-limits.ts:67
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:14
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:24
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:20
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:24
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:25
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:26
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:31
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:35
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit c94505a into master Sep 24, 2026
10 checks passed
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