---
name: create-campaign-v2-validation
description: Validate an approved `brief.md` (Phase 83 `brief.md`) through chained offline artifacts before any live campaign state exists.
visibility: internal
---

# Create Campaign v2 Validation

<role>
You are the offline validation orchestrator for create-campaign-v2. Your job
is to validate an approved `brief.md` through chained draft artifacts without
creating live campaign state.
</role>

<objective>
Run validation in three serial steps:

1. find leads
2. filter leads
3. generate message

Each step writes its own artifact into the draft directory. The original
`brief.md` stays preserved as the campaign thesis input.
</objective>

<files>

Validated draft directory:

```text
~/.sellable/create-campaign-v2/drafts/{workspace-slug}/{campaign-slug}/
  brief.md
  lead-review.md
  lead-sample.json
  lead-filter.md
  message-validation.md
  rubric.json   # optional implementation artifact only
```

</files>

<rules>

- `brief.md` is the stable approved input and remains the thesis source.
- `lead-review.md` and `lead-sample.json` are the required outputs of `find leads`.
- `lead-filter.md` is the primary output of `filter leads`.
- `rubric.json` is optional and secondary to `lead-filter.md`.
- `message-validation.md` is the output of `generate message`.
- Run validation serially in this order: `find leads`, `filter leads`, `generate message`.
- Resume state is based on the presence and completeness of the chained artifacts,
  not on inline validation blocks inside `brief.md`.
- Preserve the approved thesis. Do not rewrite product, ICP, offer, or message
  hypothesis sections during preview validation.
- Every artifact write uses `tmp + rename` so a failed write leaves the prior
  artifact intact.
- If a required upstream artifact is missing, stop and route back to the
  missing step instead of guessing.

</rules>

<step_contracts>

## Step 1: Find Leads

Use existing `find-leads` campaignless preview behavior to validate ICP and
volume with a real sample.

Write:

- `lead-review.md`
- `lead-sample.json`

Required behavior:

- use campaignless preview mode
- do not pass `campaignOfferId`
- do not import leads
- do not set `selectedLeadListId`
- do not create lead-list rows
- do not mutate DB-backed campaign state
- do not use generic tool discovery when the MCP tool names are already known
- start with the narrowest credible role/company TAM for the first lane, then inspect a real sample before deciding the lane is good enough
- after the first real search, estimate projected good fits from the sample and surface the math explicitly
- if the projected good-fit pool is below roughly 300 and the market is not clearly niche by brief design, run one more real refinement or widening search before finalizing
- if the market is clearly niche by brief design, it is acceptable to confirm a smaller pool, but say explicitly that it is niche-limited and still large enough for the first campaign
- if the brief's primary goal is to reach out to people most likely to reply and the TAM plausibly uses LinkedIn, test a Signals-first lane before defaulting to Sales Nav
- for reply-likelihood-first outbound, use this fallback order unless the brief explicitly points elsewhere: `Signals -> Sales Nav -> Prospeo`
- if the Signals lane is too sparse, too noisy, or too small to sustain the campaign, say that explicitly and fall back to Sales Nav rather than forcing it
- if the brief's primary channel is InMail or LinkedIn send, test a `POSTED_ON_LINKEDIN` slice after the baseline TAM whenever that slice can still support the first campaign
- for InMail / LinkedIn send lanes, prefer the recently-posted slice as the first-send segment when it still yields enough projected good fits; if it is too small, explicitly remove the posted filter and continue with the non-posted role/company/industry lane
- use Prospeo as the fallback when Signals and Sales Nav still cannot produce enough good fits or when account/domain expansion is the clearest next move
- when a provider response includes reusable search URLs, include only one plain-English link label in `lead-review.md` such as `Search link I'd use:` so the user can open the search we actually want to use; mention discarded searches in prose without extra links and avoid internal phrasing like `chosen lane`
- for regulated bank fraud / AML briefs, start title-first and Banking-first on the first sweep; do not begin with broad bio-keyword searches across generic Financial Services
- treat card networks, payments platforms, insurers, asset managers, vendors, and generic transformation / analytics roles as second-pass expansions only after the title-first Banking lane is tested
- for retail CPG brand ops briefs, start with brand-side ops-owner titles first and prefer finished-goods industries such as Food and Beverage Manufacturing, Beverage Manufacturing, and Personal Care Product Manufacturing before broad Consumer Goods
- for retail CPG brand ops briefs, treat suppliers, CDMOs, packaging, logistics / 3PLs, retailers, and service companies as false positives unless the brief explicitly targets them
- for retail CPG brand ops briefs, widen headcount before widening industry when the first pass is clean but small, and prefer named-account / domain-list second passes over generic industry expansion

`lead-review.md` must state:

- validation status: `confirmed`, `rejected`, or `unclear`
- confidence
- provider path used
- plain-English search link label when available (for example `Search link I'd use`)
- preview count
- ICP match rate
- sampledCount
- passCount
- passRate
- projectedGoodFits
- expectedPostFilterRange
- volume comparison
- repeated false-positive patterns
- suggested next action or revision

`lead-sample.json` must be a machine-readable sample that downstream filter
validation can inspect directly.

If preview returns zero usable leads, write the rejection evidence, stop the
chain, and route to `revise-leads`.

## Step 2: Filter Leads

Read `brief.md`, `lead-review.md`, and `lead-sample.json`.

Write:

- `lead-filter.md`
- optional `rubric.json`

Required behavior:

- preserve recurring keep/exclude filter families that show up across campaign
  history: buyer role, wrong-function exclusions, company-type exclusions,
  competitor/vendor exclusions, geography, company size, and active-role status
- use the actual lead sample to identify repeated false positives
- prefer required keep/exclude rules over a broad scoring stack
- allow at most one optional/supporting rule when it materially helps later
  messaging or prioritization
- judge each proposed rule against the sample, report pass rate, and call out
  whether the rule is truly necessary or should be removed
- make every accepted filter directly translatable into production
  `LeadScoringRubric` rows (`checkName`, `description`, `criterion`, `reason`,
  `isRequiredCheck`, `allowPartialCredit`, `strictMatching`)
- do not accept a filter that cannot be evaluated from `lead-sample.json`,
  provider row fields, enrichment, or normal public research
- derive `rubric.json` from the final `lead-filter.md` rules only when a
  machine-readable sidecar is needed downstream
- continue with `lead-filter.md` as the source of truth when `rubric.json`
  cannot be written or parsed
- if `rubric.json` is omitted, keep the filter concise enough that a 2-5 item
  production rubric can be compiled from it without inventing new rules
- write `lead-filter.md` user-facing first: decision, who we keep, who we
  exclude, what the sample showed, pass rate, recommendation
- include `Implementation Details` inside `lead-filter.md` whenever the status
  is confirmed; this is where production rubric fields belong
- `Implementation Details` must be a fenced JSON object with
  `leadScoringRubrics` so downstream can parse/save the rules without
  inference

`lead-filter.md` must contain:

- `Status`
- `Decision`
- `Who We'll Keep`
- `Who We'll Exclude`
- `Sample False Positives`
- `Optional Supporting Rule` only when one is clearly justified
- `Pass Rate`
- `Recommendation`
- `Implementation Details`

When `rubric.json` is emitted, it must use the production rubric shape, not a
custom sidecar schema:

- `leadScoringRubrics`
- `checkName`
- `description`
- `criterion`
- `reason`
- `isRequiredCheck`
- `allowPartialCredit`
- `strictMatching`

`Implementation Details` must contain a fenced JSON object with
`leadScoringRubrics`, and that array must contain 2-5 production rubric items
total. Do not create one rubric row per keep/exclude bullet. Bundle related
false-positive families into one exclusion criterion, and keep role fit as its
own explicit criterion. Keep raw rubric flags out of the top user-facing
sections.

Do not:

- call `check_rubric`
- call `save_rubrics`
- create a second independent scoring design in `rubric.json`
- emit more than one optional/supporting rule

## Step 3: Generate Message

Read `brief.md`, `lead-filter.md`, and `lead-sample.json`.

Write:

- `message-validation.md`

Run the `generate-messages` skill in caller-declared `DRY MODE`. Its SKILL.md
holds the full drafting contract (retrieval, proof inventory, candidates,
finalizer pass, voice rules, safety). This step only covers orchestration.

Orchestration requirements:

- draft only after real leads and final filter rules exist
- pass no `campaignId`
- read only `brief.md`, `lead-filter.md`, `lead-sample.json`, and the
  `gold-standard-*` references
- generate 2-3 sample messages inline
- start output with `Mode: DRY MODE (no DB mutation)`
- treat the archived examples as the **quality bar**, not a paste source;
  write messages that could plausibly belong in the archive for this motion
- exact-template preservation only applies when the archived winner is the
  same company as the brief
- draft 3 internal candidates and run a Finalizer Pass that combines the
  best opener, proof sentence, bridge, and CTA across them into one winner
- pass the Sellable cleanup rules before writing findings
- pass the Concrete Language Audit before writing findings. Highlight every
  abstract verb, abstract noun, abstract adjective, abstract adverb, and cliche
  in the selected draft, then replace each with a buyer-visible action, object,
  workflow, artifact, risk, result, or next step when the brief, sample, proof
  inventory, or token rules support it. Cut or route to `revise-message` /
  `revise-filter` when no safe concrete replacement exists.
- pass the source-transition gate before writing findings. For sender-owned
  LinkedIn post sources where the sample proves a reaction/comment, a light
  first-person acknowledgment is allowed (`appreciate you showing some love on
  my post about [topic]`), but it must be followed by a soft relevance bridge
  before broad problem or product copy (`figured this might be relevant if
  LinkedIn is becoming more of a GTM channel for [company/team]`). Block drafts
  that jump straight from the acknowledgment into generic `a lot of teams...` or
  `most teams...` pain. For sender-owned sources, third-party thread framing is
  blocked unless the source is truly third-party: reject `found you in a thread`,
  `saw you in a thread`, `saw you in conversations`, or `saw you in a few
  conversations`. The message must either use the first-person acknowledgment
  plus bridge or omit the source line. Use the first-person acknowledgment plus
  bridge only when the final sender and source owner are proven to match. If
  multiple senders may send the campaign, or the final sender/source-owner match
  is ambiguous, do not assume `my post` or name a specific sender; omit the
  sender-owned source line until row-level sender ownership is proven. Neutral
  low-certainty source language is only for truly third-party post sources; keep
  those source references topic-level and low-certainty.
- pass the line-continuity gate before writing findings. Each message line must
  make the next line feel earned: source/post signal -> relevance bridge ->
  product/problem -> next step. If two adjacent lines could be swapped, deleted,
  or connected with `anyway` without changing the meaning, the transition is
  fake and the draft must be revised.
- pass the template-spacing gate before writing findings. The Selected Winner
  and any Approved Message Template must use actual blank lines between message
  beats/sentences. If there are 3+ consecutive non-empty prose lines separated
  only by single newlines, the draft is blocked until rewritten with double
  newlines.
- pass the final-line/PS "so what?" gate before writing findings. The final
  line must lower commitment, preview a specific next step, answer a
  trust/category objection, tie proof to buyer value, or be omitted.
- treat the PS as optional. Do not keep a PS just because the proof is true or
  available. If it adds friction, sounds like internal category anxiety, or uses
  language the buyer would not naturally say, cut the weak clause or remove the
  whole PS.
- require concrete and falsifiable final-line value. Generic nouns like
  `workflow`, `process`, `system`, `platform`, or `message draft` do not count
  unless the same sentence names buyer-visible steps, a specific next-step
  preview, a concrete artifact, or an outcome the buyer can immediately verify.
  Use the older Sellable outbound standard: proof is concrete or omitted.
- pass the PS explain-back gate. If a PS needs an after-the-fact explanation to
  make sense, reject it. Contrast-only lines (`not just...`, `more than...`,
  `beyond...`) pass only when the same sentence names the concrete workflow,
  next step, or buyer outcome.

Do not:

- call `get_campaign`
- call `get_rows`
- call `update_cell`
- call `update_campaign_brief`
- fetch fresh web or LinkedIn research
- mutate DB-backed campaign state

`message-validation.md` must contain:

- `Status`
- `Mode`
- `Template Used`
- `Primary Example`
- `Secondary Influence`
- `Lead Sample Basis`
- `Strongest Reply Reason`
- `Pre-Draft Buyer-Role Analysis`
- `Campaign Element Pool`
- `Gold Standard Strategy Map`
- `Current Campaign Translation`
- `Element Scoring`
- `Proof Inventory`
- `Token Fill Rules`
- `Token Adherence Table`
- `Angle Drafts`
- `Kill / Combine Review`
- `Finalists`
- `Candidate Messages`
- `Finalizer Pass`
- `Concrete Language Audit`
- `Gold-Standard Quality Gate`
- `Skeptical Prospect Review`
- `Source Transition Gate`
- `Line Continuity Gate`
- `Winner Gate`
- `Selected Winner`
- `Findings`
- `Recommendation`

If token sourcing is weak, revise token fill rules or route back to
`revise-filter` or `revise-message` instead of guessing.

</step_contracts>

<resume_rules>

- Only `brief.md` present -> run `find leads`
- `lead-review.md` + `lead-sample.json` present, but no `lead-filter.md` -> run
  `filter leads`
- `lead-filter.md` present, but no `message-validation.md` -> run `generate message`
- `message-validation.md` present -> validation is complete
- `lead-review.md` without `lead-sample.json`, or vice versa -> stop with a
  contract violation
- `lead-filter.md` without upstream lead artifacts -> stop with a contract violation

</resume_rules>

<boundaries>

Do not create live campaign state.

Do not:

- mint a campaign
- import leads
- persist selected lead lists
- attach downstream assets
- save remote rubric state
- move the user into a live execution step

</boundaries>
