@canonry/canonry 7.13.0 → 7.15.0
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/assets/agent-workspace/skills/canonry/references/canonry-cli.md +19 -9
- package/assets/assets/AuditHistoryPanel-Cl9tUvSZ.js +1 -0
- package/assets/assets/{BacklinksPage-BQxB_eFt.js → BacklinksPage-CTeTJ9dh.js} +1 -1
- package/assets/assets/{HistoryPage-DgGUM2tr.js → HistoryPage-DpOoXr2P.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-Cl91vUu5.js → MeasurementPropertyPage-DxXsgM25.js} +1 -1
- package/assets/assets/ProjectPage-By9arjLJ.js +8 -0
- package/assets/assets/{RunsPage-BtjVuurK.js → RunsPage-CfDi7I1z.js} +1 -1
- package/assets/assets/SettingsPage-BHDb1apK.js +1 -0
- package/assets/assets/{SiteHealthSection-DMHWsfM4.js → SiteHealthSection-DQZyy4P_.js} +3 -3
- package/assets/assets/{TrafficPage-Bf0NkbIR.js → TrafficPage-DX_vN2RL.js} +2 -2
- package/assets/assets/{TrafficSourceDetailPage-BtwOdTAO.js → TrafficSourceDetailPage-Cg92zrtZ.js} +1 -1
- package/assets/assets/{extract-error-message-Cyc59Xi7.js → extract-error-message-BKxVCbov.js} +1 -1
- package/assets/assets/index-DoQJETc6.js +86 -0
- package/assets/assets/{index-D4QB1Igv.css → index-aBS1f2Bj.css} +1 -1
- package/assets/assets/{react-sigma_core.esm.min-CFB4bYg_.js → react-sigma_core.esm.min-BhqZcSgh.js} +1 -1
- package/assets/assets/{v2-overview-adapter-CLF10Pij.js → v2-overview-adapter-DZ5YsE_G.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-GQMSJEMZ.js → chunk-7HJYGDRW.js} +384 -57
- package/dist/{chunk-AU7OUDWR.js → chunk-KOBK4I5N.js} +5677 -5412
- package/dist/{chunk-BTHEFJCI.js → chunk-LC7GMHOL.js} +6 -6
- package/dist/{chunk-GBH7N3VZ.js → chunk-N2MRW2DH.js} +93 -31
- package/dist/{chunk-7I64KEGM.js → chunk-SG6T3DEP.js} +2 -2
- package/dist/{chunk-SM6ZZAPK.js → chunk-SGQBPTWQ.js} +2 -2
- package/dist/{chunk-EPSHIH63.js → chunk-YFCKHEKM.js} +3 -3
- package/dist/cli.js +1339 -1240
- package/dist/{demo-server-WJUA4EIG.js → demo-server-YWGUGZMI.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-DJRH4Z3D.js → intelligence-service-7RKUUR7O.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +11 -11
- package/assets/assets/AuditHistoryPanel-BYxGn9mS.js +0 -1
- package/assets/assets/ProjectPage-B_S900QN.js +0 -8
- package/assets/assets/SettingsPage-p0x0ZwGY.js +0 -1
- package/assets/assets/index-DR_kNdE4.js +0 -86
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
AdsOperationStepStates,
|
|
31
31
|
AdsReconcileStrategies,
|
|
32
32
|
AdsReviewStatuses,
|
|
33
|
+
AiEngineLeadRateUnavailableReasons,
|
|
33
34
|
AiReferralTrafficClasses,
|
|
34
35
|
AppError,
|
|
35
36
|
BacklinkSources,
|
|
@@ -223,7 +224,9 @@ import {
|
|
|
223
224
|
agentProvidersResponseDtoSchema,
|
|
224
225
|
aggregateHarvestedQueries,
|
|
225
226
|
aggregateSentiment,
|
|
227
|
+
aiEngineForReferralSource,
|
|
226
228
|
aiReferralClassCounts,
|
|
229
|
+
aiReferralEngineLabel,
|
|
227
230
|
alreadyExists,
|
|
228
231
|
answerProseForMentions,
|
|
229
232
|
apiKeyDtoSchema,
|
|
@@ -465,6 +468,7 @@ import {
|
|
|
465
468
|
internalError,
|
|
466
469
|
intersectScopes,
|
|
467
470
|
isBrowserProvider,
|
|
471
|
+
isGa4AiAssistantChannel,
|
|
468
472
|
isLocationRedirectStatus,
|
|
469
473
|
isNegativeReviewRating,
|
|
470
474
|
isProviderAccountFailure,
|
|
@@ -685,6 +689,7 @@ import {
|
|
|
685
689
|
resultsExportDtoSchema,
|
|
686
690
|
retryAfterDelayMs,
|
|
687
691
|
roundRatio,
|
|
692
|
+
runAdmissionDtoSchema,
|
|
688
693
|
runCompletenessDtoSchema,
|
|
689
694
|
runDetailDtoSchema,
|
|
690
695
|
runDtoSchema,
|
|
@@ -820,7 +825,7 @@ import {
|
|
|
820
825
|
wordpressSchemaDeployResultDtoSchema,
|
|
821
826
|
wordpressSchemaStatusResultDtoSchema,
|
|
822
827
|
wordpressStatusDtoSchema
|
|
823
|
-
} from "./chunk-
|
|
828
|
+
} from "./chunk-KOBK4I5N.js";
|
|
824
829
|
|
|
825
830
|
// src/intelligence-service.ts
|
|
826
831
|
import { eq as eq87, desc as desc39, asc as asc17, and as and73, ne as ne16, or as or21, inArray as inArray35, gte as gte23, lte as lte20, isNull as isNull16, sql as sql32, exists as exists2 } from "drizzle-orm";
|
|
@@ -1274,6 +1279,12 @@ var runs = sqliteTable("runs", {
|
|
|
1274
1279
|
* lose them; folded into `error` and cleared when the run finalizes.
|
|
1275
1280
|
*/
|
|
1276
1281
|
pendingProviderErrors: text("pending_provider_errors", { mode: "json" }).$type(),
|
|
1282
|
+
/**
|
|
1283
|
+
* Providers this run does not call because each keeps failing on its
|
|
1284
|
+
* account, with the failure streak that decided it, frozen at queue time
|
|
1285
|
+
* (`providerAccountAdmission`). Null when it skips none.
|
|
1286
|
+
*/
|
|
1287
|
+
skippedProviders: text("skipped_providers", { mode: "json" }).$type(),
|
|
1277
1288
|
createdAt: text("created_at").notNull()
|
|
1278
1289
|
}, (table) => [
|
|
1279
1290
|
index("idx_runs_project").on(table.projectId),
|
|
@@ -8322,6 +8333,16 @@ var MIGRATION_VERSIONS = [
|
|
|
8322
8333
|
`ALTER TABLE dashboard_sessions ADD COLUMN api_key_hash TEXT NOT NULL DEFAULT ''`,
|
|
8323
8334
|
`ALTER TABLE dashboard_sessions ADD COLUMN password_fingerprint TEXT`
|
|
8324
8335
|
]
|
|
8336
|
+
},
|
|
8337
|
+
{
|
|
8338
|
+
// Providers a run does not call because each keeps failing on its
|
|
8339
|
+
// account, frozen at queue time. Nullable with no default: every existing
|
|
8340
|
+
// run, and every run an older writer queues, skips none.
|
|
8341
|
+
version: 175,
|
|
8342
|
+
name: "runs-skipped-providers",
|
|
8343
|
+
statements: [
|
|
8344
|
+
`ALTER TABLE runs ADD COLUMN skipped_providers TEXT`
|
|
8345
|
+
]
|
|
8325
8346
|
}
|
|
8326
8347
|
];
|
|
8327
8348
|
var AERO_LEDGER_REBUILDS = [
|
|
@@ -17405,7 +17426,11 @@ function sliceFor(plan, params) {
|
|
|
17405
17426
|
}
|
|
17406
17427
|
function assertMeasurementRunStampable(db, params) {
|
|
17407
17428
|
const stamp = measurementStamp(db, params);
|
|
17408
|
-
resolveQueueDispatch(db, params, stamp);
|
|
17429
|
+
const dispatch = resolveQueueDispatch(db, params, stamp);
|
|
17430
|
+
if (params.dispatchMode === ProviderDispatchModes.batch) {
|
|
17431
|
+
const admission = queueProviderAdmission(db, params, stamp, params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
17432
|
+
dispatchAfterProviderAdmission(params, dispatch, admission);
|
|
17433
|
+
}
|
|
17409
17434
|
}
|
|
17410
17435
|
function resolveQueueDispatch(tx, params, stamp) {
|
|
17411
17436
|
const trigger = stamp?.scope ? RunTriggers.probe : params.trigger ?? RunTriggers.manual;
|
|
@@ -17427,6 +17452,30 @@ function resolveQueueDispatch(tx, params, stamp) {
|
|
|
17427
17452
|
}
|
|
17428
17453
|
return resolution;
|
|
17429
17454
|
}
|
|
17455
|
+
function dispatchAfterProviderAdmission(params, dispatch, admission) {
|
|
17456
|
+
if (admission.refused) return dispatch;
|
|
17457
|
+
const skippedProviders = admission.skipped;
|
|
17458
|
+
const modes = Object.fromEntries(Object.entries(dispatch.modes).filter(([provider]) => !(provider in skippedProviders)));
|
|
17459
|
+
if (params.dispatchMode === ProviderDispatchModes.batch && Object.keys(modes).length === 0) {
|
|
17460
|
+
throw validationError(
|
|
17461
|
+
`${batchDispatchRefusalMessage(dispatch.ineligible)} Held back after repeated account failures: ${Object.keys(skippedProviders).join(", ")}. Pass force to call those providers now.`,
|
|
17462
|
+
{ ineligible: dispatch.ineligible, skippedProviders }
|
|
17463
|
+
);
|
|
17464
|
+
}
|
|
17465
|
+
return { ...dispatch, modes };
|
|
17466
|
+
}
|
|
17467
|
+
function queueProviderAdmission(db, params, stamp, now) {
|
|
17468
|
+
return (params.kind ?? RunKinds["answer-visibility"]) === RunKinds["answer-visibility"] ? providerAccountAdmission(db, {
|
|
17469
|
+
projectId: params.projectId,
|
|
17470
|
+
trigger: stamp?.scope ? RunTriggers.probe : params.trigger ?? RunTriggers.manual,
|
|
17471
|
+
force: params.force ?? false,
|
|
17472
|
+
now,
|
|
17473
|
+
providers: () => providersARunWouldCall(
|
|
17474
|
+
stamp?.identity.providers ?? providerRoster(db, params),
|
|
17475
|
+
params.runnableProviders
|
|
17476
|
+
)
|
|
17477
|
+
}) : { skipped: {} };
|
|
17478
|
+
}
|
|
17430
17479
|
function providersARunWouldCall(roster, runnable) {
|
|
17431
17480
|
const listed = normalizeProviders(roster);
|
|
17432
17481
|
if (runnable == null) return listed;
|
|
@@ -17447,63 +17496,124 @@ function providerSettingsChangeCouldFix(action, diff) {
|
|
|
17447
17496
|
return false;
|
|
17448
17497
|
}
|
|
17449
17498
|
}
|
|
17450
|
-
function
|
|
17451
|
-
|
|
17499
|
+
function heldProviderAccounts(db, params) {
|
|
17500
|
+
const held = /* @__PURE__ */ new Map();
|
|
17452
17501
|
const recent = db.select({
|
|
17453
17502
|
id: runs.id,
|
|
17454
17503
|
status: runs.status,
|
|
17455
17504
|
error: runs.error,
|
|
17456
17505
|
createdAt: runs.createdAt,
|
|
17457
|
-
finishedAt: runs.finishedAt
|
|
17506
|
+
finishedAt: runs.finishedAt,
|
|
17507
|
+
skippedProviders: runs.skippedProviders
|
|
17458
17508
|
}).from(runs).where(and11(
|
|
17459
17509
|
eq17(runs.projectId, params.projectId),
|
|
17460
17510
|
eq17(runs.kind, RunKinds["answer-visibility"]),
|
|
17461
17511
|
inArray6(runs.status, [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed, RunStatuses.cancelled])
|
|
17462
17512
|
)).orderBy(desc7(runs.createdAt), desc7(runs.id)).limit(PROVIDER_ACCOUNT_LOOKBACK).all().map((run) => ({ ...run, providers: parseRunError(run.error)?.providers ?? {} }));
|
|
17463
|
-
const
|
|
17464
|
-
|
|
17513
|
+
const entries = recent.flatMap((run) => Object.values(run.providers));
|
|
17514
|
+
const withAccountFailure = recent.filter((run) => Object.values(run.providers).some((entry) => !entry.skipped && isProviderAccountFailure(entry.code)));
|
|
17515
|
+
if (withAccountFailure.length < PROVIDER_ACCOUNT_FAILURE_STREAK && !entries.some((entry) => entry.skipped)) return held;
|
|
17465
17516
|
const calling = normalizeProviders(params.providers());
|
|
17466
|
-
if (calling.length === 0) return
|
|
17517
|
+
if (calling.length === 0) return held;
|
|
17467
17518
|
const answered2 = (runId, provider) => db.select({ id: querySnapshots.id }).from(querySnapshots).where(and11(eq17(querySnapshots.runId, runId), eq17(querySnapshots.provider, provider))).limit(1).get() !== void 0;
|
|
17468
|
-
const
|
|
17519
|
+
const finished = (run) => Date.parse(run.finishedAt ?? run.createdAt);
|
|
17520
|
+
const now = Date.parse(params.now);
|
|
17469
17521
|
for (const provider of calling) {
|
|
17470
17522
|
let count4 = 0;
|
|
17471
|
-
let
|
|
17523
|
+
let newest = null;
|
|
17472
17524
|
let oldest = null;
|
|
17473
17525
|
let code = null;
|
|
17526
|
+
let vouched = null;
|
|
17527
|
+
let ended = false;
|
|
17474
17528
|
for (const run of recent) {
|
|
17475
17529
|
const entry = run.providers[provider];
|
|
17476
17530
|
if (!entry) {
|
|
17477
|
-
if (run.status !== RunStatuses.failed && answered2(run.id, provider))
|
|
17531
|
+
if (run.status !== RunStatuses.failed && answered2(run.id, provider)) {
|
|
17532
|
+
ended = true;
|
|
17533
|
+
break;
|
|
17534
|
+
}
|
|
17535
|
+
continue;
|
|
17536
|
+
}
|
|
17537
|
+
if (entry.skipped) {
|
|
17538
|
+
vouched = run.skippedProviders?.[provider] ?? null;
|
|
17539
|
+
if (vouched) break;
|
|
17478
17540
|
continue;
|
|
17479
17541
|
}
|
|
17480
|
-
if (!isProviderAccountFailure(entry.code))
|
|
17481
|
-
|
|
17542
|
+
if (!isProviderAccountFailure(entry.code)) {
|
|
17543
|
+
ended = true;
|
|
17544
|
+
break;
|
|
17545
|
+
}
|
|
17546
|
+
newest ??= run;
|
|
17482
17547
|
code ??= entry.code;
|
|
17483
17548
|
oldest = run;
|
|
17484
17549
|
count4 += 1;
|
|
17485
17550
|
if (count4 === PROVIDER_ACCOUNT_FAILURE_STREAK) break;
|
|
17486
17551
|
}
|
|
17487
|
-
if (
|
|
17488
|
-
|
|
17552
|
+
if (ended) continue;
|
|
17553
|
+
if (count4 < PROVIDER_ACCOUNT_FAILURE_STREAK && !vouched) continue;
|
|
17554
|
+
const retryAfterMs = newest ? finished(newest) + PROVIDER_ACCOUNT_RETRY_HOURS * 36e5 : Date.parse(vouched.retryAfter);
|
|
17555
|
+
if (!(now < retryAfterMs)) continue;
|
|
17556
|
+
held.set(provider, {
|
|
17557
|
+
code: code ?? vouched.code,
|
|
17558
|
+
consecutiveRuns: PROVIDER_ACCOUNT_FAILURE_STREAK,
|
|
17559
|
+
// A vouched streak began before the runs read here.
|
|
17560
|
+
since: vouched ? vouched.since : oldest.createdAt,
|
|
17561
|
+
latestRunId: newest?.id ?? vouched.latestRunId,
|
|
17562
|
+
retryAfter: new Date(retryAfterMs).toISOString()
|
|
17563
|
+
});
|
|
17564
|
+
}
|
|
17565
|
+
if (held.size === 0) return held;
|
|
17566
|
+
const since = [...held.values()].map((streak) => streak.since).sort()[0];
|
|
17567
|
+
const settingsChanges = db.select({ entityId: auditLog.entityId, action: auditLog.action, diff: auditLog.diff, createdAt: auditLog.createdAt }).from(auditLog).where(and11(eq17(auditLog.entityType, "provider"), inArray6(auditLog.entityId, [...held.keys()]), gt3(auditLog.createdAt, since))).all();
|
|
17568
|
+
for (const change of settingsChanges) {
|
|
17569
|
+
const streak = held.get(change.entityId ?? "");
|
|
17570
|
+
if (streak && change.createdAt > streak.since && providerSettingsChangeCouldFix(change.action, change.diff)) {
|
|
17571
|
+
held.delete(change.entityId);
|
|
17572
|
+
}
|
|
17489
17573
|
}
|
|
17490
|
-
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
const
|
|
17495
|
-
const settingsChanges = db.select({ entityId: auditLog.entityId, action: auditLog.action, diff: auditLog.diff, createdAt: auditLog.createdAt }).from(auditLog).where(and11(eq17(auditLog.entityType, "provider"), inArray6(auditLog.entityId, calling), gt3(auditLog.createdAt, since))).all();
|
|
17496
|
-
const fixedSince = settingsChanges.some((change) => {
|
|
17497
|
-
const streak = streaks.get(change.entityId ?? "");
|
|
17498
|
-
return streak !== void 0 && change.createdAt > streak.oldest.createdAt && providerSettingsChangeCouldFix(change.action, change.diff);
|
|
17499
|
-
});
|
|
17500
|
-
if (fixedSince) return null;
|
|
17574
|
+
return held;
|
|
17575
|
+
}
|
|
17576
|
+
function providerAccountFailures(held) {
|
|
17577
|
+
const streaks = [...held.values()];
|
|
17578
|
+
const newest = [...streaks].sort((a, b) => b.retryAfter.localeCompare(a.retryAfter))[0];
|
|
17501
17579
|
return {
|
|
17502
17580
|
consecutiveRuns: PROVIDER_ACCOUNT_FAILURE_STREAK,
|
|
17503
|
-
latestRunId: newest.
|
|
17504
|
-
since,
|
|
17505
|
-
|
|
17506
|
-
|
|
17581
|
+
latestRunId: newest.latestRunId,
|
|
17582
|
+
since: streaks.map((streak) => streak.since).sort()[0],
|
|
17583
|
+
// The first provider called again lets a run through.
|
|
17584
|
+
retryAfter: streaks.map((streak) => streak.retryAfter).sort()[0],
|
|
17585
|
+
providers: Object.fromEntries([...held].map(([provider, streak]) => [provider, streak.code]))
|
|
17586
|
+
};
|
|
17587
|
+
}
|
|
17588
|
+
function providerAccountAdmission(db, params) {
|
|
17589
|
+
if (params.force || params.trigger === RunTriggers.probe) return { skipped: {} };
|
|
17590
|
+
let calling = null;
|
|
17591
|
+
const providers = () => calling ??= normalizeProviders(params.providers());
|
|
17592
|
+
const held = heldProviderAccounts(db, { projectId: params.projectId, now: params.now, providers });
|
|
17593
|
+
if (held.size === 0) return { skipped: {} };
|
|
17594
|
+
if (providers().every((provider) => held.has(provider))) return { refused: providerAccountFailures(held) };
|
|
17595
|
+
return { skipped: Object.fromEntries(held) };
|
|
17596
|
+
}
|
|
17597
|
+
function sweepProviders(db, projectId, runnable, selection) {
|
|
17598
|
+
const planProviders = activeRevisionProviders(db, projectId);
|
|
17599
|
+
if (planProviders.length > 0) return providersARunWouldCall(planProviders, runnable);
|
|
17600
|
+
const schedule = db.select({ providers: schedules.providers, enabled: schedules.enabled }).from(schedules).where(and11(eq17(schedules.projectId, projectId), eq17(schedules.kind, RunKinds["answer-visibility"]))).get();
|
|
17601
|
+
const project = db.select({ providers: projects.providers }).from(projects).where(eq17(projects.id, projectId)).get();
|
|
17602
|
+
return providersARunWouldCall(resolveRunProviderSelection({
|
|
17603
|
+
requestedProviders: selection === RunTriggers.scheduled && schedule?.enabled ? schedule.providers : null,
|
|
17604
|
+
projectProviders: project?.providers ?? [],
|
|
17605
|
+
runnableProviders: runnable
|
|
17606
|
+
}), runnable);
|
|
17607
|
+
}
|
|
17608
|
+
function runAdmissionState(db, params) {
|
|
17609
|
+
let calling = null;
|
|
17610
|
+
const providers = () => calling ??= sweepProviders(db, params.projectId, params.runnableProviders, params.selection ?? RunTriggers.scheduled);
|
|
17611
|
+
const held = heldProviderAccounts(db, { projectId: params.projectId, now: params.now, providers });
|
|
17612
|
+
const refused = held.size > 0 && providers().every((provider) => held.has(provider));
|
|
17613
|
+
return {
|
|
17614
|
+
refused,
|
|
17615
|
+
retryAfter: refused ? providerAccountFailures(held).retryAfter : null,
|
|
17616
|
+
providers: Object.fromEntries(held)
|
|
17507
17617
|
};
|
|
17508
17618
|
}
|
|
17509
17619
|
function providersFailingError(projectName, failures) {
|
|
@@ -17513,6 +17623,20 @@ function providersFailingError(projectName, failures) {
|
|
|
17513
17623
|
{ projectName, ...failures }
|
|
17514
17624
|
);
|
|
17515
17625
|
}
|
|
17626
|
+
var RUN_REFUSED_AUDIT_ACTION = "run.refused";
|
|
17627
|
+
function recordRunRefusal(db, projectId, failures, audit) {
|
|
17628
|
+
const last = db.select({ diff: auditLog.diff }).from(auditLog).where(and11(eq17(auditLog.projectId, projectId), eq17(auditLog.action, RUN_REFUSED_AUDIT_ACTION))).orderBy(desc7(auditLog.createdAt), desc7(auditLog.id)).limit(1).get();
|
|
17629
|
+
if (last && parseJsonColumn(last.diff, {}).latestRunId === failures.latestRunId) return false;
|
|
17630
|
+
writeAuditLog(db, {
|
|
17631
|
+
projectId,
|
|
17632
|
+
actor: audit.actor,
|
|
17633
|
+
action: RUN_REFUSED_AUDIT_ACTION,
|
|
17634
|
+
entityType: audit.entityType,
|
|
17635
|
+
entityId: audit.entityId,
|
|
17636
|
+
diff: { code: "PROVIDERS_FAILING", ...failures }
|
|
17637
|
+
});
|
|
17638
|
+
return true;
|
|
17639
|
+
}
|
|
17516
17640
|
function queueRunIfProjectIdle(db, params) {
|
|
17517
17641
|
const createdAt = params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
17518
17642
|
const kind = params.kind ?? "answer-visibility";
|
|
@@ -17553,17 +17677,14 @@ function queueRunIfProjectIdle(db, params) {
|
|
|
17553
17677
|
}
|
|
17554
17678
|
const current = activeRun();
|
|
17555
17679
|
if (current) return { conflict: true, activeRunId: current.id };
|
|
17556
|
-
const
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
)
|
|
17565
|
-
}) : null;
|
|
17566
|
-
if (refused) return { conflict: false, refused };
|
|
17680
|
+
const admission = queueProviderAdmission(tx, params, stamp, createdAt);
|
|
17681
|
+
if (admission.refused) {
|
|
17682
|
+
const refusalRecorded = params.auditRefusal ? recordRunRefusal(tx, params.projectId, admission.refused, params.auditRefusal) : false;
|
|
17683
|
+
return { conflict: false, refused: admission.refused, refusalRecorded };
|
|
17684
|
+
}
|
|
17685
|
+
const skippedProviders = admission.skipped;
|
|
17686
|
+
const admittedDispatch = dispatchAfterProviderAdmission(params, dispatch, admission);
|
|
17687
|
+
const dispatchModes = admittedDispatch.modes;
|
|
17567
17688
|
const scoped = params.queries != null || stamp?.scope != null;
|
|
17568
17689
|
const basket = scoped ? null : ensureCurrentQueryBasketRevision(tx, params.projectId, createdAt);
|
|
17569
17690
|
tx.insert(runs).values({
|
|
@@ -17587,10 +17708,11 @@ function queueRunIfProjectIdle(db, params) {
|
|
|
17587
17708
|
measurementManifest: stamp?.manifest ?? null,
|
|
17588
17709
|
measurementScope: stamp?.scope ?? null,
|
|
17589
17710
|
measurementExecutionIdentity: stamp?.identity ?? null,
|
|
17590
|
-
providerDispatchModes: Object.keys(
|
|
17711
|
+
providerDispatchModes: Object.keys(dispatchModes).length > 0 ? dispatchModes : null,
|
|
17712
|
+
skippedProviders: Object.keys(skippedProviders).length > 0 ? skippedProviders : null,
|
|
17591
17713
|
createdAt
|
|
17592
17714
|
}).run();
|
|
17593
|
-
return { conflict: false, runId, dispatch };
|
|
17715
|
+
return { conflict: false, runId, dispatch: admittedDispatch, skippedProviders };
|
|
17594
17716
|
});
|
|
17595
17717
|
}
|
|
17596
17718
|
|
|
@@ -21005,7 +21127,7 @@ async function runRoutes(app, opts) {
|
|
|
21005
21127
|
if (activeRun) {
|
|
21006
21128
|
return { conflict: true, activeRunId: activeRun.id };
|
|
21007
21129
|
}
|
|
21008
|
-
const
|
|
21130
|
+
const admission = providerAccountAdmission(tx, {
|
|
21009
21131
|
projectId: project.id,
|
|
21010
21132
|
trigger,
|
|
21011
21133
|
force: body.force ?? false,
|
|
@@ -21020,7 +21142,8 @@ async function runRoutes(app, opts) {
|
|
|
21020
21142
|
return providersARunWouldCall(roster, runnable);
|
|
21021
21143
|
}
|
|
21022
21144
|
});
|
|
21023
|
-
if (refused) return { conflict: false, refused };
|
|
21145
|
+
if (admission.refused) return { conflict: false, refused: admission.refused };
|
|
21146
|
+
const skippedProviders = Object.keys(admission.skipped).length > 0 ? admission.skipped : null;
|
|
21024
21147
|
const inserted = [];
|
|
21025
21148
|
for (const loc of projectLocations) {
|
|
21026
21149
|
const runId2 = crypto16.randomUUID();
|
|
@@ -21032,6 +21155,7 @@ async function runRoutes(app, opts) {
|
|
|
21032
21155
|
trigger,
|
|
21033
21156
|
location: loc.label,
|
|
21034
21157
|
queries: queriesColumn,
|
|
21158
|
+
skippedProviders,
|
|
21035
21159
|
createdAt: now
|
|
21036
21160
|
}).run();
|
|
21037
21161
|
inserted.push({ runId: runId2, loc });
|
|
@@ -21110,14 +21234,36 @@ async function runRoutes(app, opts) {
|
|
|
21110
21234
|
const countRow = app.db.select({ count: sql11`count(*)` }).from(runs).where(and21(eq28(runs.projectId, project.id), notProbeRun())).get();
|
|
21111
21235
|
const totalRuns = countRow?.count ?? 0;
|
|
21112
21236
|
const latestRun = app.db.select().from(runs).where(and21(eq28(runs.projectId, project.id), notProbeRun())).orderBy(desc11(runs.createdAt), desc11(runs.id)).limit(1).get();
|
|
21237
|
+
const admissionParams = {
|
|
21238
|
+
projectId: project.id,
|
|
21239
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21240
|
+
runnableProviders: opts.getRunnableProviderNames?.()
|
|
21241
|
+
};
|
|
21242
|
+
const admission = runAdmissionState(app.db, admissionParams);
|
|
21243
|
+
const manualAdmission = runAdmissionState(app.db, { ...admissionParams, selection: RunTriggers.manual });
|
|
21113
21244
|
if (!latestRun) {
|
|
21114
|
-
return reply.send({ totalRuns: 0, run: null });
|
|
21245
|
+
return reply.send({ totalRuns: 0, run: null, admission, manualAdmission });
|
|
21115
21246
|
}
|
|
21116
21247
|
return reply.send({
|
|
21117
21248
|
totalRuns,
|
|
21118
|
-
run: loadRunDetail(app, latestRun)
|
|
21249
|
+
run: loadRunDetail(app, latestRun),
|
|
21250
|
+
admission,
|
|
21251
|
+
manualAdmission
|
|
21119
21252
|
});
|
|
21120
21253
|
});
|
|
21254
|
+
app.get("/projects/:name/run-admission", async (request, reply) => {
|
|
21255
|
+
const selection = request.query.selection ?? RunTriggers.scheduled;
|
|
21256
|
+
if (selection !== RunTriggers.manual && selection !== RunTriggers.scheduled) {
|
|
21257
|
+
throw validationError('"selection" must be one of: manual, scheduled');
|
|
21258
|
+
}
|
|
21259
|
+
const project = resolveProject(app.db, request.params.name);
|
|
21260
|
+
return reply.send(runAdmissionState(app.db, {
|
|
21261
|
+
projectId: project.id,
|
|
21262
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21263
|
+
runnableProviders: opts.getRunnableProviderNames?.(),
|
|
21264
|
+
selection
|
|
21265
|
+
}));
|
|
21266
|
+
});
|
|
21121
21267
|
app.get("/runs", async (request, reply) => {
|
|
21122
21268
|
const limit = parseListLimit(request.query.limit, 500, 5e3);
|
|
21123
21269
|
const since = parseListSince(request.query.since);
|
|
@@ -21210,6 +21356,7 @@ async function runRoutes(app, opts) {
|
|
|
21210
21356
|
for (const entry of eligible) {
|
|
21211
21357
|
try {
|
|
21212
21358
|
assertMeasurementRunStampable(app.db, {
|
|
21359
|
+
createdAt: now,
|
|
21213
21360
|
projectId: entry.project.id,
|
|
21214
21361
|
kind,
|
|
21215
21362
|
trigger: "manual",
|
|
@@ -21218,7 +21365,8 @@ async function runRoutes(app, opts) {
|
|
|
21218
21365
|
runnableProviders: runnableProviderNames,
|
|
21219
21366
|
providerModels: opts.getEffectiveProviderModels?.(),
|
|
21220
21367
|
dispatchMode,
|
|
21221
|
-
batchEligibleProviders
|
|
21368
|
+
batchEligibleProviders,
|
|
21369
|
+
force: force ?? false
|
|
21222
21370
|
});
|
|
21223
21371
|
dispatchable.push(entry);
|
|
21224
21372
|
} catch (error) {
|
|
@@ -21434,6 +21582,8 @@ function formatRun(row) {
|
|
|
21434
21582
|
// Only plan runs can batch, so only they carry the frozen modes.
|
|
21435
21583
|
dispatchModes: row.providerDispatchModes ?? {}
|
|
21436
21584
|
} : {},
|
|
21585
|
+
// Only on a run that skipped a provider, so other runs read as before.
|
|
21586
|
+
...row.skippedProviders && Object.keys(row.skippedProviders).length > 0 ? { skippedProviders: row.skippedProviders } : {},
|
|
21437
21587
|
createdAt: row.createdAt
|
|
21438
21588
|
};
|
|
21439
21589
|
}
|
|
@@ -34087,6 +34237,128 @@ function clickPeriods(periods, clicks, impressions) {
|
|
|
34087
34237
|
impressions: impressions[index2] ?? 0
|
|
34088
34238
|
}));
|
|
34089
34239
|
}
|
|
34240
|
+
function compareCodeUnits(left, right) {
|
|
34241
|
+
if (left < right) return -1;
|
|
34242
|
+
return left > right ? 1 : 0;
|
|
34243
|
+
}
|
|
34244
|
+
function aiEngineLeadPeriods(periods, eventCounts, sessionCounts, leadRateAvailable) {
|
|
34245
|
+
return periods.map((period2, index2) => {
|
|
34246
|
+
const eventCount = eventCounts[index2] ?? 0;
|
|
34247
|
+
const sessions = sessionCounts[index2] ?? 0;
|
|
34248
|
+
return {
|
|
34249
|
+
...period2,
|
|
34250
|
+
eventCount,
|
|
34251
|
+
sessions,
|
|
34252
|
+
leadRate: leadRateAvailable && sessions > 0 ? roundRatio(eventCount / sessions, RatioUnits.fraction) : null
|
|
34253
|
+
};
|
|
34254
|
+
});
|
|
34255
|
+
}
|
|
34256
|
+
var UNATTRIBUTED_AI = "(unattributed)";
|
|
34257
|
+
function buildAiEngineLeadClass(input) {
|
|
34258
|
+
const { periods, leadRateAvailable } = input;
|
|
34259
|
+
const sourcesByKey = /* @__PURE__ */ new Map();
|
|
34260
|
+
for (const row of [...input.leads, ...input.sessions]) {
|
|
34261
|
+
const sources = sourcesByKey.get(row.key) ?? /* @__PURE__ */ new Set();
|
|
34262
|
+
sources.add(row.source);
|
|
34263
|
+
sourcesByKey.set(row.key, sources);
|
|
34264
|
+
}
|
|
34265
|
+
const leadsByKey = aggregateByKey(input.leads, periods, (row) => row.key, (row) => row.value);
|
|
34266
|
+
const sessionsByKey = aggregateByKey(input.sessions, periods, (row) => row.key, (row) => row.value);
|
|
34267
|
+
const zeros = Array(periods.length).fill(0);
|
|
34268
|
+
const sum = (values) => values.reduce((total, value) => total + value, 0);
|
|
34269
|
+
const countsFor = (key) => ({
|
|
34270
|
+
leads: leadsByKey.get(key) ?? zeros,
|
|
34271
|
+
sessions: sessionsByKey.get(key) ?? zeros
|
|
34272
|
+
});
|
|
34273
|
+
const sourcesFor = (key) => [...sourcesByKey.get(key) ?? []].sort(compareCodeUnits);
|
|
34274
|
+
const engineRows = [...sourcesByKey.keys()].filter((key) => key !== UNATTRIBUTED_AI).map((engine) => ({ engine, ...countsFor(engine) })).sort((left, right) => (right.leads.at(-1) ?? 0) - (left.leads.at(-1) ?? 0) || sum(right.leads) - sum(left.leads) || (right.sessions.at(-1) ?? 0) - (left.sessions.at(-1) ?? 0) || sum(right.sessions) - sum(left.sessions) || left.engine.localeCompare(right.engine));
|
|
34275
|
+
const unattributed = countsFor(UNATTRIBUTED_AI);
|
|
34276
|
+
const allRows = [...engineRows, unattributed];
|
|
34277
|
+
const totalLeads = periods.map((_, index2) => sum(allRows.map((row) => row.leads[index2] ?? 0)));
|
|
34278
|
+
const totalSessions = periods.map((_, index2) => sum(allRows.map((row) => row.sessions[index2] ?? 0)));
|
|
34279
|
+
return {
|
|
34280
|
+
periods: aiEngineLeadPeriods(periods, totalLeads, totalSessions, leadRateAvailable),
|
|
34281
|
+
engines: engineRows.map((row) => ({
|
|
34282
|
+
engine: row.engine,
|
|
34283
|
+
label: aiReferralEngineLabel(row.engine),
|
|
34284
|
+
sources: sourcesFor(row.engine),
|
|
34285
|
+
periods: aiEngineLeadPeriods(periods, row.leads, row.sessions, leadRateAvailable)
|
|
34286
|
+
})),
|
|
34287
|
+
unattributed: {
|
|
34288
|
+
sources: sourcesFor(UNATTRIBUTED_AI),
|
|
34289
|
+
periods: aiEngineLeadPeriods(periods, unattributed.leads, unattributed.sessions, leadRateAvailable)
|
|
34290
|
+
}
|
|
34291
|
+
};
|
|
34292
|
+
}
|
|
34293
|
+
function attributeAiEngineRows(input) {
|
|
34294
|
+
const engineBySource = /* @__PURE__ */ new Map();
|
|
34295
|
+
const engineOf = (source) => {
|
|
34296
|
+
let engine = engineBySource.get(source);
|
|
34297
|
+
if (engine === void 0) {
|
|
34298
|
+
engine = aiEngineForReferralSource(source);
|
|
34299
|
+
engineBySource.set(source, engine);
|
|
34300
|
+
}
|
|
34301
|
+
return engine;
|
|
34302
|
+
};
|
|
34303
|
+
let paidOnlyByLandingPage = false;
|
|
34304
|
+
const attribute = (rows, valueOf) => rows.flatMap((row) => {
|
|
34305
|
+
const engine = engineOf(row.source);
|
|
34306
|
+
if (engine === null && !isGa4AiAssistantChannel(row.channelGroup)) return [];
|
|
34307
|
+
const evidence = { source: row.source, medium: row.medium, channelGroup: row.channelGroup };
|
|
34308
|
+
const withLandingPage = classifyAiReferralTrafficClass({ ...evidence, landingPage: row.landingPage });
|
|
34309
|
+
const trafficClass = input.landingPageEvidence ? withLandingPage : classifyAiReferralTrafficClass({ ...evidence, landingPage: null });
|
|
34310
|
+
if (trafficClass !== withLandingPage) paidOnlyByLandingPage = true;
|
|
34311
|
+
return [{
|
|
34312
|
+
date: row.date,
|
|
34313
|
+
source: row.source,
|
|
34314
|
+
key: engine ?? UNATTRIBUTED_AI,
|
|
34315
|
+
trafficClass,
|
|
34316
|
+
value: valueOf(row)
|
|
34317
|
+
}];
|
|
34318
|
+
});
|
|
34319
|
+
const leads = attribute(input.leads, (row) => row.eventCount);
|
|
34320
|
+
const sessions = attribute(input.sessions, (row) => row.sessions);
|
|
34321
|
+
return { leads, sessions, paidOnlyByLandingPage };
|
|
34322
|
+
}
|
|
34323
|
+
function buildAiEngineLeads(input) {
|
|
34324
|
+
const leadRateAvailable = input.leadRateUnavailableReason === null;
|
|
34325
|
+
const classBlock = (trafficClass) => buildAiEngineLeadClass({
|
|
34326
|
+
periods: input.periods,
|
|
34327
|
+
leads: input.leads.filter((row) => row.trafficClass === trafficClass),
|
|
34328
|
+
sessions: input.sessions.filter((row) => row.trafficClass === trafficClass),
|
|
34329
|
+
leadRateAvailable
|
|
34330
|
+
});
|
|
34331
|
+
return {
|
|
34332
|
+
leadRateAvailable,
|
|
34333
|
+
leadRateUnavailableReason: input.leadRateUnavailableReason,
|
|
34334
|
+
organic: classBlock(AiReferralTrafficClasses.organic),
|
|
34335
|
+
paid: classBlock(AiReferralTrafficClasses.paid)
|
|
34336
|
+
};
|
|
34337
|
+
}
|
|
34338
|
+
function latestDate(rows) {
|
|
34339
|
+
let latest = null;
|
|
34340
|
+
for (const row of rows) {
|
|
34341
|
+
if (latest === null || row.date > latest) latest = row.date;
|
|
34342
|
+
}
|
|
34343
|
+
return latest;
|
|
34344
|
+
}
|
|
34345
|
+
function hasLeadDayWithoutSessions(acquisitionRows, leadRows) {
|
|
34346
|
+
const acquisitionDates = new Set(acquisitionRows.map((row) => row.date));
|
|
34347
|
+
return leadRows.some((row) => !acquisitionDates.has(row.date));
|
|
34348
|
+
}
|
|
34349
|
+
function aiEngineLeadRateUnavailableReason(input) {
|
|
34350
|
+
if (!input.hasData) return AiEngineLeadRateUnavailableReasons["no-data"];
|
|
34351
|
+
if (input.acquisitionStatus !== "ready" || input.leadStatus !== "ready") {
|
|
34352
|
+
return AiEngineLeadRateUnavailableReasons["sync-not-ready"];
|
|
34353
|
+
}
|
|
34354
|
+
if (input.channelLeadsUnfiltered) return AiEngineLeadRateUnavailableReasons["channel-leads-unfiltered"];
|
|
34355
|
+
if (input.paidOnlyByLandingPage) return AiEngineLeadRateUnavailableReasons["paid-split-needs-landing-page"];
|
|
34356
|
+
if (input.latestLeadDate !== null && (input.latestAcquisitionDate === null || input.latestAcquisitionDate < input.latestLeadDate)) {
|
|
34357
|
+
return AiEngineLeadRateUnavailableReasons["sessions-behind-leads"];
|
|
34358
|
+
}
|
|
34359
|
+
if (input.leadDayWithoutSessions) return AiEngineLeadRateUnavailableReasons["sessions-missing-on-lead-days"];
|
|
34360
|
+
return null;
|
|
34361
|
+
}
|
|
34090
34362
|
function engagementPeriods(periods, rows) {
|
|
34091
34363
|
return periods.map((period2) => {
|
|
34092
34364
|
const inPeriod = rows.filter((row) => row.date >= period2.startDate && row.date <= period2.endDate);
|
|
@@ -34303,6 +34575,28 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
|
|
|
34303
34575
|
(row) => row.channelGroup,
|
|
34304
34576
|
(row) => row.eventCount
|
|
34305
34577
|
);
|
|
34578
|
+
const filtersNarrowSessions = parsedHostScope.data === "marketing" || pathPrefix !== null && pathPrefix !== "/";
|
|
34579
|
+
const hasAiEngineLeadData = gaAnchor !== null && hasLeadTimeline;
|
|
34580
|
+
const aiEngineRows = attributeAiEngineRows({
|
|
34581
|
+
leads: hasAiEngineLeadData ? leads : [],
|
|
34582
|
+
sessions: hasAiEngineLeadData ? acquisition : [],
|
|
34583
|
+
landingPageEvidence: attributionScope !== "channel"
|
|
34584
|
+
});
|
|
34585
|
+
const aiEngineLeads = buildAiEngineLeads({
|
|
34586
|
+
periods: hasAiEngineLeadData ? gaPeriods : [],
|
|
34587
|
+
leads: aiEngineRows.leads,
|
|
34588
|
+
sessions: aiEngineRows.sessions,
|
|
34589
|
+
leadRateUnavailableReason: aiEngineLeadRateUnavailableReason({
|
|
34590
|
+
hasData: hasAiEngineLeadData,
|
|
34591
|
+
acquisitionStatus: state?.acquisitionStatus,
|
|
34592
|
+
leadStatus: state?.leadStatus,
|
|
34593
|
+
channelLeadsUnfiltered: attributionScope === "channel" && filtersNarrowSessions,
|
|
34594
|
+
paidOnlyByLandingPage: aiEngineRows.paidOnlyByLandingPage,
|
|
34595
|
+
latestAcquisitionDate: latestDate(acquisitionRows),
|
|
34596
|
+
latestLeadDate: latestDate(leadRows),
|
|
34597
|
+
leadDayWithoutSessions: hasLeadDayWithoutSessions(acquisitionRows, leadRows)
|
|
34598
|
+
})
|
|
34599
|
+
});
|
|
34306
34600
|
const propertyClicks = aggregateByKey(
|
|
34307
34601
|
propertyRows,
|
|
34308
34602
|
gscPeriods,
|
|
@@ -34407,7 +34701,8 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
|
|
|
34407
34701
|
channels: rankEntries(leadChannels).map(([channelGroup, values]) => ({
|
|
34408
34702
|
channelGroup,
|
|
34409
34703
|
periods: eventPeriods(gaPeriods, values)
|
|
34410
|
-
}))
|
|
34704
|
+
})),
|
|
34705
|
+
aiEngines: aiEngineLeads
|
|
34411
34706
|
},
|
|
34412
34707
|
engagement: {
|
|
34413
34708
|
status: engagementAnchor === null ? "unavailable" : "ready",
|
|
@@ -36404,7 +36699,14 @@ async function compositeRoutes(app, options) {
|
|
|
36404
36699
|
const previousVisRunGroup = visRunGroups[1] ?? [];
|
|
36405
36700
|
const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
|
|
36406
36701
|
const latestRunRow = allRuns[0] ?? null;
|
|
36407
|
-
const
|
|
36702
|
+
const admissionParams = {
|
|
36703
|
+
projectId: project.id,
|
|
36704
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36705
|
+
runnableProviders: options.getRunnableProviderNames?.()
|
|
36706
|
+
};
|
|
36707
|
+
const admission = runAdmissionState(app.db, admissionParams);
|
|
36708
|
+
const manualAdmission = runAdmissionState(app.db, { ...admissionParams, selection: RunTriggers.manual });
|
|
36709
|
+
const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow), admission, manualAdmission } : { totalRuns: 0, run: null, admission, manualAdmission };
|
|
36408
36710
|
const healthRow = app.db.select().from(healthSnapshots).where(eq50(healthSnapshots.projectId, project.id)).orderBy(desc24(healthSnapshots.createdAt)).limit(1).get();
|
|
36409
36711
|
const health = healthRow ? mapHealthRow2(healthRow) : null;
|
|
36410
36712
|
const insightRows = app.db.select().from(insights).where(eq50(insights.projectId, project.id)).orderBy(desc24(insights.createdAt)).all();
|
|
@@ -36644,6 +36946,7 @@ function summarizeRun(run) {
|
|
|
36644
36946
|
startedAt: run.startedAt,
|
|
36645
36947
|
finishedAt: run.finishedAt,
|
|
36646
36948
|
error: parseRunError(run.error),
|
|
36949
|
+
...run.skippedProviders && Object.keys(run.skippedProviders).length > 0 ? { skippedProviders: run.skippedProviders } : {},
|
|
36647
36950
|
createdAt: run.createdAt
|
|
36648
36951
|
};
|
|
36649
36952
|
}
|
|
@@ -38068,6 +38371,7 @@ var SCHEMA_TABLE = {
|
|
|
38068
38371
|
IntegrationSettingsSummaryDto: integrationSettingsSummaryDtoSchema,
|
|
38069
38372
|
KeywordDto: keywordDtoSchema,
|
|
38070
38373
|
LatestProjectRunDto: latestProjectRunDtoSchema,
|
|
38374
|
+
RunAdmissionDto: runAdmissionDtoSchema,
|
|
38071
38375
|
LocationContext: locationContextSchema,
|
|
38072
38376
|
MeasurementDiscoveryRequest: measurementDiscoveryRequestSchema,
|
|
38073
38377
|
MeasurementDiscoveryResponse: measurementDiscoveryResponseSchema,
|
|
@@ -40544,7 +40848,7 @@ var routeCatalog = [
|
|
|
40544
40848
|
dispatchMode: dispatchModeRequestSchema,
|
|
40545
40849
|
force: {
|
|
40546
40850
|
type: "boolean",
|
|
40547
|
-
description: "
|
|
40851
|
+
description: "Call every provider: queue the run even when it would be refused with PROVIDERS_FAILING, and skip no provider that keeps failing on its account. Admission only; never stored."
|
|
40548
40852
|
}
|
|
40549
40853
|
}
|
|
40550
40854
|
}
|
|
@@ -40552,12 +40856,15 @@ var routeCatalog = [
|
|
|
40552
40856
|
}
|
|
40553
40857
|
},
|
|
40554
40858
|
responses: {
|
|
40555
|
-
201: jsonResponse(
|
|
40859
|
+
201: jsonResponse(
|
|
40860
|
+
`Run queued. \`skippedProviders\` names each provider it will not call because that provider failed on its account in each of its last ${PROVIDER_ACCOUNT_FAILURE_STREAK} runs (until its \`retryAfter\`); the run calls the rest and ends partial.`,
|
|
40861
|
+
"RunDto"
|
|
40862
|
+
),
|
|
40556
40863
|
400: errorResponse(
|
|
40557
40864
|
'Invalid request: an untracked query, a measurement scope naming a group/target/question the published plan does not contain, a scope combined with a query list, a per-run location on a plan project, a provider roster the plan was not published for, or `dispatchMode: "batch"` when no provider of the run can batch (`details.ineligible` names each provider\'s reason).'
|
|
40558
40865
|
),
|
|
40559
40866
|
422: errorResponse(
|
|
40560
|
-
`NO_QUERIES: the project has no tracked queries. PROVIDERS_FAILING: every provider the run would call failed on its account (rejected key, denied access, no credit) in each of its last ${PROVIDER_ACCOUNT_FAILURE_STREAK} runs. \`details.providers\` names each provider's code and \`details.retryAfter\` when one run is let through again (${PROVIDER_ACCOUNT_RETRY_HOURS}h after
|
|
40867
|
+
`NO_QUERIES: the project has no tracked queries. PROVIDERS_FAILING: every provider the run would call failed on its account (rejected key, denied access, no credit) in each of its last ${PROVIDER_ACCOUNT_FAILURE_STREAK} runs. \`details.providers\` names each provider's code and \`details.retryAfter\` when one run is let through again (${PROVIDER_ACCOUNT_RETRY_HOURS}h after a provider's newest failure; the run then calls only the providers due a retry). Saving a new key, model or endpoint in a provider's settings lets the next run through at once, a probe is never refused, and \`force: true\` overrides. When only some providers keep failing, the run is queued without them instead.`
|
|
40561
40868
|
),
|
|
40562
40869
|
409: errorResponse("Run already in progress."),
|
|
40563
40870
|
503: errorResponse("No runnable answer provider is configured.")
|
|
@@ -40577,12 +40884,33 @@ var routeCatalog = [
|
|
|
40577
40884
|
method: "get",
|
|
40578
40885
|
path: "/api/v1/projects/{name}/runs/latest",
|
|
40579
40886
|
summary: "Get the latest project run",
|
|
40887
|
+
description: "The newest non-probe run, the run count, and `admission`: whether the next full sweep would be refused (PROVIDERS_FAILING) and which providers it would skip because each keeps failing on its account, with their codes and `retryAfter`. `admission` uses the scheduled roster; `manualAdmission` describes an unfiltered manual launch. A scheduled sweep that is refused leaves no run, so read `admission`, not the latest run, to tell.",
|
|
40580
40888
|
tags: ["runs"],
|
|
40581
40889
|
parameters: [nameParameter],
|
|
40582
40890
|
responses: {
|
|
40583
40891
|
200: jsonResponse("Latest run returned.", "LatestProjectRunDto")
|
|
40584
40892
|
}
|
|
40585
40893
|
},
|
|
40894
|
+
{
|
|
40895
|
+
method: "get",
|
|
40896
|
+
path: "/api/v1/projects/{name}/run-admission",
|
|
40897
|
+
summary: "Get whether the next sweep would be admitted",
|
|
40898
|
+
description: `The \`admission\` or \`manualAdmission\` of \`/projects/{name}/runs/latest\` without the latest run: whether the next full sweep would be refused (PROVIDERS_FAILING) and which providers it would skip because each failed on its account in each of its last ${PROVIDER_ACCOUNT_FAILURE_STREAK} runs, with each provider's code and \`retryAfter\`.`,
|
|
40899
|
+
tags: ["runs"],
|
|
40900
|
+
parameters: [
|
|
40901
|
+
nameParameter,
|
|
40902
|
+
{
|
|
40903
|
+
name: "selection",
|
|
40904
|
+
in: "query",
|
|
40905
|
+
description: "Read-selection identity: scheduled (default) uses the enabled schedule's providers; manual uses the unfiltered manual launch's project or instance providers. Both use an Advanced revision's frozen engines.",
|
|
40906
|
+
schema: { type: "string", enum: ["manual", "scheduled"], default: "scheduled" }
|
|
40907
|
+
}
|
|
40908
|
+
],
|
|
40909
|
+
responses: {
|
|
40910
|
+
200: jsonResponse("Run admission returned.", "RunAdmissionDto"),
|
|
40911
|
+
400: errorResponse("Invalid admission selection.")
|
|
40912
|
+
}
|
|
40913
|
+
},
|
|
40586
40914
|
{
|
|
40587
40915
|
method: "get",
|
|
40588
40916
|
path: "/api/v1/runs",
|
|
@@ -40617,7 +40945,7 @@ var routeCatalog = [
|
|
|
40617
40945
|
dispatchMode: dispatchModeRequestSchema,
|
|
40618
40946
|
force: {
|
|
40619
40947
|
type: "boolean",
|
|
40620
|
-
description: "
|
|
40948
|
+
description: "Call every provider: queue projects that would be refused with PROVIDERS_FAILING, and skip no provider that keeps failing on its account. Admission only; never stored."
|
|
40621
40949
|
}
|
|
40622
40950
|
}
|
|
40623
40951
|
}
|
|
@@ -76149,7 +76477,6 @@ function verifyIpForRuleDecision(ip, ruleId) {
|
|
|
76149
76477
|
}
|
|
76150
76478
|
|
|
76151
76479
|
// ../integration-traffic/src/rules.ts
|
|
76152
|
-
var LEGACY_CHATGPT_DOMAIN = "chat.openai.com";
|
|
76153
76480
|
var SELF_TRAFFIC_USER_AGENT_PATTERNS = [
|
|
76154
76481
|
/AINYC-AEO-Audit\//i
|
|
76155
76482
|
];
|
|
@@ -76532,7 +76859,7 @@ var DEFAULT_AI_CRAWLER_USER_AGENT_SUBSTRINGS = [
|
|
|
76532
76859
|
];
|
|
76533
76860
|
var DEFAULT_AI_REFERRER_RULES = [
|
|
76534
76861
|
{ domain: AI_ENGINE_DOMAINS.chatgpt, operator: "OpenAI", product: "ChatGPT" },
|
|
76535
|
-
{ domain:
|
|
76862
|
+
{ domain: AI_ENGINE_DOMAINS.chatgptLegacy, operator: "OpenAI", product: "ChatGPT" },
|
|
76536
76863
|
{ domain: AI_ENGINE_DOMAINS.perplexity, operator: "Perplexity", product: "Perplexity" },
|
|
76537
76864
|
{ domain: AI_ENGINE_DOMAINS.claude, operator: "Anthropic", product: "Claude" },
|
|
76538
76865
|
{ domain: AI_ENGINE_DOMAINS.gemini, operator: "Google", product: "Gemini" },
|
|
@@ -89646,7 +89973,7 @@ async function apiRoutes(app, opts) {
|
|
|
89646
89973
|
await api.register(citationRoutes);
|
|
89647
89974
|
await api.register(visibilityStatsRoutes);
|
|
89648
89975
|
await api.register(resultsExportRoutes);
|
|
89649
|
-
await api.register(compositeRoutes, { sentiment: opts.sentiment });
|
|
89976
|
+
await api.register(compositeRoutes, { sentiment: opts.sentiment, getRunnableProviderNames: opts.getRunnableProviderNames });
|
|
89650
89977
|
await api.register(contentRoutes, {
|
|
89651
89978
|
explainContentRecommendation: opts.explainContentRecommendation,
|
|
89652
89979
|
briefContentRecommendation: opts.briefContentRecommendation,
|