@ainyc/canonry 5.2.1 → 5.3.1
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 +2 -2
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -0
- package/assets/assets/{AuditHistoryPanel-CkBLHz2H.js → AuditHistoryPanel-D5cOFMKE.js} +1 -1
- package/assets/assets/{BacklinksPage-D2YWJUf9.js → BacklinksPage-BiOj8ztV.js} +1 -1
- package/assets/assets/{HistoryPage-C9j3RzMN.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-DTJuJ8mb.js → RunRow-BLBAwIot.js} +1 -1
- package/assets/assets/{RunsPage-CdKorpOj.js → RunsPage-BVgBbE1M.js} +1 -1
- package/assets/assets/{SettingsPage-BOjZh2vW.js → SettingsPage-DYtN-1_-.js} +1 -1
- package/assets/assets/{SiteHealthSection-D7sPztpW.js → SiteHealthSection-ElUwcAS4.js} +3 -3
- package/assets/assets/{TrafficPage-BCodKdgN.js → TrafficPage-D6QT_2hU.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D_zueOK5.js → TrafficSourceDetailPage-BnLzF82w.js} +1 -1
- package/assets/assets/{extract-error-message-BZYGBapN.js → extract-error-message-DIzjGZxl.js} +1 -1
- package/assets/assets/{index-aZ69fB-u.js → index-CoQVOsWw.js} +3 -3
- package/assets/assets/{react-sigma_core.esm.min-m72qsd73.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-5VONCLVQ.js → chunk-3COTRCK3.js} +1 -1
- package/dist/{chunk-YQNKULAD.js → chunk-IACO5R7A.js} +837 -367
- package/dist/{chunk-AGUCTX7L.js → chunk-TF6HFT3Q.js} +198 -57
- package/dist/cli.js +6 -6
- package/dist/{demo-server-BIPT53EY.js → demo-server-TXW3UX3P.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-SJO5E7WN.js → intelligence-service-H34EOB6K.js} +1 -1
- package/package.json +13 -13
- package/assets/assets/MeasurementPropertyPage-Ci3CUr7Q.js +0 -1
- package/assets/assets/ProjectPage-Bdq_KTXX.js +0 -8
- package/assets/assets/v2-overview-adapter-BgsdlByS.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,
|
|
@@ -1385,6 +1386,28 @@ var doctorHealthState = sqliteTable("doctor_health_state", {
|
|
|
1385
1386
|
summary: text("summary").notNull(),
|
|
1386
1387
|
checkedAt: text("checked_at").notNull(),
|
|
1387
1388
|
/** When we last emitted for this (status, code). Null until first emit. */
|
|
1389
|
+
notifiedAt: text("notified_at"),
|
|
1390
|
+
/**
|
|
1391
|
+
* Every failing check from the last pass as sorted `status:code` pairs. The
|
|
1392
|
+
* headline code alone cannot see a second breach arriving UNDER an existing
|
|
1393
|
+
* one: a GBP outage opening beneath a standing GA warning left the worst code
|
|
1394
|
+
* unchanged, so it was graded, listed in the payload, and then never sent.
|
|
1395
|
+
* NULL on rows written before this column existed — unknown is not the same
|
|
1396
|
+
* as changed, so the trigger falls back to the code rule for one pass rather
|
|
1397
|
+
* than paging every already-degraded project the moment this ships.
|
|
1398
|
+
*/
|
|
1399
|
+
failingSignature: text("failing_signature")
|
|
1400
|
+
});
|
|
1401
|
+
var siteLivenessState = sqliteTable("site_liveness_state", {
|
|
1402
|
+
projectId: text("project_id").primaryKey(),
|
|
1403
|
+
/** Last graded probe: ok | fail. Skipped probes are not recorded. */
|
|
1404
|
+
status: text("status").notNull(),
|
|
1405
|
+
code: text("code").notNull(),
|
|
1406
|
+
summary: text("summary").notNull(),
|
|
1407
|
+
/** Failed passes in a row. Paging waits for two so one blip never alerts. */
|
|
1408
|
+
consecutiveFailures: integer("consecutive_failures").notNull().default(0),
|
|
1409
|
+
checkedAt: text("checked_at").notNull(),
|
|
1410
|
+
/** Set when an outage was actually paged; cleared on recovery. */
|
|
1388
1411
|
notifiedAt: text("notified_at")
|
|
1389
1412
|
});
|
|
1390
1413
|
var notifications = sqliteTable("notifications", {
|
|
@@ -7291,6 +7314,35 @@ var MIGRATION_VERSIONS = [
|
|
|
7291
7314
|
`CREATE INDEX IF NOT EXISTS idx_runtime_logs_module_level_ts ON runtime_logs(module, level, ts, sequence)`,
|
|
7292
7315
|
`CREATE INDEX IF NOT EXISTS idx_runtime_logs_ts ON runtime_logs(ts, sequence)`
|
|
7293
7316
|
]
|
|
7317
|
+
},
|
|
7318
|
+
{
|
|
7319
|
+
// Liveness gets its own row per project. See siteLivenessState in
|
|
7320
|
+
// schema.ts: a fast "site is up" pass must never overwrite the 6h doctor
|
|
7321
|
+
// state, or it would clear an unrelated outage and send a false recovery.
|
|
7322
|
+
version: 156,
|
|
7323
|
+
name: "site-liveness-state",
|
|
7324
|
+
statements: [
|
|
7325
|
+
`CREATE TABLE IF NOT EXISTS site_liveness_state (
|
|
7326
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
7327
|
+
status TEXT NOT NULL,
|
|
7328
|
+
code TEXT NOT NULL,
|
|
7329
|
+
summary TEXT NOT NULL,
|
|
7330
|
+
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
7331
|
+
checked_at TEXT NOT NULL,
|
|
7332
|
+
notified_at TEXT
|
|
7333
|
+
)`
|
|
7334
|
+
]
|
|
7335
|
+
},
|
|
7336
|
+
{
|
|
7337
|
+
// A breach opening underneath an existing one never reached anyone: the
|
|
7338
|
+
// trigger keyed on the worst check's code, and a lower-ranked check going
|
|
7339
|
+
// bad does not change it. Existing rows get NULL, which the notifier reads
|
|
7340
|
+
// as "unknown" rather than "changed" so shipping this pages nobody.
|
|
7341
|
+
version: 157,
|
|
7342
|
+
name: "doctor-health-failing-signature",
|
|
7343
|
+
statements: [
|
|
7344
|
+
`ALTER TABLE doctor_health_state ADD COLUMN failing_signature TEXT`
|
|
7345
|
+
]
|
|
7294
7346
|
}
|
|
7295
7347
|
];
|
|
7296
7348
|
function addRunsMeasurementPlanVersionForeignKey(tx) {
|
|
@@ -7757,8 +7809,8 @@ function projectContext(event, nested) {
|
|
|
7757
7809
|
const value = event[key] ?? nested[key];
|
|
7758
7810
|
if (typeof value === "boolean") context[key] = value;
|
|
7759
7811
|
}
|
|
7760
|
-
const
|
|
7761
|
-
if (
|
|
7812
|
+
const errorCode2 = optionalString(event.errorCode ?? nested.errorCode, 128);
|
|
7813
|
+
if (errorCode2 !== void 0) context.errorCode = errorCode2;
|
|
7762
7814
|
return context;
|
|
7763
7815
|
}
|
|
7764
7816
|
var ID_FIELDS = [
|
|
@@ -43383,9 +43435,10 @@ function healthView(payload) {
|
|
|
43383
43435
|
value: bulletList(others.map((check2) => `${check2.status} \u2014 ${check2.code}`))
|
|
43384
43436
|
});
|
|
43385
43437
|
}
|
|
43438
|
+
const website = health.code.startsWith("site.reachability.");
|
|
43386
43439
|
return {
|
|
43387
43440
|
severity,
|
|
43388
|
-
title: recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
|
|
43441
|
+
title: website ? recovered ? `${project.name} website is back up` : `${project.name} website is down` : recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
|
|
43389
43442
|
body: health.summary,
|
|
43390
43443
|
fields,
|
|
43391
43444
|
url: payload.dashboardUrl,
|
|
@@ -52229,14 +52282,14 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
|
|
|
52229
52282
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
52230
52283
|
const entityId = outcome.entity?.id ?? row.entityId;
|
|
52231
52284
|
const exhausted = outcome.state === AdsOperationStates.unknown && row.reconcileAttempts >= maxAttempts;
|
|
52232
|
-
const
|
|
52285
|
+
const errorCode2 = exhausted ? ADS_RECONCILIATION_QUARANTINED : outcome.errorCode ?? null;
|
|
52233
52286
|
const errorMessage = exhausted ? `Reconciliation stopped after ${maxAttempts} inconclusive attempts; manual remediation is required` : outcome.errorMessage ?? null;
|
|
52234
52287
|
app.db.transaction((tx) => {
|
|
52235
52288
|
const result = tx.update(adsOperations).set({
|
|
52236
52289
|
state: outcome.state,
|
|
52237
52290
|
entityId,
|
|
52238
52291
|
upstreamUpdatedAt: outcome.entity?.updatedAt ?? row.upstreamUpdatedAt,
|
|
52239
|
-
errorCode,
|
|
52292
|
+
errorCode: errorCode2,
|
|
52240
52293
|
errorMessage,
|
|
52241
52294
|
lastReconciledAt: now,
|
|
52242
52295
|
leaseOwner: null,
|
|
@@ -52251,7 +52304,7 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
|
|
|
52251
52304
|
if (result.changes > 0) {
|
|
52252
52305
|
writeAuditLog(tx, reconciliationAuditEntry(row, outcome.state, context, {
|
|
52253
52306
|
entityId,
|
|
52254
|
-
errorCode
|
|
52307
|
+
errorCode: errorCode2
|
|
52255
52308
|
}));
|
|
52256
52309
|
}
|
|
52257
52310
|
});
|
|
@@ -57516,11 +57569,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
57516
57569
|
const result = await bulkSetSeoMeta(connection, metaEntries);
|
|
57517
57570
|
const applied = result.results.filter((r) => r.status === "applied").length;
|
|
57518
57571
|
const manual = result.results.filter((r) => r.status === "manual").length;
|
|
57519
|
-
const
|
|
57572
|
+
const skipped3 = result.results.filter((r) => r.status === "skipped").length;
|
|
57520
57573
|
steps.push({
|
|
57521
57574
|
name: "set-meta",
|
|
57522
57575
|
status: "completed",
|
|
57523
|
-
summary: `${applied} applied, ${manual} manual-assist, ${
|
|
57576
|
+
summary: `${applied} applied, ${manual} manual-assist, ${skipped3} skipped`
|
|
57524
57577
|
});
|
|
57525
57578
|
}
|
|
57526
57579
|
} catch (err) {
|
|
@@ -57542,11 +57595,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
57542
57595
|
const result = await deploySchemaFromProfile(connection, profile);
|
|
57543
57596
|
const deployed = result.results.filter((r) => r.status === "deployed").length;
|
|
57544
57597
|
const stripped = result.results.filter((r) => r.status === "stripped").length;
|
|
57545
|
-
const
|
|
57598
|
+
const skipped3 = result.results.filter((r) => r.status === "skipped").length;
|
|
57546
57599
|
steps.push({
|
|
57547
57600
|
name: "schema-deploy",
|
|
57548
57601
|
status: "completed",
|
|
57549
|
-
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${
|
|
57602
|
+
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped3} skipped`
|
|
57550
57603
|
});
|
|
57551
57604
|
}
|
|
57552
57605
|
} catch (err) {
|
|
@@ -77300,7 +77353,7 @@ async function trafficRoutes(app, opts) {
|
|
|
77300
77353
|
startedAt,
|
|
77301
77354
|
createdAt: startedAt
|
|
77302
77355
|
}).run();
|
|
77303
|
-
const markFailed = (msg,
|
|
77356
|
+
const markFailed = (msg, errorCode2) => {
|
|
77304
77357
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
77305
77358
|
app.db.transaction((tx) => {
|
|
77306
77359
|
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq55(runs.id, runId)).run();
|
|
@@ -77320,7 +77373,7 @@ async function trafficRoutes(app, opts) {
|
|
|
77320
77373
|
aiUserFetchHits: 0,
|
|
77321
77374
|
aiReferralHits: 0,
|
|
77322
77375
|
durationMs: Date.now() - syncStartedAtMs,
|
|
77323
|
-
errorCode
|
|
77376
|
+
errorCode: errorCode2
|
|
77324
77377
|
});
|
|
77325
77378
|
} catch {
|
|
77326
77379
|
}
|
|
@@ -79472,9 +79525,418 @@ var ga4ConnectionCheck = {
|
|
|
79472
79525
|
};
|
|
79473
79526
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
79474
79527
|
|
|
79528
|
+
// ../api-routes/src/doctor/checks/data-freshness.ts
|
|
79529
|
+
import { and as and51, desc as desc26, eq as eq59, inArray as inArray22 } from "drizzle-orm";
|
|
79530
|
+
var GA_DATA_AGING_DAYS = 3;
|
|
79531
|
+
var GA_DATA_STALE_DAYS = 5;
|
|
79532
|
+
var GSC_DATA_AGING_DAYS = 5;
|
|
79533
|
+
var GSC_DATA_STALE_DAYS = 7;
|
|
79534
|
+
var SYNC_IDLE_DAYS = 3;
|
|
79535
|
+
var GSC_REPORTING_TIME_ZONE2 = "America/Los_Angeles";
|
|
79536
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
79537
|
+
var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
79538
|
+
function utcFromIsoDate(date) {
|
|
79539
|
+
const match = ISO_DATE.exec(date);
|
|
79540
|
+
if (!match) return null;
|
|
79541
|
+
return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
79542
|
+
}
|
|
79543
|
+
function daysBetweenIsoDates(from, to) {
|
|
79544
|
+
const start = utcFromIsoDate(from);
|
|
79545
|
+
const end = utcFromIsoDate(to);
|
|
79546
|
+
if (start === null || end === null) return null;
|
|
79547
|
+
return Math.floor((end - start) / DAY_MS2);
|
|
79548
|
+
}
|
|
79549
|
+
function daysSinceDate(date, now = /* @__PURE__ */ new Date(), timeZone) {
|
|
79550
|
+
const today = timeZone ? formatIsoDateInTimeZone(now.toISOString(), timeZone) : `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}`;
|
|
79551
|
+
return daysBetweenIsoDates(date, today);
|
|
79552
|
+
}
|
|
79553
|
+
function skipped(code, summary2) {
|
|
79554
|
+
return { status: CheckStatuses.skipped, code, summary: summary2, remediation: null };
|
|
79555
|
+
}
|
|
79556
|
+
function lastSyncAgeDays(ctx, kinds) {
|
|
79557
|
+
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();
|
|
79558
|
+
if (!row?.createdAt) return null;
|
|
79559
|
+
const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
|
|
79560
|
+
return Number.isFinite(age) ? age : null;
|
|
79561
|
+
}
|
|
79562
|
+
function grade(ctx, spec, newestDate) {
|
|
79563
|
+
const projectName = ctx.project.name;
|
|
79564
|
+
const code = (suffix) => `${spec.source}.data.${suffix}`;
|
|
79565
|
+
const sync = spec.syncCommand(projectName);
|
|
79566
|
+
const syncAge = lastSyncAgeDays(ctx, spec.syncKinds);
|
|
79567
|
+
const syncIdle = syncAge === null || syncAge >= SYNC_IDLE_DAYS;
|
|
79568
|
+
if (!newestDate) {
|
|
79569
|
+
return {
|
|
79570
|
+
status: CheckStatuses.warn,
|
|
79571
|
+
code: code("never-synced"),
|
|
79572
|
+
summary: `${spec.label} is connected but no daily data has been stored yet.`,
|
|
79573
|
+
remediation: `Run \`${sync}\`, or set a data-refresh schedule so it keeps itself current.`,
|
|
79574
|
+
details: { lastSyncAgeDays: syncAge }
|
|
79575
|
+
};
|
|
79576
|
+
}
|
|
79577
|
+
const age = daysSinceDate(newestDate, /* @__PURE__ */ new Date(), spec.timeZone);
|
|
79578
|
+
if (age === null) {
|
|
79579
|
+
return {
|
|
79580
|
+
status: CheckStatuses.warn,
|
|
79581
|
+
code: code("unreadable-date"),
|
|
79582
|
+
summary: `Newest stored ${spec.label} date "${newestDate}" is not a calendar date.`,
|
|
79583
|
+
remediation: null,
|
|
79584
|
+
details: { newestDate }
|
|
79585
|
+
};
|
|
79586
|
+
}
|
|
79587
|
+
const details = { newestDate, ageDays: age, agingDays: spec.agingDays, staleDays: spec.staleDays, lastSyncAgeDays: syncAge };
|
|
79588
|
+
if (age < spec.agingDays) {
|
|
79589
|
+
return {
|
|
79590
|
+
status: CheckStatuses.ok,
|
|
79591
|
+
code: code("fresh"),
|
|
79592
|
+
summary: `${spec.label} data is current through ${newestDate}.`,
|
|
79593
|
+
remediation: null,
|
|
79594
|
+
details
|
|
79595
|
+
};
|
|
79596
|
+
}
|
|
79597
|
+
if (syncIdle) {
|
|
79598
|
+
return {
|
|
79599
|
+
status: CheckStatuses.warn,
|
|
79600
|
+
code: code("not-syncing"),
|
|
79601
|
+
summary: `No ${spec.label} sync has completed in ${syncAge === null ? "any recorded run" : `${syncAge} days`}, and stored data ends ${newestDate}.`,
|
|
79602
|
+
remediation: `Run \`${sync}\`, or set a data-refresh schedule: \`canonry schedule set ${projectName} --kind data-refresh --preset daily\`.`,
|
|
79603
|
+
details
|
|
79604
|
+
};
|
|
79605
|
+
}
|
|
79606
|
+
const remediation = `${spec.quietExplanation} ${spec.brokenExplanation}`;
|
|
79607
|
+
return {
|
|
79608
|
+
status: CheckStatuses.warn,
|
|
79609
|
+
code: code(age >= spec.staleDays ? "stale" : "aging"),
|
|
79610
|
+
summary: `${spec.label} syncs are running but have stored nothing newer than ${newestDate} (${age} days).`,
|
|
79611
|
+
remediation,
|
|
79612
|
+
details
|
|
79613
|
+
};
|
|
79614
|
+
}
|
|
79615
|
+
var GA_SPEC = {
|
|
79616
|
+
source: "ga",
|
|
79617
|
+
label: "GA4",
|
|
79618
|
+
agingDays: GA_DATA_AGING_DAYS,
|
|
79619
|
+
staleDays: GA_DATA_STALE_DAYS,
|
|
79620
|
+
syncKinds: ["ga-sync"],
|
|
79621
|
+
syncCommand: (name) => `canonry ga sync ${name}`,
|
|
79622
|
+
quietExplanation: "GA4 omits days with no sessions, so a genuinely quiet period clears itself on the next visit.",
|
|
79623
|
+
brokenExplanation: "If the site did have visitors, check that the GA4 tag or its Tag Manager container is still on the live site."
|
|
79624
|
+
};
|
|
79625
|
+
var GSC_SPEC = {
|
|
79626
|
+
source: "gsc",
|
|
79627
|
+
label: "Search Console",
|
|
79628
|
+
agingDays: GSC_DATA_AGING_DAYS,
|
|
79629
|
+
staleDays: GSC_DATA_STALE_DAYS,
|
|
79630
|
+
timeZone: GSC_REPORTING_TIME_ZONE2,
|
|
79631
|
+
syncKinds: ["gsc-sync"],
|
|
79632
|
+
syncCommand: (name) => `canonry google sync ${name}`,
|
|
79633
|
+
quietExplanation: "Search Console omits days with no impressions and reports two to three days behind, so a quiet period clears itself.",
|
|
79634
|
+
brokenExplanation: "If the site did have impressions, check that the property still lists this site and the connected account still has access."
|
|
79635
|
+
};
|
|
79636
|
+
function gaConnected(ctx) {
|
|
79637
|
+
const project = ctx.project;
|
|
79638
|
+
if (!ctx.ga4CredentialStore && !ctx.googleConnectionStore) return "store-unavailable";
|
|
79639
|
+
if (ctx.ga4CredentialStore?.getConnection(project.name)) return "connected";
|
|
79640
|
+
if (ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4")) return "connected";
|
|
79641
|
+
return "not-connected";
|
|
79642
|
+
}
|
|
79643
|
+
var gaRecentDataCheck = {
|
|
79644
|
+
id: "ga.data.recent-data",
|
|
79645
|
+
category: CheckCategories.integrations,
|
|
79646
|
+
scope: CheckScopes.project,
|
|
79647
|
+
title: "GA4 data still arriving",
|
|
79648
|
+
run: (ctx) => {
|
|
79649
|
+
if (!ctx.project) return skipped("ga.data.no-project", "Project context required.");
|
|
79650
|
+
const connection = gaConnected(ctx);
|
|
79651
|
+
if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
|
|
79652
|
+
if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
|
|
79653
|
+
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq59(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gaDailyTotals.date)).limit(1).get();
|
|
79654
|
+
return grade(ctx, GA_SPEC, newest?.date);
|
|
79655
|
+
}
|
|
79656
|
+
};
|
|
79657
|
+
var gscRecentDataCheck = {
|
|
79658
|
+
id: "gsc.data.recent-data",
|
|
79659
|
+
category: CheckCategories.integrations,
|
|
79660
|
+
scope: CheckScopes.project,
|
|
79661
|
+
title: "Search Console data still arriving",
|
|
79662
|
+
run: (ctx) => {
|
|
79663
|
+
if (!ctx.project) return skipped("gsc.data.no-project", "Project context required.");
|
|
79664
|
+
if (!ctx.googleConnectionStore) return skipped("gsc.data.store-unavailable", "No Google connection store configured for this deployment.");
|
|
79665
|
+
if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
|
|
79666
|
+
return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
|
|
79667
|
+
}
|
|
79668
|
+
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq59(gscDataWatermarks.projectId, ctx.project.id)).get();
|
|
79669
|
+
if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
|
|
79670
|
+
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq59(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gscDailyTotals.date)).limit(1).get();
|
|
79671
|
+
return grade(ctx, GSC_SPEC, newest?.date);
|
|
79672
|
+
}
|
|
79673
|
+
};
|
|
79674
|
+
var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
|
|
79675
|
+
|
|
79676
|
+
// ../api-routes/src/site-reachability.ts
|
|
79677
|
+
import dns3 from "dns/promises";
|
|
79678
|
+
import http3 from "http";
|
|
79679
|
+
import https3 from "https";
|
|
79680
|
+
import net3 from "net";
|
|
79681
|
+
var SITE_REACHABILITY_USER_AGENT = "Canonry site-liveness (+https://canonry.ai)";
|
|
79682
|
+
var DEFAULT_SITE_MAX_REDIRECTS = 10;
|
|
79683
|
+
var DEFAULT_TIMEOUT_MS5 = 1e4;
|
|
79684
|
+
var DEFAULT_RETRY_DELAY_MS = 2e3;
|
|
79685
|
+
var DNS_TIMEOUT_MS = 5e3;
|
|
79686
|
+
var MAX_ADDRESSES_PER_HOP = 4;
|
|
79687
|
+
var LENIENT_RETRY_CODES = /* @__PURE__ */ new Set([
|
|
79688
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
79689
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
79690
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
79691
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
79692
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
79693
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
79694
|
+
"HPE_INVALID_HEADER_TOKEN",
|
|
79695
|
+
"HPE_UNEXPECTED_CONTENT_LENGTH",
|
|
79696
|
+
"HPE_INVALID_CONSTANT"
|
|
79697
|
+
]);
|
|
79698
|
+
var NO_SUCH_HOST_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "ENODATA"]);
|
|
79699
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
79700
|
+
function errorCode(err) {
|
|
79701
|
+
const code = err?.code;
|
|
79702
|
+
return typeof code === "string" ? code : "";
|
|
79703
|
+
}
|
|
79704
|
+
async function lookupSiteAddresses(hostname, timeoutMs = DNS_TIMEOUT_MS) {
|
|
79705
|
+
const literalFamily = net3.isIPv6(hostname) ? 6 : net3.isIPv4(hostname) ? 4 : null;
|
|
79706
|
+
if (literalFamily) return { kind: "addresses", addresses: [{ address: hostname, family: literalFamily }] };
|
|
79707
|
+
let timer;
|
|
79708
|
+
const deadline = new Promise((resolve) => {
|
|
79709
|
+
timer = setTimeout(() => resolve("timeout"), timeoutMs);
|
|
79710
|
+
});
|
|
79711
|
+
try {
|
|
79712
|
+
const settled = await Promise.race([
|
|
79713
|
+
Promise.allSettled([dns3.resolve4(hostname), dns3.resolve6(hostname)]),
|
|
79714
|
+
deadline
|
|
79715
|
+
]);
|
|
79716
|
+
if (settled === "timeout") return { kind: "resolver-error", reason: `DNS did not answer within ${Math.round(timeoutMs / 1e3)}s` };
|
|
79717
|
+
const [ipv4, ipv6] = settled;
|
|
79718
|
+
const found = /* @__PURE__ */ new Map();
|
|
79719
|
+
if (ipv4.status === "fulfilled") for (const address of ipv4.value) found.set(`4:${address}`, { address, family: 4 });
|
|
79720
|
+
if (ipv6.status === "fulfilled") for (const address of ipv6.value) found.set(`6:${address}`, { address, family: 6 });
|
|
79721
|
+
if (found.size > 0) return { kind: "addresses", addresses: [...found.values()] };
|
|
79722
|
+
const codes = [ipv4, ipv6].map((r) => r.status === "rejected" ? errorCode(r.reason) : "");
|
|
79723
|
+
if (codes.every((code) => NO_SUCH_HOST_CODES.has(code))) {
|
|
79724
|
+
return { kind: "no-such-host", reason: `${hostname} has no DNS address (${codes[0]})` };
|
|
79725
|
+
}
|
|
79726
|
+
const failing = codes.find((code) => code && !NO_SUCH_HOST_CODES.has(code)) ?? "unknown resolver error";
|
|
79727
|
+
return { kind: "resolver-error", reason: `DNS lookup for ${hostname} failed (${failing})` };
|
|
79728
|
+
} finally {
|
|
79729
|
+
if (timer) clearTimeout(timer);
|
|
79730
|
+
}
|
|
79731
|
+
}
|
|
79732
|
+
async function requestPinnedStatus(target, timeoutMs, options = {}) {
|
|
79733
|
+
const secure = target.url.protocol === "https:";
|
|
79734
|
+
const port = target.url.port ? Number(target.url.port) : secure ? 443 : 80;
|
|
79735
|
+
const requestOptions = {
|
|
79736
|
+
hostname: target.address,
|
|
79737
|
+
family: target.family,
|
|
79738
|
+
port,
|
|
79739
|
+
method: "GET",
|
|
79740
|
+
path: `${target.url.pathname}${target.url.search}`,
|
|
79741
|
+
headers: { Host: target.url.host, "User-Agent": SITE_REACHABILITY_USER_AGENT, Accept: "text/html,*/*;q=0.8" },
|
|
79742
|
+
insecureHTTPParser: options.lenient === true
|
|
79743
|
+
};
|
|
79744
|
+
if (secure) {
|
|
79745
|
+
requestOptions.servername = target.url.hostname.replace(/^\[|\]$/g, "");
|
|
79746
|
+
if (options.lenient) requestOptions.rejectUnauthorized = false;
|
|
79747
|
+
}
|
|
79748
|
+
return await new Promise((resolve, reject) => {
|
|
79749
|
+
let settled = false;
|
|
79750
|
+
const request = (secure ? https3.request : http3.request)(requestOptions, (response) => {
|
|
79751
|
+
if (settled) {
|
|
79752
|
+
response.destroy();
|
|
79753
|
+
return;
|
|
79754
|
+
}
|
|
79755
|
+
settled = true;
|
|
79756
|
+
clearTimeout(deadline);
|
|
79757
|
+
const location = typeof response.headers.location === "string" ? response.headers.location : null;
|
|
79758
|
+
resolve({ status: response.statusCode ?? 0, location });
|
|
79759
|
+
response.destroy();
|
|
79760
|
+
request.destroy();
|
|
79761
|
+
});
|
|
79762
|
+
const deadline = setTimeout(() => {
|
|
79763
|
+
if (settled) return;
|
|
79764
|
+
settled = true;
|
|
79765
|
+
request.destroy();
|
|
79766
|
+
reject(new Error(`no response within ${Math.round(timeoutMs / 1e3)}s`));
|
|
79767
|
+
}, timeoutMs);
|
|
79768
|
+
request.on("error", (err) => {
|
|
79769
|
+
if (settled) return;
|
|
79770
|
+
settled = true;
|
|
79771
|
+
clearTimeout(deadline);
|
|
79772
|
+
reject(err);
|
|
79773
|
+
});
|
|
79774
|
+
request.end();
|
|
79775
|
+
});
|
|
79776
|
+
}
|
|
79777
|
+
async function probeSiteReachability(url, options = {}) {
|
|
79778
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS5;
|
|
79779
|
+
const maxRedirects = options.maxRedirects ?? DEFAULT_SITE_MAX_REDIRECTS;
|
|
79780
|
+
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
79781
|
+
const transport = options.transport ?? ((target, ms, opts) => requestPinnedStatus(target, ms, opts));
|
|
79782
|
+
const resolveAddresses = options.resolveAddresses ?? ((hostname) => lookupSiteAddresses(hostname));
|
|
79783
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
79784
|
+
const started = Date.now();
|
|
79785
|
+
const approveHop = async (hopUrl) => {
|
|
79786
|
+
let hostname;
|
|
79787
|
+
try {
|
|
79788
|
+
hostname = new URL(hopUrl).hostname.replace(/^\[|\]$/g, "");
|
|
79789
|
+
} catch {
|
|
79790
|
+
return { kind: "refused", reason: `${hopUrl} is not a valid URL` };
|
|
79791
|
+
}
|
|
79792
|
+
const lookup = await resolveAddresses(hostname);
|
|
79793
|
+
if (lookup.kind !== "addresses") return lookup;
|
|
79794
|
+
const targets = [];
|
|
79795
|
+
let refusal = "";
|
|
79796
|
+
for (const address of lookup.addresses.slice(0, MAX_ADDRESSES_PER_HOP)) {
|
|
79797
|
+
const approved = await resolveMeasurementSitemapTarget(hopUrl, { resolveAddresses: async () => [address] });
|
|
79798
|
+
if (approved.ok) targets.push(approved.target);
|
|
79799
|
+
else if (!refusal) refusal = approved.message;
|
|
79800
|
+
}
|
|
79801
|
+
if (targets.length === 0) return { kind: "refused", reason: refusal || `${hostname} has no address this instance may dial` };
|
|
79802
|
+
return { kind: "targets", targets };
|
|
79803
|
+
};
|
|
79804
|
+
const attempt = async () => {
|
|
79805
|
+
let current = url;
|
|
79806
|
+
for (let hop = 0; ; hop += 1) {
|
|
79807
|
+
const approved = await approveHop(current);
|
|
79808
|
+
if (approved.kind === "no-such-host") return { kind: "down", finalUrl: current, httpStatus: null, reason: approved.reason };
|
|
79809
|
+
if (approved.kind === "resolver-error") return { kind: "unavailable", reason: approved.reason };
|
|
79810
|
+
if (approved.kind === "refused") return { kind: "unreachable", finalUrl: current, reason: approved.reason };
|
|
79811
|
+
let response = null;
|
|
79812
|
+
let lastError = "";
|
|
79813
|
+
for (const target of approved.targets) {
|
|
79814
|
+
try {
|
|
79815
|
+
response = await transport(target, timeoutMs);
|
|
79816
|
+
break;
|
|
79817
|
+
} catch (err) {
|
|
79818
|
+
lastError = describeError(err);
|
|
79819
|
+
if (LENIENT_RETRY_CODES.has(errorCode(err))) {
|
|
79820
|
+
try {
|
|
79821
|
+
const lenient = await transport(target, timeoutMs, { lenient: true });
|
|
79822
|
+
response = { ...lenient, lenient: `${errorCode(err)} (accepted leniently)` };
|
|
79823
|
+
break;
|
|
79824
|
+
} catch (retryErr) {
|
|
79825
|
+
lastError = describeError(retryErr);
|
|
79826
|
+
}
|
|
79827
|
+
}
|
|
79828
|
+
}
|
|
79829
|
+
}
|
|
79830
|
+
if (!response) return { kind: "down", finalUrl: current, httpStatus: null, reason: lastError || "no address answered" };
|
|
79831
|
+
if (REDIRECT_STATUSES.has(response.status) && response.location) {
|
|
79832
|
+
if (hop >= maxRedirects) {
|
|
79833
|
+
return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `more than ${maxRedirects} redirects` };
|
|
79834
|
+
}
|
|
79835
|
+
try {
|
|
79836
|
+
current = new URL(response.location, current).toString();
|
|
79837
|
+
} catch {
|
|
79838
|
+
return { kind: "down", finalUrl: current, httpStatus: response.status, reason: "redirect with an invalid Location header" };
|
|
79839
|
+
}
|
|
79840
|
+
continue;
|
|
79841
|
+
}
|
|
79842
|
+
if (response.status >= 500 || response.status === 0) {
|
|
79843
|
+
return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `HTTP ${response.status}` };
|
|
79844
|
+
}
|
|
79845
|
+
return { kind: "up", finalUrl: current, httpStatus: response.status, lenient: response.lenient };
|
|
79846
|
+
}
|
|
79847
|
+
};
|
|
79848
|
+
let attempts = 1;
|
|
79849
|
+
let outcome = await attempt();
|
|
79850
|
+
if (outcome.kind === "down" || outcome.kind === "unreachable") {
|
|
79851
|
+
await sleep(retryDelayMs);
|
|
79852
|
+
attempts = 2;
|
|
79853
|
+
outcome = await attempt();
|
|
79854
|
+
}
|
|
79855
|
+
const durationMs = Date.now() - started;
|
|
79856
|
+
if (outcome.kind === "unavailable") return { state: "unavailable", url, reason: outcome.reason };
|
|
79857
|
+
if (outcome.kind === "up") {
|
|
79858
|
+
return { state: "up", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, attempts, durationMs, ...outcome.lenient ? { lenient: outcome.lenient } : {} };
|
|
79859
|
+
}
|
|
79860
|
+
if (outcome.kind === "unreachable") return { state: "unreachable", url, finalUrl: outcome.finalUrl, reason: outcome.reason, attempts, durationMs };
|
|
79861
|
+
return { state: "down", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, reason: outcome.reason, attempts, durationMs };
|
|
79862
|
+
}
|
|
79863
|
+
|
|
79864
|
+
// ../api-routes/src/doctor/checks/site-reachability.ts
|
|
79865
|
+
var SITE_REACHABILITY_CHECK_ID = "site.reachability";
|
|
79866
|
+
function probeHostFromDomain(value) {
|
|
79867
|
+
const trimmed = (value ?? "").trim();
|
|
79868
|
+
if (!trimmed) return null;
|
|
79869
|
+
let hostname;
|
|
79870
|
+
try {
|
|
79871
|
+
hostname = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`).hostname;
|
|
79872
|
+
} catch {
|
|
79873
|
+
return null;
|
|
79874
|
+
}
|
|
79875
|
+
hostname = hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
|
79876
|
+
if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
|
|
79877
|
+
return hostname;
|
|
79878
|
+
}
|
|
79879
|
+
var skipped2 = (code, summary2) => ({ status: CheckStatuses.skipped, code, summary: summary2, remediation: null });
|
|
79880
|
+
var siteReachabilityCheck = {
|
|
79881
|
+
id: SITE_REACHABILITY_CHECK_ID,
|
|
79882
|
+
category: CheckCategories.integrations,
|
|
79883
|
+
scope: CheckScopes.project,
|
|
79884
|
+
title: "Website reachable",
|
|
79885
|
+
// Reaches the network, and one bad answer is not an outage: the liveness loop
|
|
79886
|
+
// names it explicitly and applies the two-pass debounce. An unfiltered
|
|
79887
|
+
// `canonry doctor --project` must not exit 1 on a single dropped packet.
|
|
79888
|
+
optIn: true,
|
|
79889
|
+
run: async (ctx) => {
|
|
79890
|
+
if (!ctx.project) return skipped2("site.reachability.no-project", "Project context required.");
|
|
79891
|
+
const host = probeHostFromDomain(ctx.project.canonicalDomain);
|
|
79892
|
+
if (!host) {
|
|
79893
|
+
return skipped2("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
|
|
79894
|
+
}
|
|
79895
|
+
const url = `https://${host}/`;
|
|
79896
|
+
const probe = ctx.probeSiteReachability ?? probeSiteReachability;
|
|
79897
|
+
const result = await probe(url);
|
|
79898
|
+
if (result.state === "unavailable") {
|
|
79899
|
+
return skipped2("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
|
|
79900
|
+
}
|
|
79901
|
+
if (result.state === "up") {
|
|
79902
|
+
return {
|
|
79903
|
+
status: CheckStatuses.ok,
|
|
79904
|
+
code: "site.reachability.up",
|
|
79905
|
+
summary: `${url} answered HTTP ${result.httpStatus} in ${result.durationMs} ms.`,
|
|
79906
|
+
remediation: null,
|
|
79907
|
+
details: {
|
|
79908
|
+
url,
|
|
79909
|
+
finalUrl: result.finalUrl,
|
|
79910
|
+
httpStatus: result.httpStatus,
|
|
79911
|
+
attempts: result.attempts,
|
|
79912
|
+
durationMs: result.durationMs,
|
|
79913
|
+
...result.lenient ? { tlsOrParserWarning: result.lenient } : {}
|
|
79914
|
+
}
|
|
79915
|
+
};
|
|
79916
|
+
}
|
|
79917
|
+
if (result.state === "unreachable") {
|
|
79918
|
+
return {
|
|
79919
|
+
status: CheckStatuses.fail,
|
|
79920
|
+
code: "site.reachability.refused-address",
|
|
79921
|
+
summary: `${url} resolves only to addresses that cannot serve visitors: ${result.reason}.`,
|
|
79922
|
+
remediation: "Check the domain registration and its DNS records. A suspended or parked domain resolves this way.",
|
|
79923
|
+
details: { url, finalUrl: result.finalUrl, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
|
|
79924
|
+
};
|
|
79925
|
+
}
|
|
79926
|
+
return {
|
|
79927
|
+
status: CheckStatuses.fail,
|
|
79928
|
+
code: "site.reachability.down",
|
|
79929
|
+
summary: `${url} is not responding: ${result.reason}.`,
|
|
79930
|
+
remediation: "Open the site in a browser and check with the host. Canonry keeps checking and sends health.recovered when it answers again.",
|
|
79931
|
+
details: { url, finalUrl: result.finalUrl, httpStatus: result.httpStatus, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
|
|
79932
|
+
};
|
|
79933
|
+
}
|
|
79934
|
+
};
|
|
79935
|
+
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
79936
|
+
|
|
79475
79937
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
79476
|
-
import { and as
|
|
79477
|
-
var RECENT_SYNC_WARN_DAYS2 =
|
|
79938
|
+
import { and as and52, eq as eq60 } from "drizzle-orm";
|
|
79939
|
+
var RECENT_SYNC_WARN_DAYS2 = 4;
|
|
79478
79940
|
var RECENT_SYNC_FAIL_DAYS2 = 30;
|
|
79479
79941
|
function skippedNoProject3() {
|
|
79480
79942
|
return {
|
|
@@ -79706,7 +80168,7 @@ var recentSyncCheck = {
|
|
|
79706
80168
|
title: "GBP recent sync",
|
|
79707
80169
|
run: (ctx) => {
|
|
79708
80170
|
if (!ctx.project) return skippedNoProject3();
|
|
79709
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
80171
|
+
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
80172
|
if (selected.length === 0) {
|
|
79711
80173
|
return {
|
|
79712
80174
|
status: CheckStatuses.skipped,
|
|
@@ -79766,7 +80228,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
79766
80228
|
);
|
|
79767
80229
|
|
|
79768
80230
|
// ../api-routes/src/doctor/checks/places.ts
|
|
79769
|
-
import { eq as
|
|
80231
|
+
import { eq as eq61 } from "drizzle-orm";
|
|
79770
80232
|
var apiKeyCheck = {
|
|
79771
80233
|
id: "gbp.places.api-key",
|
|
79772
80234
|
category: CheckCategories.auth,
|
|
@@ -79811,7 +80273,7 @@ var apiKeyCheck = {
|
|
|
79811
80273
|
details: { tier: cfg.tier }
|
|
79812
80274
|
};
|
|
79813
80275
|
}
|
|
79814
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
80276
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq61(gbpLocations.projectId, ctx.project.id)).all();
|
|
79815
80277
|
const selected = rows.filter((r) => r.selected);
|
|
79816
80278
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
79817
80279
|
const details = {
|
|
@@ -80326,7 +80788,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
80326
80788
|
];
|
|
80327
80789
|
|
|
80328
80790
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
80329
|
-
import { and as
|
|
80791
|
+
import { and as and53, eq as eq62, gte as gte15, inArray as inArray23, ne as ne10, sql as sql24 } from "drizzle-orm";
|
|
80330
80792
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
80331
80793
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
80332
80794
|
function isCloudflareDirectPush(source) {
|
|
@@ -80365,8 +80827,8 @@ function skippedNoProject5() {
|
|
|
80365
80827
|
function loadProbes(ctx) {
|
|
80366
80828
|
if (!ctx.project) return [];
|
|
80367
80829
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
80368
|
-
|
|
80369
|
-
|
|
80830
|
+
and53(
|
|
80831
|
+
eq62(trafficSources.projectId, ctx.project.id),
|
|
80370
80832
|
ne10(trafficSources.status, TrafficSourceStatuses.archived)
|
|
80371
80833
|
)
|
|
80372
80834
|
).all();
|
|
@@ -80472,27 +80934,27 @@ var recentDataCheck = {
|
|
|
80472
80934
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
80473
80935
|
const recentCrawlers = Number(
|
|
80474
80936
|
ctx.db.select({ total: sql24`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
80475
|
-
|
|
80476
|
-
|
|
80477
|
-
|
|
80937
|
+
and53(
|
|
80938
|
+
eq62(crawlerEventsHourly.projectId, ctx.project.id),
|
|
80939
|
+
inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
80478
80940
|
gte15(crawlerEventsHourly.tsHour, warnCutoff)
|
|
80479
80941
|
)
|
|
80480
80942
|
).get()?.total ?? 0
|
|
80481
80943
|
);
|
|
80482
80944
|
const recentReferrals = Number(
|
|
80483
80945
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
80484
|
-
|
|
80485
|
-
|
|
80486
|
-
|
|
80946
|
+
and53(
|
|
80947
|
+
eq62(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
80948
|
+
inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
80487
80949
|
gte15(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
80488
80950
|
)
|
|
80489
80951
|
).get()?.total ?? 0
|
|
80490
80952
|
);
|
|
80491
80953
|
const recentUserFetches = Number(
|
|
80492
80954
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
80493
|
-
|
|
80494
|
-
|
|
80495
|
-
|
|
80955
|
+
and53(
|
|
80956
|
+
eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
80957
|
+
inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
80496
80958
|
gte15(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
80497
80959
|
)
|
|
80498
80960
|
).get()?.total ?? 0
|
|
@@ -80516,27 +80978,27 @@ var recentDataCheck = {
|
|
|
80516
80978
|
}
|
|
80517
80979
|
const olderCrawlers = Number(
|
|
80518
80980
|
ctx.db.select({ total: sql24`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
80519
|
-
|
|
80520
|
-
|
|
80521
|
-
|
|
80981
|
+
and53(
|
|
80982
|
+
eq62(crawlerEventsHourly.projectId, ctx.project.id),
|
|
80983
|
+
inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
80522
80984
|
gte15(crawlerEventsHourly.tsHour, failCutoff)
|
|
80523
80985
|
)
|
|
80524
80986
|
).get()?.total ?? 0
|
|
80525
80987
|
);
|
|
80526
80988
|
const olderReferrals = Number(
|
|
80527
80989
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
80528
|
-
|
|
80529
|
-
|
|
80530
|
-
|
|
80990
|
+
and53(
|
|
80991
|
+
eq62(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
80992
|
+
inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
80531
80993
|
gte15(aiReferralEventsHourly.tsHour, failCutoff)
|
|
80532
80994
|
)
|
|
80533
80995
|
).get()?.total ?? 0
|
|
80534
80996
|
);
|
|
80535
80997
|
const olderUserFetches = Number(
|
|
80536
80998
|
ctx.db.select({ total: sql24`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
80537
|
-
|
|
80538
|
-
|
|
80539
|
-
|
|
80999
|
+
and53(
|
|
81000
|
+
eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
81001
|
+
inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
80540
81002
|
gte15(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
80541
81003
|
)
|
|
80542
81004
|
).get()?.total ?? 0
|
|
@@ -80601,7 +81063,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
|
|
|
80601
81063
|
function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
80602
81064
|
const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
|
|
80603
81065
|
const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
|
|
80604
|
-
const
|
|
81066
|
+
const skipped3 = results.filter((r) => r.output.status === CheckStatuses.skipped);
|
|
80605
81067
|
const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
|
|
80606
81068
|
const detail = {
|
|
80607
81069
|
sources: results.map((r) => ({
|
|
@@ -80635,7 +81097,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
80635
81097
|
return {
|
|
80636
81098
|
status: CheckStatuses.ok,
|
|
80637
81099
|
code: `traffic.${fallbackId}.ok`,
|
|
80638
|
-
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${
|
|
81100
|
+
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped3.length > 0 ? ` (${skipped3.length} skipped)` : ""}.`,
|
|
80639
81101
|
details: detail
|
|
80640
81102
|
};
|
|
80641
81103
|
}
|
|
@@ -80643,7 +81105,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
80643
81105
|
status: CheckStatuses.skipped,
|
|
80644
81106
|
code: `traffic.${fallbackId}.all-skipped`,
|
|
80645
81107
|
summary: `No source-type validator was available for any of the ${results.length} connected source(s).`,
|
|
80646
|
-
remediation:
|
|
81108
|
+
remediation: skipped3.find((result) => result.output.remediation)?.output.remediation,
|
|
80647
81109
|
details: detail
|
|
80648
81110
|
};
|
|
80649
81111
|
}
|
|
@@ -81069,7 +81531,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
|
|
|
81069
81531
|
];
|
|
81070
81532
|
var FRESH_SNAPSHOT_WARN_DAYS = 7;
|
|
81071
81533
|
var FRESH_SNAPSHOT_FAIL_DAYS = 30;
|
|
81072
|
-
var
|
|
81534
|
+
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
81073
81535
|
function unavailableStatus() {
|
|
81074
81536
|
return {
|
|
81075
81537
|
status: CheckStatuses.skipped,
|
|
@@ -81156,7 +81618,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
|
|
|
81156
81618
|
details: { latestSnapshotAt }
|
|
81157
81619
|
};
|
|
81158
81620
|
}
|
|
81159
|
-
const ageDays = (now.getTime() - snapshotMs) /
|
|
81621
|
+
const ageDays = (now.getTime() - snapshotMs) / DAY_MS3;
|
|
81160
81622
|
const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays)) };
|
|
81161
81623
|
if (ageDays < 0) {
|
|
81162
81624
|
return {
|
|
@@ -81377,13 +81839,16 @@ var ALL_CHECKS = [
|
|
|
81377
81839
|
...BING_AUTH_CHECKS,
|
|
81378
81840
|
...WORDPRESS_PUBLISH_CHECKS,
|
|
81379
81841
|
...GA_AUTH_CHECKS,
|
|
81842
|
+
...DATA_FRESHNESS_CHECKS,
|
|
81380
81843
|
...ADS_CHECKS,
|
|
81381
81844
|
...GOOGLE_MARKETING_DOCTOR_CHECKS,
|
|
81382
81845
|
...PROVIDERS_CHECKS,
|
|
81383
81846
|
...TRAFFIC_SOURCE_CHECKS,
|
|
81384
81847
|
...BACKLINKS_CHECKS,
|
|
81385
81848
|
...CONTENT_CHECKS,
|
|
81386
|
-
...AGENT_CHECKS
|
|
81849
|
+
...AGENT_CHECKS,
|
|
81850
|
+
// Network probe last, so a slow or unreachable site never delays the local checks.
|
|
81851
|
+
...SITE_REACHABILITY_CHECKS
|
|
81387
81852
|
];
|
|
81388
81853
|
|
|
81389
81854
|
// ../api-routes/src/doctor/runner.ts
|
|
@@ -81405,6 +81870,7 @@ async function runChecks(ctx, checks, options = {}) {
|
|
|
81405
81870
|
const projectName = ctx.project?.name ?? null;
|
|
81406
81871
|
const selected = checks.filter((check2) => {
|
|
81407
81872
|
if (check2.scope !== targetScope) return false;
|
|
81873
|
+
if (check2.optIn && filters.length === 0) return false;
|
|
81408
81874
|
return matchesCheckId(check2.id, filters);
|
|
81409
81875
|
});
|
|
81410
81876
|
const results = [];
|
|
@@ -81515,7 +81981,7 @@ async function doctorRoutes(app, opts) {
|
|
|
81515
81981
|
|
|
81516
81982
|
// ../api-routes/src/discovery/routes.ts
|
|
81517
81983
|
import crypto46 from "crypto";
|
|
81518
|
-
import { and as
|
|
81984
|
+
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
81985
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
81520
81986
|
async function discoveryRoutes(app, opts) {
|
|
81521
81987
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -81549,13 +82015,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
81549
82015
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81550
82016
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
81551
82017
|
const decision = app.db.transaction((tx) => {
|
|
81552
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
81553
|
-
|
|
81554
|
-
|
|
82018
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and54(
|
|
82019
|
+
eq63(discoverySessions.projectId, project.id),
|
|
82020
|
+
eq63(discoverySessions.icpDescription, icpDescription),
|
|
81555
82021
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
81556
82022
|
// semantics, so a request with a different (or no) buyer must start
|
|
81557
82023
|
// its own session, never adopt another buyer's probes.
|
|
81558
|
-
parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) :
|
|
82024
|
+
parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) : eq63(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
81559
82025
|
// Locations are identity too: a different service-area subset seeds
|
|
81560
82026
|
// and probes a different geo, so it must never reuse another geo's
|
|
81561
82027
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -81565,18 +82031,18 @@ async function discoveryRoutes(app, opts) {
|
|
|
81565
82031
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
81566
82032
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
81567
82033
|
// upgrade, never a wrong reuse.
|
|
81568
|
-
locations.length === 0 ? or13(isNull10(discoverySessions.locations),
|
|
82034
|
+
locations.length === 0 ? or13(isNull10(discoverySessions.locations), eq63(discoverySessions.locations, locations)) : eq63(discoverySessions.locations, locations),
|
|
81569
82035
|
// Seed provider set is identity: a different phrasing distribution
|
|
81570
82036
|
// must never reuse another set's session. Null (= Gemini-only
|
|
81571
82037
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
81572
|
-
seedProviders == null ? isNull10(discoverySessions.seedProviders) :
|
|
81573
|
-
|
|
82038
|
+
seedProviders == null ? isNull10(discoverySessions.seedProviders) : eq63(discoverySessions.seedProviders, seedProviders),
|
|
82039
|
+
inArray24(discoverySessions.status, [
|
|
81574
82040
|
DiscoverySessionStatuses.queued,
|
|
81575
82041
|
DiscoverySessionStatuses.seeding,
|
|
81576
82042
|
DiscoverySessionStatuses.probing
|
|
81577
82043
|
]),
|
|
81578
82044
|
gte16(discoverySessions.createdAt, ageFloorIso)
|
|
81579
|
-
)).orderBy(
|
|
82045
|
+
)).orderBy(desc27(discoverySessions.createdAt)).get();
|
|
81580
82046
|
if (existing && existing.runId) {
|
|
81581
82047
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
81582
82048
|
}
|
|
@@ -81645,7 +82111,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81645
82111
|
const project = resolveProject(app.db, request.params.name);
|
|
81646
82112
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
81647
82113
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
81648
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
82114
|
+
const rows = app.db.select().from(discoverySessions).where(eq63(discoverySessions.projectId, project.id)).orderBy(desc27(discoverySessions.createdAt)).limit(limit).all();
|
|
81649
82115
|
return reply.send(rows.map(serializeSession));
|
|
81650
82116
|
}
|
|
81651
82117
|
);
|
|
@@ -81653,11 +82119,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
81653
82119
|
"/projects/:name/discover/sessions/:id",
|
|
81654
82120
|
async (request, reply) => {
|
|
81655
82121
|
const project = resolveProject(app.db, request.params.name);
|
|
81656
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82122
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81657
82123
|
if (!session || session.projectId !== project.id) {
|
|
81658
82124
|
throw notFound("Discovery session", request.params.id);
|
|
81659
82125
|
}
|
|
81660
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
82126
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
81661
82127
|
const detail = {
|
|
81662
82128
|
...serializeSession(session),
|
|
81663
82129
|
probes: probeRows.map(serializeProbe)
|
|
@@ -81669,7 +82135,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81669
82135
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
81670
82136
|
async (request, reply) => {
|
|
81671
82137
|
const project = resolveProject(app.db, request.params.name);
|
|
81672
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82138
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81673
82139
|
if (!session || session.projectId !== project.id) {
|
|
81674
82140
|
throw notFound("Discovery session", request.params.id);
|
|
81675
82141
|
}
|
|
@@ -81677,7 +82143,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81677
82143
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
81678
82144
|
const applyAnchor = request.query.anchor !== "false";
|
|
81679
82145
|
const provider = session.seedProvider ?? "gemini";
|
|
81680
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
82146
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
81681
82147
|
const extract = opts.harvestSearchQueries;
|
|
81682
82148
|
const probesWithQueries = probeRows.map((row) => {
|
|
81683
82149
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -81688,7 +82154,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81688
82154
|
return { searchQueries: [] };
|
|
81689
82155
|
}
|
|
81690
82156
|
});
|
|
81691
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
82157
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query);
|
|
81692
82158
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
81693
82159
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
81694
82160
|
effectiveDomains(project)
|
|
@@ -81735,12 +82201,12 @@ async function discoveryRoutes(app, opts) {
|
|
|
81735
82201
|
"/projects/:name/discover/sessions/:id/promote",
|
|
81736
82202
|
async (request, reply) => {
|
|
81737
82203
|
const project = resolveProject(app.db, request.params.name);
|
|
81738
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82204
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81739
82205
|
if (!session || session.projectId !== project.id) {
|
|
81740
82206
|
throw notFound("Discovery session", request.params.id);
|
|
81741
82207
|
}
|
|
81742
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
81743
|
-
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
82208
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
82209
|
+
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
|
|
81744
82210
|
const seenCompetitors = new Set(existingCompetitors);
|
|
81745
82211
|
const cited = /* @__PURE__ */ new Set();
|
|
81746
82212
|
const aspirational = /* @__PURE__ */ new Set();
|
|
@@ -81769,7 +82235,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81769
82235
|
);
|
|
81770
82236
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
81771
82237
|
const project = resolveProject(app.db, request.params.name);
|
|
81772
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
82238
|
+
const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
|
|
81773
82239
|
if (!session || session.projectId !== project.id) {
|
|
81774
82240
|
throw notFound("Discovery session", request.params.id);
|
|
81775
82241
|
}
|
|
@@ -81792,7 +82258,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81792
82258
|
const bucketSet = new Set(buckets);
|
|
81793
82259
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
81794
82260
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
81795
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
82261
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
|
|
81796
82262
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
81797
82263
|
for (const probe of probeRows) {
|
|
81798
82264
|
if (!probe.bucket) continue;
|
|
@@ -81800,7 +82266,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81800
82266
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
81801
82267
|
}
|
|
81802
82268
|
const existingQueries = new Set(
|
|
81803
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
82269
|
+
app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
81804
82270
|
);
|
|
81805
82271
|
const promotedQueries = [];
|
|
81806
82272
|
const skippedQueries = [];
|
|
@@ -81816,7 +82282,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
81816
82282
|
const skippedCompetitors = [];
|
|
81817
82283
|
if (includeCompetitors) {
|
|
81818
82284
|
const existingCompetitors = new Set(
|
|
81819
|
-
app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
82285
|
+
app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
|
|
81820
82286
|
);
|
|
81821
82287
|
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
81822
82288
|
for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
|
|
@@ -81935,7 +82401,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
|
|
|
81935
82401
|
|
|
81936
82402
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
81937
82403
|
import crypto47 from "crypto";
|
|
81938
|
-
import { eq as
|
|
82404
|
+
import { eq as eq64 } from "drizzle-orm";
|
|
81939
82405
|
var DEFAULT_MAX_PROBES = 100;
|
|
81940
82406
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
81941
82407
|
function classifyProbeBucket(input) {
|
|
@@ -82022,7 +82488,7 @@ async function executeDiscovery(opts) {
|
|
|
82022
82488
|
status: DiscoverySessionStatuses.seeding,
|
|
82023
82489
|
dedupThreshold,
|
|
82024
82490
|
startedAt
|
|
82025
|
-
}).where(
|
|
82491
|
+
}).where(eq64(discoverySessions.id, opts.sessionId)).run();
|
|
82026
82492
|
const seedResult = await opts.deps.seed({
|
|
82027
82493
|
project: opts.project,
|
|
82028
82494
|
icpDescription: opts.icpDescription,
|
|
@@ -82083,7 +82549,7 @@ async function executeDiscovery(opts) {
|
|
|
82083
82549
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
82084
82550
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
82085
82551
|
warning
|
|
82086
|
-
}).where(
|
|
82552
|
+
}).where(eq64(discoverySessions.id, opts.sessionId)).run();
|
|
82087
82553
|
const probeLocation = opts.locations?.[0];
|
|
82088
82554
|
const probeResults = await mapWithConcurrency(
|
|
82089
82555
|
probedCanonicals,
|
|
@@ -82137,7 +82603,7 @@ async function executeDiscovery(opts) {
|
|
|
82137
82603
|
wastedCount: buckets["wasted-surface"],
|
|
82138
82604
|
competitorMap,
|
|
82139
82605
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
82140
|
-
}).where(
|
|
82606
|
+
}).where(eq64(discoverySessions.id, opts.sessionId)).run();
|
|
82141
82607
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
82142
82608
|
return {
|
|
82143
82609
|
buckets,
|
|
@@ -82177,7 +82643,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
82177
82643
|
status: DiscoverySessionStatuses.failed,
|
|
82178
82644
|
error,
|
|
82179
82645
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
82180
|
-
}).where(
|
|
82646
|
+
}).where(eq64(discoverySessions.id, sessionId)).run();
|
|
82181
82647
|
}
|
|
82182
82648
|
function dedupeStrings(input) {
|
|
82183
82649
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -82195,7 +82661,7 @@ function dedupeStrings(input) {
|
|
|
82195
82661
|
|
|
82196
82662
|
// ../api-routes/src/technical-aeo.ts
|
|
82197
82663
|
import crypto48 from "crypto";
|
|
82198
|
-
import { and as
|
|
82664
|
+
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
82665
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
82200
82666
|
var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
82201
82667
|
...SiteCrawlFetchedStates,
|
|
@@ -82408,8 +82874,8 @@ function parseLinkKind(value) {
|
|
|
82408
82874
|
return allowed.data;
|
|
82409
82875
|
}
|
|
82410
82876
|
function linkKindFilter(linkKind) {
|
|
82411
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
82412
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
82877
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq65(siteCrawlEdges.isTemplate, false);
|
|
82878
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq65(siteCrawlEdges.isTemplate, true);
|
|
82413
82879
|
return void 0;
|
|
82414
82880
|
}
|
|
82415
82881
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
@@ -82514,50 +82980,50 @@ function changedFields(before, after, fields) {
|
|
|
82514
82980
|
async function technicalAeoRoutes(app, opts) {
|
|
82515
82981
|
const resolveCrawl = (projectId, runId) => {
|
|
82516
82982
|
const filters = [
|
|
82517
|
-
|
|
82518
|
-
|
|
82519
|
-
|
|
82520
|
-
|
|
82983
|
+
eq65(siteCrawlSnapshots.projectId, projectId),
|
|
82984
|
+
eq65(runs.projectId, projectId),
|
|
82985
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
82986
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82521
82987
|
notProbeRun()
|
|
82522
82988
|
];
|
|
82523
82989
|
if (runId) {
|
|
82524
|
-
filters.push(
|
|
82990
|
+
filters.push(eq65(siteCrawlSnapshots.runId, runId));
|
|
82525
82991
|
} else {
|
|
82526
|
-
filters.push(
|
|
82992
|
+
filters.push(eq65(siteCrawlSnapshots.complete, true), eq65(runs.status, RunStatuses.completed));
|
|
82527
82993
|
}
|
|
82528
|
-
return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
82994
|
+
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
82995
|
};
|
|
82530
82996
|
const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
|
|
82531
82997
|
const pageInScope = (scope, selector) => {
|
|
82532
82998
|
if (!selector.nodeKey && !selector.url) return null;
|
|
82533
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
82534
|
-
|
|
82535
|
-
|
|
82536
|
-
|
|
82537
|
-
selector.nodeKey ?
|
|
82999
|
+
return app.db.select().from(siteCrawlPages).where(and55(
|
|
83000
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83001
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83002
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83003
|
+
selector.nodeKey ? eq65(siteCrawlPages.nodeKey, selector.nodeKey) : eq65(siteCrawlPages.url, selector.url)
|
|
82538
83004
|
)).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
82539
83005
|
};
|
|
82540
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
82541
|
-
|
|
82542
|
-
|
|
82543
|
-
|
|
82544
|
-
|
|
83006
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and55(
|
|
83007
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83008
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83009
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83010
|
+
eq65(siteCrawlPages.depth, 0)
|
|
82545
83011
|
)).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
|
-
|
|
83012
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and55(
|
|
83013
|
+
eq65(runs.id, runId),
|
|
83014
|
+
eq65(runs.projectId, projectId),
|
|
83015
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83016
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82551
83017
|
notProbeRun()
|
|
82552
83018
|
)).limit(1).get());
|
|
82553
83019
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
82554
83020
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
82555
83021
|
};
|
|
82556
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
82557
|
-
|
|
82558
|
-
|
|
82559
|
-
|
|
82560
|
-
|
|
83022
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
|
|
83023
|
+
eq65(siteAuditSnapshots.projectId, projectId),
|
|
83024
|
+
eq65(runs.projectId, projectId),
|
|
83025
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83026
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82561
83027
|
notProbeRun()
|
|
82562
83028
|
)).limit(1).get());
|
|
82563
83029
|
const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
|
|
@@ -82578,20 +83044,20 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82578
83044
|
app.get("/projects/:name/technical-aeo", async (request) => {
|
|
82579
83045
|
const project = resolveProject(app.db, request.params.name);
|
|
82580
83046
|
const baseFilters = [
|
|
82581
|
-
|
|
82582
|
-
|
|
82583
|
-
|
|
82584
|
-
|
|
83047
|
+
eq65(siteAuditSnapshots.projectId, project.id),
|
|
83048
|
+
eq65(runs.projectId, project.id),
|
|
83049
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83050
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82585
83051
|
notProbeRun()
|
|
82586
83052
|
];
|
|
82587
|
-
const targetFilters = request.query.runId ? [...baseFilters,
|
|
82588
|
-
const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
83053
|
+
const targetFilters = request.query.runId ? [...baseFilters, eq65(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
|
|
83054
|
+
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
83055
|
if (!latest) {
|
|
82590
83056
|
if (request.query.runId) throw notFound("Site audit run", request.query.runId);
|
|
82591
83057
|
return emptyScore(project.name);
|
|
82592
83058
|
}
|
|
82593
83059
|
const snap = latest.snap;
|
|
82594
|
-
const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs,
|
|
83060
|
+
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
83061
|
const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
|
|
82596
83062
|
const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
|
|
82597
83063
|
return {
|
|
@@ -82618,14 +83084,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82618
83084
|
app.get("/projects/:name/technical-aeo/pages", async (request) => {
|
|
82619
83085
|
const project = resolveProject(app.db, request.params.name);
|
|
82620
83086
|
const targetFilters = [
|
|
82621
|
-
|
|
82622
|
-
|
|
82623
|
-
|
|
82624
|
-
|
|
83087
|
+
eq65(siteAuditSnapshots.projectId, project.id),
|
|
83088
|
+
eq65(runs.projectId, project.id),
|
|
83089
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83090
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82625
83091
|
notProbeRun()
|
|
82626
83092
|
];
|
|
82627
|
-
if (request.query.runId) targetFilters.push(
|
|
82628
|
-
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs,
|
|
83093
|
+
if (request.query.runId) targetFilters.push(eq65(siteAuditSnapshots.runId, request.query.runId));
|
|
83094
|
+
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
83095
|
if (!latest && request.query.runId) {
|
|
82630
83096
|
throw notFound("Site audit run", request.query.runId);
|
|
82631
83097
|
}
|
|
@@ -82633,14 +83099,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82633
83099
|
return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
|
|
82634
83100
|
}
|
|
82635
83101
|
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 =
|
|
83102
|
+
const conds = [eq65(siteAuditPages.projectId, project.id), eq65(siteAuditPages.runId, latest.runId)];
|
|
83103
|
+
if (statusFilter) conds.push(eq65(siteAuditPages.status, statusFilter));
|
|
83104
|
+
const where = and55(...conds);
|
|
82639
83105
|
const totalRow = app.db.select({ value: count2() }).from(siteAuditPages).where(where).get();
|
|
82640
83106
|
const total = totalRow?.value ?? 0;
|
|
82641
83107
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
82642
83108
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
82643
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
83109
|
+
const orderBy = request.query.sort === "score-desc" ? desc28(siteAuditPages.overallScore) : request.query.sort === "url" ? asc11(siteAuditPages.url) : asc11(siteAuditPages.overallScore);
|
|
82644
83110
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
82645
83111
|
const pages = rows.map((row) => ({
|
|
82646
83112
|
url: row.url,
|
|
@@ -82659,13 +83125,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82659
83125
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
82660
83126
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
82661
83127
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
82662
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
82663
|
-
|
|
82664
|
-
|
|
82665
|
-
|
|
82666
|
-
|
|
83128
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
|
|
83129
|
+
eq65(siteAuditSnapshots.projectId, project.id),
|
|
83130
|
+
eq65(runs.projectId, project.id),
|
|
83131
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83132
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
82667
83133
|
notProbeRun()
|
|
82668
|
-
)).orderBy(
|
|
83134
|
+
)).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
82669
83135
|
return { project: project.name, points: rows.reverse() };
|
|
82670
83136
|
});
|
|
82671
83137
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -82757,10 +83223,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82757
83223
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
82758
83224
|
snapshot.rootUrl
|
|
82759
83225
|
)?.nodeKey ?? null;
|
|
82760
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
82761
|
-
|
|
82762
|
-
|
|
82763
|
-
|
|
83226
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and55(
|
|
83227
|
+
eq65(siteCrawlGraphLayouts.projectId, project.id),
|
|
83228
|
+
eq65(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
83229
|
+
eq65(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
82764
83230
|
)).limit(1).get();
|
|
82765
83231
|
if (!persistedLayout) {
|
|
82766
83232
|
return {
|
|
@@ -82811,9 +83277,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82811
83277
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
82812
83278
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
82813
83279
|
const graphScope = [
|
|
82814
|
-
|
|
82815
|
-
|
|
82816
|
-
|
|
83280
|
+
eq65(siteCrawlGraphNodes.projectId, project.id),
|
|
83281
|
+
eq65(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
83282
|
+
eq65(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
82817
83283
|
];
|
|
82818
83284
|
const nodeRows = app.db.select({
|
|
82819
83285
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -82832,17 +83298,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82832
83298
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
82833
83299
|
x: siteCrawlGraphNodes.x,
|
|
82834
83300
|
y: siteCrawlGraphNodes.y
|
|
82835
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
82836
|
-
|
|
82837
|
-
|
|
82838
|
-
|
|
82839
|
-
|
|
82840
|
-
)).where(
|
|
83301
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and55(
|
|
83302
|
+
eq65(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
83303
|
+
eq65(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
83304
|
+
eq65(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
83305
|
+
eq65(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
83306
|
+
)).where(and55(...graphScope, lt10(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc11(siteCrawlGraphNodes.sampleRank)).all();
|
|
82841
83307
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
82842
83308
|
...row,
|
|
82843
83309
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
82844
83310
|
}));
|
|
82845
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
83311
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq65(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
82846
83312
|
const edges = app.db.select({
|
|
82847
83313
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
82848
83314
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -82850,22 +83316,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82850
83316
|
followable: siteCrawlGraphEdges.followable,
|
|
82851
83317
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
82852
83318
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
82853
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
82854
|
-
|
|
82855
|
-
|
|
82856
|
-
|
|
82857
|
-
|
|
83319
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and55(
|
|
83320
|
+
eq65(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
83321
|
+
eq65(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
83322
|
+
eq65(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
83323
|
+
eq65(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
82858
83324
|
lt10(graphSourceNode.sampleRank, maxNodes)
|
|
82859
|
-
)).innerJoin(graphTargetNode,
|
|
82860
|
-
|
|
82861
|
-
|
|
82862
|
-
|
|
82863
|
-
|
|
83325
|
+
)).innerJoin(graphTargetNode, and55(
|
|
83326
|
+
eq65(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
83327
|
+
eq65(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
83328
|
+
eq65(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
83329
|
+
eq65(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
82864
83330
|
lt10(graphTargetNode.sampleRank, maxNodes)
|
|
82865
|
-
)).where(
|
|
82866
|
-
|
|
82867
|
-
|
|
82868
|
-
|
|
83331
|
+
)).where(and55(
|
|
83332
|
+
eq65(siteCrawlGraphEdges.projectId, project.id),
|
|
83333
|
+
eq65(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
83334
|
+
eq65(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
82869
83335
|
lt10(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
82870
83336
|
graphLinkKindFilter
|
|
82871
83337
|
)).orderBy(asc11(siteCrawlGraphEdges.sampleRank)).all();
|
|
@@ -82959,15 +83425,15 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
82959
83425
|
let frontier = [focus.nodeKey];
|
|
82960
83426
|
let queryTruncated = false;
|
|
82961
83427
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
82962
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
82963
|
-
|
|
82964
|
-
|
|
82965
|
-
|
|
82966
|
-
|
|
83428
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and55(
|
|
83429
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83430
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83431
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83432
|
+
eq65(siteCrawlEdges.internal, true),
|
|
82967
83433
|
isNotNull5(siteCrawlEdges.targetNodeKey),
|
|
82968
83434
|
or14(
|
|
82969
|
-
|
|
82970
|
-
|
|
83435
|
+
inArray25(siteCrawlEdges.sourceNodeKey, frontier),
|
|
83436
|
+
inArray25(siteCrawlEdges.targetNodeKey, frontier)
|
|
82971
83437
|
)
|
|
82972
83438
|
)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
|
|
82973
83439
|
if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
|
|
@@ -83000,22 +83466,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83000
83466
|
frontier = [...next].sort();
|
|
83001
83467
|
}
|
|
83002
83468
|
const nodeKeys = [...distances.keys()];
|
|
83003
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
83004
|
-
|
|
83005
|
-
|
|
83006
|
-
|
|
83007
|
-
|
|
83469
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
|
|
83470
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83471
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83472
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83473
|
+
inArray25(siteCrawlPages.nodeKey, nodeKeys)
|
|
83008
83474
|
)).all();
|
|
83009
83475
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
83010
83476
|
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
|
-
|
|
83477
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and55(
|
|
83478
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83479
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83480
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83481
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83016
83482
|
isNotNull5(siteCrawlEdges.targetNodeKey),
|
|
83017
|
-
|
|
83018
|
-
|
|
83483
|
+
inArray25(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
83484
|
+
inArray25(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
83019
83485
|
)).limit(1).get();
|
|
83020
83486
|
if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
|
|
83021
83487
|
omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
|
|
@@ -83110,23 +83576,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83110
83576
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
83111
83577
|
let truncated = false;
|
|
83112
83578
|
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
|
-
|
|
83579
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and55(
|
|
83580
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83581
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83582
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83583
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83584
|
+
eq65(siteCrawlEdges.followable, true),
|
|
83585
|
+
eq65(siteCrawlEdges.relation, "anchor"),
|
|
83120
83586
|
isNotNull5(siteCrawlEdges.targetNodeKey),
|
|
83121
|
-
|
|
83587
|
+
inArray25(siteCrawlEdges.sourceNodeKey, frontier)
|
|
83122
83588
|
)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
83123
83589
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
83124
83590
|
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
|
-
|
|
83591
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and55(
|
|
83592
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83593
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83594
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83595
|
+
inArray25(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
83130
83596
|
)).all().map((row) => row.nodeKey));
|
|
83131
83597
|
const next = /* @__PURE__ */ new Set();
|
|
83132
83598
|
for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
|
|
@@ -83168,11 +83634,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83168
83634
|
pathEdges.unshift(step.edge);
|
|
83169
83635
|
pathKeys.unshift(step.nodeKey);
|
|
83170
83636
|
}
|
|
83171
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
83172
|
-
|
|
83173
|
-
|
|
83174
|
-
|
|
83175
|
-
|
|
83637
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and55(
|
|
83638
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83639
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83640
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83641
|
+
inArray25(siteCrawlPages.nodeKey, pathKeys)
|
|
83176
83642
|
)).all();
|
|
83177
83643
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
83178
83644
|
const pathDetection = templateDetectionOf(snapshot.templateDetection);
|
|
@@ -83201,25 +83667,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83201
83667
|
throw validationError("change must be all, added, removed, or changed");
|
|
83202
83668
|
}
|
|
83203
83669
|
const completeSnapshotFilters = [
|
|
83204
|
-
|
|
83205
|
-
|
|
83206
|
-
|
|
83207
|
-
|
|
83208
|
-
|
|
83670
|
+
eq65(siteCrawlSnapshots.projectId, project.id),
|
|
83671
|
+
eq65(runs.projectId, project.id),
|
|
83672
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83673
|
+
eq65(runs.status, RunStatuses.completed),
|
|
83674
|
+
eq65(siteCrawlSnapshots.complete, true),
|
|
83209
83675
|
notProbeRun()
|
|
83210
83676
|
];
|
|
83211
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
83677
|
+
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
83678
|
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,
|
|
83679
|
+
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
83680
|
...completeSnapshotFilters,
|
|
83215
83681
|
or14(
|
|
83216
83682
|
lt10(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
83217
|
-
|
|
83218
|
-
|
|
83683
|
+
and55(
|
|
83684
|
+
eq65(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
83219
83685
|
lt10(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
83220
83686
|
)
|
|
83221
83687
|
)
|
|
83222
|
-
)).orderBy(
|
|
83688
|
+
)).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
83223
83689
|
if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
|
|
83224
83690
|
if (!afterTarget) {
|
|
83225
83691
|
return { project: project.name, state: "unavailable", reason: "no-crawl", fromRunId: null, toRunId: null };
|
|
@@ -83450,18 +83916,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83450
83916
|
const visibleKeys = keyRows.slice(0, limit);
|
|
83451
83917
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
83452
83918
|
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
|
-
|
|
83919
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
|
|
83920
|
+
eq65(siteCrawlPages.projectId, scope.projectId),
|
|
83921
|
+
eq65(siteCrawlPages.runId, scope.runId),
|
|
83922
|
+
eq65(siteCrawlPages.attemptId, scope.attemptId),
|
|
83923
|
+
inArray25(siteCrawlPages.nodeKey, pageKeys)
|
|
83458
83924
|
)).all();
|
|
83459
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
83460
|
-
|
|
83461
|
-
|
|
83462
|
-
|
|
83463
|
-
|
|
83464
|
-
|
|
83925
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and55(
|
|
83926
|
+
eq65(siteCrawlEdges.projectId, scope.projectId),
|
|
83927
|
+
eq65(siteCrawlEdges.runId, scope.runId),
|
|
83928
|
+
eq65(siteCrawlEdges.attemptId, scope.attemptId),
|
|
83929
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83930
|
+
inArray25(siteCrawlEdges.edgeKey, linkKeys)
|
|
83465
83931
|
)).all();
|
|
83466
83932
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
83467
83933
|
const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -83586,30 +84052,30 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83586
84052
|
return { project: project.name, hasCrawlData: true, runId: snapshot.runId, total: 0, nextCursor: null, healthStateFilter: null, pages: [] };
|
|
83587
84053
|
}
|
|
83588
84054
|
const filters = [
|
|
83589
|
-
|
|
83590
|
-
|
|
83591
|
-
|
|
84055
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84056
|
+
eq65(siteCrawlPages.runId, snapshot.runId),
|
|
84057
|
+
eq65(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
83592
84058
|
];
|
|
83593
|
-
if (request.query.nodeKey) filters.push(
|
|
84059
|
+
if (request.query.nodeKey) filters.push(eq65(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
83594
84060
|
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(
|
|
84061
|
+
if (inventoryEligible != null) filters.push(eq65(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
84062
|
+
if (request.query.fetchState) filters.push(eq65(siteCrawlPages.fetchState, request.query.fetchState));
|
|
84063
|
+
if (request.query.indexabilityState) filters.push(eq65(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
84064
|
+
if (request.query.auditState) filters.push(eq65(siteCrawlPages.auditState, request.query.auditState));
|
|
83599
84065
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
83600
84066
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
83601
84067
|
const offset = decodeCursor(request.query.cursor);
|
|
83602
|
-
const orderBy = request.query.sort === "score-desc" ? [
|
|
84068
|
+
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
84069
|
let healthStateFilter = null;
|
|
83604
84070
|
if (healthState) {
|
|
83605
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
83606
|
-
|
|
83607
|
-
|
|
83608
|
-
|
|
84071
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and55(
|
|
84072
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84073
|
+
eq65(siteCrawlPages.runId, snapshot.runId),
|
|
84074
|
+
eq65(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
83609
84075
|
isNull11(siteCrawlPages.healthState)
|
|
83610
84076
|
)).limit(1).get();
|
|
83611
84077
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
83612
|
-
if (healthStateFilter === "applied") filters.push(
|
|
84078
|
+
if (healthStateFilter === "applied") filters.push(eq65(siteCrawlPages.healthState, healthState));
|
|
83613
84079
|
}
|
|
83614
84080
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
83615
84081
|
return {
|
|
@@ -83622,7 +84088,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83622
84088
|
pages: []
|
|
83623
84089
|
};
|
|
83624
84090
|
}
|
|
83625
|
-
const where =
|
|
84091
|
+
const where = and55(...filters);
|
|
83626
84092
|
const total = app.db.select({ value: count2() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
83627
84093
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
83628
84094
|
const nextOffset = offset + rows.length;
|
|
@@ -83653,10 +84119,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83653
84119
|
url: siteCrawlPages.url,
|
|
83654
84120
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
83655
84121
|
fetchState: siteCrawlPages.fetchState
|
|
83656
|
-
}).from(siteCrawlPages).where(
|
|
83657
|
-
|
|
83658
|
-
|
|
83659
|
-
|
|
84122
|
+
}).from(siteCrawlPages).where(and55(
|
|
84123
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84124
|
+
eq65(siteCrawlPages.runId, snapshot.runId),
|
|
84125
|
+
eq65(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
83660
84126
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
83661
84127
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
83662
84128
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -83735,17 +84201,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83735
84201
|
};
|
|
83736
84202
|
}
|
|
83737
84203
|
const filters = [
|
|
83738
|
-
|
|
83739
|
-
|
|
83740
|
-
|
|
83741
|
-
|
|
84204
|
+
eq65(siteCrawlEdges.projectId, project.id),
|
|
84205
|
+
eq65(siteCrawlEdges.runId, snapshot.runId),
|
|
84206
|
+
eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
84207
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83742
84208
|
linkKindFilter(linkKind)
|
|
83743
84209
|
];
|
|
83744
|
-
if (request.query.sourceUrl) filters.push(
|
|
83745
|
-
if (request.query.targetUrl) filters.push(
|
|
84210
|
+
if (request.query.sourceUrl) filters.push(eq65(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
84211
|
+
if (request.query.targetUrl) filters.push(eq65(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
83746
84212
|
const followable = parseBoolean2(request.query.followable);
|
|
83747
|
-
if (followable != null) filters.push(
|
|
83748
|
-
const where =
|
|
84213
|
+
if (followable != null) filters.push(eq65(siteCrawlEdges.followable, followable));
|
|
84214
|
+
const where = and55(...filters);
|
|
83749
84215
|
const total = app.db.select({ value: count2() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
83750
84216
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
83751
84217
|
const offset = decodeCursor(request.query.cursor);
|
|
@@ -83803,17 +84269,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83803
84269
|
};
|
|
83804
84270
|
}
|
|
83805
84271
|
const scope = [
|
|
83806
|
-
|
|
83807
|
-
|
|
83808
|
-
|
|
83809
|
-
|
|
84272
|
+
eq65(siteCrawlEdges.projectId, project.id),
|
|
84273
|
+
eq65(siteCrawlEdges.runId, snapshot.runId),
|
|
84274
|
+
eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
84275
|
+
eq65(siteCrawlEdges.internal, true),
|
|
83810
84276
|
linkKindFilter(linkKind)
|
|
83811
84277
|
];
|
|
83812
|
-
const inboundMatch = request.query.nodeKey && request.query.url ? or14(
|
|
83813
|
-
const outboundMatch = request.query.nodeKey && request.query.url ? or14(
|
|
84278
|
+
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);
|
|
84279
|
+
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
84280
|
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(
|
|
84281
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, inboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
84282
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, outboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
83817
84283
|
return {
|
|
83818
84284
|
project: project.name,
|
|
83819
84285
|
hasCrawlData: true,
|
|
@@ -83846,11 +84312,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83846
84312
|
if (!snapshot.attemptId) {
|
|
83847
84313
|
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
84314
|
}
|
|
83849
|
-
const where =
|
|
83850
|
-
|
|
83851
|
-
|
|
83852
|
-
|
|
83853
|
-
|
|
84315
|
+
const where = and55(
|
|
84316
|
+
eq65(siteCrawlFindings.projectId, project.id),
|
|
84317
|
+
eq65(siteCrawlFindings.runId, snapshot.runId),
|
|
84318
|
+
eq65(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
84319
|
+
eq65(siteCrawlFindings.findingType, "dead-link"),
|
|
83854
84320
|
// Belt and braces on the client-facing surface: a row without a status
|
|
83855
84321
|
// code is not evidence of a broken link, and this response is where such
|
|
83856
84322
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -83899,19 +84365,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83899
84365
|
createdAt: runs.createdAt,
|
|
83900
84366
|
startedAt: runs.startedAt,
|
|
83901
84367
|
finishedAt: runs.finishedAt
|
|
83902
|
-
}).from(runs).where(
|
|
83903
|
-
|
|
83904
|
-
|
|
84368
|
+
}).from(runs).where(and55(
|
|
84369
|
+
eq65(runs.projectId, project.id),
|
|
84370
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83905
84371
|
notProbeRun()
|
|
83906
|
-
)).orderBy(
|
|
84372
|
+
)).orderBy(desc28(runs.createdAt), desc28(runs.id)).limit(limit).all();
|
|
83907
84373
|
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
|
-
|
|
84374
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(
|
|
84375
|
+
eq65(siteCrawlSnapshots.projectId, project.id),
|
|
84376
|
+
eq65(runs.projectId, project.id),
|
|
84377
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
84378
|
+
inArray25(runs.status, SURFACEABLE_STATUSES),
|
|
83913
84379
|
notProbeRun(),
|
|
83914
|
-
|
|
84380
|
+
inArray25(siteCrawlSnapshots.runId, runIds)
|
|
83915
84381
|
)).all().map((row) => row.runId));
|
|
83916
84382
|
return {
|
|
83917
84383
|
project: project.name,
|
|
@@ -83924,21 +84390,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83924
84390
|
});
|
|
83925
84391
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
83926
84392
|
const project = resolveProject(app.db, request.params.name);
|
|
83927
|
-
const run = app.db.select().from(runs).where(
|
|
83928
|
-
|
|
83929
|
-
|
|
83930
|
-
|
|
84393
|
+
const run = app.db.select().from(runs).where(and55(
|
|
84394
|
+
eq65(runs.id, request.params.runId),
|
|
84395
|
+
eq65(runs.projectId, project.id),
|
|
84396
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83931
84397
|
notProbeRun()
|
|
83932
84398
|
)).get();
|
|
83933
84399
|
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
|
-
|
|
84400
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and55(
|
|
84401
|
+
eq65(siteCrawlAttempts.projectId, project.id),
|
|
84402
|
+
eq65(siteCrawlAttempts.runId, run.id)
|
|
84403
|
+
)).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
84404
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and55(
|
|
84405
|
+
eq65(siteCrawlGraphLayouts.projectId, project.id),
|
|
84406
|
+
eq65(siteCrawlGraphLayouts.runId, run.id),
|
|
84407
|
+
eq65(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
83942
84408
|
)).get() : void 0;
|
|
83943
84409
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
83944
84410
|
return {
|
|
@@ -83969,19 +84435,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83969
84435
|
};
|
|
83970
84436
|
});
|
|
83971
84437
|
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(
|
|
84438
|
+
const project = tx.select().from(projects).where(eq65(projects.name, request.params.name)).get();
|
|
83973
84439
|
if (!project) throw notFound("Project", request.params.name);
|
|
83974
|
-
const run = tx.select().from(runs).where(
|
|
83975
|
-
|
|
83976
|
-
|
|
83977
|
-
|
|
84440
|
+
const run = tx.select().from(runs).where(and55(
|
|
84441
|
+
eq65(runs.id, request.params.runId),
|
|
84442
|
+
eq65(runs.projectId, project.id),
|
|
84443
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
83978
84444
|
notProbeRun()
|
|
83979
84445
|
)).get();
|
|
83980
84446
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
83981
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
83982
|
-
|
|
83983
|
-
|
|
83984
|
-
)).orderBy(
|
|
84447
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and55(
|
|
84448
|
+
eq65(siteCrawlAttempts.projectId, project.id),
|
|
84449
|
+
eq65(siteCrawlAttempts.runId, run.id)
|
|
84450
|
+
)).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
83985
84451
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
83986
84452
|
const base = {
|
|
83987
84453
|
project: project.name,
|
|
@@ -83994,11 +84460,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
83994
84460
|
if (!attempt) {
|
|
83995
84461
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
83996
84462
|
}
|
|
83997
|
-
const auditedWhere =
|
|
83998
|
-
|
|
83999
|
-
|
|
84000
|
-
|
|
84001
|
-
|
|
84463
|
+
const auditedWhere = and55(
|
|
84464
|
+
eq65(siteCrawlPages.projectId, project.id),
|
|
84465
|
+
eq65(siteCrawlPages.runId, run.id),
|
|
84466
|
+
eq65(siteCrawlPages.attemptId, attempt.id),
|
|
84467
|
+
eq65(siteCrawlPages.auditState, "success")
|
|
84002
84468
|
);
|
|
84003
84469
|
const pagesAudited = tx.select({ value: count2() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
84004
84470
|
if (state !== "collecting") {
|
|
@@ -84009,7 +84475,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
84009
84475
|
url: siteCrawlPages.url,
|
|
84010
84476
|
auditScore: siteCrawlPages.auditScore,
|
|
84011
84477
|
auditFields: siteCrawlPages.auditFields
|
|
84012
|
-
}).from(siteCrawlPages).where(
|
|
84478
|
+
}).from(siteCrawlPages).where(and55(
|
|
84013
84479
|
auditedWhere,
|
|
84014
84480
|
isNotNull5(siteCrawlPages.auditScore),
|
|
84015
84481
|
lt10(siteCrawlPages.auditScore, 70)
|
|
@@ -84045,13 +84511,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
84045
84511
|
status: runs.status,
|
|
84046
84512
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
84047
84513
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
84048
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
84049
|
-
|
|
84050
|
-
|
|
84051
|
-
)).where(
|
|
84052
|
-
|
|
84053
|
-
|
|
84054
|
-
|
|
84514
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and55(
|
|
84515
|
+
eq65(siteCrawlRunRequests.projectId, runs.projectId),
|
|
84516
|
+
eq65(siteCrawlRunRequests.runId, runs.id)
|
|
84517
|
+
)).where(and55(
|
|
84518
|
+
eq65(runs.projectId, project.id),
|
|
84519
|
+
eq65(runs.kind, RunKinds["site-audit"]),
|
|
84520
|
+
inArray25(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
84055
84521
|
)).get();
|
|
84056
84522
|
if (existing) {
|
|
84057
84523
|
if (existing.identityKey === identityKey) {
|
|
@@ -84105,7 +84571,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
84105
84571
|
// ../api-routes/src/research.ts
|
|
84106
84572
|
import crypto49 from "crypto";
|
|
84107
84573
|
import { z as z5 } from "zod";
|
|
84108
|
-
import { and as
|
|
84574
|
+
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
84575
|
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
84576
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
84111
84577
|
async function researchRoutes(app, opts) {
|
|
@@ -84123,7 +84589,7 @@ async function researchRoutes(app, opts) {
|
|
|
84123
84589
|
const input = parsed.data;
|
|
84124
84590
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
84125
84591
|
const requestHash2 = directResearchRequestHash(input);
|
|
84126
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
84592
|
+
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
84593
|
if (existingReceipt) {
|
|
84128
84594
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
84129
84595
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -84155,7 +84621,7 @@ async function researchRoutes(app, opts) {
|
|
|
84155
84621
|
const initiatedBy = researchPrincipal(request);
|
|
84156
84622
|
const decision = app.db.transaction((tx) => {
|
|
84157
84623
|
if (input.idempotencyKey) {
|
|
84158
|
-
const existing = tx.select().from(researchRuns).where(
|
|
84624
|
+
const existing = tx.select().from(researchRuns).where(and56(eq66(researchRuns.projectId, project.id), eq66(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
84159
84625
|
if (existing) {
|
|
84160
84626
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
84161
84627
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -84163,8 +84629,8 @@ async function researchRoutes(app, opts) {
|
|
|
84163
84629
|
}
|
|
84164
84630
|
if (initiatedBy?.limited) {
|
|
84165
84631
|
const { start, end, date } = utcDayBounds(now);
|
|
84166
|
-
const used = tx.select({ value: count3() }).from(researchRuns).where(
|
|
84167
|
-
|
|
84632
|
+
const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
|
|
84633
|
+
eq66(researchRuns.projectId, project.id),
|
|
84168
84634
|
gte17(researchRuns.createdAt, start),
|
|
84169
84635
|
lt11(researchRuns.createdAt, end),
|
|
84170
84636
|
sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -84195,15 +84661,15 @@ async function researchRoutes(app, opts) {
|
|
|
84195
84661
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
84196
84662
|
const requestHash2 = researchBatchRequestHash(input);
|
|
84197
84663
|
const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
|
|
84198
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
84199
|
-
|
|
84200
|
-
|
|
84664
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and56(
|
|
84665
|
+
eq66(researchRuns.projectId, project.id),
|
|
84666
|
+
eq66(researchRuns.idempotencyKey, receiptKeys[0])
|
|
84201
84667
|
)).get();
|
|
84202
84668
|
if (firstReceipt) {
|
|
84203
84669
|
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
|
-
|
|
84670
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and56(
|
|
84671
|
+
eq66(researchRuns.projectId, project.id),
|
|
84672
|
+
eq66(researchRuns.idempotencyKey, key)
|
|
84207
84673
|
)).get());
|
|
84208
84674
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
84209
84675
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -84218,8 +84684,8 @@ async function researchRoutes(app, opts) {
|
|
|
84218
84684
|
const decision = app.db.transaction((tx) => {
|
|
84219
84685
|
if (initiatedBy?.limited) {
|
|
84220
84686
|
const { start, end, date } = utcDayBounds(now);
|
|
84221
|
-
const used = tx.select({ value: count3() }).from(researchRuns).where(
|
|
84222
|
-
|
|
84687
|
+
const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
|
|
84688
|
+
eq66(researchRuns.projectId, project.id),
|
|
84223
84689
|
gte17(researchRuns.createdAt, start),
|
|
84224
84690
|
lt11(researchRuns.createdAt, end),
|
|
84225
84691
|
sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -84273,10 +84739,10 @@ async function researchRoutes(app, opts) {
|
|
|
84273
84739
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
84274
84740
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
84275
84741
|
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(
|
|
84742
|
+
const rows = app.db.select().from(researchRuns).where(and56(
|
|
84743
|
+
eq66(researchRuns.projectId, project.id),
|
|
84744
|
+
cursor ? or15(lt11(researchRuns.createdAt, cursor.createdAt), and56(eq66(researchRuns.createdAt, cursor.createdAt), lt11(researchRuns.id, cursor.id))) : void 0
|
|
84745
|
+
)).orderBy(desc29(researchRuns.createdAt), desc29(researchRuns.id)).limit(limit + 1).all();
|
|
84280
84746
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
84281
84747
|
const last = runs2.at(-1);
|
|
84282
84748
|
const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
|
|
@@ -84402,9 +84868,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
84402
84868
|
return stored;
|
|
84403
84869
|
}
|
|
84404
84870
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
84405
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
84406
|
-
|
|
84407
|
-
|
|
84871
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and56(
|
|
84872
|
+
eq66(measurementQueryTemplates.projectId, projectId),
|
|
84873
|
+
eq66(measurementQueryTemplates.id, selection.templateId)
|
|
84408
84874
|
)).get();
|
|
84409
84875
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
84410
84876
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -84413,7 +84879,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
84413
84879
|
}
|
|
84414
84880
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
84415
84881
|
if (bindingLocation) {
|
|
84416
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
84882
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq66(projects.id, projectId)).get();
|
|
84417
84883
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
84418
84884
|
}
|
|
84419
84885
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -84426,9 +84892,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
84426
84892
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
84427
84893
|
}
|
|
84428
84894
|
function getDetail(app, projectId, id) {
|
|
84429
|
-
const row = app.db.select().from(researchRuns).where(
|
|
84895
|
+
const row = app.db.select().from(researchRuns).where(and56(eq66(researchRuns.id, id), eq66(researchRuns.projectId, projectId))).get();
|
|
84430
84896
|
if (!row) throw notFound("Research run", id);
|
|
84431
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
84897
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq66(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
84432
84898
|
return { ...serializeRun(row), queries: queries2 };
|
|
84433
84899
|
}
|
|
84434
84900
|
function serializeRun(row) {
|
|
@@ -84458,7 +84924,7 @@ function utcDayBounds(now) {
|
|
|
84458
84924
|
|
|
84459
84925
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
84460
84926
|
import crypto50 from "crypto";
|
|
84461
|
-
import { and as
|
|
84927
|
+
import { and as and57, eq as eq67 } from "drizzle-orm";
|
|
84462
84928
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
84463
84929
|
return db.transaction((tx) => {
|
|
84464
84930
|
const run = tx.select({
|
|
@@ -84466,7 +84932,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
84466
84932
|
trigger: runs.trigger,
|
|
84467
84933
|
status: runs.status,
|
|
84468
84934
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
84469
|
-
}).from(runs).where(
|
|
84935
|
+
}).from(runs).where(and57(eq67(runs.projectId, input.projectId), eq67(runs.id, input.runId))).get();
|
|
84470
84936
|
if (!run) throw notFound("Run", input.runId);
|
|
84471
84937
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
84472
84938
|
return null;
|
|
@@ -84476,7 +84942,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
84476
84942
|
if (definition.queries.some((query) => query.queryClass !== (classifier?.classify(query.queryText) ?? null))) {
|
|
84477
84943
|
throw validationError("Query classes must match the captured identity and text.");
|
|
84478
84944
|
}
|
|
84479
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
84945
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and57(eq67(simpleMeasurementDefinitions.projectId, input.projectId), eq67(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
84480
84946
|
if (existing) {
|
|
84481
84947
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
84482
84948
|
const replayDefinition = {
|
|
@@ -84497,15 +84963,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
84497
84963
|
if (run.status !== RunStatuses.running) {
|
|
84498
84964
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
84499
84965
|
}
|
|
84500
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
84966
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq67(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
84501
84967
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
84502
84968
|
}
|
|
84503
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
84969
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq67(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
84504
84970
|
if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
84505
84971
|
throw validationError("Every captured query must belong to the run project.");
|
|
84506
84972
|
}
|
|
84507
84973
|
if (definition.competitors !== void 0) {
|
|
84508
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
84974
|
+
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq67(competitors.projectId, input.projectId)).all().map((competitor) => competitor.domain));
|
|
84509
84975
|
const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
|
|
84510
84976
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
84511
84977
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
@@ -85449,10 +85915,10 @@ var log = createLogger("IntelligenceService");
|
|
|
85449
85915
|
var runChronologyKey = sql27`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
85450
85916
|
function analyzableRunPredicates(projectId) {
|
|
85451
85917
|
return [
|
|
85452
|
-
|
|
85918
|
+
eq68(runs.projectId, projectId),
|
|
85453
85919
|
// Only this kind writes query_snapshots.
|
|
85454
|
-
|
|
85455
|
-
or16(
|
|
85920
|
+
eq68(runs.kind, RunKinds["answer-visibility"]),
|
|
85921
|
+
or16(eq68(runs.status, RunStatuses.completed), eq68(runs.status, RunStatuses.partial)),
|
|
85456
85922
|
// Defensive: RunCoordinator already skips probes before analyzeAndPersist
|
|
85457
85923
|
// is called, but a future call site invoking it directly for a probe must
|
|
85458
85924
|
// still not pollute the intelligence window.
|
|
@@ -85497,16 +85963,16 @@ var IntelligenceService = class {
|
|
|
85497
85963
|
* Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
|
|
85498
85964
|
*/
|
|
85499
85965
|
analyzeAndPersist(runId, projectId) {
|
|
85500
|
-
const currentRunRecord = this.db.select().from(runs).where(
|
|
85966
|
+
const currentRunRecord = this.db.select().from(runs).where(and58(eq68(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
|
|
85501
85967
|
if (!currentRunRecord) {
|
|
85502
85968
|
log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
|
|
85503
85969
|
return null;
|
|
85504
85970
|
}
|
|
85505
85971
|
const currentLocation = currentRunRecord.location ?? null;
|
|
85506
85972
|
const recentRuns = this.db.select().from(runs).where(
|
|
85507
|
-
|
|
85973
|
+
and58(
|
|
85508
85974
|
...analyzableRunPredicates(projectId),
|
|
85509
|
-
currentLocation === null ? isNull12(runs.location) :
|
|
85975
|
+
currentLocation === null ? isNull12(runs.location) : eq68(runs.location, currentLocation),
|
|
85510
85976
|
// Never look forward. The window is this run's own past, so
|
|
85511
85977
|
// re-analyzing a historical run compares it against its true
|
|
85512
85978
|
// predecessor rather than against sweeps that came after it.
|
|
@@ -85514,10 +85980,10 @@ var IntelligenceService = class {
|
|
|
85514
85980
|
// MEASUREMENT: only runs that actually wrote snapshots — see above.
|
|
85515
85981
|
// Index seek on idx_snapshots_run, short-circuited on the first row.
|
|
85516
85982
|
exists(
|
|
85517
|
-
this.db.select({ one: sql27`1` }).from(querySnapshots).where(
|
|
85983
|
+
this.db.select({ one: sql27`1` }).from(querySnapshots).where(eq68(querySnapshots.runId, runs.id))
|
|
85518
85984
|
)
|
|
85519
85985
|
)
|
|
85520
|
-
).orderBy(
|
|
85986
|
+
).orderBy(desc30(runs.finishedAt), desc30(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
85521
85987
|
const trackedCompetitors = this.loadTrackedCompetitors(projectId);
|
|
85522
85988
|
const currentRun = this.buildRunData(
|
|
85523
85989
|
runId,
|
|
@@ -85589,7 +86055,7 @@ var IntelligenceService = class {
|
|
|
85589
86055
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
85590
86056
|
*/
|
|
85591
86057
|
analyzeAndPersistGbp(runId, projectId) {
|
|
85592
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
86058
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq68(runs.id, runId)).get();
|
|
85593
86059
|
if (!runRow) {
|
|
85594
86060
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
85595
86061
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -85597,9 +86063,9 @@ var IntelligenceService = class {
|
|
|
85597
86063
|
}
|
|
85598
86064
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
85599
86065
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
85600
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
85601
|
-
|
|
85602
|
-
|
|
86066
|
+
const selected = this.db.select().from(gbpLocations).where(and58(
|
|
86067
|
+
eq68(gbpLocations.projectId, projectId),
|
|
86068
|
+
eq68(gbpLocations.selected, true),
|
|
85603
86069
|
gte18(gbpLocations.syncedAt, windowStart),
|
|
85604
86070
|
lte15(gbpLocations.syncedAt, windowEnd)
|
|
85605
86071
|
)).all();
|
|
@@ -85634,12 +86100,12 @@ var IntelligenceService = class {
|
|
|
85634
86100
|
}
|
|
85635
86101
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
85636
86102
|
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(
|
|
86103
|
+
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();
|
|
86104
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and58(eq68(gbpPlaceActions.projectId, projectId), eq68(gbpPlaceActions.locationName, locationName))).all();
|
|
86105
|
+
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();
|
|
86106
|
+
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and58(eq68(gbpLocations.projectId, projectId), eq68(gbpLocations.locationName, locationName))).get();
|
|
85641
86107
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
85642
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
86108
|
+
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
86109
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
85644
86110
|
const summary2 = buildGbpSummary({
|
|
85645
86111
|
locationName,
|
|
@@ -85675,7 +86141,7 @@ var IntelligenceService = class {
|
|
|
85675
86141
|
/** Build the month-over-month keyword series for a location from the
|
|
85676
86142
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
85677
86143
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
85678
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
86144
|
+
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
86145
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
85680
86146
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
85681
86147
|
const recentMonth = months[0] ?? null;
|
|
@@ -85706,7 +86172,7 @@ var IntelligenceService = class {
|
|
|
85706
86172
|
*/
|
|
85707
86173
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
85708
86174
|
const covered = new Set(coveredLocationNames);
|
|
85709
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
86175
|
+
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
86176
|
const staleIds = [];
|
|
85711
86177
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
85712
86178
|
for (const row of existing) {
|
|
@@ -85717,7 +86183,7 @@ var IntelligenceService = class {
|
|
|
85717
86183
|
}
|
|
85718
86184
|
this.db.transaction((tx) => {
|
|
85719
86185
|
for (const id of staleIds) {
|
|
85720
|
-
tx.delete(insights).where(
|
|
86186
|
+
tx.delete(insights).where(eq68(insights.id, id)).run();
|
|
85721
86187
|
}
|
|
85722
86188
|
for (const insight of gbpInsights) {
|
|
85723
86189
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -85803,7 +86269,7 @@ var IntelligenceService = class {
|
|
|
85803
86269
|
* create per run + aggregate). DB is left untouched.
|
|
85804
86270
|
*/
|
|
85805
86271
|
backfill(projectName, opts, onProgress) {
|
|
85806
|
-
const project = this.db.select().from(projects).where(
|
|
86272
|
+
const project = this.db.select().from(projects).where(eq68(projects.name, projectName)).get();
|
|
85807
86273
|
if (!project) {
|
|
85808
86274
|
throw new Error(`Project "${projectName}" not found`);
|
|
85809
86275
|
}
|
|
@@ -85815,7 +86281,7 @@ var IntelligenceService = class {
|
|
|
85815
86281
|
}
|
|
85816
86282
|
sinceTimestamp = parsed;
|
|
85817
86283
|
}
|
|
85818
|
-
const allRuns = this.db.select().from(runs).where(
|
|
86284
|
+
const allRuns = this.db.select().from(runs).where(and58(...analyzableRunPredicates(project.id))).orderBy(asc12(runs.finishedAt)).all();
|
|
85819
86285
|
const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
|
|
85820
86286
|
const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
|
|
85821
86287
|
let startIdx = 0;
|
|
@@ -85839,14 +86305,14 @@ var IntelligenceService = class {
|
|
|
85839
86305
|
});
|
|
85840
86306
|
}
|
|
85841
86307
|
let processed = 0;
|
|
85842
|
-
let
|
|
86308
|
+
let skipped3 = 0;
|
|
85843
86309
|
let totalInsights = 0;
|
|
85844
86310
|
const isDryRun = opts?.dryRun === true;
|
|
85845
86311
|
const perRunDelta = [];
|
|
85846
86312
|
let wouldDeleteTotal = 0;
|
|
85847
86313
|
const existingByRunId = /* @__PURE__ */ new Map();
|
|
85848
86314
|
if (isDryRun && targetRuns.length > 0) {
|
|
85849
|
-
const rows = this.db.select({ runId: insights.runId }).from(insights).where(
|
|
86315
|
+
const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray26(insights.runId, targetRuns.map((r) => r.id))).all();
|
|
85850
86316
|
for (const r of rows) {
|
|
85851
86317
|
if (r.runId == null) continue;
|
|
85852
86318
|
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
@@ -85871,14 +86337,14 @@ var IntelligenceService = class {
|
|
|
85871
86337
|
}
|
|
85872
86338
|
onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: result.insights.length });
|
|
85873
86339
|
} else {
|
|
85874
|
-
|
|
86340
|
+
skipped3++;
|
|
85875
86341
|
onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: 0 });
|
|
85876
86342
|
}
|
|
85877
86343
|
}
|
|
85878
86344
|
if (isDryRun) {
|
|
85879
86345
|
return {
|
|
85880
86346
|
processed,
|
|
85881
|
-
skipped,
|
|
86347
|
+
skipped: skipped3,
|
|
85882
86348
|
totalInsights,
|
|
85883
86349
|
dryRun: true,
|
|
85884
86350
|
delta: {
|
|
@@ -85889,10 +86355,10 @@ var IntelligenceService = class {
|
|
|
85889
86355
|
}
|
|
85890
86356
|
};
|
|
85891
86357
|
}
|
|
85892
|
-
return { processed, skipped, totalInsights };
|
|
86358
|
+
return { processed, skipped: skipped3, totalInsights };
|
|
85893
86359
|
}
|
|
85894
86360
|
loadTrackedCompetitors(projectId) {
|
|
85895
|
-
return this.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
86361
|
+
return this.db.select({ domain: competitors.domain }).from(competitors).where(eq68(competitors.projectId, projectId)).all().map((r) => r.domain);
|
|
85896
86362
|
}
|
|
85897
86363
|
/**
|
|
85898
86364
|
* Which of `runIds` actually wrote query snapshots. One grouped read, so
|
|
@@ -85901,7 +86367,7 @@ var IntelligenceService = class {
|
|
|
85901
86367
|
*/
|
|
85902
86368
|
runIdsWithSnapshots(runIds) {
|
|
85903
86369
|
if (runIds.length === 0) return /* @__PURE__ */ new Set();
|
|
85904
|
-
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(
|
|
86370
|
+
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray26(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
|
|
85905
86371
|
return new Set(rows.map((r) => r.runId));
|
|
85906
86372
|
}
|
|
85907
86373
|
/**
|
|
@@ -85923,15 +86389,15 @@ var IntelligenceService = class {
|
|
|
85923
86389
|
}
|
|
85924
86390
|
persistResult(result, runId, projectId) {
|
|
85925
86391
|
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(
|
|
86392
|
+
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
86393
|
for (const row of existingInsights) {
|
|
85928
86394
|
if (row.dismissed) {
|
|
85929
86395
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
85930
86396
|
}
|
|
85931
86397
|
}
|
|
85932
86398
|
this.db.transaction((tx) => {
|
|
85933
|
-
tx.delete(insights).where(
|
|
85934
|
-
tx.delete(healthSnapshots).where(
|
|
86399
|
+
tx.delete(insights).where(eq68(insights.runId, runId)).run();
|
|
86400
|
+
tx.delete(healthSnapshots).where(eq68(healthSnapshots.runId, runId)).run();
|
|
85935
86401
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
85936
86402
|
for (const insight of result.insights) {
|
|
85937
86403
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -85978,8 +86444,8 @@ var IntelligenceService = class {
|
|
|
85978
86444
|
clicks: gscSearchData.clicks,
|
|
85979
86445
|
impressions: gscSearchData.impressions,
|
|
85980
86446
|
position: gscSearchData.position
|
|
85981
|
-
}).from(gscSearchData).where(
|
|
85982
|
-
|
|
86447
|
+
}).from(gscSearchData).where(and58(
|
|
86448
|
+
eq68(gscSearchData.projectId, projectId),
|
|
85983
86449
|
gte18(gscSearchData.date, window.startDate),
|
|
85984
86450
|
lte15(gscSearchData.date, window.endDate)
|
|
85985
86451
|
)).all();
|
|
@@ -86032,21 +86498,21 @@ var IntelligenceService = class {
|
|
|
86032
86498
|
const key = row.query.toLowerCase();
|
|
86033
86499
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
86034
86500
|
}
|
|
86035
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
86501
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq68(projects.id, projectId)).get();
|
|
86036
86502
|
const locationCount = Math.max(
|
|
86037
86503
|
1,
|
|
86038
86504
|
(projectRow?.locations ?? []).length
|
|
86039
86505
|
);
|
|
86040
86506
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
86041
86507
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
86042
|
-
|
|
86043
|
-
|
|
86044
|
-
|
|
86045
|
-
or16(
|
|
86508
|
+
and58(
|
|
86509
|
+
eq68(runs.projectId, projectId),
|
|
86510
|
+
eq68(runs.kind, RunKinds["answer-visibility"]),
|
|
86511
|
+
or16(eq68(runs.status, "completed"), eq68(runs.status, "partial")),
|
|
86046
86512
|
// Defensive — see top of file.
|
|
86047
86513
|
ne11(runs.trigger, RunTriggers.probe)
|
|
86048
86514
|
)
|
|
86049
|
-
).orderBy(
|
|
86515
|
+
).orderBy(desc30(runs.createdAt), desc30(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
86050
86516
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
86051
86517
|
const recentRunIds = [];
|
|
86052
86518
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -86062,7 +86528,7 @@ var IntelligenceService = class {
|
|
|
86062
86528
|
const haveHistory = recentRunIds.length > 0;
|
|
86063
86529
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
86064
86530
|
if (haveHistory) {
|
|
86065
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
86531
|
+
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
86532
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
86067
86533
|
for (const row of priorRows) {
|
|
86068
86534
|
if (!row.runId) continue;
|
|
@@ -86091,7 +86557,7 @@ var IntelligenceService = class {
|
|
|
86091
86557
|
});
|
|
86092
86558
|
}
|
|
86093
86559
|
buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
|
|
86094
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
86560
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq68(projects.id, projectId)).get();
|
|
86095
86561
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
86096
86562
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
86097
86563
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -86109,7 +86575,7 @@ var IntelligenceService = class {
|
|
|
86109
86575
|
citedDomains: querySnapshots.citedDomains,
|
|
86110
86576
|
rawResponse: querySnapshots.rawResponse,
|
|
86111
86577
|
snapshotLocation: querySnapshots.location
|
|
86112
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
86578
|
+
}).from(querySnapshots).leftJoin(queries, eq68(querySnapshots.queryId, queries.id)).where(eq68(querySnapshots.runId, runId)).all();
|
|
86113
86579
|
const snapshots = [];
|
|
86114
86580
|
let orphanCount = 0;
|
|
86115
86581
|
for (const r of rows) {
|
|
@@ -86170,6 +86636,7 @@ export {
|
|
|
86170
86636
|
schedules,
|
|
86171
86637
|
insightNotifyState,
|
|
86172
86638
|
doctorHealthState,
|
|
86639
|
+
siteLivenessState,
|
|
86173
86640
|
notifications,
|
|
86174
86641
|
googleConnections,
|
|
86175
86642
|
gscSearchData,
|
|
@@ -86318,6 +86785,9 @@ export {
|
|
|
86318
86785
|
DEFAULT_BOT_LIST,
|
|
86319
86786
|
generateWorkerScript,
|
|
86320
86787
|
generateWranglerToml,
|
|
86788
|
+
SITE_REACHABILITY_CHECK_ID,
|
|
86789
|
+
SITE_REACHABILITY_CHECKS,
|
|
86790
|
+
runChecks,
|
|
86321
86791
|
executeDiscovery,
|
|
86322
86792
|
markSessionFailed,
|
|
86323
86793
|
captureSimpleMeasurementDefinition,
|