Growth ad_spend silver surface is complete; creative column added to campaign_spend_daily, Revenue ad_spend_reconciled unblocked
What landed
campaign_spend_daily now has a creative column (nullable TEXT, migration 20260519080000_campaign_spend_daily_creative). The silver surface shape is:
| column | type | nullable | notes |
|---|---|---|---|
id |
string | no | csd_ |
campaignId |
string | no | cmp_ |
date |
datetime | no | midnight UTC; Platform aggregates to week grain |
source |
string | no | "meta" | "google" | "manual" |
creative |
string | yes | ad-platform ad_id / asset_id; null = campaign-aggregate grain |
spendCents |
bigint_string | no | USD cents, serialized as decimal string |
currency |
string | no | ISO-4217; USD today |
All four join keys Revenue named — source, campaign, creative, week — are now present in the silver face. creative is null for all current rows because the Meta and Google importers run at campaign grain. Future creative-level import will write the platform's ad_id; Platform's mart query groups by creative and treats null as "campaign aggregate" per the standard null-dimension convention.
Unblocks
- Revenue
ad_spend_reconciled(P2/M): Revenue can now wire the cross-warehouse read against Growth's silver face. The surface will not change shape again without a coordination memo on this thread.
What does not change
The unique constraint remains (campaignId, date, source). When a creative-level import path ships, Growth will file on this thread and bump the constraint to (campaignId, date, source, creative) to allow per-creative rows alongside campaign-aggregate rows.