@canonry/canonry 7.12.0 → 7.14.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 +14 -7
- package/assets/assets/AuditHistoryPanel-gAkqWz9z.js +1 -0
- package/assets/assets/{BacklinksPage-CbRIZhu5.js → BacklinksPage-C67DtHo8.js} +1 -1
- package/assets/assets/{HistoryPage-Dfq1vK9k.js → HistoryPage-BQuW6Z6m.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-DcH0zYNN.js → MeasurementPropertyPage-B1XS6DR0.js} +1 -1
- package/assets/assets/ProjectPage-CV_7bOA7.js +8 -0
- package/assets/assets/{RunsPage-aOeJ60pX.js → RunsPage-CJYihZYC.js} +1 -1
- package/assets/assets/SettingsPage-Dd3J2DQE.js +1 -0
- package/assets/assets/{SiteHealthSection-CUOt7jeo.js → SiteHealthSection-iktyuzt2.js} +3 -3
- package/assets/assets/{TrafficPage-C0fVYizx.js → TrafficPage-CWjK7F3j.js} +2 -2
- package/assets/assets/{TrafficSourceDetailPage-LMhXbFlA.js → TrafficSourceDetailPage-D4_AXQNE.js} +1 -1
- package/assets/assets/{extract-error-message-5aHy0fsK.js → extract-error-message-Cxefvthz.js} +1 -1
- package/assets/assets/{index-BJh8cX5E.js → index-BbmiGIM_.js} +27 -27
- package/assets/assets/index-aBS1f2Bj.css +1 -0
- package/assets/assets/{react-sigma_core.esm.min-ldX35rMq.js → react-sigma_core.esm.min-DDEPa7AV.js} +1 -1
- package/assets/assets/{v2-overview-adapter-CaO9vY-l.js → v2-overview-adapter-CLxlMdb2.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-BTHEFJCI.js → chunk-4LQ4HKVK.js} +5 -5
- package/dist/{chunk-AU7OUDWR.js → chunk-A25FO3Z5.js} +64 -13
- package/dist/{chunk-GQMSJEMZ.js → chunk-EZRBFH77.js} +233 -54
- package/dist/{chunk-DXI2FTSW.js → chunk-KXHPQMBP.js} +93 -31
- package/dist/{chunk-EPSHIH63.js → chunk-LUQ5G5OO.js} +3 -3
- package/dist/{chunk-7I64KEGM.js → chunk-WHVEDCHY.js} +2 -2
- package/dist/{chunk-SM6ZZAPK.js → chunk-ZHXOR2LJ.js} +2 -2
- package/dist/cli.js +1300 -1244
- package/dist/{demo-server-WJUA4EIG.js → demo-server-SIATTXXS.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-DJRH4Z3D.js → intelligence-service-UEEVETII.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +13 -13
- package/assets/assets/AuditHistoryPanel-Bpkuj-iF.js +0 -1
- package/assets/assets/ProjectPage-enF4twWt.js +0 -8
- package/assets/assets/SettingsPage-nA0_ImoQ.js +0 -1
- package/assets/assets/index-BNeiohKS.css +0 -1
|
@@ -685,6 +685,7 @@ import {
|
|
|
685
685
|
resultsExportDtoSchema,
|
|
686
686
|
retryAfterDelayMs,
|
|
687
687
|
roundRatio,
|
|
688
|
+
runAdmissionDtoSchema,
|
|
688
689
|
runCompletenessDtoSchema,
|
|
689
690
|
runDetailDtoSchema,
|
|
690
691
|
runDtoSchema,
|
|
@@ -820,7 +821,7 @@ import {
|
|
|
820
821
|
wordpressSchemaDeployResultDtoSchema,
|
|
821
822
|
wordpressSchemaStatusResultDtoSchema,
|
|
822
823
|
wordpressStatusDtoSchema
|
|
823
|
-
} from "./chunk-
|
|
824
|
+
} from "./chunk-A25FO3Z5.js";
|
|
824
825
|
|
|
825
826
|
// src/intelligence-service.ts
|
|
826
827
|
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 +1275,12 @@ var runs = sqliteTable("runs", {
|
|
|
1274
1275
|
* lose them; folded into `error` and cleared when the run finalizes.
|
|
1275
1276
|
*/
|
|
1276
1277
|
pendingProviderErrors: text("pending_provider_errors", { mode: "json" }).$type(),
|
|
1278
|
+
/**
|
|
1279
|
+
* Providers this run does not call because each keeps failing on its
|
|
1280
|
+
* account, with the failure streak that decided it, frozen at queue time
|
|
1281
|
+
* (`providerAccountAdmission`). Null when it skips none.
|
|
1282
|
+
*/
|
|
1283
|
+
skippedProviders: text("skipped_providers", { mode: "json" }).$type(),
|
|
1277
1284
|
createdAt: text("created_at").notNull()
|
|
1278
1285
|
}, (table) => [
|
|
1279
1286
|
index("idx_runs_project").on(table.projectId),
|
|
@@ -8322,6 +8329,16 @@ var MIGRATION_VERSIONS = [
|
|
|
8322
8329
|
`ALTER TABLE dashboard_sessions ADD COLUMN api_key_hash TEXT NOT NULL DEFAULT ''`,
|
|
8323
8330
|
`ALTER TABLE dashboard_sessions ADD COLUMN password_fingerprint TEXT`
|
|
8324
8331
|
]
|
|
8332
|
+
},
|
|
8333
|
+
{
|
|
8334
|
+
// Providers a run does not call because each keeps failing on its
|
|
8335
|
+
// account, frozen at queue time. Nullable with no default: every existing
|
|
8336
|
+
// run, and every run an older writer queues, skips none.
|
|
8337
|
+
version: 175,
|
|
8338
|
+
name: "runs-skipped-providers",
|
|
8339
|
+
statements: [
|
|
8340
|
+
`ALTER TABLE runs ADD COLUMN skipped_providers TEXT`
|
|
8341
|
+
]
|
|
8325
8342
|
}
|
|
8326
8343
|
];
|
|
8327
8344
|
var AERO_LEDGER_REBUILDS = [
|
|
@@ -17405,7 +17422,11 @@ function sliceFor(plan, params) {
|
|
|
17405
17422
|
}
|
|
17406
17423
|
function assertMeasurementRunStampable(db, params) {
|
|
17407
17424
|
const stamp = measurementStamp(db, params);
|
|
17408
|
-
resolveQueueDispatch(db, params, stamp);
|
|
17425
|
+
const dispatch = resolveQueueDispatch(db, params, stamp);
|
|
17426
|
+
if (params.dispatchMode === ProviderDispatchModes.batch) {
|
|
17427
|
+
const admission = queueProviderAdmission(db, params, stamp, params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
17428
|
+
dispatchAfterProviderAdmission(params, dispatch, admission);
|
|
17429
|
+
}
|
|
17409
17430
|
}
|
|
17410
17431
|
function resolveQueueDispatch(tx, params, stamp) {
|
|
17411
17432
|
const trigger = stamp?.scope ? RunTriggers.probe : params.trigger ?? RunTriggers.manual;
|
|
@@ -17427,6 +17448,30 @@ function resolveQueueDispatch(tx, params, stamp) {
|
|
|
17427
17448
|
}
|
|
17428
17449
|
return resolution;
|
|
17429
17450
|
}
|
|
17451
|
+
function dispatchAfterProviderAdmission(params, dispatch, admission) {
|
|
17452
|
+
if (admission.refused) return dispatch;
|
|
17453
|
+
const skippedProviders = admission.skipped;
|
|
17454
|
+
const modes = Object.fromEntries(Object.entries(dispatch.modes).filter(([provider]) => !(provider in skippedProviders)));
|
|
17455
|
+
if (params.dispatchMode === ProviderDispatchModes.batch && Object.keys(modes).length === 0) {
|
|
17456
|
+
throw validationError(
|
|
17457
|
+
`${batchDispatchRefusalMessage(dispatch.ineligible)} Held back after repeated account failures: ${Object.keys(skippedProviders).join(", ")}. Pass force to call those providers now.`,
|
|
17458
|
+
{ ineligible: dispatch.ineligible, skippedProviders }
|
|
17459
|
+
);
|
|
17460
|
+
}
|
|
17461
|
+
return { ...dispatch, modes };
|
|
17462
|
+
}
|
|
17463
|
+
function queueProviderAdmission(db, params, stamp, now) {
|
|
17464
|
+
return (params.kind ?? RunKinds["answer-visibility"]) === RunKinds["answer-visibility"] ? providerAccountAdmission(db, {
|
|
17465
|
+
projectId: params.projectId,
|
|
17466
|
+
trigger: stamp?.scope ? RunTriggers.probe : params.trigger ?? RunTriggers.manual,
|
|
17467
|
+
force: params.force ?? false,
|
|
17468
|
+
now,
|
|
17469
|
+
providers: () => providersARunWouldCall(
|
|
17470
|
+
stamp?.identity.providers ?? providerRoster(db, params),
|
|
17471
|
+
params.runnableProviders
|
|
17472
|
+
)
|
|
17473
|
+
}) : { skipped: {} };
|
|
17474
|
+
}
|
|
17430
17475
|
function providersARunWouldCall(roster, runnable) {
|
|
17431
17476
|
const listed = normalizeProviders(roster);
|
|
17432
17477
|
if (runnable == null) return listed;
|
|
@@ -17447,63 +17492,124 @@ function providerSettingsChangeCouldFix(action, diff) {
|
|
|
17447
17492
|
return false;
|
|
17448
17493
|
}
|
|
17449
17494
|
}
|
|
17450
|
-
function
|
|
17451
|
-
|
|
17495
|
+
function heldProviderAccounts(db, params) {
|
|
17496
|
+
const held = /* @__PURE__ */ new Map();
|
|
17452
17497
|
const recent = db.select({
|
|
17453
17498
|
id: runs.id,
|
|
17454
17499
|
status: runs.status,
|
|
17455
17500
|
error: runs.error,
|
|
17456
17501
|
createdAt: runs.createdAt,
|
|
17457
|
-
finishedAt: runs.finishedAt
|
|
17502
|
+
finishedAt: runs.finishedAt,
|
|
17503
|
+
skippedProviders: runs.skippedProviders
|
|
17458
17504
|
}).from(runs).where(and11(
|
|
17459
17505
|
eq17(runs.projectId, params.projectId),
|
|
17460
17506
|
eq17(runs.kind, RunKinds["answer-visibility"]),
|
|
17461
17507
|
inArray6(runs.status, [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed, RunStatuses.cancelled])
|
|
17462
17508
|
)).orderBy(desc7(runs.createdAt), desc7(runs.id)).limit(PROVIDER_ACCOUNT_LOOKBACK).all().map((run) => ({ ...run, providers: parseRunError(run.error)?.providers ?? {} }));
|
|
17463
|
-
const
|
|
17464
|
-
|
|
17509
|
+
const entries = recent.flatMap((run) => Object.values(run.providers));
|
|
17510
|
+
const withAccountFailure = recent.filter((run) => Object.values(run.providers).some((entry) => !entry.skipped && isProviderAccountFailure(entry.code)));
|
|
17511
|
+
if (withAccountFailure.length < PROVIDER_ACCOUNT_FAILURE_STREAK && !entries.some((entry) => entry.skipped)) return held;
|
|
17465
17512
|
const calling = normalizeProviders(params.providers());
|
|
17466
|
-
if (calling.length === 0) return
|
|
17513
|
+
if (calling.length === 0) return held;
|
|
17467
17514
|
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
|
|
17515
|
+
const finished = (run) => Date.parse(run.finishedAt ?? run.createdAt);
|
|
17516
|
+
const now = Date.parse(params.now);
|
|
17469
17517
|
for (const provider of calling) {
|
|
17470
17518
|
let count4 = 0;
|
|
17471
|
-
let
|
|
17519
|
+
let newest = null;
|
|
17472
17520
|
let oldest = null;
|
|
17473
17521
|
let code = null;
|
|
17522
|
+
let vouched = null;
|
|
17523
|
+
let ended = false;
|
|
17474
17524
|
for (const run of recent) {
|
|
17475
17525
|
const entry = run.providers[provider];
|
|
17476
17526
|
if (!entry) {
|
|
17477
|
-
if (run.status !== RunStatuses.failed && answered2(run.id, provider))
|
|
17527
|
+
if (run.status !== RunStatuses.failed && answered2(run.id, provider)) {
|
|
17528
|
+
ended = true;
|
|
17529
|
+
break;
|
|
17530
|
+
}
|
|
17531
|
+
continue;
|
|
17532
|
+
}
|
|
17533
|
+
if (entry.skipped) {
|
|
17534
|
+
vouched = run.skippedProviders?.[provider] ?? null;
|
|
17535
|
+
if (vouched) break;
|
|
17478
17536
|
continue;
|
|
17479
17537
|
}
|
|
17480
|
-
if (!isProviderAccountFailure(entry.code))
|
|
17481
|
-
|
|
17538
|
+
if (!isProviderAccountFailure(entry.code)) {
|
|
17539
|
+
ended = true;
|
|
17540
|
+
break;
|
|
17541
|
+
}
|
|
17542
|
+
newest ??= run;
|
|
17482
17543
|
code ??= entry.code;
|
|
17483
17544
|
oldest = run;
|
|
17484
17545
|
count4 += 1;
|
|
17485
17546
|
if (count4 === PROVIDER_ACCOUNT_FAILURE_STREAK) break;
|
|
17486
17547
|
}
|
|
17487
|
-
if (
|
|
17488
|
-
|
|
17548
|
+
if (ended) continue;
|
|
17549
|
+
if (count4 < PROVIDER_ACCOUNT_FAILURE_STREAK && !vouched) continue;
|
|
17550
|
+
const retryAfterMs = newest ? finished(newest) + PROVIDER_ACCOUNT_RETRY_HOURS * 36e5 : Date.parse(vouched.retryAfter);
|
|
17551
|
+
if (!(now < retryAfterMs)) continue;
|
|
17552
|
+
held.set(provider, {
|
|
17553
|
+
code: code ?? vouched.code,
|
|
17554
|
+
consecutiveRuns: PROVIDER_ACCOUNT_FAILURE_STREAK,
|
|
17555
|
+
// A vouched streak began before the runs read here.
|
|
17556
|
+
since: vouched ? vouched.since : oldest.createdAt,
|
|
17557
|
+
latestRunId: newest?.id ?? vouched.latestRunId,
|
|
17558
|
+
retryAfter: new Date(retryAfterMs).toISOString()
|
|
17559
|
+
});
|
|
17489
17560
|
}
|
|
17490
|
-
|
|
17491
|
-
const
|
|
17492
|
-
const
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17561
|
+
if (held.size === 0) return held;
|
|
17562
|
+
const since = [...held.values()].map((streak) => streak.since).sort()[0];
|
|
17563
|
+
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();
|
|
17564
|
+
for (const change of settingsChanges) {
|
|
17565
|
+
const streak = held.get(change.entityId ?? "");
|
|
17566
|
+
if (streak && change.createdAt > streak.since && providerSettingsChangeCouldFix(change.action, change.diff)) {
|
|
17567
|
+
held.delete(change.entityId);
|
|
17568
|
+
}
|
|
17569
|
+
}
|
|
17570
|
+
return held;
|
|
17571
|
+
}
|
|
17572
|
+
function providerAccountFailures(held) {
|
|
17573
|
+
const streaks = [...held.values()];
|
|
17574
|
+
const newest = [...streaks].sort((a, b) => b.retryAfter.localeCompare(a.retryAfter))[0];
|
|
17501
17575
|
return {
|
|
17502
17576
|
consecutiveRuns: PROVIDER_ACCOUNT_FAILURE_STREAK,
|
|
17503
|
-
latestRunId: newest.
|
|
17504
|
-
since,
|
|
17505
|
-
|
|
17506
|
-
|
|
17577
|
+
latestRunId: newest.latestRunId,
|
|
17578
|
+
since: streaks.map((streak) => streak.since).sort()[0],
|
|
17579
|
+
// The first provider called again lets a run through.
|
|
17580
|
+
retryAfter: streaks.map((streak) => streak.retryAfter).sort()[0],
|
|
17581
|
+
providers: Object.fromEntries([...held].map(([provider, streak]) => [provider, streak.code]))
|
|
17582
|
+
};
|
|
17583
|
+
}
|
|
17584
|
+
function providerAccountAdmission(db, params) {
|
|
17585
|
+
if (params.force || params.trigger === RunTriggers.probe) return { skipped: {} };
|
|
17586
|
+
let calling = null;
|
|
17587
|
+
const providers = () => calling ??= normalizeProviders(params.providers());
|
|
17588
|
+
const held = heldProviderAccounts(db, { projectId: params.projectId, now: params.now, providers });
|
|
17589
|
+
if (held.size === 0) return { skipped: {} };
|
|
17590
|
+
if (providers().every((provider) => held.has(provider))) return { refused: providerAccountFailures(held) };
|
|
17591
|
+
return { skipped: Object.fromEntries(held) };
|
|
17592
|
+
}
|
|
17593
|
+
function sweepProviders(db, projectId, runnable, selection) {
|
|
17594
|
+
const planProviders = activeRevisionProviders(db, projectId);
|
|
17595
|
+
if (planProviders.length > 0) return providersARunWouldCall(planProviders, runnable);
|
|
17596
|
+
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();
|
|
17597
|
+
const project = db.select({ providers: projects.providers }).from(projects).where(eq17(projects.id, projectId)).get();
|
|
17598
|
+
return providersARunWouldCall(resolveRunProviderSelection({
|
|
17599
|
+
requestedProviders: selection === RunTriggers.scheduled && schedule?.enabled ? schedule.providers : null,
|
|
17600
|
+
projectProviders: project?.providers ?? [],
|
|
17601
|
+
runnableProviders: runnable
|
|
17602
|
+
}), runnable);
|
|
17603
|
+
}
|
|
17604
|
+
function runAdmissionState(db, params) {
|
|
17605
|
+
let calling = null;
|
|
17606
|
+
const providers = () => calling ??= sweepProviders(db, params.projectId, params.runnableProviders, params.selection ?? RunTriggers.scheduled);
|
|
17607
|
+
const held = heldProviderAccounts(db, { projectId: params.projectId, now: params.now, providers });
|
|
17608
|
+
const refused = held.size > 0 && providers().every((provider) => held.has(provider));
|
|
17609
|
+
return {
|
|
17610
|
+
refused,
|
|
17611
|
+
retryAfter: refused ? providerAccountFailures(held).retryAfter : null,
|
|
17612
|
+
providers: Object.fromEntries(held)
|
|
17507
17613
|
};
|
|
17508
17614
|
}
|
|
17509
17615
|
function providersFailingError(projectName, failures) {
|
|
@@ -17513,6 +17619,20 @@ function providersFailingError(projectName, failures) {
|
|
|
17513
17619
|
{ projectName, ...failures }
|
|
17514
17620
|
);
|
|
17515
17621
|
}
|
|
17622
|
+
var RUN_REFUSED_AUDIT_ACTION = "run.refused";
|
|
17623
|
+
function recordRunRefusal(db, projectId, failures, audit) {
|
|
17624
|
+
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();
|
|
17625
|
+
if (last && parseJsonColumn(last.diff, {}).latestRunId === failures.latestRunId) return false;
|
|
17626
|
+
writeAuditLog(db, {
|
|
17627
|
+
projectId,
|
|
17628
|
+
actor: audit.actor,
|
|
17629
|
+
action: RUN_REFUSED_AUDIT_ACTION,
|
|
17630
|
+
entityType: audit.entityType,
|
|
17631
|
+
entityId: audit.entityId,
|
|
17632
|
+
diff: { code: "PROVIDERS_FAILING", ...failures }
|
|
17633
|
+
});
|
|
17634
|
+
return true;
|
|
17635
|
+
}
|
|
17516
17636
|
function queueRunIfProjectIdle(db, params) {
|
|
17517
17637
|
const createdAt = params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
17518
17638
|
const kind = params.kind ?? "answer-visibility";
|
|
@@ -17553,17 +17673,14 @@ function queueRunIfProjectIdle(db, params) {
|
|
|
17553
17673
|
}
|
|
17554
17674
|
const current = activeRun();
|
|
17555
17675
|
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 };
|
|
17676
|
+
const admission = queueProviderAdmission(tx, params, stamp, createdAt);
|
|
17677
|
+
if (admission.refused) {
|
|
17678
|
+
const refusalRecorded = params.auditRefusal ? recordRunRefusal(tx, params.projectId, admission.refused, params.auditRefusal) : false;
|
|
17679
|
+
return { conflict: false, refused: admission.refused, refusalRecorded };
|
|
17680
|
+
}
|
|
17681
|
+
const skippedProviders = admission.skipped;
|
|
17682
|
+
const admittedDispatch = dispatchAfterProviderAdmission(params, dispatch, admission);
|
|
17683
|
+
const dispatchModes = admittedDispatch.modes;
|
|
17567
17684
|
const scoped = params.queries != null || stamp?.scope != null;
|
|
17568
17685
|
const basket = scoped ? null : ensureCurrentQueryBasketRevision(tx, params.projectId, createdAt);
|
|
17569
17686
|
tx.insert(runs).values({
|
|
@@ -17587,10 +17704,11 @@ function queueRunIfProjectIdle(db, params) {
|
|
|
17587
17704
|
measurementManifest: stamp?.manifest ?? null,
|
|
17588
17705
|
measurementScope: stamp?.scope ?? null,
|
|
17589
17706
|
measurementExecutionIdentity: stamp?.identity ?? null,
|
|
17590
|
-
providerDispatchModes: Object.keys(
|
|
17707
|
+
providerDispatchModes: Object.keys(dispatchModes).length > 0 ? dispatchModes : null,
|
|
17708
|
+
skippedProviders: Object.keys(skippedProviders).length > 0 ? skippedProviders : null,
|
|
17591
17709
|
createdAt
|
|
17592
17710
|
}).run();
|
|
17593
|
-
return { conflict: false, runId, dispatch };
|
|
17711
|
+
return { conflict: false, runId, dispatch: admittedDispatch, skippedProviders };
|
|
17594
17712
|
});
|
|
17595
17713
|
}
|
|
17596
17714
|
|
|
@@ -21005,7 +21123,7 @@ async function runRoutes(app, opts) {
|
|
|
21005
21123
|
if (activeRun) {
|
|
21006
21124
|
return { conflict: true, activeRunId: activeRun.id };
|
|
21007
21125
|
}
|
|
21008
|
-
const
|
|
21126
|
+
const admission = providerAccountAdmission(tx, {
|
|
21009
21127
|
projectId: project.id,
|
|
21010
21128
|
trigger,
|
|
21011
21129
|
force: body.force ?? false,
|
|
@@ -21020,7 +21138,8 @@ async function runRoutes(app, opts) {
|
|
|
21020
21138
|
return providersARunWouldCall(roster, runnable);
|
|
21021
21139
|
}
|
|
21022
21140
|
});
|
|
21023
|
-
if (refused) return { conflict: false, refused };
|
|
21141
|
+
if (admission.refused) return { conflict: false, refused: admission.refused };
|
|
21142
|
+
const skippedProviders = Object.keys(admission.skipped).length > 0 ? admission.skipped : null;
|
|
21024
21143
|
const inserted = [];
|
|
21025
21144
|
for (const loc of projectLocations) {
|
|
21026
21145
|
const runId2 = crypto16.randomUUID();
|
|
@@ -21032,6 +21151,7 @@ async function runRoutes(app, opts) {
|
|
|
21032
21151
|
trigger,
|
|
21033
21152
|
location: loc.label,
|
|
21034
21153
|
queries: queriesColumn,
|
|
21154
|
+
skippedProviders,
|
|
21035
21155
|
createdAt: now
|
|
21036
21156
|
}).run();
|
|
21037
21157
|
inserted.push({ runId: runId2, loc });
|
|
@@ -21110,14 +21230,36 @@ async function runRoutes(app, opts) {
|
|
|
21110
21230
|
const countRow = app.db.select({ count: sql11`count(*)` }).from(runs).where(and21(eq28(runs.projectId, project.id), notProbeRun())).get();
|
|
21111
21231
|
const totalRuns = countRow?.count ?? 0;
|
|
21112
21232
|
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();
|
|
21233
|
+
const admissionParams = {
|
|
21234
|
+
projectId: project.id,
|
|
21235
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21236
|
+
runnableProviders: opts.getRunnableProviderNames?.()
|
|
21237
|
+
};
|
|
21238
|
+
const admission = runAdmissionState(app.db, admissionParams);
|
|
21239
|
+
const manualAdmission = runAdmissionState(app.db, { ...admissionParams, selection: RunTriggers.manual });
|
|
21113
21240
|
if (!latestRun) {
|
|
21114
|
-
return reply.send({ totalRuns: 0, run: null });
|
|
21241
|
+
return reply.send({ totalRuns: 0, run: null, admission, manualAdmission });
|
|
21115
21242
|
}
|
|
21116
21243
|
return reply.send({
|
|
21117
21244
|
totalRuns,
|
|
21118
|
-
run: loadRunDetail(app, latestRun)
|
|
21245
|
+
run: loadRunDetail(app, latestRun),
|
|
21246
|
+
admission,
|
|
21247
|
+
manualAdmission
|
|
21119
21248
|
});
|
|
21120
21249
|
});
|
|
21250
|
+
app.get("/projects/:name/run-admission", async (request, reply) => {
|
|
21251
|
+
const selection = request.query.selection ?? RunTriggers.scheduled;
|
|
21252
|
+
if (selection !== RunTriggers.manual && selection !== RunTriggers.scheduled) {
|
|
21253
|
+
throw validationError('"selection" must be one of: manual, scheduled');
|
|
21254
|
+
}
|
|
21255
|
+
const project = resolveProject(app.db, request.params.name);
|
|
21256
|
+
return reply.send(runAdmissionState(app.db, {
|
|
21257
|
+
projectId: project.id,
|
|
21258
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21259
|
+
runnableProviders: opts.getRunnableProviderNames?.(),
|
|
21260
|
+
selection
|
|
21261
|
+
}));
|
|
21262
|
+
});
|
|
21121
21263
|
app.get("/runs", async (request, reply) => {
|
|
21122
21264
|
const limit = parseListLimit(request.query.limit, 500, 5e3);
|
|
21123
21265
|
const since = parseListSince(request.query.since);
|
|
@@ -21210,6 +21352,7 @@ async function runRoutes(app, opts) {
|
|
|
21210
21352
|
for (const entry of eligible) {
|
|
21211
21353
|
try {
|
|
21212
21354
|
assertMeasurementRunStampable(app.db, {
|
|
21355
|
+
createdAt: now,
|
|
21213
21356
|
projectId: entry.project.id,
|
|
21214
21357
|
kind,
|
|
21215
21358
|
trigger: "manual",
|
|
@@ -21218,7 +21361,8 @@ async function runRoutes(app, opts) {
|
|
|
21218
21361
|
runnableProviders: runnableProviderNames,
|
|
21219
21362
|
providerModels: opts.getEffectiveProviderModels?.(),
|
|
21220
21363
|
dispatchMode,
|
|
21221
|
-
batchEligibleProviders
|
|
21364
|
+
batchEligibleProviders,
|
|
21365
|
+
force: force ?? false
|
|
21222
21366
|
});
|
|
21223
21367
|
dispatchable.push(entry);
|
|
21224
21368
|
} catch (error) {
|
|
@@ -21434,6 +21578,8 @@ function formatRun(row) {
|
|
|
21434
21578
|
// Only plan runs can batch, so only they carry the frozen modes.
|
|
21435
21579
|
dispatchModes: row.providerDispatchModes ?? {}
|
|
21436
21580
|
} : {},
|
|
21581
|
+
// Only on a run that skipped a provider, so other runs read as before.
|
|
21582
|
+
...row.skippedProviders && Object.keys(row.skippedProviders).length > 0 ? { skippedProviders: row.skippedProviders } : {},
|
|
21437
21583
|
createdAt: row.createdAt
|
|
21438
21584
|
};
|
|
21439
21585
|
}
|
|
@@ -36404,7 +36550,14 @@ async function compositeRoutes(app, options) {
|
|
|
36404
36550
|
const previousVisRunGroup = visRunGroups[1] ?? [];
|
|
36405
36551
|
const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
|
|
36406
36552
|
const latestRunRow = allRuns[0] ?? null;
|
|
36407
|
-
const
|
|
36553
|
+
const admissionParams = {
|
|
36554
|
+
projectId: project.id,
|
|
36555
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36556
|
+
runnableProviders: options.getRunnableProviderNames?.()
|
|
36557
|
+
};
|
|
36558
|
+
const admission = runAdmissionState(app.db, admissionParams);
|
|
36559
|
+
const manualAdmission = runAdmissionState(app.db, { ...admissionParams, selection: RunTriggers.manual });
|
|
36560
|
+
const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow), admission, manualAdmission } : { totalRuns: 0, run: null, admission, manualAdmission };
|
|
36408
36561
|
const healthRow = app.db.select().from(healthSnapshots).where(eq50(healthSnapshots.projectId, project.id)).orderBy(desc24(healthSnapshots.createdAt)).limit(1).get();
|
|
36409
36562
|
const health = healthRow ? mapHealthRow2(healthRow) : null;
|
|
36410
36563
|
const insightRows = app.db.select().from(insights).where(eq50(insights.projectId, project.id)).orderBy(desc24(insights.createdAt)).all();
|
|
@@ -36644,6 +36797,7 @@ function summarizeRun(run) {
|
|
|
36644
36797
|
startedAt: run.startedAt,
|
|
36645
36798
|
finishedAt: run.finishedAt,
|
|
36646
36799
|
error: parseRunError(run.error),
|
|
36800
|
+
...run.skippedProviders && Object.keys(run.skippedProviders).length > 0 ? { skippedProviders: run.skippedProviders } : {},
|
|
36647
36801
|
createdAt: run.createdAt
|
|
36648
36802
|
};
|
|
36649
36803
|
}
|
|
@@ -38068,6 +38222,7 @@ var SCHEMA_TABLE = {
|
|
|
38068
38222
|
IntegrationSettingsSummaryDto: integrationSettingsSummaryDtoSchema,
|
|
38069
38223
|
KeywordDto: keywordDtoSchema,
|
|
38070
38224
|
LatestProjectRunDto: latestProjectRunDtoSchema,
|
|
38225
|
+
RunAdmissionDto: runAdmissionDtoSchema,
|
|
38071
38226
|
LocationContext: locationContextSchema,
|
|
38072
38227
|
MeasurementDiscoveryRequest: measurementDiscoveryRequestSchema,
|
|
38073
38228
|
MeasurementDiscoveryResponse: measurementDiscoveryResponseSchema,
|
|
@@ -40544,7 +40699,7 @@ var routeCatalog = [
|
|
|
40544
40699
|
dispatchMode: dispatchModeRequestSchema,
|
|
40545
40700
|
force: {
|
|
40546
40701
|
type: "boolean",
|
|
40547
|
-
description: "
|
|
40702
|
+
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
40703
|
}
|
|
40549
40704
|
}
|
|
40550
40705
|
}
|
|
@@ -40552,12 +40707,15 @@ var routeCatalog = [
|
|
|
40552
40707
|
}
|
|
40553
40708
|
},
|
|
40554
40709
|
responses: {
|
|
40555
|
-
201: jsonResponse(
|
|
40710
|
+
201: jsonResponse(
|
|
40711
|
+
`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.`,
|
|
40712
|
+
"RunDto"
|
|
40713
|
+
),
|
|
40556
40714
|
400: errorResponse(
|
|
40557
40715
|
'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
40716
|
),
|
|
40559
40717
|
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
|
|
40718
|
+
`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
40719
|
),
|
|
40562
40720
|
409: errorResponse("Run already in progress."),
|
|
40563
40721
|
503: errorResponse("No runnable answer provider is configured.")
|
|
@@ -40577,12 +40735,33 @@ var routeCatalog = [
|
|
|
40577
40735
|
method: "get",
|
|
40578
40736
|
path: "/api/v1/projects/{name}/runs/latest",
|
|
40579
40737
|
summary: "Get the latest project run",
|
|
40738
|
+
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
40739
|
tags: ["runs"],
|
|
40581
40740
|
parameters: [nameParameter],
|
|
40582
40741
|
responses: {
|
|
40583
40742
|
200: jsonResponse("Latest run returned.", "LatestProjectRunDto")
|
|
40584
40743
|
}
|
|
40585
40744
|
},
|
|
40745
|
+
{
|
|
40746
|
+
method: "get",
|
|
40747
|
+
path: "/api/v1/projects/{name}/run-admission",
|
|
40748
|
+
summary: "Get whether the next sweep would be admitted",
|
|
40749
|
+
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\`.`,
|
|
40750
|
+
tags: ["runs"],
|
|
40751
|
+
parameters: [
|
|
40752
|
+
nameParameter,
|
|
40753
|
+
{
|
|
40754
|
+
name: "selection",
|
|
40755
|
+
in: "query",
|
|
40756
|
+
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.",
|
|
40757
|
+
schema: { type: "string", enum: ["manual", "scheduled"], default: "scheduled" }
|
|
40758
|
+
}
|
|
40759
|
+
],
|
|
40760
|
+
responses: {
|
|
40761
|
+
200: jsonResponse("Run admission returned.", "RunAdmissionDto"),
|
|
40762
|
+
400: errorResponse("Invalid admission selection.")
|
|
40763
|
+
}
|
|
40764
|
+
},
|
|
40586
40765
|
{
|
|
40587
40766
|
method: "get",
|
|
40588
40767
|
path: "/api/v1/runs",
|
|
@@ -40617,7 +40796,7 @@ var routeCatalog = [
|
|
|
40617
40796
|
dispatchMode: dispatchModeRequestSchema,
|
|
40618
40797
|
force: {
|
|
40619
40798
|
type: "boolean",
|
|
40620
|
-
description: "
|
|
40799
|
+
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
40800
|
}
|
|
40622
40801
|
}
|
|
40623
40802
|
}
|
|
@@ -89646,7 +89825,7 @@ async function apiRoutes(app, opts) {
|
|
|
89646
89825
|
await api.register(citationRoutes);
|
|
89647
89826
|
await api.register(visibilityStatsRoutes);
|
|
89648
89827
|
await api.register(resultsExportRoutes);
|
|
89649
|
-
await api.register(compositeRoutes, { sentiment: opts.sentiment });
|
|
89828
|
+
await api.register(compositeRoutes, { sentiment: opts.sentiment, getRunnableProviderNames: opts.getRunnableProviderNames });
|
|
89650
89829
|
await api.register(contentRoutes, {
|
|
89651
89830
|
explainContentRecommendation: opts.explainContentRecommendation,
|
|
89652
89831
|
briefContentRecommendation: opts.briefContentRecommendation,
|