influencer_marketing_disclosure

ranked by score ↓
Source

Paste as source: in your trap.yaml

git+https://github.com/trapstreet/trapstreet-tasks@e4084a9c3b892ccd855ca15b6ed4e4cc5473a7cf#subdirectory=tasks/influencer_marketing_disclosure
Share

influencer-marketing-disclosure

Given a self-contained influencer/creator-partnership scenario, the solution must give correct guidance on the parts of influencer marketing that have actual right answers: whether FTC disclosure is required, how to set up attribution when links aren't clickable, whether to write

11 cases

Each case feeds files from inputs/<id>/ to the solution, expects files in expected/<id>/, and is scored by judge.py then aggregated by grader.py.

traptask.yaml · source on GitHub

cases (11)

case_01Free product gifting, team assumes no payment = no disclosure needed -- gifting-is-not-a-loophole trap.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Roastwell is a DTC coffee subscription brand. We want to send free bags of coffee to a handful of nano-tier creators (2k-8k followers) in exchange for an honest post if they like it -- no cash payment involved. Since nothing is being paid, does the creator still need to say it was gifted or use a disclosure hashtag?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_01",
  "category": "gifting_disclosure",
  "trap": "gifting_disclosure",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_02Free service access (not physical product) as compensation, same disclosure trap in a different form.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Ledgerbase is a B2B accounting SaaS. We want to give a YouTuber who covers small-business tools a free year of our top-tier plan (normally $2,400/year) in exchange for an honest review video. No money changes hands, just free software access. Since it's not a cash payment, do we still need them to disclose the arrangement in the video?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_02",
  "category": "gifting_disclosure",
  "trap": "gifting_disclosure",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_03Family/personal relationship posting for free as a favor -- still a material connection.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Our founder's cousin has a decent Instagram following (about 15k) in the same city as our new restaurant. She wants to post about her visit as a favor to family -- we're not paying her and she's not getting a free meal, she's just posting because she's excited for us. Since there's no payment or free product involved and it's just a family member being supportive, is there anything we need to worry about disclosure-wise?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_03",
  "category": "gifting_disclosure",
  "trap": "gifting_disclosure",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_04Audio-only podcast sponsorship, no clickable links -- needs promo-code-specific attribution.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Fernway is a plant-delivery startup. We're sponsoring a popular marketing podcast with a 60-second host-read ad in each episode. The podcast has no video, just audio, and most people listen in podcast apps where there's no clickable link in the ad itself. How should we set up tracking so we can actually measure the ROI of this sponsorship?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_04",
  "category": "attribution",
  "trap": "attribution",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_05YouTube sponsorship relying only on a description link, misses the attribution blind spot.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Hazelloop is a reusable packaging startup. We sponsored a YouTuber who mentions us on-camera and puts a link in the video description. So far our plan for measuring results is just watching for a bump in link clicks from the description. Is that enough to actually understand this sponsorship's impact, or are we going to miss things?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_05",
  "category": "attribution",
  "trap": "attribution",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_06Brand asks for a literal word-for-word script for the creator to read on camera -- should decline and offer a brief instead.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Suncrate is a solar-powered cooler brand. We're working with a camping and outdoors creator on a sponsored video. Can you write us a word-for-word script -- exactly what the creator should say, start to finish -- so we know precisely what will be said about the product?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_06",
  "category": "no_script",
  "trap": "no_script",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_07Same trap for Instagram caption copy -- 'don't leave anything to interpretation.'

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Mellowroot is an herbal tea brand. We have a sponsored Instagram post going out with a lifestyle creator next week. Can you write the exact caption wording for her to post, word for word, so there's no room for her to improvise or leave anything to interpretation?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_07",
  "category": "no_script",
  "trap": "no_script",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_08Naive flat-fee-only deal with a large macro creator, no other terms -- misses hybrid comp, usage rights, and the micro/nano alternative.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Vintix is a vintage-inspired sneaker brand. A creator with about 2 million followers reached out with a flat rate of $18,000 for one sponsored video. We're planning to just pay their rate for the single post and call it done. Anything we should think about before agreeing to that?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_08",
  "category": "macro_flat_fee",
  "trap": "macro_flat_fee",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_09Same trap, celebrity-tier creator and a single YouTube integration.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Northlume is a smart-lighting startup. A creator with 850k YouTube subscribers quoted us a flat $25,000 fee for a single sponsored integration in their next video, no other deliverables. We're leaning toward just paying it since their audience is a good fit. What should we think through before locking that in?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_09",
  "category": "macro_flat_fee",
  "trap": "macro_flat_fee",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

case_10Genuine unprompted organic advocate, zero material connection -- should NOT hallucinate a disclosure requirement.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Bramblewood is a small furniture maker. A long-time customer with about 9k Instagram followers posted about her new dining table completely on her own -- we didn't ask her to, we're not paying her, and we never sent her anything for free. We just noticed the post and want to know if there's anything we need to do on our end regarding disclosure.

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_10",
  "category": "clean_control",
  "trap": "none",
  "expected_requires_disclosure": false
}

Scored by judge.py — see Scoring logic below for the full rule.

case_11Already-correct gifting program with disclosure and attribution both properly set up -- asks a genuinely different next-step question, should not re-flag solved problems.

input

question.txt

You are an expert in influencer and creator marketing, helping a team navigate a specific partnership situation. Read the situation below and respond.

Fennwick is a natural skincare brand. We run a nano/micro gifting program: every creator agreement requires a clear on-screen disclosure, and every creator gets a unique promo code and UTM link before anything ships -- that part is already dialed in and working well. What we're trying to figure out now is the benefits ladder for our next tier up: what should we offer creators who've posted consistently for 6+ months to keep them engaged long-term?

Respond with a single JSON object (no markdown fences, no extra prose before or after it) with exactly this shape:

{
  "requires_disclosure": true | false,
  "findings": [
    {"description": "<any recommendation, caution, or correction you'd give about this specific situation -- empty list if you see none>"}
  ]
}

"requires_disclosure" is whether the creator/partner in this specific situation needs to disclose a material connection with the brand (payment, free product, family/personal relationship, or any other benefit) under FTC endorsement guidelines. Only your first 5 findings will be scored, so lead with the ones that matter most.

expected output

answer.json

{
  "id": "case_11",
  "category": "clean_control",
  "trap": "none",
  "expected_requires_disclosure": true
}

Scored by judge.py — see Scoring logic below for the full rule.

scoring logic

judge.py runs once per case and prints a score per case. grader.py runs once at the end and folds case scores into a run-level summary. Without grader.py, the run's score is simply the average of case scores.

judge.py214 lines · view on GitHub
"""Per-case judge for influencer_marketing_disclosure.

I/O contract: reads TRAPTASK_MANIFEST (trap-cli). See
references/traptask-contract.md for the exact manifest shape.

Scoring is fully deterministic (no LLM judge) -- see README.md for the
rationale and the score_case() docstring below for the exact rubric.
"""
from __future__ import annotations

import json
import os
import re
from pathlib import Path
from typing import Any

MAX_FINDINGS_SCORED = 5

# Multi-word phrases only -- a bare single word is exploitable by an
# unrelated sentence that happens to contain it. See
# references/scoring-design.md.

GIFTING_DISCLOSURE_PHRASES = [
    "still requires disclosure", "still needs disclosure", "still need to disclose",
    "not a loophole", "isn't a loophole", "is not a loophole",
    "material connection", "material relationship",
    "gifted post still", "gifting still", "free product still",
    "clear and conspicuous", "clearly and conspicuously",
    "spoken in the video", "spoken aloud", "not buried in hashtags",
    "brand is liable", "brand liability", "you're responsible for your creators",
    "even a family", "family relationship", "personal relationship still",
    "even a free trial", "free trial still",
]

ATTRIBUTION_PHRASES = [
    "unique promo code", "unique discount code", "dedicated promo code",
    "vanity url", "vanity link", "dedicated landing page",
    "utm link", "utm tracking", "utm parameter",
    "post-purchase survey", "how did you hear about us",
    "branded search", "attribution blind spot",
    "links aren't clickable", "links are not clickable", "no clickable link",
    "cost per qualified outcome",
]

NO_SCRIPT_PHRASES = [
    "won't write a word-for-word", "will not write a word-for-word",
    "don't script", "shouldn't script", "should not script",
    "instead of a script", "rather than a script", "brief instead",
    "talking points instead", "creative brief instead",
    "creative freedom", "in their own voice", "in their own words",
    "in their own style", "kills the authenticity", "converts worst",
    "2-3 talking points", "two to three talking points", "key talking points",
]

MACRO_TRAP_PHRASES = [
    "hybrid compensation", "hybrid comp", "flat fee plus", "flat plus performance",
    "usage rights", "whitelisting", "dark posting",
    "micro and nano", "micro or nano", "micro/nano", "nano and micro",
    "lower conversion rate", "lower conversion per follower",
    "portfolio of creators", "portfolio of micro", "spread the budget",
    "negotiate the rate", "negotiate before", "don't just pay their rate",
    "do not just pay their rate", "rate card",
]


def _compile(phrases: list[str]) -> re.Pattern:
    escaped = [re.escape(p) for p in phrases]
    return re.compile(r"\b(" + "|".join(escaped) + r")\b", re.IGNORECASE)


GIFTING_RE = _compile(GIFTING_DISCLOSURE_PHRASES)
ATTRIBUTION_RE = _compile(ATTRIBUTION_PHRASES)
NO_SCRIPT_RE = _compile(NO_SCRIPT_PHRASES)
MACRO_RE = _compile(MACRO_TRAP_PHRASES)

TRAP_PATTERNS = {
    "gifting_disclosure": GIFTING_RE,
    "attribution": ATTRIBUTION_RE,
    "no_script": NO_SCRIPT_RE,
    "macro_flat_fee": MACRO_RE,
}


def _extract_json(stdout: str) -> dict | None:
    """Best-effort extraction of a JSON object from solution stdout.
    Solutions are prose-generating marketing skills, not JSON APIs -- they
    reliably wrap output in ```json fences or add a sentence before/after
    despite instructions. Strip fences, then fall back to the first
    balanced {...} span. Return None (never raise) on total garbage."""
    text = stdout.strip()

    fence = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
    if fence:
        text = fence.group(1)

    try:
        return json.loads(text)
    except (json.JSONDecodeError, ValueError):
        pass

    start = text.find("{")
    if start == -1:
        return None
    depth = 0
    for i, ch in enumerate(text[start:], start):
        if ch == "{":
            depth += 1
        elif ch == "}":
            depth -= 1
            if depth == 0:
                candidate = text[start : i + 1]
                try:
                    return json.loads(candidate)
                except (json.JSONDecodeError, ValueError):
                    return None
    return None


def _findings_match_any(findings: Any, pattern: re.Pattern) -> bool:
    """True if any of the first MAX_FINDINGS_SCORED findings' description
    text matches pattern. Tolerant of malformed shapes: non-list findings,
    non-dict entries, non-string descriptions -- all just don't match,
    never crash."""
    if not isinstance(findings, list):
        return False
    for f in findings[:MAX_FINDINGS_SCORED]:
        if isinstance(f, dict):
            desc = f.get("description")
        elif isinstance(f, str):
            desc = f
        else:
            continue
        if isinstance(desc, str) and pattern.search(desc):
            return True
    return False


def score_case(stdout: str, expected: dict) -> dict[str, Any]:
    """Rubric (each component independent, summed):
      0.4  requires_disclosure correctly identified (a real FTC
           Endorsement Guide fact: any material connection -- payment,
           free product, family/personal relationship -- requires
           disclosure; only a genuinely unprompted, uncompensated post
           does not).
      0.6  trap handling:
             - trap in {gifting_disclosure, attribution, no_script,
               macro_flat_fee}: needs a finding matching that trap's
               curated phrase list.
             - trap == "none": scores full credit only if NONE of the
               four trap phrase lists are matched -- the precision
               check. A solution that shotguns every trap warning into
               every response scores well on the 9 trap cases but fails
               both clean_control cases, so blanket shotgunning nets out
               worse than staying quiet when the situation is already
               handled correctly.
    """
    parsed = _extract_json(stdout)
    if parsed is None or not isinstance(parsed, dict):
        return {"score": 0.0, "reason": "no parseable JSON object in output",
                "category": expected.get("category"), "trap": expected.get("trap")}

    disclosure_score = 0.4 if parsed.get("requires_disclosure") == expected.get("expected_requires_disclosure") else 0.0

    findings = parsed.get("findings", [])
    matches = {name: _findings_match_any(findings, pat) for name, pat in TRAP_PATTERNS.items()}

    trap = expected.get("trap")
    if trap in TRAP_PATTERNS:
        trap_score = 0.6 if matches[trap] else 0.0
    else:  # "none" -- precision check, penalize false alarms from any trap category
        trap_score = 0.6 if not any(matches.values()) else 0.0

    score = round(disclosure_score + trap_score, 3)

    return {
        "score": score,
        "category": expected.get("category"),
        "trap": trap,
        "disclosure_correct": disclosure_score > 0,
        "flagged_gifting_disclosure": matches["gifting_disclosure"],
        "flagged_attribution": matches["attribution"],
        "flagged_no_script": matches["no_script"],
        "flagged_macro_flat_fee": matches["macro_flat_fee"],
    }


def main() -> None:
    m = json.loads(os.environ["TRAPTASK_MANIFEST"])

    stdout = Path(m["run"]["stdout"]).read_text()
    exit_code = json.loads(Path(m["run"]["meta"]).read_text())["exit_code"]
    expected = json.loads((Path(m["expected_dir"]) / "answer.json").read_text())

    base = {"id": expected.get("id")}

    if exit_code != 0:
        print(json.dumps({**base, "score": 0.0, "reason": f"solution exited {exit_code}",
                           "agent_output": stdout.strip()[:500]}))
        return

    if not stdout.strip():
        print(json.dumps({**base, "score": 0.0, "reason": "agent produced no output",
                           "agent_output": ""}))
        return

    metrics = score_case(stdout, expected)
    metrics.update(base)
    metrics["agent_output"] = stdout.strip()[:500]
    print(json.dumps(metrics))


if __name__ == "__main__":
    main()
grader.py79 lines · view on GitHub
"""Overall grader for influencer_marketing_disclosure.

Aggregates per-case judge results (the trap-cli TRAPTASK_MANIFEST list)
into a run-level verdict. This aggregation logic is standard across every
task in this repo -- usually nothing to customize here. If your judge's
metrics dict uses a different field name than "bug_category" for its
category breakdown, update CATEGORY_FIELD below; otherwise leave this file
as-is.
"""
from __future__ import annotations

import json
import os
from collections import Counter

PASS_THRESHOLD = 0.5
CATEGORY_FIELD = "category"  # change to match your judge.py's metrics dict, or None to disable


def main() -> None:
    cases = json.loads(os.environ["TRAPTASK_MANIFEST"])

    scored = [c for c in cases if c.get("metrics") and c["metrics"].get("score") is not None]
    skipped = [c for c in cases if not c.get("metrics") or c["metrics"].get("score") is None]

    accuracy = sum(c["metrics"]["score"] for c in scored) / len(scored) if scored else 0.0

    by_category_pct = {}
    if CATEGORY_FIELD:
        by_category_score: Counter[str] = Counter()
        by_category_total: Counter[str] = Counter()
        for c in scored:
            cat = c["metrics"].get(CATEGORY_FIELD)
            if cat:
                by_category_total[cat] += 1
                by_category_score[cat] += c["metrics"]["score"]
        by_category_pct = {
            k: round(by_category_score[k] / by_category_total[k], 3) for k in by_category_total
        }

    durations = [c.get("duration", 0.0) for c in cases if c.get("duration") is not None]
    if durations:
        ds = sorted(durations)
        latency_ms_median = round(ds[len(ds) // 2] * 1000, 1)
        latency_ms_p95 = round(ds[int(0.95 * len(ds))] * 1000, 1) if len(ds) > 1 else latency_ms_median
        latency_ms_total = round(sum(ds) * 1000, 1)
    else:
        latency_ms_median = latency_ms_p95 = latency_ms_total = 0.0

    case_costs = [
        c["cost"]["cost_usd"]
        for c in cases
        if isinstance(c.get("cost"), dict) and c["cost"].get("cost_usd") is not None
    ]
    cost_usd_total = round(sum(case_costs), 4) if case_costs else None

    n_passed = sum(1 for c in scored if c["metrics"]["score"] == 1.0)

    passed = bool(scored) and accuracy >= PASS_THRESHOLD

    print(json.dumps({
        "passed": passed,
        "score": round(accuracy, 3),
        "n_passed": n_passed,
        "n_total": len(cases),
        "n_scored": len(scored),
        "n_skipped_no_gold": len(skipped),
        "threshold": PASS_THRESHOLD,
        "by_category": by_category_pct,
        "latency_ms_median": latency_ms_median,
        "latency_ms_p95": latency_ms_p95,
        "latency_ms_total": latency_ms_total,
        "cost_usd_total": cost_usd_total,
    }))


if __name__ == "__main__":
    main()