Files
getcargohq__cargo-skills/cargo-gtm/scripts/fixtures_name_validation.json
Auree Aubert 2792b990ed feat(gtm): executable QA layer — fixture-tested contact-accuracy scripts (#53)
* feat(gtm): executable QA layer — fixture-tested contact-accuracy scripts

Four deterministic TypeScript scripts under cargo-gtm/scripts/ (Node >= 22.18
native type-stripping, zero deps in file mode) replace in-context row checking:

- validate-emails.ts        free syntax/risk/duplicate cull before paid verifyEmail
- select-current-role.ts    current role from an experiences array (job changers)
- validate-linkedin-names.ts name<->profile match (same-name decoys); P>=0.95/R>=0.85 gate
- contact-accuracy-audit.ts  final per-row SEND/VERIFY/REVIEW/REMOVE stamp

Each reads --input CSV/JSON or a finished run directly (--workflow-uuid via
@cargo-ai/api, reusing the CLI's stored login) and self-tests with --fixtures
(152 synthetic cases total). New qa-script-fixtures CI job runs every suite on
push/PR. Doctrine in references/contact-accuracy.md ("run the script, don't
re-derive"); wired into the router (new section 8, spine step 8) and the
prospecting / outreach-activation verify steps. cargo-gtm -> 1.2.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(gtm): provider playbooks 6 → 20 + read-first gate (#54)

Fourteen new per-provider playbooks grounded in live `connection integration
get` schemas: sourcing/company-data (linkedin, proxycurl, apolloio, oceanio,
datagma), email/contact (hunter, prospeo, icypeas, findyMail, leadMagic,
contactOut, zeroBounce), research (firecrawl, serper). Each carries the action
table with credit costs, real config-shape examples, input quirks, cost traps,
and its recipe-spine position.

Router §11 opens with a hard read-first stop before any paid call to a covered
provider; the linter now errors when a playbook on disk is missing from the
SKILL.md catalog.

Grounding also surfaced and fixed a recipe bug: linkedin-url-lookup.md
documented findProfileUrl inputs that don't exist — the live schema is
fullName (required) + companyName. Stale cost-table rows (prospeo.verifyEmail,
serper pricing) flagged in the CHANGELOG for the next regeneration.
cargo-gtm -> 1.3.0.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(gtm): QA-chain review feedback — six wiring bugs in scripts and recipes

- contact-accuracy-audit.ts: detect a bare "status" header (raw waterfall
  verifyEmail rows) as the verification column, lowest priority so specific
  headers still win; two new fixture cases.
- lib/common.ts: accept raw execute-batch output ({"results": [...]} /
  {"records": [...]}) in JSON inputs — no jq unwrap needed.
- validate-emails.ts + contact-accuracy-audit.ts: new --json flag emits rows
  as a JSON array for jq chaining; duplicates now get recommendation "skip"
  so they never enter the paid verify batch (savings count includes them).
- prospecting.md step 6/6.5: the paid verify batch is built from the culled
  output (recommendation != "skip"), an explicit merge step joins statuses
  back onto ALL rows before the audit, and only audit_action == "SEND" rows
  proceed to write-back.
- outreach-activation.md step 4: same cull -> verify survivors -> merge ->
  audit-all -> SEND-only flow; steps 5-6 now read the audited SEND set
  (deliverable.json), with the step-5/6 jq paths fixed for array input.
- contact-accuracy.md: documents the never-pre-filter-to-valid rule, the
  execute-batch input support, and --json.

All four fixture suites pass; the full recipe chain was exercised end-to-end
on simulated batch outputs (disposable + duplicate culled before payment,
catch-all single-source -> VERIFY, only SEND rows in the handoff set).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gtm): case-insensitive status merge; strict identity coverage; dedupe verdict

- Recipes: the verification-status merge now joins on the lowercased email —
  verifyEmail results may re-case the address, and a missed join left
  emailStatus empty, silently degrading deliverable rows to VERIFY.
- contact-accuracy-audit.ts: identityColumnsPresent now requires BOTH
  name_match and role_confidence columns — one upstream script alone no
  longer masks partial identity coverage (partial-signals flag stays on).
- contact-accuracy-audit.ts: new duplicate-row REMOVE rule reading the
  is_duplicate column — a later occurrence of an address never reaches the
  sequencer even when all other signals are green (found while re-testing:
  the case-insensitive join let the dupe inherit "valid" and SEND).

23 fixture cases pass; chain re-run with re-cased verify results yields
exactly one SEND.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gtm): merge verifyEmail via email_status, not status

waterfall.verifyEmail batch rows expose the verdict on email_status per the
connector output schema. The recipe merge jq read .status, so merged rows got
an empty emailStatus and contact-accuracy-audit stamped VERIFY instead of SEND.

Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Auree Aubert <aureeaubert@users.noreply.github.com>
2026-07-10 00:16:01 -07:00

377 lines
9.8 KiB
JSON

{
"cases": [
{
"sourceName": "John Carter",
"profileName": "John Carter",
"expected": true,
"note": "exact match"
},
{
"sourceName": "MARIA LOPES",
"profileName": "maria lopes",
"expected": true,
"note": "case-insensitive"
},
{
"sourceName": "José Álvarez",
"profileName": "Jose Alvarez",
"expected": true,
"note": "accents normalized (NFD strip)"
},
{
"sourceName": "Renee Dupont",
"profileName": "Renée Dupont",
"expected": true,
"note": "accents normalized, profile side"
},
{
"sourceName": "Mike Turner",
"profileName": "Michael Turner",
"expected": true,
"note": "nickname source→formal"
},
{
"sourceName": "Michael Turner",
"profileName": "Mike Turner",
"expected": true,
"note": "nickname formal→source"
},
{
"sourceName": "Bob Whitfield",
"profileName": "Robert Whitfield",
"expected": true,
"note": "nickname Bob/Robert"
},
{
"sourceName": "Elizabeth Kraus",
"profileName": "Liz Kraus",
"expected": true,
"note": "nickname Liz/Elizabeth"
},
{
"sourceName": "Beth Nolan",
"profileName": "Elizabeth Nolan",
"expected": true,
"note": "nickname Beth/Elizabeth"
},
{
"sourceName": "Rick Osborne",
"profileName": "Richard Osborne",
"expected": true,
"note": "nickname Rick/Richard"
},
{
"sourceName": "Jim Falk",
"profileName": "James Falk",
"expected": true,
"note": "nickname Jim/James"
},
{
"sourceName": "Katie Merton",
"profileName": "Katherine Merton",
"expected": true,
"note": "nickname Katie/Katherine"
},
{
"sourceName": "Tony Marek",
"profileName": "Anthony Marek",
"expected": true,
"note": "nickname Tony/Anthony"
},
{
"sourceName": "Chris Devlin",
"profileName": "Christopher Devlin",
"expected": true,
"note": "nickname Chris/Christopher"
},
{
"sourceName": "Chris Halloran",
"profileName": "Christine Halloran",
"expected": true,
"note": "nickname Chris/Christine (same nickname, second formal)"
},
{
"sourceName": "Dave Pemberton",
"profileName": "David Pemberton",
"expected": true,
"note": "nickname Dave/David"
},
{
"sourceName": "Ted Grayson",
"profileName": "Edward Grayson",
"expected": true,
"note": "nickname Ted/Edward"
},
{
"sourceName": "Alex Riven",
"profileName": "Alexandra Riven",
"expected": true,
"note": "nickname Alex/Alexandra"
},
{
"sourceName": "Chuck Ambrose",
"profileName": "Charles Ambrose",
"expected": true,
"note": "nickname Chuck/Charles"
},
{
"sourceName": "Peggy Lindqvist",
"profileName": "Margaret Lindqvist",
"expected": true,
"note": "nickname Peggy/Margaret"
},
{
"sourceName": "Jack Tremaine",
"profileName": "John Tremaine",
"expected": true,
"note": "nickname Jack/John"
},
{
"sourceName": "Sam Ellery",
"profileName": "Samantha Ellery",
"expected": true,
"note": "nickname Sam/Samantha"
},
{
"sourceName": "Steve Calloway",
"profileName": "Stephen Calloway",
"expected": true,
"note": "nickname Steve/Stephen"
},
{
"sourceName": "Zack Templeton",
"profileName": "Zachary Templeton",
"expected": true,
"note": "nickname Zack/Zachary"
},
{
"sourceName": "Meg Ostrander",
"profileName": "Margaret Ostrander",
"expected": true,
"note": "nickname Meg/Margaret"
},
{
"sourceName": "J. Rutherford",
"profileName": "James Rutherford",
"expected": true,
"note": "source first name is an initial"
},
{
"sourceName": "Nathaniel Brooks",
"profileName": "N. Brooks",
"expected": true,
"note": "profile first name is an initial"
},
{
"sourceName": "Ana García",
"profileName": "Ana García Fernández",
"expected": true,
"note": "maiden/married double surname on profile"
},
{
"sourceName": "Laura Jensen",
"profileName": "Laura Jensen Holt",
"expected": true,
"note": "married surname appended on profile"
},
{
"sourceName": "Claire Beaumont",
"profileName": "Claire Beaumont-Ridley",
"expected": true,
"note": "hyphenated profile surname contains source surname"
},
{
"sourceName": "Priya Raghavan-Iyer",
"profileName": "Priya Raghavan",
"expected": true,
"note": "hyphenated source surname, profile has one half"
},
{
"sourceName": "Bob Callahan",
"profileName": "Robert \"Bob\" Callahan",
"expected": true,
"note": "quoted nickname in profile"
},
{
"sourceName": "Kate Winslow",
"profileName": "Katherine (Kate) Winslow",
"expected": true,
"note": "parenthesized nickname in profile"
},
{
"sourceName": "Sunny Deshpande",
"profileName": "Suniti (Sunny) Deshpande",
"expected": true,
"note": "parenthesized nickname not in the nickname table"
},
{
"sourceName": "Daniel Okafor",
"profileName": "Daniel Okafor, PhD",
"expected": true,
"note": "credentials after comma stripped"
},
{
"sourceName": "Marcus Bell Jr",
"profileName": "Marcus Bell",
"expected": true,
"note": "generational suffix stripped from source"
},
{
"sourceName": "Dr Helena Voss",
"profileName": "Helena Voss",
"expected": true,
"note": "title stripped from source"
},
{
"sourceName": "Tara Whitcombe",
"profileName": "Tara Whitcombe 🚀",
"expected": true,
"note": "emoji stripped from profile"
},
{
"sourceName": "Sofia Marchetti",
"profileName": "Sofia Elena Marchetti",
"expected": true,
"note": "middle name on profile ignored"
},
{
"sourceName": "Omar Haddad",
"profileName": "Omar K. Haddad",
"expected": true,
"note": "middle initial on profile ignored"
},
{
"sourceName": "Peter James Quill",
"profileName": "Peter Quill",
"expected": true,
"note": "middle name on source ignored"
},
{
"sourceName": "田中 太郎",
"profileName": "田中 太郎",
"expected": true,
"note": "non-Latin (CJK) exact match"
},
{
"sourceName": "Иван Смирнов",
"profileName": "Иван Смирнов",
"expected": true,
"note": "non-Latin (Cyrillic) exact match"
},
{
"sourceName": "Jen Falco",
"profileName": "Jennifer Falco Marsh",
"expected": true,
"note": "nickname plus married surname combined"
},
{
"sourceName": "WILLIAM HART",
"profileName": "Will Hart, MBA",
"expected": true,
"note": "case + nickname + credential suffix combined"
},
{
"sourceName": "John Carter",
"profileName": "John Mercer",
"expected": false,
"note": "same first name, different surname — decoy"
},
{
"sourceName": "Alice Fontaine",
"profileName": "Marie Fontaine",
"expected": false,
"note": "same surname, different first name"
},
{
"sourceName": "David Kowalski",
"profileName": "",
"expected": false,
"note": "empty profile name"
},
{
"sourceName": "Sarah Linden",
"profileName": "Tobias Grunwald",
"expected": false,
"note": "completely different names"
},
{
"sourceName": "Mike Turner",
"profileName": "Nick Turner",
"expected": false,
"note": "different nicknames of different formal names"
},
{
"sourceName": "Robert Callahan",
"profileName": "Robert",
"expected": false,
"note": "single-token profile cannot confirm surname"
},
{
"sourceName": "Иван Смирнов",
"profileName": "Пётр Волков",
"expected": false,
"note": "non-Latin (Cyrillic) mismatch"
},
{
"sourceName": "李 伟",
"profileName": "王 芳",
"expected": false,
"note": "non-Latin (CJK) mismatch"
},
{
"sourceName": "Jonathan Pierce",
"profileName": "John Pierson",
"expected": false,
"note": "near-miss decoy on both name parts"
},
{
"sourceName": "Emily Watkins",
"profileName": "Emma Watkins",
"expected": false,
"note": "similar first names are not nicknames"
},
{
"sourceName": "T. Vasquez",
"profileName": "Ramon Vasquez",
"expected": false,
"note": "initial does not match first letter"
},
{
"sourceName": "Karen Doyle",
"profileName": "LinkedIn Member",
"expected": false,
"note": "scraper placeholder garbage"
},
{
"sourceName": "Hannah Brightwater",
"profileName": "H",
"expected": false,
"note": "single-letter profile name"
},
{
"sourceName": "Noah Feldstein",
"profileName": " ",
"expected": false,
"note": "whitespace-only profile name"
},
{
"sourceName": "Greg Santos",
"profileName": "Theodore \"Teddy\" Santos",
"expected": false,
"note": "quoted nickname does not rescue a first-name mismatch"
},
{
"sourceName": "Kate Holm",
"profileName": "Katie Holm",
"expected": true,
"note": "two nicknames sharing a formal name (Kate/Katie via Katherine)"
},
{
"sourceName": "Rick Sanders",
"profileName": "Dick Sanders",
"expected": true,
"note": "shared formal via Richard"
}
]
}