Channel Discovery
Channel Discovery grows the shared Creator universe (ADR 0019) so that tracked Brands’ sponsor
videos are observed. It nominates Channel candidates from four sources, qualifies them, and
admits the highest-scoring ones through the same admission path as a person adding a Channel. It is
implemented in packages/api/src/channel-discovery/, runs as TypeScript jobs on Cloudflare Queues
driven by the server’s scheduled trigger, and is off by default. This page describes
implemented behavior; it does not claim Discovery is enabled in production.
Channel candidates
Section titled “Channel candidates”A candidate is keyed by its canonical UC… Channel ID, so every handle, URL, and search result for
the same Channel converges on one record. Each sighting records its source, the Brand (and
query, for Brand search) or Creator that produced it, its rank, an evidence reference, and when it
was first and last seen. Repeating a sighting increments its count; it never creates a duplicate.
| Source | Evidence reference |
|---|---|
meta_creator |
Approved YouTube social link decision, Creator name-search match, or Meta Page profile link |
brand_search |
Video (or Channel) in a Brand’s youtube-search results, with query and rank |
sponsor_graph |
A Mention: its Channel’s featured/related Channels, or Channels in the same Brand’s results |
user, staff |
A Channel a Workspace member or Mosaic staff admitted (staff = MOSAIC_STAFF_USAGE_WORKSPACE_IDS) |
seed |
A Channel from the operator seed |
Candidates are pending, qualified, rejected (with a reason), or admitted. A candidate
already in the roster is admitted with admitted_by = roster; one Discovery admitted has
admitted_by = discovery.
Discovery stores no Creator names or Channel titles. A Creator name search records the Creator’s ID, not the query text, and a name match only nominates a Channel for collection. It never links that Channel to the Creator (Creator identities still merge only by staff decision).
Sources
Section titled “Sources”- Meta partnership Creators. For every Creator with explicit Meta partnership evidence, the
current approved
youtubesocial link is resolved to its Channel through the logged-out public page and RSS feed (no Data API quota). YouTube URLs in the Creator’s confirmed Meta Page profile URL or metadata are resolved the same way. A Creator with no approved link gets onetype: channelssearch for its display name; only results whose name equals the display name after normalization (lowercase letters and digits) are sighted, at most two. - Brand search. Each active catalog Brand is searched for its name, staff product terms, and
each confirmed company domain and exact campaign hostname, in that order, at most eight queries
per Brand, once per refresh period (seven days by default). Staff add product terms with
POST /control-plane/channel-discovery/query-terms({ "brandId": "…", "term": "Skylight calendar" }); terms never become Matchers. Video and live results with an owning Channel are sighted by rank; Shorts lockups without an owner are counted but not sighted. - Sponsor graph. For each visible Mention in the last 30 days, every Channel already sighted
in that Brand’s search results gets a
sponsor_graphsighting, and the Mention Channel’s channel graph (Home featured-channel and collaboration shelves, and the Channels tab where YouTube still exposes it) is read once per refresh period and sighted. Mentions come from the current canonical Mentions and from ADR 0019youtube_mentionrows. - Existing admissions. Channels admitted by users, staff, or the seed are recorded as sightings
and marked
admitted, so Discovery never re-admits them and its metrics cover the whole roster.
Sightings that reveal a suppressed or erased Channel, video, or Creator, or a governed
candidate, are dropped (ADR 0018). A reference that resolves only to governed subjects completes
as governed and its work keeps no copy of the handle or URL.
Erasure
Section titled “Erasure”An ADR 0018 erasure scrubs Discovery in the same transaction as the rest of Mosaic:
- Candidates. The candidate of an erased Channel, and any candidate sighted through an erased
Creator’s evidence (their approved link, Meta Page profile, or name search), becomes a terminal
rejected/governedtombstone. Subscriber count, upload recency, kids status, profile source, admission score, and admission time are cleared; counts, priority, and times stay. A tombstone is never re-sighted, re-qualified, admitted, or marked as admitted from the roster. A Channel found only by an exact match on the erased Creator’s name is tombstoned too: a name match cannot prove the Channel is someone else’s, and the cost is a few Channels of coverage. - Sightings. Sightings of those candidates, sightings attributed to the erased Creator, and
sightings whose evidence is an erased video, a hidden Mention, or scrubbed work keep their source,
Brand, rank, counts, and times; their query becomes null and their evidence reference
[erased]. - Work. Reference resolution and name searches for the erased Creator, the erased Channel’s
channel graph, and references that resolved to or literally name the erased Channel keep only
{ "kind", "erased": true }. Unfinished rows complete assubject_erased. Work keys hold only identifiers, periods, Brand query text, and a SHA-256 digest of a Page-profile reference.
A sighting write, an admission, and an erasure are serialized by an advisory lock that erasure
takes exclusively before writing any receipt. A concurrent sighting is either scrubbed or refused.
The admission service re-reads the candidate under a row lock inside its transaction, so an erasure
that lands between a reservation and the admission refuses it as subject_governed; the candidate
stays a tombstone and its reservation is released. The erasure also makes each affected run’s
Apify storage due for deletion again (see below).
Qualification and scoring
Section titled “Qualification and scoring”Qualification reads up to 50 candidates at a time, highest priority first, with one official
channels.list call (snippet,statistics,status,topicDetails, one Data API unit) and each
Channel’s public RSS feed (no quota). In order, a candidate is rejected as:
| Reason | Rule |
|---|---|
governed |
The Channel or its Creator is suppressed or erased. Terminal. |
unavailable |
channels.list did not return the Channel. |
made_for_kids |
status.madeForKids is true. |
music |
Title ends in - Topic or VEVO, or every official topic is a music topic. |
brand_owned |
Title or handle is a catalog Brand’s name or confirmed-hostname label, or starts with the Brand’s name while linking to its confirmed hostname. |
no_recent_upload |
No public upload in the preceding 12 months. Re-checked after 90 days. |
madeForKids comes only from the Data API; without a Data API profile source, qualification waits
rather than guessing. Priority (before qualification) is
12 × Brands (max 5) + 6 × sources (max 4) + rank weight (Σ 3/rank, max 12) + 20 for an approved Creator link + 10 for any Meta Creator source + 15 for a channel-graph sighting (8 for a co-search sighting). The admission score adds recent activity (0–10: uploads in the last 90 days,
saturating at 12) and audience (0–10: 2.5 × (log10(subscribers) − 3), so 1K scores 0, 100K 5,
and 10M 10). Scoring version: channel-discovery-score.v1.
Admission and budgets
Section titled “Admission and budgets”Every 15 minutes an admission job admits the best qualified candidates with an admission score of
at least 30 and at least 1,000 public subscribers (hidden counts are allowed), until the daily
admission budget is used. Each admission calls the existing admission service, which re-checks the
execution policy, governance, uniqueness, and 12-month recency, creates the Channel with a
discovery contributor (no Workspace or member), forms the silent Channel-addition baseline, and
queues the first collection. A Channel governed after qualification is refused.
| Budget (per UTC day) | Default | Environment variable |
|---|---|---|
| Discovery admissions | 25 | MOSAIC_CHANNEL_DISCOVERY_DAILY_ADMISSIONS |
| Apify spend | $1.00 | MOSAIC_CHANNEL_DISCOVERY_DAILY_APIFY_USD |
| YouTube Data API units | 300 | MOSAIC_CHANNEL_DISCOVERY_DAILY_DATA_API_UNITS |
User and staff admissions do not count against the Discovery budget. An Actor run is charged at its
maxTotalChargeUsd cap ($0.02) when it starts and at Apify’s reported usageTotalUsd once it
finishes (observed 0.2.3 costs: about $0.0002 per search without extras and $0.0017 per channel
graph). A run held back only by other runs’ unsettled reservations retries in five minutes; a run
that settled spend alone would push past the day’s Apify budget waits for the next UTC day. An
admission reserves two Data API units (the admission check) and a qualification batch one. The Data
API units are also metered in the shared quota ledger and pause with it.
MOSAIC_CHANNEL_DISCOVERY_MODE is off (default: nothing is planned), observe (source and
qualify, never admit), or admit. An invalid value is off. Actor-backed sources need
APIFY_TOKEN; without it only Creator links, profile links, co-search, and existing admissions run.
Actors
Section titled “Actors”Discovery calls private Apify Actors through a pinned run client modeled on the Meta collector: it
resolves the reviewed build ID to its build number, starts with a timeout, memory, and charge cap,
verifies that the run belongs to that Actor and build, polls, and reads exactly one dataset item.
It never selects latest. An unset pin fails closed.
| Actor | Reviewed pin (brianyip/youtube-actors 0.2.3) |
|---|---|
brianatpaceline/youtube-search |
thHLqRaE7gjTv9L47, build AuAtciodNwbzvDSXY; override with MOSAIC_CHANNEL_DISCOVERY_SEARCH_BUILD_ID |
brianatpaceline/youtube-channel-details |
ImFUBTmcGFPcFeyKN, build tESQnG7LsqaDKn5sv; override with MOSAIC_CHANNEL_DISCOVERY_GRAPH_ACTOR_ID/…_BUILD_ID |
Search requests { query, type?, includeExtras: false, maxPages: 1, region: "US" } and reads the
ScrapeCreators-shaped envelope; a row whose enrichmentStatus is failed (for example BLOCKED)
is a coverage gap and is not sighted. A SUCCEEDED run with a PARTIAL status still yields its
complete listing. The channel graph requests { channelId, includeChannelGraph: true } and reads
channelGraph.channels[] (channelId, source of home_featured_channels,
home_collaborations, or channels_tab); a surface reporting failed is a gap, never an empty
graph. A new Actor build is a reviewed pin change.
Jobs and recovery
Section titled “Jobs and recovery”Durable state lives in Postgres; there is no trigger logic. Once per 15-minute slot, the scheduled
trigger plans at most 40 new work rows with idempotent keys (for example one search per Brand, query,
and refresh period); every tick syncs existing admissions and relays due row IDs to the collection queue as channel_discovery_work messages. A consumer starts a row with a
compare-and-set and a ten-minute lease, and finishes, fails, or defers it back to the outbox. Every
message is acknowledged; the row owns retries. Expired leases are re-sent by the next relay. An
Actor start writes a dispatch marker first, so a lost start response ends the row as
outcome_unknown instead of paying for a second run. Admission reservations older than an hour are
reconciled against the roster.
Apify run storage. Apify keeps each run’s default dataset (the output) and key-value store
(including its INPUT, which can carry a Creator’s name or handle). Once a run’s results are
durably recorded and its work row completes, succeeded or failed, Discovery deletes both through
DELETE /v2/actor-runs/{runId}/dataset and /key-value-store, addressed by the run ID recorded
on the row. A 404 counts as already deleted. The result is recorded on the row
(provider_storage_state pending, deleted, or abandoned, with attempts, last outcome, and
deletion time; migration 0099). Deletion is best effort and never blocks ingestion: a failure stays
pending and each relay tick sweeps due rows (up to 20) an hour later, also while Discovery is
off. After eight failed attempts the row is abandoned; an erasure that reaches the row makes it
pending again.
Staff metrics
Section titled “Staff metrics”GET /control-plane/channel-discovery/metrics (Mosaic staff browser sessions only; API keys are
refused) returns candidates by state and by source × state, rejections by reason, Discovery
admissions per day and spend per day for 14 days, cost per admitted Channel over the trailing seven
days (Apify USD and Data API units), and the work backlog by kind and state. Governed Channels are
excluded from every count. This route is not an OpenAPI operation or MCP tool.