Subsystem 19 — Advertising
Purpose
Campaign/creative/targeting/rotation management filling CMS’s existing, previously-unimplemented
ad-slot component (subsystem 05) with real content — admin-curated campaigns, each with one
or more creatives, optional service-area/page-slug targeting, and stateless weighted-random
rotation among currently-eligible creatives.
Depends on
@mercatus-liber/core only. Never imports cms, service-areas, catalog,
marketing-catalog, or analytics. Targeting fields (serviceAreaId, pageSlug) are bare
strings resolved by the app-composition layer at render time — this package never resolves or
validates them, mirroring recommendations’ deliberately thin posture (see
docs/subsystems/18-recommendations.md).
Responsibilities
Campaignentity: id, name, status,startsAt/endsAt(nullable ISO 8601 date range),targeting: { serviceAreaId, pageSlug }(both nullable — null on a field means untargeted on that dimension, matches every request), an ordered list ofcreatives.Creative: id, headline, body, imageUrl, linkHref, weight (relative rotation weight, default 1).CampaignRepository(adapter pattern) + in-memory reference implementation.AdvertisingService.getActiveCreativeForSlot({ pageSlug?, serviceAreaId?, now? })— resolves every currently-eligible campaign (active status, within date range, targeting matches or is wildcard), flattens creatives, and does a stateless weighted-random pick. No persisted rotation state; computed fresh every call.- Admin CRUD (
/admin/advertising).
Explicitly NOT this subsystem’s job
- Real ad-serving (external networks, bidding, budget pacing) — an in-house promotional- placement manager for a store’s own campaigns only.
- Click-through/impression tracking or performance-based rotation optimization — analytics
(subsystem 13) has no queryable read side today; this is a documented, deferred gap, not a
silent omission. See
.pHive/epics/advertising/docs/design-discussion.md§4. - A/B testing or statistically-driven creative selection — no experimentation infrastructure exists anywhere in this repo.
- Cron/scheduled activation beyond simple
startsAt/endsAtchecked inline at render time — no job-scheduler infrastructure exists. - Changing
packages/cms’s public contract —ComponentInstance,PageRepository,CmsService, and the component registry are all untouched; the existing"ad-slot"render case inapps/reference-storefront/components/cms-sections.tsxbecomes a real component that composes this subsystem, the same app-layer pattern bundles/recommendations already established.
Decoupling notes
packages/advertising’s only dependency is @mercatus-liber/core. Verify via
grep -rn "@mercatus-liber/advertising" packages/cms packages/cart packages/checkout-orders/src packages/promotions/src packages/bundles/src packages/recommendations/src packages/inventory/src packages/pdp/src packages/service-areas/src packages/analytics/src
before merge — every one of those must return zero hits; advertising is consumed only by
apps/reference-storefront.
Open questions
- Should targeting support more dimensions (e.g. product category, customer segment) beyond
service-area and CMS page slug? Deferred — v1 covers the two cheaply-available “where”
dimensions already established elsewhere in this repo (service-areas, CMS pages); more
dimensions can be added additively to the
targetingobject without a breaking change. - A genuinely performance-optimized rotation (favoring creatives with better click-through) is a real future capability, blocked on analytics having no read side today — same deferred-not-forgotten posture as recommendations’ open question 1.