@signaliz/sdk 1.0.17 → 1.0.18
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/README.md +7 -2
- package/dist/{chunk-ZKE4L57J.mjs → chunk-VFBQCWOM.mjs} +631 -42
- package/dist/cli.js +660 -44
- package/dist/cli.mjs +28 -1
- package/dist/index.d.mts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +631 -42
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +631 -42
- package/dist/mcp-config.mjs +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -881,6 +881,9 @@ function recordOrNull(value) {
|
|
|
881
881
|
function stringArray(value) {
|
|
882
882
|
return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
|
|
883
883
|
}
|
|
884
|
+
function recordArray(value) {
|
|
885
|
+
return Array.isArray(value) ? value.map(recordOrNull).filter((item) => Boolean(item)) : [];
|
|
886
|
+
}
|
|
884
887
|
function diagnosticMessages(value) {
|
|
885
888
|
if (!Array.isArray(value)) return [];
|
|
886
889
|
return value.map((item) => {
|
|
@@ -1042,7 +1045,8 @@ function buildArgs(request) {
|
|
|
1042
1045
|
suppress_prior_campaign_ids: request.cacheReusePolicy.suppressPriorCampaignIds,
|
|
1043
1046
|
suppress_prior_list_ids: request.cacheReusePolicy.suppressPriorListIds,
|
|
1044
1047
|
uniqueness: request.cacheReusePolicy.uniqueness,
|
|
1045
|
-
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata
|
|
1048
|
+
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata,
|
|
1049
|
+
verified_cache_ttl_days: request.cacheReusePolicy.verifiedCacheTtlDays
|
|
1046
1050
|
};
|
|
1047
1051
|
}
|
|
1048
1052
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
@@ -1247,7 +1251,11 @@ var init_campaigns = __esm({
|
|
|
1247
1251
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
1248
1252
|
brainContext: data.brain_context ?? {},
|
|
1249
1253
|
learningHoldout: data.learning_holdout ?? {},
|
|
1250
|
-
providerRoute: mapProviderRoute(data)
|
|
1254
|
+
providerRoute: mapProviderRoute(data),
|
|
1255
|
+
effectiveSourceRouting: recordOrNull(data.effective_source_routing ?? data.source_routing),
|
|
1256
|
+
sourcePlan: recordOrNull(data.source_plan ?? data.plan?.source_plan),
|
|
1257
|
+
sourceRoutes: recordArray(data.source_routes ?? data.source_plan?.routes ?? data.plan?.source_plan?.routes),
|
|
1258
|
+
sourceShards: recordArray(data.source_shards ?? data.source_plan?.shards ?? data.plan?.source_plan?.shards)
|
|
1251
1259
|
};
|
|
1252
1260
|
}
|
|
1253
1261
|
async getCampaignBuildStatus(campaignBuildId) {
|
|
@@ -2446,6 +2454,21 @@ function routesFromCustomerTools(tools) {
|
|
|
2446
2454
|
}))
|
|
2447
2455
|
);
|
|
2448
2456
|
}
|
|
2457
|
+
function normalizeCampaignBuilderLearningHoldout(input, gtmCampaignId, campaignName) {
|
|
2458
|
+
const source = asRecord(input);
|
|
2459
|
+
if (source.enabled === false) return { enabled: false };
|
|
2460
|
+
const controlPercentage = numberOrUndefined2(source.control_percentage ?? source.controlPercentage);
|
|
2461
|
+
const minControlSize = numberOrUndefined2(source.min_control_size ?? source.minControlSize);
|
|
2462
|
+
const experimentKey = stringValue(source.experiment_key ?? source.experimentKey) ?? (gtmCampaignId ? `gtm_campaign:${gtmCampaignId}:learning_holdout` : `campaign_builder_agent:${hashString(campaignName)}:learning_holdout`);
|
|
2463
|
+
return {
|
|
2464
|
+
enabled: true,
|
|
2465
|
+
controlPercentage: controlPercentage ?? 0.2,
|
|
2466
|
+
minControlSize: minControlSize ?? 50,
|
|
2467
|
+
experimentKey,
|
|
2468
|
+
sourceCampaignId: stringValue(source.source_campaign_id ?? source.sourceCampaignId) ?? gtmCampaignId,
|
|
2469
|
+
primaryMetric: stringValue(source.primary_metric ?? source.primaryMetric) ?? "positive_reply_rate"
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2449
2472
|
function createBuildRequest(request, defaults, targetCount, campaignName, scopeBuildArgs, routes = [], memory, scopeBrainPreflight) {
|
|
2450
2473
|
const buildRequest = {
|
|
2451
2474
|
name: campaignName,
|
|
@@ -2478,6 +2501,11 @@ function createBuildRequest(request, defaults, targetCount, campaignName, scopeB
|
|
|
2478
2501
|
};
|
|
2479
2502
|
const scoped = asRecord(scopeBuildArgs);
|
|
2480
2503
|
buildRequest.gtmCampaignId = request.gtmCampaignId ?? stringValue(scoped.gtm_campaign_id ?? scoped.gtmCampaignId);
|
|
2504
|
+
buildRequest.learningHoldout = normalizeCampaignBuilderLearningHoldout(
|
|
2505
|
+
request.learningHoldout ?? scoped.learning_holdout ?? scoped.learningHoldout,
|
|
2506
|
+
buildRequest.gtmCampaignId,
|
|
2507
|
+
campaignName
|
|
2508
|
+
);
|
|
2481
2509
|
if (typeof scoped.name === "string") buildRequest.name = scoped.name;
|
|
2482
2510
|
if (typeof scoped.prompt === "string") buildRequest.prompt = scoped.prompt;
|
|
2483
2511
|
if (typeof scoped.target_count === "number") buildRequest.targetCount = scoped.target_count;
|
|
@@ -2552,6 +2580,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
|
|
|
2552
2580
|
target_icp: targetIcp,
|
|
2553
2581
|
provider_chain: providerChain,
|
|
2554
2582
|
lead_source: providerChain[0] ?? "signaliz",
|
|
2583
|
+
learning_holdout: buildRequest.learningHoldout,
|
|
2555
2584
|
tool_sequence: [
|
|
2556
2585
|
{ tool: "gtm_brain_learning_cycle_plan", purpose: "Plan the Brain learning loop for this campaign.", arguments: learningArgs },
|
|
2557
2586
|
{ tool: "gtm_brain_seed_defaults", purpose: "Seed campaign defaults from workspace memory and safe aggregate patterns.", arguments: defaultsArgs },
|
|
@@ -3219,6 +3248,16 @@ function buildCampaignArgs(request) {
|
|
|
3219
3248
|
if (request.dedupKeys) args.dedup_keys = request.dedupKeys;
|
|
3220
3249
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
3221
3250
|
if (request.brainDefaults) args.brain_defaults = request.brainDefaults;
|
|
3251
|
+
if (request.learningHoldout) {
|
|
3252
|
+
args.learning_holdout = compact({
|
|
3253
|
+
enabled: request.learningHoldout.enabled,
|
|
3254
|
+
control_percentage: request.learningHoldout.controlPercentage,
|
|
3255
|
+
min_control_size: request.learningHoldout.minControlSize,
|
|
3256
|
+
experiment_key: request.learningHoldout.experimentKey,
|
|
3257
|
+
source_campaign_id: request.learningHoldout.sourceCampaignId,
|
|
3258
|
+
primary_metric: request.learningHoldout.primaryMetric
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
3222
3261
|
if (request.campaignStrategyContext) args.campaign_strategy_context = request.campaignStrategyContext;
|
|
3223
3262
|
if (request.deliveryRisk) args.delivery_risk = request.deliveryRisk;
|
|
3224
3263
|
if (request.icp) {
|
|
@@ -3315,6 +3354,7 @@ function buildExecutionPrepareArgs(request, dryRun, confirmSpend) {
|
|
|
3315
3354
|
delivery_risk: buildArgs2.delivery_risk,
|
|
3316
3355
|
dedup_keys: buildArgs2.dedup_keys,
|
|
3317
3356
|
policy: buildArgs2.policy,
|
|
3357
|
+
learning_holdout: buildArgs2.learning_holdout,
|
|
3318
3358
|
signals: buildArgs2.signals,
|
|
3319
3359
|
qualification: buildArgs2.qualification,
|
|
3320
3360
|
copy: buildArgs2.copy,
|
|
@@ -3349,10 +3389,143 @@ function mapBuildResult(data, dryRun) {
|
|
|
3349
3389
|
targetLimitReason: data.target_limit_reason,
|
|
3350
3390
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
3351
3391
|
brainContext: data.brain_context ?? {},
|
|
3392
|
+
learningHoldout: data.learning_holdout ?? {},
|
|
3352
3393
|
providerRoute: mapProviderRoute2(data)
|
|
3353
3394
|
};
|
|
3354
3395
|
}
|
|
3396
|
+
function mapAgentCampaignPhaseHealth(value) {
|
|
3397
|
+
if (!Array.isArray(value)) return [];
|
|
3398
|
+
return value.map((phase) => {
|
|
3399
|
+
const record = asRecord(phase);
|
|
3400
|
+
return {
|
|
3401
|
+
phase: stringValue(record.phase) ?? "unknown",
|
|
3402
|
+
status: stringValue(record.status) ?? "unknown",
|
|
3403
|
+
recordsProcessed: numberOrNull2(record.records_processed ?? record.recordsProcessed),
|
|
3404
|
+
recordsSucceeded: numberOrNull2(record.records_succeeded ?? record.recordsSucceeded),
|
|
3405
|
+
recordsFailed: numberOrNull2(record.records_failed ?? record.recordsFailed),
|
|
3406
|
+
heartbeatAgeSeconds: numberOrNull2(record.heartbeat_age_seconds ?? record.heartbeatAgeSeconds ?? record.phase_age_seconds)
|
|
3407
|
+
};
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
3410
|
+
function deriveCampaignBuildTerminalState(input) {
|
|
3411
|
+
const status = input.status.toLowerCase();
|
|
3412
|
+
const heartbeatAgeSeconds = input.phaseAgeSeconds;
|
|
3413
|
+
const stale = input.staleRunningPhase === true;
|
|
3414
|
+
const safeWatchAction = input.triggerRunId ? `signaliz ops run-status ${input.triggerRunId} --watch` : "signaliz campaign-agent status <campaign_build_id>";
|
|
3415
|
+
if (CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES.has(status)) {
|
|
3416
|
+
return {
|
|
3417
|
+
kind: "blocked",
|
|
3418
|
+
label: "Blocked terminal state",
|
|
3419
|
+
reviewable: true,
|
|
3420
|
+
terminal: true,
|
|
3421
|
+
stale: false,
|
|
3422
|
+
phase: input.phase,
|
|
3423
|
+
phaseStatus: input.phaseStatus,
|
|
3424
|
+
heartbeatAgeSeconds,
|
|
3425
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
3426
|
+
terminalReason: input.terminalReason,
|
|
3427
|
+
staleReason: null,
|
|
3428
|
+
safeNextAction: input.nextAction || "Review errors and rerun only after the blocker is resolved."
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
if (status === "pending_approval") {
|
|
3432
|
+
return {
|
|
3433
|
+
kind: "pending_approval",
|
|
3434
|
+
label: "Pending delivery approval",
|
|
3435
|
+
reviewable: true,
|
|
3436
|
+
terminal: false,
|
|
3437
|
+
stale: false,
|
|
3438
|
+
phase: input.phase,
|
|
3439
|
+
phaseStatus: input.phaseStatus,
|
|
3440
|
+
heartbeatAgeSeconds,
|
|
3441
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
3442
|
+
terminalReason: input.terminalReason,
|
|
3443
|
+
staleReason: null,
|
|
3444
|
+
safeNextAction: input.approvalAction || input.nextAction || "Review rows and approve delivery only after the approval packet passes."
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
if (CAMPAIGN_BUILDER_TERMINAL_STATUSES.has(status)) {
|
|
3448
|
+
return {
|
|
3449
|
+
kind: "terminal",
|
|
3450
|
+
label: "Terminal and reviewable",
|
|
3451
|
+
reviewable: true,
|
|
3452
|
+
terminal: true,
|
|
3453
|
+
stale: false,
|
|
3454
|
+
phase: input.phase,
|
|
3455
|
+
phaseStatus: input.phaseStatus,
|
|
3456
|
+
heartbeatAgeSeconds,
|
|
3457
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
3458
|
+
terminalReason: input.terminalReason,
|
|
3459
|
+
staleReason: null,
|
|
3460
|
+
safeNextAction: input.nextAction || "Review rows, artifacts, scorecard, and approval locks before delivery."
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
if (status === "running" || status === "queued" || status === "processing") {
|
|
3464
|
+
if (stale) {
|
|
3465
|
+
return {
|
|
3466
|
+
kind: "running_stale",
|
|
3467
|
+
label: "Running but stale",
|
|
3468
|
+
reviewable: false,
|
|
3469
|
+
terminal: false,
|
|
3470
|
+
stale: true,
|
|
3471
|
+
phase: input.phase,
|
|
3472
|
+
phaseStatus: input.phaseStatus,
|
|
3473
|
+
heartbeatAgeSeconds,
|
|
3474
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
3475
|
+
terminalReason: null,
|
|
3476
|
+
staleReason: input.staleReason || "The active phase heartbeat is stale.",
|
|
3477
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
return {
|
|
3481
|
+
kind: "running_healthy",
|
|
3482
|
+
label: "Running and healthy",
|
|
3483
|
+
reviewable: false,
|
|
3484
|
+
terminal: false,
|
|
3485
|
+
stale: false,
|
|
3486
|
+
phase: input.phase,
|
|
3487
|
+
phaseStatus: input.phaseStatus,
|
|
3488
|
+
heartbeatAgeSeconds,
|
|
3489
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
3490
|
+
terminalReason: null,
|
|
3491
|
+
staleReason: null,
|
|
3492
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
return {
|
|
3496
|
+
kind: "unknown",
|
|
3497
|
+
label: "Unknown build state",
|
|
3498
|
+
reviewable: false,
|
|
3499
|
+
terminal: false,
|
|
3500
|
+
stale,
|
|
3501
|
+
phase: input.phase,
|
|
3502
|
+
phaseStatus: input.phaseStatus,
|
|
3503
|
+
heartbeatAgeSeconds,
|
|
3504
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
3505
|
+
terminalReason: input.terminalReason,
|
|
3506
|
+
staleReason: input.staleReason,
|
|
3507
|
+
safeNextAction: input.nextAction || "Read campaign build status again before making a launch or delivery decision."
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3355
3510
|
function mapAgentCampaignBuildStatus(data) {
|
|
3511
|
+
const customerRowCounts = mapAgentCustomerRowCounts(data.customer_row_counts);
|
|
3512
|
+
const phaseHealth = mapAgentCampaignPhaseHealth(data.phases);
|
|
3513
|
+
const terminalReason = stringValue(data.terminal_reason) ?? customerRowCounts?.terminalReason ?? null;
|
|
3514
|
+
const staleReason = stringValue(data.stale_reason) ?? stringValue(asRecord(data.diagnostics).stale_reason) ?? null;
|
|
3515
|
+
const nextPollAfterSeconds = numberOrNull2(data.next_poll_after_seconds ?? data.nextPollAfterSeconds);
|
|
3516
|
+
const terminalState = deriveCampaignBuildTerminalState({
|
|
3517
|
+
status: stringValue(data.status) ?? "unknown",
|
|
3518
|
+
phase: stringValue(data.current_phase) ?? null,
|
|
3519
|
+
phaseStatus: stringValue(data.current_phase_status) ?? null,
|
|
3520
|
+
staleRunningPhase: data.stale_running_phase === true,
|
|
3521
|
+
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
3522
|
+
nextPollAfterSeconds,
|
|
3523
|
+
terminalReason,
|
|
3524
|
+
staleReason,
|
|
3525
|
+
nextAction: formatAgentNextAction(data.next_action),
|
|
3526
|
+
approvalAction: formatAgentNextAction(data.approval_action),
|
|
3527
|
+
triggerRunId: stringValue(data.trigger_run_id) ?? null
|
|
3528
|
+
});
|
|
3356
3529
|
return {
|
|
3357
3530
|
campaignBuildId: stringValue(data.campaign_build_id) ?? "",
|
|
3358
3531
|
campaignId: stringValue(data.campaign_id) ?? null,
|
|
@@ -3370,12 +3543,18 @@ function mapAgentCampaignBuildStatus(data) {
|
|
|
3370
3543
|
errors: diagnosticMessages2(data.errors),
|
|
3371
3544
|
artifactCount: numberOrUndefined2(data.artifact_count) ?? 0,
|
|
3372
3545
|
artifactDownloads: Array.isArray(data.artifact_downloads) ? data.artifact_downloads.map(mapAgentCampaignArtifactDownload) : [],
|
|
3373
|
-
customerRowCounts
|
|
3546
|
+
customerRowCounts,
|
|
3374
3547
|
phaseAttemptTotals: nullableRecord(data.phase_attempt_totals) ?? void 0,
|
|
3375
3548
|
providerRoute: mapProviderRoute2(data),
|
|
3376
3549
|
triggerRunId: stringValue(data.trigger_run_id) ?? null,
|
|
3377
3550
|
staleRunningPhase: data.stale_running_phase === true,
|
|
3378
3551
|
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
3552
|
+
nextPollAfterSeconds,
|
|
3553
|
+
terminalReason,
|
|
3554
|
+
staleReason,
|
|
3555
|
+
safeNextAction: terminalState.safeNextAction,
|
|
3556
|
+
terminalState,
|
|
3557
|
+
phaseHealth,
|
|
3379
3558
|
diagnostics: nullableRecord(data.diagnostics) ?? {},
|
|
3380
3559
|
nextAction: formatAgentNextAction(data.next_action),
|
|
3381
3560
|
approvalAction: formatAgentNextAction(data.approval_action),
|
|
@@ -3463,67 +3642,276 @@ function mapAgentCampaignArtifact(data) {
|
|
|
3463
3642
|
createdAt: stringValue(data.created_at) ?? ""
|
|
3464
3643
|
};
|
|
3465
3644
|
}
|
|
3645
|
+
function campaignReviewGate(id, label, status, detail) {
|
|
3646
|
+
return {
|
|
3647
|
+
id,
|
|
3648
|
+
label,
|
|
3649
|
+
status,
|
|
3650
|
+
passed: status !== "blocked",
|
|
3651
|
+
detail
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
function northStarMetric(id, label, status, detail, value, target) {
|
|
3655
|
+
return {
|
|
3656
|
+
id,
|
|
3657
|
+
label,
|
|
3658
|
+
status,
|
|
3659
|
+
detail,
|
|
3660
|
+
...value !== void 0 ? { value } : {},
|
|
3661
|
+
...target !== void 0 ? { target } : {}
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
function createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState) {
|
|
3665
|
+
if (!terminalState.reviewable) {
|
|
3666
|
+
const pendingMetric = northStarMetric(
|
|
3667
|
+
"terminal_state",
|
|
3668
|
+
"Terminal-state contract",
|
|
3669
|
+
terminalState.kind === "running_stale" ? "blocked" : "pending",
|
|
3670
|
+
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.`,
|
|
3671
|
+
terminalState.kind,
|
|
3672
|
+
"terminal or pending_approval"
|
|
3673
|
+
);
|
|
3674
|
+
return {
|
|
3675
|
+
version: "campaign-builder-north-star.v1",
|
|
3676
|
+
status: pendingMetric.status,
|
|
3677
|
+
score: null,
|
|
3678
|
+
moatState: "scaffolding_ready",
|
|
3679
|
+
metrics: [pendingMetric],
|
|
3680
|
+
blockers: pendingMetric.status === "blocked" ? [pendingMetric.detail] : [],
|
|
3681
|
+
warnings: pendingMetric.status === "pending" ? [pendingMetric.detail] : [],
|
|
3682
|
+
nextAction: terminalState.safeNextAction
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
const sampledRows = rows.rows.length;
|
|
3686
|
+
const rowData = rows.rows.map((row) => asRecord(row.data));
|
|
3687
|
+
const requestedTarget = status.customerRowCounts?.requestedTarget ?? status.recordsTotal ?? rows.count;
|
|
3688
|
+
const finalRows = status.customerRowCounts?.deliveredRows ?? status.customerRowCounts?.qaReadyRows ?? status.customerRowCounts?.copiedRows ?? status.customerRowCounts?.acceptedRows ?? rows.count;
|
|
3689
|
+
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
3690
|
+
const verifiedEmailRows = rowData.filter(campaignReviewDataHasVerifiedEmail).length;
|
|
3691
|
+
const uniqueEmails = new Set(rowData.map(campaignReviewDataEmail).filter(Boolean)).size;
|
|
3692
|
+
const uniqueDomains = new Set(rowData.map(campaignReviewDataDomain).filter(Boolean)).size;
|
|
3693
|
+
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
3694
|
+
const copyQaIssuesByRow = rowData.map(campaignReviewDataCopyQaIssues).filter((issues) => issues.length > 0);
|
|
3695
|
+
const copyQaIssueCounts = countCampaignReviewIssues(copyQaIssuesByRow);
|
|
3696
|
+
const signalBackedRows = rowData.filter(campaignReviewDataHasSignalBasis).length;
|
|
3697
|
+
const noSupportedSignalRows = rowData.filter(campaignReviewDataHasNoSupportedSignal).length;
|
|
3698
|
+
const unsupportedSignalClaims = rowData.filter(campaignReviewDataHasUnsupportedSignalClaim).length;
|
|
3699
|
+
const approvalLockedRows = rowData.filter(campaignReviewDataHasApprovalLock).length;
|
|
3700
|
+
const holdoutRows = rowData.filter(campaignReviewDataHasHoldoutAttribution).length;
|
|
3701
|
+
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
3702
|
+
const deliveredRows = status.customerRowCounts?.deliveredRows ?? 0;
|
|
3703
|
+
const feedbackReady = campaignReviewStatusHasFeedbackReadiness(status);
|
|
3704
|
+
const feedbackRequiredForApproval = terminalState.kind === "pending_approval";
|
|
3705
|
+
const moat = campaignReviewMoatState(status, holdoutRows);
|
|
3706
|
+
const metrics = [
|
|
3707
|
+
northStarMetric(
|
|
3708
|
+
"terminal_state",
|
|
3709
|
+
"Terminal-state contract",
|
|
3710
|
+
terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
3711
|
+
terminalState.label,
|
|
3712
|
+
terminalState.kind,
|
|
3713
|
+
"terminal or pending_approval"
|
|
3714
|
+
),
|
|
3715
|
+
northStarMetric(
|
|
3716
|
+
"target_fill",
|
|
3717
|
+
"Target fill",
|
|
3718
|
+
requestedTarget && finalRows < requestedTarget ? "review" : "pass",
|
|
3719
|
+
requestedTarget ? `${finalRows || 0}/${requestedTarget} final or reviewable row(s) are accounted for.` : "No requested target was reported; using sampled rows for review.",
|
|
3720
|
+
finalRows || sampledRows,
|
|
3721
|
+
requestedTarget || sampledRows
|
|
3722
|
+
),
|
|
3723
|
+
northStarMetric(
|
|
3724
|
+
"unique_emails",
|
|
3725
|
+
"Unique emails",
|
|
3726
|
+
sampledRows === 0 ? "blocked" : uniqueEmails === rowsWithEmail ? "pass" : "blocked",
|
|
3727
|
+
sampledRows === 0 ? "No sampled rows were available for email uniqueness review." : `${uniqueEmails}/${rowsWithEmail} sampled email(s) are unique.`,
|
|
3728
|
+
uniqueEmails,
|
|
3729
|
+
rowsWithEmail
|
|
3730
|
+
),
|
|
3731
|
+
northStarMetric(
|
|
3732
|
+
"unique_domains",
|
|
3733
|
+
"Unique domains",
|
|
3734
|
+
sampledRows === 0 ? "blocked" : uniqueDomains === sampledRows ? "pass" : "review",
|
|
3735
|
+
sampledRows === 0 ? "No sampled rows were available for domain uniqueness review." : `${uniqueDomains}/${sampledRows} sampled company domain(s) are unique.`,
|
|
3736
|
+
uniqueDomains,
|
|
3737
|
+
sampledRows
|
|
3738
|
+
),
|
|
3739
|
+
northStarMetric(
|
|
3740
|
+
"verified_email_rows",
|
|
3741
|
+
"Verified email rows",
|
|
3742
|
+
sampledRows === 0 ? "blocked" : verifiedEmailRows === sampledRows ? "pass" : rowsWithEmail === sampledRows ? "review" : "blocked",
|
|
3743
|
+
sampledRows === 0 ? "No sampled rows were available for email verification review." : `${verifiedEmailRows}/${sampledRows} sampled row(s) carry verified-email evidence.`,
|
|
3744
|
+
verifiedEmailRows,
|
|
3745
|
+
sampledRows
|
|
3746
|
+
),
|
|
3747
|
+
northStarMetric(
|
|
3748
|
+
"copy_completeness",
|
|
3749
|
+
"Copy completeness",
|
|
3750
|
+
sampledRows === 0 ? "blocked" : copyReadyRows === sampledRows ? "pass" : "blocked",
|
|
3751
|
+
sampledRows === 0 ? "No sampled rows were available for copy review." : `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.`,
|
|
3752
|
+
copyReadyRows,
|
|
3753
|
+
sampledRows
|
|
3754
|
+
),
|
|
3755
|
+
northStarMetric(
|
|
3756
|
+
"copy_quality",
|
|
3757
|
+
"Copy QA",
|
|
3758
|
+
copyQaIssuesByRow.length === 0 ? "pass" : "blocked",
|
|
3759
|
+
copyQaIssuesByRow.length === 0 ? "No sampled copy QA blockers were detected." : `${copyQaIssuesByRow.length}/${sampledRows} sampled row(s) have copy QA issue(s): ${formatCampaignReviewIssueCounts(copyQaIssueCounts)}.`,
|
|
3760
|
+
copyQaIssuesByRow.length,
|
|
3761
|
+
0
|
|
3762
|
+
),
|
|
3763
|
+
northStarMetric(
|
|
3764
|
+
"signal_grounding",
|
|
3765
|
+
"Signal grounding",
|
|
3766
|
+
unsupportedSignalClaims > 0 ? "blocked" : signalBackedRows > 0 ? "pass" : "review",
|
|
3767
|
+
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.",
|
|
3768
|
+
signalBackedRows,
|
|
3769
|
+
sampledRows
|
|
3770
|
+
),
|
|
3771
|
+
northStarMetric(
|
|
3772
|
+
"approval_locks",
|
|
3773
|
+
"Approval locks",
|
|
3774
|
+
approvalLockedRows > 0 || deliveredRows === 0 ? "pass" : "review",
|
|
3775
|
+
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.",
|
|
3776
|
+
approvalLockedRows,
|
|
3777
|
+
sampledRows
|
|
3778
|
+
),
|
|
3779
|
+
northStarMetric(
|
|
3780
|
+
"export_send_safety",
|
|
3781
|
+
"Export/send safety",
|
|
3782
|
+
deliveredRows > 0 && status.status !== "completed" ? "blocked" : "pass",
|
|
3783
|
+
deliveredRows > 0 ? `${deliveredRows} row(s) are delivered; status is ${status.status}.` : "No delivered rows are reported before approval.",
|
|
3784
|
+
deliveredRows,
|
|
3785
|
+
0
|
|
3786
|
+
),
|
|
3787
|
+
northStarMetric(
|
|
3788
|
+
"holdout_attribution",
|
|
3789
|
+
"Holdout attribution",
|
|
3790
|
+
holdoutRows > 0 ? "pass" : "review",
|
|
3791
|
+
holdoutRows > 0 ? `${holdoutRows}/${sampledRows} sampled row(s) preserve holdout attribution.` : "No holdout attribution was visible in the sampled rows.",
|
|
3792
|
+
holdoutRows,
|
|
3793
|
+
sampledRows
|
|
3794
|
+
),
|
|
3795
|
+
northStarMetric(
|
|
3796
|
+
"feedback_readiness",
|
|
3797
|
+
"Feedback readiness",
|
|
3798
|
+
feedbackReady ? "pass" : feedbackRequiredForApproval ? "blocked" : "review",
|
|
3799
|
+
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.",
|
|
3800
|
+
feedbackReady,
|
|
3801
|
+
true
|
|
3802
|
+
),
|
|
3803
|
+
northStarMetric(
|
|
3804
|
+
"causal_moat",
|
|
3805
|
+
"Causal moat",
|
|
3806
|
+
moat.state === "proved" ? "pass" : "review",
|
|
3807
|
+
moat.detail,
|
|
3808
|
+
moat.state,
|
|
3809
|
+
"proved"
|
|
3810
|
+
),
|
|
3811
|
+
northStarMetric(
|
|
3812
|
+
"artifact_readback",
|
|
3813
|
+
"Artifact readback",
|
|
3814
|
+
artifactCount > 0 ? "pass" : "review",
|
|
3815
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available for review.` : "No review artifact is available yet.",
|
|
3816
|
+
artifactCount,
|
|
3817
|
+
1
|
|
3818
|
+
)
|
|
3819
|
+
];
|
|
3820
|
+
const gradedMetrics = metrics.filter((metric) => metric.status !== "pending");
|
|
3821
|
+
const score = gradedMetrics.length === 0 ? null : Math.round(gradedMetrics.filter((metric) => metric.status === "pass").length / gradedMetrics.length * 100);
|
|
3822
|
+
const blockers = metrics.filter((metric) => metric.status === "blocked").map((metric) => metric.detail);
|
|
3823
|
+
const warnings = metrics.filter((metric) => metric.status === "review").map((metric) => metric.detail);
|
|
3824
|
+
const scorecardStatus = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "review" : "pass";
|
|
3825
|
+
return {
|
|
3826
|
+
version: "campaign-builder-north-star.v1",
|
|
3827
|
+
status: scorecardStatus,
|
|
3828
|
+
score,
|
|
3829
|
+
moatState: moat.state,
|
|
3830
|
+
metrics,
|
|
3831
|
+
blockers,
|
|
3832
|
+
warnings,
|
|
3833
|
+
nextAction: scorecardStatus === "blocked" ? "Fix blocked North Star gates before approval or delivery." : terminalState.safeNextAction
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3466
3836
|
function createCampaignBuilderBuildReview(status, rows, artifacts) {
|
|
3837
|
+
const terminalState = status.terminalState ?? deriveCampaignBuildTerminalState({
|
|
3838
|
+
status: status.status,
|
|
3839
|
+
phase: status.currentPhase,
|
|
3840
|
+
phaseStatus: status.currentPhaseStatus,
|
|
3841
|
+
staleRunningPhase: status.staleRunningPhase === true,
|
|
3842
|
+
phaseAgeSeconds: status.phaseAgeSeconds ?? null,
|
|
3843
|
+
nextPollAfterSeconds: status.nextPollAfterSeconds ?? null,
|
|
3844
|
+
terminalReason: status.terminalReason ?? status.customerRowCounts?.terminalReason ?? null,
|
|
3845
|
+
staleReason: status.staleReason ?? null,
|
|
3846
|
+
nextAction: status.nextAction,
|
|
3847
|
+
approvalAction: status.approvalAction,
|
|
3848
|
+
triggerRunId: status.triggerRunId ?? null
|
|
3849
|
+
});
|
|
3467
3850
|
const sampledRows = rows.rows.length;
|
|
3468
3851
|
const availableRows = rows.count || sampledRows;
|
|
3469
3852
|
const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
|
|
3470
3853
|
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
3471
3854
|
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
3472
3855
|
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
3856
|
+
const northStarScorecard = createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState);
|
|
3473
3857
|
const gates = [
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3858
|
+
campaignReviewGate(
|
|
3859
|
+
"build_reviewable",
|
|
3860
|
+
"Build reviewable",
|
|
3861
|
+
terminalState.kind === "running_healthy" ? "pending" : terminalState.kind === "running_stale" || terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
3862
|
+
terminalState.label
|
|
3863
|
+
),
|
|
3864
|
+
campaignReviewGate(
|
|
3865
|
+
"rows_available",
|
|
3866
|
+
"Rows available",
|
|
3867
|
+
sampledRows > 0 ? "pass" : terminalState.reviewable ? "blocked" : "pending",
|
|
3868
|
+
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."
|
|
3869
|
+
),
|
|
3870
|
+
campaignReviewGate(
|
|
3871
|
+
"qualified_sample",
|
|
3872
|
+
"Sample rows qualified",
|
|
3873
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : qualifiedRows === sampledRows ? "pass" : "blocked",
|
|
3874
|
+
sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
|
|
3875
|
+
),
|
|
3876
|
+
campaignReviewGate(
|
|
3877
|
+
"email_coverage",
|
|
3878
|
+
"Email coverage",
|
|
3879
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : rowsWithEmail === sampledRows ? "pass" : "blocked",
|
|
3880
|
+
sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
|
|
3881
|
+
),
|
|
3882
|
+
campaignReviewGate(
|
|
3883
|
+
"artifact_available",
|
|
3884
|
+
"Artifact available",
|
|
3885
|
+
artifactCount > 0 ? "pass" : terminalState.reviewable ? "review" : "pending",
|
|
3886
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
|
|
3887
|
+
),
|
|
3888
|
+
campaignReviewGate(
|
|
3889
|
+
"no_failed_rows",
|
|
3890
|
+
"No failed rows",
|
|
3891
|
+
status.recordsFailed === 0 ? "pass" : "blocked",
|
|
3892
|
+
status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
|
|
3893
|
+
)
|
|
3510
3894
|
];
|
|
3511
|
-
const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
|
|
3895
|
+
const blockedReasons = gates.filter((gate) => gate.status === "blocked" || !gate.passed).map((gate) => gate.detail);
|
|
3512
3896
|
const warnings = [
|
|
3513
3897
|
rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
|
|
3514
3898
|
sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
|
|
3515
3899
|
...status.warnings
|
|
3516
3900
|
].filter((item) => Boolean(item));
|
|
3517
|
-
const readyForDeliveryApproval = blockedReasons.length === 0;
|
|
3901
|
+
const readyForDeliveryApproval = terminalState.reviewable && blockedReasons.length === 0 && northStarScorecard.status !== "blocked";
|
|
3518
3902
|
return {
|
|
3519
3903
|
campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
|
|
3520
3904
|
status,
|
|
3521
3905
|
rows,
|
|
3522
3906
|
artifacts,
|
|
3523
3907
|
gates,
|
|
3908
|
+
northStarScorecard,
|
|
3524
3909
|
summary: {
|
|
3525
3910
|
status: status.status,
|
|
3526
3911
|
phase: status.currentPhase,
|
|
3912
|
+
terminalState: terminalState.kind,
|
|
3913
|
+
scorecardStatus: northStarScorecard.status,
|
|
3914
|
+
scorecardScore: northStarScorecard.score,
|
|
3527
3915
|
sampledRows,
|
|
3528
3916
|
availableRows,
|
|
3529
3917
|
qualifiedRows,
|
|
@@ -3577,6 +3965,184 @@ function campaignReviewRowHasCopy(row) {
|
|
|
3577
3965
|
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
3966
|
);
|
|
3579
3967
|
}
|
|
3968
|
+
function campaignReviewDataEmail(data) {
|
|
3969
|
+
return stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail) || stringValue(asRecord(data.contact).email);
|
|
3970
|
+
}
|
|
3971
|
+
function campaignReviewDataDomain(data) {
|
|
3972
|
+
const domain = stringValue(data.company_domain) || stringValue(data.companyDomain) || stringValue(asRecord(data.company).domain);
|
|
3973
|
+
return domain?.toLowerCase();
|
|
3974
|
+
}
|
|
3975
|
+
function campaignReviewDataHasVerifiedEmail(data) {
|
|
3976
|
+
const status = String(data.email_status ?? data.emailStatus ?? asRecord(data.contact).email_status ?? "").toLowerCase();
|
|
3977
|
+
return data.email_verified === true || data.emailVerified === true || asRecord(data.contact).email_verified === true || ["valid", "validated", "verified"].includes(status);
|
|
3978
|
+
}
|
|
3979
|
+
function campaignReviewDataCopyParts(data) {
|
|
3980
|
+
const copy = asRecord(data.copy);
|
|
3981
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
3982
|
+
return [
|
|
3983
|
+
stringValue(data.subject),
|
|
3984
|
+
stringValue(data.subject_line),
|
|
3985
|
+
stringValue(data.opener),
|
|
3986
|
+
stringValue(data.body),
|
|
3987
|
+
stringValue(data.cta),
|
|
3988
|
+
stringValue(copy.subject),
|
|
3989
|
+
stringValue(copy.opener),
|
|
3990
|
+
stringValue(copy.body),
|
|
3991
|
+
stringValue(copy.cta),
|
|
3992
|
+
stringValue(rawCopy.subject),
|
|
3993
|
+
stringValue(rawCopy.opener),
|
|
3994
|
+
stringValue(rawCopy.body),
|
|
3995
|
+
stringValue(rawCopy.cta)
|
|
3996
|
+
].filter((part) => Boolean(part));
|
|
3997
|
+
}
|
|
3998
|
+
function campaignReviewDataHasBadGreeting(data) {
|
|
3999
|
+
return campaignReviewDataCopyParts(data).some(
|
|
4000
|
+
(part) => /\bhi\s*(?:,|\{\{|\[)|\bhello\s*(?:,|\{\{|\[)/i.test(part) || /\b(first_name|firstname|name)\b/i.test(part)
|
|
4001
|
+
);
|
|
4002
|
+
}
|
|
4003
|
+
function campaignReviewDataHasBannedPhrase(data) {
|
|
4004
|
+
const text = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4005
|
+
return NORTH_STAR_BANNED_COPY_PHRASES.some((phrase) => text.includes(phrase));
|
|
4006
|
+
}
|
|
4007
|
+
function campaignReviewDataSubject(data) {
|
|
4008
|
+
const copy = asRecord(data.copy);
|
|
4009
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4010
|
+
return stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(copy.subject) || stringValue(rawCopy.subject);
|
|
4011
|
+
}
|
|
4012
|
+
function campaignReviewDataCta(data) {
|
|
4013
|
+
const copy = asRecord(data.copy);
|
|
4014
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4015
|
+
return stringValue(data.cta) || stringValue(copy.cta) || stringValue(rawCopy.cta);
|
|
4016
|
+
}
|
|
4017
|
+
function countCampaignReviewIssues(issueRows) {
|
|
4018
|
+
return issueRows.flat().reduce((acc, issue) => {
|
|
4019
|
+
acc[issue] = (acc[issue] || 0) + 1;
|
|
4020
|
+
return acc;
|
|
4021
|
+
}, {});
|
|
4022
|
+
}
|
|
4023
|
+
function formatCampaignReviewIssueCounts(counts) {
|
|
4024
|
+
const entries = Object.entries(counts);
|
|
4025
|
+
return entries.length > 0 ? entries.map(([issue, count]) => `${issue}=${count}`).join(", ") : "none";
|
|
4026
|
+
}
|
|
4027
|
+
function campaignReviewDataCopyQaIssues(data) {
|
|
4028
|
+
const issues = /* @__PURE__ */ new Set();
|
|
4029
|
+
const copyParts = campaignReviewDataCopyParts(data);
|
|
4030
|
+
if (copyParts.length === 0) return [];
|
|
4031
|
+
if (campaignReviewDataHasBadGreeting(data)) issues.add("bad_greeting");
|
|
4032
|
+
if (campaignReviewDataHasBannedPhrase(data)) issues.add("banned_or_vague_phrase");
|
|
4033
|
+
if (campaignReviewDataHasUnsupportedSignalClaim(data)) issues.add("unsupported_signal_claim");
|
|
4034
|
+
if (campaignReviewDataPersonalizationBasis(data).length === 0) issues.add("missing_personalization_basis");
|
|
4035
|
+
if (!campaignReviewDataCta(data)) issues.add("missing_cta");
|
|
4036
|
+
const subject = campaignReviewDataSubject(data);
|
|
4037
|
+
if (subject && subject.length > 70) issues.add("overlong_subject");
|
|
4038
|
+
return [...issues];
|
|
4039
|
+
}
|
|
4040
|
+
function campaignReviewDataPersonalizationBasis(data) {
|
|
4041
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4042
|
+
const copy = asRecord(data.copy);
|
|
4043
|
+
const metadata = asRecord(data.metadata);
|
|
4044
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4045
|
+
return [
|
|
4046
|
+
...arrayOfStrings(data.personalization_basis) ?? [],
|
|
4047
|
+
...arrayOfStrings(data.personalizationBasis) ?? [],
|
|
4048
|
+
...arrayOfStrings(copy.personalization_basis) ?? [],
|
|
4049
|
+
...arrayOfStrings(rawCopy.personalization_basis) ?? [],
|
|
4050
|
+
...arrayOfStrings(metadata.personalization_basis) ?? [],
|
|
4051
|
+
...arrayOfStrings(metadata.personalizationBasis) ?? [],
|
|
4052
|
+
...arrayOfStrings(signalEvidence.personalization_basis) ?? []
|
|
4053
|
+
];
|
|
4054
|
+
}
|
|
4055
|
+
function campaignReviewDataHasNoSupportedSignal(data) {
|
|
4056
|
+
const metadata = asRecord(data.metadata);
|
|
4057
|
+
const copy = asRecord(data.copy);
|
|
4058
|
+
const state = [
|
|
4059
|
+
data.copy_signal_state,
|
|
4060
|
+
data.signal_basis,
|
|
4061
|
+
data.signal_type,
|
|
4062
|
+
metadata.copy_signal_state,
|
|
4063
|
+
metadata.signal_basis,
|
|
4064
|
+
metadata.signal_type,
|
|
4065
|
+
copy.copy_signal_state
|
|
4066
|
+
].map((value) => stringValue(value)?.toLowerCase()).filter(Boolean);
|
|
4067
|
+
return data.no_supported_signal === true || metadata.no_supported_signal === true || state.includes("no_supported_signal");
|
|
4068
|
+
}
|
|
4069
|
+
function campaignReviewDataHasSignalBasis(data) {
|
|
4070
|
+
if (campaignReviewDataHasNoSupportedSignal(data)) return false;
|
|
4071
|
+
const metadata = asRecord(data.metadata);
|
|
4072
|
+
const signal = asRecord(data.signal);
|
|
4073
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4074
|
+
const basis = campaignReviewDataPersonalizationBasis(data);
|
|
4075
|
+
return Boolean(
|
|
4076
|
+
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))
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
function campaignReviewDataHasUnsupportedSignalClaim(data) {
|
|
4080
|
+
const copyText = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4081
|
+
const claimsSignal = /\b(hiring|funding|raised|launched|expanding|appointed|announced|recent news|new role|job posting)\b/.test(copyText);
|
|
4082
|
+
return claimsSignal && !campaignReviewDataHasSignalBasis(data);
|
|
4083
|
+
}
|
|
4084
|
+
function campaignReviewDataHasApprovalLock(data) {
|
|
4085
|
+
const metadata = asRecord(data.fit_metadata ?? data.metadata);
|
|
4086
|
+
const approval = String(data.approval_status ?? data.delivery_approval_status ?? metadata.approval_status ?? "").toLowerCase();
|
|
4087
|
+
const exportStatus = String(data.export_status ?? data.exportStatus ?? "").toLowerCase();
|
|
4088
|
+
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);
|
|
4089
|
+
}
|
|
4090
|
+
function campaignReviewDataHasHoldoutAttribution(data) {
|
|
4091
|
+
const fitMetadata = asRecord(data.fit_metadata ?? data.fitMetadata);
|
|
4092
|
+
const metadata = asRecord(data.metadata);
|
|
4093
|
+
const holdout = asRecord(
|
|
4094
|
+
fitMetadata.learning_holdout ?? fitMetadata.learningHoldout ?? metadata.learning_holdout ?? metadata.learningHoldout ?? data.learning_holdout ?? data.learningHoldout
|
|
4095
|
+
);
|
|
4096
|
+
const experimentKey = stringValue(holdout.experiment_key ?? holdout.experimentKey ?? data.experiment_key ?? data.experimentKey);
|
|
4097
|
+
const cohort = stringValue(holdout.cohort_role ?? holdout.cohortRole ?? data.cohort_role ?? data.learning_cohort ?? data.learningCohort);
|
|
4098
|
+
const brainApplied = holdout.brain_applied ?? holdout.brainApplied ?? data.brain_applied ?? data.signaliz_brain_applied;
|
|
4099
|
+
return Boolean(experimentKey && cohort && brainApplied !== void 0);
|
|
4100
|
+
}
|
|
4101
|
+
function campaignReviewStatusHasFeedbackReadiness(status) {
|
|
4102
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
4103
|
+
const feedbackReadiness = asRecord(diagnostics.feedback_readiness ?? diagnostics.feedbackReadiness);
|
|
4104
|
+
const feedbackSubstrate = asRecord(diagnostics.feedback_substrate ?? diagnostics.feedbackSubstrate);
|
|
4105
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
4106
|
+
return feedbackReadiness.ready === true || feedbackReadiness.webhook_configured === true || feedbackSubstrate.stage === "ready" || Number(evidenceCounts.feedback_events ?? 0) > 0;
|
|
4107
|
+
}
|
|
4108
|
+
function campaignReviewMoatState(status, holdoutRows) {
|
|
4109
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
4110
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
4111
|
+
const embeddedHoldout = asRecord(diagnostics.embedded_holdout ?? diagnostics.embeddedHoldout);
|
|
4112
|
+
const holdoutLift = asRecord(diagnostics.holdout_lift ?? diagnostics.holdoutLift ?? diagnostics.holdout_measurement ?? diagnostics.holdoutMeasurement);
|
|
4113
|
+
const controlSample = Math.max(
|
|
4114
|
+
Number(evidenceCounts.holdout_control_sample_size ?? 0),
|
|
4115
|
+
Number(evidenceCounts.embedded_holdout_control_sample ?? 0),
|
|
4116
|
+
Number(embeddedHoldout.control_sample_size ?? 0)
|
|
4117
|
+
);
|
|
4118
|
+
const learnedSample = Math.max(
|
|
4119
|
+
Number(evidenceCounts.holdout_learned_sample_size ?? 0),
|
|
4120
|
+
Number(evidenceCounts.embedded_holdout_learned_sample ?? 0),
|
|
4121
|
+
Number(embeddedHoldout.learned_sample_size ?? 0)
|
|
4122
|
+
);
|
|
4123
|
+
const feedbackEvents = Math.max(
|
|
4124
|
+
Number(evidenceCounts.feedback_events ?? 0),
|
|
4125
|
+
Number(evidenceCounts.campaign_build_feedback_events ?? 0),
|
|
4126
|
+
Number(evidenceCounts.delivery_holdout_sent_outcomes ?? 0)
|
|
4127
|
+
);
|
|
4128
|
+
const proved = diagnostics.closed_loop_proof_ready === true || holdoutLift.honest_measurement === true && holdoutLift.positive_lift === true;
|
|
4129
|
+
if (proved) {
|
|
4130
|
+
return {
|
|
4131
|
+
state: "proved",
|
|
4132
|
+
detail: "Real attributed feedback shows positive lift for explicit control and learned cohorts."
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
if (controlSample > 0 && learnedSample > 0 && feedbackEvents > 0) {
|
|
4136
|
+
return {
|
|
4137
|
+
state: "moved_needs_lift_volume",
|
|
4138
|
+
detail: `Control and learned cohorts have attributed feedback (${controlSample} control, ${learnedSample} learned, ${feedbackEvents} feedback event(s)); keep collecting lift volume before claiming proof.`
|
|
4139
|
+
};
|
|
4140
|
+
}
|
|
4141
|
+
return {
|
|
4142
|
+
state: "scaffolding_ready",
|
|
4143
|
+
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."
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
3580
4146
|
function createCampaignBuilderReadinessLane(route, plan) {
|
|
3581
4147
|
const builtIn = campaignBuilderBuiltInForRoute(route);
|
|
3582
4148
|
const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
|
|
@@ -3877,7 +4443,7 @@ function hashString(input) {
|
|
|
3877
4443
|
function errorMessage(error) {
|
|
3878
4444
|
return error instanceof Error ? error.message : String(error);
|
|
3879
4445
|
}
|
|
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;
|
|
4446
|
+
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
4447
|
var init_campaign_builder_agent = __esm({
|
|
3882
4448
|
"src/resources/campaign-builder-agent.ts"() {
|
|
3883
4449
|
"use strict";
|
|
@@ -4459,6 +5025,7 @@ var init_campaign_builder_agent = __esm({
|
|
|
4459
5025
|
},
|
|
4460
5026
|
brain_defaults: plan.buildRequest.brainDefaults,
|
|
4461
5027
|
brain_preflight: plan.buildRequest.brainPreflight,
|
|
5028
|
+
learning_holdout: plan.buildRequest.learningHoldout,
|
|
4462
5029
|
campaign_strategy_context: plan.buildRequest.campaignStrategyContext,
|
|
4463
5030
|
delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
4464
5031
|
delivery_risk: plan.buildRequest.deliveryRisk
|
|
@@ -4472,7 +5039,8 @@ var init_campaign_builder_agent = __esm({
|
|
|
4472
5039
|
estimated_credits: plan.estimatedCredits
|
|
4473
5040
|
},
|
|
4474
5041
|
brain_delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
4475
|
-
delivery_risk: plan.buildRequest.deliveryRisk
|
|
5042
|
+
delivery_risk: plan.buildRequest.deliveryRisk,
|
|
5043
|
+
learning_holdout: plan.buildRequest.learningHoldout
|
|
4476
5044
|
},
|
|
4477
5045
|
approval_required: plan.approvals.some((approval) => approval.blocking),
|
|
4478
5046
|
actor_type: "agent",
|
|
@@ -4788,6 +5356,17 @@ var init_campaign_builder_agent = __esm({
|
|
|
4788
5356
|
return this.client.mcp("tools/call", { name: tool, arguments: compact(args) });
|
|
4789
5357
|
}
|
|
4790
5358
|
};
|
|
5359
|
+
CAMPAIGN_BUILDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled", "cancelled"]);
|
|
5360
|
+
CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["failed", "canceled", "cancelled"]);
|
|
5361
|
+
NORTH_STAR_BANNED_COPY_PHRASES = [
|
|
5362
|
+
"i have been following",
|
|
5363
|
+
"i saw that",
|
|
5364
|
+
"would you be opposed",
|
|
5365
|
+
"unlock new opportunities",
|
|
5366
|
+
"significant success",
|
|
5367
|
+
"we understand",
|
|
5368
|
+
"hope this finds you well"
|
|
5369
|
+
];
|
|
4791
5370
|
}
|
|
4792
5371
|
});
|
|
4793
5372
|
|
|
@@ -7858,6 +8437,16 @@ var init_gtm_kernel = __esm({
|
|
|
7858
8437
|
min_privacy_k: input.minPrivacyK
|
|
7859
8438
|
});
|
|
7860
8439
|
}
|
|
8440
|
+
/** Queue a dry-run-first bridge from imported historical feedback to attribution-only Campaign Build surrogates. */
|
|
8441
|
+
async runHistoricalCampaignBuildBridge(input = {}) {
|
|
8442
|
+
return this.callMcp("gtm_historical_campaign_build_bridge_run", {
|
|
8443
|
+
dry_run: input.dryRun,
|
|
8444
|
+
write_approved: input.writeApproved,
|
|
8445
|
+
max_events: input.maxEvents,
|
|
8446
|
+
max_campaigns: input.maxCampaigns,
|
|
8447
|
+
min_feedback_events: input.minFeedbackEvents
|
|
8448
|
+
});
|
|
8449
|
+
}
|
|
7861
8450
|
/** Prepare a provider-agnostic feedback webhook URL for Smartlead, HeyReach, Airbyte, or custom sender events. */
|
|
7862
8451
|
async prepareFeedbackWebhook(input) {
|
|
7863
8452
|
return this.callMcp("gtm_feedback_webhook_prepare", {
|
|
@@ -9954,6 +10543,13 @@ function printCampaignAgentBuildStatus(status) {
|
|
|
9954
10543
|
if (status.campaignId) console.log(`Campaign ID: ${status.campaignId}`);
|
|
9955
10544
|
console.log(`Status: ${status.status}`);
|
|
9956
10545
|
console.log(`Phase: ${status.currentPhase || "N/A"}`);
|
|
10546
|
+
const terminalState = asRecord4(status.terminalState);
|
|
10547
|
+
if (terminalState.kind) {
|
|
10548
|
+
console.log(`Terminal state: ${terminalState.kind} (${terminalState.label || "no label"})`);
|
|
10549
|
+
if (terminalState.heartbeatAgeSeconds !== void 0 && terminalState.heartbeatAgeSeconds !== null) {
|
|
10550
|
+
console.log(`Heartbeat age: ${terminalState.heartbeatAgeSeconds}s`);
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
9957
10553
|
console.log(`Rows: ${status.recordsSucceeded}/${status.recordsTotal} succeeded, ${status.recordsFailed} failed`);
|
|
9958
10554
|
printCampaignCustomerRowCounts(status);
|
|
9959
10555
|
console.log(`Artifacts: ${status.artifactCount}`);
|
|
@@ -9975,6 +10571,8 @@ function printCampaignAgentBuildStatus(status) {
|
|
|
9975
10571
|
console.log(`Next: ${status.nextAction}`);
|
|
9976
10572
|
} else if (status.status === "pending_approval") {
|
|
9977
10573
|
console.log(`Next: signaliz campaign-agent approve ${status.campaignBuildId} --destination-type webhook`);
|
|
10574
|
+
} else if (terminalState.safeNextAction) {
|
|
10575
|
+
console.log(`Next: ${terminalState.safeNextAction}`);
|
|
9978
10576
|
}
|
|
9979
10577
|
}
|
|
9980
10578
|
function printCampaignCustomerRowCounts(status) {
|
|
@@ -9998,17 +10596,20 @@ function printCampaignAgentBuildReview(review) {
|
|
|
9998
10596
|
console.log(`Campaign: ${review.status?.name || review.campaignBuildId}`);
|
|
9999
10597
|
console.log(`Status: ${summary.status || review.status?.status}`);
|
|
10000
10598
|
console.log(`Phase: ${summary.phase || review.status?.currentPhase || "N/A"}`);
|
|
10599
|
+
console.log(`Terminal state: ${summary.terminalState || review.status?.terminalState?.kind || "unknown"}`);
|
|
10001
10600
|
console.log(`Rows: ${summary.sampledRows || 0} sampled, ${summary.availableRows || 0} available`);
|
|
10002
10601
|
console.log(`Qualified: ${summary.qualifiedRows || 0}`);
|
|
10003
10602
|
console.log(`Emails: ${summary.rowsWithEmail || 0}`);
|
|
10004
10603
|
console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
|
|
10005
10604
|
console.log(`Artifacts: ${summary.artifactCount || 0}`);
|
|
10605
|
+
printCampaignAgentNorthStarScorecard(review.northStarScorecard);
|
|
10006
10606
|
if (review.campaignBuildId) printCampaignArtifactDownload(review.status, review.campaignBuildId);
|
|
10007
10607
|
console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
|
|
10008
10608
|
if (Array.isArray(review.gates) && review.gates.length > 0) {
|
|
10009
10609
|
console.log("\nGates:");
|
|
10010
10610
|
for (const gate of review.gates) {
|
|
10011
|
-
|
|
10611
|
+
const status = String(gate.status || (gate.passed ? "pass" : "blocked")).toUpperCase();
|
|
10612
|
+
console.log(`- ${status} ${gate.label}: ${gate.detail}`);
|
|
10012
10613
|
}
|
|
10013
10614
|
}
|
|
10014
10615
|
const warnings = Array.isArray(summary.warnings) ? summary.warnings : [];
|
|
@@ -10022,6 +10623,21 @@ function printCampaignAgentBuildReview(review) {
|
|
|
10022
10623
|
for (const action of nextActions) console.log(`- ${action}`);
|
|
10023
10624
|
}
|
|
10024
10625
|
}
|
|
10626
|
+
function printCampaignAgentNorthStarScorecard(scorecard) {
|
|
10627
|
+
const card = asRecord4(scorecard);
|
|
10628
|
+
if (!card.version) return;
|
|
10629
|
+
const score = card.score === null || card.score === void 0 ? "pending" : `${card.score}%`;
|
|
10630
|
+
console.log(`North Star: ${String(card.status || "unknown")} (${score})`);
|
|
10631
|
+
if (card.moatState) console.log(`Moat state: ${card.moatState}`);
|
|
10632
|
+
const metrics = Array.isArray(card.metrics) ? card.metrics : [];
|
|
10633
|
+
if (metrics.length > 0) {
|
|
10634
|
+
console.log("\nNorth Star gates:");
|
|
10635
|
+
for (const metric of metrics.slice(0, 12)) {
|
|
10636
|
+
const item = asRecord4(metric);
|
|
10637
|
+
console.log(`- ${String(item.status || "review").toUpperCase()} ${item.label || item.id}: ${item.detail || ""}`);
|
|
10638
|
+
}
|
|
10639
|
+
}
|
|
10640
|
+
}
|
|
10025
10641
|
function formatCampaignAgentRows(rows) {
|
|
10026
10642
|
return rows.map((row) => {
|
|
10027
10643
|
const data = asRecord4(row.data);
|