@signaliz/sdk 1.0.17 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -483,6 +483,38 @@ var init_signals = __esm({
483
483
  constructor(client) {
484
484
  this.client = client;
485
485
  }
486
+ /** Turn the strongest supported company signal into three complete, evidence-backed emails. */
487
+ async signalToCopy(params) {
488
+ const data = await this.client.post("api/v1/signal-to-copy", {
489
+ company_domain: params.companyDomain,
490
+ person_name: params.personName,
491
+ title: params.title,
492
+ campaign_offer: params.campaignOffer,
493
+ research_prompt: params.researchPrompt
494
+ });
495
+ return {
496
+ success: data.success ?? true,
497
+ strongestSignal: data.strongest_signal ?? "",
498
+ whyNow: data.why_now ?? "",
499
+ subjectLine: data.subject_line ?? "",
500
+ openingLine: data.opening_line ?? "",
501
+ confidence: data.confidence ?? 0,
502
+ evidenceUrl: data.evidence_url ?? "",
503
+ researchPrompt: data.research_prompt,
504
+ variations: (data.variations ?? []).map((v) => ({
505
+ style: v.style,
506
+ subjectLine: v.subject_line ?? "",
507
+ openingLine: v.opening_line ?? "",
508
+ cta: v.cta ?? "",
509
+ prediction: v.prediction,
510
+ email: v.email
511
+ }))
512
+ };
513
+ }
514
+ /** @deprecated Use signalToCopy. Retained for compatibility. */
515
+ async personalize(params) {
516
+ return this.signalToCopy(params);
517
+ }
486
518
  /** Enrich a company with actionable signals (V1) */
487
519
  async enrich(params) {
488
520
  const args = {
@@ -491,6 +523,9 @@ var init_signals = __esm({
491
523
  if (params.domain) args.domain = params.domain;
492
524
  if (params.researchPrompt) args.research_prompt = params.researchPrompt;
493
525
  if (params.signalTypes) args.signal_types = params.signalTypes;
526
+ if (params.targetSignalCount) args.target_signal_count = params.targetSignalCount;
527
+ if (params.lookbackDays) args.lookback_days = params.lookbackDays;
528
+ if (params.enableDeepSearch !== void 0) args.enable_deep_search = params.enableDeepSearch;
494
529
  const data = await this.client.mcp("tools/call", {
495
530
  name: "enrich_company_signals",
496
531
  arguments: args
@@ -500,6 +535,16 @@ var init_signals = __esm({
500
535
  signals: (data.signals ?? []).map((s) => ({
501
536
  title: s.title,
502
537
  signalType: s.signal_type,
538
+ sourceType: s.source_type ?? s.signal_classification?.label ?? "unknown",
539
+ sourceProvenance: s.source_provenance ?? s.metadata?.source_provenance ?? null,
540
+ signalFamily: s.signal_family ?? s.signal_type ?? null,
541
+ signalEventType: s.signal_event_type ?? s.signal_classification?.slug ?? null,
542
+ signalEventLabel: s.signal_event_label ?? s.signal_classification?.label ?? null,
543
+ signalClassification: s.signal_classification ? {
544
+ label: s.signal_classification.label,
545
+ slug: s.signal_classification.slug,
546
+ rationale: s.signal_classification.rationale
547
+ } : null,
503
548
  confidenceScore: s.confidence_score ?? 0,
504
549
  detectedAt: s.detected_at ?? "",
505
550
  url: s.url,
@@ -540,8 +585,17 @@ var init_signals = __esm({
540
585
  content: s.content ?? "",
541
586
  date: s.date ?? null,
542
587
  sourceUrl: s.source_url ?? null,
543
- sourceType: s.source_type ?? "unknown",
544
- confidence: s.confidence ?? null
588
+ sourceType: s.source_type ?? s.signal_classification?.label ?? "unknown",
589
+ sourceProvenance: s.source_provenance ?? s.metadata?.source_provenance ?? null,
590
+ signalFamily: s.signal_family ?? s.type ?? null,
591
+ signalEventType: s.signal_event_type ?? s.signal_classification?.slug ?? null,
592
+ signalEventLabel: s.signal_event_label ?? s.signal_classification?.label ?? null,
593
+ confidence: s.confidence ?? null,
594
+ signalClassification: s.signal_classification ? {
595
+ label: s.signal_classification.label,
596
+ slug: s.signal_classification.slug,
597
+ rationale: s.signal_classification.rationale
598
+ } : null
545
599
  })),
546
600
  signalCount: data.signal_count ?? 0,
547
601
  intelligence: data.intelligence ? {
@@ -584,6 +638,58 @@ var init_signals = __esm({
584
638
  }
585
639
  };
586
640
  }
641
+ /** Plan or run experimental Signal Fusion with explicit spend controls. */
642
+ async fusion(params) {
643
+ const args = {};
644
+ if (params.companyName) args.company_name = params.companyName;
645
+ if (params.domain) args.domain = params.domain;
646
+ if (params.companyDomain) args.company_domain = params.companyDomain;
647
+ if (params.linkedinUrl) args.linkedin_url = params.linkedinUrl;
648
+ if (params.companies) args.companies = params.companies;
649
+ if (params.preset) args.preset = params.preset;
650
+ if (params.researchPrompt) args.research_prompt = params.researchPrompt;
651
+ if (params.signalTypes) args.signal_types = params.signalTypes;
652
+ if (params.lookbackDays) args.lookback_days = params.lookbackDays;
653
+ if (params.targetSignalCount) args.target_signal_count = params.targetSignalCount;
654
+ if (params.maxToolCalls) args.max_tool_calls = params.maxToolCalls;
655
+ if (params.maxTokens) args.max_tokens = params.maxTokens;
656
+ if (params.timeoutMs) args.timeout_ms = params.timeoutMs;
657
+ const dryRun = params.dryRun ?? params.dry_run;
658
+ const confirmSpend = params.confirmSpend ?? params.confirm_spend;
659
+ const maxCredits = params.maxCredits ?? params.max_credits;
660
+ const maxCostUsd = params.maxCostUsd ?? params.max_cost_usd;
661
+ if (dryRun !== void 0) args.dry_run = dryRun;
662
+ if (confirmSpend !== void 0) args.confirm_spend = confirmSpend;
663
+ if (maxCredits !== void 0) args.max_credits = maxCredits;
664
+ if (maxCostUsd !== void 0) args.max_cost_usd = maxCostUsd;
665
+ const data = await this.client.post("signal-fusion", args);
666
+ return {
667
+ success: data.success ?? false,
668
+ capability: data.capability ?? "signal_fusion",
669
+ displayName: data.display_name ?? "Signal Fusion",
670
+ company: data.company,
671
+ signals: (data.signals ?? []).map((s) => ({
672
+ id: s.id,
673
+ title: s.title,
674
+ type: s.type,
675
+ content: s.content ?? "",
676
+ date: s.date ?? null,
677
+ sourceUrl: s.source_url ?? null,
678
+ sourceType: s.source_type ?? "unknown",
679
+ confidence: s.confidence ?? null
680
+ })),
681
+ signalCount: data.signal_count ?? data.signals?.length ?? 0,
682
+ results: data.results ?? [],
683
+ model: data.model,
684
+ fusion: data.fusion,
685
+ costUsd: data.cost_usd ?? 0,
686
+ tokensUsed: data.tokens_used ?? 0,
687
+ costSummary: data.cost_summary,
688
+ creditsUsed: data.total_credits_used ?? data.credits_used ?? 0,
689
+ billingMetadata: data.billing_metadata,
690
+ raw: data
691
+ };
692
+ }
587
693
  };
588
694
  }
589
695
  });
@@ -835,6 +941,7 @@ function mapCustomerRowCounts(value) {
835
941
  acceptedRows: numberOrNull(record.accepted_rows) ?? void 0,
836
942
  usableRows: numberOrNull(record.usable_rows) ?? void 0,
837
943
  copiedRows: numberOrNull(record.copied_rows) ?? void 0,
944
+ copySkippedRows: numberOrNull(record.copy_skipped_rows) ?? void 0,
838
945
  approvalReadyRows: numberOrNull(record.approval_ready_rows) ?? void 0,
839
946
  qaReadyRows: numberOrNull(record.qa_ready_rows) ?? void 0,
840
947
  deliveredRows: numberOrNull(record.delivered_rows) ?? void 0,
@@ -881,6 +988,9 @@ function recordOrNull(value) {
881
988
  function stringArray(value) {
882
989
  return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
883
990
  }
991
+ function recordArray(value) {
992
+ return Array.isArray(value) ? value.map(recordOrNull).filter((item) => Boolean(item)) : [];
993
+ }
884
994
  function diagnosticMessages(value) {
885
995
  if (!Array.isArray(value)) return [];
886
996
  return value.map((item) => {
@@ -1042,7 +1152,8 @@ function buildArgs(request) {
1042
1152
  suppress_prior_campaign_ids: request.cacheReusePolicy.suppressPriorCampaignIds,
1043
1153
  suppress_prior_list_ids: request.cacheReusePolicy.suppressPriorListIds,
1044
1154
  uniqueness: request.cacheReusePolicy.uniqueness,
1045
- require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata
1155
+ require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata,
1156
+ verified_cache_ttl_days: request.cacheReusePolicy.verifiedCacheTtlDays
1046
1157
  };
1047
1158
  }
1048
1159
  if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
@@ -1067,7 +1178,8 @@ function buildArgs(request) {
1067
1178
  geographies: request.icp.geographies,
1068
1179
  keywords: request.icp.keywords,
1069
1180
  exclusions: request.icp.exclusions,
1070
- require_verified_email: request.icp.requireVerifiedEmail
1181
+ require_verified_email: request.icp.requireVerifiedEmail,
1182
+ persona_expansion_mode: request.icp.personaExpansionMode
1071
1183
  };
1072
1184
  }
1073
1185
  if (request.policy) {
@@ -1107,6 +1219,7 @@ function buildArgs(request) {
1107
1219
  } : void 0,
1108
1220
  sequence_steps: request.copy.sequenceSteps,
1109
1221
  copy_schema: request.copy.copySchema,
1222
+ evidence_mode: request.copy.evidenceMode,
1110
1223
  banned_phrases: request.copy.bannedPhrases,
1111
1224
  approval_required: request.copy.approvalRequired,
1112
1225
  quality_tier: request.copy.qualityTier
@@ -1247,7 +1360,11 @@ var init_campaigns = __esm({
1247
1360
  maxSupportedTargetCount: data.max_supported_target_count,
1248
1361
  brainContext: data.brain_context ?? {},
1249
1362
  learningHoldout: data.learning_holdout ?? {},
1250
- providerRoute: mapProviderRoute(data)
1363
+ providerRoute: mapProviderRoute(data),
1364
+ effectiveSourceRouting: recordOrNull(data.effective_source_routing ?? data.source_routing),
1365
+ sourcePlan: recordOrNull(data.source_plan ?? data.plan?.source_plan),
1366
+ sourceRoutes: recordArray(data.source_routes ?? data.source_plan?.routes ?? data.plan?.source_plan?.routes),
1367
+ sourceShards: recordArray(data.source_shards ?? data.source_plan?.shards ?? data.plan?.source_plan?.shards)
1251
1368
  };
1252
1369
  }
1253
1370
  async getCampaignBuildStatus(campaignBuildId) {
@@ -1368,9 +1485,10 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
1368
1485
  const estimatedCredits = estimateCredits(resolvedRequest, defaults, targetCount);
1369
1486
  const buildRequest = createBuildRequest(resolvedRequest, defaults, targetCount, campaignName, context.scopeBuildArgs, routes, memory, context.scopeBrainPreflight);
1370
1487
  const brainPreflight = asRecord(buildRequest.brainPreflight);
1488
+ const evidence = buildRequest.evidence ?? null;
1371
1489
  const approvals = deriveApprovals(resolvedRequest, routes, memory, estimatedCredits, defaults.delivery?.approvalRequired !== false);
1372
1490
  return {
1373
- planId: `cbp_${hashString(JSON.stringify({ goal: resolvedRequest.goal, campaignName, targetCount, routes }))}`,
1491
+ planId: `cbp_${hashString(JSON.stringify({ goal: resolvedRequest.goal, campaignName, targetCount, routes, evidence }))}`,
1374
1492
  campaignName,
1375
1493
  goal: resolvedRequest.goal,
1376
1494
  targetCount,
@@ -1385,6 +1503,7 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
1385
1503
  approvals,
1386
1504
  buildRequest,
1387
1505
  brainPreflight,
1506
+ evidence,
1388
1507
  operatingPlaybooks,
1389
1508
  mcpFlow: createMcpFlow(resolvedRequest, buildRequest, routes, memory, approvals),
1390
1509
  estimatedCredits,
@@ -1476,6 +1595,90 @@ function createCampaignBuilderReadiness(plan) {
1476
1595
  nextActions: campaignReadinessNextActions(plan, ready, customRoutes.length)
1477
1596
  };
1478
1597
  }
1598
+ function createCampaignBuilderActiveWorkContext(input) {
1599
+ const plan = input.readiness?.plan ?? input.plan;
1600
+ const remembered = input.remembered ?? {};
1601
+ const lastResult = asRecord(input.lastResult);
1602
+ const readinessBlockers = input.readiness?.summary.blockers ?? [];
1603
+ const explicitBlockers = input.blockers ?? [];
1604
+ const blockers = uniqueStrings([...explicitBlockers, ...readinessBlockers]);
1605
+ const warnings = uniqueStrings([
1606
+ ...input.warnings ?? [],
1607
+ ...input.readiness?.summary.warnings ?? [],
1608
+ ...plan?.warnings ?? []
1609
+ ]);
1610
+ const missingApprovals = plan ? (input.approval ? getMissingCampaignBuilderApprovals(plan, input.approval) : plan.approvals.filter((approval) => approval.blocking)).map((approval) => approval.type) : [];
1611
+ const refs = normalizeCampaignBuilderActiveWorkRefs({
1612
+ plan,
1613
+ remembered,
1614
+ refs: input.refs,
1615
+ lastResult
1616
+ });
1617
+ const state = inferCampaignBuilderActiveWorkState({
1618
+ plan,
1619
+ readiness: input.readiness,
1620
+ remembered,
1621
+ lastResult,
1622
+ blockers,
1623
+ missingApprovals
1624
+ });
1625
+ const nextSafeAction = campaignBuilderActiveWorkNextAction(state, blockers, missingApprovals, refs);
1626
+ const approvalPacket = createCampaignBuilderApprovalPacket(plan, input.approval, missingApprovals);
1627
+ const nextSafeMcpAction = campaignBuilderActiveWorkNextMcpAction({
1628
+ state,
1629
+ plan,
1630
+ refs,
1631
+ approvalPacket
1632
+ });
1633
+ const summary = {
1634
+ plan_id: plan?.planId ?? remembered.planId ?? null,
1635
+ campaign_name: plan?.campaignName ?? null,
1636
+ target_count: plan?.targetCount ?? null,
1637
+ gtm_campaign_id: plan?.buildRequest.gtmCampaignId ?? remembered.gtmCampaignId ?? null,
1638
+ campaign_build_id: stringValue(lastResult.campaign_build_id ?? lastResult.campaignBuildId) ?? remembered.campaignBuildId ?? null,
1639
+ provider_campaign_id: remembered.providerCampaignId ?? null
1640
+ };
1641
+ const approvalBoundary = campaignBuilderActiveWorkApprovalBoundary(missingApprovals, state);
1642
+ const conversationStarters = campaignBuilderActiveWorkConversationStarters({
1643
+ state,
1644
+ blockers,
1645
+ missingApprovals,
1646
+ refs,
1647
+ nextSafeAction
1648
+ });
1649
+ return {
1650
+ packet_version: "campaign-builder-active-work-context.v1",
1651
+ read_only: true,
1652
+ no_spend: true,
1653
+ no_provider_writes: true,
1654
+ private_safe: true,
1655
+ state,
1656
+ summary,
1657
+ refs,
1658
+ blockers,
1659
+ warnings,
1660
+ missing_approval_types: missingApprovals,
1661
+ approval_packet: approvalPacket,
1662
+ approval_boundary: approvalBoundary,
1663
+ next_safe_action: nextSafeAction,
1664
+ next_safe_mcp_action: nextSafeMcpAction,
1665
+ conversation_starters: conversationStarters,
1666
+ openai_agent_handoff: createCampaignBuilderOpenAiAgentHandoff({
1667
+ plan,
1668
+ state,
1669
+ summary,
1670
+ refs,
1671
+ blockers,
1672
+ warnings,
1673
+ missingApprovals,
1674
+ approvalPacket,
1675
+ approvalBoundary,
1676
+ nextSafeAction,
1677
+ nextSafeMcpAction,
1678
+ conversationStarters
1679
+ })
1680
+ };
1681
+ }
1479
1682
  function createCampaignBuilderProofReceipt(plan, result) {
1480
1683
  const strategyMemory = asRecord(plan.strategyMemoryStatus);
1481
1684
  const dryRunResult = asRecord(result);
@@ -1534,6 +1737,7 @@ function createCampaignBuilderProofReceipt(plan, result) {
1534
1737
  estimated_credits: plan.estimatedCredits,
1535
1738
  operating_playbooks: plan.operatingPlaybooks.map((playbook) => playbook.slug)
1536
1739
  },
1740
+ evidence: plan.evidence,
1537
1741
  gates,
1538
1742
  strategy_memory_status: summarizeCampaignBuilderProofStrategyMemory(strategyMemory),
1539
1743
  dry_run_result: summarizeCampaignBuilderProofDryRun(dryRunResult),
@@ -1589,6 +1793,56 @@ function getCampaignBuilderOperatingPlaybooksForRequest(request) {
1589
1793
  ]);
1590
1794
  return slugs.map(getCampaignBuilderOperatingPlaybook).filter((playbook) => Boolean(playbook));
1591
1795
  }
1796
+ function normalizeCampaignBuilderEvidence(evidence) {
1797
+ if (!evidence) return null;
1798
+ const attachmentSummary = (evidence.attachments ?? []).filter((attachment) => stringValue(attachment.name)).slice(0, 5).map((attachment) => ({
1799
+ name: stringValue(attachment.name) ?? "attachment",
1800
+ kind: stringValue(attachment.kind) ?? null,
1801
+ media_type: stringValue(attachment.mediaType) ?? null,
1802
+ redacted: attachment.redacted !== false,
1803
+ columns: uniqueStrings(attachment.columns ?? []).slice(0, 25),
1804
+ row_count: numberOrUndefined2(attachment.rowCount) ?? null,
1805
+ content_summary: stringValue(attachment.contentSummary)?.slice(0, 500) ?? null,
1806
+ evidence_fields: uniqueStrings(attachment.evidenceFields ?? []).slice(0, 25)
1807
+ }));
1808
+ const urlSummary = (evidence.urls ?? []).filter((url) => stringValue(url.url)).slice(0, 8).map((url) => {
1809
+ const rawUrl = stringValue(url.url) ?? "";
1810
+ return {
1811
+ url: rawUrl,
1812
+ host: stringValue(url.host) ?? hostFromUrl(rawUrl) ?? null,
1813
+ type: stringValue(url.type) ?? null,
1814
+ label: stringValue(url.label) ?? null,
1815
+ summary: stringValue(url.summary)?.slice(0, 500) ?? null,
1816
+ redacted: url.redacted === true
1817
+ };
1818
+ });
1819
+ const operatorNotes = uniqueStrings(evidence.notes ?? []).map((note) => note.slice(0, 240)).slice(0, 5);
1820
+ if (!attachmentSummary.length && !urlSummary.length && !operatorNotes.length) return null;
1821
+ return {
1822
+ packet_version: "campaign-builder-evidence.v1",
1823
+ private_safe: true,
1824
+ attachment_summary: attachmentSummary,
1825
+ url_summary: urlSummary,
1826
+ operator_notes: operatorNotes,
1827
+ privacy_boundary: "Use these attachment and URL summaries as planning evidence only. Do not expose raw private attachment text, raw rows, leads, emails, provider payloads, copy bodies, or secrets.",
1828
+ raw_private_fields_withheld: [
1829
+ "raw_private_attachment_text",
1830
+ "raw_attachment_rows",
1831
+ "raw_leads",
1832
+ "email_addresses",
1833
+ "provider_auth_payloads",
1834
+ "copy_bodies",
1835
+ "secrets"
1836
+ ]
1837
+ };
1838
+ }
1839
+ function hostFromUrl(value) {
1840
+ try {
1841
+ return new URL(value).hostname || void 0;
1842
+ } catch {
1843
+ return void 0;
1844
+ }
1845
+ }
1592
1846
  function applyCampaignBuilderStrategyTemplate(request) {
1593
1847
  const template = getCampaignBuilderStrategyTemplate(request.strategyTemplate);
1594
1848
  if (!template) return request;
@@ -2280,7 +2534,7 @@ function normalizeMemory(request) {
2280
2534
  };
2281
2535
  }
2282
2536
  function defaultAgencyMemoryQueries(request, configured) {
2283
- const operatingPlaybookTerms = getCampaignBuilderOperatingPlaybooksForRequest(request).flatMap((playbook) => [playbook.label, ...playbook.memoryKeywords]).join(" ");
2537
+ const operatingPlaybookTerms = getCampaignBuilderOperatingPlaybooksForRequest(request).flatMap((playbook) => [playbook.label, ...playbook.memoryKeywords, ...playbook.knowledgeSources]).join(" ");
2284
2538
  const queries = [{
2285
2539
  query: request.goal,
2286
2540
  scopes: ["workspace", configured.useNetworkPatterns ? "network_patterns" : "operator_notes"],
@@ -2446,6 +2700,21 @@ function routesFromCustomerTools(tools) {
2446
2700
  }))
2447
2701
  );
2448
2702
  }
2703
+ function normalizeCampaignBuilderLearningHoldout(input, gtmCampaignId, campaignName) {
2704
+ const source = asRecord(input);
2705
+ if (source.enabled === false) return { enabled: false };
2706
+ const controlPercentage = numberOrUndefined2(source.control_percentage ?? source.controlPercentage);
2707
+ const minControlSize = numberOrUndefined2(source.min_control_size ?? source.minControlSize);
2708
+ const experimentKey = stringValue(source.experiment_key ?? source.experimentKey) ?? (gtmCampaignId ? `gtm_campaign:${gtmCampaignId}:learning_holdout` : `campaign_builder_agent:${hashString(campaignName)}:learning_holdout`);
2709
+ return {
2710
+ enabled: true,
2711
+ controlPercentage: controlPercentage ?? 0.2,
2712
+ minControlSize: minControlSize ?? 50,
2713
+ experimentKey,
2714
+ sourceCampaignId: stringValue(source.source_campaign_id ?? source.sourceCampaignId) ?? gtmCampaignId,
2715
+ primaryMetric: stringValue(source.primary_metric ?? source.primaryMetric) ?? "positive_reply_rate"
2716
+ };
2717
+ }
2449
2718
  function createBuildRequest(request, defaults, targetCount, campaignName, scopeBuildArgs, routes = [], memory, scopeBrainPreflight) {
2450
2719
  const buildRequest = {
2451
2720
  name: campaignName,
@@ -2476,8 +2745,15 @@ function createBuildRequest(request, defaults, targetCount, campaignName, scopeB
2476
2745
  fillPolicy: "aggressive"
2477
2746
  }
2478
2747
  };
2748
+ const evidence = normalizeCampaignBuilderEvidence(request.evidence);
2749
+ if (evidence) buildRequest.evidence = evidence;
2479
2750
  const scoped = asRecord(scopeBuildArgs);
2480
2751
  buildRequest.gtmCampaignId = request.gtmCampaignId ?? stringValue(scoped.gtm_campaign_id ?? scoped.gtmCampaignId);
2752
+ buildRequest.learningHoldout = normalizeCampaignBuilderLearningHoldout(
2753
+ request.learningHoldout ?? scoped.learning_holdout ?? scoped.learningHoldout,
2754
+ buildRequest.gtmCampaignId,
2755
+ campaignName
2756
+ );
2481
2757
  if (typeof scoped.name === "string") buildRequest.name = scoped.name;
2482
2758
  if (typeof scoped.prompt === "string") buildRequest.prompt = scoped.prompt;
2483
2759
  if (typeof scoped.target_count === "number") buildRequest.targetCount = scoped.target_count;
@@ -2502,6 +2778,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
2502
2778
  memory.enabled ? "feedback" : void 0
2503
2779
  ]);
2504
2780
  const providerChain = uniqueStrings(["signaliz", ...request.preferredProviders ?? [], ...routes.map((route) => route.provider)]);
2781
+ const evidence = buildRequest.evidence ?? normalizeCampaignBuilderEvidence(request.evidence);
2505
2782
  const targetIcp = compact({
2506
2783
  personas: buildRequest.icp?.personas,
2507
2784
  industries: buildRequest.icp?.industries,
@@ -2514,6 +2791,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
2514
2791
  const learningArgs = compact({
2515
2792
  campaign_brief: request.goal,
2516
2793
  target_icp: targetIcp,
2794
+ evidence_context: evidence,
2517
2795
  layers,
2518
2796
  provider_chain: providerChain,
2519
2797
  lead_source: providerChain[0] ?? "signaliz",
@@ -2525,6 +2803,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
2525
2803
  const defaultsArgs = compact({
2526
2804
  campaign_brief: request.goal,
2527
2805
  target_icp: targetIcp,
2806
+ evidence_context: evidence,
2528
2807
  layers,
2529
2808
  include_global: memory.useNetworkPatterns,
2530
2809
  include_memory: memory.enabled,
@@ -2535,6 +2814,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
2535
2814
  provider_chain: providerChain,
2536
2815
  lead_source: providerChain[0] ?? "signaliz",
2537
2816
  target_icp: targetIcp,
2817
+ evidence_context: evidence,
2538
2818
  include_global: memory.useNetworkPatterns,
2539
2819
  limit: 25
2540
2820
  });
@@ -2547,11 +2827,14 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
2547
2827
  label: playbook.label,
2548
2828
  quality_gates: playbook.qualityGates,
2549
2829
  required_fields: playbook.requiredFields,
2550
- activation_boundary: playbook.activationBoundary
2830
+ activation_boundary: playbook.activationBoundary,
2831
+ knowledge_sources: playbook.knowledgeSources
2551
2832
  })),
2552
2833
  target_icp: targetIcp,
2834
+ evidence_context: evidence,
2553
2835
  provider_chain: providerChain,
2554
2836
  lead_source: providerChain[0] ?? "signaliz",
2837
+ learning_holdout: buildRequest.learningHoldout,
2555
2838
  tool_sequence: [
2556
2839
  { tool: "gtm_brain_learning_cycle_plan", purpose: "Plan the Brain learning loop for this campaign.", arguments: learningArgs },
2557
2840
  { tool: "gtm_brain_seed_defaults", purpose: "Seed campaign defaults from workspace memory and safe aggregate patterns.", arguments: defaultsArgs },
@@ -2821,7 +3104,7 @@ function createMcpFlow(request, buildRequest, routes, memory, approvals) {
2821
3104
  phase: "plan",
2822
3105
  tool: "nango_mcp_tools_list",
2823
3106
  arguments: {
2824
- format: "mcp"
3107
+ format: "openai"
2825
3108
  },
2826
3109
  readOnly: true,
2827
3110
  approvalRequired: false
@@ -3130,6 +3413,7 @@ function buildGtmCampaignBuildPlanArgs(request, buildRequest, routes, memory) {
3130
3413
  layers: layers.length > 0 ? layers : void 0,
3131
3414
  preferred_providers: preferredProviders.length > 0 ? preferredProviders : void 0,
3132
3415
  strategy_model: strategyModel,
3416
+ evidence_context: buildRequest.evidence ?? normalizeCampaignBuilderEvidence(request.evidence),
3133
3417
  include_strategy_patterns: campaignBuilderStrategyPatternsEnabled(agencyContext),
3134
3418
  include_workflow_patterns: campaignBuilderWorkflowPatternsEnabled(agencyContext),
3135
3419
  include_nango_catalog: agencyContext.includeNangoCatalog !== false,
@@ -3138,7 +3422,8 @@ function buildGtmCampaignBuildPlanArgs(request, buildRequest, routes, memory) {
3138
3422
  slug: playbook.slug,
3139
3423
  label: playbook.label,
3140
3424
  quality_gates: playbook.qualityGates,
3141
- activation_boundary: playbook.activationBoundary
3425
+ activation_boundary: playbook.activationBoundary,
3426
+ knowledge_sources: playbook.knowledgeSources
3142
3427
  })),
3143
3428
  include_memory: memory.enabled,
3144
3429
  include_brain: true,
@@ -3219,8 +3504,19 @@ function buildCampaignArgs(request) {
3219
3504
  if (request.dedupKeys) args.dedup_keys = request.dedupKeys;
3220
3505
  if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
3221
3506
  if (request.brainDefaults) args.brain_defaults = request.brainDefaults;
3507
+ if (request.learningHoldout) {
3508
+ args.learning_holdout = compact({
3509
+ enabled: request.learningHoldout.enabled,
3510
+ control_percentage: request.learningHoldout.controlPercentage,
3511
+ min_control_size: request.learningHoldout.minControlSize,
3512
+ experiment_key: request.learningHoldout.experimentKey,
3513
+ source_campaign_id: request.learningHoldout.sourceCampaignId,
3514
+ primary_metric: request.learningHoldout.primaryMetric
3515
+ });
3516
+ }
3222
3517
  if (request.campaignStrategyContext) args.campaign_strategy_context = request.campaignStrategyContext;
3223
3518
  if (request.deliveryRisk) args.delivery_risk = request.deliveryRisk;
3519
+ if (request.evidence) args.evidence_context = request.evidence;
3224
3520
  if (request.icp) {
3225
3521
  args.icp = compact({
3226
3522
  personas: request.icp.personas,
@@ -3270,7 +3566,8 @@ function buildCampaignArgs(request) {
3270
3566
  max_body_words: request.copy.maxBodyWords,
3271
3567
  sender_context: request.copy.senderContext,
3272
3568
  offer_context: request.copy.offerContext,
3273
- model: request.copy.model
3569
+ model: request.copy.model,
3570
+ evidence_mode: request.copy.evidenceMode
3274
3571
  });
3275
3572
  }
3276
3573
  if (request.delivery) {
@@ -3315,6 +3612,7 @@ function buildExecutionPrepareArgs(request, dryRun, confirmSpend) {
3315
3612
  delivery_risk: buildArgs2.delivery_risk,
3316
3613
  dedup_keys: buildArgs2.dedup_keys,
3317
3614
  policy: buildArgs2.policy,
3615
+ learning_holdout: buildArgs2.learning_holdout,
3318
3616
  signals: buildArgs2.signals,
3319
3617
  qualification: buildArgs2.qualification,
3320
3618
  copy: buildArgs2.copy,
@@ -3349,10 +3647,143 @@ function mapBuildResult(data, dryRun) {
3349
3647
  targetLimitReason: data.target_limit_reason,
3350
3648
  maxSupportedTargetCount: data.max_supported_target_count,
3351
3649
  brainContext: data.brain_context ?? {},
3650
+ learningHoldout: data.learning_holdout ?? {},
3352
3651
  providerRoute: mapProviderRoute2(data)
3353
3652
  };
3354
3653
  }
3654
+ function mapAgentCampaignPhaseHealth(value) {
3655
+ if (!Array.isArray(value)) return [];
3656
+ return value.map((phase) => {
3657
+ const record = asRecord(phase);
3658
+ return {
3659
+ phase: stringValue(record.phase) ?? "unknown",
3660
+ status: stringValue(record.status) ?? "unknown",
3661
+ recordsProcessed: numberOrNull2(record.records_processed ?? record.recordsProcessed),
3662
+ recordsSucceeded: numberOrNull2(record.records_succeeded ?? record.recordsSucceeded),
3663
+ recordsFailed: numberOrNull2(record.records_failed ?? record.recordsFailed),
3664
+ heartbeatAgeSeconds: numberOrNull2(record.heartbeat_age_seconds ?? record.heartbeatAgeSeconds ?? record.phase_age_seconds)
3665
+ };
3666
+ });
3667
+ }
3668
+ function deriveCampaignBuildTerminalState(input) {
3669
+ const status = input.status.toLowerCase();
3670
+ const heartbeatAgeSeconds = input.phaseAgeSeconds;
3671
+ const stale = input.staleRunningPhase === true;
3672
+ const safeWatchAction = input.triggerRunId ? `signaliz ops run-status ${input.triggerRunId} --watch` : "signaliz campaign-agent status <campaign_build_id>";
3673
+ if (CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES.has(status)) {
3674
+ return {
3675
+ kind: "blocked",
3676
+ label: "Blocked terminal state",
3677
+ reviewable: true,
3678
+ terminal: true,
3679
+ stale: false,
3680
+ phase: input.phase,
3681
+ phaseStatus: input.phaseStatus,
3682
+ heartbeatAgeSeconds,
3683
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
3684
+ terminalReason: input.terminalReason,
3685
+ staleReason: null,
3686
+ safeNextAction: input.nextAction || "Review errors and rerun only after the blocker is resolved."
3687
+ };
3688
+ }
3689
+ if (status === "pending_approval") {
3690
+ return {
3691
+ kind: "pending_approval",
3692
+ label: "Pending delivery approval",
3693
+ reviewable: true,
3694
+ terminal: false,
3695
+ stale: false,
3696
+ phase: input.phase,
3697
+ phaseStatus: input.phaseStatus,
3698
+ heartbeatAgeSeconds,
3699
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
3700
+ terminalReason: input.terminalReason,
3701
+ staleReason: null,
3702
+ safeNextAction: input.approvalAction || input.nextAction || "Review rows and approve delivery only after the approval packet passes."
3703
+ };
3704
+ }
3705
+ if (CAMPAIGN_BUILDER_TERMINAL_STATUSES.has(status)) {
3706
+ return {
3707
+ kind: "terminal",
3708
+ label: "Terminal and reviewable",
3709
+ reviewable: true,
3710
+ terminal: true,
3711
+ stale: false,
3712
+ phase: input.phase,
3713
+ phaseStatus: input.phaseStatus,
3714
+ heartbeatAgeSeconds,
3715
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
3716
+ terminalReason: input.terminalReason,
3717
+ staleReason: null,
3718
+ safeNextAction: input.nextAction || "Review rows, artifacts, scorecard, and approval locks before delivery."
3719
+ };
3720
+ }
3721
+ if (status === "running" || status === "queued" || status === "processing") {
3722
+ if (stale) {
3723
+ return {
3724
+ kind: "running_stale",
3725
+ label: "Running but stale",
3726
+ reviewable: false,
3727
+ terminal: false,
3728
+ stale: true,
3729
+ phase: input.phase,
3730
+ phaseStatus: input.phaseStatus,
3731
+ heartbeatAgeSeconds,
3732
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
3733
+ terminalReason: null,
3734
+ staleReason: input.staleReason || "The active phase heartbeat is stale.",
3735
+ safeNextAction: input.nextAction || safeWatchAction
3736
+ };
3737
+ }
3738
+ return {
3739
+ kind: "running_healthy",
3740
+ label: "Running and healthy",
3741
+ reviewable: false,
3742
+ terminal: false,
3743
+ stale: false,
3744
+ phase: input.phase,
3745
+ phaseStatus: input.phaseStatus,
3746
+ heartbeatAgeSeconds,
3747
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
3748
+ terminalReason: null,
3749
+ staleReason: null,
3750
+ safeNextAction: input.nextAction || safeWatchAction
3751
+ };
3752
+ }
3753
+ return {
3754
+ kind: "unknown",
3755
+ label: "Unknown build state",
3756
+ reviewable: false,
3757
+ terminal: false,
3758
+ stale,
3759
+ phase: input.phase,
3760
+ phaseStatus: input.phaseStatus,
3761
+ heartbeatAgeSeconds,
3762
+ nextPollAfterSeconds: input.nextPollAfterSeconds,
3763
+ terminalReason: input.terminalReason,
3764
+ staleReason: input.staleReason,
3765
+ safeNextAction: input.nextAction || "Read campaign build status again before making a launch or delivery decision."
3766
+ };
3767
+ }
3355
3768
  function mapAgentCampaignBuildStatus(data) {
3769
+ const customerRowCounts = mapAgentCustomerRowCounts(data.customer_row_counts);
3770
+ const phaseHealth = mapAgentCampaignPhaseHealth(data.phases);
3771
+ const terminalReason = stringValue(data.terminal_reason) ?? customerRowCounts?.terminalReason ?? null;
3772
+ const staleReason = stringValue(data.stale_reason) ?? stringValue(asRecord(data.diagnostics).stale_reason) ?? null;
3773
+ const nextPollAfterSeconds = numberOrNull2(data.next_poll_after_seconds ?? data.nextPollAfterSeconds);
3774
+ const terminalState = deriveCampaignBuildTerminalState({
3775
+ status: stringValue(data.status) ?? "unknown",
3776
+ phase: stringValue(data.current_phase) ?? null,
3777
+ phaseStatus: stringValue(data.current_phase_status) ?? null,
3778
+ staleRunningPhase: data.stale_running_phase === true,
3779
+ phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
3780
+ nextPollAfterSeconds,
3781
+ terminalReason,
3782
+ staleReason,
3783
+ nextAction: formatAgentNextAction(data.next_action),
3784
+ approvalAction: formatAgentNextAction(data.approval_action),
3785
+ triggerRunId: stringValue(data.trigger_run_id) ?? null
3786
+ });
3356
3787
  return {
3357
3788
  campaignBuildId: stringValue(data.campaign_build_id) ?? "",
3358
3789
  campaignId: stringValue(data.campaign_id) ?? null,
@@ -3370,12 +3801,18 @@ function mapAgentCampaignBuildStatus(data) {
3370
3801
  errors: diagnosticMessages2(data.errors),
3371
3802
  artifactCount: numberOrUndefined2(data.artifact_count) ?? 0,
3372
3803
  artifactDownloads: Array.isArray(data.artifact_downloads) ? data.artifact_downloads.map(mapAgentCampaignArtifactDownload) : [],
3373
- customerRowCounts: mapAgentCustomerRowCounts(data.customer_row_counts),
3804
+ customerRowCounts,
3374
3805
  phaseAttemptTotals: nullableRecord(data.phase_attempt_totals) ?? void 0,
3375
3806
  providerRoute: mapProviderRoute2(data),
3376
3807
  triggerRunId: stringValue(data.trigger_run_id) ?? null,
3377
3808
  staleRunningPhase: data.stale_running_phase === true,
3378
3809
  phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
3810
+ nextPollAfterSeconds,
3811
+ terminalReason,
3812
+ staleReason,
3813
+ safeNextAction: terminalState.safeNextAction,
3814
+ terminalState,
3815
+ phaseHealth,
3379
3816
  diagnostics: nullableRecord(data.diagnostics) ?? {},
3380
3817
  nextAction: formatAgentNextAction(data.next_action),
3381
3818
  approvalAction: formatAgentNextAction(data.approval_action),
@@ -3405,6 +3842,7 @@ function mapAgentCustomerRowCounts(value) {
3405
3842
  acceptedRows: numberOrUndefined2(record.accepted_rows),
3406
3843
  usableRows: numberOrUndefined2(record.usable_rows),
3407
3844
  copiedRows: numberOrUndefined2(record.copied_rows),
3845
+ copySkippedRows: numberOrUndefined2(record.copy_skipped_rows),
3408
3846
  approvalReadyRows: numberOrUndefined2(record.approval_ready_rows),
3409
3847
  qaReadyRows: numberOrUndefined2(record.qa_ready_rows),
3410
3848
  deliveredRows: numberOrUndefined2(record.delivered_rows),
@@ -3463,67 +3901,276 @@ function mapAgentCampaignArtifact(data) {
3463
3901
  createdAt: stringValue(data.created_at) ?? ""
3464
3902
  };
3465
3903
  }
3904
+ function campaignReviewGate(id, label, status, detail) {
3905
+ return {
3906
+ id,
3907
+ label,
3908
+ status,
3909
+ passed: status !== "blocked",
3910
+ detail
3911
+ };
3912
+ }
3913
+ function northStarMetric(id, label, status, detail, value, target) {
3914
+ return {
3915
+ id,
3916
+ label,
3917
+ status,
3918
+ detail,
3919
+ ...value !== void 0 ? { value } : {},
3920
+ ...target !== void 0 ? { target } : {}
3921
+ };
3922
+ }
3923
+ function createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState) {
3924
+ if (!terminalState.reviewable) {
3925
+ const pendingMetric = northStarMetric(
3926
+ "terminal_state",
3927
+ "Terminal-state contract",
3928
+ terminalState.kind === "running_stale" ? "blocked" : "pending",
3929
+ terminalState.kind === "running_stale" ? `Build is stale in ${terminalState.phase || "unknown phase"}; do not grade output yet.` : `Build is ${terminalState.label.toLowerCase()}; wait for terminal or approval state before grading.`,
3930
+ terminalState.kind,
3931
+ "terminal or pending_approval"
3932
+ );
3933
+ return {
3934
+ version: "campaign-builder-north-star.v1",
3935
+ status: pendingMetric.status,
3936
+ score: null,
3937
+ moatState: "scaffolding_ready",
3938
+ metrics: [pendingMetric],
3939
+ blockers: pendingMetric.status === "blocked" ? [pendingMetric.detail] : [],
3940
+ warnings: pendingMetric.status === "pending" ? [pendingMetric.detail] : [],
3941
+ nextAction: terminalState.safeNextAction
3942
+ };
3943
+ }
3944
+ const sampledRows = rows.rows.length;
3945
+ const rowData = rows.rows.map((row) => asRecord(row.data));
3946
+ const requestedTarget = status.customerRowCounts?.requestedTarget ?? status.recordsTotal ?? rows.count;
3947
+ const finalRows = status.customerRowCounts?.deliveredRows ?? status.customerRowCounts?.qaReadyRows ?? status.customerRowCounts?.copySkippedRows ?? status.customerRowCounts?.copiedRows ?? status.customerRowCounts?.acceptedRows ?? rows.count;
3948
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
3949
+ const verifiedEmailRows = rowData.filter(campaignReviewDataHasVerifiedEmail).length;
3950
+ const uniqueEmails = new Set(rowData.map(campaignReviewDataEmail).filter(Boolean)).size;
3951
+ const uniqueDomains = new Set(rowData.map(campaignReviewDataDomain).filter(Boolean)).size;
3952
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
3953
+ const copyQaIssuesByRow = rowData.map(campaignReviewDataCopyQaIssues).filter((issues) => issues.length > 0);
3954
+ const copyQaIssueCounts = countCampaignReviewIssues(copyQaIssuesByRow);
3955
+ const signalBackedRows = rowData.filter(campaignReviewDataHasSignalBasis).length;
3956
+ const noSupportedSignalRows = rowData.filter(campaignReviewDataHasNoSupportedSignal).length;
3957
+ const unsupportedSignalClaims = rowData.filter(campaignReviewDataHasUnsupportedSignalClaim).length;
3958
+ const approvalLockedRows = rowData.filter(campaignReviewDataHasApprovalLock).length;
3959
+ const holdoutRows = rowData.filter(campaignReviewDataHasHoldoutAttribution).length;
3960
+ const artifactCount = artifacts.length || status.artifactCount || 0;
3961
+ const deliveredRows = status.customerRowCounts?.deliveredRows ?? 0;
3962
+ const feedbackReady = campaignReviewStatusHasFeedbackReadiness(status);
3963
+ const feedbackRequiredForApproval = terminalState.kind === "pending_approval";
3964
+ const moat = campaignReviewMoatState(status, holdoutRows);
3965
+ const metrics = [
3966
+ northStarMetric(
3967
+ "terminal_state",
3968
+ "Terminal-state contract",
3969
+ terminalState.kind === "blocked" ? "blocked" : "pass",
3970
+ terminalState.label,
3971
+ terminalState.kind,
3972
+ "terminal or pending_approval"
3973
+ ),
3974
+ northStarMetric(
3975
+ "target_fill",
3976
+ "Target fill",
3977
+ requestedTarget && finalRows < requestedTarget ? "review" : "pass",
3978
+ requestedTarget ? `${finalRows || 0}/${requestedTarget} final or reviewable row(s) are accounted for.` : "No requested target was reported; using sampled rows for review.",
3979
+ finalRows || sampledRows,
3980
+ requestedTarget || sampledRows
3981
+ ),
3982
+ northStarMetric(
3983
+ "unique_emails",
3984
+ "Unique emails",
3985
+ sampledRows === 0 ? "blocked" : uniqueEmails === rowsWithEmail ? "pass" : "blocked",
3986
+ sampledRows === 0 ? "No sampled rows were available for email uniqueness review." : `${uniqueEmails}/${rowsWithEmail} sampled email(s) are unique.`,
3987
+ uniqueEmails,
3988
+ rowsWithEmail
3989
+ ),
3990
+ northStarMetric(
3991
+ "unique_domains",
3992
+ "Unique domains",
3993
+ sampledRows === 0 ? "blocked" : uniqueDomains === sampledRows ? "pass" : "review",
3994
+ sampledRows === 0 ? "No sampled rows were available for domain uniqueness review." : `${uniqueDomains}/${sampledRows} sampled company domain(s) are unique.`,
3995
+ uniqueDomains,
3996
+ sampledRows
3997
+ ),
3998
+ northStarMetric(
3999
+ "verified_email_rows",
4000
+ "Verified email rows",
4001
+ sampledRows === 0 ? "blocked" : verifiedEmailRows === sampledRows ? "pass" : rowsWithEmail === sampledRows ? "review" : "blocked",
4002
+ sampledRows === 0 ? "No sampled rows were available for email verification review." : `${verifiedEmailRows}/${sampledRows} sampled row(s) carry verified-email evidence.`,
4003
+ verifiedEmailRows,
4004
+ sampledRows
4005
+ ),
4006
+ northStarMetric(
4007
+ "copy_completeness",
4008
+ "Copy completeness",
4009
+ sampledRows === 0 ? "blocked" : copyReadyRows === sampledRows ? "pass" : "blocked",
4010
+ sampledRows === 0 ? "No sampled rows were available for copy review." : `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.`,
4011
+ copyReadyRows,
4012
+ sampledRows
4013
+ ),
4014
+ northStarMetric(
4015
+ "copy_quality",
4016
+ "Copy QA",
4017
+ copyQaIssuesByRow.length === 0 ? "pass" : "blocked",
4018
+ copyQaIssuesByRow.length === 0 ? "No sampled copy QA blockers were detected." : `${copyQaIssuesByRow.length}/${sampledRows} sampled row(s) have copy QA issue(s): ${formatCampaignReviewIssueCounts(copyQaIssueCounts)}.`,
4019
+ copyQaIssuesByRow.length,
4020
+ 0
4021
+ ),
4022
+ northStarMetric(
4023
+ "signal_grounding",
4024
+ "Signal grounding",
4025
+ unsupportedSignalClaims > 0 ? "blocked" : signalBackedRows > 0 ? "pass" : "review",
4026
+ unsupportedSignalClaims > 0 ? `${unsupportedSignalClaims} sampled row(s) make unsupported signal claims.` : signalBackedRows > 0 ? `${signalBackedRows}/${sampledRows} sampled row(s) include supported signal or personalization basis.` : noSupportedSignalRows > 0 ? `${noSupportedSignalRows}/${sampledRows} sampled row(s) are explicitly marked no_supported_signal; copy should stay conservative.` : "No sampled rows include supported signal evidence; copy should stay conservative.",
4027
+ signalBackedRows,
4028
+ sampledRows
4029
+ ),
4030
+ northStarMetric(
4031
+ "approval_locks",
4032
+ "Approval locks",
4033
+ approvalLockedRows > 0 || deliveredRows === 0 ? "pass" : "review",
4034
+ approvalLockedRows > 0 ? `${approvalLockedRows}/${sampledRows} sampled row(s) show approval/export lock metadata.` : "Approval-lock metadata was not visible in sampled rows; preserve delivery approval gates.",
4035
+ approvalLockedRows,
4036
+ sampledRows
4037
+ ),
4038
+ northStarMetric(
4039
+ "export_send_safety",
4040
+ "Export/send safety",
4041
+ deliveredRows > 0 && status.status !== "completed" ? "blocked" : "pass",
4042
+ deliveredRows > 0 ? `${deliveredRows} row(s) are delivered; status is ${status.status}.` : "No delivered rows are reported before approval.",
4043
+ deliveredRows,
4044
+ 0
4045
+ ),
4046
+ northStarMetric(
4047
+ "holdout_attribution",
4048
+ "Holdout attribution",
4049
+ holdoutRows > 0 ? "pass" : "review",
4050
+ holdoutRows > 0 ? `${holdoutRows}/${sampledRows} sampled row(s) preserve holdout attribution.` : "No holdout attribution was visible in the sampled rows.",
4051
+ holdoutRows,
4052
+ sampledRows
4053
+ ),
4054
+ northStarMetric(
4055
+ "feedback_readiness",
4056
+ "Feedback readiness",
4057
+ feedbackReady ? "pass" : feedbackRequiredForApproval ? "blocked" : "review",
4058
+ feedbackReady ? "Feedback readiness is visible in status diagnostics." : feedbackRequiredForApproval ? "Feedback webhook or approved pull readiness is required before delivery approval." : "Feedback webhook or approved pull readiness was not visible; require it before real send approval.",
4059
+ feedbackReady,
4060
+ true
4061
+ ),
4062
+ northStarMetric(
4063
+ "causal_moat",
4064
+ "Causal moat",
4065
+ moat.state === "proved" ? "pass" : "review",
4066
+ moat.detail,
4067
+ moat.state,
4068
+ "proved"
4069
+ ),
4070
+ northStarMetric(
4071
+ "artifact_readback",
4072
+ "Artifact readback",
4073
+ artifactCount > 0 ? "pass" : "review",
4074
+ artifactCount > 0 ? `${artifactCount} artifact(s) are available for review.` : "No review artifact is available yet.",
4075
+ artifactCount,
4076
+ 1
4077
+ )
4078
+ ];
4079
+ const gradedMetrics = metrics.filter((metric) => metric.status !== "pending");
4080
+ const score = gradedMetrics.length === 0 ? null : Math.round(gradedMetrics.filter((metric) => metric.status === "pass").length / gradedMetrics.length * 100);
4081
+ const blockers = metrics.filter((metric) => metric.status === "blocked").map((metric) => metric.detail);
4082
+ const warnings = metrics.filter((metric) => metric.status === "review").map((metric) => metric.detail);
4083
+ const scorecardStatus = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "review" : "pass";
4084
+ return {
4085
+ version: "campaign-builder-north-star.v1",
4086
+ status: scorecardStatus,
4087
+ score,
4088
+ moatState: moat.state,
4089
+ metrics,
4090
+ blockers,
4091
+ warnings,
4092
+ nextAction: scorecardStatus === "blocked" ? "Fix blocked North Star gates before approval or delivery." : terminalState.safeNextAction
4093
+ };
4094
+ }
3466
4095
  function createCampaignBuilderBuildReview(status, rows, artifacts) {
4096
+ const terminalState = status.terminalState ?? deriveCampaignBuildTerminalState({
4097
+ status: status.status,
4098
+ phase: status.currentPhase,
4099
+ phaseStatus: status.currentPhaseStatus,
4100
+ staleRunningPhase: status.staleRunningPhase === true,
4101
+ phaseAgeSeconds: status.phaseAgeSeconds ?? null,
4102
+ nextPollAfterSeconds: status.nextPollAfterSeconds ?? null,
4103
+ terminalReason: status.terminalReason ?? status.customerRowCounts?.terminalReason ?? null,
4104
+ staleReason: status.staleReason ?? null,
4105
+ nextAction: status.nextAction,
4106
+ approvalAction: status.approvalAction,
4107
+ triggerRunId: status.triggerRunId ?? null
4108
+ });
3467
4109
  const sampledRows = rows.rows.length;
3468
4110
  const availableRows = rows.count || sampledRows;
3469
4111
  const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
3470
4112
  const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
3471
4113
  const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
3472
4114
  const artifactCount = artifacts.length || status.artifactCount || 0;
4115
+ const northStarScorecard = createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState);
3473
4116
  const gates = [
3474
- {
3475
- id: "build_completed",
3476
- label: "Build completed",
3477
- passed: status.status === "completed",
3478
- detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
3479
- },
3480
- {
3481
- id: "rows_available",
3482
- label: "Rows available",
3483
- passed: sampledRows > 0,
3484
- detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
3485
- },
3486
- {
3487
- id: "qualified_sample",
3488
- label: "Sample rows qualified",
3489
- passed: sampledRows > 0 && qualifiedRows === sampledRows,
3490
- detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
3491
- },
3492
- {
3493
- id: "email_coverage",
3494
- label: "Email coverage",
3495
- passed: sampledRows > 0 && rowsWithEmail === sampledRows,
3496
- detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
3497
- },
3498
- {
3499
- id: "artifact_available",
3500
- label: "Artifact available",
3501
- passed: artifactCount > 0,
3502
- detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
3503
- },
3504
- {
3505
- id: "no_failed_rows",
3506
- label: "No failed rows",
3507
- passed: status.recordsFailed === 0,
3508
- detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
3509
- }
4117
+ campaignReviewGate(
4118
+ "build_reviewable",
4119
+ "Build reviewable",
4120
+ terminalState.kind === "running_healthy" ? "pending" : terminalState.kind === "running_stale" || terminalState.kind === "blocked" ? "blocked" : "pass",
4121
+ terminalState.label
4122
+ ),
4123
+ campaignReviewGate(
4124
+ "rows_available",
4125
+ "Rows available",
4126
+ sampledRows > 0 ? "pass" : terminalState.reviewable ? "blocked" : "pending",
4127
+ sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : terminalState.reviewable ? "No rows were returned for review." : "Rows are not graded until the build is reviewable."
4128
+ ),
4129
+ campaignReviewGate(
4130
+ "qualified_sample",
4131
+ "Sample rows qualified",
4132
+ sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : qualifiedRows === sampledRows ? "pass" : "blocked",
4133
+ sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
4134
+ ),
4135
+ campaignReviewGate(
4136
+ "email_coverage",
4137
+ "Email coverage",
4138
+ sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : rowsWithEmail === sampledRows ? "pass" : "blocked",
4139
+ sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
4140
+ ),
4141
+ campaignReviewGate(
4142
+ "artifact_available",
4143
+ "Artifact available",
4144
+ artifactCount > 0 ? "pass" : terminalState.reviewable ? "review" : "pending",
4145
+ artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
4146
+ ),
4147
+ campaignReviewGate(
4148
+ "no_failed_rows",
4149
+ "No failed rows",
4150
+ status.recordsFailed === 0 ? "pass" : "blocked",
4151
+ status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
4152
+ )
3510
4153
  ];
3511
- const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
4154
+ const blockedReasons = gates.filter((gate) => gate.status === "blocked" || !gate.passed).map((gate) => gate.detail);
3512
4155
  const warnings = [
3513
4156
  rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
3514
4157
  sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
3515
4158
  ...status.warnings
3516
4159
  ].filter((item) => Boolean(item));
3517
- const readyForDeliveryApproval = blockedReasons.length === 0;
4160
+ const readyForDeliveryApproval = terminalState.reviewable && blockedReasons.length === 0 && northStarScorecard.status !== "blocked";
3518
4161
  return {
3519
4162
  campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
3520
4163
  status,
3521
4164
  rows,
3522
4165
  artifacts,
3523
4166
  gates,
4167
+ northStarScorecard,
3524
4168
  summary: {
3525
4169
  status: status.status,
3526
4170
  phase: status.currentPhase,
4171
+ terminalState: terminalState.kind,
4172
+ scorecardStatus: northStarScorecard.status,
4173
+ scorecardScore: northStarScorecard.score,
3527
4174
  sampledRows,
3528
4175
  availableRows,
3529
4176
  qualifiedRows,
@@ -3577,6 +4224,184 @@ function campaignReviewRowHasCopy(row) {
3577
4224
  stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(data.opener) || stringValue(data.body) || stringValue(data.cta) || stringValue(copy.subject) || stringValue(copy.body) || stringValue(rawCopy.subject) || stringValue(rawCopy.body)
3578
4225
  );
3579
4226
  }
4227
+ function campaignReviewDataEmail(data) {
4228
+ return stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail) || stringValue(asRecord(data.contact).email);
4229
+ }
4230
+ function campaignReviewDataDomain(data) {
4231
+ const domain = stringValue(data.company_domain) || stringValue(data.companyDomain) || stringValue(asRecord(data.company).domain);
4232
+ return domain?.toLowerCase();
4233
+ }
4234
+ function campaignReviewDataHasVerifiedEmail(data) {
4235
+ const status = String(data.email_status ?? data.emailStatus ?? asRecord(data.contact).email_status ?? "").toLowerCase();
4236
+ return data.email_verified === true || data.emailVerified === true || asRecord(data.contact).email_verified === true || ["valid", "validated", "verified"].includes(status);
4237
+ }
4238
+ function campaignReviewDataCopyParts(data) {
4239
+ const copy = asRecord(data.copy);
4240
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
4241
+ return [
4242
+ stringValue(data.subject),
4243
+ stringValue(data.subject_line),
4244
+ stringValue(data.opener),
4245
+ stringValue(data.body),
4246
+ stringValue(data.cta),
4247
+ stringValue(copy.subject),
4248
+ stringValue(copy.opener),
4249
+ stringValue(copy.body),
4250
+ stringValue(copy.cta),
4251
+ stringValue(rawCopy.subject),
4252
+ stringValue(rawCopy.opener),
4253
+ stringValue(rawCopy.body),
4254
+ stringValue(rawCopy.cta)
4255
+ ].filter((part) => Boolean(part));
4256
+ }
4257
+ function campaignReviewDataHasBadGreeting(data) {
4258
+ return campaignReviewDataCopyParts(data).some(
4259
+ (part) => /\bhi\s*(?:,|\{\{|\[)|\bhello\s*(?:,|\{\{|\[)/i.test(part) || /\b(first_name|firstname|name)\b/i.test(part)
4260
+ );
4261
+ }
4262
+ function campaignReviewDataHasBannedPhrase(data) {
4263
+ const text = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
4264
+ return NORTH_STAR_BANNED_COPY_PHRASES.some((phrase) => text.includes(phrase));
4265
+ }
4266
+ function campaignReviewDataSubject(data) {
4267
+ const copy = asRecord(data.copy);
4268
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
4269
+ return stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(copy.subject) || stringValue(rawCopy.subject);
4270
+ }
4271
+ function campaignReviewDataCta(data) {
4272
+ const copy = asRecord(data.copy);
4273
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
4274
+ return stringValue(data.cta) || stringValue(copy.cta) || stringValue(rawCopy.cta);
4275
+ }
4276
+ function countCampaignReviewIssues(issueRows) {
4277
+ return issueRows.flat().reduce((acc, issue) => {
4278
+ acc[issue] = (acc[issue] || 0) + 1;
4279
+ return acc;
4280
+ }, {});
4281
+ }
4282
+ function formatCampaignReviewIssueCounts(counts) {
4283
+ const entries = Object.entries(counts);
4284
+ return entries.length > 0 ? entries.map(([issue, count]) => `${issue}=${count}`).join(", ") : "none";
4285
+ }
4286
+ function campaignReviewDataCopyQaIssues(data) {
4287
+ const issues = /* @__PURE__ */ new Set();
4288
+ const copyParts = campaignReviewDataCopyParts(data);
4289
+ if (copyParts.length === 0) return [];
4290
+ if (campaignReviewDataHasBadGreeting(data)) issues.add("bad_greeting");
4291
+ if (campaignReviewDataHasBannedPhrase(data)) issues.add("banned_or_vague_phrase");
4292
+ if (campaignReviewDataHasUnsupportedSignalClaim(data)) issues.add("unsupported_signal_claim");
4293
+ if (campaignReviewDataPersonalizationBasis(data).length === 0) issues.add("missing_personalization_basis");
4294
+ if (!campaignReviewDataCta(data)) issues.add("missing_cta");
4295
+ const subject = campaignReviewDataSubject(data);
4296
+ if (subject && subject.length > 70) issues.add("overlong_subject");
4297
+ return [...issues];
4298
+ }
4299
+ function campaignReviewDataPersonalizationBasis(data) {
4300
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
4301
+ const copy = asRecord(data.copy);
4302
+ const metadata = asRecord(data.metadata);
4303
+ const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
4304
+ return [
4305
+ ...arrayOfStrings(data.personalization_basis) ?? [],
4306
+ ...arrayOfStrings(data.personalizationBasis) ?? [],
4307
+ ...arrayOfStrings(copy.personalization_basis) ?? [],
4308
+ ...arrayOfStrings(rawCopy.personalization_basis) ?? [],
4309
+ ...arrayOfStrings(metadata.personalization_basis) ?? [],
4310
+ ...arrayOfStrings(metadata.personalizationBasis) ?? [],
4311
+ ...arrayOfStrings(signalEvidence.personalization_basis) ?? []
4312
+ ];
4313
+ }
4314
+ function campaignReviewDataHasNoSupportedSignal(data) {
4315
+ const metadata = asRecord(data.metadata);
4316
+ const copy = asRecord(data.copy);
4317
+ const state = [
4318
+ data.copy_signal_state,
4319
+ data.signal_basis,
4320
+ data.signal_type,
4321
+ metadata.copy_signal_state,
4322
+ metadata.signal_basis,
4323
+ metadata.signal_type,
4324
+ copy.copy_signal_state
4325
+ ].map((value) => stringValue(value)?.toLowerCase()).filter(Boolean);
4326
+ return data.no_supported_signal === true || metadata.no_supported_signal === true || state.includes("no_supported_signal");
4327
+ }
4328
+ function campaignReviewDataHasSignalBasis(data) {
4329
+ if (campaignReviewDataHasNoSupportedSignal(data)) return false;
4330
+ const metadata = asRecord(data.metadata);
4331
+ const signal = asRecord(data.signal);
4332
+ const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
4333
+ const basis = campaignReviewDataPersonalizationBasis(data);
4334
+ return Boolean(
4335
+ stringValue(data.signal_type) || stringValue(data.signalType) || stringValue(data.signal_summary) || stringValue(data.signalSummary) || stringValue(data.signal_source_url) || stringValue(data.signalSourceUrl) || stringValue(metadata.signal_type) || stringValue(metadata.signal_summary) || stringValue(metadata.signal_source_url) || stringValue(signal.type) || stringValue(signal.summary) || stringValue(signal.source_url) || stringValue(signalEvidence.signal_type) || stringValue(signalEvidence.summary) || stringValue(signalEvidence.source_url) || basis.some((item) => /\bsignal|hiring|funding|leadership|growth|launch|news|technology\b/i.test(item))
4336
+ );
4337
+ }
4338
+ function campaignReviewDataHasUnsupportedSignalClaim(data) {
4339
+ const copyText = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
4340
+ const claimsSignal = /\b(hiring|funding|raised|launched|expanding|appointed|announced|recent news|new role|job posting)\b/.test(copyText);
4341
+ return claimsSignal && !campaignReviewDataHasSignalBasis(data);
4342
+ }
4343
+ function campaignReviewDataHasApprovalLock(data) {
4344
+ const metadata = asRecord(data.fit_metadata ?? data.metadata);
4345
+ const approval = String(data.approval_status ?? data.delivery_approval_status ?? metadata.approval_status ?? "").toLowerCase();
4346
+ const exportStatus = String(data.export_status ?? data.exportStatus ?? "").toLowerCase();
4347
+ return data.export_ready === false || data.exportReady === false || data.delivery_approved === false || data.deliveryApproved === false || ["pending", "pending_approval", "blocked", "approval_required", "held"].includes(approval) || ["pending", "blocked", "approval_required", "held"].includes(exportStatus);
4348
+ }
4349
+ function campaignReviewDataHasHoldoutAttribution(data) {
4350
+ const fitMetadata = asRecord(data.fit_metadata ?? data.fitMetadata);
4351
+ const metadata = asRecord(data.metadata);
4352
+ const holdout = asRecord(
4353
+ fitMetadata.learning_holdout ?? fitMetadata.learningHoldout ?? metadata.learning_holdout ?? metadata.learningHoldout ?? data.learning_holdout ?? data.learningHoldout
4354
+ );
4355
+ const experimentKey = stringValue(holdout.experiment_key ?? holdout.experimentKey ?? data.experiment_key ?? data.experimentKey);
4356
+ const cohort = stringValue(holdout.cohort_role ?? holdout.cohortRole ?? data.cohort_role ?? data.learning_cohort ?? data.learningCohort);
4357
+ const brainApplied = holdout.brain_applied ?? holdout.brainApplied ?? data.brain_applied ?? data.signaliz_brain_applied;
4358
+ return Boolean(experimentKey && cohort && brainApplied !== void 0);
4359
+ }
4360
+ function campaignReviewStatusHasFeedbackReadiness(status) {
4361
+ const diagnostics = asRecord(status.diagnostics);
4362
+ const feedbackReadiness = asRecord(diagnostics.feedback_readiness ?? diagnostics.feedbackReadiness);
4363
+ const feedbackSubstrate = asRecord(diagnostics.feedback_substrate ?? diagnostics.feedbackSubstrate);
4364
+ const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
4365
+ return feedbackReadiness.ready === true || feedbackReadiness.webhook_configured === true || feedbackSubstrate.stage === "ready" || Number(evidenceCounts.feedback_events ?? 0) > 0;
4366
+ }
4367
+ function campaignReviewMoatState(status, holdoutRows) {
4368
+ const diagnostics = asRecord(status.diagnostics);
4369
+ const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
4370
+ const embeddedHoldout = asRecord(diagnostics.embedded_holdout ?? diagnostics.embeddedHoldout);
4371
+ const holdoutLift = asRecord(diagnostics.holdout_lift ?? diagnostics.holdoutLift ?? diagnostics.holdout_measurement ?? diagnostics.holdoutMeasurement);
4372
+ const controlSample = Math.max(
4373
+ Number(evidenceCounts.holdout_control_sample_size ?? 0),
4374
+ Number(evidenceCounts.embedded_holdout_control_sample ?? 0),
4375
+ Number(embeddedHoldout.control_sample_size ?? 0)
4376
+ );
4377
+ const learnedSample = Math.max(
4378
+ Number(evidenceCounts.holdout_learned_sample_size ?? 0),
4379
+ Number(evidenceCounts.embedded_holdout_learned_sample ?? 0),
4380
+ Number(embeddedHoldout.learned_sample_size ?? 0)
4381
+ );
4382
+ const feedbackEvents = Math.max(
4383
+ Number(evidenceCounts.feedback_events ?? 0),
4384
+ Number(evidenceCounts.campaign_build_feedback_events ?? 0),
4385
+ Number(evidenceCounts.delivery_holdout_sent_outcomes ?? 0)
4386
+ );
4387
+ const proved = diagnostics.closed_loop_proof_ready === true || holdoutLift.honest_measurement === true && holdoutLift.positive_lift === true;
4388
+ if (proved) {
4389
+ return {
4390
+ state: "proved",
4391
+ detail: "Real attributed feedback shows positive lift for explicit control and learned cohorts."
4392
+ };
4393
+ }
4394
+ if (controlSample > 0 && learnedSample > 0 && feedbackEvents > 0) {
4395
+ return {
4396
+ state: "moved_needs_lift_volume",
4397
+ detail: `Control and learned cohorts have attributed feedback (${controlSample} control, ${learnedSample} learned, ${feedbackEvents} feedback event(s)); keep collecting lift volume before claiming proof.`
4398
+ };
4399
+ }
4400
+ return {
4401
+ state: "scaffolding_ready",
4402
+ detail: holdoutRows > 0 ? `${holdoutRows} sampled row(s) carry holdout attribution, but real control/learned feedback lift is not proved yet.` : "Holdout and feedback scaffolding may be ready, but no real control/learned feedback lift is proved yet."
4403
+ };
4404
+ }
3580
4405
  function createCampaignBuilderReadinessLane(route, plan) {
3581
4406
  const builtIn = campaignBuilderBuiltInForRoute(route);
3582
4407
  const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
@@ -3610,6 +4435,342 @@ function createCampaignBuilderReadinessLane(route, plan) {
3610
4435
  nextAction: customRoute ? `Review ${route.provider} route setup before launch approval.` : void 0
3611
4436
  };
3612
4437
  }
4438
+ function createCampaignBuilderApprovalPacket(plan, approval, missingTypes) {
4439
+ const requestedTypes = plan ? uniqueStrings(plan.approvals.filter((item) => item.blocking).map((item) => item.type)) : [];
4440
+ const approvalMatchesPlan = Boolean(plan && approval?.planId === plan.planId);
4441
+ const approvedTypes = approvalMatchesPlan ? approval?.approvedTypes ?? [] : [];
4442
+ const approvedRoutes = approvalMatchesPlan ? approval?.approvedRouteIds ?? [] : [];
4443
+ const missingIds = new Set(
4444
+ plan && approvalMatchesPlan && approval ? getMissingCampaignBuilderApprovals(plan, approval).map((item) => item.id) : plan?.approvals.filter((item) => item.blocking).map((item) => item.id) ?? []
4445
+ );
4446
+ const routeIdsRequired = plan ? uniqueStrings(plan.approvals.map((item) => item.routeId).filter((id) => Boolean(id))) : [];
4447
+ const spendLimitRequired = plan ? Math.max(0, ...plan.approvals.filter((item) => item.type === "spend" && item.blocking).map((item) => item.estimatedCredits ?? plan.estimatedCredits)) : 0;
4448
+ const normalizedSpendLimit = spendLimitRequired > 0 ? spendLimitRequired : null;
4449
+ const approvalState = !plan ? "unscoped" : missingTypes.length > 0 ? "requires_approval" : "approved";
4450
+ const approvalInput = {
4451
+ approved_by: approval?.approvedBy ?? null,
4452
+ approved_at: approval?.approvedAt ?? null,
4453
+ approved_types: approvedTypes,
4454
+ approved_route_ids: approvedRoutes,
4455
+ spend_limit_credits: approval?.spendLimitCredits ?? null,
4456
+ notes: approval?.notes
4457
+ };
4458
+ return {
4459
+ packet_version: "campaign-builder-approval-packet.v1",
4460
+ read_only: true,
4461
+ no_spend: true,
4462
+ no_provider_writes: true,
4463
+ private_safe: true,
4464
+ approval_state: approvalState,
4465
+ plan_id: plan?.planId ?? null,
4466
+ campaign_name: plan?.campaignName ?? null,
4467
+ requested_approval_types: requestedTypes,
4468
+ missing_approval_types: uniqueStrings(missingTypes),
4469
+ spend_limit_credits_required: normalizedSpendLimit,
4470
+ route_ids_required: routeIdsRequired,
4471
+ required_approvals: (plan?.approvals ?? []).map((item) => compact({
4472
+ id: item.id,
4473
+ type: item.type,
4474
+ label: item.label,
4475
+ reason: item.reason,
4476
+ blocking: item.blocking,
4477
+ route_id: item.routeId,
4478
+ provider: item.provider,
4479
+ estimated_credits: item.estimatedCredits,
4480
+ status: missingIds.has(item.id) ? "missing" : "approved"
4481
+ })),
4482
+ approval_input: approvalInput,
4483
+ cli_handoff: plan ? {
4484
+ command: campaignBuilderApprovalCliHandoff(requestedTypes, routeIdsRequired, normalizedSpendLimit),
4485
+ safety: "Template only: run after explicit human approval identity, approved types, route scope, and spend limit are filled in."
4486
+ } : null,
4487
+ sdk_handoff: plan ? {
4488
+ method: "campaignBuilderAgent.buildApprovedPlan",
4489
+ approval: compact({
4490
+ approvedBy: approval?.approvedBy ?? "<operator-email>",
4491
+ approvedTypes: requestedTypes,
4492
+ approvedRouteIds: routeIdsRequired,
4493
+ spendLimitCredits: normalizedSpendLimit ?? void 0,
4494
+ notes: approval?.notes
4495
+ }),
4496
+ options: {
4497
+ idempotencyKey: "<set-before-execution>"
4498
+ },
4499
+ safety: "Call only after the approval packet is accepted; this helper does not execute writes or spend."
4500
+ } : null,
4501
+ mcp_handoffs: plan ? campaignBuilderApprovalMcpHandoffs(plan, requestedTypes, routeIdsRequired, normalizedSpendLimit) : [],
4502
+ safety_boundary: "This approval packet is a reusable request for human approval; it is not an approval grant and it does not authorize spend, provider writes, sender loading, delivery, or launch by itself."
4503
+ };
4504
+ }
4505
+ function campaignBuilderApprovalCliHandoff(requestedTypes, routeIds, spendLimit) {
4506
+ return [
4507
+ "signaliz campaign-agent build-approved --request-file campaign-request.json --confirm-launch",
4508
+ requestedTypes.length > 0 ? `--approved-types ${shellArg(requestedTypes.join(","))}` : "--approve-all",
4509
+ "--approved-by <operator-email>",
4510
+ spendLimit !== null ? `--spend-limit ${spendLimit}` : void 0,
4511
+ routeIds.length > 0 ? `--approved-route-ids ${shellArg(routeIds.join(","))}` : void 0
4512
+ ].filter(Boolean).join(" ");
4513
+ }
4514
+ function campaignBuilderApprovalMcpHandoffs(plan, requestedTypes, routeIds, spendLimit) {
4515
+ const approvalContext = compact({
4516
+ plan_id: plan.planId,
4517
+ approval_types: requestedTypes,
4518
+ approved_route_ids: routeIds,
4519
+ spend_limit_credits: spendLimit,
4520
+ approved_by: "<operator-email>"
4521
+ });
4522
+ return plan.mcpFlow.filter((step) => ["execution-prepare", "approved-build", "delivery-approval"].includes(step.id)).map((step) => ({
4523
+ id: step.id,
4524
+ tool: step.tool,
4525
+ arguments: step.id === "approved-build" ? compact({ ...step.arguments, approval_context: approvalContext }) : step.arguments,
4526
+ read_only: step.readOnly,
4527
+ approval_required: step.approvalRequired,
4528
+ safety: step.readOnly ? "Read-only readiness handoff; safe to inspect before approval." : "Write-capable handoff; run only after the approval packet is accepted."
4529
+ }));
4530
+ }
4531
+ function campaignBuilderActiveWorkNextMcpAction(input) {
4532
+ const campaignBuildRef = input.refs.find((ref) => ref.kind === "campaign_build" && ref.id);
4533
+ if (campaignBuildRef) {
4534
+ return {
4535
+ id: "check-campaign-build-status",
4536
+ tool: "get_campaign_build_status",
4537
+ arguments: { campaign_build_id: campaignBuildRef.id },
4538
+ read_only: true,
4539
+ approval_required: false,
4540
+ safety: "Read-only status check for the remembered campaign build; does not spend, write, export, send, or approve anything."
4541
+ };
4542
+ }
4543
+ const campaignRef = input.refs.find((ref) => ref.kind === "gtm_campaign" && ref.id);
4544
+ if (campaignRef) {
4545
+ return {
4546
+ id: "check-gtm-campaign-status",
4547
+ tool: "gtm_campaign_execution_status",
4548
+ arguments: { campaign_id: campaignRef.id },
4549
+ read_only: true,
4550
+ approval_required: false,
4551
+ safety: "Read-only campaign execution status check; does not start or mutate the campaign."
4552
+ };
4553
+ }
4554
+ const preferredStepId = input.state === "ready_for_dry_run" ? "dry-run-build" : input.state === "planning" || input.state === "blocked" ? "agency-campaign-build-plan" : null;
4555
+ const preferredStep = preferredStepId ? input.plan?.mcpFlow.find((step) => step.id === preferredStepId && step.readOnly) : void 0;
4556
+ if (preferredStep) return campaignBuilderMcpStepToActiveWorkAction(preferredStep, "Read-only continuation from the saved campaign-agent plan.");
4557
+ const readOnlyApprovalHandoff = input.approvalPacket.mcp_handoffs.find((handoff) => handoff.read_only && !handoff.approval_required);
4558
+ if (readOnlyApprovalHandoff) return readOnlyApprovalHandoff;
4559
+ const firstReadOnlyPlanStep = input.plan?.mcpFlow.find((step) => step.readOnly && !step.approvalRequired);
4560
+ return firstReadOnlyPlanStep ? campaignBuilderMcpStepToActiveWorkAction(firstReadOnlyPlanStep, "Read-only saved plan check; safe before approvals or writes.") : null;
4561
+ }
4562
+ function campaignBuilderMcpStepToActiveWorkAction(step, safety) {
4563
+ return {
4564
+ id: step.id,
4565
+ tool: step.tool,
4566
+ arguments: step.arguments,
4567
+ read_only: step.readOnly,
4568
+ approval_required: step.approvalRequired,
4569
+ safety
4570
+ };
4571
+ }
4572
+ function normalizeCampaignBuilderActiveWorkRefs(input) {
4573
+ const refs = [];
4574
+ const pushRef = (ref) => {
4575
+ if (!ref?.id) return;
4576
+ refs.push(ref);
4577
+ };
4578
+ pushRef(input.plan?.planId ? { kind: "plan", id: input.plan.planId, label: input.plan.campaignName, source: "plan" } : void 0);
4579
+ pushRef(input.remembered.planId ? { kind: "plan", id: input.remembered.planId, source: "remembered" } : void 0);
4580
+ pushRef(
4581
+ input.plan?.buildRequest.gtmCampaignId ?? input.remembered.gtmCampaignId ? {
4582
+ kind: "gtm_campaign",
4583
+ id: input.plan?.buildRequest.gtmCampaignId ?? input.remembered.gtmCampaignId,
4584
+ label: input.plan?.campaignName,
4585
+ source: input.plan?.buildRequest.gtmCampaignId ? "plan" : "remembered"
4586
+ } : void 0
4587
+ );
4588
+ const campaignBuildId = stringValue(input.lastResult.campaign_build_id ?? input.lastResult.campaignBuildId) ?? input.remembered.campaignBuildId;
4589
+ pushRef(campaignBuildId ? { kind: "campaign_build", id: campaignBuildId, status: activeWorkStatus(input.lastResult) ?? input.remembered.status, source: "result" } : void 0);
4590
+ pushRef(
4591
+ input.remembered.providerCampaignId ? {
4592
+ kind: "provider_campaign",
4593
+ id: input.remembered.providerCampaignId,
4594
+ label: input.remembered.provider,
4595
+ status: input.remembered.status,
4596
+ source: "remembered"
4597
+ } : void 0
4598
+ );
4599
+ for (const ref of input.remembered.refs ?? []) pushRef(ref);
4600
+ for (const ref of input.refs ?? []) pushRef(ref);
4601
+ const seen = /* @__PURE__ */ new Set();
4602
+ return refs.filter((ref) => {
4603
+ const key = `${ref.kind}:${ref.id}`;
4604
+ if (seen.has(key)) return false;
4605
+ seen.add(key);
4606
+ return true;
4607
+ });
4608
+ }
4609
+ function inferCampaignBuilderActiveWorkState(input) {
4610
+ if (input.blockers.length > 0) return "blocked";
4611
+ const status = activeWorkStatus(input.lastResult) ?? input.remembered.status;
4612
+ const normalized = status?.toLowerCase();
4613
+ if (normalized && ["completed", "complete", "succeeded", "success", "done"].includes(normalized)) return "completed";
4614
+ if (normalized && ["queued", "running", "in_progress", "processing", "pending"].includes(normalized)) return "queued_or_running";
4615
+ const dryRunComplete = input.lastResult.dry_run === true || input.lastResult.dryRun === true || normalized === "dry_run";
4616
+ if (dryRunComplete) return input.missingApprovals.length > 0 ? "needs_approval" : "dry_run_complete";
4617
+ if (input.missingApprovals.length > 0) return "needs_approval";
4618
+ if (input.readiness?.summary.ready === true) return "ready_for_dry_run";
4619
+ if (input.plan) return "planning";
4620
+ return "unknown";
4621
+ }
4622
+ function campaignBuilderActiveWorkApprovalBoundary(missingApprovals, state) {
4623
+ if (missingApprovals.length > 0) {
4624
+ return `Blocked before spend, provider writes, delivery, or launch until approval covers: ${uniqueStrings(missingApprovals).join(", ")}.`;
4625
+ }
4626
+ if (state === "dry_run_complete" || state === "ready_for_dry_run") {
4627
+ return "Read-only planning and dry-runs are allowed; spend, provider writes, sender loading, delivery, and launch still require explicit approval.";
4628
+ }
4629
+ return "This packet is read-only and does not authorize spend, provider writes, sender loading, delivery, or launch.";
4630
+ }
4631
+ function campaignBuilderActiveWorkNextAction(state, blockers, missingApprovals, refs) {
4632
+ if (blockers.length > 0) return `Resolve blocker: ${blockers[0]}`;
4633
+ if (missingApprovals.length > 0) return `Ask the operator to approve ${uniqueStrings(missingApprovals).join(", ")} before any write or spend action.`;
4634
+ if (state === "ready_for_dry_run") return "Run or inspect the read-only campaign dry-run, then return the receipt for approval review.";
4635
+ if (state === "dry_run_complete") return "Review the dry-run receipt and collect explicit launch or delivery approval before any write action.";
4636
+ if (state === "queued_or_running") return "Poll the remembered campaign build or provider job status; do not start a duplicate job.";
4637
+ if (state === "completed") return "Review final artifacts and delivery gates before export, sync, sender load, or launch.";
4638
+ if (refs.length > 0) return "Use the remembered refs to fetch current read-only status before asking the operator for IDs.";
4639
+ return "Create or refresh a campaign-builder plan before taking any spendful or write action.";
4640
+ }
4641
+ function campaignBuilderActiveWorkConversationStarters(input) {
4642
+ const starters = [];
4643
+ if (input.blockers.length > 0) {
4644
+ starters.push(
4645
+ `I found a blocker: ${input.blockers[0]}. Want me to stay in read-only diagnostics and prepare the fix path?`
4646
+ );
4647
+ }
4648
+ if (input.missingApprovals.length > 0) {
4649
+ starters.push(
4650
+ `You still owe approval for ${uniqueStrings(input.missingApprovals).join(", ")}. Want the exact approval packet before anything writes or spends?`
4651
+ );
4652
+ }
4653
+ if (input.state === "queued_or_running") {
4654
+ const buildRef = input.refs.find((ref) => ref.kind === "campaign_build");
4655
+ starters.push(buildRef ? `I can check campaign build ${buildRef.id} now and avoid starting a duplicate job.` : "I can check the running job status now and avoid starting duplicate work.");
4656
+ }
4657
+ if (input.state === "dry_run_complete") {
4658
+ starters.push("The dry run is complete. Want me to summarize blockers, artifacts, and the approval handoff?");
4659
+ }
4660
+ if (input.state === "ready_for_dry_run") {
4661
+ starters.push("Everything is ready for a safe dry run. Want me to run the no-spend preview first?");
4662
+ }
4663
+ if (input.state === "completed") {
4664
+ starters.push("The build looks complete. Want me to review rows, artifacts, and delivery gates before export or send approval?");
4665
+ }
4666
+ starters.push(`Next safe action: ${input.nextSafeAction}`);
4667
+ return uniqueStrings(starters).slice(0, 4);
4668
+ }
4669
+ function createCampaignBuilderOpenAiAgentHandoff(input) {
4670
+ const readOnlyPlanActions = input.plan?.mcpFlow.filter((step) => step.readOnly && !step.approvalRequired).map((step) => campaignBuilderMcpStepToActiveWorkAction(step, "Read-only Signaliz MCP continuation for the saved campaign-agent plan.")) ?? [];
4671
+ const routeSetupActions = readOnlyPlanActions.filter(
4672
+ (action) => action.tool === "gtm_provider_recipe_prepare" || action.tool === "gtm_provider_route_activate"
4673
+ );
4674
+ const safeReadActions = uniqueCampaignBuilderActiveWorkActions([
4675
+ input.nextSafeMcpAction,
4676
+ ...routeSetupActions,
4677
+ ...readOnlyPlanActions
4678
+ ]);
4679
+ const approvalGatedActions = uniqueCampaignBuilderActiveWorkActions([
4680
+ ...input.approvalPacket.mcp_handoffs.filter((action) => action.approval_required || !action.read_only),
4681
+ ...input.plan?.mcpFlow.filter((step) => step.approvalRequired || !step.readOnly).map((step) => campaignBuilderMcpStepToActiveWorkAction(step, "Approval-gated Signaliz MCP continuation for the saved campaign-agent plan.")) ?? []
4682
+ ]);
4683
+ const approvalTypes = uniqueStrings([
4684
+ ...input.missingApprovals,
4685
+ ...input.approvalPacket.requested_approval_types
4686
+ ]);
4687
+ return {
4688
+ packet_version: "campaign-builder-openai-agent-handoff.v1",
4689
+ surface: "openai_agents_sdk",
4690
+ agent: {
4691
+ name: "Signaliz GTM Campaign Agent",
4692
+ handoff_description: "Owns safe, conversational GTM campaign planning and continuation across Signaliz MCP, SDK, attachments, URLs, customer tools, approvals, and sender/export gates.",
4693
+ instructions: [
4694
+ "Be warm, direct, and expert-level GTM. Translate the current state into plain operator language before proposing work.",
4695
+ "Start from the packet state, refs, evidence_context, approval_boundary, and next_safe_action. Do not ask for IDs already present in refs or summary.",
4696
+ "Use attachment_summary, url_summary, and operator_notes as first-class planning evidence, while honoring the evidence privacy_boundary and withheld raw fields.",
4697
+ "Prefer Signaliz-native, cached, remembered, and read-only MCP actions before provider calls. Never start duplicate jobs when a campaign_build or provider job is queued or running.",
4698
+ "Call only tools listed in safe_read_tools without approval. Treat approval_gated_tools as human-review surfaces, not permission to spend, write, export, load senders, deliver, or launch.",
4699
+ "When approval is missing, show the approval_packet and the exact missing approval scope before any side-effecting step."
4700
+ ],
4701
+ output_contract: [
4702
+ "current_state: one concise sentence describing where the campaign work stands.",
4703
+ "evidence_used: attachment, URL, memory, route, or integration evidence used without exposing private raw rows or secrets.",
4704
+ "next_safe_mcp_action: the read-only Signaliz MCP action to run next, or null when none is safe.",
4705
+ "approval_boundary: the human approval scope required before spend, provider writes, delivery, export, sender load, or launch.",
4706
+ "operator_reply: conversational GTM guidance with blockers, risks, and the safest next move."
4707
+ ]
4708
+ },
4709
+ context: {
4710
+ state: input.state,
4711
+ summary: input.summary,
4712
+ refs: input.refs,
4713
+ blockers: input.blockers,
4714
+ warnings: input.warnings,
4715
+ evidence_context: input.plan?.evidence ?? null,
4716
+ approval_packet: input.approvalPacket,
4717
+ approval_boundary: input.approvalBoundary,
4718
+ next_safe_action: input.nextSafeAction,
4719
+ conversation_starters: input.conversationStarters
4720
+ },
4721
+ tools: {
4722
+ mcp_server_label: "signaliz",
4723
+ remote_mcp_compatible: true,
4724
+ next_safe_mcp_action: input.nextSafeMcpAction,
4725
+ safe_read_tools: safeReadActions.map(
4726
+ (action) => campaignBuilderOpenAiAgentTool(action, "Safe read-only Signaliz MCP action available before approval.")
4727
+ ),
4728
+ approval_gated_tools: approvalGatedActions.map(
4729
+ (action) => campaignBuilderOpenAiAgentTool(action, "Requires explicit human review before execution.")
4730
+ )
4731
+ },
4732
+ guardrails: {
4733
+ input: [
4734
+ "Reject or pause requests to expose raw private attachment text, raw rows, leads, emails, provider payloads, copy bodies, API keys, or secrets.",
4735
+ "Pause when the user asks to spend credits, write to a provider, export, load a sender, deliver, or launch without explicit approval scope."
4736
+ ],
4737
+ tool: [
4738
+ "Read-only tools may run when they match safe_read_tools and their arguments match this packet.",
4739
+ "Any tool with approval_required=true or read_only=false requires human review and the approval_packet scope first."
4740
+ ],
4741
+ human_review_required_for: approvalTypes,
4742
+ privacy: input.plan?.evidence?.privacy_boundary ?? "Use summarized context only. Do not expose raw private attachment text, raw rows, leads, emails, provider payloads, copy bodies, secrets, or credentials."
4743
+ }
4744
+ };
4745
+ }
4746
+ function campaignBuilderOpenAiAgentTool(action, purpose) {
4747
+ return {
4748
+ id: action.id,
4749
+ type: "mcp",
4750
+ server_label: "signaliz",
4751
+ name: action.tool,
4752
+ arguments: action.arguments,
4753
+ read_only: action.read_only,
4754
+ approval_required: action.approval_required,
4755
+ purpose,
4756
+ safety: action.safety
4757
+ };
4758
+ }
4759
+ function uniqueCampaignBuilderActiveWorkActions(actions) {
4760
+ const seen = /* @__PURE__ */ new Set();
4761
+ const unique = [];
4762
+ for (const action of actions) {
4763
+ if (!action) continue;
4764
+ const key = `${action.id}:${action.tool}`;
4765
+ if (seen.has(key)) continue;
4766
+ seen.add(key);
4767
+ unique.push(action);
4768
+ }
4769
+ return unique;
4770
+ }
4771
+ function activeWorkStatus(record) {
4772
+ return stringValue(record.status ?? record.state ?? record.phase);
4773
+ }
3613
4774
  function campaignBuilderBuiltInForRoute(route) {
3614
4775
  return Object.entries(CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS).find(([, routeId]) => route.id === routeId)?.[0];
3615
4776
  }
@@ -3877,7 +5038,7 @@ function hashString(input) {
3877
5038
  function errorMessage(error) {
3878
5039
  return error instanceof Error ? error.message : String(error);
3879
5040
  }
3880
- var DEFAULT_SIGNALIZ_CAMPAIGN_BUILDER_DEFAULTS, DEFAULT_CAMPAIGN_BUILDER_BUILT_INS, CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS, DEFAULT_CAMPAIGN_BUILDER_APPROVAL_TYPES, CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS, CAMPAIGN_BUILDER_STRATEGY_TEMPLATES, CampaignBuilderAgent;
5041
+ var DEFAULT_SIGNALIZ_CAMPAIGN_BUILDER_DEFAULTS, DEFAULT_CAMPAIGN_BUILDER_BUILT_INS, CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS, DEFAULT_CAMPAIGN_BUILDER_APPROVAL_TYPES, CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS, CAMPAIGN_BUILDER_STRATEGY_TEMPLATES, CampaignBuilderAgent, CAMPAIGN_BUILDER_TERMINAL_STATUSES, CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES, NORTH_STAR_BANNED_COPY_PHRASES;
3881
5042
  var init_campaign_builder_agent = __esm({
3882
5043
  "src/resources/campaign-builder-agent.ts"() {
3883
5044
  "use strict";
@@ -3897,7 +5058,8 @@ var init_campaign_builder_agent = __esm({
3897
5058
  copy: {
3898
5059
  enabled: true,
3899
5060
  tone: "direct",
3900
- maxBodyWords: 125
5061
+ maxBodyWords: 125,
5062
+ evidenceMode: "signal_led"
3901
5063
  },
3902
5064
  delivery: {
3903
5065
  enabled: true,
@@ -3954,7 +5116,8 @@ var init_campaign_builder_agent = __esm({
3954
5116
  "Block export when required identity, company, or email fields are missing."
3955
5117
  ],
3956
5118
  activationBoundary: "Cache inventory is read-only. Fresh sourcing, cache writes, export, and sender loading require explicit approval.",
3957
- memoryKeywords: ["cache inventory", "large list", "unique emails", "top-up sourcing", "source metadata"]
5119
+ memoryKeywords: ["cache inventory", "large list", "unique emails", "top-up sourcing", "source metadata"],
5120
+ knowledgeSources: ["data-ops/contact-data-lifecycle", "data-ops/credit-optimization-guide", "gtm/data-quality-ops", "gtm/lead-generation-strategies"]
3958
5121
  },
3959
5122
  {
3960
5123
  slug: "net-new-suppressed-list",
@@ -3979,7 +5142,8 @@ var init_campaign_builder_agent = __esm({
3979
5142
  "Do not describe rows as fully qualified when the approved path only proves net-new email status."
3980
5143
  ],
3981
5144
  activationBoundary: "Suppression review must pass before export, upload, CRM sync, sender load, or sequence launch.",
3982
- memoryKeywords: ["suppression", "net-new", "prior email overlap", "domain review", "provenance-rich QA"]
5145
+ memoryKeywords: ["suppression", "net-new", "prior email overlap", "domain review", "provenance-rich QA"],
5146
+ knowledgeSources: ["gtm/data-quality-ops", "data-ops/contact-data-lifecycle", "gtm/outbound-sequencing"]
3983
5147
  },
3984
5148
  {
3985
5149
  slug: "proof-first-vertical-gate",
@@ -4004,7 +5168,8 @@ var init_campaign_builder_agent = __esm({
4004
5168
  "Contact fit should meet the configured threshold before email finding or copy generation."
4005
5169
  ],
4006
5170
  activationBoundary: "Proof gates are required before contact spend, copy, export, or external writes.",
4007
- memoryKeywords: ["proof gate", "vertical exclusion", "company before contact", "pass review fail", "fit threshold"]
5171
+ memoryKeywords: ["proof gate", "vertical exclusion", "company before contact", "pass review fail", "fit threshold"],
5172
+ knowledgeSources: ["gtm/icp-design-framework", "gtm/strategy/scoring.yaml", "gtm/advanced/account-prioritization-frameworks", "gtm/advanced/predictive-lead-scoring", "gtm/verticals"]
4008
5173
  },
4009
5174
  {
4010
5175
  slug: "signal-led-copy-approval",
@@ -4029,7 +5194,8 @@ var init_campaign_builder_agent = __esm({
4029
5194
  "Destination fields must remain blocked until approval metadata is present."
4030
5195
  ],
4031
5196
  activationBoundary: "Copy readiness is not delivery approval. Export, sync, sender load, and launch require a separate human-approved gate.",
4032
- memoryKeywords: ["company signals", "evidence-fed copy", "why now", "copy blocker", "approval gate"]
5197
+ memoryKeywords: ["company signals", "evidence-fed copy", "why now", "copy blocker", "approval gate"],
5198
+ knowledgeSources: ["gtm/email-personalization", "gtm/signal-based-selling", "gtm/signal-combination-matrix", "gtm/advanced/buyer-journey-mapping"]
4033
5199
  },
4034
5200
  {
4035
5201
  slug: "domain-first-recovery",
@@ -4054,7 +5220,8 @@ var init_campaign_builder_agent = __esm({
4054
5220
  "Target count means final held rows, not raw sourced rows."
4055
5221
  ],
4056
5222
  activationBoundary: "Domain recovery informs sourcing. Contact enrichment, paid qualification, and export still require approved gates.",
4057
- memoryKeywords: ["domain-first", "yield modeling", "attrition", "accepted domains", "gap fill"]
5223
+ memoryKeywords: ["domain-first", "yield modeling", "attrition", "accepted domains", "gap fill"],
5224
+ knowledgeSources: ["gtm/lead-generation-strategies", "data-ops/enrichment-waterfall-strategy", "data-ops/credit-optimization-guide", "gtm/territory-account-planning"]
4058
5225
  },
4059
5226
  {
4060
5227
  slug: "table-workflow-handoff",
@@ -4079,7 +5246,112 @@ var init_campaign_builder_agent = __esm({
4079
5246
  "Provider route activation must dry-run before any external write."
4080
5247
  ],
4081
5248
  activationBoundary: "MCP, webhook, managed integration, and destination routes remain dry-run until the reviewed route and campaign are approved.",
4082
- memoryKeywords: ["table workflow", "request ready", "provider status", "export blocker", "BYO integration"]
5249
+ memoryKeywords: ["table workflow", "request ready", "provider status", "export blocker", "BYO integration"],
5250
+ knowledgeSources: ["gtm/multi-channel-orchestration", "data-ops/enrichment-waterfall-strategy", "recipes/clay-gtm-webhook-recipe", "systems/blueprints/multi-channel-orchestration"]
5251
+ },
5252
+ {
5253
+ slug: "icp-persona-segmentation",
5254
+ label: "ICP And Persona Segmentation",
5255
+ whenToUse: [
5256
+ "The brief has a broad market, multiple personas, or unclear buying committee.",
5257
+ "The campaign should rank account tiers, persona fit, vertical nuance, and exclusions before sourcing at scale."
5258
+ ],
5259
+ sequence: [
5260
+ "Convert the brief into account tiers, persona roles, buying committee influence, and explicit exclusions.",
5261
+ "Choose vertical guidance only from the customer-safe GTM library and keep internal platform docs out of prompts.",
5262
+ "Score accounts before contacts, then score contacts against persona, seniority, function, geography, and intent fit.",
5263
+ "Return segment counts and review buckets before spend, copy, export, or launch."
5264
+ ],
5265
+ requiredFields: {
5266
+ segment: ["segment_name", "tier", "persona_priority", "buying_committee_role", "fit_reason", "exclusion_reason"],
5267
+ scoring: ["account_fit_score", "persona_fit_score", "segment_confidence", "review_bucket"]
5268
+ },
5269
+ qualityGates: [
5270
+ "Do not treat adjacent personas or verticals as matches unless the brief allows them.",
5271
+ "Keep ICP, persona, and exclusion evidence visible in the proof packet.",
5272
+ "Low-confidence segments route to review before contact discovery or copy."
5273
+ ],
5274
+ activationBoundary: "Segmentation is planning guidance. Paid sourcing, enrichment, export, and launch require the normal approval gates.",
5275
+ memoryKeywords: ["ICP design", "persona segmentation", "buying committee", "account tiers", "fit scoring", "vertical nuance"],
5276
+ knowledgeSources: ["gtm/icp-design-framework", "gtm/personas/sdr.yaml", "gtm/personas/ae.yaml", "gtm/personas/cro.yaml", "gtm/personas/revops.yaml", "gtm/advanced/account-prioritization-frameworks", "gtm/verticals"]
5277
+ },
5278
+ {
5279
+ slug: "buying-signal-prioritization",
5280
+ label: "Buying Signal Prioritization",
5281
+ whenToUse: [
5282
+ "The campaign depends on timing, intent, trigger events, or account prioritization.",
5283
+ "The operator wants why-now proof instead of generic list generation."
5284
+ ],
5285
+ sequence: [
5286
+ "Map the campaign goal to signal families, trigger freshness, and buyer-journey stage.",
5287
+ "Rank signals by relevance, recency, source quality, and persona-specific actionability.",
5288
+ "Attach evidence URLs or evidence summaries without exposing private memory rows.",
5289
+ "Use signal strength to decide source order, copy angle, and review priority."
5290
+ ],
5291
+ requiredFields: {
5292
+ signal: ["signal_type", "signal_date", "signal_source", "why_now", "signal_confidence", "buyer_stage"],
5293
+ prioritization: ["priority_score", "priority_reason", "next_best_action", "review_reason"]
5294
+ },
5295
+ qualityGates: [
5296
+ "Do not use stale, unsupported, or weak signals as personalization proof.",
5297
+ "Signal fit cannot override hard ICP, geography, suppression, or verified-email gates.",
5298
+ "Rows with uncertain why-now evidence must stay review-only."
5299
+ ],
5300
+ activationBoundary: "Signal ranking can prioritize review and copy. It does not approve outreach, sender loading, exports, or sends.",
5301
+ memoryKeywords: ["buying signals", "intent data", "funding trigger", "new executive", "tech adoption", "buyer journey", "why now"],
5302
+ knowledgeSources: ["gtm/signal-based-selling", "gtm/signal-combination-matrix", "gtm/advanced/intent-data-strategies", "gtm/advanced/buyer-journey-mapping", "gtm/signals/funding-round", "gtm/signals/new-executive", "gtm/signals/tech-adoption", "recipes/funding-trigger-outreach", "recipes/new-hire-outreach"]
5303
+ },
5304
+ {
5305
+ slug: "multi-channel-sequence-fit",
5306
+ label: "Multi-Channel Sequence Fit",
5307
+ whenToUse: [
5308
+ "The campaign needs email, LinkedIn, call, webhook, CRM, or sequencer coordination.",
5309
+ "Copy and delivery should adapt by persona, channel, deliverability risk, and proof strength."
5310
+ ],
5311
+ sequence: [
5312
+ "Choose channel mix from persona, deal motion, evidence strength, deliverability constraints, and connected destination readiness.",
5313
+ "Draft sequence logic with email, LinkedIn, call, and task handoffs only where supported by the approved destination.",
5314
+ "Keep copy concise, proof-backed, channel-specific, and reviewable before delivery.",
5315
+ "Run deliverability and destination readiness checks before sender load or launch approval."
5316
+ ],
5317
+ requiredFields: {
5318
+ channel: ["channel", "touch_number", "persona_context", "message_angle", "channel_blocker", "approval_status"],
5319
+ deliverability: ["verified_email", "domain_risk", "send_window", "unsubscribe_risk", "sender_ready"]
5320
+ },
5321
+ qualityGates: [
5322
+ "Email copy must be row-supported and under the configured campaign tone/length limits.",
5323
+ "LinkedIn, phone, CRM, webhook, and sequencer actions remain destination-locked until approved.",
5324
+ "Deliverability risk or missing sender readiness blocks launch even when rows are qualified."
5325
+ ],
5326
+ activationBoundary: "Sequence strategy is draft-only. External writes, sender loading, launch, and sending need separate explicit approval.",
5327
+ memoryKeywords: ["outbound sequencing", "email personalization", "LinkedIn outreach", "cold calling", "multi-channel orchestration", "deliverability"],
5328
+ knowledgeSources: ["gtm/outbound-sequencing", "gtm/email-personalization", "gtm/linkedin-outreach-strategies", "gtm/cold-calling-frameworks", "gtm/multi-channel-orchestration", "gtm/advanced/email-deliverability", "systems/blueprints/multi-channel-orchestration"]
5329
+ },
5330
+ {
5331
+ slug: "revops-feedback-loop",
5332
+ label: "RevOps Feedback Loop",
5333
+ whenToUse: [
5334
+ "The campaign should improve from replies, meetings, delivery results, CRM outcomes, or operator QA.",
5335
+ "The operator needs post-build monitoring, governance, or continuous ops follow-up."
5336
+ ],
5337
+ sequence: [
5338
+ "Define success metrics, guardrails, and feedback sources before launch.",
5339
+ "Collect aggregate outcomes, QA blockers, delivery health, and operator edits after each campaign phase.",
5340
+ "Separate workspace-private evidence from public-safe learnings and Brain defaults.",
5341
+ "Feed approved aggregate learnings back into future sourcing, qualification, copy, and suppression rules."
5342
+ ],
5343
+ requiredFields: {
5344
+ feedback: ["source", "event_type", "metric", "sample_size", "confidence", "learning_candidate"],
5345
+ governance: ["approval_owner", "last_reviewed_at", "risk_flag", "next_review_action"]
5346
+ },
5347
+ qualityGates: [
5348
+ "Do not write memory or Brain learnings from raw replies, private labels, or provider payloads without approved redaction.",
5349
+ "Use holdouts or pre/post comparisons before claiming causal lift.",
5350
+ "Delivery, suppression, and opt-out signals must update future gates before scale-up."
5351
+ ],
5352
+ activationBoundary: "Feedback review is read-only until memory writes, Brain writes, CRM updates, or campaign changes are explicitly approved.",
5353
+ memoryKeywords: ["RevOps", "feedback loop", "campaign learning", "reply outcomes", "sales marketing alignment", "data hygiene", "holdout"],
5354
+ knowledgeSources: ["gtm/revenue-operations", "gtm/advanced/sales-marketing-alignment", "gtm/advanced/predictive-lead-scoring", "gtm/data-quality-ops", "data-ops/contact-data-lifecycle", "recipes/crm-data-hygiene", "gtm/playbooks/revops-automation-playbook"]
4083
5355
  }
4084
5356
  ];
4085
5357
  CAMPAIGN_BUILDER_STRATEGY_TEMPLATES = [
@@ -4166,7 +5438,15 @@ var init_campaign_builder_agent = __esm({
4166
5438
  agencyContext: {
4167
5439
  partnerEcosystem: ["signaliz", "instantly", "nango"]
4168
5440
  },
4169
- operatingPlaybookSlugs: ["net-new-suppressed-list", "proof-first-vertical-gate", "signal-led-copy-approval"]
5441
+ operatingPlaybookSlugs: [
5442
+ "net-new-suppressed-list",
5443
+ "proof-first-vertical-gate",
5444
+ "signal-led-copy-approval",
5445
+ "icp-persona-segmentation",
5446
+ "buying-signal-prioritization",
5447
+ "multi-channel-sequence-fit",
5448
+ "revops-feedback-loop"
5449
+ ]
4170
5450
  },
4171
5451
  {
4172
5452
  slug: "non-medical-home-care",
@@ -4239,7 +5519,15 @@ var init_campaign_builder_agent = __esm({
4239
5519
  agencyContext: {
4240
5520
  partnerEcosystem: ["clay", "octave", "signaliz", "instantly", "nango"]
4241
5521
  },
4242
- operatingPlaybookSlugs: ["proof-first-vertical-gate", "domain-first-recovery", "table-workflow-handoff"]
5522
+ operatingPlaybookSlugs: [
5523
+ "proof-first-vertical-gate",
5524
+ "domain-first-recovery",
5525
+ "table-workflow-handoff",
5526
+ "icp-persona-segmentation",
5527
+ "buying-signal-prioritization",
5528
+ "multi-channel-sequence-fit",
5529
+ "revops-feedback-loop"
5530
+ ]
4243
5531
  },
4244
5532
  {
4245
5533
  slug: "agency-founder-led",
@@ -4300,7 +5588,15 @@ var init_campaign_builder_agent = __esm({
4300
5588
  agencyContext: {
4301
5589
  partnerEcosystem: ["signaliz", "octave", "instantly", "nango"]
4302
5590
  },
4303
- operatingPlaybookSlugs: ["net-new-suppressed-list", "signal-led-copy-approval", "table-workflow-handoff"]
5591
+ operatingPlaybookSlugs: [
5592
+ "net-new-suppressed-list",
5593
+ "signal-led-copy-approval",
5594
+ "table-workflow-handoff",
5595
+ "icp-persona-segmentation",
5596
+ "buying-signal-prioritization",
5597
+ "multi-channel-sequence-fit",
5598
+ "revops-feedback-loop"
5599
+ ]
4304
5600
  },
4305
5601
  {
4306
5602
  slug: "cloud-infrastructure-displacement",
@@ -4387,7 +5683,15 @@ var init_campaign_builder_agent = __esm({
4387
5683
  agencyContext: {
4388
5684
  partnerEcosystem: ["signaliz", "octave", "instantly", "nango"]
4389
5685
  },
4390
- operatingPlaybookSlugs: ["proof-first-vertical-gate", "domain-first-recovery", "signal-led-copy-approval"]
5686
+ operatingPlaybookSlugs: [
5687
+ "proof-first-vertical-gate",
5688
+ "domain-first-recovery",
5689
+ "signal-led-copy-approval",
5690
+ "icp-persona-segmentation",
5691
+ "buying-signal-prioritization",
5692
+ "multi-channel-sequence-fit",
5693
+ "revops-feedback-loop"
5694
+ ]
4391
5695
  }
4392
5696
  ];
4393
5697
  CampaignBuilderAgent = class {
@@ -4418,6 +5722,9 @@ var init_campaign_builder_agent = __esm({
4418
5722
  const plan = await this.createPlan(request, options);
4419
5723
  return createCampaignBuilderReadiness(plan);
4420
5724
  }
5725
+ activeWorkContext(input) {
5726
+ return createCampaignBuilderActiveWorkContext(input);
5727
+ }
4421
5728
  async proof(request, options = {}) {
4422
5729
  const plan = await this.createPlan(request, options);
4423
5730
  const result = await this.dryRunPlan(plan, {
@@ -4459,6 +5766,7 @@ var init_campaign_builder_agent = __esm({
4459
5766
  },
4460
5767
  brain_defaults: plan.buildRequest.brainDefaults,
4461
5768
  brain_preflight: plan.buildRequest.brainPreflight,
5769
+ learning_holdout: plan.buildRequest.learningHoldout,
4462
5770
  campaign_strategy_context: plan.buildRequest.campaignStrategyContext,
4463
5771
  delivery_risk_preflight: plan.buildRequest.deliveryRisk,
4464
5772
  delivery_risk: plan.buildRequest.deliveryRisk
@@ -4472,7 +5780,8 @@ var init_campaign_builder_agent = __esm({
4472
5780
  estimated_credits: plan.estimatedCredits
4473
5781
  },
4474
5782
  brain_delivery_risk_preflight: plan.buildRequest.deliveryRisk,
4475
- delivery_risk: plan.buildRequest.deliveryRisk
5783
+ delivery_risk: plan.buildRequest.deliveryRisk,
5784
+ learning_holdout: plan.buildRequest.learningHoldout
4476
5785
  },
4477
5786
  approval_required: plan.approvals.some((approval) => approval.blocking),
4478
5787
  actor_type: "agent",
@@ -4788,6 +6097,17 @@ var init_campaign_builder_agent = __esm({
4788
6097
  return this.client.mcp("tools/call", { name: tool, arguments: compact(args) });
4789
6098
  }
4790
6099
  };
6100
+ CAMPAIGN_BUILDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled", "cancelled"]);
6101
+ CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["failed", "canceled", "cancelled"]);
6102
+ NORTH_STAR_BANNED_COPY_PHRASES = [
6103
+ "i have been following",
6104
+ "i saw that",
6105
+ "would you be opposed",
6106
+ "unlock new opportunities",
6107
+ "significant success",
6108
+ "we understand",
6109
+ "hope this finds you well"
6110
+ ];
4791
6111
  }
4792
6112
  });
4793
6113
 
@@ -7345,6 +8665,36 @@ function recordsFromParams(params) {
7345
8665
  if (params.input) return { input: params.input };
7346
8666
  return { input: {} };
7347
8667
  }
8668
+ function toFusionConfig(params) {
8669
+ return {
8670
+ system_prompt: params.systemPrompt || params.system_prompt,
8671
+ user_template: params.userTemplate || params.user_template || params.prompt,
8672
+ preset: params.preset,
8673
+ temperature: params.temperature,
8674
+ max_tool_calls: params.maxToolCalls || params.max_tool_calls,
8675
+ max_tokens: params.maxTokens || params.max_tokens,
8676
+ timeout_ms: params.timeoutMs || params.timeout_ms,
8677
+ output_fields: params.outputFields || params.output_fields || [{ name: "response", type: "text", description: "Structured Fusion enrichment result" }]
8678
+ };
8679
+ }
8680
+ function toFusionSpendControls(params) {
8681
+ const controls = {};
8682
+ const dryRun = params.dryRun ?? params.dry_run;
8683
+ const confirmSpend = params.confirmSpend ?? params.confirm_spend;
8684
+ const maxCredits = params.maxCredits ?? params.max_credits;
8685
+ const maxCostUsd = params.maxCostUsd ?? params.max_cost_usd;
8686
+ if (dryRun !== void 0) controls.dry_run = dryRun;
8687
+ if (confirmSpend !== void 0) controls.confirm_spend = confirmSpend;
8688
+ if (maxCredits !== void 0) controls.max_credits = maxCredits;
8689
+ if (maxCostUsd !== void 0) controls.max_cost_usd = maxCostUsd;
8690
+ return controls;
8691
+ }
8692
+ function fusionRecordsFromParams(params) {
8693
+ const records = params.records || params.inputs;
8694
+ if (records?.length) return { records };
8695
+ if (params.input) return { input: params.input };
8696
+ return { input: {} };
8697
+ }
7348
8698
  var Ai;
7349
8699
  var init_ai = __esm({
7350
8700
  "src/resources/ai.ts"() {
@@ -7382,6 +8732,33 @@ var init_ai = __esm({
7382
8732
  raw: data
7383
8733
  };
7384
8734
  }
8735
+ /**
8736
+ * Plan or run experimental AI Enrichment Fusion with explicit spend controls.
8737
+ */
8738
+ async fusion(params) {
8739
+ if (!params.prompt && !params.userTemplate && !params.user_template) {
8740
+ throw new Error("prompt or userTemplate is required.");
8741
+ }
8742
+ const data = await this.client.post("ai-enrichment-fusion", {
8743
+ ...fusionRecordsFromParams(params),
8744
+ config: toFusionConfig(params),
8745
+ ...toFusionSpendControls(params)
8746
+ });
8747
+ return {
8748
+ success: data.success ?? false,
8749
+ capability: data.capability ?? "ai_enrichment_fusion",
8750
+ displayName: data.display_name ?? "AI Enrichment Fusion",
8751
+ results: data.results ?? [],
8752
+ creditsUsed: data.total_credits_used ?? data.credits_used ?? 0,
8753
+ model: data.model,
8754
+ fusion: data.fusion,
8755
+ tokensUsed: data.tokens_used ?? 0,
8756
+ costUsd: data.cost_usd ?? 0,
8757
+ costSummary: data.cost_summary,
8758
+ billingMetadata: data.billing_metadata,
8759
+ raw: data
8760
+ };
8761
+ }
7385
8762
  };
7386
8763
  }
7387
8764
  });
@@ -7858,6 +9235,16 @@ var init_gtm_kernel = __esm({
7858
9235
  min_privacy_k: input.minPrivacyK
7859
9236
  });
7860
9237
  }
9238
+ /** Queue a dry-run-first bridge from imported historical feedback to attribution-only Campaign Build surrogates. */
9239
+ async runHistoricalCampaignBuildBridge(input = {}) {
9240
+ return this.callMcp("gtm_historical_campaign_build_bridge_run", {
9241
+ dry_run: input.dryRun,
9242
+ write_approved: input.writeApproved,
9243
+ max_events: input.maxEvents,
9244
+ max_campaigns: input.maxCampaigns,
9245
+ min_feedback_events: input.minFeedbackEvents
9246
+ });
9247
+ }
7861
9248
  /** Prepare a provider-agnostic feedback webhook URL for Smartlead, HeyReach, Airbyte, or custom sender events. */
7862
9249
  async prepareFeedbackWebhook(input) {
7863
9250
  return this.callMcp("gtm_feedback_webhook_prepare", {
@@ -8034,6 +9421,24 @@ var init_gtm_kernel = __esm({
8034
9421
  limit: input.limit
8035
9422
  });
8036
9423
  }
9424
+ /** Predict booked-meeting likelihood for uploaded, inline, or MCP-sourced lead rows without persisting raw rows. */
9425
+ async predictMeetingLikelihood(input) {
9426
+ return this.callMcp("gtm_predict_meeting_likelihood", {
9427
+ lead_rows: input.leadRows,
9428
+ input_source: input.inputSource,
9429
+ campaign_id: input.campaignId,
9430
+ campaign_build_id: input.campaignBuildId,
9431
+ days: input.days,
9432
+ feedback_limit: input.feedbackLimit,
9433
+ include_features: input.includeFeatures,
9434
+ include_global: input.includeGlobal,
9435
+ min_feedback_events: input.minFeedbackEvents,
9436
+ min_historical_feature_sample_size: input.minHistoricalFeatureSampleSize,
9437
+ min_workspace_count: input.minWorkspaceCount,
9438
+ min_privacy_k: input.minPrivacyK,
9439
+ limit: input.limit
9440
+ });
9441
+ }
8037
9442
  /** Rank provider-chain, ICP, source, copy, and sequence failure patterns without writing raw lead data. */
8038
9443
  async failurePatterns(options = {}) {
8039
9444
  return this.callMcp("gtm_brain_failure_patterns", {
@@ -8534,6 +9939,76 @@ var init_gtm_kernel = __esm({
8534
9939
  }
8535
9940
  });
8536
9941
 
9942
+ // src/resources/public-ground-truth.ts
9943
+ var PublicGroundTruth;
9944
+ var init_public_ground_truth = __esm({
9945
+ "src/resources/public-ground-truth.ts"() {
9946
+ "use strict";
9947
+ PublicGroundTruth = class {
9948
+ constructor(client) {
9949
+ this.client = client;
9950
+ }
9951
+ /**
9952
+ * Search the public-ground-truth cache by company/name/domain/native ID,
9953
+ * or by plain-language industry + location.
9954
+ * Results use the same snake_case wire shape returned by the hosted MCP API.
9955
+ */
9956
+ async search(params) {
9957
+ return this.client.mcp("tools/call", {
9958
+ name: "search_public_ground_truth",
9959
+ arguments: {
9960
+ query: params.query ?? params.search,
9961
+ industry: params.industry,
9962
+ location: params.location,
9963
+ source_id: params.sourceId ?? params.source_id,
9964
+ entity_type: params.entityType ?? params.entity_type,
9965
+ native_id: params.nativeId ?? params.native_id,
9966
+ entity_name: params.entityName ?? params.entity_name,
9967
+ domain: params.domain,
9968
+ website_url: params.websiteUrl ?? params.website_url,
9969
+ phone: params.phone,
9970
+ email: params.email,
9971
+ city: params.city,
9972
+ state: params.state,
9973
+ postal_code: params.postalCode ?? params.postal_code,
9974
+ country: params.country,
9975
+ source_url: params.sourceUrl ?? params.source_url,
9976
+ join_keys: params.joinKeys ?? params.join_keys,
9977
+ row_data: params.rowData ?? params.row_data,
9978
+ naics: params.naics,
9979
+ industry_code: params.industryCode ?? params.industry_code,
9980
+ industry_type: params.industryType ?? params.industry_type,
9981
+ year: params.year,
9982
+ quarter: params.quarter,
9983
+ area_fips: params.areaFips ?? params.area_fips,
9984
+ geo_id: params.geoId ?? params.geo_id,
9985
+ own_code: params.ownCode ?? params.own_code,
9986
+ source_updated_after: params.sourceUpdatedAfter ?? params.source_updated_after,
9987
+ source_updated_before: params.sourceUpdatedBefore ?? params.source_updated_before,
9988
+ observed_after: params.observedAfter ?? params.observed_after,
9989
+ observed_before: params.observedBefore ?? params.observed_before,
9990
+ limit: params.limit,
9991
+ offset: params.offset
9992
+ }
9993
+ });
9994
+ }
9995
+ /**
9996
+ * List available public-ground-truth cache sources and their join-key metadata.
9997
+ */
9998
+ async listSources(params = {}) {
9999
+ return this.client.mcp("tools/call", {
10000
+ name: "list_public_ground_truth_sources",
10001
+ arguments: {
10002
+ query: params.query ?? params.search,
10003
+ status: params.status,
10004
+ limit: params.limit
10005
+ }
10006
+ });
10007
+ }
10008
+ };
10009
+ }
10010
+ });
10011
+
8537
10012
  // src/index.ts
8538
10013
  var index_exports = {};
8539
10014
  __export(index_exports, {
@@ -8548,10 +10023,12 @@ __export(index_exports, {
8548
10023
  GtmKernel: () => GtmKernel,
8549
10024
  Icps: () => Icps,
8550
10025
  Ops: () => Ops,
10026
+ PublicGroundTruth: () => PublicGroundTruth,
8551
10027
  Signaliz: () => Signaliz,
8552
10028
  SignalizError: () => SignalizError,
8553
10029
  applyCampaignBuilderStrategyTemplate: () => applyCampaignBuilderStrategyTemplate,
8554
10030
  collectExecutionReferences: () => collectExecutionReferences,
10031
+ createCampaignBuilderActiveWorkContext: () => createCampaignBuilderActiveWorkContext,
8555
10032
  createCampaignBuilderAgentPlan: () => createCampaignBuilderAgentPlan,
8556
10033
  createCampaignBuilderAgentRequestTemplate: () => createCampaignBuilderAgentRequestTemplate,
8557
10034
  createCampaignBuilderApproval: () => createCampaignBuilderApproval,
@@ -8606,6 +10083,7 @@ function inferMcpToolCategory(toolName) {
8606
10083
  }
8607
10084
  if (toolName.includes("icp")) return "icp";
8608
10085
  if (toolName.startsWith("ai_clean_")) return "data_cleaning";
10086
+ if (toolName.includes("public_ground_truth")) return "public_data";
8609
10087
  if (toolName.includes("system") || toolName.includes("workflow")) return "automation";
8610
10088
  if (toolName.includes("agent") || toolName.includes("platform_health") || toolName === "discover_capabilities") return "observability";
8611
10089
  return void 0;
@@ -8659,9 +10137,11 @@ var init_index = __esm({
8659
10137
  init_ops();
8660
10138
  init_ai();
8661
10139
  init_gtm_kernel();
10140
+ init_public_ground_truth();
8662
10141
  init_errors();
8663
10142
  init_ai();
8664
10143
  init_gtm_kernel();
10144
+ init_public_ground_truth();
8665
10145
  init_campaigns();
8666
10146
  init_campaign_builder_agent();
8667
10147
  init_icps();
@@ -8683,6 +10163,7 @@ var init_index = __esm({
8683
10163
  this.ops = new Ops(this.client);
8684
10164
  this.ai = new Ai(this.client);
8685
10165
  this.gtm = new GtmKernel(this.client);
10166
+ this.publicGroundTruth = new PublicGroundTruth(this.client);
8686
10167
  }
8687
10168
  /** Get current workspace info including credits */
8688
10169
  async getWorkspace() {
@@ -8841,6 +10322,13 @@ async function main() {
8841
10322
  await handleGtmCommand(signaliz, process.argv.slice(3));
8842
10323
  break;
8843
10324
  }
10325
+ case "public-data":
10326
+ case "ground-truth":
10327
+ case "public-ground-truth": {
10328
+ const signaliz = await createSignalizClient();
10329
+ await handlePublicGroundTruthCommand(signaliz, process.argv.slice(3));
10330
+ break;
10331
+ }
8844
10332
  case "campaign-agent":
8845
10333
  case "campaign-builder": {
8846
10334
  await handleCampaignAgentCommand(createSignalizClient, process.argv.slice(3));
@@ -8850,6 +10338,51 @@ async function main() {
8850
10338
  printHelp();
8851
10339
  }
8852
10340
  }
10341
+ async function handlePublicGroundTruthCommand(signaliz, argv) {
10342
+ const subcommand = argv[0] && !argv[0].startsWith("--") ? argv[0] : "search";
10343
+ if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
10344
+ printPublicGroundTruthHelp();
10345
+ return;
10346
+ }
10347
+ if (subcommand === "sources" || subcommand === "list-sources") {
10348
+ const flags2 = parseFlags(argv.slice(subcommand === argv[0] ? 1 : 0));
10349
+ const result2 = await signaliz.publicGroundTruth.listSources({
10350
+ query: stringFlag(flags2, "query", "q", "search"),
10351
+ status: stringFlag(flags2, "status"),
10352
+ limit: numberFlag(flags2, "limit")
10353
+ });
10354
+ if (booleanFlag(flags2, "json")) {
10355
+ printJson(result2);
10356
+ } else {
10357
+ printPublicGroundTruthSources(result2);
10358
+ }
10359
+ return;
10360
+ }
10361
+ if (subcommand !== "search") {
10362
+ throw new Error(`Unknown public-data command: ${subcommand}`);
10363
+ }
10364
+ const flags = parseFlags(argv.slice(subcommand === argv[0] ? 1 : 0));
10365
+ const query = stringFlag(flags, "query", "q", "search");
10366
+ const sourceId = stringFlag(flags, "source-id", "sourceId");
10367
+ const entityType = stringFlag(flags, "entity-type", "entityType");
10368
+ const filters = publicGroundTruthFiltersFromFlags(flags);
10369
+ if (!query && !sourceId && !entityType && Object.keys(filters).length === 0) {
10370
+ throw new Error("public-data search requires --query or at least one filter");
10371
+ }
10372
+ const result = await signaliz.publicGroundTruth.search({
10373
+ query,
10374
+ sourceId,
10375
+ entityType,
10376
+ ...filters,
10377
+ limit: numberFlag(flags, "limit"),
10378
+ offset: numberFlag(flags, "offset")
10379
+ });
10380
+ if (booleanFlag(flags, "json")) {
10381
+ printJson(result);
10382
+ } else {
10383
+ printPublicGroundTruthSearch(result);
10384
+ }
10385
+ }
8853
10386
  async function handleGtmCommand(signaliz, argv) {
8854
10387
  const subcommand = argv[0];
8855
10388
  if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
@@ -9836,6 +11369,69 @@ function jsonObjectFromString(value, label) {
9836
11369
  }
9837
11370
  return parsed;
9838
11371
  }
11372
+ var PUBLIC_GROUND_TRUTH_CLI_FILTERS = [
11373
+ ["industry", "industry"],
11374
+ ["location", "location"],
11375
+ ["nativeId", "native-id", "nativeId"],
11376
+ ["entityName", "entity-name", "entityName"],
11377
+ ["domain", "domain"],
11378
+ ["websiteUrl", "website-url", "websiteUrl"],
11379
+ ["phone", "phone"],
11380
+ ["email", "email"],
11381
+ ["city", "city"],
11382
+ ["state", "state"],
11383
+ ["postalCode", "postal-code", "postalCode"],
11384
+ ["country", "country"],
11385
+ ["sourceUrl", "source-url", "sourceUrl"],
11386
+ ["naics", "naics"],
11387
+ ["industryCode", "industry-code", "industryCode"],
11388
+ ["industryType", "industry-type", "industryType"],
11389
+ ["year", "year"],
11390
+ ["quarter", "quarter"],
11391
+ ["areaFips", "area-fips", "areaFips"],
11392
+ ["geoId", "geo-id", "geoId"],
11393
+ ["ownCode", "own-code", "ownCode"],
11394
+ ["sourceUpdatedAfter", "source-updated-after", "sourceUpdatedAfter"],
11395
+ ["sourceUpdatedBefore", "source-updated-before", "sourceUpdatedBefore"],
11396
+ ["observedAfter", "observed-after", "observedAfter"],
11397
+ ["observedBefore", "observed-before", "observedBefore"]
11398
+ ];
11399
+ function objectFromKeyValueFlags(flags, keyValueNames, jsonNames) {
11400
+ const object = {};
11401
+ for (const jsonName of jsonNames) {
11402
+ const parsed = jsonObjectFlag(flags, jsonName);
11403
+ if (parsed) Object.assign(object, parsed);
11404
+ }
11405
+ for (const pair of repeatedStringFlag(flags, ...keyValueNames)) {
11406
+ const eq = pair.indexOf("=");
11407
+ if (eq <= 0) throw new Error(`Expected key=value for --${keyValueNames[0]}`);
11408
+ const key = pair.slice(0, eq).trim();
11409
+ const value = pair.slice(eq + 1).trim();
11410
+ if (!key) throw new Error(`Expected non-empty key for --${keyValueNames[0]}`);
11411
+ object[key] = value;
11412
+ }
11413
+ return Object.keys(object).length > 0 ? object : void 0;
11414
+ }
11415
+ function publicGroundTruthFiltersFromFlags(flags) {
11416
+ const filters = {};
11417
+ for (const [paramName, ...flagNames] of PUBLIC_GROUND_TRUTH_CLI_FILTERS) {
11418
+ const value = stringFlag(flags, ...flagNames);
11419
+ if (value) filters[paramName] = value;
11420
+ }
11421
+ const joinKeys = objectFromKeyValueFlags(
11422
+ flags,
11423
+ ["join-key", "joinKey"],
11424
+ ["join-keys-json", "joinKeysJson"]
11425
+ );
11426
+ const rowData = objectFromKeyValueFlags(
11427
+ flags,
11428
+ ["row-data", "rowData"],
11429
+ ["row-data-json", "rowDataJson"]
11430
+ );
11431
+ if (joinKeys) filters.joinKeys = joinKeys;
11432
+ if (rowData) filters.rowData = rowData;
11433
+ return filters;
11434
+ }
9839
11435
  function printResult(result, flags) {
9840
11436
  if (booleanFlag(flags, "json")) {
9841
11437
  printJson(result);
@@ -9912,10 +11508,59 @@ function printCampaignAgentExecution(payload, flags) {
9912
11508
  printJson(payload);
9913
11509
  return;
9914
11510
  }
11511
+ printCampaignAgentOperatorVerdict(payload);
9915
11512
  printCampaignAgentPlan(payload.plan);
9916
11513
  console.log("\nResult:");
9917
11514
  printJson(payload.result);
9918
11515
  }
11516
+ function printCampaignAgentOperatorVerdict(payload) {
11517
+ const result = asRecord4(payload.result);
11518
+ const build = hasRecordKeys(asRecord4(result.build)) ? asRecord4(result.build) : result;
11519
+ const finalStatus = asRecord4(result.finalStatus);
11520
+ const deliveryApproval = asRecord4(result.deliveryApproval);
11521
+ const status = textFromRecord(finalStatus, "status") ?? textFromRecord(build, "status") ?? "unknown";
11522
+ const dryRun = build.dryRun === true || build.dry_run === true || status === "dry_run";
11523
+ const campaignBuildId = textFromRecord(build, "campaignBuildId", "campaign_build_id") ?? textFromRecord(finalStatus, "campaignBuildId", "campaign_build_id");
11524
+ const campaignId = textFromRecord(build, "campaignId", "campaign_id") ?? textFromRecord(finalStatus, "campaignId", "campaign_id");
11525
+ if (dryRun) {
11526
+ console.log("Operator verdict: dry run complete; nothing launched.");
11527
+ console.log("Safety: Dry-run only. No spend, provider write, sender load, delivery, or launch ran.");
11528
+ } else if (textFromRecord(finalStatus, "status") === "completed") {
11529
+ console.log("Operator verdict: approved campaign build completed.");
11530
+ console.log("Safety: Approved build path. Review rows and artifacts before any delivery expansion.");
11531
+ } else {
11532
+ console.log(`Operator verdict: approved campaign build ${status}.`);
11533
+ console.log("Safety: Approved build path. Spend and provider writes require the supplied approval packet; delivery remains separately gated.");
11534
+ }
11535
+ console.log(`Approval state: ${campaignAgentApprovalState(payload)}`);
11536
+ if (campaignId) console.log(`Campaign ID: ${campaignId}`);
11537
+ if (campaignBuildId) console.log(`Campaign build ID: ${campaignBuildId}`);
11538
+ if (hasRecordKeys(finalStatus)) console.log(`Final status: ${textFromRecord(finalStatus, "status") ?? "unknown"}`);
11539
+ if (hasRecordKeys(deliveryApproval)) console.log(`Delivery approval: ${textFromRecord(deliveryApproval, "status") ?? "unknown"}`);
11540
+ console.log(`Next safe command: ${campaignAgentNextSafeCommand(payload.plan, dryRun, campaignBuildId)}`);
11541
+ console.log("");
11542
+ }
11543
+ function campaignAgentApprovalState(payload) {
11544
+ if (payload.approval) return "approval packet supplied";
11545
+ const required = payload.plan.approvals.map((item) => item.type);
11546
+ return required.length ? `missing ${required.join(", ")}` : "no explicit approvals required";
11547
+ }
11548
+ function campaignAgentNextSafeCommand(plan, dryRun, campaignBuildId) {
11549
+ if (campaignBuildId) return `signaliz campaign-agent review ${campaignBuildId} --limit 100`;
11550
+ if (!dryRun) return "signaliz campaign-agent status <campaign-build-id>";
11551
+ return [
11552
+ "signaliz campaign-agent build-approved",
11553
+ "--goal",
11554
+ shellQuote(plan.campaignName),
11555
+ "--target-count",
11556
+ String(plan.targetCount),
11557
+ "--confirm-launch",
11558
+ "--approve-all",
11559
+ "--approved-by <operator-email>",
11560
+ "--spend-limit",
11561
+ String(plan.estimatedCredits)
11562
+ ].join(" ");
11563
+ }
9919
11564
  function printCampaignAgentReadiness(readiness) {
9920
11565
  const summary = asRecord4(readiness.summary);
9921
11566
  const plan = readiness.plan;
@@ -9954,6 +11599,13 @@ function printCampaignAgentBuildStatus(status) {
9954
11599
  if (status.campaignId) console.log(`Campaign ID: ${status.campaignId}`);
9955
11600
  console.log(`Status: ${status.status}`);
9956
11601
  console.log(`Phase: ${status.currentPhase || "N/A"}`);
11602
+ const terminalState = asRecord4(status.terminalState);
11603
+ if (terminalState.kind) {
11604
+ console.log(`Terminal state: ${terminalState.kind} (${terminalState.label || "no label"})`);
11605
+ if (terminalState.heartbeatAgeSeconds !== void 0 && terminalState.heartbeatAgeSeconds !== null) {
11606
+ console.log(`Heartbeat age: ${terminalState.heartbeatAgeSeconds}s`);
11607
+ }
11608
+ }
9957
11609
  console.log(`Rows: ${status.recordsSucceeded}/${status.recordsTotal} succeeded, ${status.recordsFailed} failed`);
9958
11610
  printCampaignCustomerRowCounts(status);
9959
11611
  console.log(`Artifacts: ${status.artifactCount}`);
@@ -9975,6 +11627,8 @@ function printCampaignAgentBuildStatus(status) {
9975
11627
  console.log(`Next: ${status.nextAction}`);
9976
11628
  } else if (status.status === "pending_approval") {
9977
11629
  console.log(`Next: signaliz campaign-agent approve ${status.campaignBuildId} --destination-type webhook`);
11630
+ } else if (terminalState.safeNextAction) {
11631
+ console.log(`Next: ${terminalState.safeNextAction}`);
9978
11632
  }
9979
11633
  }
9980
11634
  function printCampaignCustomerRowCounts(status) {
@@ -9982,7 +11636,10 @@ function printCampaignCustomerRowCounts(status) {
9982
11636
  if (!counts || typeof counts !== "object") return;
9983
11637
  const value = (camel, snake) => counts[camel] ?? counts[snake] ?? "n/a";
9984
11638
  const requested = value("requestedTarget", "requested_target");
9985
- console.log(`Customer rows: accepted ${value("acceptedRows", "accepted_rows")}/${requested}, copied ${value("copiedRows", "copied_rows")}, approval-ready ${value("approvalReadyRows", "approval_ready_rows")}, QA-ready ${value("qaReadyRows", "qa_ready_rows")}, delivered ${value("deliveredRows", "delivered_rows")}`);
11639
+ const copied = value("copiedRows", "copied_rows");
11640
+ const copySkipped = value("copySkippedRows", "copy_skipped_rows");
11641
+ const copyLabel = typeof copySkipped === "number" && copySkipped > 0 && (!copied || copied === "n/a") ? `copy-skipped ${copySkipped}` : `copied ${copied}`;
11642
+ console.log(`Customer rows: accepted ${value("acceptedRows", "accepted_rows")}/${requested}, ${copyLabel}, approval-ready ${value("approvalReadyRows", "approval_ready_rows")}, QA-ready ${value("qaReadyRows", "qa_ready_rows")}, delivered ${value("deliveredRows", "delivered_rows")}`);
9986
11643
  const shortfalls = [
9987
11644
  ["accepted", value("acceptedShortfall", "accepted_shortfall")],
9988
11645
  ["usable", value("usableShortfall", "usable_shortfall")],
@@ -9998,17 +11655,20 @@ function printCampaignAgentBuildReview(review) {
9998
11655
  console.log(`Campaign: ${review.status?.name || review.campaignBuildId}`);
9999
11656
  console.log(`Status: ${summary.status || review.status?.status}`);
10000
11657
  console.log(`Phase: ${summary.phase || review.status?.currentPhase || "N/A"}`);
11658
+ console.log(`Terminal state: ${summary.terminalState || review.status?.terminalState?.kind || "unknown"}`);
10001
11659
  console.log(`Rows: ${summary.sampledRows || 0} sampled, ${summary.availableRows || 0} available`);
10002
11660
  console.log(`Qualified: ${summary.qualifiedRows || 0}`);
10003
11661
  console.log(`Emails: ${summary.rowsWithEmail || 0}`);
10004
11662
  console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
10005
11663
  console.log(`Artifacts: ${summary.artifactCount || 0}`);
11664
+ printCampaignAgentNorthStarScorecard(review.northStarScorecard);
10006
11665
  if (review.campaignBuildId) printCampaignArtifactDownload(review.status, review.campaignBuildId);
10007
11666
  console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
10008
11667
  if (Array.isArray(review.gates) && review.gates.length > 0) {
10009
11668
  console.log("\nGates:");
10010
11669
  for (const gate of review.gates) {
10011
- console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
11670
+ const status = String(gate.status || (gate.passed ? "pass" : "blocked")).toUpperCase();
11671
+ console.log(`- ${status} ${gate.label}: ${gate.detail}`);
10012
11672
  }
10013
11673
  }
10014
11674
  const warnings = Array.isArray(summary.warnings) ? summary.warnings : [];
@@ -10022,6 +11682,21 @@ function printCampaignAgentBuildReview(review) {
10022
11682
  for (const action of nextActions) console.log(`- ${action}`);
10023
11683
  }
10024
11684
  }
11685
+ function printCampaignAgentNorthStarScorecard(scorecard) {
11686
+ const card = asRecord4(scorecard);
11687
+ if (!card.version) return;
11688
+ const score = card.score === null || card.score === void 0 ? "pending" : `${card.score}%`;
11689
+ console.log(`North Star: ${String(card.status || "unknown")} (${score})`);
11690
+ if (card.moatState) console.log(`Moat state: ${card.moatState}`);
11691
+ const metrics = Array.isArray(card.metrics) ? card.metrics : [];
11692
+ if (metrics.length > 0) {
11693
+ console.log("\nNorth Star gates:");
11694
+ for (const metric of metrics.slice(0, 12)) {
11695
+ const item = asRecord4(metric);
11696
+ console.log(`- ${String(item.status || "review").toUpperCase()} ${item.label || item.id}: ${item.detail || ""}`);
11697
+ }
11698
+ }
11699
+ }
10025
11700
  function formatCampaignAgentRows(rows) {
10026
11701
  return rows.map((row) => {
10027
11702
  const data = asRecord4(row.data);
@@ -10099,6 +11774,20 @@ function printCampaignAgentCopySnippets(rows, limit = 5) {
10099
11774
  function asRecord4(value) {
10100
11775
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
10101
11776
  }
11777
+ function hasRecordKeys(value) {
11778
+ return Object.keys(value).length > 0;
11779
+ }
11780
+ function textFromRecord(record, ...keys) {
11781
+ for (const key of keys) {
11782
+ const value = record[key];
11783
+ if (typeof value === "string" && value.trim()) return value.trim();
11784
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
11785
+ }
11786
+ return void 0;
11787
+ }
11788
+ function shellQuote(value) {
11789
+ return `'${String(value || "").replace(/'/g, `'\\''`)}'`;
11790
+ }
10102
11791
  function printCampaignAgentProof(proof) {
10103
11792
  console.log(`Campaign-agent proof: ${proof.success ? "passed" : "failed"}`);
10104
11793
  const campaign = asRecord4(proof.campaign);
@@ -10116,6 +11805,51 @@ function printCampaignAgentProof(proof) {
10116
11805
  function printJson(value) {
10117
11806
  console.log(JSON.stringify(value, null, 2));
10118
11807
  }
11808
+ function truncateCell(value, width) {
11809
+ const text = String(value ?? "").replace(/\s+/g, " ").trim();
11810
+ return text.length > width ? `${text.slice(0, Math.max(width - 3, 0))}...` : text;
11811
+ }
11812
+ function printPublicGroundTruthSearch(result) {
11813
+ const records = Array.isArray(result.records) ? result.records : [];
11814
+ console.log(`Public data search: ${result.total_count ?? 0} matches (${records.length} returned)`);
11815
+ if (result.query) console.log(`Query: ${result.query}`);
11816
+ if (result.source_id) console.log(`Source: ${result.source_id}`);
11817
+ if (result.entity_type) console.log(`Entity type: ${result.entity_type}`);
11818
+ if (!records.length) {
11819
+ console.log("No records matched.");
11820
+ return;
11821
+ }
11822
+ console.log("");
11823
+ console.log("Source".padEnd(22) + "Native ID".padEnd(22) + "Name".padEnd(42) + "Industry".padEnd(28) + "Domain".padEnd(24) + "Loc".padEnd(12) + "Match");
11824
+ console.log("-".repeat(160));
11825
+ for (const record of records) {
11826
+ const loc = [record.city, record.state].filter(Boolean).join(", ");
11827
+ const industry = [record.industry_code, record.industry_name].filter(Boolean).join(" ");
11828
+ console.log(
11829
+ truncateCell(record.source_id, 21).padEnd(22) + truncateCell(record.native_id, 21).padEnd(22) + truncateCell(record.entity_name, 41).padEnd(42) + truncateCell(industry, 27).padEnd(28) + truncateCell(record.domain, 23).padEnd(24) + truncateCell(loc, 11).padEnd(12) + truncateCell(record.match_type, 16)
11830
+ );
11831
+ }
11832
+ if (result.next_offset !== null && result.next_offset !== void 0) {
11833
+ console.log(`
11834
+ Next page: --offset ${result.next_offset}`);
11835
+ }
11836
+ }
11837
+ function printPublicGroundTruthSources(result) {
11838
+ const sources = Array.isArray(result.sources) ? result.sources : [];
11839
+ console.log(`Public data sources: ${sources.length}/${result.total ?? sources.length}`);
11840
+ if (!sources.length) {
11841
+ console.log("No sources matched.");
11842
+ return;
11843
+ }
11844
+ console.log("");
11845
+ console.log("Source ID".padEnd(30) + "Status".padEnd(12) + "Rung".padEnd(7) + "Join key".padEnd(20) + "Cadence".padEnd(16) + "Name");
11846
+ console.log("-".repeat(120));
11847
+ for (const source of sources) {
11848
+ console.log(
11849
+ truncateCell(source.id, 29).padEnd(30) + truncateCell(source.ingestion_status, 11).padEnd(12) + String(source.ground_truth_rung ?? "").padEnd(7) + truncateCell(source.primary_join_key || source.join_key_quality, 19).padEnd(20) + truncateCell(source.update_cadence, 15).padEnd(16) + truncateCell(source.name, 40)
11850
+ );
11851
+ }
11852
+ }
10119
11853
  function printHelp() {
10120
11854
  console.log(`
10121
11855
  Signaliz CLI v1.0.0
@@ -10123,6 +11857,8 @@ Signaliz CLI v1.0.0
10123
11857
  Usage:
10124
11858
  signaliz test Verify API connectivity
10125
11859
  signaliz tools List all available MCP tools
11860
+ signaliz public-data search --query "swift transportation" --limit 10
11861
+ signaliz public-data sources --status ready
10126
11862
  signaliz gtm context --json Load GTM Kernel workspace context
10127
11863
  signaliz gtm bootstrap --json Inspect GTM Kernel readiness
10128
11864
  signaliz gtm templates --json List private-safe campaign strategy templates
@@ -10153,6 +11889,25 @@ Environment:
10153
11889
  SIGNALIZ_BASE_URL Optional API base URL override
10154
11890
  `);
10155
11891
  }
11892
+ function printPublicGroundTruthHelp() {
11893
+ console.log(`
11894
+ Signaliz public-data commands:
11895
+
11896
+ signaliz public-data search [--query TEXT] [--industry TEXT] [--location TEXT] [--source-id ID] [--domain DOMAIN] [--naics CODE] [--state ST] [--year YYYY] [--join-key k=v] [--row-data k=v] [--limit N] [--offset N] [--json]
11897
+ signaliz public-data sources [--query TEXT] [--status ready|partial|blocked|planned] [--limit N] [--json]
11898
+
11899
+ Aliases:
11900
+ signaliz ground-truth search ...
11901
+
11902
+ Examples:
11903
+ signaliz public-data search --query "swift transportation" --source-id fmcsa_company_census --limit 10
11904
+ signaliz public-data search --industry "software publishers" --location CA --limit 25 --json
11905
+ signaliz public-data search --source-id bls_qcew --naics 541511 --state CA --year 2025 --json
11906
+ signaliz public-data search --domain swifttrans.com --json
11907
+ signaliz public-data search --join-key ein=123456789 --json
11908
+ signaliz public-data sources --status ready --json
11909
+ `);
11910
+ }
10156
11911
  function printGtmHelp() {
10157
11912
  console.log(`
10158
11913
  Signaliz GTM commands: