@ainyc/canonry 5.2.0 → 5.3.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/README.md +3 -3
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -0
- package/assets/assets/{AuditHistoryPanel-D0H5TtxC.js → AuditHistoryPanel-D5cOFMKE.js} +1 -1
- package/assets/assets/{BacklinksPage-DRihwG5O.js → BacklinksPage-BiOj8ztV.js} +1 -1
- package/assets/assets/{HistoryPage-BQ2nEXly.js → HistoryPage-DDBCwooU.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-CMoU2yA_.js +1 -0
- package/assets/assets/ProjectPage-CrFybBls.js +8 -0
- package/assets/assets/{RunRow-Bo_Adiy0.js → RunRow-BLBAwIot.js} +1 -1
- package/assets/assets/{RunsPage-CDkbzjuo.js → RunsPage-BVgBbE1M.js} +1 -1
- package/assets/assets/{SettingsPage-CF4i_EzR.js → SettingsPage-DYtN-1_-.js} +1 -1
- package/assets/assets/{SiteHealthSection-BrzBV7dt.js → SiteHealthSection-ElUwcAS4.js} +3 -3
- package/assets/assets/{TrafficPage-BHSz1N4P.js → TrafficPage-D6QT_2hU.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DzHLwhpy.js → TrafficSourceDetailPage-BnLzF82w.js} +1 -1
- package/assets/assets/{extract-error-message-WHM24Sai.js → extract-error-message-DIzjGZxl.js} +1 -1
- package/assets/assets/{index-5W4ZZnwg.js → index-CoQVOsWw.js} +16 -16
- package/assets/assets/{react-sigma_core.esm.min-BB2Lcq4W.js → react-sigma_core.esm.min-DTbIcdn4.js} +1 -1
- package/assets/assets/v2-overview-adapter-B_N4ilQV.js +1 -0
- package/assets/assets/{vendor-lucide-D_WBzb76.js → vendor-lucide-nVpYViBH.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-CW2KPN3L.js → chunk-HOOU4NS2.js} +194 -56
- package/dist/{chunk-5VONCLVQ.js → chunk-MNUFMEAX.js} +1 -1
- package/dist/{chunk-YQNKULAD.js → chunk-QAGZWYPI.js} +815 -366
- package/dist/cli.js +6 -6
- package/dist/{demo-server-BIPT53EY.js → demo-server-Y7YWLPU7.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-SJO5E7WN.js → intelligence-service-PR6T6JKY.js} +1 -1
- package/package.json +12 -12
- package/assets/assets/MeasurementPropertyPage-Bp6EURzJ.js +0 -1
- package/assets/assets/ProjectPage-Cd03Phwx.js +0 -8
- package/assets/assets/v2-overview-adapter-ebrNjcUG.js +0 -1
|
@@ -709,7 +709,7 @@ import {
|
|
|
709
709
|
} from "./chunk-2U5IW3G5.js";
|
|
710
710
|
|
|
711
711
|
// src/intelligence-service.ts
|
|
712
|
-
import { eq as
|
|
712
|
+
import { eq as eq68, desc as desc30, asc as asc12, and as and58, ne as ne11, or as or16, inArray as inArray26, gte as gte18, lte as lte15, isNull as isNull12, sql as sql27, exists } from "drizzle-orm";
|
|
713
713
|
|
|
714
714
|
// ../db/src/client.ts
|
|
715
715
|
import { mkdirSync } from "fs";
|
|
@@ -825,6 +825,7 @@ __export(schema_exports, {
|
|
|
825
825
|
siteCrawlPages: () => siteCrawlPages,
|
|
826
826
|
siteCrawlRunRequests: () => siteCrawlRunRequests,
|
|
827
827
|
siteCrawlSnapshots: () => siteCrawlSnapshots,
|
|
828
|
+
siteLivenessState: () => siteLivenessState,
|
|
828
829
|
trafficEventReceipts: () => trafficEventReceipts,
|
|
829
830
|
trafficSources: () => trafficSources,
|
|
830
831
|
usageCounters: () => usageCounters,
|
|
@@ -1387,6 +1388,18 @@ var doctorHealthState = sqliteTable("doctor_health_state", {
|
|
|
1387
1388
|
/** When we last emitted for this (status, code). Null until first emit. */
|
|
1388
1389
|
notifiedAt: text("notified_at")
|
|
1389
1390
|
});
|
|
1391
|
+
var siteLivenessState = sqliteTable("site_liveness_state", {
|
|
1392
|
+
projectId: text("project_id").primaryKey(),
|
|
1393
|
+
/** Last graded probe: ok | fail. Skipped probes are not recorded. */
|
|
1394
|
+
status: text("status").notNull(),
|
|
1395
|
+
code: text("code").notNull(),
|
|
1396
|
+
summary: text("summary").notNull(),
|
|
1397
|
+
/** Failed passes in a row. Paging waits for two so one blip never alerts. */
|
|
1398
|
+
consecutiveFailures: integer("consecutive_failures").notNull().default(0),
|
|
1399
|
+
checkedAt: text("checked_at").notNull(),
|
|
1400
|
+
/** Set when an outage was actually paged; cleared on recovery. */
|
|
1401
|
+
notifiedAt: text("notified_at")
|
|
1402
|
+
});
|
|
1390
1403
|
var notifications = sqliteTable("notifications", {
|
|
1391
1404
|
id: text("id").primaryKey(),
|
|
1392
1405
|
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
@@ -7291,6 +7304,24 @@ var MIGRATION_VERSIONS = [
|
|
|
7291
7304
|
`CREATE INDEX IF NOT EXISTS idx_runtime_logs_module_level_ts ON runtime_logs(module, level, ts, sequence)`,
|
|
7292
7305
|
`CREATE INDEX IF NOT EXISTS idx_runtime_logs_ts ON runtime_logs(ts, sequence)`
|
|
7293
7306
|
]
|
|
7307
|
+
},
|
|
7308
|
+
{
|
|
7309
|
+
// Liveness gets its own row per project. See siteLivenessState in
|
|
7310
|
+
// schema.ts: a fast "site is up" pass must never overwrite the 6h doctor
|
|
7311
|
+
// state, or it would clear an unrelated outage and send a false recovery.
|
|
7312
|
+
version: 156,
|
|
7313
|
+
name: "site-liveness-state",
|
|
7314
|
+
statements: [
|
|
7315
|
+
`CREATE TABLE IF NOT EXISTS site_liveness_state (
|
|
7316
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
7317
|
+
status TEXT NOT NULL,
|
|
7318
|
+
code TEXT NOT NULL,
|
|
7319
|
+
summary TEXT NOT NULL,
|
|
7320
|
+
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
7321
|
+
checked_at TEXT NOT NULL,
|
|
7322
|
+
notified_at TEXT
|
|
7323
|
+
)`
|
|
7324
|
+
]
|
|
7294
7325
|
}
|
|
7295
7326
|
];
|
|
7296
7327
|
function addRunsMeasurementPlanVersionForeignKey(tx) {
|
|
@@ -7757,8 +7788,8 @@ function projectContext(event, nested) {
|
|
|
7757
7788
|
const value = event[key] ?? nested[key];
|
|
7758
7789
|
if (typeof value === "boolean") context[key] = value;
|
|
7759
7790
|
}
|
|
7760
|
-
const
|
|
7761
|
-
if (
|
|
7791
|
+
const errorCode2 = optionalString(event.errorCode ?? nested.errorCode, 128);
|
|
7792
|
+
if (errorCode2 !== void 0) context.errorCode = errorCode2;
|
|
7762
7793
|
return context;
|
|
7763
7794
|
}
|
|
7764
7795
|
var ID_FIELDS = [
|
|
@@ -43383,9 +43414,10 @@ function healthView(payload) {
|
|
|
43383
43414
|
value: bulletList(others.map((check2) => `${check2.status} \u2014 ${check2.code}`))
|
|
43384
43415
|
});
|
|
43385
43416
|
}
|
|
43417
|
+
const website = health.code.startsWith("site.reachability.");
|
|
43386
43418
|
return {
|
|
43387
43419
|
severity,
|
|
43388
|
-
title: recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
|
|
43420
|
+
title: website ? recovered ? `${project.name} website is back up` : `${project.name} website is down` : recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
|
|
43389
43421
|
body: health.summary,
|
|
43390
43422
|
fields,
|
|
43391
43423
|
url: payload.dashboardUrl,
|
|
@@ -52229,14 +52261,14 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
|
|
|
52229
52261
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
52230
52262
|
const entityId = outcome.entity?.id ?? row.entityId;
|
|
52231
52263
|
const exhausted = outcome.state === AdsOperationStates.unknown && row.reconcileAttempts >= maxAttempts;
|
|
52232
|
-
const
|
|
52264
|
+
const errorCode2 = exhausted ? ADS_RECONCILIATION_QUARANTINED : outcome.errorCode ?? null;
|
|
52233
52265
|
const errorMessage = exhausted ? `Reconciliation stopped after ${maxAttempts} inconclusive attempts; manual remediation is required` : outcome.errorMessage ?? null;
|
|
52234
52266
|
app.db.transaction((tx) => {
|
|
52235
52267
|
const result = tx.update(adsOperations).set({
|
|
52236
52268
|
state: outcome.state,
|
|
52237
52269
|
entityId,
|
|
52238
52270
|
upstreamUpdatedAt: outcome.entity?.updatedAt ?? row.upstreamUpdatedAt,
|
|
52239
|
-
errorCode,
|
|
52271
|
+
errorCode: errorCode2,
|
|
52240
52272
|
errorMessage,
|
|
52241
52273
|
lastReconciledAt: now,
|
|
52242
52274
|
leaseOwner: null,
|
|
@@ -52251,7 +52283,7 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
|
|
|
52251
52283
|
if (result.changes > 0) {
|
|
52252
52284
|
writeAuditLog(tx, reconciliationAuditEntry(row, outcome.state, context, {
|
|
52253
52285
|
entityId,
|
|
52254
|
-
errorCode
|
|
52286
|
+
errorCode: errorCode2
|
|
52255
52287
|
}));
|
|
52256
52288
|
}
|
|
52257
52289
|
});
|
|
@@ -57516,11 +57548,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
57516
57548
|
const result = await bulkSetSeoMeta(connection, metaEntries);
|
|
57517
57549
|
const applied = result.results.filter((r) => r.status === "applied").length;
|
|
57518
57550
|
const manual = result.results.filter((r) => r.status === "manual").length;
|
|
57519
|
-
const
|
|
57551
|
+
const skipped3 = result.results.filter((r) => r.status === "skipped").length;
|
|
57520
57552
|
steps.push({
|
|
57521
57553
|
name: "set-meta",
|
|
57522
57554
|
status: "completed",
|
|
57523
|
-
summary: `${applied} applied, ${manual} manual-assist, ${
|
|
57555
|
+
summary: `${applied} applied, ${manual} manual-assist, ${skipped3} skipped`
|
|
57524
57556
|
});
|
|
57525
57557
|
}
|
|
57526
57558
|
} catch (err) {
|
|
@@ -57542,11 +57574,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
57542
57574
|
const result = await deploySchemaFromProfile(connection, profile);
|
|
57543
57575
|
const deployed = result.results.filter((r) => r.status === "deployed").length;
|
|
57544
57576
|
const stripped = result.results.filter((r) => r.status === "stripped").length;
|
|
57545
|
-
const
|
|
57577
|
+
const skipped3 = result.results.filter((r) => r.status === "skipped").length;
|
|
57546
57578
|
steps.push({
|
|
57547
57579
|
name: "schema-deploy",
|
|
57548
57580
|
status: "completed",
|
|
57549
|
-
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${
|
|
57581
|
+
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped3} skipped`
|
|
57550
57582
|
});
|
|
57551
57583
|
}
|
|
57552
57584
|
} catch (err) {
|
|
@@ -77300,7 +77332,7 @@ async function trafficRoutes(app, opts) {
|
|
|
77300
77332
|
startedAt,
|
|
77301
77333
|
createdAt: startedAt
|
|
77302
77334
|
}).run();
|
|
77303
|
-
const markFailed = (msg,
|
|
77335
|
+
const markFailed = (msg, errorCode2) => {
|
|
77304
77336
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
77305
77337
|
app.db.transaction((tx) => {
|
|
77306
77338
|
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq55(runs.id, runId)).run();
|
|
@@ -77320,7 +77352,7 @@ async function trafficRoutes(app, opts) {
|
|
|
77320
77352
|
aiUserFetchHits: 0,
|
|
77321
77353
|
aiReferralHits: 0,
|
|
77322
77354
|
durationMs: Date.now() - syncStartedAtMs,
|
|
77323
|
-
errorCode
|
|
77355
|
+
errorCode: errorCode2
|
|
77324
77356
|
});
|
|
77325
77357
|
} catch {
|
|
77326
77358
|
}
|
|
@@ -79472,8 +79504,417 @@ var ga4ConnectionCheck = {
|
|
|
79472
79504
|
};
|
|
79473
79505
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
79474
79506
|
|
|
79507
|
+
// ../api-routes/src/doctor/checks/data-freshness.ts
|
|
79508
|
+
import { and as and51, desc as desc26, eq as eq59, inArray as inArray22 } from "drizzle-orm";
|
|
79509
|
+
var GA_DATA_AGING_DAYS = 3;
|
|
79510
|
+
var GA_DATA_STALE_DAYS = 5;
|
|
79511
|
+
var GSC_DATA_AGING_DAYS = 5;
|
|
79512
|
+
var GSC_DATA_STALE_DAYS = 7;
|
|
79513
|
+
var SYNC_IDLE_DAYS = 3;
|
|
79514
|
+
var GSC_REPORTING_TIME_ZONE2 = "America/Los_Angeles";
|
|
79515
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
79516
|
+
var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
79517
|
+
function utcFromIsoDate(date) {
|
|
79518
|
+
const match = ISO_DATE.exec(date);
|
|
79519
|
+
if (!match) return null;
|
|
79520
|
+
return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
79521
|
+
}
|
|
79522
|
+
function daysBetweenIsoDates(from, to) {
|
|
79523
|
+
const start = utcFromIsoDate(from);
|
|
79524
|
+
const end = utcFromIsoDate(to);
|
|
79525
|
+
if (start === null || end === null) return null;
|
|
79526
|
+
return Math.floor((end - start) / DAY_MS2);
|
|
79527
|
+
}
|
|
79528
|
+
function daysSinceDate(date, now = /* @__PURE__ */ new Date(), timeZone) {
|
|
79529
|
+
const today = timeZone ? formatIsoDateInTimeZone(now.toISOString(), timeZone) : `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}`;
|
|
79530
|
+
return daysBetweenIsoDates(date, today);
|
|
79531
|
+
}
|
|
79532
|
+
function skipped(code, summary2) {
|
|
79533
|
+
return { status: CheckStatuses.skipped, code, summary: summary2, remediation: null };
|
|
79534
|
+
}
|
|
79535
|
+
function lastSyncAgeDays(ctx, kinds) {
|
|
79536
|
+
const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and51(eq59(runs.projectId, ctx.project.id), inArray22(runs.kind, kinds), eq59(runs.status, "completed"))).orderBy(desc26(runs.createdAt)).limit(1).get();
|
|
79537
|
+
if (!row?.createdAt) return null;
|
|
79538
|
+
const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
|
|
79539
|
+
return Number.isFinite(age) ? age : null;
|
|
79540
|
+
}
|
|
79541
|
+
function grade(ctx, spec, newestDate) {
|
|
79542
|
+
const projectName = ctx.project.name;
|
|
79543
|
+
const code = (suffix) => `${spec.source}.data.${suffix}`;
|
|
79544
|
+
const sync = spec.syncCommand(projectName);
|
|
79545
|
+
const syncAge = lastSyncAgeDays(ctx, spec.syncKinds);
|
|
79546
|
+
const syncIdle = syncAge === null || syncAge >= SYNC_IDLE_DAYS;
|
|
79547
|
+
if (!newestDate) {
|
|
79548
|
+
return {
|
|
79549
|
+
status: CheckStatuses.warn,
|
|
79550
|
+
code: code("never-synced"),
|
|
79551
|
+
summary: `${spec.label} is connected but no daily data has been stored yet.`,
|
|
79552
|
+
remediation: `Run \`${sync}\`, or set a data-refresh schedule so it keeps itself current.`,
|
|
79553
|
+
details: { lastSyncAgeDays: syncAge }
|
|
79554
|
+
};
|
|
79555
|
+
}
|
|
79556
|
+
const age = daysSinceDate(newestDate, /* @__PURE__ */ new Date(), spec.timeZone);
|
|
79557
|
+
if (age === null) {
|
|
79558
|
+
return {
|
|
79559
|
+
status: CheckStatuses.warn,
|
|
79560
|
+
code: code("unreadable-date"),
|
|
79561
|
+
summary: `Newest stored ${spec.label} date "${newestDate}" is not a calendar date.`,
|
|
79562
|
+
remediation: null,
|
|
79563
|
+
details: { newestDate }
|
|
79564
|
+
};
|
|
79565
|
+
}
|
|
79566
|
+
const details = { newestDate, ageDays: age, agingDays: spec.agingDays, staleDays: spec.staleDays, lastSyncAgeDays: syncAge };
|
|
79567
|
+
if (age < spec.agingDays) {
|
|
79568
|
+
return {
|
|
79569
|
+
status: CheckStatuses.ok,
|
|
79570
|
+
code: code("fresh"),
|
|
79571
|
+
summary: `${spec.label} data is current through ${newestDate}.`,
|
|
79572
|
+
remediation: null,
|
|
79573
|
+
details
|
|
79574
|
+
};
|
|
79575
|
+
}
|
|
79576
|
+
if (syncIdle) {
|
|
79577
|
+
return {
|
|
79578
|
+
status: CheckStatuses.warn,
|
|
79579
|
+
code: code("not-syncing"),
|
|
79580
|
+
summary: `No ${spec.label} sync has completed in ${syncAge === null ? "any recorded run" : `${syncAge} days`}, and stored data ends ${newestDate}.`,
|
|
79581
|
+
remediation: `Run \`${sync}\`, or set a data-refresh schedule: \`canonry schedule set ${projectName} --kind data-refresh --preset daily\`.`,
|
|
79582
|
+
details
|
|
79583
|
+
};
|
|
79584
|
+
}
|
|
79585
|
+
const remediation = `${spec.quietExplanation} ${spec.brokenExplanation}`;
|
|
79586
|
+
return {
|
|
79587
|
+
status: CheckStatuses.warn,
|
|
79588
|
+
code: code(age >= spec.staleDays ? "stale" : "aging"),
|
|
79589
|
+
summary: `${spec.label} syncs are running but have stored nothing newer than ${newestDate} (${age} days).`,
|
|
79590
|
+
remediation,
|
|
79591
|
+
details
|
|
79592
|
+
};
|
|
79593
|
+
}
|
|
79594
|
+
var GA_SPEC = {
|
|
79595
|
+
source: "ga",
|
|
79596
|
+
label: "GA4",
|
|
79597
|
+
agingDays: GA_DATA_AGING_DAYS,
|
|
79598
|
+
staleDays: GA_DATA_STALE_DAYS,
|
|
79599
|
+
syncKinds: ["ga-sync"],
|
|
79600
|
+
syncCommand: (name) => `canonry ga sync ${name}`,
|
|
79601
|
+
quietExplanation: "GA4 omits days with no sessions, so a genuinely quiet period clears itself on the next visit.",
|
|
79602
|
+
brokenExplanation: "If the site did have visitors, check that the GA4 tag or its Tag Manager container is still on the live site."
|
|
79603
|
+
};
|
|
79604
|
+
var GSC_SPEC = {
|
|
79605
|
+
source: "gsc",
|
|
79606
|
+
label: "Search Console",
|
|
79607
|
+
agingDays: GSC_DATA_AGING_DAYS,
|
|
79608
|
+
staleDays: GSC_DATA_STALE_DAYS,
|
|
79609
|
+
timeZone: GSC_REPORTING_TIME_ZONE2,
|
|
79610
|
+
syncKinds: ["gsc-sync"],
|
|
79611
|
+
syncCommand: (name) => `canonry google sync ${name}`,
|
|
79612
|
+
quietExplanation: "Search Console omits days with no impressions and reports two to three days behind, so a quiet period clears itself.",
|
|
79613
|
+
brokenExplanation: "If the site did have impressions, check that the property still lists this site and the connected account still has access."
|
|
79614
|
+
};
|
|
79615
|
+
function gaConnected(ctx) {
|
|
79616
|
+
const project = ctx.project;
|
|
79617
|
+
if (!ctx.ga4CredentialStore && !ctx.googleConnectionStore) return "store-unavailable";
|
|
79618
|
+
if (ctx.ga4CredentialStore?.getConnection(project.name)) return "connected";
|
|
79619
|
+
if (ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4")) return "connected";
|
|
79620
|
+
return "not-connected";
|
|
79621
|
+
}
|
|
79622
|
+
var gaRecentDataCheck = {
|
|
79623
|
+
id: "ga.data.recent-data",
|
|
79624
|
+
category: CheckCategories.integrations,
|
|
79625
|
+
scope: CheckScopes.project,
|
|
79626
|
+
title: "GA4 data still arriving",
|
|
79627
|
+
run: (ctx) => {
|
|
79628
|
+
if (!ctx.project) return skipped("ga.data.no-project", "Project context required.");
|
|
79629
|
+
const connection = gaConnected(ctx);
|
|
79630
|
+
if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
|
|
79631
|
+
if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
|
|
79632
|
+
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq59(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gaDailyTotals.date)).limit(1).get();
|
|
79633
|
+
return grade(ctx, GA_SPEC, newest?.date);
|
|
79634
|
+
}
|
|
79635
|
+
};
|
|
79636
|
+
var gscRecentDataCheck = {
|
|
79637
|
+
id: "gsc.data.recent-data",
|
|
79638
|
+
category: CheckCategories.integrations,
|
|
79639
|
+
scope: CheckScopes.project,
|
|
79640
|
+
title: "Search Console data still arriving",
|
|
79641
|
+
run: (ctx) => {
|
|
79642
|
+
if (!ctx.project) return skipped("gsc.data.no-project", "Project context required.");
|
|
79643
|
+
if (!ctx.googleConnectionStore) return skipped("gsc.data.store-unavailable", "No Google connection store configured for this deployment.");
|
|
79644
|
+
if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
|
|
79645
|
+
return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
|
|
79646
|
+
}
|
|
79647
|
+
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq59(gscDataWatermarks.projectId, ctx.project.id)).get();
|
|
79648
|
+
if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
|
|
79649
|
+
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq59(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gscDailyTotals.date)).limit(1).get();
|
|
79650
|
+
return grade(ctx, GSC_SPEC, newest?.date);
|
|
79651
|
+
}
|
|
79652
|
+
};
|
|
79653
|
+
var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
|
|
79654
|
+
|
|
79655
|
+
// ../api-routes/src/site-reachability.ts
|
|
79656
|
+
import dns3 from "dns/promises";
|
|
79657
|
+
import http3 from "http";
|
|
79658
|
+
import https3 from "https";
|
|
79659
|
+
import net3 from "net";
|
|
79660
|
+
var SITE_REACHABILITY_USER_AGENT = "Canonry site-liveness (+https://canonry.ai)";
|
|
79661
|
+
var DEFAULT_SITE_MAX_REDIRECTS = 10;
|
|
79662
|
+
var DEFAULT_TIMEOUT_MS5 = 1e4;
|
|
79663
|
+
var DEFAULT_RETRY_DELAY_MS = 2e3;
|
|
79664
|
+
var DNS_TIMEOUT_MS = 5e3;
|
|
79665
|
+
var MAX_ADDRESSES_PER_HOP = 4;
|
|
79666
|
+
var LENIENT_RETRY_CODES = /* @__PURE__ */ new Set([
|
|
79667
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
79668
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
79669
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
79670
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
79671
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
79672
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
79673
|
+
"HPE_INVALID_HEADER_TOKEN",
|
|
79674
|
+
"HPE_UNEXPECTED_CONTENT_LENGTH",
|
|
79675
|
+
"HPE_INVALID_CONSTANT"
|
|
79676
|
+
]);
|
|
79677
|
+
var NO_SUCH_HOST_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "ENODATA"]);
|
|
79678
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
79679
|
+
function errorCode(err) {
|
|
79680
|
+
const code = err?.code;
|
|
79681
|
+
return typeof code === "string" ? code : "";
|
|
79682
|
+
}
|
|
79683
|
+
async function lookupSiteAddresses(hostname, timeoutMs = DNS_TIMEOUT_MS) {
|
|
79684
|
+
const literalFamily = net3.isIPv6(hostname) ? 6 : net3.isIPv4(hostname) ? 4 : null;
|
|
79685
|
+
if (literalFamily) return { kind: "addresses", addresses: [{ address: hostname, family: literalFamily }] };
|
|
79686
|
+
let timer;
|
|
79687
|
+
const deadline = new Promise((resolve) => {
|
|
79688
|
+
timer = setTimeout(() => resolve("timeout"), timeoutMs);
|
|
79689
|
+
});
|
|
79690
|
+
try {
|
|
79691
|
+
const settled = await Promise.race([
|
|
79692
|
+
Promise.allSettled([dns3.resolve4(hostname), dns3.resolve6(hostname)]),
|
|
79693
|
+
deadline
|
|
79694
|
+
]);
|
|
79695
|
+
if (settled === "timeout") return { kind: "resolver-error", reason: `DNS did not answer within ${Math.round(timeoutMs / 1e3)}s` };
|
|
79696
|
+
const [ipv4, ipv6] = settled;
|
|
79697
|
+
const found = /* @__PURE__ */ new Map();
|
|
79698
|
+
if (ipv4.status === "fulfilled") for (const address of ipv4.value) found.set(`4:${address}`, { address, family: 4 });
|
|
79699
|
+
if (ipv6.status === "fulfilled") for (const address of ipv6.value) found.set(`6:${address}`, { address, family: 6 });
|
|
79700
|
+
if (found.size > 0) return { kind: "addresses", addresses: [...found.values()] };
|
|
79701
|
+
const codes = [ipv4, ipv6].map((r) => r.status === "rejected" ? errorCode(r.reason) : "");
|
|
79702
|
+
if (codes.every((code) => NO_SUCH_HOST_CODES.has(code))) {
|
|
79703
|
+
return { kind: "no-such-host", reason: `${hostname} has no DNS address (${codes[0]})` };
|
|
79704
|
+
}
|
|
79705
|
+
const failing = codes.find((code) => code && !NO_SUCH_HOST_CODES.has(code)) ?? "unknown resolver error";
|
|
79706
|
+
return { kind: "resolver-error", reason: `DNS lookup for ${hostname} failed (${failing})` };
|
|
79707
|
+
} finally {
|
|
79708
|
+
if (timer) clearTimeout(timer);
|
|
79709
|
+
}
|
|
79710
|
+
}
|
|
79711
|
+
async function requestPinnedStatus(target, timeoutMs, options = {}) {
|
|
79712
|
+
const secure = target.url.protocol === "https:";
|
|
79713
|
+
const port = target.url.port ? Number(target.url.port) : secure ? 443 : 80;
|
|
79714
|
+
const requestOptions = {
|
|
79715
|
+
hostname: target.address,
|
|
79716
|
+
family: target.family,
|
|
79717
|
+
port,
|
|
79718
|
+
method: "GET",
|
|
79719
|
+
path: `${target.url.pathname}${target.url.search}`,
|
|
79720
|
+
headers: { Host: target.url.host, "User-Agent": SITE_REACHABILITY_USER_AGENT, Accept: "text/html,*/*;q=0.8" },
|
|
79721
|
+
insecureHTTPParser: options.lenient === true
|
|
79722
|
+
};
|
|
79723
|
+
if (secure) {
|
|
79724
|
+
requestOptions.servername = target.url.hostname.replace(/^\[|\]$/g, "");
|
|
79725
|
+
if (options.lenient) requestOptions.rejectUnauthorized = false;
|
|
79726
|
+
}
|
|
79727
|
+
return await new Promise((resolve, reject) => {
|
|
79728
|
+
let settled = false;
|
|
79729
|
+
const request = (secure ? https3.request : http3.request)(requestOptions, (response) => {
|
|
79730
|
+
if (settled) {
|
|
79731
|
+
response.destroy();
|
|
79732
|
+
return;
|
|
79733
|
+
}
|
|
79734
|
+
settled = true;
|
|
79735
|
+
clearTimeout(deadline);
|
|
79736
|
+
const location = typeof response.headers.location === "string" ? response.headers.location : null;
|
|
79737
|
+
resolve({ status: response.statusCode ?? 0, location });
|
|
79738
|
+
response.destroy();
|
|
79739
|
+
request.destroy();
|
|
79740
|
+
});
|
|
79741
|
+
const deadline = setTimeout(() => {
|
|
79742
|
+
if (settled) return;
|
|
79743
|
+
settled = true;
|
|
79744
|
+
request.destroy();
|
|
79745
|
+
reject(new Error(`no response within ${Math.round(timeoutMs / 1e3)}s`));
|
|
79746
|
+
}, timeoutMs);
|
|
79747
|
+
request.on("error", (err) => {
|
|
79748
|
+
if (settled) return;
|
|
79749
|
+
settled = true;
|
|
79750
|
+
clearTimeout(deadline);
|
|
79751
|
+
reject(err);
|
|
79752
|
+
});
|
|
79753
|
+
request.end();
|
|
79754
|
+
});
|
|
79755
|
+
}
|
|
79756
|
+
async function probeSiteReachability(url, options = {}) {
|
|
79757
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS5;
|
|
79758
|
+
const maxRedirects = options.maxRedirects ?? DEFAULT_SITE_MAX_REDIRECTS;
|
|
79759
|
+
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
79760
|
+
const transport = options.transport ?? ((target, ms, opts) => requestPinnedStatus(target, ms, opts));
|
|
79761
|
+
const resolveAddresses = options.resolveAddresses ?? ((hostname) => lookupSiteAddresses(hostname));
|
|
79762
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
79763
|
+
const started = Date.now();
|
|
79764
|
+
const approveHop = async (hopUrl) => {
|
|
79765
|
+
let hostname;
|
|
79766
|
+
try {
|
|
79767
|
+
hostname = new URL(hopUrl).hostname.replace(/^\[|\]$/g, "");
|
|
79768
|
+
} catch {
|
|
79769
|
+
return { kind: "refused", reason: `${hopUrl} is not a valid URL` };
|
|
79770
|
+
}
|
|
79771
|
+
const lookup = await resolveAddresses(hostname);
|
|
79772
|
+
if (lookup.kind !== "addresses") return lookup;
|
|
79773
|
+
const targets = [];
|
|
79774
|
+
let refusal = "";
|
|
79775
|
+
for (const address of lookup.addresses.slice(0, MAX_ADDRESSES_PER_HOP)) {
|
|
79776
|
+
const approved = await resolveMeasurementSitemapTarget(hopUrl, { resolveAddresses: async () => [address] });
|
|
79777
|
+
if (approved.ok) targets.push(approved.target);
|
|
79778
|
+
else if (!refusal) refusal = approved.message;
|
|
79779
|
+
}
|
|
79780
|
+
if (targets.length === 0) return { kind: "refused", reason: refusal || `${hostname} has no address this instance may dial` };
|
|
79781
|
+
return { kind: "targets", targets };
|
|
79782
|
+
};
|
|
79783
|
+
const attempt = async () => {
|
|
79784
|
+
let current = url;
|
|
79785
|
+
for (let hop = 0; ; hop += 1) {
|
|
79786
|
+
const approved = await approveHop(current);
|
|
79787
|
+
if (approved.kind === "no-such-host") return { kind: "down", finalUrl: current, httpStatus: null, reason: approved.reason };
|
|
79788
|
+
if (approved.kind === "resolver-error") return { kind: "unavailable", reason: approved.reason };
|
|
79789
|
+
if (approved.kind === "refused") return { kind: "unreachable", finalUrl: current, reason: approved.reason };
|
|
79790
|
+
let response = null;
|
|
79791
|
+
let lastError = "";
|
|
79792
|
+
for (const target of approved.targets) {
|
|
79793
|
+
try {
|
|
79794
|
+
response = await transport(target, timeoutMs);
|
|
79795
|
+
break;
|
|
79796
|
+
} catch (err) {
|
|
79797
|
+
lastError = describeError(err);
|
|
79798
|
+
if (LENIENT_RETRY_CODES.has(errorCode(err))) {
|
|
79799
|
+
try {
|
|
79800
|
+
const lenient = await transport(target, timeoutMs, { lenient: true });
|
|
79801
|
+
response = { ...lenient, lenient: `${errorCode(err)} (accepted leniently)` };
|
|
79802
|
+
break;
|
|
79803
|
+
} catch (retryErr) {
|
|
79804
|
+
lastError = describeError(retryErr);
|
|
79805
|
+
}
|
|
79806
|
+
}
|
|
79807
|
+
}
|
|
79808
|
+
}
|
|
79809
|
+
if (!response) return { kind: "down", finalUrl: current, httpStatus: null, reason: lastError || "no address answered" };
|
|
79810
|
+
if (REDIRECT_STATUSES.has(response.status) && response.location) {
|
|
79811
|
+
if (hop >= maxRedirects) {
|
|
79812
|
+
return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `more than ${maxRedirects} redirects` };
|
|
79813
|
+
}
|
|
79814
|
+
try {
|
|
79815
|
+
current = new URL(response.location, current).toString();
|
|
79816
|
+
} catch {
|
|
79817
|
+
return { kind: "down", finalUrl: current, httpStatus: response.status, reason: "redirect with an invalid Location header" };
|
|
79818
|
+
}
|
|
79819
|
+
continue;
|
|
79820
|
+
}
|
|
79821
|
+
if (response.status >= 500 || response.status === 0) {
|
|
79822
|
+
return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `HTTP ${response.status}` };
|
|
79823
|
+
}
|
|
79824
|
+
return { kind: "up", finalUrl: current, httpStatus: response.status, lenient: response.lenient };
|
|
79825
|
+
}
|
|
79826
|
+
};
|
|
79827
|
+
let attempts = 1;
|
|
79828
|
+
let outcome = await attempt();
|
|
79829
|
+
if (outcome.kind === "down" || outcome.kind === "unreachable") {
|
|
79830
|
+
await sleep(retryDelayMs);
|
|
79831
|
+
attempts = 2;
|
|
79832
|
+
outcome = await attempt();
|
|
79833
|
+
}
|
|
79834
|
+
const durationMs = Date.now() - started;
|
|
79835
|
+
if (outcome.kind === "unavailable") return { state: "unavailable", url, reason: outcome.reason };
|
|
79836
|
+
if (outcome.kind === "up") {
|
|
79837
|
+
return { state: "up", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, attempts, durationMs, ...outcome.lenient ? { lenient: outcome.lenient } : {} };
|
|
79838
|
+
}
|
|
79839
|
+
if (outcome.kind === "unreachable") return { state: "unreachable", url, finalUrl: outcome.finalUrl, reason: outcome.reason, attempts, durationMs };
|
|
79840
|
+
return { state: "down", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, reason: outcome.reason, attempts, durationMs };
|
|
79841
|
+
}
|
|
79842
|
+
|
|
79843
|
+
// ../api-routes/src/doctor/checks/site-reachability.ts
|
|
79844
|
+
var SITE_REACHABILITY_CHECK_ID = "site.reachability";
|
|
79845
|
+
function probeHostFromDomain(value) {
|
|
79846
|
+
const trimmed = (value ?? "").trim();
|
|
79847
|
+
if (!trimmed) return null;
|
|
79848
|
+
let hostname;
|
|
79849
|
+
try {
|
|
79850
|
+
hostname = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`).hostname;
|
|
79851
|
+
} catch {
|
|
79852
|
+
return null;
|
|
79853
|
+
}
|
|
79854
|
+
hostname = hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
|
79855
|
+
if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
|
|
79856
|
+
return hostname;
|
|
79857
|
+
}
|
|
79858
|
+
var skipped2 = (code, summary2) => ({ status: CheckStatuses.skipped, code, summary: summary2, remediation: null });
|
|
79859
|
+
var siteReachabilityCheck = {
|
|
79860
|
+
id: SITE_REACHABILITY_CHECK_ID,
|
|
79861
|
+
category: CheckCategories.integrations,
|
|
79862
|
+
scope: CheckScopes.project,
|
|
79863
|
+
title: "Website reachable",
|
|
79864
|
+
// Reaches the network, and one bad answer is not an outage: the liveness loop
|
|
79865
|
+
// names it explicitly and applies the two-pass debounce. An unfiltered
|
|
79866
|
+
// `canonry doctor --project` must not exit 1 on a single dropped packet.
|
|
79867
|
+
optIn: true,
|
|
79868
|
+
run: async (ctx) => {
|
|
79869
|
+
if (!ctx.project) return skipped2("site.reachability.no-project", "Project context required.");
|
|
79870
|
+
const host = probeHostFromDomain(ctx.project.canonicalDomain);
|
|
79871
|
+
if (!host) {
|
|
79872
|
+
return skipped2("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
|
|
79873
|
+
}
|
|
79874
|
+
const url = `https://${host}/`;
|
|
79875
|
+
const probe = ctx.probeSiteReachability ?? probeSiteReachability;
|
|
79876
|
+
const result = await probe(url);
|
|
79877
|
+
if (result.state === "unavailable") {
|
|
79878
|
+
return skipped2("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
|
|
79879
|
+
}
|
|
79880
|
+
if (result.state === "up") {
|
|
79881
|
+
return {
|
|
79882
|
+
status: CheckStatuses.ok,
|
|
79883
|
+
code: "site.reachability.up",
|
|
79884
|
+
summary: `${url} answered HTTP ${result.httpStatus} in ${result.durationMs} ms.`,
|
|
79885
|
+
remediation: null,
|
|
79886
|
+
details: {
|
|
79887
|
+
url,
|
|
79888
|
+
finalUrl: result.finalUrl,
|
|
79889
|
+
httpStatus: result.httpStatus,
|
|
79890
|
+
attempts: result.attempts,
|
|
79891
|
+
durationMs: result.durationMs,
|
|
79892
|
+
...result.lenient ? { tlsOrParserWarning: result.lenient } : {}
|
|
79893
|
+
}
|
|
79894
|
+
};
|
|
79895
|
+
}
|
|
79896
|
+
if (result.state === "unreachable") {
|
|
79897
|
+
return {
|
|
79898
|
+
status: CheckStatuses.fail,
|
|
79899
|
+
code: "site.reachability.refused-address",
|
|
79900
|
+
summary: `${url} resolves only to addresses that cannot serve visitors: ${result.reason}.`,
|
|
79901
|
+
remediation: "Check the domain registration and its DNS records. A suspended or parked domain resolves this way.",
|
|
79902
|
+
details: { url, finalUrl: result.finalUrl, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
|
|
79903
|
+
};
|
|
79904
|
+
}
|
|
79905
|
+
return {
|
|
79906
|
+
status: CheckStatuses.fail,
|
|
79907
|
+
code: "site.reachability.down",
|
|
79908
|
+
summary: `${url} is not responding: ${result.reason}.`,
|
|
79909
|
+
remediation: "Open the site in a browser and check with the host. Canonry keeps checking and sends health.recovered when it answers again.",
|
|
79910
|
+
details: { url, finalUrl: result.finalUrl, httpStatus: result.httpStatus, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
|
|
79911
|
+
};
|
|
79912
|
+
}
|
|
79913
|
+
};
|
|
79914
|
+
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
79915
|
+
|
|
79475
79916
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
79476
|
-
import { and as
|
|
79917
|
+
import { and as and52, eq as eq60 } from "drizzle-orm";
|
|
79477
79918
|
var RECENT_SYNC_WARN_DAYS2 = 7;
|
|
79478
79919
|
var RECENT_SYNC_FAIL_DAYS2 = 30;
|
|
79479
79920
|
function skippedNoProject3() {
|
|
@@ -79706,7 +80147,7 @@ var recentSyncCheck = {
|
|
|
79706
80147
|
title: "GBP recent sync",
|
|
79707
80148
|
run: (ctx) => {
|
|
79708
80149
|
if (!ctx.project) return skippedNoProject3();
|
|
79709
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
80150
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and52(eq60(gbpLocations.projectId, ctx.project.id), eq60(gbpLocations.selected, true))).all();
|
|
79710
80151
|
if (selected.length === 0) {
|
|
79711
80152
|
return {
|
|
79712
80153
|
status: CheckStatuses.skipped,
|
|
@@ -79766,7 +80207,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
79766
80207
|
);
|
|
79767
80208
|
|
|
79768
80209
|
// ../api-routes/src/doctor/checks/places.ts
|
|
79769
|
-
import { eq as
|
|
80210
|
+
import { eq as eq61 } from "drizzle-orm";
|
|
79770
80211
|
var apiKeyCheck = {
|
|
79771
80212
|
id: "gbp.places.api-key",
|
|
79772
80213
|
category: CheckCategories.auth,
|
|
@@ -79811,7 +80252,7 @@ var apiKeyCheck = {
|
|
|
79811
80252
|
details: { tier: cfg.tier }
|
|
79812
80253
|
};
|
|
79813
80254
|
}
|
|
79814
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
80255
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq61(gbpLocations.projectId, ctx.project.id)).all();
|
|
79815
80256
|
const selected = rows.filter((r) => r.selected);
|
|
79816
80257
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
79817
80258
|
const details = {
|
|
@@ -80326,7 +80767,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
80326
80767
|
];
|
|
80327
80768
|
|
|
80328
80769
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
80329
|
-
import { and as
|
|
80770
|
+
import { and as and53, eq as eq62, gte as gte15, inArray as inArray23, ne as ne10, sql as sql24 } from "drizzle-orm";
|
|
80330
80771
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
80331
80772
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
80332
80773
|
function isCloudflareDirectPush(source) {
|
|
@@ -80365,8 +80806,8 @@ function skippedNoProject5() {
|
|
|
80365
80806
|
function loadProbes(ctx) {
|
|
80366
80807
|
if (!ctx.project) return [];
|
|
80367
80808
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
80368
|
-
|
|
80369
|
-
|
|
80809
|
+
and53(
|
|
80810
|
+
eq62(trafficSources.projectId, ctx.project.id),
|
|
80370
80811
|
ne10(trafficSources.status, TrafficSourceStatuses.archived)
|
|
80371
80812
|
)
|
|
80372
80813
|
).all();
|
|
@@ -80472,27 +80913,27 @@ var recentDataCheck = {
|
|
|
80472
80913
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
80473
80914
|
const recentCrawlers = Number(
|
|
80474
80915
|
ctx.db.select({ total: sql24`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
80475
|
-
|
|
80476
|
-
|
|
80477
|
-
|
|
80916
|
+
and53(
|
|
80917
|
+
eq62(crawlerEventsHourly.projectId, ctx.project.id),
|
|
80918
|
+
inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
80478
80919
|
gte15(crawlerEventsHourly.tsHour, warnCutoff)
|
|
80479
80920
|
)
|
|
80480
80921
|
).get()?.total ?? 0
|
|
80481
80922
|
);
|
|
80482
80923
|
const recentReferrals = Number(
|
|
80483
80924
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
80484
|
-
|
|
80485
|
-
|
|
80486
|
-
|
|
80925
|
+
and53(
|
|
80926
|
+
eq62(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
80927
|
+
inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
80487
80928
|
gte15(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
80488
80929
|
)
|
|
80489
80930
|
).get()?.total ?? 0
|
|
80490
80931
|
);
|
|
80491
80932
|
const recentUserFetches = Number(
|
|
80492
80933
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
80493
|
-
|
|
80494
|
-
|
|
80495
|
-
|
|
80934
|
+
and53(
|
|
80935
|
+
eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
80936
|
+
inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
80496
80937
|
gte15(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
80497
80938
|
)
|
|
80498
80939
|
).get()?.total ?? 0
|
|
@@ -80516,27 +80957,27 @@ var recentDataCheck = {
|
|
|
80516
80957
|
}
|
|
80517
80958
|
const olderCrawlers = Number(
|
|
80518
80959
|
ctx.db.select({ total: sql24`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
80519
|
-
|
|
80520
|
-
|
|
80521
|
-
|
|
80960
|
+
and53(
|
|
80961
|
+
eq62(crawlerEventsHourly.projectId, ctx.project.id),
|
|
80962
|
+
inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
80522
80963
|
gte15(crawlerEventsHourly.tsHour, failCutoff)
|
|
80523
80964
|
)
|
|
80524
80965
|
).get()?.total ?? 0
|
|
80525
80966
|
);
|
|
80526
80967
|
const olderReferrals = Number(
|
|
80527
80968
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
80528
|
-
|
|
80529
|
-
|
|
80530
|
-
|
|
80969
|
+
and53(
|
|
80970
|
+
eq62(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
80971
|
+
inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
80531
80972
|
gte15(aiReferralEventsHourly.tsHour, failCutoff)
|
|
80532
80973
|
)
|
|
80533
80974
|
).get()?.total ?? 0
|
|
80534
80975
|
);
|
|
80535
80976
|
const olderUserFetches = Number(
|
|
80536
80977
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
80537
|
-
|
|
80538
|
-
|
|
80539
|
-
|
|
80978
|
+
and53(
|
|
80979
|
+
eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
80980
|
+
inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
80540
80981
|
gte15(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
80541
80982
|
)
|
|
80542
80983
|
).get()?.total ?? 0
|
|
@@ -80601,7 +81042,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
|
|
|
80601
81042
|
function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
80602
81043
|
const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
|
|
80603
81044
|
const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
|
|
80604
|
-
const
|
|
81045
|
+
const skipped3 = results.filter((r) => r.output.status === CheckStatuses.skipped);
|
|
80605
81046
|
const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
|
|
80606
81047
|
const detail = {
|
|
80607
81048
|
sources: results.map((r) => ({
|
|
@@ -80635,7 +81076,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
80635
81076
|
return {
|
|
80636
81077
|
status: CheckStatuses.ok,
|
|
80637
81078
|
code: `traffic.${fallbackId}.ok`,
|
|
80638
|
-
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${
|
|
81079
|
+
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped3.length > 0 ? ` (${skipped3.length} skipped)` : ""}.`,
|
|
80639
81080
|
details: detail
|
|
80640
81081
|
};
|
|
80641
81082
|
}
|
|
@@ -80643,7 +81084,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
80643
81084
|
status: CheckStatuses.skipped,
|
|
80644
81085
|
code: `traffic.${fallbackId}.all-skipped`,
|
|
80645
81086
|
summary: `No source-type validator was available for any of the ${results.length} connected source(s).`,
|
|
80646
|
-
remediation:
|
|
81087
|
+
remediation: skipped3.find((result) => result.output.remediation)?.output.remediation,
|
|
80647
81088
|
details: detail
|
|
80648
81089
|
};
|
|
80649
81090
|
}
|
|
@@ -81069,7 +81510,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
|
|
|
81069
81510
|
];
|
|
81070
81511
|
var FRESH_SNAPSHOT_WARN_DAYS = 7;
|
|
81071
81512
|
var FRESH_SNAPSHOT_FAIL_DAYS = 30;
|
|
81072
|
-
var
|
|
81513
|
+
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
81073
81514
|
function unavailableStatus() {
|
|
81074
81515
|
return {
|
|
81075
81516
|
status: CheckStatuses.skipped,
|
|
@@ -81156,7 +81597,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
|
|
|
81156
81597
|
details: { latestSnapshotAt }
|
|
81157
81598
|
};
|
|
81158
81599
|
}
|
|
81159
|
-
const ageDays = (now.getTime() - snapshotMs) /
|
|
81600
|
+
const ageDays = (now.getTime() - snapshotMs) / DAY_MS3;
|
|
81160
81601
|
const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays)) };
|
|
81161
81602
|
if (ageDays < 0) {
|
|
81162
81603
|
return {
|
|
@@ -81377,13 +81818,16 @@ var ALL_CHECKS = [
|
|
|
81377
81818
|
...BING_AUTH_CHECKS,
|
|
81378
81819
|
...WORDPRESS_PUBLISH_CHECKS,
|
|
81379
81820
|
...GA_AUTH_CHECKS,
|
|
81821
|
+
...DATA_FRESHNESS_CHECKS,
|
|
81380
81822
|
...ADS_CHECKS,
|
|
81381
81823
|
...GOOGLE_MARKETING_DOCTOR_CHECKS,
|
|
81382
81824
|
...PROVIDERS_CHECKS,
|
|
81383
81825
|
...TRAFFIC_SOURCE_CHECKS,
|
|
81384
81826
|
...BACKLINKS_CHECKS,
|
|
81385
81827
|
...CONTENT_CHECKS,
|
|
81386
|
-
...AGENT_CHECKS
|
|
81828
|
+
...AGENT_CHECKS,
|
|
81829
|
+
// Network probe last, so a slow or unreachable site never delays the local checks.
|
|
81830
|
+
...SITE_REACHABILITY_CHECKS
|
|
81387
81831
|
];
|
|
81388
81832
|
|
|
81389
81833
|
// ../api-routes/src/doctor/runner.ts
|
|
@@ -81405,6 +81849,7 @@ async function runChecks(ctx, checks, options = {}) {
|
|
|
81405
81849
|
const projectName = ctx.project?.name ?? null;
|
|
81406
81850
|
const selected = checks.filter((check2) => {
|
|
81407
81851
|
if (check2.scope !== targetScope) return false;
|
|
81852
|
+
if (check2.optIn && filters.length === 0) return false;
|
|
81408
81853
|
return matchesCheckId(check2.id, filters);
|
|
81409
81854
|
});
|
|
81410
81855
|
const results = [];
|
|
@@ -81515,7 +81960,7 @@ async function doctorRoutes(app, opts) {
|
|
|
81515
81960
|
|
|
81516
81961
|
// ../api-routes/src/discovery/routes.ts
|
|
81517
81962
|
import crypto46 from "crypto";
|
|
81518
|
-
import { and as
|
|
81963
|
+
import { and as and54, desc as desc27, eq as eq63, gte as gte16, inArray as inArray24, isNull as isNull10, or as or13 } from "drizzle-orm";
|
|
81519
81964
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
81520
81965
|
async function discoveryRoutes(app, opts) {
|
|
81521
81966
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -81549,13 +81994,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
81549
81994
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81550
81995
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
81551
81996
|
const decision = app.db.transaction((tx) => {
|
|
81552
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
81553
|
-
|
|
81554
|
-
|
|
81997
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and54(
|
|
81998
|
+
eq63(discoverySessions.projectId, project.id),
|
|
81999
|
+
eq63(discoverySessions.icpDescription, icpDescription),
|
|
81555
82000
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
81556
82001
|
// semantics, so a request with a different (or no) buyer must start
|
|
81557
82002
|
// its own session, never adopt another buyer's probes.
|
|
81558
|
-
parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) :
|
|
82003
|
+
parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) : eq63(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
81559
82004
|
// Locations are identity too: a different service-area subset seeds
|
|
81560
82005
|
// and probes a different geo, so it must never reuse another geo's
|
|
81561
82006
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -81565,18 +82010,18 @@ async function discoveryRoutes(app, opts) {
|
|
|
81565
82010
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
81566
82011
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
81567
82012
|
// upgrade, never a wrong reuse.
|
|
81568
|
-
locations.length === 0 ? or13(isNull10(discoverySessions.locations),
|
|
82013
|
+
locations.length === 0 ? or13(isNull10(discoverySessions.locations), eq63(discoverySessions.locations, locations)) : eq63(discoverySessions.locations, locations),
|
|
81569
82014
|
// Seed provider set is identity: a different phrasing distribution
|
|
81570
82015
|
// must never reuse another set's session. Null (= Gemini-only
|
|
81571
82016
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
81572
|
-
seedProviders == null ? isNull10(discoverySessions.seedProviders) :
|
|
81573
|
-
|
|
82017
|
+
seedProviders == null ? isNull10(discoverySessions.seedProviders) : eq63(discoverySessions.seedProviders, seedProviders),
|
|
82018
|
+
inArray24(discoverySessions.status, [
|
|
81574
82019
|
DiscoverySessionStatuses.queued,
|
|
81575
82020
|
DiscoverySessionStatuses.seeding,
|
|
81576
82021
|
DiscoverySessionStatuses.probing
|
|
81577
82022
|
]),
|
|
81578
82023
|
gte16(discoverySessions.createdAt, ageFloorIso)
|
|
81579
|
-
)).orderBy(
|
|
82024
|
+
)).orderBy(desc27(discoverySessions.createdAt)).get();
|
|
81580
82025
|
if (existing && existing.runId) {
|
|
81581
82026
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
81582
82027
|
}
|
|
@@ -81645,7 +82090,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81645
82090
|
const project = resolveProject(app.db, request.params.name);
|
|
81646
82091
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
81647
82092
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
81648
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
82093
|
+
const rows = app.db.select().from(discoverySessions).where(eq63(discoverySessions.projectId, project.id)).orderBy(desc27(discoverySessions.createdAt)).limit(limit).all();
|
|
81649
82094
|
return reply.send(rows.map(serializeSession));
|
|
81650
82095
|
}
|
|
81651
82096
|
);
|
|
@@ -81653,11 +82098,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
81653
82098
|
"/projects/:name/discover/sessions/:id",
|
|
81654
82099
|
async (request, reply) => {
|
|
81655
82100
|
const project = resolveProject(app.db, request.params.name);
|
|
81656
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82101
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81657
82102
|
if (!session || session.projectId !== project.id) {
|
|
81658
82103
|
throw notFound("Discovery session", request.params.id);
|
|
81659
82104
|
}
|
|
81660
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
82105
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
81661
82106
|
const detail = {
|
|
81662
82107
|
...serializeSession(session),
|
|
81663
82108
|
probes: probeRows.map(serializeProbe)
|
|
@@ -81669,7 +82114,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81669
82114
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
81670
82115
|
async (request, reply) => {
|
|
81671
82116
|
const project = resolveProject(app.db, request.params.name);
|
|
81672
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82117
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81673
82118
|
if (!session || session.projectId !== project.id) {
|
|
81674
82119
|
throw notFound("Discovery session", request.params.id);
|
|
81675
82120
|
}
|
|
@@ -81677,7 +82122,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81677
82122
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
81678
82123
|
const applyAnchor = request.query.anchor !== "false";
|
|
81679
82124
|
const provider = session.seedProvider ?? "gemini";
|
|
81680
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
82125
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
81681
82126
|
const extract = opts.harvestSearchQueries;
|
|
81682
82127
|
const probesWithQueries = probeRows.map((row) => {
|
|
81683
82128
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -81688,7 +82133,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81688
82133
|
return { searchQueries: [] };
|
|
81689
82134
|
}
|
|
81690
82135
|
});
|
|
81691
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
82136
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query);
|
|
81692
82137
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
81693
82138
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
81694
82139
|
effectiveDomains(project)
|
|
@@ -81735,12 +82180,12 @@ async function discoveryRoutes(app, opts) {
|
|
|
81735
82180
|
"/projects/:name/discover/sessions/:id/promote",
|
|
81736
82181
|
async (request, reply) => {
|
|
81737
82182
|
const project = resolveProject(app.db, request.params.name);
|
|
81738
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82183
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81739
82184
|
if (!session || session.projectId !== project.id) {
|
|
81740
82185
|
throw notFound("Discovery session", request.params.id);
|
|
81741
82186
|
}
|
|
81742
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
81743
|
-
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
82187
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
82188
|
+
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
|
|
81744
82189
|
const seenCompetitors = new Set(existingCompetitors);
|
|
81745
82190
|
const cited = /* @__PURE__ */ new Set();
|
|
81746
82191
|
const aspirational = /* @__PURE__ */ new Set();
|
|
@@ -81769,7 +82214,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81769
82214
|
);
|
|
81770
82215
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
81771
82216
|
const project = resolveProject(app.db, request.params.name);
|
|
81772
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82217
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81773
82218
|
if (!session || session.projectId !== project.id) {
|
|
81774
82219
|
throw notFound("Discovery session", request.params.id);
|
|
81775
82220
|
}
|
|
@@ -81792,7 +82237,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81792
82237
|
const bucketSet = new Set(buckets);
|
|
81793
82238
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
81794
82239
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
81795
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
82240
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
81796
82241
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
81797
82242
|
for (const probe of probeRows) {
|
|
81798
82243
|
if (!probe.bucket) continue;
|
|
@@ -81800,7 +82245,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81800
82245
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
81801
82246
|
}
|
|
81802
82247
|
const existingQueries = new Set(
|
|
81803
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
82248
|
+
app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
81804
82249
|
);
|
|
81805
82250
|
const promotedQueries = [];
|
|
81806
82251
|
const skippedQueries = [];
|
|
@@ -81816,7 +82261,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81816
82261
|
const skippedCompetitors = [];
|
|
81817
82262
|
if (includeCompetitors) {
|
|
81818
82263
|
const existingCompetitors = new Set(
|
|
81819
|
-
app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
82264
|
+
app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
|
|
81820
82265
|
);
|
|
81821
82266
|
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
81822
82267
|
for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
|
|
@@ -81935,7 +82380,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
|
|
|
81935
82380
|
|
|
81936
82381
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
81937
82382
|
import crypto47 from "crypto";
|
|
81938
|
-
import { eq as
|
|
82383
|
+
import { eq as eq64 } from "drizzle-orm";
|
|
81939
82384
|
var DEFAULT_MAX_PROBES = 100;
|
|
81940
82385
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
81941
82386
|
function classifyProbeBucket(input) {
|
|
@@ -82022,7 +82467,7 @@ async function executeDiscovery(opts) {
|
|
|
82022
82467
|
status: DiscoverySessionStatuses.seeding,
|
|
82023
82468
|
dedupThreshold,
|
|
82024
82469
|
startedAt
|
|
82025
|
-
}).where(
|
|
82470
|
+
}).where(eq64(discoverySessions.id, opts.sessionId)).run();
|
|
82026
82471
|
const seedResult = await opts.deps.seed({
|
|
82027
82472
|
project: opts.project,
|
|
82028
82473
|
icpDescription: opts.icpDescription,
|
|
@@ -82083,7 +82528,7 @@ async function executeDiscovery(opts) {
|
|
|
82083
82528
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
82084
82529
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
82085
82530
|
warning
|
|
82086
|
-
}).where(
|
|
82531
|
+
}).where(eq64(discoverySessions.id, opts.sessionId)).run();
|
|
82087
82532
|
const probeLocation = opts.locations?.[0];
|
|
82088
82533
|
const probeResults = await mapWithConcurrency(
|
|
82089
82534
|
probedCanonicals,
|
|
@@ -82137,7 +82582,7 @@ async function executeDiscovery(opts) {
|
|
|
82137
82582
|
wastedCount: buckets["wasted-surface"],
|
|
82138
82583
|
competitorMap,
|
|
82139
82584
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
82140
|
-
}).where(
|
|
82585
|
+
}).where(eq64(discoverySessions.id, opts.sessionId)).run();
|
|
82141
82586
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
82142
82587
|
return {
|
|
82143
82588
|
buckets,
|
|
@@ -82177,7 +82622,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
82177
82622
|
status: DiscoverySessionStatuses.failed,
|
|
82178
82623
|
error,
|
|
82179
82624
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
82180
|
-
}).where(
|
|
82625
|
+
}).where(eq64(discoverySessions.id, sessionId)).run();
|
|
82181
82626
|
}
|
|
82182
82627
|
function dedupeStrings(input) {
|
|
82183
82628
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -82195,7 +82640,7 @@ function dedupeStrings(input) {
|
|
|
82195
82640
|
|
|
82196
82641
|
// ../api-routes/src/technical-aeo.ts
|
|
82197
82642
|
import crypto48 from "crypto";
|
|
82198
|
-
import { and as
|
|
82643
|
+
import { and as and55, asc as asc11, count as count2, desc as desc28, eq as eq65, inArray as inArray25, isNotNull as isNotNull5, isNull as isNull11, lt as lt10, or as or14, sql as sql25 } from "drizzle-orm";
|
|
82199
82644
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
82200
82645
|
var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
82201
82646
|
...SiteCrawlFetchedStates,
|
|
@@ -82408,8 +82853,8 @@ function parseLinkKind(value) {
|
|
|
82408
82853
|
return allowed.data;
|
|
82409
82854
|
}
|
|
82410
82855
|
function linkKindFilter(linkKind) {
|
|
82411
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
82412
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
82856
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq65(siteCrawlEdges.isTemplate, false);
|
|
82857
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq65(siteCrawlEdges.isTemplate, true);
|
|
82413
82858
|
return void 0;
|
|
82414
82859
|
}
|
|
82415
82860
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
@@ -82514,50 +82959,50 @@ function changedFields(before, after, fields) {
|
|
|
82514
82959
|
async function technicalAeoRoutes(app, opts) {
|
|
82515
82960
|
const resolveCrawl = (projectId, runId) => {
|
|
82516
82961
|
const filters = [
|
|
82517
|
-
|
|
82518
|
-
|
|
82519
|
-
|
|
82520
|
-
|
|
82962
|
+
eq65(siteCrawlSnapshots.projectId, projectId),
|
|
82963
|
+
eq65(runs.projectId, projectId),
|
|
82964
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
82965
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82521
82966
|
notProbeRun()
|
|
82522
82967
|
];
|
|
82523
82968
|
if (runId) {
|
|
82524
|
-
filters.push(
|
|
82969
|
+
filters.push(eq65(siteCrawlSnapshots.runId, runId));
|
|
82525
82970
|
} else {
|
|
82526
|
-
filters.push(
|
|
82971
|
+
filters.push(eq65(siteCrawlSnapshots.complete, true), eq65(runs.status, RunStatuses.completed));
|
|
82527
82972
|
}
|
|
82528
|
-
return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
82973
|
+
return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(...filters)).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get();
|
|
82529
82974
|
};
|
|
82530
82975
|
const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
|
|
82531
82976
|
const pageInScope = (scope, selector) => {
|
|
82532
82977
|
if (!selector.nodeKey && !selector.url) return null;
|
|
82533
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
82534
|
-
|
|
82535
|
-
|
|
82536
|
-
|
|
82537
|
-
selector.nodeKey ?
|
|
82978
|
+
return app.db.select().from(siteCrawlPages).where(and55(
|
|
82979
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
82980
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
82981
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
82982
|
+
selector.nodeKey ? eq65(siteCrawlPages.nodeKey, selector.nodeKey) : eq65(siteCrawlPages.url, selector.url)
|
|
82538
82983
|
)).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
82539
82984
|
};
|
|
82540
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
82541
|
-
|
|
82542
|
-
|
|
82543
|
-
|
|
82544
|
-
|
|
82985
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and55(
|
|
82986
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
82987
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
82988
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
82989
|
+
eq65(siteCrawlPages.depth, 0)
|
|
82545
82990
|
)).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
82546
|
-
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(
|
|
82547
|
-
|
|
82548
|
-
|
|
82549
|
-
|
|
82550
|
-
|
|
82991
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and55(
|
|
82992
|
+
eq65(runs.id, runId),
|
|
82993
|
+
eq65(runs.projectId, projectId),
|
|
82994
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
82995
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82551
82996
|
notProbeRun()
|
|
82552
82997
|
)).limit(1).get());
|
|
82553
82998
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
82554
82999
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
82555
83000
|
};
|
|
82556
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
82557
|
-
|
|
82558
|
-
|
|
82559
|
-
|
|
82560
|
-
|
|
83001
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
|
|
83002
|
+
eq65(siteAuditSnapshots.projectId, projectId),
|
|
83003
|
+
eq65(runs.projectId, projectId),
|
|
83004
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83005
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82561
83006
|
notProbeRun()
|
|
82562
83007
|
)).limit(1).get());
|
|
82563
83008
|
const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
|
|
@@ -82578,20 +83023,20 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82578
83023
|
app.get("/projects/:name/technical-aeo", async (request) => {
|
|
82579
83024
|
const project = resolveProject(app.db, request.params.name);
|
|
82580
83025
|
const baseFilters = [
|
|
82581
|
-
|
|
82582
|
-
|
|
82583
|
-
|
|
82584
|
-
|
|
83026
|
+
eq65(siteAuditSnapshots.projectId, project.id),
|
|
83027
|
+
eq65(runs.projectId, project.id),
|
|
83028
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83029
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82585
83030
|
notProbeRun()
|
|
82586
83031
|
];
|
|
82587
|
-
const targetFilters = request.query.runId ? [...baseFilters,
|
|
82588
|
-
const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
83032
|
+
const targetFilters = request.query.runId ? [...baseFilters, eq65(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
|
|
83033
|
+
const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(...targetFilters)).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(1).get();
|
|
82589
83034
|
if (!latest) {
|
|
82590
83035
|
if (request.query.runId) throw notFound("Site audit run", request.query.runId);
|
|
82591
83036
|
return emptyScore(project.name);
|
|
82592
83037
|
}
|
|
82593
83038
|
const snap = latest.snap;
|
|
82594
|
-
const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs,
|
|
83039
|
+
const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(...baseFilters, lt10(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
|
|
82595
83040
|
const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
|
|
82596
83041
|
const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
|
|
82597
83042
|
return {
|
|
@@ -82618,14 +83063,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82618
83063
|
app.get("/projects/:name/technical-aeo/pages", async (request) => {
|
|
82619
83064
|
const project = resolveProject(app.db, request.params.name);
|
|
82620
83065
|
const targetFilters = [
|
|
82621
|
-
|
|
82622
|
-
|
|
82623
|
-
|
|
82624
|
-
|
|
83066
|
+
eq65(siteAuditSnapshots.projectId, project.id),
|
|
83067
|
+
eq65(runs.projectId, project.id),
|
|
83068
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83069
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82625
83070
|
notProbeRun()
|
|
82626
83071
|
];
|
|
82627
|
-
if (request.query.runId) targetFilters.push(
|
|
82628
|
-
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs,
|
|
83072
|
+
if (request.query.runId) targetFilters.push(eq65(siteAuditSnapshots.runId, request.query.runId));
|
|
83073
|
+
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(...targetFilters)).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(1).get();
|
|
82629
83074
|
if (!latest && request.query.runId) {
|
|
82630
83075
|
throw notFound("Site audit run", request.query.runId);
|
|
82631
83076
|
}
|
|
@@ -82633,14 +83078,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82633
83078
|
return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
|
|
82634
83079
|
}
|
|
82635
83080
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
82636
|
-
const conds = [
|
|
82637
|
-
if (statusFilter) conds.push(
|
|
82638
|
-
const where =
|
|
83081
|
+
const conds = [eq65(siteAuditPages.projectId, project.id), eq65(siteAuditPages.runId, latest.runId)];
|
|
83082
|
+
if (statusFilter) conds.push(eq65(siteAuditPages.status, statusFilter));
|
|
83083
|
+
const where = and55(...conds);
|
|
82639
83084
|
const totalRow = app.db.select({ value: count2() }).from(siteAuditPages).where(where).get();
|
|
82640
83085
|
const total = totalRow?.value ?? 0;
|
|
82641
83086
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
82642
83087
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
82643
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
83088
|
+
const orderBy = request.query.sort === "score-desc" ? desc28(siteAuditPages.overallScore) : request.query.sort === "url" ? asc11(siteAuditPages.url) : asc11(siteAuditPages.overallScore);
|
|
82644
83089
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
82645
83090
|
const pages = rows.map((row) => ({
|
|
82646
83091
|
url: row.url,
|
|
@@ -82659,13 +83104,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82659
83104
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
82660
83105
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
82661
83106
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
82662
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
82663
|
-
|
|
82664
|
-
|
|
82665
|
-
|
|
82666
|
-
|
|
83107
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
|
|
83108
|
+
eq65(siteAuditSnapshots.projectId, project.id),
|
|
83109
|
+
eq65(runs.projectId, project.id),
|
|
83110
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83111
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82667
83112
|
notProbeRun()
|
|
82668
|
-
)).orderBy(
|
|
83113
|
+
)).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
82669
83114
|
return { project: project.name, points: rows.reverse() };
|
|
82670
83115
|
});
|
|
82671
83116
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -82757,10 +83202,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82757
83202
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
82758
83203
|
snapshot.rootUrl
|
|
82759
83204
|
)?.nodeKey ?? null;
|
|
82760
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
82761
|
-
|
|
82762
|
-
|
|
82763
|
-
|
|
83205
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and55(
|
|
83206
|
+
eq65(siteCrawlGraphLayouts.projectId, project.id),
|
|
83207
|
+
eq65(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
83208
|
+
eq65(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
82764
83209
|
)).limit(1).get();
|
|
82765
83210
|
if (!persistedLayout) {
|
|
82766
83211
|
return {
|
|
@@ -82811,9 +83256,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82811
83256
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
82812
83257
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
82813
83258
|
const graphScope = [
|
|
82814
|
-
|
|
82815
|
-
|
|
82816
|
-
|
|
83259
|
+
eq65(siteCrawlGraphNodes.projectId, project.id),
|
|
83260
|
+
eq65(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
83261
|
+
eq65(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
82817
83262
|
];
|
|
82818
83263
|
const nodeRows = app.db.select({
|
|
82819
83264
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -82832,17 +83277,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82832
83277
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
82833
83278
|
x: siteCrawlGraphNodes.x,
|
|
82834
83279
|
y: siteCrawlGraphNodes.y
|
|
82835
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
82836
|
-
|
|
82837
|
-
|
|
82838
|
-
|
|
82839
|
-
|
|
82840
|
-
)).where(
|
|
83280
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and55(
|
|
83281
|
+
eq65(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
83282
|
+
eq65(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
83283
|
+
eq65(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
83284
|
+
eq65(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
83285
|
+
)).where(and55(...graphScope, lt10(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc11(siteCrawlGraphNodes.sampleRank)).all();
|
|
82841
83286
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
82842
83287
|
...row,
|
|
82843
83288
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
82844
83289
|
}));
|
|
82845
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
83290
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq65(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
82846
83291
|
const edges = app.db.select({
|
|
82847
83292
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
82848
83293
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -82850,22 +83295,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82850
83295
|
followable: siteCrawlGraphEdges.followable,
|
|
82851
83296
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
82852
83297
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
82853
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
82854
|
-
|
|
82855
|
-
|
|
82856
|
-
|
|
82857
|
-
|
|
83298
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and55(
|
|
83299
|
+
eq65(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
83300
|
+
eq65(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
83301
|
+
eq65(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
83302
|
+
eq65(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
82858
83303
|
lt10(graphSourceNode.sampleRank, maxNodes)
|
|
82859
|
-
)).innerJoin(graphTargetNode,
|
|
82860
|
-
|
|
82861
|
-
|
|
82862
|
-
|
|
82863
|
-
|
|
83304
|
+
)).innerJoin(graphTargetNode, and55(
|
|
83305
|
+
eq65(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
83306
|
+
eq65(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
83307
|
+
eq65(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
83308
|
+
eq65(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
82864
83309
|
lt10(graphTargetNode.sampleRank, maxNodes)
|
|
82865
|
-
)).where(
|
|
82866
|
-
|
|
82867
|
-
|
|
82868
|
-
|
|
83310
|
+
)).where(and55(
|
|
83311
|
+
eq65(siteCrawlGraphEdges.projectId, project.id),
|
|
83312
|
+
eq65(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
83313
|
+
eq65(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
82869
83314
|
lt10(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
82870
83315
|
graphLinkKindFilter
|
|
82871
83316
|
)).orderBy(asc11(siteCrawlGraphEdges.sampleRank)).all();
|
|
@@ -82959,15 +83404,15 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82959
83404
|
let frontier = [focus.nodeKey];
|
|
82960
83405
|
let queryTruncated = false;
|
|
82961
83406
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
82962
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
82963
|
-
|
|
82964
|
-
|
|
82965
|
-
|
|
82966
|
-
|
|
83407
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and55(
|
|
83408
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83409
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83410
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83411
|
+
eq65(siteCrawlEdges.internal, true),
|
|
82967
83412
|
isNotNull5(siteCrawlEdges.targetNodeKey),
|
|
82968
83413
|
or14(
|
|
82969
|
-
|
|
82970
|
-
|
|
83414
|
+
inArray25(siteCrawlEdges.sourceNodeKey, frontier),
|
|
83415
|
+
inArray25(siteCrawlEdges.targetNodeKey, frontier)
|
|
82971
83416
|
)
|
|
82972
83417
|
)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
|
|
82973
83418
|
if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
|
|
@@ -83000,22 +83445,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83000
83445
|
frontier = [...next].sort();
|
|
83001
83446
|
}
|
|
83002
83447
|
const nodeKeys = [...distances.keys()];
|
|
83003
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
83004
|
-
|
|
83005
|
-
|
|
83006
|
-
|
|
83007
|
-
|
|
83448
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
|
|
83449
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83450
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83451
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83452
|
+
inArray25(siteCrawlPages.nodeKey, nodeKeys)
|
|
83008
83453
|
)).all();
|
|
83009
83454
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
83010
83455
|
const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
|
|
83011
|
-
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(
|
|
83012
|
-
|
|
83013
|
-
|
|
83014
|
-
|
|
83015
|
-
|
|
83456
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and55(
|
|
83457
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83458
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83459
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83460
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83016
83461
|
isNotNull5(siteCrawlEdges.targetNodeKey),
|
|
83017
|
-
|
|
83018
|
-
|
|
83462
|
+
inArray25(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
83463
|
+
inArray25(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
83019
83464
|
)).limit(1).get();
|
|
83020
83465
|
if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
|
|
83021
83466
|
omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
|
|
@@ -83110,23 +83555,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83110
83555
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
83111
83556
|
let truncated = false;
|
|
83112
83557
|
for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
|
|
83113
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
83114
|
-
|
|
83115
|
-
|
|
83116
|
-
|
|
83117
|
-
|
|
83118
|
-
|
|
83119
|
-
|
|
83558
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and55(
|
|
83559
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83560
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83561
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83562
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83563
|
+
eq65(siteCrawlEdges.followable, true),
|
|
83564
|
+
eq65(siteCrawlEdges.relation, "anchor"),
|
|
83120
83565
|
isNotNull5(siteCrawlEdges.targetNodeKey),
|
|
83121
|
-
|
|
83566
|
+
inArray25(siteCrawlEdges.sourceNodeKey, frontier)
|
|
83122
83567
|
)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
83123
83568
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
83124
83569
|
const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
|
|
83125
|
-
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(
|
|
83126
|
-
|
|
83127
|
-
|
|
83128
|
-
|
|
83129
|
-
|
|
83570
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and55(
|
|
83571
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83572
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83573
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83574
|
+
inArray25(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
83130
83575
|
)).all().map((row) => row.nodeKey));
|
|
83131
83576
|
const next = /* @__PURE__ */ new Set();
|
|
83132
83577
|
for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
|
|
@@ -83168,11 +83613,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83168
83613
|
pathEdges.unshift(step.edge);
|
|
83169
83614
|
pathKeys.unshift(step.nodeKey);
|
|
83170
83615
|
}
|
|
83171
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
83172
|
-
|
|
83173
|
-
|
|
83174
|
-
|
|
83175
|
-
|
|
83616
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and55(
|
|
83617
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83618
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83619
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83620
|
+
inArray25(siteCrawlPages.nodeKey, pathKeys)
|
|
83176
83621
|
)).all();
|
|
83177
83622
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
83178
83623
|
const pathDetection = templateDetectionOf(snapshot.templateDetection);
|
|
@@ -83201,25 +83646,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83201
83646
|
throw validationError("change must be all, added, removed, or changed");
|
|
83202
83647
|
}
|
|
83203
83648
|
const completeSnapshotFilters = [
|
|
83204
|
-
|
|
83205
|
-
|
|
83206
|
-
|
|
83207
|
-
|
|
83208
|
-
|
|
83649
|
+
eq65(siteCrawlSnapshots.projectId, project.id),
|
|
83650
|
+
eq65(runs.projectId, project.id),
|
|
83651
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83652
|
+
eq65(runs.status, RunStatuses.completed),
|
|
83653
|
+
eq65(siteCrawlSnapshots.complete, true),
|
|
83209
83654
|
notProbeRun()
|
|
83210
83655
|
];
|
|
83211
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
83656
|
+
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(...completeSnapshotFilters)).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get();
|
|
83212
83657
|
if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
|
|
83213
|
-
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
83658
|
+
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(
|
|
83214
83659
|
...completeSnapshotFilters,
|
|
83215
83660
|
or14(
|
|
83216
83661
|
lt10(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
83217
|
-
|
|
83218
|
-
|
|
83662
|
+
and55(
|
|
83663
|
+
eq65(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
83219
83664
|
lt10(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
83220
83665
|
)
|
|
83221
83666
|
)
|
|
83222
|
-
)).orderBy(
|
|
83667
|
+
)).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
83223
83668
|
if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
|
|
83224
83669
|
if (!afterTarget) {
|
|
83225
83670
|
return { project: project.name, state: "unavailable", reason: "no-crawl", fromRunId: null, toRunId: null };
|
|
@@ -83450,18 +83895,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83450
83895
|
const visibleKeys = keyRows.slice(0, limit);
|
|
83451
83896
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
83452
83897
|
const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
|
|
83453
|
-
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
83454
|
-
|
|
83455
|
-
|
|
83456
|
-
|
|
83457
|
-
|
|
83898
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
|
|
83899
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83900
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83901
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83902
|
+
inArray25(siteCrawlPages.nodeKey, pageKeys)
|
|
83458
83903
|
)).all();
|
|
83459
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
83460
|
-
|
|
83461
|
-
|
|
83462
|
-
|
|
83463
|
-
|
|
83464
|
-
|
|
83904
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and55(
|
|
83905
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83906
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83907
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83908
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83909
|
+
inArray25(siteCrawlEdges.edgeKey, linkKeys)
|
|
83465
83910
|
)).all();
|
|
83466
83911
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
83467
83912
|
const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -83586,30 +84031,30 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83586
84031
|
return { project: project.name, hasCrawlData: true, runId: snapshot.runId, total: 0, nextCursor: null, healthStateFilter: null, pages: [] };
|
|
83587
84032
|
}
|
|
83588
84033
|
const filters = [
|
|
83589
|
-
|
|
83590
|
-
|
|
83591
|
-
|
|
84034
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84035
|
+
eq65(siteCrawlPages.runId, snapshot.runId),
|
|
84036
|
+
eq65(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
83592
84037
|
];
|
|
83593
|
-
if (request.query.nodeKey) filters.push(
|
|
84038
|
+
if (request.query.nodeKey) filters.push(eq65(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
83594
84039
|
const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
|
|
83595
|
-
if (inventoryEligible != null) filters.push(
|
|
83596
|
-
if (request.query.fetchState) filters.push(
|
|
83597
|
-
if (request.query.indexabilityState) filters.push(
|
|
83598
|
-
if (request.query.auditState) filters.push(
|
|
84040
|
+
if (inventoryEligible != null) filters.push(eq65(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
84041
|
+
if (request.query.fetchState) filters.push(eq65(siteCrawlPages.fetchState, request.query.fetchState));
|
|
84042
|
+
if (request.query.indexabilityState) filters.push(eq65(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
84043
|
+
if (request.query.auditState) filters.push(eq65(siteCrawlPages.auditState, request.query.auditState));
|
|
83599
84044
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
83600
84045
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
83601
84046
|
const offset = decodeCursor(request.query.cursor);
|
|
83602
|
-
const orderBy = request.query.sort === "score-desc" ? [
|
|
84047
|
+
const orderBy = request.query.sort === "score-desc" ? [desc28(siteCrawlPages.auditScore), asc11(siteCrawlPages.nodeKey)] : request.query.sort === "score-asc" ? [asc11(siteCrawlPages.auditScore), asc11(siteCrawlPages.nodeKey)] : request.query.sort === "path" ? [asc11(siteCrawlPages.path), asc11(siteCrawlPages.nodeKey)] : [asc11(siteCrawlPages.url), asc11(siteCrawlPages.nodeKey)];
|
|
83603
84048
|
let healthStateFilter = null;
|
|
83604
84049
|
if (healthState) {
|
|
83605
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
83606
|
-
|
|
83607
|
-
|
|
83608
|
-
|
|
84050
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and55(
|
|
84051
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84052
|
+
eq65(siteCrawlPages.runId, snapshot.runId),
|
|
84053
|
+
eq65(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
83609
84054
|
isNull11(siteCrawlPages.healthState)
|
|
83610
84055
|
)).limit(1).get();
|
|
83611
84056
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
83612
|
-
if (healthStateFilter === "applied") filters.push(
|
|
84057
|
+
if (healthStateFilter === "applied") filters.push(eq65(siteCrawlPages.healthState, healthState));
|
|
83613
84058
|
}
|
|
83614
84059
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
83615
84060
|
return {
|
|
@@ -83622,7 +84067,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83622
84067
|
pages: []
|
|
83623
84068
|
};
|
|
83624
84069
|
}
|
|
83625
|
-
const where =
|
|
84070
|
+
const where = and55(...filters);
|
|
83626
84071
|
const total = app.db.select({ value: count2() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
83627
84072
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
83628
84073
|
const nextOffset = offset + rows.length;
|
|
@@ -83653,10 +84098,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83653
84098
|
url: siteCrawlPages.url,
|
|
83654
84099
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
83655
84100
|
fetchState: siteCrawlPages.fetchState
|
|
83656
|
-
}).from(siteCrawlPages).where(
|
|
83657
|
-
|
|
83658
|
-
|
|
83659
|
-
|
|
84101
|
+
}).from(siteCrawlPages).where(and55(
|
|
84102
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84103
|
+
eq65(siteCrawlPages.runId, snapshot.runId),
|
|
84104
|
+
eq65(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
83660
84105
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
83661
84106
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
83662
84107
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -83735,17 +84180,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83735
84180
|
};
|
|
83736
84181
|
}
|
|
83737
84182
|
const filters = [
|
|
83738
|
-
|
|
83739
|
-
|
|
83740
|
-
|
|
83741
|
-
|
|
84183
|
+
eq65(siteCrawlEdges.projectId, project.id),
|
|
84184
|
+
eq65(siteCrawlEdges.runId, snapshot.runId),
|
|
84185
|
+
eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
84186
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83742
84187
|
linkKindFilter(linkKind)
|
|
83743
84188
|
];
|
|
83744
|
-
if (request.query.sourceUrl) filters.push(
|
|
83745
|
-
if (request.query.targetUrl) filters.push(
|
|
84189
|
+
if (request.query.sourceUrl) filters.push(eq65(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
84190
|
+
if (request.query.targetUrl) filters.push(eq65(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
83746
84191
|
const followable = parseBoolean2(request.query.followable);
|
|
83747
|
-
if (followable != null) filters.push(
|
|
83748
|
-
const where =
|
|
84192
|
+
if (followable != null) filters.push(eq65(siteCrawlEdges.followable, followable));
|
|
84193
|
+
const where = and55(...filters);
|
|
83749
84194
|
const total = app.db.select({ value: count2() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
83750
84195
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
83751
84196
|
const offset = decodeCursor(request.query.cursor);
|
|
@@ -83803,17 +84248,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83803
84248
|
};
|
|
83804
84249
|
}
|
|
83805
84250
|
const scope = [
|
|
83806
|
-
|
|
83807
|
-
|
|
83808
|
-
|
|
83809
|
-
|
|
84251
|
+
eq65(siteCrawlEdges.projectId, project.id),
|
|
84252
|
+
eq65(siteCrawlEdges.runId, snapshot.runId),
|
|
84253
|
+
eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
84254
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83810
84255
|
linkKindFilter(linkKind)
|
|
83811
84256
|
];
|
|
83812
|
-
const inboundMatch = request.query.nodeKey && request.query.url ? or14(
|
|
83813
|
-
const outboundMatch = request.query.nodeKey && request.query.url ? or14(
|
|
84257
|
+
const inboundMatch = request.query.nodeKey && request.query.url ? or14(eq65(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq65(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq65(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq65(siteCrawlEdges.targetUrl, request.query.url);
|
|
84258
|
+
const outboundMatch = request.query.nodeKey && request.query.url ? or14(eq65(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq65(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq65(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq65(siteCrawlEdges.sourceUrl, request.query.url);
|
|
83814
84259
|
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
83815
|
-
const inboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
83816
|
-
const outboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
84260
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, inboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
84261
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, outboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
83817
84262
|
return {
|
|
83818
84263
|
project: project.name,
|
|
83819
84264
|
hasCrawlData: true,
|
|
@@ -83846,11 +84291,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83846
84291
|
if (!snapshot.attemptId) {
|
|
83847
84292
|
return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [] };
|
|
83848
84293
|
}
|
|
83849
|
-
const where =
|
|
83850
|
-
|
|
83851
|
-
|
|
83852
|
-
|
|
83853
|
-
|
|
84294
|
+
const where = and55(
|
|
84295
|
+
eq65(siteCrawlFindings.projectId, project.id),
|
|
84296
|
+
eq65(siteCrawlFindings.runId, snapshot.runId),
|
|
84297
|
+
eq65(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
84298
|
+
eq65(siteCrawlFindings.findingType, "dead-link"),
|
|
83854
84299
|
// Belt and braces on the client-facing surface: a row without a status
|
|
83855
84300
|
// code is not evidence of a broken link, and this response is where such
|
|
83856
84301
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -83899,19 +84344,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83899
84344
|
createdAt: runs.createdAt,
|
|
83900
84345
|
startedAt: runs.startedAt,
|
|
83901
84346
|
finishedAt: runs.finishedAt
|
|
83902
|
-
}).from(runs).where(
|
|
83903
|
-
|
|
83904
|
-
|
|
84347
|
+
}).from(runs).where(and55(
|
|
84348
|
+
eq65(runs.projectId, project.id),
|
|
84349
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83905
84350
|
notProbeRun()
|
|
83906
|
-
)).orderBy(
|
|
84351
|
+
)).orderBy(desc28(runs.createdAt), desc28(runs.id)).limit(limit).all();
|
|
83907
84352
|
const runIds = rows.map((row) => row.runId);
|
|
83908
|
-
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
83909
|
-
|
|
83910
|
-
|
|
83911
|
-
|
|
83912
|
-
|
|
84353
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(
|
|
84354
|
+
eq65(siteCrawlSnapshots.projectId, project.id),
|
|
84355
|
+
eq65(runs.projectId, project.id),
|
|
84356
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
84357
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
83913
84358
|
notProbeRun(),
|
|
83914
|
-
|
|
84359
|
+
inArray25(siteCrawlSnapshots.runId, runIds)
|
|
83915
84360
|
)).all().map((row) => row.runId));
|
|
83916
84361
|
return {
|
|
83917
84362
|
project: project.name,
|
|
@@ -83924,21 +84369,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83924
84369
|
});
|
|
83925
84370
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
83926
84371
|
const project = resolveProject(app.db, request.params.name);
|
|
83927
|
-
const run = app.db.select().from(runs).where(
|
|
83928
|
-
|
|
83929
|
-
|
|
83930
|
-
|
|
84372
|
+
const run = app.db.select().from(runs).where(and55(
|
|
84373
|
+
eq65(runs.id, request.params.runId),
|
|
84374
|
+
eq65(runs.projectId, project.id),
|
|
84375
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83931
84376
|
notProbeRun()
|
|
83932
84377
|
)).get();
|
|
83933
84378
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
83934
|
-
const attempt = app.db.select().from(siteCrawlAttempts).where(
|
|
83935
|
-
|
|
83936
|
-
|
|
83937
|
-
)).orderBy(
|
|
83938
|
-
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(
|
|
83939
|
-
|
|
83940
|
-
|
|
83941
|
-
|
|
84379
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and55(
|
|
84380
|
+
eq65(siteCrawlAttempts.projectId, project.id),
|
|
84381
|
+
eq65(siteCrawlAttempts.runId, run.id)
|
|
84382
|
+
)).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
84383
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and55(
|
|
84384
|
+
eq65(siteCrawlGraphLayouts.projectId, project.id),
|
|
84385
|
+
eq65(siteCrawlGraphLayouts.runId, run.id),
|
|
84386
|
+
eq65(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
83942
84387
|
)).get() : void 0;
|
|
83943
84388
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
83944
84389
|
return {
|
|
@@ -83969,19 +84414,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83969
84414
|
};
|
|
83970
84415
|
});
|
|
83971
84416
|
app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
|
|
83972
|
-
const project = tx.select().from(projects).where(
|
|
84417
|
+
const project = tx.select().from(projects).where(eq65(projects.name, request.params.name)).get();
|
|
83973
84418
|
if (!project) throw notFound("Project", request.params.name);
|
|
83974
|
-
const run = tx.select().from(runs).where(
|
|
83975
|
-
|
|
83976
|
-
|
|
83977
|
-
|
|
84419
|
+
const run = tx.select().from(runs).where(and55(
|
|
84420
|
+
eq65(runs.id, request.params.runId),
|
|
84421
|
+
eq65(runs.projectId, project.id),
|
|
84422
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83978
84423
|
notProbeRun()
|
|
83979
84424
|
)).get();
|
|
83980
84425
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
83981
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
83982
|
-
|
|
83983
|
-
|
|
83984
|
-
)).orderBy(
|
|
84426
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and55(
|
|
84427
|
+
eq65(siteCrawlAttempts.projectId, project.id),
|
|
84428
|
+
eq65(siteCrawlAttempts.runId, run.id)
|
|
84429
|
+
)).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
83985
84430
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
83986
84431
|
const base = {
|
|
83987
84432
|
project: project.name,
|
|
@@ -83994,11 +84439,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83994
84439
|
if (!attempt) {
|
|
83995
84440
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
83996
84441
|
}
|
|
83997
|
-
const auditedWhere =
|
|
83998
|
-
|
|
83999
|
-
|
|
84000
|
-
|
|
84001
|
-
|
|
84442
|
+
const auditedWhere = and55(
|
|
84443
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84444
|
+
eq65(siteCrawlPages.runId, run.id),
|
|
84445
|
+
eq65(siteCrawlPages.attemptId, attempt.id),
|
|
84446
|
+
eq65(siteCrawlPages.auditState, "success")
|
|
84002
84447
|
);
|
|
84003
84448
|
const pagesAudited = tx.select({ value: count2() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
84004
84449
|
if (state !== "collecting") {
|
|
@@ -84009,7 +84454,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
84009
84454
|
url: siteCrawlPages.url,
|
|
84010
84455
|
auditScore: siteCrawlPages.auditScore,
|
|
84011
84456
|
auditFields: siteCrawlPages.auditFields
|
|
84012
|
-
}).from(siteCrawlPages).where(
|
|
84457
|
+
}).from(siteCrawlPages).where(and55(
|
|
84013
84458
|
auditedWhere,
|
|
84014
84459
|
isNotNull5(siteCrawlPages.auditScore),
|
|
84015
84460
|
lt10(siteCrawlPages.auditScore, 70)
|
|
@@ -84045,13 +84490,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
84045
84490
|
status: runs.status,
|
|
84046
84491
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
84047
84492
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
84048
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
84049
|
-
|
|
84050
|
-
|
|
84051
|
-
)).where(
|
|
84052
|
-
|
|
84053
|
-
|
|
84054
|
-
|
|
84493
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and55(
|
|
84494
|
+
eq65(siteCrawlRunRequests.projectId, runs.projectId),
|
|
84495
|
+
eq65(siteCrawlRunRequests.runId, runs.id)
|
|
84496
|
+
)).where(and55(
|
|
84497
|
+
eq65(runs.projectId, project.id),
|
|
84498
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
84499
|
+
inArray25(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
84055
84500
|
)).get();
|
|
84056
84501
|
if (existing) {
|
|
84057
84502
|
if (existing.identityKey === identityKey) {
|
|
@@ -84105,7 +84550,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
84105
84550
|
// ../api-routes/src/research.ts
|
|
84106
84551
|
import crypto49 from "crypto";
|
|
84107
84552
|
import { z as z5 } from "zod";
|
|
84108
|
-
import { and as
|
|
84553
|
+
import { and as and56, count as count3, desc as desc29, eq as eq66, gte as gte17, lt as lt11, or as or15, sql as sql26 } from "drizzle-orm";
|
|
84109
84554
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
84110
84555
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
84111
84556
|
async function researchRoutes(app, opts) {
|
|
@@ -84123,7 +84568,7 @@ async function researchRoutes(app, opts) {
|
|
|
84123
84568
|
const input = parsed.data;
|
|
84124
84569
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
84125
84570
|
const requestHash2 = directResearchRequestHash(input);
|
|
84126
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
84571
|
+
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and56(eq66(researchRuns.projectId, project.id), eq66(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
|
|
84127
84572
|
if (existingReceipt) {
|
|
84128
84573
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
84129
84574
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -84155,7 +84600,7 @@ async function researchRoutes(app, opts) {
|
|
|
84155
84600
|
const initiatedBy = researchPrincipal(request);
|
|
84156
84601
|
const decision = app.db.transaction((tx) => {
|
|
84157
84602
|
if (input.idempotencyKey) {
|
|
84158
|
-
const existing = tx.select().from(researchRuns).where(
|
|
84603
|
+
const existing = tx.select().from(researchRuns).where(and56(eq66(researchRuns.projectId, project.id), eq66(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
84159
84604
|
if (existing) {
|
|
84160
84605
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
84161
84606
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -84163,8 +84608,8 @@ async function researchRoutes(app, opts) {
|
|
|
84163
84608
|
}
|
|
84164
84609
|
if (initiatedBy?.limited) {
|
|
84165
84610
|
const { start, end, date } = utcDayBounds(now);
|
|
84166
|
-
const used = tx.select({ value: count3() }).from(researchRuns).where(
|
|
84167
|
-
|
|
84611
|
+
const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
|
|
84612
|
+
eq66(researchRuns.projectId, project.id),
|
|
84168
84613
|
gte17(researchRuns.createdAt, start),
|
|
84169
84614
|
lt11(researchRuns.createdAt, end),
|
|
84170
84615
|
sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -84195,15 +84640,15 @@ async function researchRoutes(app, opts) {
|
|
|
84195
84640
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
84196
84641
|
const requestHash2 = researchBatchRequestHash(input);
|
|
84197
84642
|
const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
|
|
84198
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
84199
|
-
|
|
84200
|
-
|
|
84643
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and56(
|
|
84644
|
+
eq66(researchRuns.projectId, project.id),
|
|
84645
|
+
eq66(researchRuns.idempotencyKey, receiptKeys[0])
|
|
84201
84646
|
)).get();
|
|
84202
84647
|
if (firstReceipt) {
|
|
84203
84648
|
if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
84204
|
-
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(
|
|
84205
|
-
|
|
84206
|
-
|
|
84649
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and56(
|
|
84650
|
+
eq66(researchRuns.projectId, project.id),
|
|
84651
|
+
eq66(researchRuns.idempotencyKey, key)
|
|
84207
84652
|
)).get());
|
|
84208
84653
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
84209
84654
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -84218,8 +84663,8 @@ async function researchRoutes(app, opts) {
|
|
|
84218
84663
|
const decision = app.db.transaction((tx) => {
|
|
84219
84664
|
if (initiatedBy?.limited) {
|
|
84220
84665
|
const { start, end, date } = utcDayBounds(now);
|
|
84221
|
-
const used = tx.select({ value: count3() }).from(researchRuns).where(
|
|
84222
|
-
|
|
84666
|
+
const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
|
|
84667
|
+
eq66(researchRuns.projectId, project.id),
|
|
84223
84668
|
gte17(researchRuns.createdAt, start),
|
|
84224
84669
|
lt11(researchRuns.createdAt, end),
|
|
84225
84670
|
sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -84273,10 +84718,10 @@ async function researchRoutes(app, opts) {
|
|
|
84273
84718
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
84274
84719
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
84275
84720
|
const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
|
|
84276
|
-
const rows = app.db.select().from(researchRuns).where(
|
|
84277
|
-
|
|
84278
|
-
cursor ? or15(lt11(researchRuns.createdAt, cursor.createdAt),
|
|
84279
|
-
)).orderBy(
|
|
84721
|
+
const rows = app.db.select().from(researchRuns).where(and56(
|
|
84722
|
+
eq66(researchRuns.projectId, project.id),
|
|
84723
|
+
cursor ? or15(lt11(researchRuns.createdAt, cursor.createdAt), and56(eq66(researchRuns.createdAt, cursor.createdAt), lt11(researchRuns.id, cursor.id))) : void 0
|
|
84724
|
+
)).orderBy(desc29(researchRuns.createdAt), desc29(researchRuns.id)).limit(limit + 1).all();
|
|
84280
84725
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
84281
84726
|
const last = runs2.at(-1);
|
|
84282
84727
|
const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
|
|
@@ -84402,9 +84847,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
84402
84847
|
return stored;
|
|
84403
84848
|
}
|
|
84404
84849
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
84405
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
84406
|
-
|
|
84407
|
-
|
|
84850
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and56(
|
|
84851
|
+
eq66(measurementQueryTemplates.projectId, projectId),
|
|
84852
|
+
eq66(measurementQueryTemplates.id, selection.templateId)
|
|
84408
84853
|
)).get();
|
|
84409
84854
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
84410
84855
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -84413,7 +84858,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
84413
84858
|
}
|
|
84414
84859
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
84415
84860
|
if (bindingLocation) {
|
|
84416
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
84861
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq66(projects.id, projectId)).get();
|
|
84417
84862
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
84418
84863
|
}
|
|
84419
84864
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -84426,9 +84871,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
84426
84871
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
84427
84872
|
}
|
|
84428
84873
|
function getDetail(app, projectId, id) {
|
|
84429
|
-
const row = app.db.select().from(researchRuns).where(
|
|
84874
|
+
const row = app.db.select().from(researchRuns).where(and56(eq66(researchRuns.id, id), eq66(researchRuns.projectId, projectId))).get();
|
|
84430
84875
|
if (!row) throw notFound("Research run", id);
|
|
84431
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
84876
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq66(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
84432
84877
|
return { ...serializeRun(row), queries: queries2 };
|
|
84433
84878
|
}
|
|
84434
84879
|
function serializeRun(row) {
|
|
@@ -84458,7 +84903,7 @@ function utcDayBounds(now) {
|
|
|
84458
84903
|
|
|
84459
84904
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
84460
84905
|
import crypto50 from "crypto";
|
|
84461
|
-
import { and as
|
|
84906
|
+
import { and as and57, eq as eq67 } from "drizzle-orm";
|
|
84462
84907
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
84463
84908
|
return db.transaction((tx) => {
|
|
84464
84909
|
const run = tx.select({
|
|
@@ -84466,7 +84911,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
84466
84911
|
trigger: runs.trigger,
|
|
84467
84912
|
status: runs.status,
|
|
84468
84913
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
84469
|
-
}).from(runs).where(
|
|
84914
|
+
}).from(runs).where(and57(eq67(runs.projectId, input.projectId), eq67(runs.id, input.runId))).get();
|
|
84470
84915
|
if (!run) throw notFound("Run", input.runId);
|
|
84471
84916
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
84472
84917
|
return null;
|
|
@@ -84476,7 +84921,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
84476
84921
|
if (definition.queries.some((query) => query.queryClass !== (classifier?.classify(query.queryText) ?? null))) {
|
|
84477
84922
|
throw validationError("Query classes must match the captured identity and text.");
|
|
84478
84923
|
}
|
|
84479
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
84924
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and57(eq67(simpleMeasurementDefinitions.projectId, input.projectId), eq67(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
84480
84925
|
if (existing) {
|
|
84481
84926
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
84482
84927
|
const replayDefinition = {
|
|
@@ -84497,15 +84942,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
84497
84942
|
if (run.status !== RunStatuses.running) {
|
|
84498
84943
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
84499
84944
|
}
|
|
84500
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
84945
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq67(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
84501
84946
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
84502
84947
|
}
|
|
84503
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
84948
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq67(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
84504
84949
|
if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
84505
84950
|
throw validationError("Every captured query must belong to the run project.");
|
|
84506
84951
|
}
|
|
84507
84952
|
if (definition.competitors !== void 0) {
|
|
84508
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
84953
|
+
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq67(competitors.projectId, input.projectId)).all().map((competitor) => competitor.domain));
|
|
84509
84954
|
const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
|
|
84510
84955
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
84511
84956
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
@@ -85449,10 +85894,10 @@ var log = createLogger("IntelligenceService");
|
|
|
85449
85894
|
var runChronologyKey = sql27`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
85450
85895
|
function analyzableRunPredicates(projectId) {
|
|
85451
85896
|
return [
|
|
85452
|
-
|
|
85897
|
+
eq68(runs.projectId, projectId),
|
|
85453
85898
|
// Only this kind writes query_snapshots.
|
|
85454
|
-
|
|
85455
|
-
or16(
|
|
85899
|
+
eq68(runs.kind, RunKinds["answer-visibility"]),
|
|
85900
|
+
or16(eq68(runs.status, RunStatuses.completed), eq68(runs.status, RunStatuses.partial)),
|
|
85456
85901
|
// Defensive: RunCoordinator already skips probes before analyzeAndPersist
|
|
85457
85902
|
// is called, but a future call site invoking it directly for a probe must
|
|
85458
85903
|
// still not pollute the intelligence window.
|
|
@@ -85497,16 +85942,16 @@ var IntelligenceService = class {
|
|
|
85497
85942
|
* Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
|
|
85498
85943
|
*/
|
|
85499
85944
|
analyzeAndPersist(runId, projectId) {
|
|
85500
|
-
const currentRunRecord = this.db.select().from(runs).where(
|
|
85945
|
+
const currentRunRecord = this.db.select().from(runs).where(and58(eq68(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
|
|
85501
85946
|
if (!currentRunRecord) {
|
|
85502
85947
|
log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
|
|
85503
85948
|
return null;
|
|
85504
85949
|
}
|
|
85505
85950
|
const currentLocation = currentRunRecord.location ?? null;
|
|
85506
85951
|
const recentRuns = this.db.select().from(runs).where(
|
|
85507
|
-
|
|
85952
|
+
and58(
|
|
85508
85953
|
...analyzableRunPredicates(projectId),
|
|
85509
|
-
currentLocation === null ? isNull12(runs.location) :
|
|
85954
|
+
currentLocation === null ? isNull12(runs.location) : eq68(runs.location, currentLocation),
|
|
85510
85955
|
// Never look forward. The window is this run's own past, so
|
|
85511
85956
|
// re-analyzing a historical run compares it against its true
|
|
85512
85957
|
// predecessor rather than against sweeps that came after it.
|
|
@@ -85514,10 +85959,10 @@ var IntelligenceService = class {
|
|
|
85514
85959
|
// MEASUREMENT: only runs that actually wrote snapshots — see above.
|
|
85515
85960
|
// Index seek on idx_snapshots_run, short-circuited on the first row.
|
|
85516
85961
|
exists(
|
|
85517
|
-
this.db.select({ one: sql27`1` }).from(querySnapshots).where(
|
|
85962
|
+
this.db.select({ one: sql27`1` }).from(querySnapshots).where(eq68(querySnapshots.runId, runs.id))
|
|
85518
85963
|
)
|
|
85519
85964
|
)
|
|
85520
|
-
).orderBy(
|
|
85965
|
+
).orderBy(desc30(runs.finishedAt), desc30(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
85521
85966
|
const trackedCompetitors = this.loadTrackedCompetitors(projectId);
|
|
85522
85967
|
const currentRun = this.buildRunData(
|
|
85523
85968
|
runId,
|
|
@@ -85589,7 +86034,7 @@ var IntelligenceService = class {
|
|
|
85589
86034
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
85590
86035
|
*/
|
|
85591
86036
|
analyzeAndPersistGbp(runId, projectId) {
|
|
85592
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
86037
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq68(runs.id, runId)).get();
|
|
85593
86038
|
if (!runRow) {
|
|
85594
86039
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
85595
86040
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -85597,9 +86042,9 @@ var IntelligenceService = class {
|
|
|
85597
86042
|
}
|
|
85598
86043
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
85599
86044
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
85600
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
85601
|
-
|
|
85602
|
-
|
|
86045
|
+
const selected = this.db.select().from(gbpLocations).where(and58(
|
|
86046
|
+
eq68(gbpLocations.projectId, projectId),
|
|
86047
|
+
eq68(gbpLocations.selected, true),
|
|
85603
86048
|
gte18(gbpLocations.syncedAt, windowStart),
|
|
85604
86049
|
lte15(gbpLocations.syncedAt, windowEnd)
|
|
85605
86050
|
)).all();
|
|
@@ -85634,12 +86079,12 @@ var IntelligenceService = class {
|
|
|
85634
86079
|
}
|
|
85635
86080
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
85636
86081
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
85637
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(
|
|
85638
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(
|
|
85639
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(
|
|
85640
|
-
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(
|
|
86082
|
+
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and58(eq68(gbpDailyMetrics.projectId, projectId), eq68(gbpDailyMetrics.locationName, locationName))).all();
|
|
86083
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and58(eq68(gbpPlaceActions.projectId, projectId), eq68(gbpPlaceActions.locationName, locationName))).all();
|
|
86084
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and58(eq68(gbpLodgingSnapshots.projectId, projectId), eq68(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc30(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
86085
|
+
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and58(eq68(gbpLocations.projectId, projectId), eq68(gbpLocations.locationName, locationName))).get();
|
|
85641
86086
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
85642
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
86087
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and58(eq68(gbpPlaceDetails.projectId, projectId), eq68(gbpPlaceDetails.locationName, locationName))).orderBy(desc30(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
85643
86088
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
85644
86089
|
const summary2 = buildGbpSummary({
|
|
85645
86090
|
locationName,
|
|
@@ -85675,7 +86120,7 @@ var IntelligenceService = class {
|
|
|
85675
86120
|
/** Build the month-over-month keyword series for a location from the
|
|
85676
86121
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
85677
86122
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
85678
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
86123
|
+
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and58(eq68(gbpKeywordMonthly.projectId, projectId), eq68(gbpKeywordMonthly.locationName, locationName))).all();
|
|
85679
86124
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
85680
86125
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
85681
86126
|
const recentMonth = months[0] ?? null;
|
|
@@ -85706,7 +86151,7 @@ var IntelligenceService = class {
|
|
|
85706
86151
|
*/
|
|
85707
86152
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
85708
86153
|
const covered = new Set(coveredLocationNames);
|
|
85709
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
86154
|
+
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and58(eq68(insights.projectId, projectId), eq68(insights.provider, GBP_INSIGHT_PROVIDER))).all();
|
|
85710
86155
|
const staleIds = [];
|
|
85711
86156
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
85712
86157
|
for (const row of existing) {
|
|
@@ -85717,7 +86162,7 @@ var IntelligenceService = class {
|
|
|
85717
86162
|
}
|
|
85718
86163
|
this.db.transaction((tx) => {
|
|
85719
86164
|
for (const id of staleIds) {
|
|
85720
|
-
tx.delete(insights).where(
|
|
86165
|
+
tx.delete(insights).where(eq68(insights.id, id)).run();
|
|
85721
86166
|
}
|
|
85722
86167
|
for (const insight of gbpInsights) {
|
|
85723
86168
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -85803,7 +86248,7 @@ var IntelligenceService = class {
|
|
|
85803
86248
|
* create per run + aggregate). DB is left untouched.
|
|
85804
86249
|
*/
|
|
85805
86250
|
backfill(projectName, opts, onProgress) {
|
|
85806
|
-
const project = this.db.select().from(projects).where(
|
|
86251
|
+
const project = this.db.select().from(projects).where(eq68(projects.name, projectName)).get();
|
|
85807
86252
|
if (!project) {
|
|
85808
86253
|
throw new Error(`Project "${projectName}" not found`);
|
|
85809
86254
|
}
|
|
@@ -85815,7 +86260,7 @@ var IntelligenceService = class {
|
|
|
85815
86260
|
}
|
|
85816
86261
|
sinceTimestamp = parsed;
|
|
85817
86262
|
}
|
|
85818
|
-
const allRuns = this.db.select().from(runs).where(
|
|
86263
|
+
const allRuns = this.db.select().from(runs).where(and58(...analyzableRunPredicates(project.id))).orderBy(asc12(runs.finishedAt)).all();
|
|
85819
86264
|
const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
|
|
85820
86265
|
const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
|
|
85821
86266
|
let startIdx = 0;
|
|
@@ -85839,14 +86284,14 @@ var IntelligenceService = class {
|
|
|
85839
86284
|
});
|
|
85840
86285
|
}
|
|
85841
86286
|
let processed = 0;
|
|
85842
|
-
let
|
|
86287
|
+
let skipped3 = 0;
|
|
85843
86288
|
let totalInsights = 0;
|
|
85844
86289
|
const isDryRun = opts?.dryRun === true;
|
|
85845
86290
|
const perRunDelta = [];
|
|
85846
86291
|
let wouldDeleteTotal = 0;
|
|
85847
86292
|
const existingByRunId = /* @__PURE__ */ new Map();
|
|
85848
86293
|
if (isDryRun && targetRuns.length > 0) {
|
|
85849
|
-
const rows = this.db.select({ runId: insights.runId }).from(insights).where(
|
|
86294
|
+
const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray26(insights.runId, targetRuns.map((r) => r.id))).all();
|
|
85850
86295
|
for (const r of rows) {
|
|
85851
86296
|
if (r.runId == null) continue;
|
|
85852
86297
|
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
@@ -85871,14 +86316,14 @@ var IntelligenceService = class {
|
|
|
85871
86316
|
}
|
|
85872
86317
|
onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: result.insights.length });
|
|
85873
86318
|
} else {
|
|
85874
|
-
|
|
86319
|
+
skipped3++;
|
|
85875
86320
|
onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: 0 });
|
|
85876
86321
|
}
|
|
85877
86322
|
}
|
|
85878
86323
|
if (isDryRun) {
|
|
85879
86324
|
return {
|
|
85880
86325
|
processed,
|
|
85881
|
-
skipped,
|
|
86326
|
+
skipped: skipped3,
|
|
85882
86327
|
totalInsights,
|
|
85883
86328
|
dryRun: true,
|
|
85884
86329
|
delta: {
|
|
@@ -85889,10 +86334,10 @@ var IntelligenceService = class {
|
|
|
85889
86334
|
}
|
|
85890
86335
|
};
|
|
85891
86336
|
}
|
|
85892
|
-
return { processed, skipped, totalInsights };
|
|
86337
|
+
return { processed, skipped: skipped3, totalInsights };
|
|
85893
86338
|
}
|
|
85894
86339
|
loadTrackedCompetitors(projectId) {
|
|
85895
|
-
return this.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
86340
|
+
return this.db.select({ domain: competitors.domain }).from(competitors).where(eq68(competitors.projectId, projectId)).all().map((r) => r.domain);
|
|
85896
86341
|
}
|
|
85897
86342
|
/**
|
|
85898
86343
|
* Which of `runIds` actually wrote query snapshots. One grouped read, so
|
|
@@ -85901,7 +86346,7 @@ var IntelligenceService = class {
|
|
|
85901
86346
|
*/
|
|
85902
86347
|
runIdsWithSnapshots(runIds) {
|
|
85903
86348
|
if (runIds.length === 0) return /* @__PURE__ */ new Set();
|
|
85904
|
-
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(
|
|
86349
|
+
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray26(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
|
|
85905
86350
|
return new Set(rows.map((r) => r.runId));
|
|
85906
86351
|
}
|
|
85907
86352
|
/**
|
|
@@ -85923,15 +86368,15 @@ var IntelligenceService = class {
|
|
|
85923
86368
|
}
|
|
85924
86369
|
persistResult(result, runId, projectId) {
|
|
85925
86370
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
85926
|
-
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(
|
|
86371
|
+
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq68(insights.runId, runId)).all();
|
|
85927
86372
|
for (const row of existingInsights) {
|
|
85928
86373
|
if (row.dismissed) {
|
|
85929
86374
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
85930
86375
|
}
|
|
85931
86376
|
}
|
|
85932
86377
|
this.db.transaction((tx) => {
|
|
85933
|
-
tx.delete(insights).where(
|
|
85934
|
-
tx.delete(healthSnapshots).where(
|
|
86378
|
+
tx.delete(insights).where(eq68(insights.runId, runId)).run();
|
|
86379
|
+
tx.delete(healthSnapshots).where(eq68(healthSnapshots.runId, runId)).run();
|
|
85935
86380
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
85936
86381
|
for (const insight of result.insights) {
|
|
85937
86382
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -85978,8 +86423,8 @@ var IntelligenceService = class {
|
|
|
85978
86423
|
clicks: gscSearchData.clicks,
|
|
85979
86424
|
impressions: gscSearchData.impressions,
|
|
85980
86425
|
position: gscSearchData.position
|
|
85981
|
-
}).from(gscSearchData).where(
|
|
85982
|
-
|
|
86426
|
+
}).from(gscSearchData).where(and58(
|
|
86427
|
+
eq68(gscSearchData.projectId, projectId),
|
|
85983
86428
|
gte18(gscSearchData.date, window.startDate),
|
|
85984
86429
|
lte15(gscSearchData.date, window.endDate)
|
|
85985
86430
|
)).all();
|
|
@@ -86032,21 +86477,21 @@ var IntelligenceService = class {
|
|
|
86032
86477
|
const key = row.query.toLowerCase();
|
|
86033
86478
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
86034
86479
|
}
|
|
86035
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
86480
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq68(projects.id, projectId)).get();
|
|
86036
86481
|
const locationCount = Math.max(
|
|
86037
86482
|
1,
|
|
86038
86483
|
(projectRow?.locations ?? []).length
|
|
86039
86484
|
);
|
|
86040
86485
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
86041
86486
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
86042
|
-
|
|
86043
|
-
|
|
86044
|
-
|
|
86045
|
-
or16(
|
|
86487
|
+
and58(
|
|
86488
|
+
eq68(runs.projectId, projectId),
|
|
86489
|
+
eq68(runs.kind, RunKinds["answer-visibility"]),
|
|
86490
|
+
or16(eq68(runs.status, "completed"), eq68(runs.status, "partial")),
|
|
86046
86491
|
// Defensive — see top of file.
|
|
86047
86492
|
ne11(runs.trigger, RunTriggers.probe)
|
|
86048
86493
|
)
|
|
86049
|
-
).orderBy(
|
|
86494
|
+
).orderBy(desc30(runs.createdAt), desc30(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
86050
86495
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
86051
86496
|
const recentRunIds = [];
|
|
86052
86497
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -86062,7 +86507,7 @@ var IntelligenceService = class {
|
|
|
86062
86507
|
const haveHistory = recentRunIds.length > 0;
|
|
86063
86508
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
86064
86509
|
if (haveHistory) {
|
|
86065
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
86510
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and58(eq68(insights.type, "regression"), inArray26(insights.runId, recentRunIds))).all();
|
|
86066
86511
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
86067
86512
|
for (const row of priorRows) {
|
|
86068
86513
|
if (!row.runId) continue;
|
|
@@ -86091,7 +86536,7 @@ var IntelligenceService = class {
|
|
|
86091
86536
|
});
|
|
86092
86537
|
}
|
|
86093
86538
|
buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
|
|
86094
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
86539
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq68(projects.id, projectId)).get();
|
|
86095
86540
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
86096
86541
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
86097
86542
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -86109,7 +86554,7 @@ var IntelligenceService = class {
|
|
|
86109
86554
|
citedDomains: querySnapshots.citedDomains,
|
|
86110
86555
|
rawResponse: querySnapshots.rawResponse,
|
|
86111
86556
|
snapshotLocation: querySnapshots.location
|
|
86112
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
86557
|
+
}).from(querySnapshots).leftJoin(queries, eq68(querySnapshots.queryId, queries.id)).where(eq68(querySnapshots.runId, runId)).all();
|
|
86113
86558
|
const snapshots = [];
|
|
86114
86559
|
let orphanCount = 0;
|
|
86115
86560
|
for (const r of rows) {
|
|
@@ -86170,6 +86615,7 @@ export {
|
|
|
86170
86615
|
schedules,
|
|
86171
86616
|
insightNotifyState,
|
|
86172
86617
|
doctorHealthState,
|
|
86618
|
+
siteLivenessState,
|
|
86173
86619
|
notifications,
|
|
86174
86620
|
googleConnections,
|
|
86175
86621
|
gscSearchData,
|
|
@@ -86318,6 +86764,9 @@ export {
|
|
|
86318
86764
|
DEFAULT_BOT_LIST,
|
|
86319
86765
|
generateWorkerScript,
|
|
86320
86766
|
generateWranglerToml,
|
|
86767
|
+
SITE_REACHABILITY_CHECK_ID,
|
|
86768
|
+
SITE_REACHABILITY_CHECKS,
|
|
86769
|
+
runChecks,
|
|
86321
86770
|
executeDiscovery,
|
|
86322
86771
|
markSessionFailed,
|
|
86323
86772
|
captureSimpleMeasurementDefinition,
|