Skip to content

ADR-0004 · Advertising & sponsored placements platform scope

Status: 🟡 Proposed · Depends on: ADR-0003 · Drives:

Context

The AdManager review confirmed AdManager and manifest.js are properly wired (QRPlatform.CAMPAIGNS/removeCampaign/setCampaignEnabled), and that a template-embedded ad placement concept genuinely exists (servicecard.footer, among 7 slots). But three real gaps surfaced:

  1. servicecard.footer is defined but has no consumerServiceCard.dc.html (confirmed read directly) never calls getPromo/QRPlatform anywhere. Supply-side tooling exists; demand-side doesn't.
  2. A self-serve external advertiser portal is UI scaffolding only — its own code marks the invite action (demo). A real version implies a third external-facing product surface (advertiser auth, ad-spend billing) with no architecture behind it.
  3. Advertiser-uploaded creative content has no sanitization/validation model — same risk class as the Templates Custom HTML finding, but from a less-trusted external party once (2) is real.

This also connects to an already-logged, still-open trust question: ad-serving inside a paying merchant's own dashboard needs explicit product sign-off (confirmed hardcoded "Sponsored" banner in mobile-console/Home.dc.html) — a strategy decision, not a design detail.

Options considered

A — Internal-only ad ops

Admin curates and targets all campaigns directly; no external advertiser accounts, auth, or self-managed budgets. This matches what's actually implemented today — the self-serve portion is demo-only UI.

  • For: smallest trust surface; ships now with the least new architecture (just the sponsored block type from ADR-0003 plus fixing the orphaned slot).
  • Against: doesn't realize the prototype's full stated ambition of a self-serve advertiser marketplace.

B — Full self-serve advertiser marketplace

External advertisers get their own accounts, manage budget/creative themselves, and pay QRSETU directly for ad spend. This is what the AdManager prototype's copy describes.

  • For: a genuine second revenue line if there's real advertiser demand.
  • Against: a third external-facing product surface layered on top of QRSETU's existing merchant/individual audiences — needs its own auth model, its own billing (separate from ADR-0002's merchant subscription billing), its own creative-moderation pipeline, and resolves the still-open "ad-serving inside a paying merchant's dashboard needs sign-off" question implicitly by assuming yes.

C — Staged: ship A now, gate B on validated demand

Formalize A as the real, current scope — relabel the self-serve section honestly rather than leaving it reading as a working feature. Treat B as a distinct future initiative, opened only once: ad revenue is confirmed to clear an ROI bar against the trust cost of showing ads inside a paying customer's own tool, and inventory-fill (who is actually buying) has a real answer.

  • For: doesn't compound one unresolved bet (self-serve marketplace) on top of another already-open one (should merchant-dashboard ad-serving exist at all).
  • Against: slower path to a potential second revenue line, if there is real advertiser demand today.

Recommendation

C. Standing up a full self-serve advertiser marketplace before the more basic "should QRSETU sell ads inside a paying merchant's own dashboard at all" question is answered would be building a second unresolved product bet on top of a first one. Formalize A as current scope now (a design-fixable Claude Designs prompt: relabel the self-serve section as "internal ops, self-serve planned" rather than implying it's live), and log B as its own future ADR, opened only when the dashboard-ad-serving trust question is resolved and there's real advertiser demand to size against.

Regardless of A vs B timing, design the sponsored block/slot schema from ADR-0003 generically enough that both internal-curated campaigns (A, now) and eventual self-serve campaigns (B, later) can populate the same render contract — don't hardcode "admin-only" into the rendering path even while keeping the buy-side admin-only for now. This avoids a second migration later just to let a different actor create the same kind of campaign row.

Consequences

  • Fixes the orphaned servicecard.footer slot by giving ServiceCard.dc.html (or its future template equivalent, per ADR-0003) a real sponsored block that consumes it — a design-fixable prompt already exists for this in the admin-panel review.
  • Advertiser creative sanitization only needs solving for real once B is greenlit — until then, the existing human approval gate ("the ops team reviews and approves") is the acting mitigation, since the only creative submitter is internal ops itself under option A.
  • The dashboard-ad-serving trust question stays open and blocking for B specifically; it does not block A, since A is simply formalizing what's already shipped.

Open questions for product

  1. Does QRSETU want a second, ads-based revenue line at all, and does showing sponsored placements inside a paying merchant's own dashboard fit the brand (a "premium, ad-free-feeling" bar was raised during the Service Card review)?
  2. If yes to (1): what would need to be true for B to clear an ROI bar over its added trust/build cost?
  • Tracker: "servicecard.footer slot defined but not consumed," "Self-serve advertiser portal is UI scaffolding only," "Advertiser-uploaded creative content has no sanitization model," "Ad-serving inside a paying merchant's own dashboard needs explicit product sign-off"
  • Admin panel review — Ad Manager
  • ADR-0003 (the sponsored block type this depends on)