@canonry/canonry 7.24.0 → 7.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +1 -1
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +6 -0
  3. package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +1 -0
  4. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +3 -2
  5. package/assets/assets/{AuditHistoryPanel-DKKnX8IT.js → AuditHistoryPanel-B-9DLSmD.js} +1 -1
  6. package/assets/assets/{BacklinksPage-DNbur8am.js → BacklinksPage-DPssWU5-.js} +1 -1
  7. package/assets/assets/{HistoryPage-DahtHlpM.js → HistoryPage-BAPUoHjI.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-BoNDtLfp.js → MeasurementPropertyPage-aTs5l1OV.js} +1 -1
  9. package/assets/assets/{ProjectPage--ZlpQduH.js → ProjectPage-sVY1ETmt.js} +1 -1
  10. package/assets/assets/{RunsPage-DKB2CHTM.js → RunsPage-D0q7ifK3.js} +1 -1
  11. package/assets/assets/{SettingsPage-lnmMZzEj.js → SettingsPage-Oo861eRm.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-BBjucRed.js → SiteHealthSection-BMdk3Och.js} +3 -3
  13. package/assets/assets/{TrafficPage-DNyTmPLd.js → TrafficPage-Ciot_0lN.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-D1zKZHSh.js → TrafficSourceDetailPage-D7tWadfJ.js} +1 -1
  15. package/assets/assets/{extract-error-message-v0b8P-wc.js → extract-error-message-Bb4azvlK.js} +1 -1
  16. package/assets/assets/{index-CfaBRiBG.js → index-ClA5Nf2V.js} +18 -18
  17. package/assets/assets/{react-sigma_core.esm.min-BZSJoRrV.js → react-sigma_core.esm.min-C-Y9Z74U.js} +1 -1
  18. package/assets/assets/{v2-overview-adapter-DXAl-O_h.js → v2-overview-adapter-D2S25ivE.js} +1 -1
  19. package/assets/index.html +1 -1
  20. package/dist/{chunk-DS2ZKB3T.js → chunk-6AOIFHC6.js} +1 -1
  21. package/dist/{chunk-JJBVYL45.js → chunk-BMBHREYM.js} +2 -2
  22. package/dist/{chunk-7Q7QCBJ3.js → chunk-I3546GTK.js} +2 -2
  23. package/dist/{chunk-YCPTP2NC.js → chunk-NOA7KJDB.js} +3 -3
  24. package/dist/{chunk-SG3MPCDL.js → chunk-NX257AKC.js} +145 -25
  25. package/dist/{chunk-IM2BFAAT.js → chunk-PFEQWGJN.js} +7 -5
  26. package/dist/{chunk-O5UMRQVN.js → chunk-PKQF7BV2.js} +759 -610
  27. package/dist/cli.js +14 -14
  28. package/dist/{demo-server-C7UGXXN7.js → demo-server-RUZWYXSW.js} +4 -4
  29. package/dist/index.js +6 -6
  30. package/dist/{intelligence-service-37TX4YV7.js → intelligence-service-TYSVXO3S.js} +2 -2
  31. package/dist/mcp.js +4 -4
  32. package/package.json +11 -11
@@ -864,10 +864,10 @@ import {
864
864
  wordpressSchemaDeployResultDtoSchema,
865
865
  wordpressSchemaStatusResultDtoSchema,
866
866
  wordpressStatusDtoSchema
867
- } from "./chunk-IM2BFAAT.js";
867
+ } from "./chunk-PFEQWGJN.js";
868
868
 
869
869
  // src/intelligence-service.ts
870
- import { eq as eq89, desc as desc41, asc as asc18, and as and75, ne as ne16, or as or22, inArray as inArray36, gte as gte24, lte as lte20, isNull as isNull17, sql as sql32, exists as exists2 } from "drizzle-orm";
870
+ import { eq as eq90, desc as desc42, asc as asc18, and as and76, ne as ne16, or as or23, inArray as inArray37, gte as gte24, lte as lte20, isNull as isNull17, sql as sql32, exists as exists2 } from "drizzle-orm";
871
871
 
872
872
  // ../db/src/client.ts
873
873
  import { mkdirSync } from "fs";
@@ -1771,7 +1771,34 @@ var doctorHealthState = sqliteTable("doctor_health_state", {
1771
1771
  * as changed, so the trigger falls back to the code rule for one pass rather
1772
1772
  * than paging every already-degraded project the moment this ships.
1773
1773
  */
1774
- failingSignature: text("failing_signature")
1774
+ failingSignature: text("failing_signature"),
1775
+ /**
1776
+ * The same set with each entry's check id: `status:code:id`, worst first,
1777
+ * with `warn:health.no-signal:health.no-signal` standing in for a pass where
1778
+ * no check could be graded. A check changes its code when its severity
1779
+ * changes, so only the id can tell a check that eased (fail to warn) from
1780
+ * one that cleared while another started. Kept beside `failingSignature`
1781
+ * rather than in it: a rolled-back release compares that column as a string
1782
+ * and would page every degraded project if its format changed. That release
1783
+ * leaves this column behind, so the notifier trusts it only while its
1784
+ * entries still spell `failingSignature`, and otherwise compares by code for
1785
+ * one pass. NULL on rows written before this column existed.
1786
+ */
1787
+ failingChecks: text("failing_checks"),
1788
+ /**
1789
+ * What the operator has been told is failing, in the `failingChecks` form:
1790
+ * the set reported by the last health alert a webhook accepted. A pass that
1791
+ * only clears checks sends nothing and still advances `failingChecks`, so
1792
+ * the next alert compares against this to list what cleared since the alert
1793
+ * the operator last saw. Delivery retry state lives in `pendingSignature`:
1794
+ * an earlier delivered set cannot acknowledge a later recurrence. NULL
1795
+ * while no alert has been delivered since this column exists, and reset to
1796
+ * NULL when an older release has written the row since; the notifier then
1797
+ * compares against the previous pass alone.
1798
+ */
1799
+ notifiedSignature: text("notified_signature"),
1800
+ /** Triggering breaches owed by an unaccepted degraded alert, as `status:code:id`. */
1801
+ pendingSignature: text("pending_signature")
1775
1802
  });
1776
1803
  var siteLivenessState = sqliteTable("site_liveness_state", {
1777
1804
  projectId: text("project_id").primaryKey(),
@@ -8511,6 +8538,44 @@ var MIGRATION_VERSIONS = [
8511
8538
  `CREATE INDEX IF NOT EXISTS idx_ga_search_landing_pages_order ON ga_search_landing_pages(project_id, window_key, clicks)`,
8512
8539
  `CREATE INDEX IF NOT EXISTS idx_ga_search_landing_pages_run ON ga_search_landing_pages(sync_run_id)`
8513
8540
  ]
8541
+ },
8542
+ {
8543
+ // The failing set the last DELIVERED health alert reported. A pass that
8544
+ // only clears checks now sends nothing, so the stored signature moves on
8545
+ // without the operator hearing about it; the next alert reads this to say
8546
+ // what cleared since the one they last saw, and an alert no webhook
8547
+ // accepted is sent again. Nullable with no default: existing rows, and
8548
+ // rows an older writer keeps updating, read as "no alert on record" and
8549
+ // fall back to the previous pass.
8550
+ version: 177,
8551
+ name: "doctor-health-notified-signature",
8552
+ statements: [
8553
+ `ALTER TABLE doctor_health_state ADD COLUMN notified_signature TEXT`
8554
+ ]
8555
+ },
8556
+ {
8557
+ // The last pass's failing set with check ids (`status:code:id`). A check
8558
+ // changes its code when its severity changes, so without the id a check
8559
+ // easing from fail to warn read as one entry cleared and another gained,
8560
+ // and paged. It sits beside `failing_signature` instead of changing that
8561
+ // column's format: an older binary compares it as a string, and would page
8562
+ // every degraded project after a rollback. Nullable with no default: rows
8563
+ // without it are compared by code for one pass.
8564
+ version: 178,
8565
+ name: "doctor-health-failing-checks",
8566
+ statements: [
8567
+ `ALTER TABLE doctor_health_state ADD COLUMN failing_checks TEXT`
8568
+ ]
8569
+ },
8570
+ {
8571
+ // Delivery debt is separate from the last delivered clearance baseline:
8572
+ // a check can clear quietly and recur with a code that baseline knows.
8573
+ // NULL preserves existing rows without inventing an undelivered alert.
8574
+ version: 179,
8575
+ name: "doctor-health-pending-signature",
8576
+ statements: [
8577
+ `ALTER TABLE doctor_health_state ADD COLUMN pending_signature TEXT`
8578
+ ]
8514
8579
  }
8515
8580
  ];
8516
8581
  var AERO_LEDGER_REBUILDS = [
@@ -15903,6 +15968,8 @@ var AlertFieldLabels = {
15903
15968
  check: "Check",
15904
15969
  previously: "Was",
15905
15970
  remediation: "What to do",
15971
+ newlyFailing: "New",
15972
+ cleared: "Cleared",
15906
15973
  alsoFailing: "Also failing",
15907
15974
  event: "Event",
15908
15975
  run: "Run",
@@ -15940,7 +16007,24 @@ function healthView(payload) {
15940
16007
  if (health.remediation) {
15941
16008
  fields.push({ label: AlertFieldLabels.remediation, value: health.remediation });
15942
16009
  }
15943
- const others = health.failing.filter((check2) => check2.code !== health.code);
16010
+ const added = health.added ?? [];
16011
+ if (added.length > 0) {
16012
+ const statusOf = /* @__PURE__ */ new Map();
16013
+ for (const check2 of health.failing) if (!statusOf.has(check2.code)) statusOf.set(check2.code, check2.status);
16014
+ fields.push({
16015
+ label: `${AlertFieldLabels.newlyFailing} (${added.length})`,
16016
+ value: bulletList(added.map((code) => `${statusOf.get(code) ?? health.status}: ${code}`))
16017
+ });
16018
+ }
16019
+ const cleared = health.cleared ?? [];
16020
+ if (cleared.length > 0) {
16021
+ fields.push({
16022
+ label: `${AlertFieldLabels.cleared} (${cleared.length})`,
16023
+ value: bulletList(cleared)
16024
+ });
16025
+ }
16026
+ const newlyFailing = new Set(added);
16027
+ const others = health.failing.filter((check2) => check2.code !== health.code && !newlyFailing.has(check2.code));
15944
16028
  if (others.length > 0) {
15945
16029
  fields.push({
15946
16030
  label: `${AlertFieldLabels.alsoFailing} (${others.length})`,
@@ -47459,7 +47543,9 @@ async function telemetryRoutes(app, opts) {
47459
47543
  opts.recordOnboardingEvent?.(normalizeOnboardingEventForCollection(parsed.data));
47460
47544
  return reply.status(202).send({ accepted: Boolean(opts.recordOnboardingEvent) });
47461
47545
  });
47462
- app.post("/telemetry/ui", async (request, reply) => {
47546
+ app.post("/telemetry/ui", {
47547
+ config: { readSemantic: true }
47548
+ }, async (request, reply) => {
47463
47549
  const parsed = uiTelemetryEventSchema.safeParse(request.body);
47464
47550
  if (!parsed.success) {
47465
47551
  throw validationError("Invalid UI telemetry event", {
@@ -62962,7 +63048,7 @@ async function wordpressRoutes(app, opts) {
62962
63048
 
62963
63049
  // ../api-routes/src/backlinks.ts
62964
63050
  import crypto45 from "crypto";
62965
- import { and as and56, asc as asc14, desc as desc34, eq as eq65, sql as sql25 } from "drizzle-orm";
63051
+ import { and as and57, asc as asc14, desc as desc34, eq as eq66, sql as sql25 } from "drizzle-orm";
62966
63052
 
62967
63053
  // ../integration-commoncrawl/src/constants.ts
62968
63054
  import os2 from "os";
@@ -63393,6 +63479,22 @@ function backlinkCrawlerExclusionClause() {
63393
63479
  return combined;
63394
63480
  }
63395
63481
 
63482
+ // ../api-routes/src/backlinks-source-state.ts
63483
+ import { and as and56, eq as eq65, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
63484
+ function readCommonCrawlSourceState(db, projectId) {
63485
+ const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
63486
+ eq65(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
63487
+ isNotNull8(ccReleaseSyncs.queryFinishedAt)
63488
+ )).all();
63489
+ const hasReadyRelease = completedReleases.length > 0;
63490
+ const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
63491
+ const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and56(
63492
+ eq65(backlinkSummaries.projectId, projectId),
63493
+ eq65(backlinkSummaries.source, BacklinkSources.commoncrawl)
63494
+ )).limit(1).get() !== void 0;
63495
+ return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
63496
+ }
63497
+
63396
63498
  // ../api-routes/src/backlinks.ts
63397
63499
  var BACKLINKS_UNSUPPORTED_MESSAGE = "Backlinks sync and install are only available from a local canonry install. Run `canonry backlinks install` locally to use this feature.";
63398
63500
  var NON_TERMINAL_SYNC_STATUSES = /* @__PURE__ */ new Set([
@@ -63459,11 +63561,11 @@ function mapRunRow(row) {
63459
63561
  }
63460
63562
  function latestSummaryForProject(db, projectId, source, release) {
63461
63563
  const conditions = [
63462
- eq65(backlinkSummaries.projectId, projectId),
63463
- eq65(backlinkSummaries.source, source)
63564
+ eq66(backlinkSummaries.projectId, projectId),
63565
+ eq66(backlinkSummaries.source, source)
63464
63566
  ];
63465
- if (release) conditions.push(eq65(backlinkSummaries.release, release));
63466
- return db.select().from(backlinkSummaries).where(and56(...conditions)).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63567
+ if (release) conditions.push(eq66(backlinkSummaries.release, release));
63568
+ return db.select().from(backlinkSummaries).where(and57(...conditions)).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63467
63569
  }
63468
63570
  function parseExcludeCrawlers(value) {
63469
63571
  if (!value) return false;
@@ -63471,12 +63573,12 @@ function parseExcludeCrawlers(value) {
63471
63573
  return lower === "1" || lower === "true" || lower === "yes";
63472
63574
  }
63473
63575
  function computeFilteredSummary(db, base) {
63474
- const baseDomainCondition = and56(
63475
- eq65(backlinkDomains.projectId, base.projectId),
63476
- eq65(backlinkDomains.source, base.source),
63477
- eq65(backlinkDomains.release, base.release)
63576
+ const baseDomainCondition = and57(
63577
+ eq66(backlinkDomains.projectId, base.projectId),
63578
+ eq66(backlinkDomains.source, base.source),
63579
+ eq66(backlinkDomains.release, base.release)
63478
63580
  );
63479
- const filteredCondition = and56(baseDomainCondition, backlinkCrawlerExclusionClause());
63581
+ const filteredCondition = and57(baseDomainCondition, backlinkCrawlerExclusionClause());
63480
63582
  const unfilteredAgg = db.select({
63481
63583
  count: sql25`count(*)`,
63482
63584
  total: sql25`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -63506,13 +63608,13 @@ function computeFilteredSummary(db, base) {
63506
63608
  };
63507
63609
  }
63508
63610
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
63509
- const summary3 = db.select().from(backlinkSummaries).where(and56(eq65(backlinkSummaries.projectId, projectId), eq65(backlinkSummaries.source, source))).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63611
+ const summary3 = db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, projectId), eq66(backlinkSummaries.source, source))).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63510
63612
  let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
63511
63613
  if (summary3 && excludeCrawlers) {
63512
- const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(and56(
63513
- eq65(backlinkDomains.projectId, projectId),
63514
- eq65(backlinkDomains.source, source),
63515
- eq65(backlinkDomains.release, summary3.release),
63614
+ const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(and57(
63615
+ eq66(backlinkDomains.projectId, projectId),
63616
+ eq66(backlinkDomains.source, source),
63617
+ eq66(backlinkDomains.release, summary3.release),
63516
63618
  backlinkCrawlerExclusionClause()
63517
63619
  )).get();
63518
63620
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -63527,8 +63629,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
63527
63629
  };
63528
63630
  }
63529
63631
  function computeSourceAvailability(db, project, excludeCrawlers) {
63530
- const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
63531
- const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
63632
+ const ccConnected = readCommonCrawlSourceState(db, project.id).connected;
63532
63633
  const sources = [
63533
63634
  buildSourceAvailability(db, project.id, BacklinkSources.commoncrawl, ccConnected, excludeCrawlers),
63534
63635
  buildSourceAvailability(db, project.id, BacklinkSources["bing-webmaster"], false, excludeCrawlers)
@@ -63583,7 +63684,7 @@ async function backlinksRoutes(app, opts) {
63583
63684
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
63584
63685
  );
63585
63686
  }
63586
- const existing = app.db.select().from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.release, release)).get();
63687
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.release, release)).get();
63587
63688
  const now = (/* @__PURE__ */ new Date()).toISOString();
63588
63689
  if (existing) {
63589
63690
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -63594,9 +63695,9 @@ async function backlinksRoutes(app, opts) {
63594
63695
  phaseDetail: null,
63595
63696
  error: null,
63596
63697
  updatedAt: now
63597
- }).where(eq65(ccReleaseSyncs.id, existing.id)).run();
63698
+ }).where(eq66(ccReleaseSyncs.id, existing.id)).run();
63598
63699
  opts.onReleaseSyncRequested(existing.id, release);
63599
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.id, existing.id)).get();
63700
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, existing.id)).get();
63600
63701
  return reply.status(200).send(mapSyncRow(refreshed));
63601
63702
  }
63602
63703
  const id = crypto45.randomUUID();
@@ -63608,7 +63709,7 @@ async function backlinksRoutes(app, opts) {
63608
63709
  updatedAt: now
63609
63710
  }).run();
63610
63711
  opts.onReleaseSyncRequested(id, release);
63611
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.id, id)).get();
63712
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, id)).get();
63612
63713
  return reply.status(201).send(mapSyncRow(inserted));
63613
63714
  });
63614
63715
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -63666,7 +63767,7 @@ async function backlinksRoutes(app, opts) {
63666
63767
  createdAt: now
63667
63768
  }).run();
63668
63769
  opts.onBacklinkExtractRequested(runId, project.id, release);
63669
- const run = app.db.select().from(runs).where(eq65(runs.id, runId)).get();
63770
+ const run = app.db.select().from(runs).where(eq66(runs.id, runId)).get();
63670
63771
  return reply.status(201).send(mapRunRow(run));
63671
63772
  });
63672
63773
  app.get(
@@ -63692,12 +63793,12 @@ async function backlinksRoutes(app, opts) {
63692
63793
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
63693
63794
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
63694
63795
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
63695
- const baseDomainCondition = and56(
63696
- eq65(backlinkDomains.projectId, project.id),
63697
- eq65(backlinkDomains.source, source),
63698
- eq65(backlinkDomains.release, targetRelease)
63796
+ const baseDomainCondition = and57(
63797
+ eq66(backlinkDomains.projectId, project.id),
63798
+ eq66(backlinkDomains.source, source),
63799
+ eq66(backlinkDomains.release, targetRelease)
63699
63800
  );
63700
- const domainCondition = excludeCrawlers ? and56(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
63801
+ const domainCondition = excludeCrawlers ? and57(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
63701
63802
  const totalRow = app.db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(domainCondition).get();
63702
63803
  const rows = app.db.select({
63703
63804
  linkingDomain: backlinkDomains.linkingDomain,
@@ -63721,7 +63822,7 @@ async function backlinksRoutes(app, opts) {
63721
63822
  async (request, reply) => {
63722
63823
  const project = resolveProject(app.db, request.params.name);
63723
63824
  const source = parseSourceParam(request.query.source);
63724
- const rows = app.db.select().from(backlinkSummaries).where(and56(eq65(backlinkSummaries.projectId, project.id), eq65(backlinkSummaries.source, source))).orderBy(asc14(backlinkSummaries.queriedAt)).all();
63825
+ const rows = app.db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, project.id), eq66(backlinkSummaries.source, source))).orderBy(asc14(backlinkSummaries.queriedAt)).all();
63725
63826
  const response = rows.map((r) => ({
63726
63827
  release: r.release,
63727
63828
  totalLinkingDomains: r.totalLinkingDomains,
@@ -63748,7 +63849,7 @@ async function backlinksRoutes(app, opts) {
63748
63849
  import crypto48 from "crypto";
63749
63850
  import { isIP } from "net";
63750
63851
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
63751
- import { and as and60, desc as desc35, eq as eq69, gte as gte16, inArray as inArray28, lte as lte17, sql as sql28 } from "drizzle-orm";
63852
+ import { and as and61, desc as desc35, eq as eq70, gte as gte16, inArray as inArray28, lte as lte17, sql as sql28 } from "drizzle-orm";
63752
63853
 
63753
63854
  // ../api-routes/src/cloudflare-worker-version.ts
63754
63855
  var CURRENT_CLOUDFLARE_WORKER_VERSION = "1.0.1";
@@ -80414,10 +80515,10 @@ async function ackCloudflareQueueMessages(client, options) {
80414
80515
 
80415
80516
  // ../api-routes/src/traffic-event-ingest.ts
80416
80517
  import crypto47 from "crypto";
80417
- import { and as and58, eq as eq67, gte as gte15, lt as lt12, lte as lte15, sql as sql27 } from "drizzle-orm";
80518
+ import { and as and59, eq as eq68, gte as gte15, lt as lt12, lte as lte15, sql as sql27 } from "drizzle-orm";
80418
80519
 
80419
80520
  // ../api-routes/src/raw-event-sample-retention.ts
80420
- import { and as and57, eq as eq66, lt as lt11, sql as sql26 } from "drizzle-orm";
80521
+ import { and as and58, eq as eq67, lt as lt11, sql as sql26 } from "drizzle-orm";
80421
80522
  var RAW_EVENT_SAMPLE_RETENTION_MS = 30 * 24 * 60 * 6e4;
80422
80523
  var RAW_EVENT_SAMPLE_RETENTION_SWEEP_INTERVAL_MS = 24 * 60 * 6e4;
80423
80524
  var CANONICAL_UTC_TIMESTAMP_GLOB = "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]Z";
@@ -80427,14 +80528,14 @@ function rawEventSampleRetentionCutoff(referenceAt) {
80427
80528
  return new Date(referenceMs - RAW_EVENT_SAMPLE_RETENTION_MS).toISOString();
80428
80529
  }
80429
80530
  function deleteExpiredRawEventSamples(db, cutoff, sourceId) {
80430
- const sourceScope = sourceId === void 0 ? void 0 : eq66(rawEventSamples.sourceId, sourceId);
80431
- db.delete(rawEventSamples).where(and57(sourceScope, sql26`julianday(${rawEventSamples.ts}) IS NULL`)).run();
80432
- db.delete(rawEventSamples).where(and57(
80531
+ const sourceScope = sourceId === void 0 ? void 0 : eq67(rawEventSamples.sourceId, sourceId);
80532
+ db.delete(rawEventSamples).where(and58(sourceScope, sql26`julianday(${rawEventSamples.ts}) IS NULL`)).run();
80533
+ db.delete(rawEventSamples).where(and58(
80433
80534
  sourceScope,
80434
80535
  sql26`${rawEventSamples.ts} GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
80435
80536
  lt11(rawEventSamples.ts, cutoff)
80436
80537
  )).run();
80437
- db.delete(rawEventSamples).where(and57(
80538
+ db.delete(rawEventSamples).where(and58(
80438
80539
  sourceScope,
80439
80540
  sql26`${rawEventSamples.ts} NOT GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
80440
80541
  sql26`julianday(${rawEventSamples.ts}) < julianday(${cutoff})`
@@ -80483,16 +80584,16 @@ function writeTrafficEventBatch(opts) {
80483
80584
  let aiReferralHits = 0;
80484
80585
  let unknownHits = 0;
80485
80586
  opts.db.transaction((tx) => {
80486
- tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(eq67(trafficSources.id, opts.sourceId)).run();
80487
- const source = tx.select().from(trafficSources).where(eq67(trafficSources.id, opts.sourceId)).get();
80587
+ tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(eq68(trafficSources.id, opts.sourceId)).run();
80588
+ const source = tx.select().from(trafficSources).where(eq68(trafficSources.id, opts.sourceId)).get();
80488
80589
  opts.validateSource(source);
80489
80590
  const rawSampleCutoff = enforceRawEventSampleRetention(
80490
80591
  tx,
80491
80592
  opts.sourceId,
80492
80593
  opts.receivedAt
80493
80594
  );
80494
- tx.delete(trafficEventReceipts).where(and58(
80495
- eq67(trafficEventReceipts.sourceId, opts.sourceId),
80595
+ tx.delete(trafficEventReceipts).where(and59(
80596
+ eq68(trafficEventReceipts.sourceId, opts.sourceId),
80496
80597
  lte15(trafficEventReceipts.expiresAt, opts.receivedAt)
80497
80598
  )).run();
80498
80599
  const claimedEvents = [];
@@ -80691,8 +80792,8 @@ function writeTrafficEventBatch(opts) {
80691
80792
  if (!hour || hourlySampleLimit === 0) continue;
80692
80793
  let remaining = remainingSamplesByHour.get(hour.start);
80693
80794
  if (remaining === void 0) {
80694
- const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(and58(
80695
- eq67(rawEventSamples.sourceId, opts.sourceId),
80795
+ const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(and59(
80796
+ eq68(rawEventSamples.sourceId, opts.sourceId),
80696
80797
  gte15(rawEventSamples.ts, hour.start),
80697
80798
  lt12(rawEventSamples.ts, hour.end)
80698
80799
  )).get();
@@ -80733,7 +80834,7 @@ function writeTrafficEventBatch(opts) {
80733
80834
  remainingSamplesByHour.set(hour.start, remaining - 1);
80734
80835
  }
80735
80836
  }
80736
- tx.update(trafficSources).set(opts.sourceUpdate).where(eq67(trafficSources.id, opts.sourceId)).run();
80837
+ tx.update(trafficSources).set(opts.sourceUpdate).where(eq68(trafficSources.id, opts.sourceId)).run();
80737
80838
  });
80738
80839
  return {
80739
80840
  acceptedEvents,
@@ -80751,7 +80852,7 @@ function writeTrafficEventBatch(opts) {
80751
80852
  }
80752
80853
 
80753
80854
  // ../api-routes/src/traffic-sync-lease.ts
80754
- import { and as and59, eq as eq68, isNull as isNull13, lte as lte16, or as or17 } from "drizzle-orm";
80855
+ import { and as and60, eq as eq69, isNull as isNull13, lte as lte16, or as or18 } from "drizzle-orm";
80755
80856
  function leaseExpiresAt(now, ttlMs) {
80756
80857
  const nowMs = Date.parse(now);
80757
80858
  if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
@@ -80764,12 +80865,12 @@ function tryClaimTrafficSyncLease(input) {
80764
80865
  const changed = tx.update(trafficSources).set({
80765
80866
  syncLeaseOwner: input.owner,
80766
80867
  syncLeaseExpiresAt: expiresAt
80767
- }).where(and59(
80768
- eq68(trafficSources.id, input.sourceId),
80769
- or17(
80868
+ }).where(and60(
80869
+ eq69(trafficSources.id, input.sourceId),
80870
+ or18(
80770
80871
  isNull13(trafficSources.syncLeaseOwner),
80771
80872
  lte16(trafficSources.syncLeaseExpiresAt, input.now),
80772
- eq68(trafficSources.syncLeaseOwner, input.owner)
80873
+ eq69(trafficSources.syncLeaseOwner, input.owner)
80773
80874
  )
80774
80875
  )).run();
80775
80876
  return changed.changes === 1;
@@ -80780,9 +80881,9 @@ function releaseTrafficSyncLease(input) {
80780
80881
  const changed = tx.update(trafficSources).set({
80781
80882
  syncLeaseOwner: null,
80782
80883
  syncLeaseExpiresAt: null
80783
- }).where(and59(
80784
- eq68(trafficSources.id, input.sourceId),
80785
- eq68(trafficSources.syncLeaseOwner, input.owner)
80884
+ }).where(and60(
80885
+ eq69(trafficSources.id, input.sourceId),
80886
+ eq69(trafficSources.syncLeaseOwner, input.owner)
80786
80887
  )).run();
80787
80888
  return changed.changes === 1;
80788
80889
  }, { behavior: "immediate" });
@@ -80863,7 +80964,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
80863
80964
  const authenticated = authenticateCloudflareIngest(request, store);
80864
80965
  const params = request.params;
80865
80966
  if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
80866
- const source = db.select().from(trafficSources).where(eq69(trafficSources.id, authenticated.credential.sourceId)).get();
80967
+ const source = db.select().from(trafficSources).where(eq70(trafficSources.id, authenticated.credential.sourceId)).get();
80867
80968
  if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
80868
80969
  return `cloudflare-source:${authenticated.credential.sourceId}`;
80869
80970
  }
@@ -81106,7 +81207,7 @@ async function defaultResolveAccessToken(record) {
81106
81207
  );
81107
81208
  }
81108
81209
  function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
81109
- return db.select().from(trafficSources).where(eq69(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
81210
+ return db.select().from(trafficSources).where(eq70(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
81110
81211
  }
81111
81212
  function isAuthoritativeTrafficSource(db, source) {
81112
81213
  return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
@@ -81130,13 +81231,13 @@ function wordpressBaseUrlIdentity(value) {
81130
81231
  }
81131
81232
  }
81132
81233
  function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
81133
- const schedule = tx.select().from(schedules).where(and60(
81134
- eq69(schedules.projectId, projectId),
81135
- eq69(schedules.kind, SchedulableRunKinds["traffic-sync"])
81234
+ const schedule = tx.select().from(schedules).where(and61(
81235
+ eq70(schedules.projectId, projectId),
81236
+ eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
81136
81237
  )).get();
81137
81238
  if (schedule) {
81138
81239
  if (schedule.sourceId === sourceId) return { changed: false, created: false };
81139
- tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq69(schedules.id, schedule.id)).run();
81240
+ tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq70(schedules.id, schedule.id)).run();
81140
81241
  return { changed: true, created: false };
81141
81242
  }
81142
81243
  if (!createIfMissing) return { changed: false, created: false };
@@ -81187,10 +81288,10 @@ async function runBackfillTask(options) {
81187
81288
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
81188
81289
  try {
81189
81290
  app.db.transaction((tx) => {
81190
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq69(runs.id, runId)).run();
81191
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
81291
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
81292
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
81192
81293
  if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
81193
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq69(trafficSources.id, sourceRow2.id)).run();
81294
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
81194
81295
  }
81195
81296
  });
81196
81297
  } catch {
@@ -81217,7 +81318,7 @@ async function runBackfillTask(options) {
81217
81318
  let emptyOutcome;
81218
81319
  try {
81219
81320
  const completed = app.db.transaction((tx) => {
81220
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
81321
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
81221
81322
  const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2);
81222
81323
  if (stillAuthoritative) {
81223
81324
  enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
@@ -81226,7 +81327,7 @@ async function runBackfillTask(options) {
81226
81327
  status: RunStatuses.failed,
81227
81328
  error: "Traffic source was deactivated or reconfigured during backfill",
81228
81329
  finishedAt: finishedAt2
81229
- }).where(eq69(runs.id, runId)).run();
81330
+ }).where(eq70(runs.id, runId)).run();
81230
81331
  return Boolean(stillAuthoritative);
81231
81332
  });
81232
81333
  emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
@@ -81252,13 +81353,13 @@ async function runBackfillTask(options) {
81252
81353
  const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
81253
81354
  try {
81254
81355
  const commitOutcome = app.db.transaction((tx) => {
81255
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
81356
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
81256
81357
  if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
81257
81358
  tx.update(runs).set({
81258
81359
  status: RunStatuses.failed,
81259
81360
  error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
81260
81361
  finishedAt
81261
- }).where(eq69(runs.id, runId)).run();
81362
+ }).where(eq70(runs.id, runId)).run();
81262
81363
  return "wordpress-backfill-unsupported";
81263
81364
  }
81264
81365
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
@@ -81266,7 +81367,7 @@ async function runBackfillTask(options) {
81266
81367
  status: RunStatuses.failed,
81267
81368
  error: "Traffic source was deactivated or reconfigured during backfill",
81268
81369
  finishedAt
81269
- }).where(eq69(runs.id, runId)).run();
81370
+ }).where(eq70(runs.id, runId)).run();
81270
81371
  return "source-inactive";
81271
81372
  }
81272
81373
  const rawSampleCutoff = enforceRawEventSampleRetention(
@@ -81275,29 +81376,29 @@ async function runBackfillTask(options) {
81275
81376
  finishedAt
81276
81377
  );
81277
81378
  tx.delete(crawlerEventsHourly).where(
81278
- and60(
81279
- eq69(crawlerEventsHourly.sourceId, sourceRow2.id),
81379
+ and61(
81380
+ eq70(crawlerEventsHourly.sourceId, sourceRow2.id),
81280
81381
  gte16(crawlerEventsHourly.tsHour, windowStartIso),
81281
81382
  lte17(crawlerEventsHourly.tsHour, windowEndIso)
81282
81383
  )
81283
81384
  ).run();
81284
81385
  tx.delete(aiUserFetchEventsHourly).where(
81285
- and60(
81286
- eq69(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
81386
+ and61(
81387
+ eq70(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
81287
81388
  gte16(aiUserFetchEventsHourly.tsHour, windowStartIso),
81288
81389
  lte17(aiUserFetchEventsHourly.tsHour, windowEndIso)
81289
81390
  )
81290
81391
  ).run();
81291
81392
  tx.delete(aiReferralEventsHourly).where(
81292
- and60(
81293
- eq69(aiReferralEventsHourly.sourceId, sourceRow2.id),
81393
+ and61(
81394
+ eq70(aiReferralEventsHourly.sourceId, sourceRow2.id),
81294
81395
  gte16(aiReferralEventsHourly.tsHour, windowStartIso),
81295
81396
  lte17(aiReferralEventsHourly.tsHour, windowEndIso)
81296
81397
  )
81297
81398
  ).run();
81298
81399
  tx.delete(rawEventSamples).where(
81299
- and60(
81300
- eq69(rawEventSamples.sourceId, sourceRow2.id),
81400
+ and61(
81401
+ eq70(rawEventSamples.sourceId, sourceRow2.id),
81301
81402
  gte16(rawEventSamples.ts, windowStartIso),
81302
81403
  lte17(rawEventSamples.ts, windowEndIso)
81303
81404
  )
@@ -81489,8 +81590,8 @@ async function runBackfillTask(options) {
81489
81590
  lastEventIds: newRingBuffer,
81490
81591
  ...skipRecovered ? { skippedThroughAt: null } : {},
81491
81592
  updatedAt: finishedAt
81492
- }).where(eq69(trafficSources.id, sourceRow2.id)).run();
81493
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq69(runs.id, runId)).run();
81593
+ }).where(eq70(trafficSources.id, sourceRow2.id)).run();
81594
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
81494
81595
  return "committed";
81495
81596
  });
81496
81597
  reportBackfill(commitOutcome === "committed" ? {
@@ -81652,7 +81753,7 @@ async function trafficRoutes(app, opts) {
81652
81753
  };
81653
81754
  const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
81654
81755
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
81655
- const activeSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
81756
+ const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
81656
81757
  const sourceId = activeSource?.id ?? crypto48.randomUUID();
81657
81758
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
81658
81759
  if (activeSource) {
@@ -81662,7 +81763,7 @@ async function trafficRoutes(app, opts) {
81662
81763
  lastError: null,
81663
81764
  configJson: config,
81664
81765
  updatedAt: now
81665
- }).where(eq69(trafficSources.id, sourceId)).run();
81766
+ }).where(eq70(trafficSources.id, sourceId)).run();
81666
81767
  } else {
81667
81768
  tx.insert(trafficSources).values({
81668
81769
  id: sourceId,
@@ -81688,7 +81789,7 @@ async function trafficRoutes(app, opts) {
81688
81789
  entityId: sourceId
81689
81790
  });
81690
81791
  return {
81691
- sourceRow: tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get(),
81792
+ sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
81692
81793
  scheduleChanged: scheduleBinding.changed
81693
81794
  };
81694
81795
  }, { behavior: "immediate" });
@@ -81743,7 +81844,7 @@ async function trafficRoutes(app, opts) {
81743
81844
  const config = { baseUrl, username };
81744
81845
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
81745
81846
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
81746
- const activeSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
81847
+ const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
81747
81848
  const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
81748
81849
  const sourceId = endpointChanged ? crypto48.randomUUID() : activeSource?.id ?? crypto48.randomUUID();
81749
81850
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
@@ -81754,14 +81855,14 @@ async function trafficRoutes(app, opts) {
81754
81855
  lastError: null,
81755
81856
  configJson: config,
81756
81857
  updatedAt: now
81757
- }).where(eq69(trafficSources.id, sourceId)).run();
81858
+ }).where(eq70(trafficSources.id, sourceId)).run();
81758
81859
  } else {
81759
81860
  if (activeSource) {
81760
81861
  tx.update(trafficSources).set({
81761
81862
  status: TrafficSourceStatuses.archived,
81762
81863
  archivedAt: now,
81763
81864
  updatedAt: now
81764
- }).where(eq69(trafficSources.id, activeSource.id)).run();
81865
+ }).where(eq70(trafficSources.id, activeSource.id)).run();
81765
81866
  }
81766
81867
  tx.insert(trafficSources).values({
81767
81868
  id: sourceId,
@@ -81787,7 +81888,7 @@ async function trafficRoutes(app, opts) {
81787
81888
  entityId: sourceId
81788
81889
  });
81789
81890
  return {
81790
- sourceRow: tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get(),
81891
+ sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
81791
81892
  scheduleChanged: scheduleBinding.changed
81792
81893
  };
81793
81894
  }, { behavior: "immediate" });
@@ -81844,7 +81945,7 @@ async function trafficRoutes(app, opts) {
81844
81945
  const config = { projectId, teamId, environment };
81845
81946
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
81846
81947
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
81847
- const activeSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
81948
+ const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
81848
81949
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
81849
81950
  let row;
81850
81951
  if (activeSource) {
@@ -81854,8 +81955,8 @@ async function trafficRoutes(app, opts) {
81854
81955
  lastError: null,
81855
81956
  configJson: config,
81856
81957
  updatedAt: now
81857
- }).where(eq69(trafficSources.id, activeSource.id)).run();
81858
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, activeSource.id)).get();
81958
+ }).where(eq70(trafficSources.id, activeSource.id)).run();
81959
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, activeSource.id)).get();
81859
81960
  } else {
81860
81961
  const newId = crypto48.randomUUID();
81861
81962
  tx.insert(trafficSources).values({
@@ -81880,7 +81981,7 @@ async function trafficRoutes(app, opts) {
81880
81981
  createdAt: now,
81881
81982
  updatedAt: now
81882
81983
  }).run();
81883
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, newId)).get();
81984
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, newId)).get();
81884
81985
  }
81885
81986
  const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
81886
81987
  writeAuditLog(tx, {
@@ -81924,7 +82025,7 @@ async function trafficRoutes(app, opts) {
81924
82025
  const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
81925
82026
  const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
81926
82027
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
81927
- const cloudflareSources = app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
82028
+ const cloudflareSources = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
81928
82029
  const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
81929
82030
  if (sameModeSource) attempt.update({ action: "reauth" });
81930
82031
  const sourceId2 = sameModeSource?.id ?? crypto48.randomUUID();
@@ -81975,7 +82076,7 @@ async function trafficRoutes(app, opts) {
81975
82076
  let scheduleChanged = false;
81976
82077
  try {
81977
82078
  const result = app.db.transaction((tx) => {
81978
- const currentSameModeSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
82079
+ const currentSameModeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
81979
82080
  if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
81980
82081
  throw operationInProgress("Cloudflare Queue source changed during connect; retry");
81981
82082
  }
@@ -81993,7 +82094,7 @@ async function trafficRoutes(app, opts) {
81993
82094
  queueBacklogObservedAt: null
81994
82095
  } : {},
81995
82096
  updatedAt: now2
81996
- }).where(eq69(trafficSources.id, sourceId2)).run();
82097
+ }).where(eq70(trafficSources.id, sourceId2)).run();
81997
82098
  } else {
81998
82099
  tx.insert(trafficSources).values({
81999
82100
  id: sourceId2,
@@ -82013,7 +82114,7 @@ async function trafficRoutes(app, opts) {
82013
82114
  }
82014
82115
  const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
82015
82116
  return {
82016
- row: tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId2)).get(),
82117
+ row: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId2)).get(),
82017
82118
  scheduleChanged: changed
82018
82119
  };
82019
82120
  }, { behavior: "immediate" });
@@ -82061,7 +82162,7 @@ async function trafficRoutes(app, opts) {
82061
82162
  const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
82062
82163
  const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
82063
82164
  const now = (/* @__PURE__ */ new Date()).toISOString();
82064
- const activeSource = app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
82165
+ const activeSource = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
82065
82166
  if (activeSource) attempt.update({ action: "reauth" });
82066
82167
  const sourceId = activeSource?.id ?? crypto48.randomUUID();
82067
82168
  const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
@@ -82126,7 +82227,7 @@ async function trafficRoutes(app, opts) {
82126
82227
  let scheduleRemoved = false;
82127
82228
  try {
82128
82229
  const result = app.db.transaction((tx) => {
82129
- const currentActiveSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
82230
+ const currentActiveSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
82130
82231
  if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
82131
82232
  throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
82132
82233
  }
@@ -82140,8 +82241,8 @@ async function trafficRoutes(app, opts) {
82140
82241
  configJson: config,
82141
82242
  ingestTokenHash,
82142
82243
  updatedAt: now
82143
- }).where(eq69(trafficSources.id, currentActiveSource.id)).run();
82144
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, currentActiveSource.id)).get();
82244
+ }).where(eq70(trafficSources.id, currentActiveSource.id)).run();
82245
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, currentActiveSource.id)).get();
82145
82246
  } else {
82146
82247
  tx.insert(trafficSources).values({
82147
82248
  id: sourceId,
@@ -82161,16 +82262,16 @@ async function trafficRoutes(app, opts) {
82161
82262
  createdAt: now,
82162
82263
  updatedAt: now
82163
82264
  }).run();
82164
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get();
82265
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
82165
82266
  }
82166
82267
  let removed = false;
82167
82268
  if (row.status === TrafficSourceStatuses.connected) {
82168
- const schedule = tx.select().from(schedules).where(and60(
82169
- eq69(schedules.projectId, project.id),
82170
- eq69(schedules.kind, SchedulableRunKinds["traffic-sync"])
82269
+ const schedule = tx.select().from(schedules).where(and61(
82270
+ eq70(schedules.projectId, project.id),
82271
+ eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
82171
82272
  )).get();
82172
82273
  if (schedule) {
82173
- tx.delete(schedules).where(eq69(schedules.id, schedule.id)).run();
82274
+ tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
82174
82275
  removed = true;
82175
82276
  }
82176
82277
  }
@@ -82224,47 +82325,47 @@ async function trafficRoutes(app, opts) {
82224
82325
  };
82225
82326
  }));
82226
82327
  app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
82227
- const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
82328
+ const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
82228
82329
  const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
82229
82330
  return integration ? { integration, action: "select" } : void 0;
82230
82331
  }, async (request) => {
82231
82332
  const project = resolveProject(app.db, request.params.name);
82232
82333
  const now = (/* @__PURE__ */ new Date()).toISOString();
82233
82334
  const result = app.db.transaction((tx) => {
82234
- const target = tx.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
82335
+ const target = tx.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
82235
82336
  if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
82236
82337
  throw notFound("Traffic source", request.params.id);
82237
82338
  }
82238
82339
  const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
82239
- const siblings = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all();
82340
+ const siblings = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all();
82240
82341
  for (const sibling of siblings) {
82241
82342
  if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
82242
82343
  tx.update(trafficSources).set({
82243
82344
  status: TrafficSourceStatuses.paused,
82244
82345
  updatedAt: now
82245
- }).where(eq69(trafficSources.id, sibling.id)).run();
82346
+ }).where(eq70(trafficSources.id, sibling.id)).run();
82246
82347
  }
82247
82348
  tx.update(trafficSources).set({
82248
82349
  status: TrafficSourceStatuses.connected,
82249
82350
  lastError: null,
82250
82351
  archivedAt: null,
82251
82352
  updatedAt: now
82252
- }).where(eq69(trafficSources.id, target.id)).run();
82353
+ }).where(eq70(trafficSources.id, target.id)).run();
82253
82354
  let scheduleAction = null;
82254
82355
  if (usesPullSchedule) {
82255
82356
  bindTrafficSyncSchedule(tx, project.id, target.id, now);
82256
82357
  scheduleAction = "upsert";
82257
82358
  } else {
82258
- const schedule = tx.select().from(schedules).where(and60(
82259
- eq69(schedules.projectId, project.id),
82260
- eq69(schedules.kind, SchedulableRunKinds["traffic-sync"])
82359
+ const schedule = tx.select().from(schedules).where(and61(
82360
+ eq70(schedules.projectId, project.id),
82361
+ eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
82261
82362
  )).get();
82262
82363
  if (schedule) {
82263
- tx.delete(schedules).where(eq69(schedules.id, schedule.id)).run();
82364
+ tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
82264
82365
  scheduleAction = "delete";
82265
82366
  }
82266
82367
  }
82267
- const source = tx.select().from(trafficSources).where(eq69(trafficSources.id, target.id)).get();
82368
+ const source = tx.select().from(trafficSources).where(eq70(trafficSources.id, target.id)).get();
82268
82369
  writeAuditLog(tx, {
82269
82370
  projectId: project.id,
82270
82371
  actor: "api",
@@ -82332,7 +82433,7 @@ async function trafficRoutes(app, opts) {
82332
82433
  }
82333
82434
  const { bearerHash, credential } = authenticated;
82334
82435
  const sourceId = credential.sourceId;
82335
- const sourceRow2 = app.db.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get();
82436
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
82336
82437
  if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
82337
82438
  throw authRequired();
82338
82439
  }
@@ -82406,7 +82507,7 @@ async function trafficRoutes(app, opts) {
82406
82507
  });
82407
82508
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
82408
82509
  const project = resolveProject(app.db, request.params.name);
82409
- const initialSourceRow = app.db.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
82510
+ const initialSourceRow = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
82410
82511
  if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
82411
82512
  throw notFound("Traffic source", request.params.id);
82412
82513
  }
@@ -82561,7 +82662,7 @@ async function trafficRoutes(app, opts) {
82561
82662
  now: (/* @__PURE__ */ new Date()).toISOString(),
82562
82663
  ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
82563
82664
  })) throw new Error("Queue sync lease was lost before acknowledgement");
82564
- const latestSource = app.db.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82665
+ const latestSource = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82565
82666
  if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
82566
82667
  throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
82567
82668
  }
@@ -82586,7 +82687,7 @@ async function trafficRoutes(app, opts) {
82586
82687
  const pulledEnvelopeCount = pulled.messages.length + pulled.skippedUnleasedMessageCount;
82587
82688
  if (pulledEnvelopeCount < CLOUDFLARE_QUEUE_BATCH_SIZE || remainingBacklogCount === 0) break;
82588
82689
  }
82589
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(eq69(runs.id, runId)).run();
82690
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(eq70(runs.id, runId)).run();
82590
82691
  writeAuditLog(app.db, {
82591
82692
  projectId: project.id,
82592
82693
  actor: "api",
@@ -82647,13 +82748,13 @@ async function trafficRoutes(app, opts) {
82647
82748
  } catch {
82648
82749
  }
82649
82750
  app.db.transaction((tx) => {
82650
- tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(eq69(runs.id, runId)).run();
82651
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82751
+ tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
82752
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82652
82753
  if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
82653
- tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and60(
82654
- eq69(trafficSources.id, sourceRow2.id),
82655
- eq69(trafficSources.status, TrafficSourceStatuses.connected),
82656
- eq69(trafficSources.syncLeaseOwner, leaseOwner)
82754
+ tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and61(
82755
+ eq70(trafficSources.id, sourceRow2.id),
82756
+ eq70(trafficSources.status, TrafficSourceStatuses.connected),
82757
+ eq70(trafficSources.syncLeaseOwner, leaseOwner)
82657
82758
  )).run();
82658
82759
  }
82659
82760
  });
@@ -82672,7 +82773,7 @@ async function trafficRoutes(app, opts) {
82672
82773
  `Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
82673
82774
  );
82674
82775
  }
82675
- const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
82776
+ const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
82676
82777
  if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
82677
82778
  throw validationError("Traffic source must be active before it can sync");
82678
82779
  }
@@ -82705,10 +82806,10 @@ async function trafficRoutes(app, opts) {
82705
82806
  const markFailed = (msg, errorCode2) => {
82706
82807
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
82707
82808
  app.db.transaction((tx) => {
82708
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq69(runs.id, runId)).run();
82709
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82809
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
82810
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82710
82811
  if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && (wordpressLeaseOwner === void 0 || latestSource.syncLeaseOwner === wordpressLeaseOwner)) {
82711
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq69(trafficSources.id, sourceRow2.id)).run();
82812
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
82712
82813
  }
82713
82814
  });
82714
82815
  try {
@@ -82802,7 +82903,7 @@ async function trafficRoutes(app, opts) {
82802
82903
  }
82803
82904
  const credential = credentialStore.getConnection(project.name);
82804
82905
  if (!credential) {
82805
- app.db.delete(runs).where(eq69(runs.id, runId)).run();
82906
+ app.db.delete(runs).where(eq70(runs.id, runId)).run();
82806
82907
  throw validationError(
82807
82908
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
82808
82909
  );
@@ -82836,14 +82937,14 @@ async function trafficRoutes(app, opts) {
82836
82937
  const windowEndIso2 = windowEnd.toISOString();
82837
82938
  const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
82838
82939
  const reservedSource = app.db.transaction((tx) => {
82839
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82940
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82840
82941
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== wordpressLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
82841
82942
  tx.update(trafficSources).set({
82842
82943
  lastSyncedAt: windowStartIso2,
82843
82944
  wordpressPendingUntil: windowEndIso2,
82844
82945
  updatedAt: reservedAt
82845
- }).where(eq69(trafficSources.id, sourceRow2.id)).run();
82846
- return tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82946
+ }).where(eq70(trafficSources.id, sourceRow2.id)).run();
82947
+ return tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82847
82948
  }, { behavior: "immediate" });
82848
82949
  if (!reservedSource) {
82849
82950
  const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
@@ -82903,12 +83004,12 @@ async function trafficRoutes(app, opts) {
82903
83004
  auditAction = "traffic.vercel.synced";
82904
83005
  const credentialStore = opts.vercelTrafficCredentialStore;
82905
83006
  if (!credentialStore) {
82906
- app.db.delete(runs).where(eq69(runs.id, runId)).run();
83007
+ app.db.delete(runs).where(eq70(runs.id, runId)).run();
82907
83008
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
82908
83009
  }
82909
83010
  const credential = credentialStore.getConnection(project.name);
82910
83011
  if (!credential) {
82911
- app.db.delete(runs).where(eq69(runs.id, runId)).run();
83012
+ app.db.delete(runs).where(eq70(runs.id, runId)).run();
82912
83013
  throw validationError(
82913
83014
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
82914
83015
  );
@@ -82926,7 +83027,7 @@ async function trafficRoutes(app, opts) {
82926
83027
  if (cappedStartMs > clampedStartMs) {
82927
83028
  const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
82928
83029
  const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
82929
- app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq69(trafficSources.id, sourceRow2.id)).run();
83030
+ app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq70(trafficSources.id, sourceRow2.id)).run();
82930
83031
  request.log.warn(
82931
83032
  {
82932
83033
  sourceId: sourceRow2.id,
@@ -83019,15 +83120,15 @@ async function trafficRoutes(app, opts) {
83019
83120
  throw providerError(`WordPress sync failed: ${msg}`);
83020
83121
  }
83021
83122
  const commitOutcome = app.db.transaction((tx) => {
83022
- const latestRow = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
83023
- const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83123
+ const latestRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
83124
+ const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83024
83125
  if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || wordpressLeaseOwner !== void 0 && latestRow.syncLeaseOwner !== wordpressLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
83025
83126
  const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
83026
83127
  tx.update(runs).set({
83027
83128
  status: RunStatuses.failed,
83028
83129
  error: "Traffic source was deactivated or reconfigured during sync",
83029
83130
  finishedAt: abortedAt
83030
- }).where(eq69(runs.id, runId)).run();
83131
+ }).where(eq70(runs.id, runId)).run();
83031
83132
  return "source-inactive";
83032
83133
  }
83033
83134
  const previousIds = latestRow.lastEventIds ?? [];
@@ -83290,8 +83391,8 @@ async function trafficRoutes(app, opts) {
83290
83391
  sourceUpdate.lastCursor = nextCursor ?? null;
83291
83392
  sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
83292
83393
  }
83293
- tx.update(trafficSources).set(sourceUpdate).where(eq69(trafficSources.id, sourceRow2.id)).run();
83294
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq69(runs.id, runId)).run();
83394
+ tx.update(trafficSources).set(sourceUpdate).where(eq70(trafficSources.id, sourceRow2.id)).run();
83395
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
83295
83396
  return "committed";
83296
83397
  }, { behavior: "immediate" });
83297
83398
  if (commitOutcome === "source-inactive") {
@@ -83361,7 +83462,7 @@ async function trafficRoutes(app, opts) {
83361
83462
  });
83362
83463
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
83363
83464
  const project = resolveProject(app.db, request.params.name);
83364
- const sourceRow2 = app.db.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
83465
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
83365
83466
  if (!sourceRow2 || sourceRow2.projectId !== project.id) {
83366
83467
  throw notFound("Traffic source", request.params.id);
83367
83468
  }
@@ -83474,7 +83575,7 @@ async function trafficRoutes(app, opts) {
83474
83575
  const startedAt = windowEnd.toISOString();
83475
83576
  const runId = crypto48.randomUUID();
83476
83577
  app.db.transaction((tx) => {
83477
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
83578
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
83478
83579
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
83479
83580
  throw validationError("Traffic source must remain active and unchanged before it can backfill");
83480
83581
  }
@@ -83517,8 +83618,8 @@ async function trafficRoutes(app, opts) {
83517
83618
  pathNormalized: crawlerEventsHourly.pathNormalized,
83518
83619
  hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
83519
83620
  }).from(crawlerEventsHourly).where(
83520
- and60(
83521
- eq69(crawlerEventsHourly.sourceId, row.id),
83621
+ and61(
83622
+ eq70(crawlerEventsHourly.sourceId, row.id),
83522
83623
  gte16(crawlerEventsHourly.tsHour, since)
83523
83624
  )
83524
83625
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
@@ -83527,8 +83628,8 @@ async function trafficRoutes(app, opts) {
83527
83628
  );
83528
83629
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
83529
83630
  const aiUserFetchTotals = app.db.select({ total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
83530
- and60(
83531
- eq69(aiUserFetchEventsHourly.sourceId, row.id),
83631
+ and61(
83632
+ eq70(aiUserFetchEventsHourly.sourceId, row.id),
83532
83633
  gte16(aiUserFetchEventsHourly.tsHour, since)
83533
83634
  )
83534
83635
  ).get();
@@ -83537,22 +83638,22 @@ async function trafficRoutes(app, opts) {
83537
83638
  nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
83538
83639
  landed: sql28`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
83539
83640
  }).from(aiReferralEventsHourly).where(
83540
- and60(
83541
- eq69(aiReferralEventsHourly.sourceId, row.id),
83641
+ and61(
83642
+ eq70(aiReferralEventsHourly.sourceId, row.id),
83542
83643
  gte16(aiReferralEventsHourly.tsHour, since)
83543
83644
  )
83544
83645
  ).get();
83545
83646
  const sampleTotals = app.db.select({ total: sql28`COUNT(*)` }).from(rawEventSamples).where(
83546
- and60(
83547
- eq69(rawEventSamples.sourceId, row.id),
83647
+ and61(
83648
+ eq70(rawEventSamples.sourceId, row.id),
83548
83649
  gte16(rawEventSamples.ts, since)
83549
83650
  )
83550
83651
  ).get();
83551
83652
  const latestRun = app.db.select().from(runs).where(
83552
- and60(
83553
- eq69(runs.projectId, projectId),
83554
- eq69(runs.kind, RunKinds["traffic-sync"]),
83555
- eq69(runs.sourceId, row.id)
83653
+ and61(
83654
+ eq70(runs.projectId, projectId),
83655
+ eq70(runs.kind, RunKinds["traffic-sync"]),
83656
+ eq70(runs.sourceId, row.id)
83556
83657
  )
83557
83658
  ).orderBy(desc35(runs.startedAt)).limit(1).get();
83558
83659
  return {
@@ -83588,7 +83689,7 @@ async function trafficRoutes(app, opts) {
83588
83689
  const now = (/* @__PURE__ */ new Date()).toISOString();
83589
83690
  let updatedRow;
83590
83691
  app.db.transaction((tx) => {
83591
- const sourceRow2 = tx.select().from(trafficSources).where(and60(eq69(trafficSources.projectId, project.id), eq69(trafficSources.id, request.params.id))).get();
83692
+ const sourceRow2 = tx.select().from(trafficSources).where(and61(eq70(trafficSources.projectId, project.id), eq70(trafficSources.id, request.params.id))).get();
83592
83693
  if (!sourceRow2) {
83593
83694
  throw notFound("traffic source", request.params.id);
83594
83695
  }
@@ -83602,7 +83703,7 @@ async function trafficRoutes(app, opts) {
83602
83703
  `Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
83603
83704
  );
83604
83705
  }
83605
- const hasConnectedSibling = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83706
+ const hasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83606
83707
  if (hasConnectedSibling) {
83607
83708
  throw validationError(
83608
83709
  `Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
@@ -83619,7 +83720,7 @@ async function trafficRoutes(app, opts) {
83619
83720
  status: TrafficSourceStatuses.connected,
83620
83721
  lastError: null,
83621
83722
  updatedAt: now
83622
- }).where(eq69(trafficSources.id, sourceRow2.id)).run();
83723
+ }).where(eq70(trafficSources.id, sourceRow2.id)).run();
83623
83724
  writeAuditLog(tx, auditFromRequest(request, {
83624
83725
  projectId: project.id,
83625
83726
  actor: "api",
@@ -83627,20 +83728,20 @@ async function trafficRoutes(app, opts) {
83627
83728
  entityType: "traffic_source",
83628
83729
  entityId: sourceRow2.id
83629
83730
  }));
83630
- updatedRow = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
83731
+ updatedRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
83631
83732
  });
83632
83733
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
83633
83734
  }));
83634
83735
  app.get("/projects/:name/traffic/sources", async (request) => {
83635
83736
  const project = resolveProject(app.db, request.params.name);
83636
- const rows = app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83737
+ const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83637
83738
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
83638
83739
  const response = { sources };
83639
83740
  return response;
83640
83741
  });
83641
83742
  app.get("/projects/:name/traffic/status", async (request) => {
83642
83743
  const project = resolveProject(app.db, request.params.name);
83643
- const rows = app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83744
+ const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83644
83745
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
83645
83746
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
83646
83747
  const response = { sources };
@@ -83650,7 +83751,7 @@ async function trafficRoutes(app, opts) {
83650
83751
  "/projects/:name/traffic/sources/:id",
83651
83752
  async (request) => {
83652
83753
  const project = resolveProject(app.db, request.params.name);
83653
- const row = app.db.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
83754
+ const row = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
83654
83755
  if (!row || row.projectId !== project.id) {
83655
83756
  throw notFound("Traffic source", request.params.id);
83656
83757
  }
@@ -83723,12 +83824,12 @@ async function trafficRoutes(app, opts) {
83723
83824
  const seriesByBucket = /* @__PURE__ */ new Map();
83724
83825
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
83725
83826
  const crawlerFilters = [
83726
- eq69(crawlerEventsHourly.projectId, project.id),
83827
+ eq70(crawlerEventsHourly.projectId, project.id),
83727
83828
  gte16(crawlerEventsHourly.tsHour, sinceIso),
83728
83829
  lte17(crawlerEventsHourly.tsHour, untilIso)
83729
83830
  ];
83730
- if (sourceIdParam) crawlerFilters.push(eq69(crawlerEventsHourly.sourceId, sourceIdParam));
83731
- const crawlerWhere = and60(...crawlerFilters);
83831
+ if (sourceIdParam) crawlerFilters.push(eq70(crawlerEventsHourly.sourceId, sourceIdParam));
83832
+ const crawlerWhere = and61(...crawlerFilters);
83732
83833
  const pathTotals = app.db.select({
83733
83834
  pathNormalized: crawlerEventsHourly.pathNormalized,
83734
83835
  hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
@@ -83798,12 +83899,12 @@ async function trafficRoutes(app, opts) {
83798
83899
  }
83799
83900
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
83800
83901
  const userFetchFilters = [
83801
- eq69(aiUserFetchEventsHourly.projectId, project.id),
83902
+ eq70(aiUserFetchEventsHourly.projectId, project.id),
83802
83903
  gte16(aiUserFetchEventsHourly.tsHour, sinceIso),
83803
83904
  lte17(aiUserFetchEventsHourly.tsHour, untilIso)
83804
83905
  ];
83805
- if (sourceIdParam) userFetchFilters.push(eq69(aiUserFetchEventsHourly.sourceId, sourceIdParam));
83806
- const userFetchWhere = and60(...userFetchFilters);
83906
+ if (sourceIdParam) userFetchFilters.push(eq70(aiUserFetchEventsHourly.sourceId, sourceIdParam));
83907
+ const userFetchWhere = and61(...userFetchFilters);
83807
83908
  const total = app.db.select({
83808
83909
  total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
83809
83910
  rows: sql28`COUNT(*)`
@@ -83837,12 +83938,12 @@ async function trafficRoutes(app, opts) {
83837
83938
  }
83838
83939
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
83839
83940
  const aiFilters = [
83840
- eq69(aiReferralEventsHourly.projectId, project.id),
83941
+ eq70(aiReferralEventsHourly.projectId, project.id),
83841
83942
  gte16(aiReferralEventsHourly.tsHour, sinceIso),
83842
83943
  lte17(aiReferralEventsHourly.tsHour, untilIso)
83843
83944
  ];
83844
- if (sourceIdParam) aiFilters.push(eq69(aiReferralEventsHourly.sourceId, sourceIdParam));
83845
- const aiWhere = and60(...aiFilters);
83945
+ if (sourceIdParam) aiFilters.push(eq70(aiReferralEventsHourly.sourceId, sourceIdParam));
83946
+ const aiWhere = and61(...aiFilters);
83846
83947
  const total = app.db.select({
83847
83948
  total: sql28`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
83848
83949
  nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
@@ -83917,7 +84018,7 @@ async function trafficRoutes(app, opts) {
83917
84018
  windowEnd: untilIso,
83918
84019
  series: (() => {
83919
84020
  const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
83920
- const coverageScope = (col, srcCol) => sourceIdParam ? and60(eq69(col, project.id), eq69(srcCol, sourceIdParam)) : eq69(col, project.id);
84021
+ const coverageScope = (col, srcCol) => sourceIdParam ? and61(eq70(col, project.id), eq70(srcCol, sourceIdParam)) : eq70(col, project.id);
83921
84022
  const firsts = [
83922
84023
  app.db.select({ v: sql28`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
83923
84024
  app.db.select({ v: sql28`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
@@ -83995,7 +84096,7 @@ async function trafficRoutes(app, opts) {
83995
84096
  }
83996
84097
 
83997
84098
  // ../api-routes/src/doctor/checks/report-sweeps.ts
83998
- import { and as and61, asc as asc15, eq as eq70, gte as gte17, lte as lte18 } from "drizzle-orm";
84099
+ import { and as and62, asc as asc15, eq as eq71, gte as gte17, lte as lte18 } from "drizzle-orm";
83999
84100
 
84000
84101
  // ../api-routes/src/visibility-attribution.ts
84001
84102
  function buildQueryAttribution2(projectQueries) {
@@ -84029,11 +84130,11 @@ function inspectSweep(ctx, run) {
84029
84130
  answerMentioned: querySnapshots.answerMentioned,
84030
84131
  answerText: querySnapshots.answerText,
84031
84132
  measurementExecutionId: querySnapshots.measurementExecutionId
84032
- }).from(querySnapshots).where(eq70(querySnapshots.runId, run.id)).all();
84133
+ }).from(querySnapshots).where(eq71(querySnapshots.runId, run.id)).all();
84033
84134
  if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
84034
84135
  try {
84035
84136
  if (run.measurementPlanVersionId) {
84036
- const version = ctx.db.select().from(measurementPlanVersions).where(and61(eq70(measurementPlanVersions.projectId, ctx.project.id), eq70(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
84137
+ const version = ctx.db.select().from(measurementPlanVersions).where(and62(eq71(measurementPlanVersions.projectId, ctx.project.id), eq71(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
84037
84138
  if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
84038
84139
  const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
84039
84140
  const manifest = measurementRunExpectedSlots(run, plan);
@@ -84065,10 +84166,10 @@ function inspectSweep(ctx, run) {
84065
84166
  };
84066
84167
  return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
84067
84168
  }
84068
- const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq70(simpleMeasurementDefinitions.runId, run.id)).get();
84169
+ const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq71(simpleMeasurementDefinitions.runId, run.id)).get();
84069
84170
  const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
84070
- const project = ctx.db.select().from(projects).where(eq70(projects.id, ctx.project.id)).get();
84071
- const basket = frozen ? frozen.queries.map((query) => ({ id: query.queryId, query: query.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq70(queries.projectId, project.id)).all();
84171
+ const project = ctx.db.select().from(projects).where(eq71(projects.id, ctx.project.id)).get();
84172
+ const basket = frozen ? frozen.queries.map((query) => ({ id: query.queryId, query: query.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq71(queries.projectId, project.id)).all();
84072
84173
  const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
84073
84174
  const attribution = buildQueryAttribution2(basket);
84074
84175
  const expected = new Set(basket.flatMap((query) => providers.map((provider) => JSON.stringify([query.id, provider.trim().toLowerCase()]))));
@@ -84117,7 +84218,7 @@ var reportSweepsCheck = {
84117
84218
  const graded = gradedReportMonths(reportMonths, now);
84118
84219
  const months = reportMonths.map((month) => {
84119
84220
  const bounds = calendarMonthBounds(month);
84120
- const monthRuns = ctx.db.select().from(runs).where(and61(eq70(runs.projectId, ctx.project.id), eq70(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte17(runs.createdAt, bounds.since), lte18(runs.createdAt, bounds.until))).orderBy(asc15(runs.createdAt), asc15(runs.id)).all();
84221
+ const monthRuns = ctx.db.select().from(runs).where(and62(eq71(runs.projectId, ctx.project.id), eq71(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte17(runs.createdAt, bounds.since), lte18(runs.createdAt, bounds.until))).orderBy(asc15(runs.createdAt), asc15(runs.id)).all();
84121
84222
  const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
84122
84223
  return {
84123
84224
  month,
@@ -84140,7 +84241,7 @@ var reportSweepsCheck = {
84140
84241
  };
84141
84242
 
84142
84243
  // ../api-routes/src/doctor/checks/report-models.ts
84143
- import { and as and62, eq as eq71, gte as gte18, inArray as inArray29, lte as lte19 } from "drizzle-orm";
84244
+ import { and as and63, eq as eq72, gte as gte18, inArray as inArray29, lte as lte19 } from "drizzle-orm";
84144
84245
  var reportModelsCheck = {
84145
84246
  id: "report.models",
84146
84247
  category: CheckCategories.providers,
@@ -84155,7 +84256,7 @@ var reportModelsCheck = {
84155
84256
  const months = reportMonths.flatMap((month) => {
84156
84257
  const bounds = calendarMonthBounds(month);
84157
84258
  const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
84158
- const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq71(runs.id, querySnapshots.runId)).where(and62(eq71(runs.projectId, ctx.project.id), eq71(runs.kind, RunKinds["answer-visibility"]), inArray29(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte18(runs.createdAt, bounds.since), lte19(runs.createdAt, bounds.until))).all();
84259
+ const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq72(runs.id, querySnapshots.runId)).where(and63(eq72(runs.projectId, ctx.project.id), eq72(runs.kind, RunKinds["answer-visibility"]), inArray29(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte18(runs.createdAt, bounds.since), lte19(runs.createdAt, bounds.until))).all();
84159
84260
  return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
84160
84261
  month,
84161
84262
  previousMonth: previous,
@@ -84196,7 +84297,7 @@ var reportModelsCheck = {
84196
84297
  };
84197
84298
 
84198
84299
  // ../api-routes/src/doctor/checks/report-daily-data.ts
84199
- import { eq as eq72 } from "drizzle-orm";
84300
+ import { eq as eq73 } from "drizzle-orm";
84200
84301
  var REPORTING_LAG_DAYS = 3;
84201
84302
  function dateSpan(dates) {
84202
84303
  const sorted = [...dates].sort();
@@ -84205,31 +84306,31 @@ function dateSpan(dates) {
84205
84306
  function latestSyncRange(ctx, source) {
84206
84307
  const projectId = ctx.project.id;
84207
84308
  if (source === "ga") {
84208
- const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq72(gaDailyTotals.projectId, projectId)).all();
84309
+ const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq73(gaDailyTotals.projectId, projectId)).all();
84209
84310
  const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
84210
- const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq72(gaTrafficSummaries.projectId, projectId)).get();
84311
+ const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq73(gaTrafficSummaries.projectId, projectId)).get();
84211
84312
  if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
84212
84313
  const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
84213
84314
  return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
84214
84315
  }
84215
- const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq72(gscDailyTotals.projectId, projectId)).all();
84316
+ const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq73(gscDailyTotals.projectId, projectId)).all();
84216
84317
  const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
84217
84318
  const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
84218
84319
  if (!span || latest === null) return null;
84219
- const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq72(gscDataWatermarks.projectId, projectId)).get();
84320
+ const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq73(gscDataWatermarks.projectId, projectId)).get();
84220
84321
  return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
84221
84322
  }
84222
84323
  function dailyCoverage(ctx, month, source) {
84223
84324
  const project = ctx.project;
84224
84325
  const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
84225
- const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq72(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq72(gscDailyTotals.projectId, project.id)).all();
84326
+ const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq73(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq73(gscDailyTotals.projectId, project.id)).all();
84226
84327
  if (!connection && stored.length === 0) return null;
84227
84328
  const bounds = calendarMonthBounds(month);
84228
84329
  const start = bounds.since.slice(0, 10);
84229
84330
  const end = bounds.until.slice(0, 10);
84230
84331
  const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
84231
84332
  const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
84232
- const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq72(projects.id, project.id)).get().date.slice(0, 10);
84333
+ const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq73(projects.id, project.id)).get().date.slice(0, 10);
84233
84334
  const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
84234
84335
  const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
84235
84336
  const byDate = new Map(stored.map((row) => [row.date, row.count]));
@@ -84311,7 +84412,7 @@ var reportDailyDataCheck = {
84311
84412
  };
84312
84413
 
84313
84414
  // ../api-routes/src/doctor/checks/referral-assessment.ts
84314
- import { and as and63, eq as eq73, ne as ne14 } from "drizzle-orm";
84415
+ import { and as and64, eq as eq74, ne as ne14 } from "drizzle-orm";
84315
84416
  var REFERRAL_ASSESSMENT_CHECKS = [{
84316
84417
  id: "report.ai-referral-bursts",
84317
84418
  category: CheckCategories.integrations,
@@ -84320,7 +84421,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
84320
84421
  notificationPolicy: CheckNotificationPolicies.silent,
84321
84422
  run: (ctx) => {
84322
84423
  if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.no-project", summary: "Select a project to inspect referral evidence." };
84323
- const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and63(eq73(trafficSources.projectId, ctx.project.id), ne14(trafficSources.status, TrafficSourceStatuses.archived))).get();
84424
+ const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and64(eq74(trafficSources.projectId, ctx.project.id), ne14(trafficSources.status, TrafficSourceStatuses.archived))).get();
84324
84425
  if (!configured) {
84325
84426
  return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
84326
84427
  }
@@ -84680,7 +84781,6 @@ var skillsTriggerSurfaceCheck = {
84680
84781
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck, skillsTriggerSurfaceCheck];
84681
84782
 
84682
84783
  // ../api-routes/src/doctor/checks/backlinks.ts
84683
- import { and as and64, eq as eq74 } from "drizzle-orm";
84684
84784
  function skippedNoProject() {
84685
84785
  return {
84686
84786
  status: CheckStatuses.skipped,
@@ -84696,36 +84796,58 @@ var BACKLINKS_CHECKS = [
84696
84796
  title: "Backlinks source connected",
84697
84797
  run: (ctx) => {
84698
84798
  if (!ctx.project) return skippedNoProject();
84699
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq74(projects.id, ctx.project.id)).get();
84700
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq74(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
84701
- const ccConnected = projectRow?.autoExtract === true && !!readySync;
84702
- const connected = [];
84703
- if (ccConnected) connected.push(BacklinkSources.commoncrawl);
84704
- if (connected.length === 0) {
84799
+ const state = readCommonCrawlSourceState(ctx.db, ctx.project.id);
84800
+ const details = {
84801
+ commoncrawl: state.connected,
84802
+ commoncrawlReadyRelease: state.hasReadyRelease,
84803
+ commoncrawlHasData: state.hasProjectData
84804
+ };
84805
+ if (!state.hasReadyRelease) {
84806
+ return {
84807
+ status: CheckStatuses.warn,
84808
+ code: "backlinks.source.none",
84809
+ summary: `No backlink source is set up for ${ctx.project.name}: no Common Crawl release has been synced.`,
84810
+ remediation: "Run `canonry backlinks sync` to download the newest Common Crawl release for the workspace.",
84811
+ details
84812
+ };
84813
+ }
84814
+ if (!state.hasProjectData) {
84705
84815
  return {
84706
84816
  status: CheckStatuses.warn,
84707
84817
  code: "backlinks.source.none",
84708
- summary: `No backlink source is set up for ${ctx.project.name}.`,
84709
- remediation: "Enable Common Crawl by setting autoExtractBacklinks on the project, then run `canonry backlinks sync`.",
84710
- details: { commoncrawl: ccConnected }
84818
+ summary: state.hasCurrentReadyRelease ? `A Common Crawl release is synced, but no backlinks have been extracted for ${ctx.project.name} yet.` : `No backlinks have been extracted for ${ctx.project.name} yet, and no Common Crawl release is currently ready.`,
84819
+ remediation: state.hasCurrentReadyRelease ? `Run \`canonry backlinks extract ${ctx.project.name}\` to extract this project's backlinks from the synced release.` : "Run `canonry backlinks sync` to make a Common Crawl release ready and extract the workspace projects.",
84820
+ details
84711
84821
  };
84712
84822
  }
84713
- const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and64(
84714
- eq74(backlinkSummaries.projectId, ctx.project.id),
84715
- eq74(backlinkSummaries.source, BacklinkSources.commoncrawl)
84716
- )).limit(1).get() : false;
84823
+ const connected = [BacklinkSources.commoncrawl];
84717
84824
  return {
84718
84825
  status: CheckStatuses.ok,
84719
84826
  code: "backlinks.source.connected",
84720
84827
  summary: "Common Crawl backlink source is set up.",
84721
- remediation: ccConnected && !ccHasData ? `Common Crawl is ready but no backlinks have been extracted for ${ctx.project.name} yet \u2014 run \`canonry backlinks extract ${ctx.project.name}\`.` : null,
84722
- details: { commoncrawl: ccConnected, connected, commoncrawlHasData: ccHasData }
84828
+ remediation: null,
84829
+ details: { ...details, connected }
84723
84830
  };
84724
84831
  }
84725
84832
  }
84726
84833
  ];
84727
84834
 
84728
84835
  // ../api-routes/src/doctor/checks/bing-auth.ts
84836
+ import { and as and65, desc as desc36, eq as eq75, inArray as inArray30 } from "drizzle-orm";
84837
+ var BING_CONNECTION_ENTITY = "bing_connection";
84838
+ var BING_CONNECTED_ACTION = "bing.connected";
84839
+ var BING_DISCONNECTED_ACTION = "bing.disconnected";
84840
+ function lostBingConnection(ctx, project) {
84841
+ const hasRow = (row) => row !== void 0;
84842
+ const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(eq75(bingCoverageSnapshots.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingUrlInspections.id }).from(bingUrlInspections).where(eq75(bingUrlInspections.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingKeywordStats.id }).from(bingKeywordStats).where(eq75(bingKeywordStats.projectId, project.id)).limit(1).get());
84843
+ if (!storedData) return false;
84844
+ const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and65(
84845
+ eq75(auditLog.entityType, BING_CONNECTION_ENTITY),
84846
+ eq75(auditLog.entityId, project.canonicalDomain),
84847
+ inArray30(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
84848
+ )).orderBy(desc36(auditLog.createdAt)).limit(1).get();
84849
+ return lastChange?.action !== BING_DISCONNECTED_ACTION;
84850
+ }
84729
84851
  var BING_AUTH_CHECKS = [
84730
84852
  {
84731
84853
  id: "bing.auth.connection",
@@ -84752,10 +84874,18 @@ var BING_AUTH_CHECKS = [
84752
84874
  }
84753
84875
  const conn = store.getConnection(ctx.project.canonicalDomain);
84754
84876
  if (!conn) {
84877
+ if (!lostBingConnection(ctx, ctx.project)) {
84878
+ return {
84879
+ status: CheckStatuses.skipped,
84880
+ code: "bing.auth.not-connected",
84881
+ summary: `${ctx.project.name} has not connected Bing Webmaster Tools.`,
84882
+ remediation: `Optional: run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to track Bing indexing.`
84883
+ };
84884
+ }
84755
84885
  return {
84756
84886
  status: CheckStatuses.fail,
84757
84887
  code: "bing.auth.no-connection",
84758
- summary: `No Bing connection for ${ctx.project.canonicalDomain}.`,
84888
+ summary: `No Bing connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} has stored Bing data from an earlier connection.`,
84759
84889
  remediation: `Run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to authorize.`
84760
84890
  };
84761
84891
  }
@@ -84872,7 +85002,7 @@ var BING_AUTH_CHECKS = [
84872
85002
  ];
84873
85003
 
84874
85004
  // ../api-routes/src/doctor/checks/content.ts
84875
- import { eq as eq75 } from "drizzle-orm";
85005
+ import { eq as eq76 } from "drizzle-orm";
84876
85006
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
84877
85007
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
84878
85008
  function skippedNoProject2() {
@@ -84885,13 +85015,17 @@ function skippedNoProject2() {
84885
85015
  }
84886
85016
  function loadProject(ctx) {
84887
85017
  if (!ctx.project) return null;
84888
- return ctx.db.select().from(projects).where(eq75(projects.id, ctx.project.id)).get() ?? null;
85018
+ return ctx.db.select().from(projects).where(eq76(projects.id, ctx.project.id)).get() ?? null;
84889
85019
  }
84890
85020
  var winnabilityCoverageCheck = {
84891
85021
  id: "content.winnability.coverage",
84892
85022
  category: CheckCategories.integrations,
84893
85023
  scope: CheckScopes.project,
84894
85024
  title: "Content winnability classification coverage",
85025
+ // Advice about content targeting, not a broken instrument. Most projects sit
85026
+ // below the threshold for good, and a check that never clears keeps the
85027
+ // project permanently degraded, so no recovery could ever be sent.
85028
+ notificationPolicy: CheckNotificationPolicies.silent,
84895
85029
  run: (ctx) => {
84896
85030
  if (!ctx.project) return skippedNoProject2();
84897
85031
  const project = loadProject(ctx);
@@ -84974,7 +85108,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
84974
85108
  );
84975
85109
 
84976
85110
  // ../api-routes/src/doctor/checks/ads.ts
84977
- import { eq as eq76 } from "drizzle-orm";
85111
+ import { eq as eq77 } from "drizzle-orm";
84978
85112
  var RECENT_SYNC_WARN_DAYS = 7;
84979
85113
  var RECENT_SYNC_FAIL_DAYS = 30;
84980
85114
  var adsConnectionCheck = {
@@ -84991,7 +85125,7 @@ var adsConnectionCheck = {
84991
85125
  remediation: null
84992
85126
  };
84993
85127
  }
84994
- const row = ctx.db.select().from(adsConnections).where(eq76(adsConnections.projectId, ctx.project.id)).get();
85128
+ const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
84995
85129
  if (!row) {
84996
85130
  return {
84997
85131
  status: CheckStatuses.skipped,
@@ -85041,7 +85175,7 @@ var adsRecentSyncCheck = {
85041
85175
  remediation: null
85042
85176
  };
85043
85177
  }
85044
- const row = ctx.db.select().from(adsConnections).where(eq76(adsConnections.projectId, ctx.project.id)).get();
85178
+ const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
85045
85179
  if (!row) {
85046
85180
  return {
85047
85181
  status: CheckStatuses.skipped,
@@ -85251,7 +85385,7 @@ var ga4ConnectionCheck = {
85251
85385
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
85252
85386
 
85253
85387
  // ../api-routes/src/doctor/checks/data-freshness.ts
85254
- import { and as and65, desc as desc36, eq as eq77, inArray as inArray30 } from "drizzle-orm";
85388
+ import { and as and66, desc as desc37, eq as eq78, inArray as inArray31 } from "drizzle-orm";
85255
85389
  var GA_DATA_AGING_DAYS = 3;
85256
85390
  var GA_DATA_STALE_DAYS = 5;
85257
85391
  var GSC_DATA_AGING_DAYS = 5;
@@ -85279,7 +85413,7 @@ function skipped(code, summary3) {
85279
85413
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
85280
85414
  }
85281
85415
  function lastSyncAgeDays(ctx, kinds) {
85282
- const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and65(eq77(runs.projectId, ctx.project.id), inArray30(runs.kind, kinds), eq77(runs.status, "completed"))).orderBy(desc36(runs.createdAt)).limit(1).get();
85416
+ const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and66(eq78(runs.projectId, ctx.project.id), inArray31(runs.kind, kinds), eq78(runs.status, "completed"))).orderBy(desc37(runs.createdAt)).limit(1).get();
85283
85417
  if (!row?.createdAt) return null;
85284
85418
  const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
85285
85419
  return Number.isFinite(age) ? age : null;
@@ -85375,7 +85509,7 @@ var gaRecentDataCheck = {
85375
85509
  const connection = gaConnected(ctx);
85376
85510
  if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
85377
85511
  if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
85378
- const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq77(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc36(gaDailyTotals.date)).limit(1).get();
85512
+ const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq78(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gaDailyTotals.date)).limit(1).get();
85379
85513
  return grade(ctx, GA_SPEC, newest?.date);
85380
85514
  }
85381
85515
  };
@@ -85390,9 +85524,9 @@ var gscRecentDataCheck = {
85390
85524
  if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
85391
85525
  return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
85392
85526
  }
85393
- const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq77(gscDataWatermarks.projectId, ctx.project.id)).get();
85527
+ const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq78(gscDataWatermarks.projectId, ctx.project.id)).get();
85394
85528
  if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
85395
- const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq77(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc36(gscDailyTotals.date)).limit(1).get();
85529
+ const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq78(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gscDailyTotals.date)).limit(1).get();
85396
85530
  return grade(ctx, GSC_SPEC, newest?.date);
85397
85531
  }
85398
85532
  };
@@ -85660,7 +85794,7 @@ var siteReachabilityCheck = {
85660
85794
  var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
85661
85795
 
85662
85796
  // ../api-routes/src/doctor/checks/gbp-auth.ts
85663
- import { and as and66, eq as eq78 } from "drizzle-orm";
85797
+ import { and as and67, eq as eq79 } from "drizzle-orm";
85664
85798
  var RECENT_SYNC_WARN_DAYS2 = 4;
85665
85799
  var RECENT_SYNC_FAIL_DAYS2 = 30;
85666
85800
  function skippedNoProject3() {
@@ -85679,10 +85813,25 @@ function storeUnavailable() {
85679
85813
  remediation: null
85680
85814
  };
85681
85815
  }
85816
+ function hasSelectedLocation(ctx, projectId) {
85817
+ return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and67(eq79(gbpLocations.projectId, projectId), eq79(gbpLocations.selected, true))).limit(1).get();
85818
+ }
85682
85819
  async function resolveGbpToken(ctx) {
85683
85820
  if (!ctx.project) return { ok: false, output: skippedNoProject3() };
85684
85821
  const store = ctx.googleConnectionStore;
85685
85822
  if (!store) return { ok: false, output: storeUnavailable() };
85823
+ const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
85824
+ if (!conn && !hasSelectedLocation(ctx, ctx.project.id)) {
85825
+ return {
85826
+ ok: false,
85827
+ output: {
85828
+ status: CheckStatuses.skipped,
85829
+ code: "gbp.auth.not-connected",
85830
+ summary: `${ctx.project.name} has not connected Google Business Profile.`,
85831
+ remediation: `Optional: run \`canonry gbp connect ${ctx.project.name}\` to track a Business Profile.`
85832
+ }
85833
+ };
85834
+ }
85686
85835
  const auth = ctx.getGoogleAuthConfig?.() ?? {};
85687
85836
  if (!auth.clientId || !auth.clientSecret) {
85688
85837
  return {
@@ -85695,14 +85844,13 @@ async function resolveGbpToken(ctx) {
85695
85844
  }
85696
85845
  };
85697
85846
  }
85698
- const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
85699
85847
  if (!conn) {
85700
85848
  return {
85701
85849
  ok: false,
85702
85850
  output: {
85703
85851
  status: CheckStatuses.fail,
85704
85852
  code: "gbp.auth.no-connection",
85705
- summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}.`,
85853
+ summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} still has selected locations.`,
85706
85854
  remediation: `Run \`canonry gbp connect ${ctx.project.name}\` to authorize.`
85707
85855
  }
85708
85856
  };
@@ -85916,7 +86064,7 @@ var recentSyncCheck = {
85916
86064
  title: "GBP recent sync",
85917
86065
  run: (ctx) => {
85918
86066
  if (!ctx.project) return skippedNoProject3();
85919
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and66(eq78(gbpLocations.projectId, ctx.project.id), eq78(gbpLocations.selected, true))).all();
86067
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and67(eq79(gbpLocations.projectId, ctx.project.id), eq79(gbpLocations.selected, true))).all();
85920
86068
  if (selected.length === 0) {
85921
86069
  return {
85922
86070
  status: CheckStatuses.skipped,
@@ -85976,7 +86124,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
85976
86124
  );
85977
86125
 
85978
86126
  // ../api-routes/src/doctor/checks/places.ts
85979
- import { eq as eq79 } from "drizzle-orm";
86127
+ import { eq as eq80 } from "drizzle-orm";
85980
86128
  var apiKeyCheck = {
85981
86129
  id: "gbp.places.api-key",
85982
86130
  category: CheckCategories.auth,
@@ -86021,7 +86169,7 @@ var apiKeyCheck = {
86021
86169
  details: { tier: cfg.tier }
86022
86170
  };
86023
86171
  }
86024
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq79(gbpLocations.projectId, ctx.project.id)).all();
86172
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq80(gbpLocations.projectId, ctx.project.id)).all();
86025
86173
  const selected = rows.filter((r) => r.selected);
86026
86174
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
86027
86175
  const details = {
@@ -86054,7 +86202,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
86054
86202
  );
86055
86203
 
86056
86204
  // ../api-routes/src/doctor/checks/gbp-reviews.ts
86057
- import { and as and67, eq as eq80 } from "drizzle-orm";
86205
+ import { and as and68, eq as eq81 } from "drizzle-orm";
86058
86206
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
86059
86207
  var V4_ACCESS_REMEDIATION = "Business Profile reviews use the legacy My Business API (mybusiness.googleapis.com), which Google enables per Cloud project only on request; the API Library and `gcloud services enable` cannot turn it on. Open the enable link in the API access approval email as the approved account, or reply to the access-request thread asking Google to enable mybusiness.googleapis.com for your project number, then run `canonry gbp sync`.";
86060
86208
  var reviewsAccessCheck = {
@@ -86082,7 +86230,7 @@ var reviewsAccessCheck = {
86082
86230
  reviewsAccess: gbpLocations.reviewsAccess,
86083
86231
  reviewsAccessReason: gbpLocations.reviewsAccessReason,
86084
86232
  reviewsCheckedAt: gbpLocations.reviewsCheckedAt
86085
- }).from(gbpLocations).where(and67(eq80(gbpLocations.projectId, project.id), eq80(gbpLocations.selected, true))).all();
86233
+ }).from(gbpLocations).where(and68(eq81(gbpLocations.projectId, project.id), eq81(gbpLocations.selected, true))).all();
86086
86234
  if (locations.length === 0) {
86087
86235
  return {
86088
86236
  status: CheckStatuses.skipped,
@@ -86102,7 +86250,7 @@ var reviewsAccessCheck = {
86102
86250
  const places = ctx.getPlacesConfig?.();
86103
86251
  const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
86104
86252
  const v4Worked = new Set(
86105
- ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and67(eq80(gbpReviewRatings.projectId, project.id), eq80(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
86253
+ ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and68(eq81(gbpReviewRatings.projectId, project.id), eq81(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
86106
86254
  );
86107
86255
  const coverageOf = (loc) => {
86108
86256
  if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
@@ -86136,7 +86284,7 @@ var reviewsAccessCheck = {
86136
86284
  none > 0 ? `${none} have no review source` : null
86137
86285
  ].filter((part) => part !== null);
86138
86286
  const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
86139
- const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq80(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
86287
+ const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq81(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
86140
86288
  const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
86141
86289
  if (none > 0 && subscribed) {
86142
86290
  return {
@@ -86694,7 +86842,7 @@ var RUNTIME_STATE_CHECKS = [
86694
86842
  ];
86695
86843
 
86696
86844
  // ../api-routes/src/doctor/checks/traffic-source.ts
86697
- import { and as and68, eq as eq81, gte as gte19, inArray as inArray31, ne as ne15, sql as sql29 } from "drizzle-orm";
86845
+ import { and as and69, eq as eq82, gte as gte19, inArray as inArray32, ne as ne15, sql as sql29 } from "drizzle-orm";
86698
86846
  var RECENT_DATA_WARN_DAYS = 7;
86699
86847
  var RECENT_DATA_FAIL_DAYS = 30;
86700
86848
  function isCloudflareDirectPush(source) {
@@ -86733,8 +86881,8 @@ function skippedNoProject5() {
86733
86881
  function loadProbes(ctx) {
86734
86882
  if (!ctx.project) return [];
86735
86883
  const rows = ctx.db.select().from(trafficSources).where(
86736
- and68(
86737
- eq81(trafficSources.projectId, ctx.project.id),
86884
+ and69(
86885
+ eq82(trafficSources.projectId, ctx.project.id),
86738
86886
  ne15(trafficSources.status, TrafficSourceStatuses.archived)
86739
86887
  )
86740
86888
  ).all();
@@ -86840,27 +86988,27 @@ var recentDataCheck = {
86840
86988
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
86841
86989
  const recentCrawlers = Number(
86842
86990
  ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
86843
- and68(
86844
- eq81(crawlerEventsHourly.projectId, ctx.project.id),
86845
- inArray31(crawlerEventsHourly.sourceId, activeSourceIds),
86991
+ and69(
86992
+ eq82(crawlerEventsHourly.projectId, ctx.project.id),
86993
+ inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
86846
86994
  gte19(crawlerEventsHourly.tsHour, warnCutoff)
86847
86995
  )
86848
86996
  ).get()?.total ?? 0
86849
86997
  );
86850
86998
  const recentReferrals = Number(
86851
86999
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
86852
- and68(
86853
- eq81(aiReferralEventsHourly.projectId, ctx.project.id),
86854
- inArray31(aiReferralEventsHourly.sourceId, activeSourceIds),
87000
+ and69(
87001
+ eq82(aiReferralEventsHourly.projectId, ctx.project.id),
87002
+ inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
86855
87003
  gte19(aiReferralEventsHourly.tsHour, warnCutoff)
86856
87004
  )
86857
87005
  ).get()?.total ?? 0
86858
87006
  );
86859
87007
  const recentUserFetches = Number(
86860
87008
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
86861
- and68(
86862
- eq81(aiUserFetchEventsHourly.projectId, ctx.project.id),
86863
- inArray31(aiUserFetchEventsHourly.sourceId, activeSourceIds),
87009
+ and69(
87010
+ eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
87011
+ inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
86864
87012
  gte19(aiUserFetchEventsHourly.tsHour, warnCutoff)
86865
87013
  )
86866
87014
  ).get()?.total ?? 0
@@ -86884,27 +87032,27 @@ var recentDataCheck = {
86884
87032
  }
86885
87033
  const olderCrawlers = Number(
86886
87034
  ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
86887
- and68(
86888
- eq81(crawlerEventsHourly.projectId, ctx.project.id),
86889
- inArray31(crawlerEventsHourly.sourceId, activeSourceIds),
87035
+ and69(
87036
+ eq82(crawlerEventsHourly.projectId, ctx.project.id),
87037
+ inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
86890
87038
  gte19(crawlerEventsHourly.tsHour, failCutoff)
86891
87039
  )
86892
87040
  ).get()?.total ?? 0
86893
87041
  );
86894
87042
  const olderReferrals = Number(
86895
87043
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
86896
- and68(
86897
- eq81(aiReferralEventsHourly.projectId, ctx.project.id),
86898
- inArray31(aiReferralEventsHourly.sourceId, activeSourceIds),
87044
+ and69(
87045
+ eq82(aiReferralEventsHourly.projectId, ctx.project.id),
87046
+ inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
86899
87047
  gte19(aiReferralEventsHourly.tsHour, failCutoff)
86900
87048
  )
86901
87049
  ).get()?.total ?? 0
86902
87050
  );
86903
87051
  const olderUserFetches = Number(
86904
87052
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
86905
- and68(
86906
- eq81(aiUserFetchEventsHourly.projectId, ctx.project.id),
86907
- inArray31(aiUserFetchEventsHourly.sourceId, activeSourceIds),
87053
+ and69(
87054
+ eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
87055
+ inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
86908
87056
  gte19(aiUserFetchEventsHourly.tsHour, failCutoff)
86909
87057
  )
86910
87058
  ).get()?.total ?? 0
@@ -87078,6 +87226,7 @@ var cacheBlindSpotCheck = {
87078
87226
  category: CheckCategories.integrations,
87079
87227
  scope: CheckScopes.project,
87080
87228
  title: "WordPress traffic cache blind spot",
87229
+ notificationPolicy: CheckNotificationPolicies.silent,
87081
87230
  run: (ctx) => {
87082
87231
  if (!ctx.project) return skippedNoProject5();
87083
87232
  const wpSources = loadProbes(ctx).filter(
@@ -87677,7 +87826,7 @@ var GOOGLE_MARKETING_DOCTOR_CHECKS = createGoogleMarketingDoctorChecks(
87677
87826
  );
87678
87827
 
87679
87828
  // ../api-routes/src/doctor/checks/integration-sync.ts
87680
- import { and as and69, count as count3, desc as desc37, eq as eq82, gt as gt7, gte as gte20, inArray as inArray32, isNull as isNull14, notInArray as notInArray2, or as or18 } from "drizzle-orm";
87829
+ import { and as and70, count as count3, desc as desc38, eq as eq83, gt as gt7, gte as gte20, inArray as inArray33, isNull as isNull14, notInArray as notInArray2, or as or19 } from "drizzle-orm";
87681
87830
  var SYNC_FAILURE_WINDOW = 3;
87682
87831
  var SUMMARY_ERROR_LIMIT = 300;
87683
87832
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -87697,7 +87846,7 @@ function gscConnection(ctx, project) {
87697
87846
  function gbpConnection(ctx, project) {
87698
87847
  if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
87699
87848
  if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
87700
- const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and69(eq82(gbpLocations.projectId, project.id), eq82(gbpLocations.selected, true))).limit(1).get();
87849
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and70(eq83(gbpLocations.projectId, project.id), eq83(gbpLocations.selected, true))).limit(1).get();
87701
87850
  return selected ? CONNECTED : { state: "no-selected-locations" };
87702
87851
  }
87703
87852
  function gaConnection(ctx, project) {
@@ -87775,20 +87924,20 @@ function gradeSyncFailures(ctx, spec) {
87775
87924
  return skipped3(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
87776
87925
  }
87777
87926
  const nothingToSync = spec.nothingToSyncErrors ?? [];
87778
- const scope = and69(
87779
- eq82(runs.projectId, project.id),
87780
- eq82(runs.kind, spec.kind),
87781
- ...nothingToSync.length > 0 ? [or18(isNull14(runs.error), notInArray2(runs.error, [...nothingToSync]))] : []
87927
+ const scope = and70(
87928
+ eq83(runs.projectId, project.id),
87929
+ eq83(runs.kind, spec.kind),
87930
+ ...nothingToSync.length > 0 ? [or19(isNull14(runs.error), notInArray2(runs.error, [...nothingToSync]))] : []
87782
87931
  );
87783
- const graded = connection.since ? and69(scope, gte20(runs.createdAt, connection.since)) : scope;
87784
- const recent = ctx.db.select({ status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and69(graded, inArray32(runs.status, FINISHED_STATUSES))).orderBy(desc37(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
87932
+ const graded = connection.since ? and70(scope, gte20(runs.createdAt, connection.since)) : scope;
87933
+ const recent = ctx.db.select({ status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and70(graded, inArray33(runs.status, FINISHED_STATUSES))).orderBy(desc38(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
87785
87934
  if (recent.length === 0) {
87786
87935
  return skipped3(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
87787
87936
  }
87788
- const lastSuccess = ctx.db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and69(graded, inArray32(runs.status, SUCCESS_STATUSES))).orderBy(desc37(runs.createdAt)).limit(1).get();
87789
- const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(and69(
87937
+ const lastSuccess = ctx.db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and70(graded, inArray33(runs.status, SUCCESS_STATUSES))).orderBy(desc38(runs.createdAt)).limit(1).get();
87938
+ const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(and70(
87790
87939
  graded,
87791
- eq82(runs.status, RunStatuses.failed),
87940
+ eq83(runs.status, RunStatuses.failed),
87792
87941
  ...lastSuccess ? [gt7(runs.createdAt, lastSuccess.createdAt)] : []
87793
87942
  )).get()?.value ?? 0;
87794
87943
  const lastSuccessAt = lastSuccess ? lastSuccess.finishedAt ?? lastSuccess.createdAt : null;
@@ -88102,7 +88251,7 @@ async function doctorRoutes(app, opts) {
88102
88251
 
88103
88252
  // ../api-routes/src/discovery/routes.ts
88104
88253
  import crypto50 from "crypto";
88105
- import { and as and70, desc as desc38, eq as eq83, gte as gte21, inArray as inArray33, isNull as isNull15, or as or19 } from "drizzle-orm";
88254
+ import { and as and71, desc as desc39, eq as eq84, gte as gte21, inArray as inArray34, isNull as isNull15, or as or20 } from "drizzle-orm";
88106
88255
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
88107
88256
  async function discoveryRoutes(app, opts) {
88108
88257
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -88136,13 +88285,13 @@ async function discoveryRoutes(app, opts) {
88136
88285
  const now = (/* @__PURE__ */ new Date()).toISOString();
88137
88286
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
88138
88287
  const decision = app.db.transaction((tx) => {
88139
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and70(
88140
- eq83(discoverySessions.projectId, project.id),
88141
- eq83(discoverySessions.icpDescription, icpDescription),
88288
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and71(
88289
+ eq84(discoverySessions.projectId, project.id),
88290
+ eq84(discoverySessions.icpDescription, icpDescription),
88142
88291
  // Buyer is part of session identity: it changes the seed prompt's
88143
88292
  // semantics, so a request with a different (or no) buyer must start
88144
88293
  // its own session, never adopt another buyer's probes.
88145
- parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) : eq83(discoverySessions.buyerDescription, parsed.data.buyerDescription),
88294
+ parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) : eq84(discoverySessions.buyerDescription, parsed.data.buyerDescription),
88146
88295
  // Locations are identity too: a different service-area subset seeds
88147
88296
  // and probes a different geo, so it must never reuse another geo's
88148
88297
  // session. resolveLocations is deterministic (project-config order),
@@ -88152,18 +88301,18 @@ async function discoveryRoutes(app, opts) {
88152
88301
  // locations a NULL row's subset is unknowable, so it conservatively
88153
88302
  // never reuses — a one-time, bounded (2h window) non-reuse after
88154
88303
  // upgrade, never a wrong reuse.
88155
- locations.length === 0 ? or19(isNull15(discoverySessions.locations), eq83(discoverySessions.locations, locations)) : eq83(discoverySessions.locations, locations),
88304
+ locations.length === 0 ? or20(isNull15(discoverySessions.locations), eq84(discoverySessions.locations, locations)) : eq84(discoverySessions.locations, locations),
88156
88305
  // Seed provider set is identity: a different phrasing distribution
88157
88306
  // must never reuse another set's session. Null (= Gemini-only
88158
88307
  // default, including explicit ['gemini']) matches legacy rows.
88159
- seedProviders == null ? isNull15(discoverySessions.seedProviders) : eq83(discoverySessions.seedProviders, seedProviders),
88160
- inArray33(discoverySessions.status, [
88308
+ seedProviders == null ? isNull15(discoverySessions.seedProviders) : eq84(discoverySessions.seedProviders, seedProviders),
88309
+ inArray34(discoverySessions.status, [
88161
88310
  DiscoverySessionStatuses.queued,
88162
88311
  DiscoverySessionStatuses.seeding,
88163
88312
  DiscoverySessionStatuses.probing
88164
88313
  ]),
88165
88314
  gte21(discoverySessions.createdAt, ageFloorIso)
88166
- )).orderBy(desc38(discoverySessions.createdAt)).get();
88315
+ )).orderBy(desc39(discoverySessions.createdAt)).get();
88167
88316
  if (existing && existing.runId) {
88168
88317
  return { reused: true, sessionId: existing.id, runId: existing.runId };
88169
88318
  }
@@ -88232,7 +88381,7 @@ async function discoveryRoutes(app, opts) {
88232
88381
  const project = resolveProject(app.db, request.params.name);
88233
88382
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
88234
88383
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
88235
- const rows = app.db.select().from(discoverySessions).where(eq83(discoverySessions.projectId, project.id)).orderBy(desc38(discoverySessions.createdAt)).limit(limit).all();
88384
+ const rows = app.db.select().from(discoverySessions).where(eq84(discoverySessions.projectId, project.id)).orderBy(desc39(discoverySessions.createdAt)).limit(limit).all();
88236
88385
  return reply.send(rows.map(serializeSession));
88237
88386
  }
88238
88387
  );
@@ -88240,11 +88389,11 @@ async function discoveryRoutes(app, opts) {
88240
88389
  "/projects/:name/discover/sessions/:id",
88241
88390
  async (request, reply) => {
88242
88391
  const project = resolveProject(app.db, request.params.name);
88243
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88392
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88244
88393
  if (!session || session.projectId !== project.id) {
88245
88394
  throw notFound("Discovery session", request.params.id);
88246
88395
  }
88247
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88396
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88248
88397
  const detail = {
88249
88398
  ...serializeSession(session),
88250
88399
  probes: probeRows.map(serializeProbe)
@@ -88256,7 +88405,7 @@ async function discoveryRoutes(app, opts) {
88256
88405
  "/projects/:name/discover/sessions/:id/harvest",
88257
88406
  async (request, reply) => {
88258
88407
  const project = resolveProject(app.db, request.params.name);
88259
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88408
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88260
88409
  if (!session || session.projectId !== project.id) {
88261
88410
  throw notFound("Discovery session", request.params.id);
88262
88411
  }
@@ -88264,7 +88413,7 @@ async function discoveryRoutes(app, opts) {
88264
88413
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
88265
88414
  const applyAnchor = request.query.anchor !== "false";
88266
88415
  const provider = session.seedProvider ?? "gemini";
88267
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88416
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88268
88417
  const extract = opts.harvestSearchQueries;
88269
88418
  const probesWithQueries = probeRows.map((row) => {
88270
88419
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -88275,7 +88424,7 @@ async function discoveryRoutes(app, opts) {
88275
88424
  return { searchQueries: [] };
88276
88425
  }
88277
88426
  });
88278
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq83(queries.projectId, project.id)).all().map((r) => r.query);
88427
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query);
88279
88428
  const anchorTerms = buildHarvestAnchorTerms(
88280
88429
  [session.icpDescription ?? "", ...trackedQueries],
88281
88430
  effectiveDomains(project)
@@ -88322,11 +88471,11 @@ async function discoveryRoutes(app, opts) {
88322
88471
  "/projects/:name/discover/sessions/:id/promote",
88323
88472
  async (request, reply) => {
88324
88473
  const project = resolveProject(app.db, request.params.name);
88325
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88474
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88326
88475
  if (!session || session.projectId !== project.id) {
88327
88476
  throw notFound("Discovery session", request.params.id);
88328
88477
  }
88329
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88478
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88330
88479
  const cited = /* @__PURE__ */ new Set();
88331
88480
  const aspirational = /* @__PURE__ */ new Set();
88332
88481
  const wasted = /* @__PURE__ */ new Set();
@@ -88361,7 +88510,7 @@ async function discoveryRoutes(app, opts) {
88361
88510
  );
88362
88511
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
88363
88512
  const project = resolveProject(app.db, request.params.name);
88364
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88513
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88365
88514
  if (!session || session.projectId !== project.id) {
88366
88515
  throw notFound("Discovery session", request.params.id);
88367
88516
  }
@@ -88384,7 +88533,7 @@ async function discoveryRoutes(app, opts) {
88384
88533
  const bucketSet = new Set(buckets);
88385
88534
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
88386
88535
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
88387
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88536
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88388
88537
  const candidateQueries = /* @__PURE__ */ new Set();
88389
88538
  for (const probe of probeRows) {
88390
88539
  if (!probe.bucket) continue;
@@ -88392,7 +88541,7 @@ async function discoveryRoutes(app, opts) {
88392
88541
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
88393
88542
  }
88394
88543
  const existingQueries = new Set(
88395
- app.db.select({ query: queries.query }).from(queries).where(eq83(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
88544
+ app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
88396
88545
  );
88397
88546
  const promotedQueries = [];
88398
88547
  const skippedQueries = [];
@@ -88605,7 +88754,7 @@ function planCompetitorPromotion(input) {
88605
88754
 
88606
88755
  // ../api-routes/src/discovery/orchestrate.ts
88607
88756
  import crypto51 from "crypto";
88608
- import { eq as eq84 } from "drizzle-orm";
88757
+ import { eq as eq85 } from "drizzle-orm";
88609
88758
  var DEFAULT_MAX_PROBES = 100;
88610
88759
  var ABSOLUTE_MAX_PROBES = 500;
88611
88760
  function classifyProbeBucket(input) {
@@ -88690,7 +88839,7 @@ async function executeDiscovery(opts) {
88690
88839
  status: DiscoverySessionStatuses.seeding,
88691
88840
  dedupThreshold,
88692
88841
  startedAt
88693
- }).where(eq84(discoverySessions.id, opts.sessionId)).run();
88842
+ }).where(eq85(discoverySessions.id, opts.sessionId)).run();
88694
88843
  const seedResult = await opts.deps.seed({
88695
88844
  project: opts.project,
88696
88845
  icpDescription: opts.icpDescription,
@@ -88751,7 +88900,7 @@ async function executeDiscovery(opts) {
88751
88900
  dedupBandPairFraction: dedupStats.bandPairFraction,
88752
88901
  dedupPairsTotal: dedupStats.pairsTotal,
88753
88902
  warning
88754
- }).where(eq84(discoverySessions.id, opts.sessionId)).run();
88903
+ }).where(eq85(discoverySessions.id, opts.sessionId)).run();
88755
88904
  const probeLocation = opts.locations?.[0];
88756
88905
  const probeResults = await mapWithConcurrency(
88757
88906
  probedCanonicals,
@@ -88805,7 +88954,7 @@ async function executeDiscovery(opts) {
88805
88954
  wastedCount: buckets["wasted-surface"],
88806
88955
  competitorMap,
88807
88956
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
88808
- }).where(eq84(discoverySessions.id, opts.sessionId)).run();
88957
+ }).where(eq85(discoverySessions.id, opts.sessionId)).run();
88809
88958
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
88810
88959
  return {
88811
88960
  buckets,
@@ -88845,7 +88994,7 @@ function markSessionFailed(db, sessionId, error) {
88845
88994
  status: DiscoverySessionStatuses.failed,
88846
88995
  error,
88847
88996
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
88848
- }).where(eq84(discoverySessions.id, sessionId)).run();
88997
+ }).where(eq85(discoverySessions.id, sessionId)).run();
88849
88998
  }
88850
88999
  function dedupeStrings(input) {
88851
89000
  const seen2 = /* @__PURE__ */ new Set();
@@ -88863,7 +89012,7 @@ function dedupeStrings(input) {
88863
89012
 
88864
89013
  // ../api-routes/src/technical-aeo.ts
88865
89014
  import crypto52 from "crypto";
88866
- import { and as and71, asc as asc16, count as count4, desc as desc39, eq as eq85, gte as gte22, inArray as inArray34, isNotNull as isNotNull8, isNull as isNull16, lt as lt13, or as or20, sql as sql30 } from "drizzle-orm";
89015
+ import { and as and72, asc as asc16, count as count4, desc as desc40, eq as eq86, gte as gte22, inArray as inArray35, isNotNull as isNotNull9, isNull as isNull16, lt as lt13, or as or21, sql as sql30 } from "drizzle-orm";
88867
89016
  import { alias } from "drizzle-orm/sqlite-core";
88868
89017
  var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
88869
89018
  ...SiteCrawlFetchedStates,
@@ -89100,8 +89249,8 @@ function parseLinkKind(value) {
89100
89249
  return allowed.data;
89101
89250
  }
89102
89251
  function linkKindFilter(linkKind) {
89103
- if (linkKind === SiteHealthLinkKinds.content) return eq85(siteCrawlEdges.isTemplate, false);
89104
- if (linkKind === SiteHealthLinkKinds.template) return eq85(siteCrawlEdges.isTemplate, true);
89252
+ if (linkKind === SiteHealthLinkKinds.content) return eq86(siteCrawlEdges.isTemplate, false);
89253
+ if (linkKind === SiteHealthLinkKinds.template) return eq86(siteCrawlEdges.isTemplate, true);
89105
89254
  return void 0;
89106
89255
  }
89107
89256
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
@@ -89205,20 +89354,20 @@ function changedFields(before, after, fields) {
89205
89354
  }
89206
89355
  async function technicalAeoRoutes(app, opts) {
89207
89356
  const visibleCrawlFilters = (projectId) => [
89208
- eq85(siteCrawlSnapshots.projectId, projectId),
89209
- eq85(runs.projectId, projectId),
89210
- eq85(runs.kind, RunKinds["site-audit"]),
89211
- inArray34(runs.status, SURFACEABLE_STATUSES),
89357
+ eq86(siteCrawlSnapshots.projectId, projectId),
89358
+ eq86(runs.projectId, projectId),
89359
+ eq86(runs.kind, RunKinds["site-audit"]),
89360
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89212
89361
  notProbeRun()
89213
89362
  ];
89214
89363
  const availableScanDatesFor = (projectId, requestedDate) => {
89215
89364
  if (!requestedDate) return void 0;
89216
89365
  const date = sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
89217
89366
  const filters = visibleCrawlFilters(projectId);
89218
- const allDates = and71(...filters);
89219
- const matchingDates = and71(...filters, eq85(sql30`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
89220
- const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc39(date)).limit(10).all().map((row) => row.date);
89221
- const countDates = (where) => app.db.select({ value: sql30`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
89367
+ const allDates = and72(...filters);
89368
+ const matchingDates = and72(...filters, eq86(sql30`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
89369
+ const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc40(date)).limit(10).all().map((row) => row.date);
89370
+ const countDates = (where) => app.db.select({ value: sql30`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
89222
89371
  return {
89223
89372
  recentDates: readDates(allDates),
89224
89373
  totalDates: countDates(allDates),
@@ -89235,33 +89384,33 @@ async function technicalAeoRoutes(app, opts) {
89235
89384
  filters.push(gte22(siteCrawlSnapshots.createdAt, requestedDate), lt13(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
89236
89385
  }
89237
89386
  const selectionFilters = scope.scoredOnly ? [...filters, sql30`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
89238
- const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(and71(...selectionFilters, runId ? eq85(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
89239
- desc39(sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
89240
- desc39(siteCrawlSnapshots.complete),
89241
- desc39(siteCrawlSnapshots.pagesFetched),
89242
- desc39(siteCrawlSnapshots.createdAt),
89243
- desc39(siteCrawlSnapshots.runId)
89387
+ const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(...selectionFilters, runId ? eq86(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
89388
+ desc40(sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
89389
+ desc40(siteCrawlSnapshots.complete),
89390
+ desc40(siteCrawlSnapshots.pagesFetched),
89391
+ desc40(siteCrawlSnapshots.createdAt),
89392
+ desc40(siteCrawlSnapshots.runId)
89244
89393
  ).limit(1).get();
89245
89394
  if (!selected) return void 0;
89246
89395
  const date = selected.snapshot.createdAt.slice(0, 10);
89247
89396
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
89248
89397
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
89249
- const sameDate = and71(
89398
+ const sameDate = and72(
89250
89399
  ...filters,
89251
89400
  gte22(siteCrawlSnapshots.createdAt, date),
89252
89401
  lt13(siteCrawlSnapshots.createdAt, nextDate.toISOString())
89253
89402
  );
89254
- const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89403
+ const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89255
89404
  const candidates = app.db.select({
89256
89405
  runId: siteCrawlSnapshots.runId,
89257
89406
  createdAt: siteCrawlSnapshots.createdAt,
89258
89407
  complete: siteCrawlSnapshots.complete,
89259
89408
  pages: siteCrawlSnapshots.pagesFetched
89260
- }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
89261
- desc39(siteCrawlSnapshots.complete),
89262
- desc39(siteCrawlSnapshots.pagesFetched),
89263
- desc39(siteCrawlSnapshots.createdAt),
89264
- desc39(siteCrawlSnapshots.runId)
89409
+ }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
89410
+ desc40(siteCrawlSnapshots.complete),
89411
+ desc40(siteCrawlSnapshots.pagesFetched),
89412
+ desc40(siteCrawlSnapshots.createdAt),
89413
+ desc40(siteCrawlSnapshots.runId)
89265
89414
  ).limit(10).all();
89266
89415
  const runSelection = {
89267
89416
  reason: runId ? "explicit-run" : scope.scoredOnly ? "latest-scored-scan" : requestedDate ? selected.snapshot.complete ? "requested-date-complete" : "requested-date-most-pages" : selected.snapshot.complete ? "latest-date-complete" : "latest-date-most-pages",
@@ -89275,20 +89424,20 @@ async function technicalAeoRoutes(app, opts) {
89275
89424
  };
89276
89425
  const resolveAudit = (projectId, runId, recordedBefore) => {
89277
89426
  const filters = [
89278
- eq85(siteAuditSnapshots.projectId, projectId),
89279
- eq85(runs.projectId, projectId),
89280
- eq85(runs.kind, RunKinds["site-audit"]),
89281
- inArray34(runs.status, SURFACEABLE_STATUSES),
89427
+ eq86(siteAuditSnapshots.projectId, projectId),
89428
+ eq86(runs.projectId, projectId),
89429
+ eq86(runs.kind, RunKinds["site-audit"]),
89430
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89282
89431
  notProbeRun(),
89283
89432
  ...recordedBefore ? [lt13(siteAuditSnapshots.createdAt, recordedBefore)] : []
89284
89433
  ];
89285
89434
  const complete = sql30`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
89286
- const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(and71(...filters, pinnedRunId ? eq85(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
89287
- desc39(sql30`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
89288
- desc39(complete),
89289
- desc39(siteAuditSnapshots.pagesAudited),
89290
- desc39(siteAuditSnapshots.createdAt),
89291
- desc39(siteAuditSnapshots.runId)
89435
+ const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(...filters, pinnedRunId ? eq86(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
89436
+ desc40(sql30`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
89437
+ desc40(complete),
89438
+ desc40(siteAuditSnapshots.pagesAudited),
89439
+ desc40(siteAuditSnapshots.createdAt),
89440
+ desc40(siteAuditSnapshots.runId)
89292
89441
  ).limit(1).get();
89293
89442
  const auditOfCrawl = (target) => {
89294
89443
  const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
@@ -89304,22 +89453,22 @@ async function technicalAeoRoutes(app, opts) {
89304
89453
  const date = selected.snap.createdAt.slice(0, 10);
89305
89454
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
89306
89455
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
89307
- const sameDate = and71(
89456
+ const sameDate = and72(
89308
89457
  ...filters,
89309
89458
  gte22(siteAuditSnapshots.createdAt, date),
89310
89459
  lt13(siteAuditSnapshots.createdAt, nextDate.toISOString())
89311
89460
  );
89312
- const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89461
+ const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89313
89462
  const candidates = app.db.select({
89314
89463
  runId: siteAuditSnapshots.runId,
89315
89464
  createdAt: siteAuditSnapshots.createdAt,
89316
89465
  complete,
89317
89466
  pages: siteAuditSnapshots.pagesAudited
89318
- }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
89319
- desc39(complete),
89320
- desc39(siteAuditSnapshots.pagesAudited),
89321
- desc39(siteAuditSnapshots.createdAt),
89322
- desc39(siteAuditSnapshots.runId)
89467
+ }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
89468
+ desc40(complete),
89469
+ desc40(siteAuditSnapshots.pagesAudited),
89470
+ desc40(siteAuditSnapshots.createdAt),
89471
+ desc40(siteAuditSnapshots.runId)
89323
89472
  ).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
89324
89473
  const runSelection = {
89325
89474
  // A crawl exists here only when none stored a scorecard: this score is a fallback.
@@ -89381,10 +89530,10 @@ async function technicalAeoRoutes(app, opts) {
89381
89530
  };
89382
89531
  const inventorySummaryFor = (scope) => {
89383
89532
  if (!scope) return null;
89384
- const groups = crawlReasonGroups(and71(
89385
- eq85(siteCrawlPages.projectId, scope.projectId),
89386
- eq85(siteCrawlPages.runId, scope.runId),
89387
- eq85(siteCrawlPages.attemptId, scope.attemptId)
89533
+ const groups = crawlReasonGroups(and72(
89534
+ eq86(siteCrawlPages.projectId, scope.projectId),
89535
+ eq86(siteCrawlPages.runId, scope.runId),
89536
+ eq86(siteCrawlPages.attemptId, scope.attemptId)
89388
89537
  ));
89389
89538
  const total = groups.reduce((sum, group) => sum + group.pages, 0);
89390
89539
  const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
@@ -89404,34 +89553,34 @@ async function technicalAeoRoutes(app, opts) {
89404
89553
  const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
89405
89554
  const pageInScope = (scope, selector) => {
89406
89555
  if (!selector.nodeKey && !selector.url) return null;
89407
- return app.db.select().from(siteCrawlPages).where(and71(
89408
- eq85(siteCrawlPages.projectId, scope.projectId),
89409
- eq85(siteCrawlPages.runId, scope.runId),
89410
- eq85(siteCrawlPages.attemptId, scope.attemptId),
89411
- selector.nodeKey ? eq85(siteCrawlPages.nodeKey, selector.nodeKey) : eq85(siteCrawlPages.url, selector.url)
89556
+ return app.db.select().from(siteCrawlPages).where(and72(
89557
+ eq86(siteCrawlPages.projectId, scope.projectId),
89558
+ eq86(siteCrawlPages.runId, scope.runId),
89559
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
89560
+ selector.nodeKey ? eq86(siteCrawlPages.nodeKey, selector.nodeKey) : eq86(siteCrawlPages.url, selector.url)
89412
89561
  )).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
89413
89562
  };
89414
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and71(
89415
- eq85(siteCrawlPages.projectId, scope.projectId),
89416
- eq85(siteCrawlPages.runId, scope.runId),
89417
- eq85(siteCrawlPages.attemptId, scope.attemptId),
89418
- eq85(siteCrawlPages.depth, 0)
89563
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and72(
89564
+ eq86(siteCrawlPages.projectId, scope.projectId),
89565
+ eq86(siteCrawlPages.runId, scope.runId),
89566
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
89567
+ eq86(siteCrawlPages.depth, 0)
89419
89568
  )).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
89420
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and71(
89421
- eq85(runs.id, runId),
89422
- eq85(runs.projectId, projectId),
89423
- eq85(runs.kind, RunKinds["site-audit"]),
89424
- inArray34(runs.status, SURFACEABLE_STATUSES),
89569
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and72(
89570
+ eq86(runs.id, runId),
89571
+ eq86(runs.projectId, projectId),
89572
+ eq86(runs.kind, RunKinds["site-audit"]),
89573
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89425
89574
  notProbeRun()
89426
89575
  )).limit(1).get());
89427
89576
  const assertKnownAuditRun = (projectId, runId) => {
89428
89577
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
89429
89578
  };
89430
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(and71(
89431
- eq85(siteAuditSnapshots.projectId, projectId),
89432
- eq85(runs.projectId, projectId),
89433
- eq85(runs.kind, RunKinds["site-audit"]),
89434
- inArray34(runs.status, SURFACEABLE_STATUSES),
89579
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
89580
+ eq86(siteAuditSnapshots.projectId, projectId),
89581
+ eq86(runs.projectId, projectId),
89582
+ eq86(runs.kind, RunKinds["site-audit"]),
89583
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89435
89584
  notProbeRun()
89436
89585
  )).limit(1).get());
89437
89586
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -89532,14 +89681,14 @@ async function technicalAeoRoutes(app, opts) {
89532
89681
  };
89533
89682
  }
89534
89683
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
89535
- const conds = [eq85(siteAuditPages.projectId, project.id), eq85(siteAuditPages.runId, latest.snap.runId)];
89536
- if (statusFilter) conds.push(eq85(siteAuditPages.status, statusFilter));
89537
- const where = and71(...conds);
89684
+ const conds = [eq86(siteAuditPages.projectId, project.id), eq86(siteAuditPages.runId, latest.snap.runId)];
89685
+ if (statusFilter) conds.push(eq86(siteAuditPages.status, statusFilter));
89686
+ const where = and72(...conds);
89538
89687
  const totalRow = app.db.select({ value: count4() }).from(siteAuditPages).where(where).get();
89539
89688
  const total = totalRow?.value ?? 0;
89540
89689
  const limit = parsePositiveInt(request.query.limit, 100, 500);
89541
89690
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
89542
- const orderBy = request.query.sort === "score-desc" ? desc39(siteAuditPages.overallScore) : request.query.sort === "url" ? asc16(siteAuditPages.url) : asc16(siteAuditPages.overallScore);
89691
+ const orderBy = request.query.sort === "score-desc" ? desc40(siteAuditPages.overallScore) : request.query.sort === "url" ? asc16(siteAuditPages.url) : asc16(siteAuditPages.overallScore);
89543
89692
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
89544
89693
  const pages = rows.map((row) => ({
89545
89694
  url: row.url,
@@ -89558,13 +89707,13 @@ async function technicalAeoRoutes(app, opts) {
89558
89707
  auditedAt: siteAuditSnapshots.auditedAt,
89559
89708
  aggregateScore: siteAuditSnapshots.aggregateScore,
89560
89709
  pagesAudited: siteAuditSnapshots.pagesAudited
89561
- }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(and71(
89562
- eq85(siteAuditSnapshots.projectId, project.id),
89563
- eq85(runs.projectId, project.id),
89564
- eq85(runs.kind, RunKinds["site-audit"]),
89565
- inArray34(runs.status, SURFACEABLE_STATUSES),
89710
+ }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
89711
+ eq86(siteAuditSnapshots.projectId, project.id),
89712
+ eq86(runs.projectId, project.id),
89713
+ eq86(runs.kind, RunKinds["site-audit"]),
89714
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89566
89715
  notProbeRun()
89567
- )).orderBy(desc39(siteAuditSnapshots.createdAt)).limit(limit).all();
89716
+ )).orderBy(desc40(siteAuditSnapshots.createdAt)).limit(limit).all();
89568
89717
  return { project: project.name, points: rows.reverse() };
89569
89718
  });
89570
89719
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -89664,10 +89813,10 @@ async function technicalAeoRoutes(app, opts) {
89664
89813
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
89665
89814
  snapshot.rootUrl
89666
89815
  )?.nodeKey ?? null;
89667
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and71(
89668
- eq85(siteCrawlGraphLayouts.projectId, project.id),
89669
- eq85(siteCrawlGraphLayouts.runId, snapshot.runId),
89670
- eq85(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
89816
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and72(
89817
+ eq86(siteCrawlGraphLayouts.projectId, project.id),
89818
+ eq86(siteCrawlGraphLayouts.runId, snapshot.runId),
89819
+ eq86(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
89671
89820
  )).limit(1).get();
89672
89821
  if (!persistedLayout) {
89673
89822
  return {
@@ -89720,9 +89869,9 @@ async function technicalAeoRoutes(app, opts) {
89720
89869
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
89721
89870
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
89722
89871
  const graphScope = [
89723
- eq85(siteCrawlGraphNodes.projectId, project.id),
89724
- eq85(siteCrawlGraphNodes.runId, snapshot.runId),
89725
- eq85(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
89872
+ eq86(siteCrawlGraphNodes.projectId, project.id),
89873
+ eq86(siteCrawlGraphNodes.runId, snapshot.runId),
89874
+ eq86(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
89726
89875
  ];
89727
89876
  const nodeRows = app.db.select({
89728
89877
  nodeKey: siteCrawlPages.nodeKey,
@@ -89741,18 +89890,18 @@ async function technicalAeoRoutes(app, opts) {
89741
89890
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
89742
89891
  x: siteCrawlGraphNodes.x,
89743
89892
  y: siteCrawlGraphNodes.y
89744
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and71(
89745
- eq85(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
89746
- eq85(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
89747
- eq85(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
89748
- eq85(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
89749
- )).where(and71(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc16(siteCrawlGraphNodes.sampleRank)).all();
89893
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and72(
89894
+ eq86(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
89895
+ eq86(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
89896
+ eq86(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
89897
+ eq86(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
89898
+ )).where(and72(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc16(siteCrawlGraphNodes.sampleRank)).all();
89750
89899
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
89751
89900
  ...row,
89752
89901
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
89753
89902
  healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
89754
89903
  }));
89755
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq85(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
89904
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq86(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
89756
89905
  const edges = app.db.select({
89757
89906
  edgeKey: siteCrawlGraphEdges.edgeKey,
89758
89907
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -89760,22 +89909,22 @@ async function technicalAeoRoutes(app, opts) {
89760
89909
  followable: siteCrawlGraphEdges.followable,
89761
89910
  occurrences: siteCrawlGraphEdges.occurrences,
89762
89911
  isTemplate: siteCrawlGraphEdges.isTemplate
89763
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and71(
89764
- eq85(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
89765
- eq85(graphSourceNode.runId, siteCrawlGraphEdges.runId),
89766
- eq85(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
89767
- eq85(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
89912
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and72(
89913
+ eq86(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
89914
+ eq86(graphSourceNode.runId, siteCrawlGraphEdges.runId),
89915
+ eq86(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
89916
+ eq86(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
89768
89917
  lt13(graphSourceNode.sampleRank, maxNodes)
89769
- )).innerJoin(graphTargetNode, and71(
89770
- eq85(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
89771
- eq85(graphTargetNode.runId, siteCrawlGraphEdges.runId),
89772
- eq85(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
89773
- eq85(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
89918
+ )).innerJoin(graphTargetNode, and72(
89919
+ eq86(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
89920
+ eq86(graphTargetNode.runId, siteCrawlGraphEdges.runId),
89921
+ eq86(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
89922
+ eq86(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
89774
89923
  lt13(graphTargetNode.sampleRank, maxNodes)
89775
- )).where(and71(
89776
- eq85(siteCrawlGraphEdges.projectId, project.id),
89777
- eq85(siteCrawlGraphEdges.runId, snapshot.runId),
89778
- eq85(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
89924
+ )).where(and72(
89925
+ eq86(siteCrawlGraphEdges.projectId, project.id),
89926
+ eq86(siteCrawlGraphEdges.runId, snapshot.runId),
89927
+ eq86(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
89779
89928
  lt13(siteCrawlGraphEdges.sampleRank, maxEdges),
89780
89929
  graphLinkKindFilter
89781
89930
  )).orderBy(asc16(siteCrawlGraphEdges.sampleRank)).all();
@@ -89870,15 +90019,15 @@ async function technicalAeoRoutes(app, opts) {
89870
90019
  let frontier = [focus.nodeKey];
89871
90020
  let queryTruncated = false;
89872
90021
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
89873
- const candidates = app.db.select().from(siteCrawlEdges).where(and71(
89874
- eq85(siteCrawlEdges.projectId, scope.projectId),
89875
- eq85(siteCrawlEdges.runId, scope.runId),
89876
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
89877
- eq85(siteCrawlEdges.internal, true),
89878
- isNotNull8(siteCrawlEdges.targetNodeKey),
89879
- or20(
89880
- inArray34(siteCrawlEdges.sourceNodeKey, frontier),
89881
- inArray34(siteCrawlEdges.targetNodeKey, frontier)
90022
+ const candidates = app.db.select().from(siteCrawlEdges).where(and72(
90023
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90024
+ eq86(siteCrawlEdges.runId, scope.runId),
90025
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90026
+ eq86(siteCrawlEdges.internal, true),
90027
+ isNotNull9(siteCrawlEdges.targetNodeKey),
90028
+ or21(
90029
+ inArray35(siteCrawlEdges.sourceNodeKey, frontier),
90030
+ inArray35(siteCrawlEdges.targetNodeKey, frontier)
89882
90031
  )
89883
90032
  )).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
89884
90033
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
@@ -89911,22 +90060,22 @@ async function technicalAeoRoutes(app, opts) {
89911
90060
  frontier = [...next].sort();
89912
90061
  }
89913
90062
  const nodeKeys = [...distances.keys()];
89914
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and71(
89915
- eq85(siteCrawlPages.projectId, scope.projectId),
89916
- eq85(siteCrawlPages.runId, scope.runId),
89917
- eq85(siteCrawlPages.attemptId, scope.attemptId),
89918
- inArray34(siteCrawlPages.nodeKey, nodeKeys)
90063
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
90064
+ eq86(siteCrawlPages.projectId, scope.projectId),
90065
+ eq86(siteCrawlPages.runId, scope.runId),
90066
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90067
+ inArray35(siteCrawlPages.nodeKey, nodeKeys)
89919
90068
  )).all();
89920
90069
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
89921
90070
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
89922
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and71(
89923
- eq85(siteCrawlEdges.projectId, scope.projectId),
89924
- eq85(siteCrawlEdges.runId, scope.runId),
89925
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
89926
- eq85(siteCrawlEdges.internal, true),
89927
- isNotNull8(siteCrawlEdges.targetNodeKey),
89928
- inArray34(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
89929
- inArray34(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
90071
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and72(
90072
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90073
+ eq86(siteCrawlEdges.runId, scope.runId),
90074
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90075
+ eq86(siteCrawlEdges.internal, true),
90076
+ isNotNull9(siteCrawlEdges.targetNodeKey),
90077
+ inArray35(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
90078
+ inArray35(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
89930
90079
  )).limit(1).get();
89931
90080
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
89932
90081
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -90019,23 +90168,23 @@ async function technicalAeoRoutes(app, opts) {
90019
90168
  let found = fromPage.nodeKey === toPage.nodeKey;
90020
90169
  let truncated = false;
90021
90170
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
90022
- const candidates = app.db.select().from(siteCrawlEdges).where(and71(
90023
- eq85(siteCrawlEdges.projectId, scope.projectId),
90024
- eq85(siteCrawlEdges.runId, scope.runId),
90025
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
90026
- eq85(siteCrawlEdges.internal, true),
90027
- eq85(siteCrawlEdges.followable, true),
90028
- eq85(siteCrawlEdges.relation, "anchor"),
90029
- isNotNull8(siteCrawlEdges.targetNodeKey),
90030
- inArray34(siteCrawlEdges.sourceNodeKey, frontier)
90171
+ const candidates = app.db.select().from(siteCrawlEdges).where(and72(
90172
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90173
+ eq86(siteCrawlEdges.runId, scope.runId),
90174
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90175
+ eq86(siteCrawlEdges.internal, true),
90176
+ eq86(siteCrawlEdges.followable, true),
90177
+ eq86(siteCrawlEdges.relation, "anchor"),
90178
+ isNotNull9(siteCrawlEdges.targetNodeKey),
90179
+ inArray35(siteCrawlEdges.sourceNodeKey, frontier)
90031
90180
  )).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
90032
90181
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
90033
90182
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
90034
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and71(
90035
- eq85(siteCrawlPages.projectId, scope.projectId),
90036
- eq85(siteCrawlPages.runId, scope.runId),
90037
- eq85(siteCrawlPages.attemptId, scope.attemptId),
90038
- inArray34(siteCrawlPages.nodeKey, candidateTargetKeys)
90183
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and72(
90184
+ eq86(siteCrawlPages.projectId, scope.projectId),
90185
+ eq86(siteCrawlPages.runId, scope.runId),
90186
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90187
+ inArray35(siteCrawlPages.nodeKey, candidateTargetKeys)
90039
90188
  )).all().map((row) => row.nodeKey));
90040
90189
  const next = /* @__PURE__ */ new Set();
90041
90190
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -90076,11 +90225,11 @@ async function technicalAeoRoutes(app, opts) {
90076
90225
  pathEdges.unshift(step.edge);
90077
90226
  pathKeys.unshift(step.nodeKey);
90078
90227
  }
90079
- const pathRows = app.db.select().from(siteCrawlPages).where(and71(
90080
- eq85(siteCrawlPages.projectId, scope.projectId),
90081
- eq85(siteCrawlPages.runId, scope.runId),
90082
- eq85(siteCrawlPages.attemptId, scope.attemptId),
90083
- inArray34(siteCrawlPages.nodeKey, pathKeys)
90228
+ const pathRows = app.db.select().from(siteCrawlPages).where(and72(
90229
+ eq86(siteCrawlPages.projectId, scope.projectId),
90230
+ eq86(siteCrawlPages.runId, scope.runId),
90231
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90232
+ inArray35(siteCrawlPages.nodeKey, pathKeys)
90084
90233
  )).all();
90085
90234
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
90086
90235
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -90108,25 +90257,25 @@ async function technicalAeoRoutes(app, opts) {
90108
90257
  throw validationError("change must be all, added, removed, or changed");
90109
90258
  }
90110
90259
  const completeSnapshotFilters = [
90111
- eq85(siteCrawlSnapshots.projectId, project.id),
90112
- eq85(runs.projectId, project.id),
90113
- eq85(runs.kind, RunKinds["site-audit"]),
90114
- eq85(runs.status, RunStatuses.completed),
90115
- eq85(siteCrawlSnapshots.complete, true),
90260
+ eq86(siteCrawlSnapshots.projectId, project.id),
90261
+ eq86(runs.projectId, project.id),
90262
+ eq86(runs.kind, RunKinds["site-audit"]),
90263
+ eq86(runs.status, RunStatuses.completed),
90264
+ eq86(siteCrawlSnapshots.complete, true),
90116
90265
  notProbeRun()
90117
90266
  ];
90118
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(and71(...completeSnapshotFilters)).orderBy(desc39(siteCrawlSnapshots.createdAt), desc39(siteCrawlSnapshots.runId)).limit(1).get();
90267
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(...completeSnapshotFilters)).orderBy(desc40(siteCrawlSnapshots.createdAt), desc40(siteCrawlSnapshots.runId)).limit(1).get();
90119
90268
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
90120
- const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(and71(
90269
+ const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(
90121
90270
  ...completeSnapshotFilters,
90122
- or20(
90271
+ or21(
90123
90272
  lt13(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
90124
- and71(
90125
- eq85(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
90273
+ and72(
90274
+ eq86(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
90126
90275
  lt13(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
90127
90276
  )
90128
90277
  )
90129
- )).orderBy(desc39(siteCrawlSnapshots.createdAt), desc39(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
90278
+ )).orderBy(desc40(siteCrawlSnapshots.createdAt), desc40(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
90130
90279
  if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
90131
90280
  if (!afterTarget) {
90132
90281
  const latestCrawl = resolveCrawl(project.id);
@@ -90367,18 +90516,18 @@ async function technicalAeoRoutes(app, opts) {
90367
90516
  const visibleKeys = keyRows.slice(0, limit);
90368
90517
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
90369
90518
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
90370
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and71(
90371
- eq85(siteCrawlPages.projectId, scope.projectId),
90372
- eq85(siteCrawlPages.runId, scope.runId),
90373
- eq85(siteCrawlPages.attemptId, scope.attemptId),
90374
- inArray34(siteCrawlPages.nodeKey, pageKeys)
90519
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
90520
+ eq86(siteCrawlPages.projectId, scope.projectId),
90521
+ eq86(siteCrawlPages.runId, scope.runId),
90522
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90523
+ inArray35(siteCrawlPages.nodeKey, pageKeys)
90375
90524
  )).all();
90376
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and71(
90377
- eq85(siteCrawlEdges.projectId, scope.projectId),
90378
- eq85(siteCrawlEdges.runId, scope.runId),
90379
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
90380
- eq85(siteCrawlEdges.internal, true),
90381
- inArray34(siteCrawlEdges.edgeKey, linkKeys)
90525
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and72(
90526
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90527
+ eq86(siteCrawlEdges.runId, scope.runId),
90528
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90529
+ eq86(siteCrawlEdges.internal, true),
90530
+ inArray35(siteCrawlEdges.edgeKey, linkKeys)
90382
90531
  )).all();
90383
90532
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
90384
90533
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -90531,16 +90680,16 @@ async function technicalAeoRoutes(app, opts) {
90531
90680
  }
90532
90681
  const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
90533
90682
  const filters = [
90534
- eq85(siteCrawlPages.projectId, project.id),
90535
- eq85(siteCrawlPages.runId, snapshot.runId),
90536
- eq85(siteCrawlPages.attemptId, snapshot.attemptId)
90683
+ eq86(siteCrawlPages.projectId, project.id),
90684
+ eq86(siteCrawlPages.runId, snapshot.runId),
90685
+ eq86(siteCrawlPages.attemptId, snapshot.attemptId)
90537
90686
  ];
90538
- if (request.query.nodeKey) filters.push(eq85(siteCrawlPages.nodeKey, request.query.nodeKey));
90687
+ if (request.query.nodeKey) filters.push(eq86(siteCrawlPages.nodeKey, request.query.nodeKey));
90539
90688
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
90540
- if (inventoryEligible != null) filters.push(eq85(siteCrawlPages.inventoryEligible, inventoryEligible));
90541
- if (request.query.fetchState) filters.push(eq85(siteCrawlPages.fetchState, request.query.fetchState));
90542
- if (request.query.indexabilityState) filters.push(eq85(siteCrawlPages.indexabilityState, request.query.indexabilityState));
90543
- if (request.query.auditState) filters.push(eq85(siteCrawlPages.auditState, request.query.auditState));
90689
+ if (inventoryEligible != null) filters.push(eq86(siteCrawlPages.inventoryEligible, inventoryEligible));
90690
+ if (request.query.fetchState) filters.push(eq86(siteCrawlPages.fetchState, request.query.fetchState));
90691
+ if (request.query.indexabilityState) filters.push(eq86(siteCrawlPages.indexabilityState, request.query.indexabilityState));
90692
+ if (request.query.auditState) filters.push(eq86(siteCrawlPages.auditState, request.query.auditState));
90544
90693
  const healthState = parseSiteHealthState(request.query.healthState);
90545
90694
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
90546
90695
  const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
@@ -90558,17 +90707,17 @@ async function technicalAeoRoutes(app, opts) {
90558
90707
  sort
90559
90708
  })).digest("base64url");
90560
90709
  const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
90561
- const orderBy = sort === "score-desc" ? [desc39(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc16(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)] : sort === "path" ? [asc16(siteCrawlPages.path), asc16(siteCrawlPages.nodeKey)] : [asc16(siteCrawlPages.url), asc16(siteCrawlPages.nodeKey)];
90710
+ const orderBy = sort === "score-desc" ? [desc40(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc16(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)] : sort === "path" ? [asc16(siteCrawlPages.path), asc16(siteCrawlPages.nodeKey)] : [asc16(siteCrawlPages.url), asc16(siteCrawlPages.nodeKey)];
90562
90711
  let healthStateFilter = null;
90563
90712
  if (healthState) {
90564
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and71(
90565
- eq85(siteCrawlPages.projectId, project.id),
90566
- eq85(siteCrawlPages.runId, snapshot.runId),
90567
- eq85(siteCrawlPages.attemptId, snapshot.attemptId),
90713
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and72(
90714
+ eq86(siteCrawlPages.projectId, project.id),
90715
+ eq86(siteCrawlPages.runId, snapshot.runId),
90716
+ eq86(siteCrawlPages.attemptId, snapshot.attemptId),
90568
90717
  isNull16(siteCrawlPages.healthState)
90569
90718
  )).limit(1).get();
90570
90719
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
90571
- if (healthStateFilter === "applied") filters.push(eq85(siteCrawlPages.healthState, healthState));
90720
+ if (healthStateFilter === "applied") filters.push(eq86(siteCrawlPages.healthState, healthState));
90572
90721
  }
90573
90722
  if (healthStateFilter === "unavailable-legacy-scan") {
90574
90723
  return {
@@ -90585,7 +90734,7 @@ async function technicalAeoRoutes(app, opts) {
90585
90734
  pages: []
90586
90735
  };
90587
90736
  }
90588
- const where = and71(...filters);
90737
+ const where = and72(...filters);
90589
90738
  const total = app.db.select({ value: count4() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
90590
90739
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
90591
90740
  const nextOffset = offset + rows.length;
@@ -90620,10 +90769,10 @@ async function technicalAeoRoutes(app, opts) {
90620
90769
  url: siteCrawlPages.url,
90621
90770
  inventoryEligible: siteCrawlPages.inventoryEligible,
90622
90771
  fetchState: siteCrawlPages.fetchState
90623
- }).from(siteCrawlPages).where(and71(
90624
- eq85(siteCrawlPages.projectId, project.id),
90625
- eq85(siteCrawlPages.runId, snapshot.runId),
90626
- eq85(siteCrawlPages.attemptId, snapshot.attemptId)
90772
+ }).from(siteCrawlPages).where(and72(
90773
+ eq86(siteCrawlPages.projectId, project.id),
90774
+ eq86(siteCrawlPages.runId, snapshot.runId),
90775
+ eq86(siteCrawlPages.attemptId, snapshot.attemptId)
90627
90776
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
90628
90777
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
90629
90778
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -90705,17 +90854,17 @@ async function technicalAeoRoutes(app, opts) {
90705
90854
  };
90706
90855
  }
90707
90856
  const filters = [
90708
- eq85(siteCrawlEdges.projectId, project.id),
90709
- eq85(siteCrawlEdges.runId, snapshot.runId),
90710
- eq85(siteCrawlEdges.attemptId, snapshot.attemptId),
90711
- eq85(siteCrawlEdges.internal, true),
90857
+ eq86(siteCrawlEdges.projectId, project.id),
90858
+ eq86(siteCrawlEdges.runId, snapshot.runId),
90859
+ eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
90860
+ eq86(siteCrawlEdges.internal, true),
90712
90861
  linkKindFilter(linkKind)
90713
90862
  ];
90714
- if (request.query.sourceUrl) filters.push(eq85(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
90715
- if (request.query.targetUrl) filters.push(eq85(siteCrawlEdges.targetUrl, request.query.targetUrl));
90863
+ if (request.query.sourceUrl) filters.push(eq86(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
90864
+ if (request.query.targetUrl) filters.push(eq86(siteCrawlEdges.targetUrl, request.query.targetUrl));
90716
90865
  const followable = parseBoolean2(request.query.followable);
90717
- if (followable != null) filters.push(eq85(siteCrawlEdges.followable, followable));
90718
- const where = and71(...filters);
90866
+ if (followable != null) filters.push(eq86(siteCrawlEdges.followable, followable));
90867
+ const where = and72(...filters);
90719
90868
  const total = app.db.select({ value: count4() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
90720
90869
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
90721
90870
  const offset = decodeCursor(request.query.cursor);
@@ -90776,17 +90925,17 @@ async function technicalAeoRoutes(app, opts) {
90776
90925
  };
90777
90926
  }
90778
90927
  const scope = [
90779
- eq85(siteCrawlEdges.projectId, project.id),
90780
- eq85(siteCrawlEdges.runId, snapshot.runId),
90781
- eq85(siteCrawlEdges.attemptId, snapshot.attemptId),
90782
- eq85(siteCrawlEdges.internal, true),
90928
+ eq86(siteCrawlEdges.projectId, project.id),
90929
+ eq86(siteCrawlEdges.runId, snapshot.runId),
90930
+ eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
90931
+ eq86(siteCrawlEdges.internal, true),
90783
90932
  linkKindFilter(linkKind)
90784
90933
  ];
90785
- const inboundMatch = request.query.nodeKey && request.query.url ? or20(eq85(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq85(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq85(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq85(siteCrawlEdges.targetUrl, request.query.url);
90786
- const outboundMatch = request.query.nodeKey && request.query.url ? or20(eq85(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq85(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq85(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq85(siteCrawlEdges.sourceUrl, request.query.url);
90934
+ const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq86(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq86(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq86(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq86(siteCrawlEdges.targetUrl, request.query.url);
90935
+ const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq86(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq86(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq86(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq86(siteCrawlEdges.sourceUrl, request.query.url);
90787
90936
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
90788
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and71(...scope, inboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90789
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and71(...scope, outboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90937
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, inboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90938
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, outboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90790
90939
  return {
90791
90940
  project: project.name,
90792
90941
  hasCrawlData: true,
@@ -90820,11 +90969,11 @@ async function technicalAeoRoutes(app, opts) {
90820
90969
  if (!snapshot.attemptId) {
90821
90970
  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: [], runSelection: target.runSelection };
90822
90971
  }
90823
- const where = and71(
90824
- eq85(siteCrawlFindings.projectId, project.id),
90825
- eq85(siteCrawlFindings.runId, snapshot.runId),
90826
- eq85(siteCrawlFindings.attemptId, snapshot.attemptId),
90827
- eq85(siteCrawlFindings.findingType, "dead-link"),
90972
+ const where = and72(
90973
+ eq86(siteCrawlFindings.projectId, project.id),
90974
+ eq86(siteCrawlFindings.runId, snapshot.runId),
90975
+ eq86(siteCrawlFindings.attemptId, snapshot.attemptId),
90976
+ eq86(siteCrawlFindings.findingType, "dead-link"),
90828
90977
  // Belt and braces on the client-facing surface: a row without a status
90829
90978
  // code is not evidence of a broken link, and this response is where such
90830
90979
  // a row would be read as one. Migration 140 removed every stored row that
@@ -90834,7 +90983,7 @@ async function technicalAeoRoutes(app, opts) {
90834
90983
  // a fabricated broken link shown to a client is not worth the saved
90835
90984
  // predicate. Applied to `total` as well as the rows, or the count would
90836
90985
  // promise a page the list cannot fill.
90837
- isNotNull8(sql30`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
90986
+ isNotNull9(sql30`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
90838
90987
  );
90839
90988
  const total = app.db.select({ value: count4() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
90840
90989
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
@@ -90874,19 +91023,19 @@ async function technicalAeoRoutes(app, opts) {
90874
91023
  createdAt: runs.createdAt,
90875
91024
  startedAt: runs.startedAt,
90876
91025
  finishedAt: runs.finishedAt
90877
- }).from(runs).where(and71(
90878
- eq85(runs.projectId, project.id),
90879
- eq85(runs.kind, RunKinds["site-audit"]),
91026
+ }).from(runs).where(and72(
91027
+ eq86(runs.projectId, project.id),
91028
+ eq86(runs.kind, RunKinds["site-audit"]),
90880
91029
  notProbeRun()
90881
- )).orderBy(desc39(runs.createdAt), desc39(runs.id)).limit(limit).all();
91030
+ )).orderBy(desc40(runs.createdAt), desc40(runs.id)).limit(limit).all();
90882
91031
  const runIds = rows.map((row) => row.runId);
90883
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(and71(
90884
- eq85(siteCrawlSnapshots.projectId, project.id),
90885
- eq85(runs.projectId, project.id),
90886
- eq85(runs.kind, RunKinds["site-audit"]),
90887
- inArray34(runs.status, SURFACEABLE_STATUSES),
91032
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(
91033
+ eq86(siteCrawlSnapshots.projectId, project.id),
91034
+ eq86(runs.projectId, project.id),
91035
+ eq86(runs.kind, RunKinds["site-audit"]),
91036
+ inArray35(runs.status, SURFACEABLE_STATUSES),
90888
91037
  notProbeRun(),
90889
- inArray34(siteCrawlSnapshots.runId, runIds)
91038
+ inArray35(siteCrawlSnapshots.runId, runIds)
90890
91039
  )).all().map((row) => row.runId));
90891
91040
  return {
90892
91041
  project: project.name,
@@ -90900,21 +91049,21 @@ async function technicalAeoRoutes(app, opts) {
90900
91049
  });
90901
91050
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
90902
91051
  const project = resolveProject(app.db, request.params.name);
90903
- const run = app.db.select().from(runs).where(and71(
90904
- eq85(runs.id, request.params.runId),
90905
- eq85(runs.projectId, project.id),
90906
- eq85(runs.kind, RunKinds["site-audit"]),
91052
+ const run = app.db.select().from(runs).where(and72(
91053
+ eq86(runs.id, request.params.runId),
91054
+ eq86(runs.projectId, project.id),
91055
+ eq86(runs.kind, RunKinds["site-audit"]),
90907
91056
  notProbeRun()
90908
91057
  )).get();
90909
91058
  if (!run) throw notFound("Site audit run", request.params.runId);
90910
- const attempt = app.db.select().from(siteCrawlAttempts).where(and71(
90911
- eq85(siteCrawlAttempts.projectId, project.id),
90912
- eq85(siteCrawlAttempts.runId, run.id)
90913
- )).orderBy(desc39(siteCrawlAttempts.attemptNumber), desc39(siteCrawlAttempts.updatedAt)).limit(1).get();
90914
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and71(
90915
- eq85(siteCrawlGraphLayouts.projectId, project.id),
90916
- eq85(siteCrawlGraphLayouts.runId, run.id),
90917
- eq85(siteCrawlGraphLayouts.attemptId, attempt.id)
91059
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and72(
91060
+ eq86(siteCrawlAttempts.projectId, project.id),
91061
+ eq86(siteCrawlAttempts.runId, run.id)
91062
+ )).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
91063
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and72(
91064
+ eq86(siteCrawlGraphLayouts.projectId, project.id),
91065
+ eq86(siteCrawlGraphLayouts.runId, run.id),
91066
+ eq86(siteCrawlGraphLayouts.attemptId, attempt.id)
90918
91067
  )).get() : void 0;
90919
91068
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
90920
91069
  return {
@@ -90945,19 +91094,19 @@ async function technicalAeoRoutes(app, opts) {
90945
91094
  };
90946
91095
  });
90947
91096
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
90948
- const project = tx.select().from(projects).where(eq85(projects.name, request.params.name)).get();
91097
+ const project = tx.select().from(projects).where(eq86(projects.name, request.params.name)).get();
90949
91098
  if (!project) throw notFound("Project", request.params.name);
90950
- const run = tx.select().from(runs).where(and71(
90951
- eq85(runs.id, request.params.runId),
90952
- eq85(runs.projectId, project.id),
90953
- eq85(runs.kind, RunKinds["site-audit"]),
91099
+ const run = tx.select().from(runs).where(and72(
91100
+ eq86(runs.id, request.params.runId),
91101
+ eq86(runs.projectId, project.id),
91102
+ eq86(runs.kind, RunKinds["site-audit"]),
90954
91103
  notProbeRun()
90955
91104
  )).get();
90956
91105
  if (!run) throw notFound("Site audit run", request.params.runId);
90957
- const attempt = tx.select().from(siteCrawlAttempts).where(and71(
90958
- eq85(siteCrawlAttempts.projectId, project.id),
90959
- eq85(siteCrawlAttempts.runId, run.id)
90960
- )).orderBy(desc39(siteCrawlAttempts.attemptNumber), desc39(siteCrawlAttempts.updatedAt)).limit(1).get();
91106
+ const attempt = tx.select().from(siteCrawlAttempts).where(and72(
91107
+ eq86(siteCrawlAttempts.projectId, project.id),
91108
+ eq86(siteCrawlAttempts.runId, run.id)
91109
+ )).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
90961
91110
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
90962
91111
  const base = {
90963
91112
  project: project.name,
@@ -90970,11 +91119,11 @@ async function technicalAeoRoutes(app, opts) {
90970
91119
  if (!attempt) {
90971
91120
  return { ...base, pagesAudited: 0, examples: [] };
90972
91121
  }
90973
- const auditedWhere = and71(
90974
- eq85(siteCrawlPages.projectId, project.id),
90975
- eq85(siteCrawlPages.runId, run.id),
90976
- eq85(siteCrawlPages.attemptId, attempt.id),
90977
- eq85(siteCrawlPages.auditState, "success")
91122
+ const auditedWhere = and72(
91123
+ eq86(siteCrawlPages.projectId, project.id),
91124
+ eq86(siteCrawlPages.runId, run.id),
91125
+ eq86(siteCrawlPages.attemptId, attempt.id),
91126
+ eq86(siteCrawlPages.auditState, "success")
90978
91127
  );
90979
91128
  const pagesAudited = tx.select({ value: count4() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
90980
91129
  if (state !== "collecting") {
@@ -90985,9 +91134,9 @@ async function technicalAeoRoutes(app, opts) {
90985
91134
  url: siteCrawlPages.url,
90986
91135
  auditScore: siteCrawlPages.auditScore,
90987
91136
  auditFields: siteCrawlPages.auditFields
90988
- }).from(siteCrawlPages).where(and71(
91137
+ }).from(siteCrawlPages).where(and72(
90989
91138
  auditedWhere,
90990
- isNotNull8(siteCrawlPages.auditScore),
91139
+ isNotNull9(siteCrawlPages.auditScore),
90991
91140
  lt13(siteCrawlPages.auditScore, 70)
90992
91141
  )).orderBy(asc16(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
90993
91142
  const examples = candidates.flatMap((candidate) => {
@@ -91021,13 +91170,13 @@ async function technicalAeoRoutes(app, opts) {
91021
91170
  status: runs.status,
91022
91171
  identityKey: siteCrawlRunRequests.identityKey,
91023
91172
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
91024
- }).from(runs).leftJoin(siteCrawlRunRequests, and71(
91025
- eq85(siteCrawlRunRequests.projectId, runs.projectId),
91026
- eq85(siteCrawlRunRequests.runId, runs.id)
91027
- )).where(and71(
91028
- eq85(runs.projectId, project.id),
91029
- eq85(runs.kind, RunKinds["site-audit"]),
91030
- inArray34(runs.status, [RunStatuses.queued, RunStatuses.running])
91173
+ }).from(runs).leftJoin(siteCrawlRunRequests, and72(
91174
+ eq86(siteCrawlRunRequests.projectId, runs.projectId),
91175
+ eq86(siteCrawlRunRequests.runId, runs.id)
91176
+ )).where(and72(
91177
+ eq86(runs.projectId, project.id),
91178
+ eq86(runs.kind, RunKinds["site-audit"]),
91179
+ inArray35(runs.status, [RunStatuses.queued, RunStatuses.running])
91031
91180
  )).get();
91032
91181
  if (existing) {
91033
91182
  if (existing.identityKey === identityKey) {
@@ -91081,7 +91230,7 @@ async function technicalAeoRoutes(app, opts) {
91081
91230
  // ../api-routes/src/research.ts
91082
91231
  import crypto53 from "crypto";
91083
91232
  import { z as z5 } from "zod";
91084
- import { and as and72, count as count5, desc as desc40, eq as eq86, gte as gte23, lt as lt14, or as or21, sql as sql31 } from "drizzle-orm";
91233
+ import { and as and73, count as count5, desc as desc41, eq as eq87, gte as gte23, lt as lt14, or as or22, sql as sql31 } from "drizzle-orm";
91085
91234
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
91086
91235
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
91087
91236
  async function researchRoutes(app, opts) {
@@ -91099,7 +91248,7 @@ async function researchRoutes(app, opts) {
91099
91248
  const input = parsed.data;
91100
91249
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
91101
91250
  const requestHash2 = directResearchRequestHash(input);
91102
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and72(eq86(researchRuns.projectId, project.id), eq86(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
91251
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
91103
91252
  if (existingReceipt) {
91104
91253
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
91105
91254
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -91131,7 +91280,7 @@ async function researchRoutes(app, opts) {
91131
91280
  const initiatedBy = researchPrincipal(request);
91132
91281
  const decision = app.db.transaction((tx) => {
91133
91282
  if (input.idempotencyKey) {
91134
- const existing = tx.select().from(researchRuns).where(and72(eq86(researchRuns.projectId, project.id), eq86(researchRuns.idempotencyKey, input.idempotencyKey))).get();
91283
+ const existing = tx.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get();
91135
91284
  if (existing) {
91136
91285
  assertSameDirectResearchRequest(existing, input, requestHash2);
91137
91286
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -91139,8 +91288,8 @@ async function researchRoutes(app, opts) {
91139
91288
  }
91140
91289
  if (initiatedBy?.limited) {
91141
91290
  const { start, end, date } = utcDayBounds(now);
91142
- const used = tx.select({ value: count5() }).from(researchRuns).where(and72(
91143
- eq86(researchRuns.projectId, project.id),
91291
+ const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
91292
+ eq87(researchRuns.projectId, project.id),
91144
91293
  gte23(researchRuns.createdAt, start),
91145
91294
  lt14(researchRuns.createdAt, end),
91146
91295
  sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -91171,15 +91320,15 @@ async function researchRoutes(app, opts) {
91171
91320
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
91172
91321
  const requestHash2 = researchBatchRequestHash(input);
91173
91322
  const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
91174
- const firstReceipt = app.db.select().from(researchRuns).where(and72(
91175
- eq86(researchRuns.projectId, project.id),
91176
- eq86(researchRuns.idempotencyKey, receiptKeys[0])
91323
+ const firstReceipt = app.db.select().from(researchRuns).where(and73(
91324
+ eq87(researchRuns.projectId, project.id),
91325
+ eq87(researchRuns.idempotencyKey, receiptKeys[0])
91177
91326
  )).get();
91178
91327
  if (firstReceipt) {
91179
91328
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
91180
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and72(
91181
- eq86(researchRuns.projectId, project.id),
91182
- eq86(researchRuns.idempotencyKey, key)
91329
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and73(
91330
+ eq87(researchRuns.projectId, project.id),
91331
+ eq87(researchRuns.idempotencyKey, key)
91183
91332
  )).get());
91184
91333
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
91185
91334
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -91194,8 +91343,8 @@ async function researchRoutes(app, opts) {
91194
91343
  const decision = app.db.transaction((tx) => {
91195
91344
  if (initiatedBy?.limited) {
91196
91345
  const { start, end, date } = utcDayBounds(now);
91197
- const used = tx.select({ value: count5() }).from(researchRuns).where(and72(
91198
- eq86(researchRuns.projectId, project.id),
91346
+ const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
91347
+ eq87(researchRuns.projectId, project.id),
91199
91348
  gte23(researchRuns.createdAt, start),
91200
91349
  lt14(researchRuns.createdAt, end),
91201
91350
  sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -91249,10 +91398,10 @@ async function researchRoutes(app, opts) {
91249
91398
  const requested = Number.parseInt(request.query.limit ?? "", 10);
91250
91399
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
91251
91400
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
91252
- const rows = app.db.select().from(researchRuns).where(and72(
91253
- eq86(researchRuns.projectId, project.id),
91254
- cursor ? or21(lt14(researchRuns.createdAt, cursor.createdAt), and72(eq86(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
91255
- )).orderBy(desc40(researchRuns.createdAt), desc40(researchRuns.id)).limit(limit + 1).all();
91401
+ const rows = app.db.select().from(researchRuns).where(and73(
91402
+ eq87(researchRuns.projectId, project.id),
91403
+ cursor ? or22(lt14(researchRuns.createdAt, cursor.createdAt), and73(eq87(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
91404
+ )).orderBy(desc41(researchRuns.createdAt), desc41(researchRuns.id)).limit(limit + 1).all();
91256
91405
  const runs2 = rows.slice(0, limit).map(serializeRun);
91257
91406
  const last = runs2.at(-1);
91258
91407
  const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
@@ -91378,9 +91527,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
91378
91527
  return stored;
91379
91528
  }
91380
91529
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
91381
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and72(
91382
- eq86(measurementQueryTemplates.projectId, projectId),
91383
- eq86(measurementQueryTemplates.id, selection.templateId)
91530
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and73(
91531
+ eq87(measurementQueryTemplates.projectId, projectId),
91532
+ eq87(measurementQueryTemplates.id, selection.templateId)
91384
91533
  )).get();
91385
91534
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
91386
91535
  const templateVersion = savedTemplate.updatedAt;
@@ -91389,7 +91538,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
91389
91538
  }
91390
91539
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
91391
91540
  if (bindingLocation) {
91392
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq86(projects.id, projectId)).get();
91541
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq87(projects.id, projectId)).get();
91393
91542
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
91394
91543
  }
91395
91544
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -91402,9 +91551,9 @@ function classifyResearchQueries(project, plan, queries2) {
91402
91551
  return queries2.map((query) => classifier?.classify(query) ?? null);
91403
91552
  }
91404
91553
  function getDetail(app, projectId, id) {
91405
- const row = app.db.select().from(researchRuns).where(and72(eq86(researchRuns.id, id), eq86(researchRuns.projectId, projectId))).get();
91554
+ const row = app.db.select().from(researchRuns).where(and73(eq87(researchRuns.id, id), eq87(researchRuns.projectId, projectId))).get();
91406
91555
  if (!row) throw notFound("Research run", id);
91407
- const queries2 = app.db.select().from(researchRunQueries).where(eq86(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
91556
+ const queries2 = app.db.select().from(researchRunQueries).where(eq87(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
91408
91557
  return { ...serializeRun(row), queries: queries2 };
91409
91558
  }
91410
91559
  function serializeRun(row) {
@@ -91434,7 +91583,7 @@ function utcDayBounds(now) {
91434
91583
 
91435
91584
  // ../api-routes/src/snapshot-competitor-refresh.ts
91436
91585
  import { setImmediate as yieldToEventLoop2 } from "timers/promises";
91437
- import { and as and73, asc as asc17, eq as eq87, inArray as inArray35 } from "drizzle-orm";
91586
+ import { and as and74, asc as asc17, eq as eq88, inArray as inArray36 } from "drizzle-orm";
91438
91587
  var ANSWER_MENTIONS_CHUNK_SIZE = 50;
91439
91588
  var ANSWER_MENTIONS_MAX_RESTARTS = 3;
91440
91589
  function backfillProjectAnswerMentions(db, projectId, opts) {
@@ -91484,14 +91633,14 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
91484
91633
  examined = 0;
91485
91634
  wouldUpdate = 0;
91486
91635
  mentioned = 0;
91487
- const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and73(
91488
- eq87(runs.kind, RunKinds["answer-visibility"]),
91489
- eq87(runs.projectId, projectId),
91490
- opts?.runId === void 0 ? void 0 : eq87(runs.id, opts.runId)
91636
+ const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and74(
91637
+ eq88(runs.kind, RunKinds["answer-visibility"]),
91638
+ eq88(runs.projectId, projectId),
91639
+ opts?.runId === void 0 ? void 0 : eq88(runs.id, opts.runId)
91491
91640
  )).all();
91492
91641
  const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
91493
91642
  for (const run of runRows) {
91494
- const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq87(querySnapshots.runId, run.id)).orderBy(asc17(querySnapshots.id)).all().map((row) => row.id);
91643
+ const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq88(querySnapshots.runId, run.id)).orderBy(asc17(querySnapshots.id)).all().map((row) => row.id);
91495
91644
  for (let offset = 0; offset < snapshotIds.length; offset += ANSWER_MENTIONS_CHUNK_SIZE) {
91496
91645
  if (chunks++ > 0 && driver.recheckIdentity) {
91497
91646
  yield;
@@ -91517,7 +91666,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
91517
91666
  competitorOverlap: querySnapshots.competitorOverlap,
91518
91667
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
91519
91668
  rawResponse: querySnapshots.rawResponse
91520
- }).from(querySnapshots).where(inArray35(querySnapshots.id, snapshotIds.slice(offset, offset + ANSWER_MENTIONS_CHUNK_SIZE))).all();
91669
+ }).from(querySnapshots).where(inArray36(querySnapshots.id, snapshotIds.slice(offset, offset + ANSWER_MENTIONS_CHUNK_SIZE))).all();
91521
91670
  const pendingUpdates = [];
91522
91671
  for (const snapshot of snapshotRows) {
91523
91672
  if (competitorFieldsOnly && snapshot.answerText == null) continue;
@@ -91567,7 +91716,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
91567
91716
  } else {
91568
91717
  db.transaction((tx) => {
91569
91718
  for (const update of pendingUpdates) {
91570
- tx.update(querySnapshots).set(update.patch).where(eq87(querySnapshots.id, update.id)).run();
91719
+ tx.update(querySnapshots).set(update.patch).where(eq88(querySnapshots.id, update.id)).run();
91571
91720
  }
91572
91721
  });
91573
91722
  updated += pendingUpdates.length;
@@ -91597,7 +91746,7 @@ function readStoredGroundingSources(rawResponse) {
91597
91746
 
91598
91747
  // ../api-routes/src/simple-measurement-definitions.ts
91599
91748
  import crypto54 from "crypto";
91600
- import { and as and74, eq as eq88 } from "drizzle-orm";
91749
+ import { and as and75, eq as eq89 } from "drizzle-orm";
91601
91750
  function captureSimpleMeasurementDefinition(db, input) {
91602
91751
  return db.transaction((tx) => {
91603
91752
  const run = tx.select({
@@ -91605,7 +91754,7 @@ function captureSimpleMeasurementDefinition(db, input) {
91605
91754
  trigger: runs.trigger,
91606
91755
  status: runs.status,
91607
91756
  measurementPlanVersionId: runs.measurementPlanVersionId
91608
- }).from(runs).where(and74(eq88(runs.projectId, input.projectId), eq88(runs.id, input.runId))).get();
91757
+ }).from(runs).where(and75(eq89(runs.projectId, input.projectId), eq89(runs.id, input.runId))).get();
91609
91758
  if (!run) throw notFound("Run", input.runId);
91610
91759
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
91611
91760
  return null;
@@ -91626,7 +91775,7 @@ function captureSimpleMeasurementDefinition(db, input) {
91626
91775
  throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
91627
91776
  }
91628
91777
  }
91629
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and74(eq88(simpleMeasurementDefinitions.projectId, input.projectId), eq88(simpleMeasurementDefinitions.runId, input.runId))).get();
91778
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and75(eq89(simpleMeasurementDefinitions.projectId, input.projectId), eq89(simpleMeasurementDefinitions.runId, input.runId))).get();
91630
91779
  if (existing) {
91631
91780
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
91632
91781
  const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition.identity;
@@ -91652,15 +91801,15 @@ function captureSimpleMeasurementDefinition(db, input) {
91652
91801
  if (run.status !== RunStatuses.running) {
91653
91802
  throw validationError("Only a running run can capture a new measurement definition.");
91654
91803
  }
91655
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq88(querySnapshots.runId, input.runId)).limit(1).get()) {
91804
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq89(querySnapshots.runId, input.runId)).limit(1).get()) {
91656
91805
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
91657
91806
  }
91658
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq88(queries.projectId, input.projectId)).all().map((query) => query.id));
91807
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq89(queries.projectId, input.projectId)).all().map((query) => query.id));
91659
91808
  if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
91660
91809
  throw validationError("Every captured query must belong to the run project.");
91661
91810
  }
91662
91811
  if (definition.competitors !== void 0) {
91663
- const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq88(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
91812
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq89(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
91664
91813
  const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
91665
91814
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
91666
91815
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
@@ -92528,10 +92677,10 @@ var log = createLogger("IntelligenceService");
92528
92677
  var runChronologyKey = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
92529
92678
  function analyzableRunPredicates(projectId) {
92530
92679
  return [
92531
- eq89(runs.projectId, projectId),
92680
+ eq90(runs.projectId, projectId),
92532
92681
  // Only this kind writes query_snapshots.
92533
- eq89(runs.kind, RunKinds["answer-visibility"]),
92534
- or22(eq89(runs.status, RunStatuses.completed), eq89(runs.status, RunStatuses.partial)),
92682
+ eq90(runs.kind, RunKinds["answer-visibility"]),
92683
+ or23(eq90(runs.status, RunStatuses.completed), eq90(runs.status, RunStatuses.partial)),
92535
92684
  // Defensive: RunCoordinator already skips probes before analyzeAndPersist
92536
92685
  // is called, but a future call site invoking it directly for a probe must
92537
92686
  // still not pollute the intelligence window.
@@ -92576,16 +92725,16 @@ var IntelligenceService = class {
92576
92725
  * Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
92577
92726
  */
92578
92727
  analyzeAndPersist(runId, projectId) {
92579
- const currentRunRecord = this.db.select().from(runs).where(and75(eq89(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
92728
+ const currentRunRecord = this.db.select().from(runs).where(and76(eq90(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
92580
92729
  if (!currentRunRecord) {
92581
92730
  log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
92582
92731
  return null;
92583
92732
  }
92584
92733
  const currentLocation = currentRunRecord.location ?? null;
92585
92734
  const recentRuns = this.db.select().from(runs).where(
92586
- and75(
92735
+ and76(
92587
92736
  ...analyzableRunPredicates(projectId),
92588
- currentLocation === null ? isNull17(runs.location) : eq89(runs.location, currentLocation),
92737
+ currentLocation === null ? isNull17(runs.location) : eq90(runs.location, currentLocation),
92589
92738
  // Never look forward. The window is this run's own past, so
92590
92739
  // re-analyzing a historical run compares it against its true
92591
92740
  // predecessor rather than against sweeps that came after it.
@@ -92593,10 +92742,10 @@ var IntelligenceService = class {
92593
92742
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
92594
92743
  // Index seek on idx_snapshots_run, short-circuited on the first row.
92595
92744
  exists2(
92596
- this.db.select({ one: sql32`1` }).from(querySnapshots).where(eq89(querySnapshots.runId, runs.id))
92745
+ this.db.select({ one: sql32`1` }).from(querySnapshots).where(eq90(querySnapshots.runId, runs.id))
92597
92746
  )
92598
92747
  )
92599
- ).orderBy(desc41(runs.finishedAt), desc41(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
92748
+ ).orderBy(desc42(runs.finishedAt), desc42(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
92600
92749
  const trackedCompetitors = this.loadTrackedCompetitors(projectId);
92601
92750
  const trackedCompetitorDomains = trackedCompetitors.map((competitor) => competitor.domain);
92602
92751
  const currentRun = this.buildRunData(
@@ -92669,7 +92818,7 @@ var IntelligenceService = class {
92669
92818
  * Returns the persisted insights so the coordinator can count critical/high.
92670
92819
  */
92671
92820
  analyzeAndPersistGbp(runId, projectId) {
92672
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq89(runs.id, runId)).get();
92821
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq90(runs.id, runId)).get();
92673
92822
  if (!runRow) {
92674
92823
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
92675
92824
  this.persistGbpInsights(runId, projectId, [], []);
@@ -92677,9 +92826,9 @@ var IntelligenceService = class {
92677
92826
  }
92678
92827
  const windowStart = runRow.startedAt ?? runRow.createdAt;
92679
92828
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
92680
- const selected = this.db.select().from(gbpLocations).where(and75(
92681
- eq89(gbpLocations.projectId, projectId),
92682
- eq89(gbpLocations.selected, true),
92829
+ const selected = this.db.select().from(gbpLocations).where(and76(
92830
+ eq90(gbpLocations.projectId, projectId),
92831
+ eq90(gbpLocations.selected, true),
92683
92832
  gte24(gbpLocations.syncedAt, windowStart),
92684
92833
  lte20(gbpLocations.syncedAt, windowEnd)
92685
92834
  )).all();
@@ -92714,12 +92863,12 @@ var IntelligenceService = class {
92714
92863
  }
92715
92864
  /** Build the per-location signal bundle the GBP analyzer consumes. */
92716
92865
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
92717
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and75(eq89(gbpDailyMetrics.projectId, projectId), eq89(gbpDailyMetrics.locationName, locationName))).all();
92718
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and75(eq89(gbpPlaceActions.projectId, projectId), eq89(gbpPlaceActions.locationName, locationName))).all();
92719
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and75(eq89(gbpLodgingSnapshots.projectId, projectId), eq89(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc41(gbpLodgingSnapshots.syncedAt)).limit(1).get();
92720
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and75(eq89(gbpLocations.projectId, projectId), eq89(gbpLocations.locationName, locationName))).get();
92866
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and76(eq90(gbpDailyMetrics.projectId, projectId), eq90(gbpDailyMetrics.locationName, locationName))).all();
92867
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and76(eq90(gbpPlaceActions.projectId, projectId), eq90(gbpPlaceActions.locationName, locationName))).all();
92868
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and76(eq90(gbpLodgingSnapshots.projectId, projectId), eq90(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc42(gbpLodgingSnapshots.syncedAt)).limit(1).get();
92869
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and76(eq90(gbpLocations.projectId, projectId), eq90(gbpLocations.locationName, locationName))).get();
92721
92870
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
92722
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and75(eq89(gbpPlaceDetails.projectId, projectId), eq89(gbpPlaceDetails.locationName, locationName))).orderBy(desc41(gbpPlaceDetails.syncedAt)).limit(1).get();
92871
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and76(eq90(gbpPlaceDetails.projectId, projectId), eq90(gbpPlaceDetails.locationName, locationName))).orderBy(desc42(gbpPlaceDetails.syncedAt)).limit(1).get();
92723
92872
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
92724
92873
  const summary3 = buildGbpSummary({
92725
92874
  locationName,
@@ -92755,7 +92904,7 @@ var IntelligenceService = class {
92755
92904
  /** Build the month-over-month keyword series for a location from the
92756
92905
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
92757
92906
  buildGbpKeywordTrend(projectId, locationName) {
92758
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and75(eq89(gbpKeywordMonthly.projectId, projectId), eq89(gbpKeywordMonthly.locationName, locationName))).all();
92907
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and76(eq90(gbpKeywordMonthly.projectId, projectId), eq90(gbpKeywordMonthly.locationName, locationName))).all();
92759
92908
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
92760
92909
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
92761
92910
  const recentMonth = months[0] ?? null;
@@ -92786,7 +92935,7 @@ var IntelligenceService = class {
92786
92935
  */
92787
92936
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
92788
92937
  const covered = new Set(coveredLocationNames);
92789
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and75(eq89(insights.projectId, projectId), eq89(insights.provider, GBP_INSIGHT_PROVIDER))).all();
92938
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and76(eq90(insights.projectId, projectId), eq90(insights.provider, GBP_INSIGHT_PROVIDER))).all();
92790
92939
  const staleIds = [];
92791
92940
  const dismissedSlots = /* @__PURE__ */ new Set();
92792
92941
  for (const row of existing) {
@@ -92797,7 +92946,7 @@ var IntelligenceService = class {
92797
92946
  }
92798
92947
  this.db.transaction((tx) => {
92799
92948
  for (const id of staleIds) {
92800
- tx.delete(insights).where(eq89(insights.id, id)).run();
92949
+ tx.delete(insights).where(eq90(insights.id, id)).run();
92801
92950
  }
92802
92951
  for (const insight of gbpInsights) {
92803
92952
  const parsed = parseGbpInsightId(insight.id);
@@ -92884,7 +93033,7 @@ var IntelligenceService = class {
92884
93033
  * create per run + aggregate). DB is left untouched.
92885
93034
  */
92886
93035
  backfill(projectName, opts, onProgress) {
92887
- const project = this.db.select().from(projects).where(eq89(projects.name, projectName)).get();
93036
+ const project = this.db.select().from(projects).where(eq90(projects.name, projectName)).get();
92888
93037
  if (!project) {
92889
93038
  throw new Error(`Project "${projectName}" not found`);
92890
93039
  }
@@ -92896,7 +93045,7 @@ var IntelligenceService = class {
92896
93045
  }
92897
93046
  sinceTimestamp = parsed;
92898
93047
  }
92899
- const allRuns = this.db.select().from(runs).where(and75(...analyzableRunPredicates(project.id))).orderBy(asc18(runs.finishedAt)).all();
93048
+ const allRuns = this.db.select().from(runs).where(and76(...analyzableRunPredicates(project.id))).orderBy(asc18(runs.finishedAt)).all();
92900
93049
  const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
92901
93050
  const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
92902
93051
  let startIdx = 0;
@@ -92927,7 +93076,7 @@ var IntelligenceService = class {
92927
93076
  let wouldDeleteTotal = 0;
92928
93077
  const existingByRunId = /* @__PURE__ */ new Map();
92929
93078
  if (isDryRun && targetRuns.length > 0) {
92930
- const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray36(insights.runId, targetRuns.map((r) => r.id))).all();
93079
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray37(insights.runId, targetRuns.map((r) => r.id))).all();
92931
93080
  for (const r of rows) {
92932
93081
  if (r.runId == null) continue;
92933
93082
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -92978,7 +93127,7 @@ var IntelligenceService = class {
92978
93127
  * signals match the same identities the dashboard and reports do.
92979
93128
  */
92980
93129
  loadTrackedCompetitors(projectId) {
92981
- return this.db.select(competitorIdentityColumns).from(competitors).where(eq89(competitors.projectId, projectId)).orderBy(competitors.domain).all();
93130
+ return this.db.select(competitorIdentityColumns).from(competitors).where(eq90(competitors.projectId, projectId)).orderBy(competitors.domain).all();
92982
93131
  }
92983
93132
  /**
92984
93133
  * Which of `runIds` actually wrote query snapshots. One grouped read, so
@@ -92987,7 +93136,7 @@ var IntelligenceService = class {
92987
93136
  */
92988
93137
  runIdsWithSnapshots(runIds) {
92989
93138
  if (runIds.length === 0) return /* @__PURE__ */ new Set();
92990
- const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray36(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
93139
+ const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray37(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
92991
93140
  return new Set(rows.map((r) => r.runId));
92992
93141
  }
92993
93142
  /**
@@ -93009,15 +93158,15 @@ var IntelligenceService = class {
93009
93158
  }
93010
93159
  persistResult(result, runId, projectId) {
93011
93160
  const previouslyDismissed = /* @__PURE__ */ new Set();
93012
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq89(insights.runId, runId)).all();
93161
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq90(insights.runId, runId)).all();
93013
93162
  for (const row of existingInsights) {
93014
93163
  if (row.dismissed) {
93015
93164
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
93016
93165
  }
93017
93166
  }
93018
93167
  this.db.transaction((tx) => {
93019
- tx.delete(insights).where(eq89(insights.runId, runId)).run();
93020
- tx.delete(healthSnapshots).where(eq89(healthSnapshots.runId, runId)).run();
93168
+ tx.delete(insights).where(eq90(insights.runId, runId)).run();
93169
+ tx.delete(healthSnapshots).where(eq90(healthSnapshots.runId, runId)).run();
93021
93170
  const now = (/* @__PURE__ */ new Date()).toISOString();
93022
93171
  for (const insight of result.insights) {
93023
93172
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -93064,8 +93213,8 @@ var IntelligenceService = class {
93064
93213
  clicks: gscSearchData.clicks,
93065
93214
  impressions: gscSearchData.impressions,
93066
93215
  position: gscSearchData.position
93067
- }).from(gscSearchData).where(and75(
93068
- eq89(gscSearchData.projectId, projectId),
93216
+ }).from(gscSearchData).where(and76(
93217
+ eq90(gscSearchData.projectId, projectId),
93069
93218
  gte24(gscSearchData.date, window.startDate),
93070
93219
  lte20(gscSearchData.date, window.endDate)
93071
93220
  )).all();
@@ -93118,21 +93267,21 @@ var IntelligenceService = class {
93118
93267
  const key = row.query.toLowerCase();
93119
93268
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
93120
93269
  }
93121
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq89(projects.id, projectId)).get();
93270
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq90(projects.id, projectId)).get();
93122
93271
  const locationCount = Math.max(
93123
93272
  1,
93124
93273
  (projectRow?.locations ?? []).length
93125
93274
  );
93126
93275
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
93127
93276
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
93128
- and75(
93129
- eq89(runs.projectId, projectId),
93130
- eq89(runs.kind, RunKinds["answer-visibility"]),
93131
- or22(eq89(runs.status, "completed"), eq89(runs.status, "partial")),
93277
+ and76(
93278
+ eq90(runs.projectId, projectId),
93279
+ eq90(runs.kind, RunKinds["answer-visibility"]),
93280
+ or23(eq90(runs.status, "completed"), eq90(runs.status, "partial")),
93132
93281
  // Defensive — see top of file.
93133
93282
  ne16(runs.trigger, RunTriggers.probe)
93134
93283
  )
93135
- ).orderBy(desc41(runs.createdAt), desc41(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
93284
+ ).orderBy(desc42(runs.createdAt), desc42(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
93136
93285
  const recentGroups = groupRunsByCreatedAt(recentRunRows);
93137
93286
  const recentRunIds = [];
93138
93287
  const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
@@ -93148,7 +93297,7 @@ var IntelligenceService = class {
93148
93297
  const haveHistory = recentRunIds.length > 0;
93149
93298
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
93150
93299
  if (haveHistory) {
93151
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and75(eq89(insights.type, "regression"), inArray36(insights.runId, recentRunIds))).all();
93300
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and76(eq90(insights.type, "regression"), inArray37(insights.runId, recentRunIds))).all();
93152
93301
  const regressionGroups = /* @__PURE__ */ new Map();
93153
93302
  for (const row of priorRows) {
93154
93303
  if (!row.runId) continue;
@@ -93177,7 +93326,7 @@ var IntelligenceService = class {
93177
93326
  });
93178
93327
  }
93179
93328
  buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
93180
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq89(projects.id, projectId)).get();
93329
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq90(projects.id, projectId)).get();
93181
93330
  const projectDomains = projectDomainRow ? effectiveDomains({
93182
93331
  canonicalDomain: projectDomainRow.canonicalDomain,
93183
93332
  ownedDomains: projectDomainRow.ownedDomains
@@ -93195,7 +93344,7 @@ var IntelligenceService = class {
93195
93344
  citedDomains: querySnapshots.citedDomains,
93196
93345
  rawResponse: querySnapshots.rawResponse,
93197
93346
  snapshotLocation: querySnapshots.location
93198
- }).from(querySnapshots).leftJoin(queries, eq89(querySnapshots.queryId, queries.id)).where(eq89(querySnapshots.runId, runId)).all();
93347
+ }).from(querySnapshots).leftJoin(queries, eq90(querySnapshots.queryId, queries.id)).where(eq90(querySnapshots.runId, runId)).all();
93199
93348
  const snapshots = [];
93200
93349
  let orphanCount = 0;
93201
93350
  for (const r of rows) {