# Campaign naming rules, read by scripts/lint_names.py.
# Draft a copy for martech-context/ together with naming-vocab.csv, and write
# both only after the naming owner approves.
#
# The linter reads a small YAML subset with no library:
# - indent with two spaces, never tabs
# - maps (key: value), inline lists [a, b, c] and block lists of plain values (- value)
# - quote any value that contains a colon or a space followed by #, and use
#   single quotes for regular expressions
# - no multi-line strings, anchors or lists of maps
#
# Platform limits under levels were checked on 2026-10-17 against the vendor
# pages in sources.yaml. Re-check before relying on a name near a limit.
# Leave platform_max out where the vendor publishes no limit.

version: '1'
owner: marketing-ops
data_owner: analytics
effective_from: '2026-10-01'

delimiter: '_'          # between tokens
word_joiner: '-'        # between words inside a token (documentation only)
lowercase: 'yes'
allowed_characters: 'a-z0-9_-'

# Fixed vocabularies live in one CSV: token, value, status (approved or
# retired), alias_of, retired_on, approved_by, approved_on, notes. The same
# file loads into the warehouse as the naming_vocab seed. The path is relative
# to this file. Change it only through the approval route in
# references/governance.md: add, alias and retire, never edit a value in place.
# A rules file may instead list values inline under vocab: (all approved),
# but not both.
vocab_file: 'naming-vocab.csv'

# Tokens checked by pattern instead of a list. A token named launch is also
# compared with the start_date column when the names file has one.
patterns:
  launch: '^20[2-9][0-9](0[1-9]|1[0-2])$'
  theme: '^(?=.{1,30}$)[a-z0-9]+(-[a-z0-9]+)*$'
  concept: '^(?=.{1,30}$)[a-z0-9]+(-[a-z0-9]+)*$'
  variant: '^v[0-9]{1,2}$'

# One entry per platform.level that appears in the names file.
#   tokens         token order, each defined in the vocabulary or under patterns
#   min_tokens     optional; trailing tokens after this count are optional
#   max_length     house limit in characters
#   platform_max   vendor limit; platform_unit is chars or bytes
#   unique_within  account, parent or none
#   duplicates     fail where the platform rejects duplicates, otherwise warn
levels:
  google_ads.campaign:
    tokens: [market, objective, funnel, audience, offer, launch]
    max_length: '100'
    platform_max: '256'       # API system limits page
    platform_unit: chars
    unique_within: account
    duplicates: fail
  google_ads.ad_group:
    tokens: [targeting, theme]
    max_length: '60'
    platform_max: '254'       # resource definition says fewer than 255
    platform_unit: chars
    unique_within: parent
    duplicates: fail
  google_ads.asset_group:
    tokens: [targeting, theme]
    max_length: '60'
    platform_max: '128'
    platform_unit: chars
    unique_within: parent
    duplicates: fail
  google_ads.ad:              # only ad types with a name field; names are immutable
    tokens: [format, concept, variant]
    max_length: '60'
    unique_within: none
  meta.campaign:
    tokens: [market, objective, funnel, audience, offer, launch]
    max_length: '100'
    unique_within: account
    duplicates: warn
  meta.ad_set:
    tokens: [targeting, theme, placement]
    max_length: '60'
    platform_max: '400'
    platform_unit: chars
    unique_within: parent
    duplicates: warn
  meta.ad:
    tokens: [format, concept, variant]
    max_length: '60'
    unique_within: parent
    duplicates: warn
  linkedin.campaign_group:
    tokens: [market, objective, funnel, audience, offer, launch]
    max_length: '100'
    platform_max: '200'
    platform_unit: bytes
    unique_within: account
    duplicates: warn
  linkedin.campaign:
    tokens: [targeting, theme, format]
    max_length: '60'
    unique_within: parent
    duplicates: warn
  linkedin.ad:
    tokens: [concept, variant]
    max_length: '60'
    unique_within: parent
    duplicates: warn

# Object IDs the linter skips, as platform:object_id. Each one needs a registry
# row with naming_exempt set to yes, a reason and a review date. The entry
# below is the fictional legacy campaign in the example files; remove it in a
# copy. A bare ID without a platform still works but matches every platform.
exempt_ids: ['google_ads:9000099']
