Skip to content

Write the asset rows, and notice when they are not written - #290

Merged
ralyodio merged 1 commit into
masterfrom
asset-rows-upsert
Sep 24, 2026
Merged

ralyodio merged 1 commit into
masterfrom
asset-rows-upsert

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

This is the root cause of everything since the RENDERER_VERSION bump.

There is a unique index on ad_video_assets (creative_id, revision, profile). Re-rendering a revision — exactly what a version bump asks for — violates it on every row.

That failure was invisible, because the insert's return value was discarded:

await supabase.from("ad_video_assets").insert(uploaded.map(...));   // no error check

So the statement failed, the job was still marked ready, and the revision kept the assets of the render it was supposed to replace.

What this explains

Every fix since the version bump has been landing in storage and then being dropped here in silence:

  • The animated banners were rendered, validated, uploaded to the bucket, and then not recorded — which is why the renderer logged animated banners: produced 3 while the table showed nothing and the campaign page showed nothing.
  • The corrected HLS codec strings never reached the existing revisions for the same reason.
  • The 298×248 dimensions and 327 KB sizes I was reading back were stale rows from before the fixes, which is why they never appeared to change no matter what I deployed.

I spent several deploys treating the symptom because the failure path was a discarded return value.

The change

Rows are upserted on that index — the same reasoning as overwriting the objects: the design did not change, the renderer did. And the error is now checked: a revision whose rows were not written points at nothing, so it fails loudly instead of being marked ready.

Verification

2570 passed / 1 failed repo-wide — the pre-existing tracker-geo failure. Both typechecks clean.

Confirmed directly in the production container before writing this: renderPreroll there returns all 8 profiles with 0 problems, and the three banners come out at exactly 300×250 / 728×90 / 320×50, 40 frames, 144 KB / 116 KB / 40 KB — all inside the 150 KB budget. The rendering was never the problem after the earlier fixes; the recording was.

There is a unique index on ad_video_assets (creative_id, revision, profile), so
re-rendering a revision — exactly what a RENDERER_VERSION bump asks for —
violated it on every row. That failure was invisible: the insert's return value
was discarded, so the statement failed, the job was still marked ready, and the
revision kept the assets of the render it was supposed to replace.

Every fix since the version bump has been landing in storage and then being
dropped here in silence. The animated banners were rendered, validated,
uploaded to the bucket, and then not recorded, which is why the renderer
reported "produced 3" while the table showed nothing and the campaign page
showed nothing. It is also why the corrected HLS codec strings never reached
the existing revisions.

The rows are now upserted on that index, for the same reason the objects are
overwritten: the design did not change, the renderer did. And the error is
checked — a revision whose rows were not written points at nothing, so it fails
loudly instead of being marked ready.

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 445f5d0 into master Sep 24, 2026
10 checks passed
@ralyodio
ralyodio deleted the asset-rows-upsert branch September 24, 2026 18:37
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