@canonry/canonry 7.24.1 → 7.32.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 (33) hide show
  1. package/README.md +3 -2
  2. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +1 -1
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +21 -2
  4. package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +1 -0
  5. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +3 -2
  6. package/assets/assets/{AuditHistoryPanel-DK3zE9dO.js → AuditHistoryPanel-cQyVA85P.js} +1 -1
  7. package/assets/assets/{BacklinksPage-WYFDcXpX.js → BacklinksPage-UGZ_9N7F.js} +1 -1
  8. package/assets/assets/{HistoryPage-CSlndAeS.js → HistoryPage-UJVFZ5LK.js} +1 -1
  9. package/assets/assets/{MeasurementPropertyPage-CivoZOGQ.js → MeasurementPropertyPage-ChpOxQfx.js} +1 -1
  10. package/assets/assets/{ProjectPage-zidnYv65.js → ProjectPage-CvxuPSiC.js} +1 -1
  11. package/assets/assets/{RunsPage-BGMAZ_mA.js → RunsPage-C6U_YcDY.js} +1 -1
  12. package/assets/assets/{SettingsPage-ndSVVJcE.js → SettingsPage-DeKxjuTr.js} +1 -1
  13. package/assets/assets/{SiteHealthSection-C7-RrsKk.js → SiteHealthSection-COeVsgop.js} +3 -3
  14. package/assets/assets/{TrafficPage-2ANBE9Lc.js → TrafficPage-DmMmeJKK.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-CLYVB4Uq.js → TrafficSourceDetailPage-CUehseZe.js} +1 -1
  16. package/assets/assets/{extract-error-message-BzPoRYiA.js → extract-error-message-BsxdS7Po.js} +1 -1
  17. package/assets/assets/{index-CjowCLs3.js → index-ahEr6Oc-.js} +19 -19
  18. package/assets/assets/{react-sigma_core.esm.min-BmKqjMGN.js → react-sigma_core.esm.min--MbbBWzJ.js} +1 -1
  19. package/assets/assets/{v2-overview-adapter-C7eI2iBf.js → v2-overview-adapter-D-IScpdp.js} +1 -1
  20. package/assets/index.html +1 -1
  21. package/dist/{chunk-43FZZFOK.js → chunk-2C6NWFN4.js} +2 -2
  22. package/dist/{chunk-656KW4RB.js → chunk-42XUY243.js} +28 -5
  23. package/dist/{chunk-OAWFZAAJ.js → chunk-FW4GX2LO.js} +3 -3
  24. package/dist/{chunk-35IRQYDW.js → chunk-K5EGSSXC.js} +145 -25
  25. package/dist/{chunk-5ITYNLS3.js → chunk-QFXD7GR5.js} +4 -3
  26. package/dist/{chunk-H7UNFLYH.js → chunk-WRHA54BN.js} +794 -619
  27. package/dist/{chunk-332IBSJJ.js → chunk-ZIETXJYU.js} +6 -4
  28. package/dist/cli.js +40 -17
  29. package/dist/{demo-server-NLD6B4WH.js → demo-server-G5OK7SWE.js} +4 -4
  30. package/dist/index.js +6 -6
  31. package/dist/{intelligence-service-JFHM2EAY.js → intelligence-service-3ZUVTIO7.js} +2 -2
  32. package/dist/mcp.js +4 -4
  33. package/package.json +10 -10
@@ -864,10 +864,10 @@ import {
864
864
  wordpressSchemaDeployResultDtoSchema,
865
865
  wordpressSchemaStatusResultDtoSchema,
866
866
  wordpressStatusDtoSchema
867
- } from "./chunk-656KW4RB.js";
867
+ } from "./chunk-42XUY243.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})`,
@@ -19120,6 +19204,34 @@ function aliasArraysEqual(a, b) {
19120
19204
  import crypto14 from "crypto";
19121
19205
  import { and as and15, eq as eq21, inArray as inArray9, sql as sql8 } from "drizzle-orm";
19122
19206
 
19207
+ // ../api-routes/src/query-class-read.ts
19208
+ function storedQueryClasses(db, projectId) {
19209
+ const classes = /* @__PURE__ */ new Map();
19210
+ let plan;
19211
+ try {
19212
+ const version = activePlanVersionRow(db, projectId);
19213
+ if (!version) return null;
19214
+ plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
19215
+ } catch {
19216
+ return classes;
19217
+ }
19218
+ if (plan.schemaVersion !== 2) return null;
19219
+ for (const assignment of plan.assignments) {
19220
+ const seen2 = classes.get(assignment.queryId);
19221
+ classes.set(
19222
+ assignment.queryId,
19223
+ seen2 === void 0 || seen2 === assignment.queryClass ? assignment.queryClass : null
19224
+ );
19225
+ }
19226
+ return classes;
19227
+ }
19228
+ function readQueryClasses(db, project, rows) {
19229
+ const stored = storedQueryClasses(db, project.id);
19230
+ if (stored !== null) return new Map(rows.map((row) => [row.id, stored.get(row.id) ?? null]));
19231
+ const classifier = compileQueryClassifier(effectiveBrandNames(project));
19232
+ return new Map(rows.map((row) => [row.id, classifier?.classify(row.query) ?? null]));
19233
+ }
19234
+
19123
19235
  // ../api-routes/src/query-replace.ts
19124
19236
  import crypto13 from "crypto";
19125
19237
  import { and as and14, eq as eq20, inArray as inArray8, isNull as isNull5 } from "drizzle-orm";
@@ -19287,11 +19399,15 @@ function generationFailure(err, fallbackMessage) {
19287
19399
  return internalError(message);
19288
19400
  }
19289
19401
  }
19402
+ function projectQueryDtos(db, project) {
19403
+ const rows = db.select().from(queries).where(eq21(queries.projectId, project.id)).all();
19404
+ const classes = readQueryClasses(db, project, rows);
19405
+ return rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt, queryClass: classes.get(r.id) ?? null }));
19406
+ }
19290
19407
  async function queryRoutes(app, opts) {
19291
19408
  app.get("/projects/:name/queries", async (request, reply) => {
19292
19409
  const project = resolveProject(app.db, request.params.name);
19293
- const rows = app.db.select().from(queries).where(eq21(queries.projectId, project.id)).all();
19294
- return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
19410
+ return reply.send(projectQueryDtos(app.db, project));
19295
19411
  });
19296
19412
  app.put("/projects/:name/queries", async (request, reply) => {
19297
19413
  const project = resolveProject(app.db, request.params.name);
@@ -19311,8 +19427,7 @@ async function queryRoutes(app, opts) {
19311
19427
  diff: { queries: body.queries }
19312
19428
  }));
19313
19429
  }, { behavior: "immediate" });
19314
- const rows = app.db.select().from(queries).where(eq21(queries.projectId, project.id)).all();
19315
- return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
19430
+ return reply.send(projectQueryDtos(app.db, project));
19316
19431
  });
19317
19432
  app.post("/projects/:name/queries/replace-preview", async (request, reply) => {
19318
19433
  const project = resolveProject(app.db, request.params.name);
@@ -19381,8 +19496,7 @@ async function queryRoutes(app, opts) {
19381
19496
  }));
19382
19497
  }
19383
19498
  }, { behavior: "immediate" });
19384
- const rows = app.db.select().from(queries).where(eq21(queries.projectId, project.id)).all();
19385
- return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
19499
+ return reply.send(projectQueryDtos(app.db, project));
19386
19500
  });
19387
19501
  app.delete("/projects/:name/queries/:id", async (request, reply) => {
19388
19502
  const project = resolveProject(app.db, request.params.name);
@@ -19439,8 +19553,7 @@ async function queryRoutes(app, opts) {
19439
19553
  diff: { added }
19440
19554
  }));
19441
19555
  }
19442
- const rows = app.db.select().from(queries).where(eq21(queries.projectId, project.id)).all();
19443
- return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
19556
+ return reply.send(projectQueryDtos(app.db, project));
19444
19557
  });
19445
19558
  app.post("/projects/:name/queries/generate", async (request, reply) => {
19446
19559
  const project = resolveProject(app.db, request.params.name);
@@ -23727,8 +23840,7 @@ var MEASUREMENT_PLAN_V2_MAX_BYTES = 4 * 1024 * 1024;
23727
23840
  var CHECKSUM_PLACEHOLDER = "0".repeat(64);
23728
23841
  var MEASUREMENT_V2_PROVENANCE_EPOCH = "1970-01-01T00:00:00.000Z";
23729
23842
  function proposeQueryClass(queryText, brandNames) {
23730
- const matcher = compileBrandAliases(brandNames.filter((name) => brandKeyFromText(name).length > 0));
23731
- return matcherMatchesText(matcher, queryText) ? "branded" : "non-brand";
23843
+ return compileQueryClassifier(brandNames)?.classify(queryText) ?? "non-brand";
23732
23844
  }
23733
23845
  function proposeQueryClassForTarget(queryText, brandNames, target) {
23734
23846
  const names = target ? [...brandNames, target.label, ...target.aliases] : brandNames;
@@ -62964,7 +63076,7 @@ async function wordpressRoutes(app, opts) {
62964
63076
 
62965
63077
  // ../api-routes/src/backlinks.ts
62966
63078
  import crypto45 from "crypto";
62967
- import { and as and56, asc as asc14, desc as desc34, eq as eq65, sql as sql25 } from "drizzle-orm";
63079
+ import { and as and57, asc as asc14, desc as desc34, eq as eq66, sql as sql25 } from "drizzle-orm";
62968
63080
 
62969
63081
  // ../integration-commoncrawl/src/constants.ts
62970
63082
  import os2 from "os";
@@ -63395,6 +63507,22 @@ function backlinkCrawlerExclusionClause() {
63395
63507
  return combined;
63396
63508
  }
63397
63509
 
63510
+ // ../api-routes/src/backlinks-source-state.ts
63511
+ import { and as and56, eq as eq65, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
63512
+ function readCommonCrawlSourceState(db, projectId) {
63513
+ const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
63514
+ eq65(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
63515
+ isNotNull8(ccReleaseSyncs.queryFinishedAt)
63516
+ )).all();
63517
+ const hasReadyRelease = completedReleases.length > 0;
63518
+ const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
63519
+ const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and56(
63520
+ eq65(backlinkSummaries.projectId, projectId),
63521
+ eq65(backlinkSummaries.source, BacklinkSources.commoncrawl)
63522
+ )).limit(1).get() !== void 0;
63523
+ return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
63524
+ }
63525
+
63398
63526
  // ../api-routes/src/backlinks.ts
63399
63527
  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.";
63400
63528
  var NON_TERMINAL_SYNC_STATUSES = /* @__PURE__ */ new Set([
@@ -63461,11 +63589,11 @@ function mapRunRow(row) {
63461
63589
  }
63462
63590
  function latestSummaryForProject(db, projectId, source, release) {
63463
63591
  const conditions = [
63464
- eq65(backlinkSummaries.projectId, projectId),
63465
- eq65(backlinkSummaries.source, source)
63592
+ eq66(backlinkSummaries.projectId, projectId),
63593
+ eq66(backlinkSummaries.source, source)
63466
63594
  ];
63467
- if (release) conditions.push(eq65(backlinkSummaries.release, release));
63468
- return db.select().from(backlinkSummaries).where(and56(...conditions)).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63595
+ if (release) conditions.push(eq66(backlinkSummaries.release, release));
63596
+ return db.select().from(backlinkSummaries).where(and57(...conditions)).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63469
63597
  }
63470
63598
  function parseExcludeCrawlers(value) {
63471
63599
  if (!value) return false;
@@ -63473,12 +63601,12 @@ function parseExcludeCrawlers(value) {
63473
63601
  return lower === "1" || lower === "true" || lower === "yes";
63474
63602
  }
63475
63603
  function computeFilteredSummary(db, base) {
63476
- const baseDomainCondition = and56(
63477
- eq65(backlinkDomains.projectId, base.projectId),
63478
- eq65(backlinkDomains.source, base.source),
63479
- eq65(backlinkDomains.release, base.release)
63604
+ const baseDomainCondition = and57(
63605
+ eq66(backlinkDomains.projectId, base.projectId),
63606
+ eq66(backlinkDomains.source, base.source),
63607
+ eq66(backlinkDomains.release, base.release)
63480
63608
  );
63481
- const filteredCondition = and56(baseDomainCondition, backlinkCrawlerExclusionClause());
63609
+ const filteredCondition = and57(baseDomainCondition, backlinkCrawlerExclusionClause());
63482
63610
  const unfilteredAgg = db.select({
63483
63611
  count: sql25`count(*)`,
63484
63612
  total: sql25`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -63508,13 +63636,13 @@ function computeFilteredSummary(db, base) {
63508
63636
  };
63509
63637
  }
63510
63638
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
63511
- const summary3 = db.select().from(backlinkSummaries).where(and56(eq65(backlinkSummaries.projectId, projectId), eq65(backlinkSummaries.source, source))).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63639
+ const summary3 = db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, projectId), eq66(backlinkSummaries.source, source))).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
63512
63640
  let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
63513
63641
  if (summary3 && excludeCrawlers) {
63514
- const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(and56(
63515
- eq65(backlinkDomains.projectId, projectId),
63516
- eq65(backlinkDomains.source, source),
63517
- eq65(backlinkDomains.release, summary3.release),
63642
+ const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(and57(
63643
+ eq66(backlinkDomains.projectId, projectId),
63644
+ eq66(backlinkDomains.source, source),
63645
+ eq66(backlinkDomains.release, summary3.release),
63518
63646
  backlinkCrawlerExclusionClause()
63519
63647
  )).get();
63520
63648
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -63529,8 +63657,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
63529
63657
  };
63530
63658
  }
63531
63659
  function computeSourceAvailability(db, project, excludeCrawlers) {
63532
- const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
63533
- const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
63660
+ const ccConnected = readCommonCrawlSourceState(db, project.id).connected;
63534
63661
  const sources = [
63535
63662
  buildSourceAvailability(db, project.id, BacklinkSources.commoncrawl, ccConnected, excludeCrawlers),
63536
63663
  buildSourceAvailability(db, project.id, BacklinkSources["bing-webmaster"], false, excludeCrawlers)
@@ -63585,7 +63712,7 @@ async function backlinksRoutes(app, opts) {
63585
63712
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
63586
63713
  );
63587
63714
  }
63588
- const existing = app.db.select().from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.release, release)).get();
63715
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.release, release)).get();
63589
63716
  const now = (/* @__PURE__ */ new Date()).toISOString();
63590
63717
  if (existing) {
63591
63718
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -63596,9 +63723,9 @@ async function backlinksRoutes(app, opts) {
63596
63723
  phaseDetail: null,
63597
63724
  error: null,
63598
63725
  updatedAt: now
63599
- }).where(eq65(ccReleaseSyncs.id, existing.id)).run();
63726
+ }).where(eq66(ccReleaseSyncs.id, existing.id)).run();
63600
63727
  opts.onReleaseSyncRequested(existing.id, release);
63601
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.id, existing.id)).get();
63728
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, existing.id)).get();
63602
63729
  return reply.status(200).send(mapSyncRow(refreshed));
63603
63730
  }
63604
63731
  const id = crypto45.randomUUID();
@@ -63610,7 +63737,7 @@ async function backlinksRoutes(app, opts) {
63610
63737
  updatedAt: now
63611
63738
  }).run();
63612
63739
  opts.onReleaseSyncRequested(id, release);
63613
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq65(ccReleaseSyncs.id, id)).get();
63740
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, id)).get();
63614
63741
  return reply.status(201).send(mapSyncRow(inserted));
63615
63742
  });
63616
63743
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -63668,7 +63795,7 @@ async function backlinksRoutes(app, opts) {
63668
63795
  createdAt: now
63669
63796
  }).run();
63670
63797
  opts.onBacklinkExtractRequested(runId, project.id, release);
63671
- const run = app.db.select().from(runs).where(eq65(runs.id, runId)).get();
63798
+ const run = app.db.select().from(runs).where(eq66(runs.id, runId)).get();
63672
63799
  return reply.status(201).send(mapRunRow(run));
63673
63800
  });
63674
63801
  app.get(
@@ -63694,12 +63821,12 @@ async function backlinksRoutes(app, opts) {
63694
63821
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
63695
63822
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
63696
63823
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
63697
- const baseDomainCondition = and56(
63698
- eq65(backlinkDomains.projectId, project.id),
63699
- eq65(backlinkDomains.source, source),
63700
- eq65(backlinkDomains.release, targetRelease)
63824
+ const baseDomainCondition = and57(
63825
+ eq66(backlinkDomains.projectId, project.id),
63826
+ eq66(backlinkDomains.source, source),
63827
+ eq66(backlinkDomains.release, targetRelease)
63701
63828
  );
63702
- const domainCondition = excludeCrawlers ? and56(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
63829
+ const domainCondition = excludeCrawlers ? and57(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
63703
63830
  const totalRow = app.db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(domainCondition).get();
63704
63831
  const rows = app.db.select({
63705
63832
  linkingDomain: backlinkDomains.linkingDomain,
@@ -63723,7 +63850,7 @@ async function backlinksRoutes(app, opts) {
63723
63850
  async (request, reply) => {
63724
63851
  const project = resolveProject(app.db, request.params.name);
63725
63852
  const source = parseSourceParam(request.query.source);
63726
- const rows = app.db.select().from(backlinkSummaries).where(and56(eq65(backlinkSummaries.projectId, project.id), eq65(backlinkSummaries.source, source))).orderBy(asc14(backlinkSummaries.queriedAt)).all();
63853
+ const rows = app.db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, project.id), eq66(backlinkSummaries.source, source))).orderBy(asc14(backlinkSummaries.queriedAt)).all();
63727
63854
  const response = rows.map((r) => ({
63728
63855
  release: r.release,
63729
63856
  totalLinkingDomains: r.totalLinkingDomains,
@@ -63750,7 +63877,7 @@ async function backlinksRoutes(app, opts) {
63750
63877
  import crypto48 from "crypto";
63751
63878
  import { isIP } from "net";
63752
63879
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
63753
- 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";
63880
+ 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";
63754
63881
 
63755
63882
  // ../api-routes/src/cloudflare-worker-version.ts
63756
63883
  var CURRENT_CLOUDFLARE_WORKER_VERSION = "1.0.1";
@@ -80416,10 +80543,10 @@ async function ackCloudflareQueueMessages(client, options) {
80416
80543
 
80417
80544
  // ../api-routes/src/traffic-event-ingest.ts
80418
80545
  import crypto47 from "crypto";
80419
- import { and as and58, eq as eq67, gte as gte15, lt as lt12, lte as lte15, sql as sql27 } from "drizzle-orm";
80546
+ import { and as and59, eq as eq68, gte as gte15, lt as lt12, lte as lte15, sql as sql27 } from "drizzle-orm";
80420
80547
 
80421
80548
  // ../api-routes/src/raw-event-sample-retention.ts
80422
- import { and as and57, eq as eq66, lt as lt11, sql as sql26 } from "drizzle-orm";
80549
+ import { and as and58, eq as eq67, lt as lt11, sql as sql26 } from "drizzle-orm";
80423
80550
  var RAW_EVENT_SAMPLE_RETENTION_MS = 30 * 24 * 60 * 6e4;
80424
80551
  var RAW_EVENT_SAMPLE_RETENTION_SWEEP_INTERVAL_MS = 24 * 60 * 6e4;
80425
80552
  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";
@@ -80429,14 +80556,14 @@ function rawEventSampleRetentionCutoff(referenceAt) {
80429
80556
  return new Date(referenceMs - RAW_EVENT_SAMPLE_RETENTION_MS).toISOString();
80430
80557
  }
80431
80558
  function deleteExpiredRawEventSamples(db, cutoff, sourceId) {
80432
- const sourceScope = sourceId === void 0 ? void 0 : eq66(rawEventSamples.sourceId, sourceId);
80433
- db.delete(rawEventSamples).where(and57(sourceScope, sql26`julianday(${rawEventSamples.ts}) IS NULL`)).run();
80434
- db.delete(rawEventSamples).where(and57(
80559
+ const sourceScope = sourceId === void 0 ? void 0 : eq67(rawEventSamples.sourceId, sourceId);
80560
+ db.delete(rawEventSamples).where(and58(sourceScope, sql26`julianday(${rawEventSamples.ts}) IS NULL`)).run();
80561
+ db.delete(rawEventSamples).where(and58(
80435
80562
  sourceScope,
80436
80563
  sql26`${rawEventSamples.ts} GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
80437
80564
  lt11(rawEventSamples.ts, cutoff)
80438
80565
  )).run();
80439
- db.delete(rawEventSamples).where(and57(
80566
+ db.delete(rawEventSamples).where(and58(
80440
80567
  sourceScope,
80441
80568
  sql26`${rawEventSamples.ts} NOT GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
80442
80569
  sql26`julianday(${rawEventSamples.ts}) < julianday(${cutoff})`
@@ -80485,16 +80612,16 @@ function writeTrafficEventBatch(opts) {
80485
80612
  let aiReferralHits = 0;
80486
80613
  let unknownHits = 0;
80487
80614
  opts.db.transaction((tx) => {
80488
- tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(eq67(trafficSources.id, opts.sourceId)).run();
80489
- const source = tx.select().from(trafficSources).where(eq67(trafficSources.id, opts.sourceId)).get();
80615
+ tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(eq68(trafficSources.id, opts.sourceId)).run();
80616
+ const source = tx.select().from(trafficSources).where(eq68(trafficSources.id, opts.sourceId)).get();
80490
80617
  opts.validateSource(source);
80491
80618
  const rawSampleCutoff = enforceRawEventSampleRetention(
80492
80619
  tx,
80493
80620
  opts.sourceId,
80494
80621
  opts.receivedAt
80495
80622
  );
80496
- tx.delete(trafficEventReceipts).where(and58(
80497
- eq67(trafficEventReceipts.sourceId, opts.sourceId),
80623
+ tx.delete(trafficEventReceipts).where(and59(
80624
+ eq68(trafficEventReceipts.sourceId, opts.sourceId),
80498
80625
  lte15(trafficEventReceipts.expiresAt, opts.receivedAt)
80499
80626
  )).run();
80500
80627
  const claimedEvents = [];
@@ -80693,8 +80820,8 @@ function writeTrafficEventBatch(opts) {
80693
80820
  if (!hour || hourlySampleLimit === 0) continue;
80694
80821
  let remaining = remainingSamplesByHour.get(hour.start);
80695
80822
  if (remaining === void 0) {
80696
- const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(and58(
80697
- eq67(rawEventSamples.sourceId, opts.sourceId),
80823
+ const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(and59(
80824
+ eq68(rawEventSamples.sourceId, opts.sourceId),
80698
80825
  gte15(rawEventSamples.ts, hour.start),
80699
80826
  lt12(rawEventSamples.ts, hour.end)
80700
80827
  )).get();
@@ -80735,7 +80862,7 @@ function writeTrafficEventBatch(opts) {
80735
80862
  remainingSamplesByHour.set(hour.start, remaining - 1);
80736
80863
  }
80737
80864
  }
80738
- tx.update(trafficSources).set(opts.sourceUpdate).where(eq67(trafficSources.id, opts.sourceId)).run();
80865
+ tx.update(trafficSources).set(opts.sourceUpdate).where(eq68(trafficSources.id, opts.sourceId)).run();
80739
80866
  });
80740
80867
  return {
80741
80868
  acceptedEvents,
@@ -80753,7 +80880,7 @@ function writeTrafficEventBatch(opts) {
80753
80880
  }
80754
80881
 
80755
80882
  // ../api-routes/src/traffic-sync-lease.ts
80756
- import { and as and59, eq as eq68, isNull as isNull13, lte as lte16, or as or17 } from "drizzle-orm";
80883
+ import { and as and60, eq as eq69, isNull as isNull13, lte as lte16, or as or18 } from "drizzle-orm";
80757
80884
  function leaseExpiresAt(now, ttlMs) {
80758
80885
  const nowMs = Date.parse(now);
80759
80886
  if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
@@ -80766,12 +80893,12 @@ function tryClaimTrafficSyncLease(input) {
80766
80893
  const changed = tx.update(trafficSources).set({
80767
80894
  syncLeaseOwner: input.owner,
80768
80895
  syncLeaseExpiresAt: expiresAt
80769
- }).where(and59(
80770
- eq68(trafficSources.id, input.sourceId),
80771
- or17(
80896
+ }).where(and60(
80897
+ eq69(trafficSources.id, input.sourceId),
80898
+ or18(
80772
80899
  isNull13(trafficSources.syncLeaseOwner),
80773
80900
  lte16(trafficSources.syncLeaseExpiresAt, input.now),
80774
- eq68(trafficSources.syncLeaseOwner, input.owner)
80901
+ eq69(trafficSources.syncLeaseOwner, input.owner)
80775
80902
  )
80776
80903
  )).run();
80777
80904
  return changed.changes === 1;
@@ -80782,9 +80909,9 @@ function releaseTrafficSyncLease(input) {
80782
80909
  const changed = tx.update(trafficSources).set({
80783
80910
  syncLeaseOwner: null,
80784
80911
  syncLeaseExpiresAt: null
80785
- }).where(and59(
80786
- eq68(trafficSources.id, input.sourceId),
80787
- eq68(trafficSources.syncLeaseOwner, input.owner)
80912
+ }).where(and60(
80913
+ eq69(trafficSources.id, input.sourceId),
80914
+ eq69(trafficSources.syncLeaseOwner, input.owner)
80788
80915
  )).run();
80789
80916
  return changed.changes === 1;
80790
80917
  }, { behavior: "immediate" });
@@ -80865,7 +80992,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
80865
80992
  const authenticated = authenticateCloudflareIngest(request, store);
80866
80993
  const params = request.params;
80867
80994
  if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
80868
- const source = db.select().from(trafficSources).where(eq69(trafficSources.id, authenticated.credential.sourceId)).get();
80995
+ const source = db.select().from(trafficSources).where(eq70(trafficSources.id, authenticated.credential.sourceId)).get();
80869
80996
  if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
80870
80997
  return `cloudflare-source:${authenticated.credential.sourceId}`;
80871
80998
  }
@@ -81108,7 +81235,7 @@ async function defaultResolveAccessToken(record) {
81108
81235
  );
81109
81236
  }
81110
81237
  function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
81111
- return db.select().from(trafficSources).where(eq69(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
81238
+ return db.select().from(trafficSources).where(eq70(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
81112
81239
  }
81113
81240
  function isAuthoritativeTrafficSource(db, source) {
81114
81241
  return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
@@ -81132,13 +81259,13 @@ function wordpressBaseUrlIdentity(value) {
81132
81259
  }
81133
81260
  }
81134
81261
  function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
81135
- const schedule = tx.select().from(schedules).where(and60(
81136
- eq69(schedules.projectId, projectId),
81137
- eq69(schedules.kind, SchedulableRunKinds["traffic-sync"])
81262
+ const schedule = tx.select().from(schedules).where(and61(
81263
+ eq70(schedules.projectId, projectId),
81264
+ eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
81138
81265
  )).get();
81139
81266
  if (schedule) {
81140
81267
  if (schedule.sourceId === sourceId) return { changed: false, created: false };
81141
- tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq69(schedules.id, schedule.id)).run();
81268
+ tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq70(schedules.id, schedule.id)).run();
81142
81269
  return { changed: true, created: false };
81143
81270
  }
81144
81271
  if (!createIfMissing) return { changed: false, created: false };
@@ -81189,10 +81316,10 @@ async function runBackfillTask(options) {
81189
81316
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
81190
81317
  try {
81191
81318
  app.db.transaction((tx) => {
81192
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq69(runs.id, runId)).run();
81193
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
81319
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
81320
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
81194
81321
  if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
81195
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq69(trafficSources.id, sourceRow2.id)).run();
81322
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
81196
81323
  }
81197
81324
  });
81198
81325
  } catch {
@@ -81219,7 +81346,7 @@ async function runBackfillTask(options) {
81219
81346
  let emptyOutcome;
81220
81347
  try {
81221
81348
  const completed = app.db.transaction((tx) => {
81222
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
81349
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
81223
81350
  const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2);
81224
81351
  if (stillAuthoritative) {
81225
81352
  enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
@@ -81228,7 +81355,7 @@ async function runBackfillTask(options) {
81228
81355
  status: RunStatuses.failed,
81229
81356
  error: "Traffic source was deactivated or reconfigured during backfill",
81230
81357
  finishedAt: finishedAt2
81231
- }).where(eq69(runs.id, runId)).run();
81358
+ }).where(eq70(runs.id, runId)).run();
81232
81359
  return Boolean(stillAuthoritative);
81233
81360
  });
81234
81361
  emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
@@ -81254,13 +81381,13 @@ async function runBackfillTask(options) {
81254
81381
  const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
81255
81382
  try {
81256
81383
  const commitOutcome = app.db.transaction((tx) => {
81257
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
81384
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
81258
81385
  if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
81259
81386
  tx.update(runs).set({
81260
81387
  status: RunStatuses.failed,
81261
81388
  error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
81262
81389
  finishedAt
81263
- }).where(eq69(runs.id, runId)).run();
81390
+ }).where(eq70(runs.id, runId)).run();
81264
81391
  return "wordpress-backfill-unsupported";
81265
81392
  }
81266
81393
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
@@ -81268,7 +81395,7 @@ async function runBackfillTask(options) {
81268
81395
  status: RunStatuses.failed,
81269
81396
  error: "Traffic source was deactivated or reconfigured during backfill",
81270
81397
  finishedAt
81271
- }).where(eq69(runs.id, runId)).run();
81398
+ }).where(eq70(runs.id, runId)).run();
81272
81399
  return "source-inactive";
81273
81400
  }
81274
81401
  const rawSampleCutoff = enforceRawEventSampleRetention(
@@ -81277,29 +81404,29 @@ async function runBackfillTask(options) {
81277
81404
  finishedAt
81278
81405
  );
81279
81406
  tx.delete(crawlerEventsHourly).where(
81280
- and60(
81281
- eq69(crawlerEventsHourly.sourceId, sourceRow2.id),
81407
+ and61(
81408
+ eq70(crawlerEventsHourly.sourceId, sourceRow2.id),
81282
81409
  gte16(crawlerEventsHourly.tsHour, windowStartIso),
81283
81410
  lte17(crawlerEventsHourly.tsHour, windowEndIso)
81284
81411
  )
81285
81412
  ).run();
81286
81413
  tx.delete(aiUserFetchEventsHourly).where(
81287
- and60(
81288
- eq69(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
81414
+ and61(
81415
+ eq70(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
81289
81416
  gte16(aiUserFetchEventsHourly.tsHour, windowStartIso),
81290
81417
  lte17(aiUserFetchEventsHourly.tsHour, windowEndIso)
81291
81418
  )
81292
81419
  ).run();
81293
81420
  tx.delete(aiReferralEventsHourly).where(
81294
- and60(
81295
- eq69(aiReferralEventsHourly.sourceId, sourceRow2.id),
81421
+ and61(
81422
+ eq70(aiReferralEventsHourly.sourceId, sourceRow2.id),
81296
81423
  gte16(aiReferralEventsHourly.tsHour, windowStartIso),
81297
81424
  lte17(aiReferralEventsHourly.tsHour, windowEndIso)
81298
81425
  )
81299
81426
  ).run();
81300
81427
  tx.delete(rawEventSamples).where(
81301
- and60(
81302
- eq69(rawEventSamples.sourceId, sourceRow2.id),
81428
+ and61(
81429
+ eq70(rawEventSamples.sourceId, sourceRow2.id),
81303
81430
  gte16(rawEventSamples.ts, windowStartIso),
81304
81431
  lte17(rawEventSamples.ts, windowEndIso)
81305
81432
  )
@@ -81491,8 +81618,8 @@ async function runBackfillTask(options) {
81491
81618
  lastEventIds: newRingBuffer,
81492
81619
  ...skipRecovered ? { skippedThroughAt: null } : {},
81493
81620
  updatedAt: finishedAt
81494
- }).where(eq69(trafficSources.id, sourceRow2.id)).run();
81495
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq69(runs.id, runId)).run();
81621
+ }).where(eq70(trafficSources.id, sourceRow2.id)).run();
81622
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
81496
81623
  return "committed";
81497
81624
  });
81498
81625
  reportBackfill(commitOutcome === "committed" ? {
@@ -81654,7 +81781,7 @@ async function trafficRoutes(app, opts) {
81654
81781
  };
81655
81782
  const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
81656
81783
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
81657
- const activeSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
81784
+ const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
81658
81785
  const sourceId = activeSource?.id ?? crypto48.randomUUID();
81659
81786
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
81660
81787
  if (activeSource) {
@@ -81664,7 +81791,7 @@ async function trafficRoutes(app, opts) {
81664
81791
  lastError: null,
81665
81792
  configJson: config,
81666
81793
  updatedAt: now
81667
- }).where(eq69(trafficSources.id, sourceId)).run();
81794
+ }).where(eq70(trafficSources.id, sourceId)).run();
81668
81795
  } else {
81669
81796
  tx.insert(trafficSources).values({
81670
81797
  id: sourceId,
@@ -81690,7 +81817,7 @@ async function trafficRoutes(app, opts) {
81690
81817
  entityId: sourceId
81691
81818
  });
81692
81819
  return {
81693
- sourceRow: tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get(),
81820
+ sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
81694
81821
  scheduleChanged: scheduleBinding.changed
81695
81822
  };
81696
81823
  }, { behavior: "immediate" });
@@ -81745,7 +81872,7 @@ async function trafficRoutes(app, opts) {
81745
81872
  const config = { baseUrl, username };
81746
81873
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
81747
81874
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
81748
- const activeSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
81875
+ const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
81749
81876
  const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
81750
81877
  const sourceId = endpointChanged ? crypto48.randomUUID() : activeSource?.id ?? crypto48.randomUUID();
81751
81878
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
@@ -81756,14 +81883,14 @@ async function trafficRoutes(app, opts) {
81756
81883
  lastError: null,
81757
81884
  configJson: config,
81758
81885
  updatedAt: now
81759
- }).where(eq69(trafficSources.id, sourceId)).run();
81886
+ }).where(eq70(trafficSources.id, sourceId)).run();
81760
81887
  } else {
81761
81888
  if (activeSource) {
81762
81889
  tx.update(trafficSources).set({
81763
81890
  status: TrafficSourceStatuses.archived,
81764
81891
  archivedAt: now,
81765
81892
  updatedAt: now
81766
- }).where(eq69(trafficSources.id, activeSource.id)).run();
81893
+ }).where(eq70(trafficSources.id, activeSource.id)).run();
81767
81894
  }
81768
81895
  tx.insert(trafficSources).values({
81769
81896
  id: sourceId,
@@ -81789,7 +81916,7 @@ async function trafficRoutes(app, opts) {
81789
81916
  entityId: sourceId
81790
81917
  });
81791
81918
  return {
81792
- sourceRow: tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get(),
81919
+ sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
81793
81920
  scheduleChanged: scheduleBinding.changed
81794
81921
  };
81795
81922
  }, { behavior: "immediate" });
@@ -81846,7 +81973,7 @@ async function trafficRoutes(app, opts) {
81846
81973
  const config = { projectId, teamId, environment };
81847
81974
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
81848
81975
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
81849
- const activeSource = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
81976
+ const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
81850
81977
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
81851
81978
  let row;
81852
81979
  if (activeSource) {
@@ -81856,8 +81983,8 @@ async function trafficRoutes(app, opts) {
81856
81983
  lastError: null,
81857
81984
  configJson: config,
81858
81985
  updatedAt: now
81859
- }).where(eq69(trafficSources.id, activeSource.id)).run();
81860
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, activeSource.id)).get();
81986
+ }).where(eq70(trafficSources.id, activeSource.id)).run();
81987
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, activeSource.id)).get();
81861
81988
  } else {
81862
81989
  const newId = crypto48.randomUUID();
81863
81990
  tx.insert(trafficSources).values({
@@ -81882,7 +82009,7 @@ async function trafficRoutes(app, opts) {
81882
82009
  createdAt: now,
81883
82010
  updatedAt: now
81884
82011
  }).run();
81885
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, newId)).get();
82012
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, newId)).get();
81886
82013
  }
81887
82014
  const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
81888
82015
  writeAuditLog(tx, {
@@ -81926,7 +82053,7 @@ async function trafficRoutes(app, opts) {
81926
82053
  const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
81927
82054
  const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
81928
82055
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
81929
- const cloudflareSources = app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
82056
+ const cloudflareSources = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
81930
82057
  const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
81931
82058
  if (sameModeSource) attempt.update({ action: "reauth" });
81932
82059
  const sourceId2 = sameModeSource?.id ?? crypto48.randomUUID();
@@ -81977,7 +82104,7 @@ async function trafficRoutes(app, opts) {
81977
82104
  let scheduleChanged = false;
81978
82105
  try {
81979
82106
  const result = app.db.transaction((tx) => {
81980
- 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);
82107
+ 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);
81981
82108
  if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
81982
82109
  throw operationInProgress("Cloudflare Queue source changed during connect; retry");
81983
82110
  }
@@ -81995,7 +82122,7 @@ async function trafficRoutes(app, opts) {
81995
82122
  queueBacklogObservedAt: null
81996
82123
  } : {},
81997
82124
  updatedAt: now2
81998
- }).where(eq69(trafficSources.id, sourceId2)).run();
82125
+ }).where(eq70(trafficSources.id, sourceId2)).run();
81999
82126
  } else {
82000
82127
  tx.insert(trafficSources).values({
82001
82128
  id: sourceId2,
@@ -82015,7 +82142,7 @@ async function trafficRoutes(app, opts) {
82015
82142
  }
82016
82143
  const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
82017
82144
  return {
82018
- row: tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId2)).get(),
82145
+ row: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId2)).get(),
82019
82146
  scheduleChanged: changed
82020
82147
  };
82021
82148
  }, { behavior: "immediate" });
@@ -82063,7 +82190,7 @@ async function trafficRoutes(app, opts) {
82063
82190
  const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
82064
82191
  const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
82065
82192
  const now = (/* @__PURE__ */ new Date()).toISOString();
82066
- 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);
82193
+ 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);
82067
82194
  if (activeSource) attempt.update({ action: "reauth" });
82068
82195
  const sourceId = activeSource?.id ?? crypto48.randomUUID();
82069
82196
  const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
@@ -82128,7 +82255,7 @@ async function trafficRoutes(app, opts) {
82128
82255
  let scheduleRemoved = false;
82129
82256
  try {
82130
82257
  const result = app.db.transaction((tx) => {
82131
- 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);
82258
+ 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);
82132
82259
  if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
82133
82260
  throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
82134
82261
  }
@@ -82142,8 +82269,8 @@ async function trafficRoutes(app, opts) {
82142
82269
  configJson: config,
82143
82270
  ingestTokenHash,
82144
82271
  updatedAt: now
82145
- }).where(eq69(trafficSources.id, currentActiveSource.id)).run();
82146
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, currentActiveSource.id)).get();
82272
+ }).where(eq70(trafficSources.id, currentActiveSource.id)).run();
82273
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, currentActiveSource.id)).get();
82147
82274
  } else {
82148
82275
  tx.insert(trafficSources).values({
82149
82276
  id: sourceId,
@@ -82163,16 +82290,16 @@ async function trafficRoutes(app, opts) {
82163
82290
  createdAt: now,
82164
82291
  updatedAt: now
82165
82292
  }).run();
82166
- row = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get();
82293
+ row = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
82167
82294
  }
82168
82295
  let removed = false;
82169
82296
  if (row.status === TrafficSourceStatuses.connected) {
82170
- const schedule = tx.select().from(schedules).where(and60(
82171
- eq69(schedules.projectId, project.id),
82172
- eq69(schedules.kind, SchedulableRunKinds["traffic-sync"])
82297
+ const schedule = tx.select().from(schedules).where(and61(
82298
+ eq70(schedules.projectId, project.id),
82299
+ eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
82173
82300
  )).get();
82174
82301
  if (schedule) {
82175
- tx.delete(schedules).where(eq69(schedules.id, schedule.id)).run();
82302
+ tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
82176
82303
  removed = true;
82177
82304
  }
82178
82305
  }
@@ -82226,47 +82353,47 @@ async function trafficRoutes(app, opts) {
82226
82353
  };
82227
82354
  }));
82228
82355
  app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
82229
- const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
82356
+ const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
82230
82357
  const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
82231
82358
  return integration ? { integration, action: "select" } : void 0;
82232
82359
  }, async (request) => {
82233
82360
  const project = resolveProject(app.db, request.params.name);
82234
82361
  const now = (/* @__PURE__ */ new Date()).toISOString();
82235
82362
  const result = app.db.transaction((tx) => {
82236
- const target = tx.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
82363
+ const target = tx.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
82237
82364
  if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
82238
82365
  throw notFound("Traffic source", request.params.id);
82239
82366
  }
82240
82367
  const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
82241
- const siblings = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all();
82368
+ const siblings = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all();
82242
82369
  for (const sibling of siblings) {
82243
82370
  if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
82244
82371
  tx.update(trafficSources).set({
82245
82372
  status: TrafficSourceStatuses.paused,
82246
82373
  updatedAt: now
82247
- }).where(eq69(trafficSources.id, sibling.id)).run();
82374
+ }).where(eq70(trafficSources.id, sibling.id)).run();
82248
82375
  }
82249
82376
  tx.update(trafficSources).set({
82250
82377
  status: TrafficSourceStatuses.connected,
82251
82378
  lastError: null,
82252
82379
  archivedAt: null,
82253
82380
  updatedAt: now
82254
- }).where(eq69(trafficSources.id, target.id)).run();
82381
+ }).where(eq70(trafficSources.id, target.id)).run();
82255
82382
  let scheduleAction = null;
82256
82383
  if (usesPullSchedule) {
82257
82384
  bindTrafficSyncSchedule(tx, project.id, target.id, now);
82258
82385
  scheduleAction = "upsert";
82259
82386
  } else {
82260
- const schedule = tx.select().from(schedules).where(and60(
82261
- eq69(schedules.projectId, project.id),
82262
- eq69(schedules.kind, SchedulableRunKinds["traffic-sync"])
82387
+ const schedule = tx.select().from(schedules).where(and61(
82388
+ eq70(schedules.projectId, project.id),
82389
+ eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
82263
82390
  )).get();
82264
82391
  if (schedule) {
82265
- tx.delete(schedules).where(eq69(schedules.id, schedule.id)).run();
82392
+ tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
82266
82393
  scheduleAction = "delete";
82267
82394
  }
82268
82395
  }
82269
- const source = tx.select().from(trafficSources).where(eq69(trafficSources.id, target.id)).get();
82396
+ const source = tx.select().from(trafficSources).where(eq70(trafficSources.id, target.id)).get();
82270
82397
  writeAuditLog(tx, {
82271
82398
  projectId: project.id,
82272
82399
  actor: "api",
@@ -82334,7 +82461,7 @@ async function trafficRoutes(app, opts) {
82334
82461
  }
82335
82462
  const { bearerHash, credential } = authenticated;
82336
82463
  const sourceId = credential.sourceId;
82337
- const sourceRow2 = app.db.select().from(trafficSources).where(eq69(trafficSources.id, sourceId)).get();
82464
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
82338
82465
  if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
82339
82466
  throw authRequired();
82340
82467
  }
@@ -82408,7 +82535,7 @@ async function trafficRoutes(app, opts) {
82408
82535
  });
82409
82536
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
82410
82537
  const project = resolveProject(app.db, request.params.name);
82411
- const initialSourceRow = app.db.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
82538
+ const initialSourceRow = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
82412
82539
  if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
82413
82540
  throw notFound("Traffic source", request.params.id);
82414
82541
  }
@@ -82563,7 +82690,7 @@ async function trafficRoutes(app, opts) {
82563
82690
  now: (/* @__PURE__ */ new Date()).toISOString(),
82564
82691
  ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
82565
82692
  })) throw new Error("Queue sync lease was lost before acknowledgement");
82566
- const latestSource = app.db.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82693
+ const latestSource = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82567
82694
  if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
82568
82695
  throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
82569
82696
  }
@@ -82588,7 +82715,7 @@ async function trafficRoutes(app, opts) {
82588
82715
  const pulledEnvelopeCount = pulled.messages.length + pulled.skippedUnleasedMessageCount;
82589
82716
  if (pulledEnvelopeCount < CLOUDFLARE_QUEUE_BATCH_SIZE || remainingBacklogCount === 0) break;
82590
82717
  }
82591
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(eq69(runs.id, runId)).run();
82718
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(eq70(runs.id, runId)).run();
82592
82719
  writeAuditLog(app.db, {
82593
82720
  projectId: project.id,
82594
82721
  actor: "api",
@@ -82649,13 +82776,13 @@ async function trafficRoutes(app, opts) {
82649
82776
  } catch {
82650
82777
  }
82651
82778
  app.db.transaction((tx) => {
82652
- tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(eq69(runs.id, runId)).run();
82653
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82779
+ tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
82780
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82654
82781
  if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
82655
- tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and60(
82656
- eq69(trafficSources.id, sourceRow2.id),
82657
- eq69(trafficSources.status, TrafficSourceStatuses.connected),
82658
- eq69(trafficSources.syncLeaseOwner, leaseOwner)
82782
+ tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and61(
82783
+ eq70(trafficSources.id, sourceRow2.id),
82784
+ eq70(trafficSources.status, TrafficSourceStatuses.connected),
82785
+ eq70(trafficSources.syncLeaseOwner, leaseOwner)
82659
82786
  )).run();
82660
82787
  }
82661
82788
  });
@@ -82674,7 +82801,7 @@ async function trafficRoutes(app, opts) {
82674
82801
  `Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
82675
82802
  );
82676
82803
  }
82677
- 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);
82804
+ 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);
82678
82805
  if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
82679
82806
  throw validationError("Traffic source must be active before it can sync");
82680
82807
  }
@@ -82707,10 +82834,10 @@ async function trafficRoutes(app, opts) {
82707
82834
  const markFailed = (msg, errorCode2) => {
82708
82835
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
82709
82836
  app.db.transaction((tx) => {
82710
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq69(runs.id, runId)).run();
82711
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82837
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
82838
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82712
82839
  if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && (wordpressLeaseOwner === void 0 || latestSource.syncLeaseOwner === wordpressLeaseOwner)) {
82713
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq69(trafficSources.id, sourceRow2.id)).run();
82840
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
82714
82841
  }
82715
82842
  });
82716
82843
  try {
@@ -82804,7 +82931,7 @@ async function trafficRoutes(app, opts) {
82804
82931
  }
82805
82932
  const credential = credentialStore.getConnection(project.name);
82806
82933
  if (!credential) {
82807
- app.db.delete(runs).where(eq69(runs.id, runId)).run();
82934
+ app.db.delete(runs).where(eq70(runs.id, runId)).run();
82808
82935
  throw validationError(
82809
82936
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
82810
82937
  );
@@ -82838,14 +82965,14 @@ async function trafficRoutes(app, opts) {
82838
82965
  const windowEndIso2 = windowEnd.toISOString();
82839
82966
  const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
82840
82967
  const reservedSource = app.db.transaction((tx) => {
82841
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82968
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82842
82969
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== wordpressLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
82843
82970
  tx.update(trafficSources).set({
82844
82971
  lastSyncedAt: windowStartIso2,
82845
82972
  wordpressPendingUntil: windowEndIso2,
82846
82973
  updatedAt: reservedAt
82847
- }).where(eq69(trafficSources.id, sourceRow2.id)).run();
82848
- return tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
82974
+ }).where(eq70(trafficSources.id, sourceRow2.id)).run();
82975
+ return tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
82849
82976
  }, { behavior: "immediate" });
82850
82977
  if (!reservedSource) {
82851
82978
  const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
@@ -82905,12 +83032,12 @@ async function trafficRoutes(app, opts) {
82905
83032
  auditAction = "traffic.vercel.synced";
82906
83033
  const credentialStore = opts.vercelTrafficCredentialStore;
82907
83034
  if (!credentialStore) {
82908
- app.db.delete(runs).where(eq69(runs.id, runId)).run();
83035
+ app.db.delete(runs).where(eq70(runs.id, runId)).run();
82909
83036
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
82910
83037
  }
82911
83038
  const credential = credentialStore.getConnection(project.name);
82912
83039
  if (!credential) {
82913
- app.db.delete(runs).where(eq69(runs.id, runId)).run();
83040
+ app.db.delete(runs).where(eq70(runs.id, runId)).run();
82914
83041
  throw validationError(
82915
83042
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
82916
83043
  );
@@ -82928,7 +83055,7 @@ async function trafficRoutes(app, opts) {
82928
83055
  if (cappedStartMs > clampedStartMs) {
82929
83056
  const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
82930
83057
  const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
82931
- app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq69(trafficSources.id, sourceRow2.id)).run();
83058
+ app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq70(trafficSources.id, sourceRow2.id)).run();
82932
83059
  request.log.warn(
82933
83060
  {
82934
83061
  sourceId: sourceRow2.id,
@@ -83021,15 +83148,15 @@ async function trafficRoutes(app, opts) {
83021
83148
  throw providerError(`WordPress sync failed: ${msg}`);
83022
83149
  }
83023
83150
  const commitOutcome = app.db.transaction((tx) => {
83024
- const latestRow = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
83025
- const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83151
+ const latestRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
83152
+ const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83026
83153
  if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || wordpressLeaseOwner !== void 0 && latestRow.syncLeaseOwner !== wordpressLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
83027
83154
  const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
83028
83155
  tx.update(runs).set({
83029
83156
  status: RunStatuses.failed,
83030
83157
  error: "Traffic source was deactivated or reconfigured during sync",
83031
83158
  finishedAt: abortedAt
83032
- }).where(eq69(runs.id, runId)).run();
83159
+ }).where(eq70(runs.id, runId)).run();
83033
83160
  return "source-inactive";
83034
83161
  }
83035
83162
  const previousIds = latestRow.lastEventIds ?? [];
@@ -83292,8 +83419,8 @@ async function trafficRoutes(app, opts) {
83292
83419
  sourceUpdate.lastCursor = nextCursor ?? null;
83293
83420
  sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
83294
83421
  }
83295
- tx.update(trafficSources).set(sourceUpdate).where(eq69(trafficSources.id, sourceRow2.id)).run();
83296
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq69(runs.id, runId)).run();
83422
+ tx.update(trafficSources).set(sourceUpdate).where(eq70(trafficSources.id, sourceRow2.id)).run();
83423
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
83297
83424
  return "committed";
83298
83425
  }, { behavior: "immediate" });
83299
83426
  if (commitOutcome === "source-inactive") {
@@ -83363,7 +83490,7 @@ async function trafficRoutes(app, opts) {
83363
83490
  });
83364
83491
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
83365
83492
  const project = resolveProject(app.db, request.params.name);
83366
- const sourceRow2 = app.db.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
83493
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
83367
83494
  if (!sourceRow2 || sourceRow2.projectId !== project.id) {
83368
83495
  throw notFound("Traffic source", request.params.id);
83369
83496
  }
@@ -83476,7 +83603,7 @@ async function trafficRoutes(app, opts) {
83476
83603
  const startedAt = windowEnd.toISOString();
83477
83604
  const runId = crypto48.randomUUID();
83478
83605
  app.db.transaction((tx) => {
83479
- const latestSource = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
83606
+ const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
83480
83607
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
83481
83608
  throw validationError("Traffic source must remain active and unchanged before it can backfill");
83482
83609
  }
@@ -83519,8 +83646,8 @@ async function trafficRoutes(app, opts) {
83519
83646
  pathNormalized: crawlerEventsHourly.pathNormalized,
83520
83647
  hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
83521
83648
  }).from(crawlerEventsHourly).where(
83522
- and60(
83523
- eq69(crawlerEventsHourly.sourceId, row.id),
83649
+ and61(
83650
+ eq70(crawlerEventsHourly.sourceId, row.id),
83524
83651
  gte16(crawlerEventsHourly.tsHour, since)
83525
83652
  )
83526
83653
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
@@ -83529,8 +83656,8 @@ async function trafficRoutes(app, opts) {
83529
83656
  );
83530
83657
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
83531
83658
  const aiUserFetchTotals = app.db.select({ total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
83532
- and60(
83533
- eq69(aiUserFetchEventsHourly.sourceId, row.id),
83659
+ and61(
83660
+ eq70(aiUserFetchEventsHourly.sourceId, row.id),
83534
83661
  gte16(aiUserFetchEventsHourly.tsHour, since)
83535
83662
  )
83536
83663
  ).get();
@@ -83539,22 +83666,22 @@ async function trafficRoutes(app, opts) {
83539
83666
  nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
83540
83667
  landed: sql28`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
83541
83668
  }).from(aiReferralEventsHourly).where(
83542
- and60(
83543
- eq69(aiReferralEventsHourly.sourceId, row.id),
83669
+ and61(
83670
+ eq70(aiReferralEventsHourly.sourceId, row.id),
83544
83671
  gte16(aiReferralEventsHourly.tsHour, since)
83545
83672
  )
83546
83673
  ).get();
83547
83674
  const sampleTotals = app.db.select({ total: sql28`COUNT(*)` }).from(rawEventSamples).where(
83548
- and60(
83549
- eq69(rawEventSamples.sourceId, row.id),
83675
+ and61(
83676
+ eq70(rawEventSamples.sourceId, row.id),
83550
83677
  gte16(rawEventSamples.ts, since)
83551
83678
  )
83552
83679
  ).get();
83553
83680
  const latestRun = app.db.select().from(runs).where(
83554
- and60(
83555
- eq69(runs.projectId, projectId),
83556
- eq69(runs.kind, RunKinds["traffic-sync"]),
83557
- eq69(runs.sourceId, row.id)
83681
+ and61(
83682
+ eq70(runs.projectId, projectId),
83683
+ eq70(runs.kind, RunKinds["traffic-sync"]),
83684
+ eq70(runs.sourceId, row.id)
83558
83685
  )
83559
83686
  ).orderBy(desc35(runs.startedAt)).limit(1).get();
83560
83687
  return {
@@ -83590,7 +83717,7 @@ async function trafficRoutes(app, opts) {
83590
83717
  const now = (/* @__PURE__ */ new Date()).toISOString();
83591
83718
  let updatedRow;
83592
83719
  app.db.transaction((tx) => {
83593
- const sourceRow2 = tx.select().from(trafficSources).where(and60(eq69(trafficSources.projectId, project.id), eq69(trafficSources.id, request.params.id))).get();
83720
+ const sourceRow2 = tx.select().from(trafficSources).where(and61(eq70(trafficSources.projectId, project.id), eq70(trafficSources.id, request.params.id))).get();
83594
83721
  if (!sourceRow2) {
83595
83722
  throw notFound("traffic source", request.params.id);
83596
83723
  }
@@ -83604,7 +83731,7 @@ async function trafficRoutes(app, opts) {
83604
83731
  `Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
83605
83732
  );
83606
83733
  }
83607
- const hasConnectedSibling = tx.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83734
+ const hasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
83608
83735
  if (hasConnectedSibling) {
83609
83736
  throw validationError(
83610
83737
  `Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
@@ -83621,7 +83748,7 @@ async function trafficRoutes(app, opts) {
83621
83748
  status: TrafficSourceStatuses.connected,
83622
83749
  lastError: null,
83623
83750
  updatedAt: now
83624
- }).where(eq69(trafficSources.id, sourceRow2.id)).run();
83751
+ }).where(eq70(trafficSources.id, sourceRow2.id)).run();
83625
83752
  writeAuditLog(tx, auditFromRequest(request, {
83626
83753
  projectId: project.id,
83627
83754
  actor: "api",
@@ -83629,20 +83756,20 @@ async function trafficRoutes(app, opts) {
83629
83756
  entityType: "traffic_source",
83630
83757
  entityId: sourceRow2.id
83631
83758
  }));
83632
- updatedRow = tx.select().from(trafficSources).where(eq69(trafficSources.id, sourceRow2.id)).get();
83759
+ updatedRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
83633
83760
  });
83634
83761
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
83635
83762
  }));
83636
83763
  app.get("/projects/:name/traffic/sources", async (request) => {
83637
83764
  const project = resolveProject(app.db, request.params.name);
83638
- const rows = app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83765
+ const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83639
83766
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
83640
83767
  const response = { sources };
83641
83768
  return response;
83642
83769
  });
83643
83770
  app.get("/projects/:name/traffic/status", async (request) => {
83644
83771
  const project = resolveProject(app.db, request.params.name);
83645
- const rows = app.db.select().from(trafficSources).where(eq69(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83772
+ const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
83646
83773
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
83647
83774
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
83648
83775
  const response = { sources };
@@ -83652,7 +83779,7 @@ async function trafficRoutes(app, opts) {
83652
83779
  "/projects/:name/traffic/sources/:id",
83653
83780
  async (request) => {
83654
83781
  const project = resolveProject(app.db, request.params.name);
83655
- const row = app.db.select().from(trafficSources).where(eq69(trafficSources.id, request.params.id)).get();
83782
+ const row = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
83656
83783
  if (!row || row.projectId !== project.id) {
83657
83784
  throw notFound("Traffic source", request.params.id);
83658
83785
  }
@@ -83725,12 +83852,12 @@ async function trafficRoutes(app, opts) {
83725
83852
  const seriesByBucket = /* @__PURE__ */ new Map();
83726
83853
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
83727
83854
  const crawlerFilters = [
83728
- eq69(crawlerEventsHourly.projectId, project.id),
83855
+ eq70(crawlerEventsHourly.projectId, project.id),
83729
83856
  gte16(crawlerEventsHourly.tsHour, sinceIso),
83730
83857
  lte17(crawlerEventsHourly.tsHour, untilIso)
83731
83858
  ];
83732
- if (sourceIdParam) crawlerFilters.push(eq69(crawlerEventsHourly.sourceId, sourceIdParam));
83733
- const crawlerWhere = and60(...crawlerFilters);
83859
+ if (sourceIdParam) crawlerFilters.push(eq70(crawlerEventsHourly.sourceId, sourceIdParam));
83860
+ const crawlerWhere = and61(...crawlerFilters);
83734
83861
  const pathTotals = app.db.select({
83735
83862
  pathNormalized: crawlerEventsHourly.pathNormalized,
83736
83863
  hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
@@ -83800,12 +83927,12 @@ async function trafficRoutes(app, opts) {
83800
83927
  }
83801
83928
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
83802
83929
  const userFetchFilters = [
83803
- eq69(aiUserFetchEventsHourly.projectId, project.id),
83930
+ eq70(aiUserFetchEventsHourly.projectId, project.id),
83804
83931
  gte16(aiUserFetchEventsHourly.tsHour, sinceIso),
83805
83932
  lte17(aiUserFetchEventsHourly.tsHour, untilIso)
83806
83933
  ];
83807
- if (sourceIdParam) userFetchFilters.push(eq69(aiUserFetchEventsHourly.sourceId, sourceIdParam));
83808
- const userFetchWhere = and60(...userFetchFilters);
83934
+ if (sourceIdParam) userFetchFilters.push(eq70(aiUserFetchEventsHourly.sourceId, sourceIdParam));
83935
+ const userFetchWhere = and61(...userFetchFilters);
83809
83936
  const total = app.db.select({
83810
83937
  total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
83811
83938
  rows: sql28`COUNT(*)`
@@ -83839,12 +83966,12 @@ async function trafficRoutes(app, opts) {
83839
83966
  }
83840
83967
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
83841
83968
  const aiFilters = [
83842
- eq69(aiReferralEventsHourly.projectId, project.id),
83969
+ eq70(aiReferralEventsHourly.projectId, project.id),
83843
83970
  gte16(aiReferralEventsHourly.tsHour, sinceIso),
83844
83971
  lte17(aiReferralEventsHourly.tsHour, untilIso)
83845
83972
  ];
83846
- if (sourceIdParam) aiFilters.push(eq69(aiReferralEventsHourly.sourceId, sourceIdParam));
83847
- const aiWhere = and60(...aiFilters);
83973
+ if (sourceIdParam) aiFilters.push(eq70(aiReferralEventsHourly.sourceId, sourceIdParam));
83974
+ const aiWhere = and61(...aiFilters);
83848
83975
  const total = app.db.select({
83849
83976
  total: sql28`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
83850
83977
  nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
@@ -83919,7 +84046,7 @@ async function trafficRoutes(app, opts) {
83919
84046
  windowEnd: untilIso,
83920
84047
  series: (() => {
83921
84048
  const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
83922
- const coverageScope = (col, srcCol) => sourceIdParam ? and60(eq69(col, project.id), eq69(srcCol, sourceIdParam)) : eq69(col, project.id);
84049
+ const coverageScope = (col, srcCol) => sourceIdParam ? and61(eq70(col, project.id), eq70(srcCol, sourceIdParam)) : eq70(col, project.id);
83923
84050
  const firsts = [
83924
84051
  app.db.select({ v: sql28`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
83925
84052
  app.db.select({ v: sql28`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
@@ -83997,7 +84124,7 @@ async function trafficRoutes(app, opts) {
83997
84124
  }
83998
84125
 
83999
84126
  // ../api-routes/src/doctor/checks/report-sweeps.ts
84000
- import { and as and61, asc as asc15, eq as eq70, gte as gte17, lte as lte18 } from "drizzle-orm";
84127
+ import { and as and62, asc as asc15, eq as eq71, gte as gte17, lte as lte18 } from "drizzle-orm";
84001
84128
 
84002
84129
  // ../api-routes/src/visibility-attribution.ts
84003
84130
  function buildQueryAttribution2(projectQueries) {
@@ -84031,11 +84158,11 @@ function inspectSweep(ctx, run) {
84031
84158
  answerMentioned: querySnapshots.answerMentioned,
84032
84159
  answerText: querySnapshots.answerText,
84033
84160
  measurementExecutionId: querySnapshots.measurementExecutionId
84034
- }).from(querySnapshots).where(eq70(querySnapshots.runId, run.id)).all();
84161
+ }).from(querySnapshots).where(eq71(querySnapshots.runId, run.id)).all();
84035
84162
  if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
84036
84163
  try {
84037
84164
  if (run.measurementPlanVersionId) {
84038
- const version = ctx.db.select().from(measurementPlanVersions).where(and61(eq70(measurementPlanVersions.projectId, ctx.project.id), eq70(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
84165
+ const version = ctx.db.select().from(measurementPlanVersions).where(and62(eq71(measurementPlanVersions.projectId, ctx.project.id), eq71(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
84039
84166
  if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
84040
84167
  const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
84041
84168
  const manifest = measurementRunExpectedSlots(run, plan);
@@ -84067,10 +84194,10 @@ function inspectSweep(ctx, run) {
84067
84194
  };
84068
84195
  return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
84069
84196
  }
84070
- const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq70(simpleMeasurementDefinitions.runId, run.id)).get();
84197
+ const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq71(simpleMeasurementDefinitions.runId, run.id)).get();
84071
84198
  const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
84072
- const project = ctx.db.select().from(projects).where(eq70(projects.id, ctx.project.id)).get();
84073
- 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();
84199
+ const project = ctx.db.select().from(projects).where(eq71(projects.id, ctx.project.id)).get();
84200
+ 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();
84074
84201
  const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
84075
84202
  const attribution = buildQueryAttribution2(basket);
84076
84203
  const expected = new Set(basket.flatMap((query) => providers.map((provider) => JSON.stringify([query.id, provider.trim().toLowerCase()]))));
@@ -84119,7 +84246,7 @@ var reportSweepsCheck = {
84119
84246
  const graded = gradedReportMonths(reportMonths, now);
84120
84247
  const months = reportMonths.map((month) => {
84121
84248
  const bounds = calendarMonthBounds(month);
84122
- 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();
84249
+ 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();
84123
84250
  const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
84124
84251
  return {
84125
84252
  month,
@@ -84142,7 +84269,7 @@ var reportSweepsCheck = {
84142
84269
  };
84143
84270
 
84144
84271
  // ../api-routes/src/doctor/checks/report-models.ts
84145
- import { and as and62, eq as eq71, gte as gte18, inArray as inArray29, lte as lte19 } from "drizzle-orm";
84272
+ import { and as and63, eq as eq72, gte as gte18, inArray as inArray29, lte as lte19 } from "drizzle-orm";
84146
84273
  var reportModelsCheck = {
84147
84274
  id: "report.models",
84148
84275
  category: CheckCategories.providers,
@@ -84157,7 +84284,7 @@ var reportModelsCheck = {
84157
84284
  const months = reportMonths.flatMap((month) => {
84158
84285
  const bounds = calendarMonthBounds(month);
84159
84286
  const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
84160
- 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();
84287
+ 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();
84161
84288
  return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
84162
84289
  month,
84163
84290
  previousMonth: previous,
@@ -84198,7 +84325,7 @@ var reportModelsCheck = {
84198
84325
  };
84199
84326
 
84200
84327
  // ../api-routes/src/doctor/checks/report-daily-data.ts
84201
- import { eq as eq72 } from "drizzle-orm";
84328
+ import { eq as eq73 } from "drizzle-orm";
84202
84329
  var REPORTING_LAG_DAYS = 3;
84203
84330
  function dateSpan(dates) {
84204
84331
  const sorted = [...dates].sort();
@@ -84207,31 +84334,31 @@ function dateSpan(dates) {
84207
84334
  function latestSyncRange(ctx, source) {
84208
84335
  const projectId = ctx.project.id;
84209
84336
  if (source === "ga") {
84210
- const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq72(gaDailyTotals.projectId, projectId)).all();
84337
+ const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq73(gaDailyTotals.projectId, projectId)).all();
84211
84338
  const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
84212
- const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq72(gaTrafficSummaries.projectId, projectId)).get();
84339
+ const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq73(gaTrafficSummaries.projectId, projectId)).get();
84213
84340
  if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
84214
84341
  const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
84215
84342
  return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
84216
84343
  }
84217
- const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq72(gscDailyTotals.projectId, projectId)).all();
84344
+ const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq73(gscDailyTotals.projectId, projectId)).all();
84218
84345
  const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
84219
84346
  const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
84220
84347
  if (!span || latest === null) return null;
84221
- const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq72(gscDataWatermarks.projectId, projectId)).get();
84348
+ const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq73(gscDataWatermarks.projectId, projectId)).get();
84222
84349
  return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
84223
84350
  }
84224
84351
  function dailyCoverage(ctx, month, source) {
84225
84352
  const project = ctx.project;
84226
84353
  const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
84227
- 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();
84354
+ 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();
84228
84355
  if (!connection && stored.length === 0) return null;
84229
84356
  const bounds = calendarMonthBounds(month);
84230
84357
  const start = bounds.since.slice(0, 10);
84231
84358
  const end = bounds.until.slice(0, 10);
84232
84359
  const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
84233
84360
  const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
84234
- const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq72(projects.id, project.id)).get().date.slice(0, 10);
84361
+ const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq73(projects.id, project.id)).get().date.slice(0, 10);
84235
84362
  const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
84236
84363
  const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
84237
84364
  const byDate = new Map(stored.map((row) => [row.date, row.count]));
@@ -84313,7 +84440,7 @@ var reportDailyDataCheck = {
84313
84440
  };
84314
84441
 
84315
84442
  // ../api-routes/src/doctor/checks/referral-assessment.ts
84316
- import { and as and63, eq as eq73, ne as ne14 } from "drizzle-orm";
84443
+ import { and as and64, eq as eq74, ne as ne14 } from "drizzle-orm";
84317
84444
  var REFERRAL_ASSESSMENT_CHECKS = [{
84318
84445
  id: "report.ai-referral-bursts",
84319
84446
  category: CheckCategories.integrations,
@@ -84322,7 +84449,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
84322
84449
  notificationPolicy: CheckNotificationPolicies.silent,
84323
84450
  run: (ctx) => {
84324
84451
  if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.no-project", summary: "Select a project to inspect referral evidence." };
84325
- const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and63(eq73(trafficSources.projectId, ctx.project.id), ne14(trafficSources.status, TrafficSourceStatuses.archived))).get();
84452
+ const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and64(eq74(trafficSources.projectId, ctx.project.id), ne14(trafficSources.status, TrafficSourceStatuses.archived))).get();
84326
84453
  if (!configured) {
84327
84454
  return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
84328
84455
  }
@@ -84682,7 +84809,6 @@ var skillsTriggerSurfaceCheck = {
84682
84809
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck, skillsTriggerSurfaceCheck];
84683
84810
 
84684
84811
  // ../api-routes/src/doctor/checks/backlinks.ts
84685
- import { and as and64, eq as eq74 } from "drizzle-orm";
84686
84812
  function skippedNoProject() {
84687
84813
  return {
84688
84814
  status: CheckStatuses.skipped,
@@ -84698,36 +84824,58 @@ var BACKLINKS_CHECKS = [
84698
84824
  title: "Backlinks source connected",
84699
84825
  run: (ctx) => {
84700
84826
  if (!ctx.project) return skippedNoProject();
84701
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq74(projects.id, ctx.project.id)).get();
84702
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq74(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
84703
- const ccConnected = projectRow?.autoExtract === true && !!readySync;
84704
- const connected = [];
84705
- if (ccConnected) connected.push(BacklinkSources.commoncrawl);
84706
- if (connected.length === 0) {
84827
+ const state = readCommonCrawlSourceState(ctx.db, ctx.project.id);
84828
+ const details = {
84829
+ commoncrawl: state.connected,
84830
+ commoncrawlReadyRelease: state.hasReadyRelease,
84831
+ commoncrawlHasData: state.hasProjectData
84832
+ };
84833
+ if (!state.hasReadyRelease) {
84707
84834
  return {
84708
84835
  status: CheckStatuses.warn,
84709
84836
  code: "backlinks.source.none",
84710
- summary: `No backlink source is set up for ${ctx.project.name}.`,
84711
- remediation: "Enable Common Crawl by setting autoExtractBacklinks on the project, then run `canonry backlinks sync`.",
84712
- details: { commoncrawl: ccConnected }
84837
+ summary: `No backlink source is set up for ${ctx.project.name}: no Common Crawl release has been synced.`,
84838
+ remediation: "Run `canonry backlinks sync` to download the newest Common Crawl release for the workspace.",
84839
+ details
84713
84840
  };
84714
84841
  }
84715
- const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and64(
84716
- eq74(backlinkSummaries.projectId, ctx.project.id),
84717
- eq74(backlinkSummaries.source, BacklinkSources.commoncrawl)
84718
- )).limit(1).get() : false;
84842
+ if (!state.hasProjectData) {
84843
+ return {
84844
+ status: CheckStatuses.warn,
84845
+ code: "backlinks.source.none",
84846
+ 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.`,
84847
+ 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.",
84848
+ details
84849
+ };
84850
+ }
84851
+ const connected = [BacklinkSources.commoncrawl];
84719
84852
  return {
84720
84853
  status: CheckStatuses.ok,
84721
84854
  code: "backlinks.source.connected",
84722
84855
  summary: "Common Crawl backlink source is set up.",
84723
- 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,
84724
- details: { commoncrawl: ccConnected, connected, commoncrawlHasData: ccHasData }
84856
+ remediation: null,
84857
+ details: { ...details, connected }
84725
84858
  };
84726
84859
  }
84727
84860
  }
84728
84861
  ];
84729
84862
 
84730
84863
  // ../api-routes/src/doctor/checks/bing-auth.ts
84864
+ import { and as and65, desc as desc36, eq as eq75, inArray as inArray30 } from "drizzle-orm";
84865
+ var BING_CONNECTION_ENTITY = "bing_connection";
84866
+ var BING_CONNECTED_ACTION = "bing.connected";
84867
+ var BING_DISCONNECTED_ACTION = "bing.disconnected";
84868
+ function lostBingConnection(ctx, project) {
84869
+ const hasRow = (row) => row !== void 0;
84870
+ 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());
84871
+ if (!storedData) return false;
84872
+ const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and65(
84873
+ eq75(auditLog.entityType, BING_CONNECTION_ENTITY),
84874
+ eq75(auditLog.entityId, project.canonicalDomain),
84875
+ inArray30(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
84876
+ )).orderBy(desc36(auditLog.createdAt)).limit(1).get();
84877
+ return lastChange?.action !== BING_DISCONNECTED_ACTION;
84878
+ }
84731
84879
  var BING_AUTH_CHECKS = [
84732
84880
  {
84733
84881
  id: "bing.auth.connection",
@@ -84754,10 +84902,18 @@ var BING_AUTH_CHECKS = [
84754
84902
  }
84755
84903
  const conn = store.getConnection(ctx.project.canonicalDomain);
84756
84904
  if (!conn) {
84905
+ if (!lostBingConnection(ctx, ctx.project)) {
84906
+ return {
84907
+ status: CheckStatuses.skipped,
84908
+ code: "bing.auth.not-connected",
84909
+ summary: `${ctx.project.name} has not connected Bing Webmaster Tools.`,
84910
+ remediation: `Optional: run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to track Bing indexing.`
84911
+ };
84912
+ }
84757
84913
  return {
84758
84914
  status: CheckStatuses.fail,
84759
84915
  code: "bing.auth.no-connection",
84760
- summary: `No Bing connection for ${ctx.project.canonicalDomain}.`,
84916
+ summary: `No Bing connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} has stored Bing data from an earlier connection.`,
84761
84917
  remediation: `Run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to authorize.`
84762
84918
  };
84763
84919
  }
@@ -84874,7 +85030,7 @@ var BING_AUTH_CHECKS = [
84874
85030
  ];
84875
85031
 
84876
85032
  // ../api-routes/src/doctor/checks/content.ts
84877
- import { eq as eq75 } from "drizzle-orm";
85033
+ import { eq as eq76 } from "drizzle-orm";
84878
85034
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
84879
85035
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
84880
85036
  function skippedNoProject2() {
@@ -84887,13 +85043,17 @@ function skippedNoProject2() {
84887
85043
  }
84888
85044
  function loadProject(ctx) {
84889
85045
  if (!ctx.project) return null;
84890
- return ctx.db.select().from(projects).where(eq75(projects.id, ctx.project.id)).get() ?? null;
85046
+ return ctx.db.select().from(projects).where(eq76(projects.id, ctx.project.id)).get() ?? null;
84891
85047
  }
84892
85048
  var winnabilityCoverageCheck = {
84893
85049
  id: "content.winnability.coverage",
84894
85050
  category: CheckCategories.integrations,
84895
85051
  scope: CheckScopes.project,
84896
85052
  title: "Content winnability classification coverage",
85053
+ // Advice about content targeting, not a broken instrument. Most projects sit
85054
+ // below the threshold for good, and a check that never clears keeps the
85055
+ // project permanently degraded, so no recovery could ever be sent.
85056
+ notificationPolicy: CheckNotificationPolicies.silent,
84897
85057
  run: (ctx) => {
84898
85058
  if (!ctx.project) return skippedNoProject2();
84899
85059
  const project = loadProject(ctx);
@@ -84976,7 +85136,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
84976
85136
  );
84977
85137
 
84978
85138
  // ../api-routes/src/doctor/checks/ads.ts
84979
- import { eq as eq76 } from "drizzle-orm";
85139
+ import { eq as eq77 } from "drizzle-orm";
84980
85140
  var RECENT_SYNC_WARN_DAYS = 7;
84981
85141
  var RECENT_SYNC_FAIL_DAYS = 30;
84982
85142
  var adsConnectionCheck = {
@@ -84993,7 +85153,7 @@ var adsConnectionCheck = {
84993
85153
  remediation: null
84994
85154
  };
84995
85155
  }
84996
- const row = ctx.db.select().from(adsConnections).where(eq76(adsConnections.projectId, ctx.project.id)).get();
85156
+ const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
84997
85157
  if (!row) {
84998
85158
  return {
84999
85159
  status: CheckStatuses.skipped,
@@ -85043,7 +85203,7 @@ var adsRecentSyncCheck = {
85043
85203
  remediation: null
85044
85204
  };
85045
85205
  }
85046
- const row = ctx.db.select().from(adsConnections).where(eq76(adsConnections.projectId, ctx.project.id)).get();
85206
+ const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
85047
85207
  if (!row) {
85048
85208
  return {
85049
85209
  status: CheckStatuses.skipped,
@@ -85253,7 +85413,7 @@ var ga4ConnectionCheck = {
85253
85413
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
85254
85414
 
85255
85415
  // ../api-routes/src/doctor/checks/data-freshness.ts
85256
- import { and as and65, desc as desc36, eq as eq77, inArray as inArray30 } from "drizzle-orm";
85416
+ import { and as and66, desc as desc37, eq as eq78, inArray as inArray31 } from "drizzle-orm";
85257
85417
  var GA_DATA_AGING_DAYS = 3;
85258
85418
  var GA_DATA_STALE_DAYS = 5;
85259
85419
  var GSC_DATA_AGING_DAYS = 5;
@@ -85281,7 +85441,7 @@ function skipped(code, summary3) {
85281
85441
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
85282
85442
  }
85283
85443
  function lastSyncAgeDays(ctx, kinds) {
85284
- 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();
85444
+ 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();
85285
85445
  if (!row?.createdAt) return null;
85286
85446
  const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
85287
85447
  return Number.isFinite(age) ? age : null;
@@ -85377,7 +85537,7 @@ var gaRecentDataCheck = {
85377
85537
  const connection = gaConnected(ctx);
85378
85538
  if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
85379
85539
  if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
85380
- const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq77(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc36(gaDailyTotals.date)).limit(1).get();
85540
+ const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq78(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gaDailyTotals.date)).limit(1).get();
85381
85541
  return grade(ctx, GA_SPEC, newest?.date);
85382
85542
  }
85383
85543
  };
@@ -85392,9 +85552,9 @@ var gscRecentDataCheck = {
85392
85552
  if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
85393
85553
  return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
85394
85554
  }
85395
- const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq77(gscDataWatermarks.projectId, ctx.project.id)).get();
85555
+ const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq78(gscDataWatermarks.projectId, ctx.project.id)).get();
85396
85556
  if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
85397
- const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq77(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc36(gscDailyTotals.date)).limit(1).get();
85557
+ const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq78(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gscDailyTotals.date)).limit(1).get();
85398
85558
  return grade(ctx, GSC_SPEC, newest?.date);
85399
85559
  }
85400
85560
  };
@@ -85662,7 +85822,7 @@ var siteReachabilityCheck = {
85662
85822
  var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
85663
85823
 
85664
85824
  // ../api-routes/src/doctor/checks/gbp-auth.ts
85665
- import { and as and66, eq as eq78 } from "drizzle-orm";
85825
+ import { and as and67, eq as eq79 } from "drizzle-orm";
85666
85826
  var RECENT_SYNC_WARN_DAYS2 = 4;
85667
85827
  var RECENT_SYNC_FAIL_DAYS2 = 30;
85668
85828
  function skippedNoProject3() {
@@ -85681,10 +85841,25 @@ function storeUnavailable() {
85681
85841
  remediation: null
85682
85842
  };
85683
85843
  }
85844
+ function hasSelectedLocation(ctx, projectId) {
85845
+ return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and67(eq79(gbpLocations.projectId, projectId), eq79(gbpLocations.selected, true))).limit(1).get();
85846
+ }
85684
85847
  async function resolveGbpToken(ctx) {
85685
85848
  if (!ctx.project) return { ok: false, output: skippedNoProject3() };
85686
85849
  const store = ctx.googleConnectionStore;
85687
85850
  if (!store) return { ok: false, output: storeUnavailable() };
85851
+ const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
85852
+ if (!conn && !hasSelectedLocation(ctx, ctx.project.id)) {
85853
+ return {
85854
+ ok: false,
85855
+ output: {
85856
+ status: CheckStatuses.skipped,
85857
+ code: "gbp.auth.not-connected",
85858
+ summary: `${ctx.project.name} has not connected Google Business Profile.`,
85859
+ remediation: `Optional: run \`canonry gbp connect ${ctx.project.name}\` to track a Business Profile.`
85860
+ }
85861
+ };
85862
+ }
85688
85863
  const auth = ctx.getGoogleAuthConfig?.() ?? {};
85689
85864
  if (!auth.clientId || !auth.clientSecret) {
85690
85865
  return {
@@ -85697,14 +85872,13 @@ async function resolveGbpToken(ctx) {
85697
85872
  }
85698
85873
  };
85699
85874
  }
85700
- const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
85701
85875
  if (!conn) {
85702
85876
  return {
85703
85877
  ok: false,
85704
85878
  output: {
85705
85879
  status: CheckStatuses.fail,
85706
85880
  code: "gbp.auth.no-connection",
85707
- summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}.`,
85881
+ summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} still has selected locations.`,
85708
85882
  remediation: `Run \`canonry gbp connect ${ctx.project.name}\` to authorize.`
85709
85883
  }
85710
85884
  };
@@ -85918,7 +86092,7 @@ var recentSyncCheck = {
85918
86092
  title: "GBP recent sync",
85919
86093
  run: (ctx) => {
85920
86094
  if (!ctx.project) return skippedNoProject3();
85921
- 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();
86095
+ 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();
85922
86096
  if (selected.length === 0) {
85923
86097
  return {
85924
86098
  status: CheckStatuses.skipped,
@@ -85978,7 +86152,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
85978
86152
  );
85979
86153
 
85980
86154
  // ../api-routes/src/doctor/checks/places.ts
85981
- import { eq as eq79 } from "drizzle-orm";
86155
+ import { eq as eq80 } from "drizzle-orm";
85982
86156
  var apiKeyCheck = {
85983
86157
  id: "gbp.places.api-key",
85984
86158
  category: CheckCategories.auth,
@@ -86023,7 +86197,7 @@ var apiKeyCheck = {
86023
86197
  details: { tier: cfg.tier }
86024
86198
  };
86025
86199
  }
86026
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq79(gbpLocations.projectId, ctx.project.id)).all();
86200
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq80(gbpLocations.projectId, ctx.project.id)).all();
86027
86201
  const selected = rows.filter((r) => r.selected);
86028
86202
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
86029
86203
  const details = {
@@ -86056,7 +86230,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
86056
86230
  );
86057
86231
 
86058
86232
  // ../api-routes/src/doctor/checks/gbp-reviews.ts
86059
- import { and as and67, eq as eq80 } from "drizzle-orm";
86233
+ import { and as and68, eq as eq81 } from "drizzle-orm";
86060
86234
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
86061
86235
  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`.";
86062
86236
  var reviewsAccessCheck = {
@@ -86084,7 +86258,7 @@ var reviewsAccessCheck = {
86084
86258
  reviewsAccess: gbpLocations.reviewsAccess,
86085
86259
  reviewsAccessReason: gbpLocations.reviewsAccessReason,
86086
86260
  reviewsCheckedAt: gbpLocations.reviewsCheckedAt
86087
- }).from(gbpLocations).where(and67(eq80(gbpLocations.projectId, project.id), eq80(gbpLocations.selected, true))).all();
86261
+ }).from(gbpLocations).where(and68(eq81(gbpLocations.projectId, project.id), eq81(gbpLocations.selected, true))).all();
86088
86262
  if (locations.length === 0) {
86089
86263
  return {
86090
86264
  status: CheckStatuses.skipped,
@@ -86104,7 +86278,7 @@ var reviewsAccessCheck = {
86104
86278
  const places = ctx.getPlacesConfig?.();
86105
86279
  const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
86106
86280
  const v4Worked = new Set(
86107
- ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and67(eq80(gbpReviewRatings.projectId, project.id), eq80(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
86281
+ ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and68(eq81(gbpReviewRatings.projectId, project.id), eq81(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
86108
86282
  );
86109
86283
  const coverageOf = (loc) => {
86110
86284
  if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
@@ -86138,7 +86312,7 @@ var reviewsAccessCheck = {
86138
86312
  none > 0 ? `${none} have no review source` : null
86139
86313
  ].filter((part) => part !== null);
86140
86314
  const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
86141
- 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)));
86315
+ 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)));
86142
86316
  const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
86143
86317
  if (none > 0 && subscribed) {
86144
86318
  return {
@@ -86696,7 +86870,7 @@ var RUNTIME_STATE_CHECKS = [
86696
86870
  ];
86697
86871
 
86698
86872
  // ../api-routes/src/doctor/checks/traffic-source.ts
86699
- import { and as and68, eq as eq81, gte as gte19, inArray as inArray31, ne as ne15, sql as sql29 } from "drizzle-orm";
86873
+ import { and as and69, eq as eq82, gte as gte19, inArray as inArray32, ne as ne15, sql as sql29 } from "drizzle-orm";
86700
86874
  var RECENT_DATA_WARN_DAYS = 7;
86701
86875
  var RECENT_DATA_FAIL_DAYS = 30;
86702
86876
  function isCloudflareDirectPush(source) {
@@ -86735,8 +86909,8 @@ function skippedNoProject5() {
86735
86909
  function loadProbes(ctx) {
86736
86910
  if (!ctx.project) return [];
86737
86911
  const rows = ctx.db.select().from(trafficSources).where(
86738
- and68(
86739
- eq81(trafficSources.projectId, ctx.project.id),
86912
+ and69(
86913
+ eq82(trafficSources.projectId, ctx.project.id),
86740
86914
  ne15(trafficSources.status, TrafficSourceStatuses.archived)
86741
86915
  )
86742
86916
  ).all();
@@ -86842,27 +87016,27 @@ var recentDataCheck = {
86842
87016
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
86843
87017
  const recentCrawlers = Number(
86844
87018
  ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
86845
- and68(
86846
- eq81(crawlerEventsHourly.projectId, ctx.project.id),
86847
- inArray31(crawlerEventsHourly.sourceId, activeSourceIds),
87019
+ and69(
87020
+ eq82(crawlerEventsHourly.projectId, ctx.project.id),
87021
+ inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
86848
87022
  gte19(crawlerEventsHourly.tsHour, warnCutoff)
86849
87023
  )
86850
87024
  ).get()?.total ?? 0
86851
87025
  );
86852
87026
  const recentReferrals = Number(
86853
87027
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
86854
- and68(
86855
- eq81(aiReferralEventsHourly.projectId, ctx.project.id),
86856
- inArray31(aiReferralEventsHourly.sourceId, activeSourceIds),
87028
+ and69(
87029
+ eq82(aiReferralEventsHourly.projectId, ctx.project.id),
87030
+ inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
86857
87031
  gte19(aiReferralEventsHourly.tsHour, warnCutoff)
86858
87032
  )
86859
87033
  ).get()?.total ?? 0
86860
87034
  );
86861
87035
  const recentUserFetches = Number(
86862
87036
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
86863
- and68(
86864
- eq81(aiUserFetchEventsHourly.projectId, ctx.project.id),
86865
- inArray31(aiUserFetchEventsHourly.sourceId, activeSourceIds),
87037
+ and69(
87038
+ eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
87039
+ inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
86866
87040
  gte19(aiUserFetchEventsHourly.tsHour, warnCutoff)
86867
87041
  )
86868
87042
  ).get()?.total ?? 0
@@ -86886,27 +87060,27 @@ var recentDataCheck = {
86886
87060
  }
86887
87061
  const olderCrawlers = Number(
86888
87062
  ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
86889
- and68(
86890
- eq81(crawlerEventsHourly.projectId, ctx.project.id),
86891
- inArray31(crawlerEventsHourly.sourceId, activeSourceIds),
87063
+ and69(
87064
+ eq82(crawlerEventsHourly.projectId, ctx.project.id),
87065
+ inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
86892
87066
  gte19(crawlerEventsHourly.tsHour, failCutoff)
86893
87067
  )
86894
87068
  ).get()?.total ?? 0
86895
87069
  );
86896
87070
  const olderReferrals = Number(
86897
87071
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
86898
- and68(
86899
- eq81(aiReferralEventsHourly.projectId, ctx.project.id),
86900
- inArray31(aiReferralEventsHourly.sourceId, activeSourceIds),
87072
+ and69(
87073
+ eq82(aiReferralEventsHourly.projectId, ctx.project.id),
87074
+ inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
86901
87075
  gte19(aiReferralEventsHourly.tsHour, failCutoff)
86902
87076
  )
86903
87077
  ).get()?.total ?? 0
86904
87078
  );
86905
87079
  const olderUserFetches = Number(
86906
87080
  ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
86907
- and68(
86908
- eq81(aiUserFetchEventsHourly.projectId, ctx.project.id),
86909
- inArray31(aiUserFetchEventsHourly.sourceId, activeSourceIds),
87081
+ and69(
87082
+ eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
87083
+ inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
86910
87084
  gte19(aiUserFetchEventsHourly.tsHour, failCutoff)
86911
87085
  )
86912
87086
  ).get()?.total ?? 0
@@ -87080,6 +87254,7 @@ var cacheBlindSpotCheck = {
87080
87254
  category: CheckCategories.integrations,
87081
87255
  scope: CheckScopes.project,
87082
87256
  title: "WordPress traffic cache blind spot",
87257
+ notificationPolicy: CheckNotificationPolicies.silent,
87083
87258
  run: (ctx) => {
87084
87259
  if (!ctx.project) return skippedNoProject5();
87085
87260
  const wpSources = loadProbes(ctx).filter(
@@ -87679,7 +87854,7 @@ var GOOGLE_MARKETING_DOCTOR_CHECKS = createGoogleMarketingDoctorChecks(
87679
87854
  );
87680
87855
 
87681
87856
  // ../api-routes/src/doctor/checks/integration-sync.ts
87682
- 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";
87857
+ 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";
87683
87858
  var SYNC_FAILURE_WINDOW = 3;
87684
87859
  var SUMMARY_ERROR_LIMIT = 300;
87685
87860
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -87699,7 +87874,7 @@ function gscConnection(ctx, project) {
87699
87874
  function gbpConnection(ctx, project) {
87700
87875
  if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
87701
87876
  if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
87702
- const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and69(eq82(gbpLocations.projectId, project.id), eq82(gbpLocations.selected, true))).limit(1).get();
87877
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and70(eq83(gbpLocations.projectId, project.id), eq83(gbpLocations.selected, true))).limit(1).get();
87703
87878
  return selected ? CONNECTED : { state: "no-selected-locations" };
87704
87879
  }
87705
87880
  function gaConnection(ctx, project) {
@@ -87777,20 +87952,20 @@ function gradeSyncFailures(ctx, spec) {
87777
87952
  return skipped3(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
87778
87953
  }
87779
87954
  const nothingToSync = spec.nothingToSyncErrors ?? [];
87780
- const scope = and69(
87781
- eq82(runs.projectId, project.id),
87782
- eq82(runs.kind, spec.kind),
87783
- ...nothingToSync.length > 0 ? [or18(isNull14(runs.error), notInArray2(runs.error, [...nothingToSync]))] : []
87955
+ const scope = and70(
87956
+ eq83(runs.projectId, project.id),
87957
+ eq83(runs.kind, spec.kind),
87958
+ ...nothingToSync.length > 0 ? [or19(isNull14(runs.error), notInArray2(runs.error, [...nothingToSync]))] : []
87784
87959
  );
87785
- const graded = connection.since ? and69(scope, gte20(runs.createdAt, connection.since)) : scope;
87786
- 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();
87960
+ const graded = connection.since ? and70(scope, gte20(runs.createdAt, connection.since)) : scope;
87961
+ 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();
87787
87962
  if (recent.length === 0) {
87788
87963
  return skipped3(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
87789
87964
  }
87790
- 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();
87791
- const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(and69(
87965
+ 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();
87966
+ const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(and70(
87792
87967
  graded,
87793
- eq82(runs.status, RunStatuses.failed),
87968
+ eq83(runs.status, RunStatuses.failed),
87794
87969
  ...lastSuccess ? [gt7(runs.createdAt, lastSuccess.createdAt)] : []
87795
87970
  )).get()?.value ?? 0;
87796
87971
  const lastSuccessAt = lastSuccess ? lastSuccess.finishedAt ?? lastSuccess.createdAt : null;
@@ -88104,7 +88279,7 @@ async function doctorRoutes(app, opts) {
88104
88279
 
88105
88280
  // ../api-routes/src/discovery/routes.ts
88106
88281
  import crypto50 from "crypto";
88107
- 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";
88282
+ 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";
88108
88283
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
88109
88284
  async function discoveryRoutes(app, opts) {
88110
88285
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -88138,13 +88313,13 @@ async function discoveryRoutes(app, opts) {
88138
88313
  const now = (/* @__PURE__ */ new Date()).toISOString();
88139
88314
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
88140
88315
  const decision = app.db.transaction((tx) => {
88141
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and70(
88142
- eq83(discoverySessions.projectId, project.id),
88143
- eq83(discoverySessions.icpDescription, icpDescription),
88316
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and71(
88317
+ eq84(discoverySessions.projectId, project.id),
88318
+ eq84(discoverySessions.icpDescription, icpDescription),
88144
88319
  // Buyer is part of session identity: it changes the seed prompt's
88145
88320
  // semantics, so a request with a different (or no) buyer must start
88146
88321
  // its own session, never adopt another buyer's probes.
88147
- parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) : eq83(discoverySessions.buyerDescription, parsed.data.buyerDescription),
88322
+ parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) : eq84(discoverySessions.buyerDescription, parsed.data.buyerDescription),
88148
88323
  // Locations are identity too: a different service-area subset seeds
88149
88324
  // and probes a different geo, so it must never reuse another geo's
88150
88325
  // session. resolveLocations is deterministic (project-config order),
@@ -88154,18 +88329,18 @@ async function discoveryRoutes(app, opts) {
88154
88329
  // locations a NULL row's subset is unknowable, so it conservatively
88155
88330
  // never reuses — a one-time, bounded (2h window) non-reuse after
88156
88331
  // upgrade, never a wrong reuse.
88157
- locations.length === 0 ? or19(isNull15(discoverySessions.locations), eq83(discoverySessions.locations, locations)) : eq83(discoverySessions.locations, locations),
88332
+ locations.length === 0 ? or20(isNull15(discoverySessions.locations), eq84(discoverySessions.locations, locations)) : eq84(discoverySessions.locations, locations),
88158
88333
  // Seed provider set is identity: a different phrasing distribution
88159
88334
  // must never reuse another set's session. Null (= Gemini-only
88160
88335
  // default, including explicit ['gemini']) matches legacy rows.
88161
- seedProviders == null ? isNull15(discoverySessions.seedProviders) : eq83(discoverySessions.seedProviders, seedProviders),
88162
- inArray33(discoverySessions.status, [
88336
+ seedProviders == null ? isNull15(discoverySessions.seedProviders) : eq84(discoverySessions.seedProviders, seedProviders),
88337
+ inArray34(discoverySessions.status, [
88163
88338
  DiscoverySessionStatuses.queued,
88164
88339
  DiscoverySessionStatuses.seeding,
88165
88340
  DiscoverySessionStatuses.probing
88166
88341
  ]),
88167
88342
  gte21(discoverySessions.createdAt, ageFloorIso)
88168
- )).orderBy(desc38(discoverySessions.createdAt)).get();
88343
+ )).orderBy(desc39(discoverySessions.createdAt)).get();
88169
88344
  if (existing && existing.runId) {
88170
88345
  return { reused: true, sessionId: existing.id, runId: existing.runId };
88171
88346
  }
@@ -88234,7 +88409,7 @@ async function discoveryRoutes(app, opts) {
88234
88409
  const project = resolveProject(app.db, request.params.name);
88235
88410
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
88236
88411
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
88237
- const rows = app.db.select().from(discoverySessions).where(eq83(discoverySessions.projectId, project.id)).orderBy(desc38(discoverySessions.createdAt)).limit(limit).all();
88412
+ const rows = app.db.select().from(discoverySessions).where(eq84(discoverySessions.projectId, project.id)).orderBy(desc39(discoverySessions.createdAt)).limit(limit).all();
88238
88413
  return reply.send(rows.map(serializeSession));
88239
88414
  }
88240
88415
  );
@@ -88242,11 +88417,11 @@ async function discoveryRoutes(app, opts) {
88242
88417
  "/projects/:name/discover/sessions/:id",
88243
88418
  async (request, reply) => {
88244
88419
  const project = resolveProject(app.db, request.params.name);
88245
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88420
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88246
88421
  if (!session || session.projectId !== project.id) {
88247
88422
  throw notFound("Discovery session", request.params.id);
88248
88423
  }
88249
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88424
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88250
88425
  const detail = {
88251
88426
  ...serializeSession(session),
88252
88427
  probes: probeRows.map(serializeProbe)
@@ -88258,7 +88433,7 @@ async function discoveryRoutes(app, opts) {
88258
88433
  "/projects/:name/discover/sessions/:id/harvest",
88259
88434
  async (request, reply) => {
88260
88435
  const project = resolveProject(app.db, request.params.name);
88261
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88436
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88262
88437
  if (!session || session.projectId !== project.id) {
88263
88438
  throw notFound("Discovery session", request.params.id);
88264
88439
  }
@@ -88266,7 +88441,7 @@ async function discoveryRoutes(app, opts) {
88266
88441
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
88267
88442
  const applyAnchor = request.query.anchor !== "false";
88268
88443
  const provider = session.seedProvider ?? "gemini";
88269
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88444
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88270
88445
  const extract = opts.harvestSearchQueries;
88271
88446
  const probesWithQueries = probeRows.map((row) => {
88272
88447
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -88277,7 +88452,7 @@ async function discoveryRoutes(app, opts) {
88277
88452
  return { searchQueries: [] };
88278
88453
  }
88279
88454
  });
88280
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq83(queries.projectId, project.id)).all().map((r) => r.query);
88455
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query);
88281
88456
  const anchorTerms = buildHarvestAnchorTerms(
88282
88457
  [session.icpDescription ?? "", ...trackedQueries],
88283
88458
  effectiveDomains(project)
@@ -88324,11 +88499,11 @@ async function discoveryRoutes(app, opts) {
88324
88499
  "/projects/:name/discover/sessions/:id/promote",
88325
88500
  async (request, reply) => {
88326
88501
  const project = resolveProject(app.db, request.params.name);
88327
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88502
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88328
88503
  if (!session || session.projectId !== project.id) {
88329
88504
  throw notFound("Discovery session", request.params.id);
88330
88505
  }
88331
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88506
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88332
88507
  const cited = /* @__PURE__ */ new Set();
88333
88508
  const aspirational = /* @__PURE__ */ new Set();
88334
88509
  const wasted = /* @__PURE__ */ new Set();
@@ -88363,7 +88538,7 @@ async function discoveryRoutes(app, opts) {
88363
88538
  );
88364
88539
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
88365
88540
  const project = resolveProject(app.db, request.params.name);
88366
- const session = app.db.select().from(discoverySessions).where(eq83(discoverySessions.id, request.params.id)).get();
88541
+ const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
88367
88542
  if (!session || session.projectId !== project.id) {
88368
88543
  throw notFound("Discovery session", request.params.id);
88369
88544
  }
@@ -88386,7 +88561,7 @@ async function discoveryRoutes(app, opts) {
88386
88561
  const bucketSet = new Set(buckets);
88387
88562
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
88388
88563
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
88389
- const probeRows = app.db.select().from(discoveryProbes).where(eq83(discoveryProbes.sessionId, session.id)).all();
88564
+ const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
88390
88565
  const candidateQueries = /* @__PURE__ */ new Set();
88391
88566
  for (const probe of probeRows) {
88392
88567
  if (!probe.bucket) continue;
@@ -88394,7 +88569,7 @@ async function discoveryRoutes(app, opts) {
88394
88569
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
88395
88570
  }
88396
88571
  const existingQueries = new Set(
88397
- app.db.select({ query: queries.query }).from(queries).where(eq83(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
88572
+ app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
88398
88573
  );
88399
88574
  const promotedQueries = [];
88400
88575
  const skippedQueries = [];
@@ -88607,7 +88782,7 @@ function planCompetitorPromotion(input) {
88607
88782
 
88608
88783
  // ../api-routes/src/discovery/orchestrate.ts
88609
88784
  import crypto51 from "crypto";
88610
- import { eq as eq84 } from "drizzle-orm";
88785
+ import { eq as eq85 } from "drizzle-orm";
88611
88786
  var DEFAULT_MAX_PROBES = 100;
88612
88787
  var ABSOLUTE_MAX_PROBES = 500;
88613
88788
  function classifyProbeBucket(input) {
@@ -88692,7 +88867,7 @@ async function executeDiscovery(opts) {
88692
88867
  status: DiscoverySessionStatuses.seeding,
88693
88868
  dedupThreshold,
88694
88869
  startedAt
88695
- }).where(eq84(discoverySessions.id, opts.sessionId)).run();
88870
+ }).where(eq85(discoverySessions.id, opts.sessionId)).run();
88696
88871
  const seedResult = await opts.deps.seed({
88697
88872
  project: opts.project,
88698
88873
  icpDescription: opts.icpDescription,
@@ -88753,7 +88928,7 @@ async function executeDiscovery(opts) {
88753
88928
  dedupBandPairFraction: dedupStats.bandPairFraction,
88754
88929
  dedupPairsTotal: dedupStats.pairsTotal,
88755
88930
  warning
88756
- }).where(eq84(discoverySessions.id, opts.sessionId)).run();
88931
+ }).where(eq85(discoverySessions.id, opts.sessionId)).run();
88757
88932
  const probeLocation = opts.locations?.[0];
88758
88933
  const probeResults = await mapWithConcurrency(
88759
88934
  probedCanonicals,
@@ -88807,7 +88982,7 @@ async function executeDiscovery(opts) {
88807
88982
  wastedCount: buckets["wasted-surface"],
88808
88983
  competitorMap,
88809
88984
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
88810
- }).where(eq84(discoverySessions.id, opts.sessionId)).run();
88985
+ }).where(eq85(discoverySessions.id, opts.sessionId)).run();
88811
88986
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
88812
88987
  return {
88813
88988
  buckets,
@@ -88847,7 +89022,7 @@ function markSessionFailed(db, sessionId, error) {
88847
89022
  status: DiscoverySessionStatuses.failed,
88848
89023
  error,
88849
89024
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
88850
- }).where(eq84(discoverySessions.id, sessionId)).run();
89025
+ }).where(eq85(discoverySessions.id, sessionId)).run();
88851
89026
  }
88852
89027
  function dedupeStrings(input) {
88853
89028
  const seen2 = /* @__PURE__ */ new Set();
@@ -88865,7 +89040,7 @@ function dedupeStrings(input) {
88865
89040
 
88866
89041
  // ../api-routes/src/technical-aeo.ts
88867
89042
  import crypto52 from "crypto";
88868
- 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";
89043
+ 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";
88869
89044
  import { alias } from "drizzle-orm/sqlite-core";
88870
89045
  var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
88871
89046
  ...SiteCrawlFetchedStates,
@@ -89102,8 +89277,8 @@ function parseLinkKind(value) {
89102
89277
  return allowed.data;
89103
89278
  }
89104
89279
  function linkKindFilter(linkKind) {
89105
- if (linkKind === SiteHealthLinkKinds.content) return eq85(siteCrawlEdges.isTemplate, false);
89106
- if (linkKind === SiteHealthLinkKinds.template) return eq85(siteCrawlEdges.isTemplate, true);
89280
+ if (linkKind === SiteHealthLinkKinds.content) return eq86(siteCrawlEdges.isTemplate, false);
89281
+ if (linkKind === SiteHealthLinkKinds.template) return eq86(siteCrawlEdges.isTemplate, true);
89107
89282
  return void 0;
89108
89283
  }
89109
89284
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
@@ -89207,20 +89382,20 @@ function changedFields(before, after, fields) {
89207
89382
  }
89208
89383
  async function technicalAeoRoutes(app, opts) {
89209
89384
  const visibleCrawlFilters = (projectId) => [
89210
- eq85(siteCrawlSnapshots.projectId, projectId),
89211
- eq85(runs.projectId, projectId),
89212
- eq85(runs.kind, RunKinds["site-audit"]),
89213
- inArray34(runs.status, SURFACEABLE_STATUSES),
89385
+ eq86(siteCrawlSnapshots.projectId, projectId),
89386
+ eq86(runs.projectId, projectId),
89387
+ eq86(runs.kind, RunKinds["site-audit"]),
89388
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89214
89389
  notProbeRun()
89215
89390
  ];
89216
89391
  const availableScanDatesFor = (projectId, requestedDate) => {
89217
89392
  if (!requestedDate) return void 0;
89218
89393
  const date = sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
89219
89394
  const filters = visibleCrawlFilters(projectId);
89220
- const allDates = and71(...filters);
89221
- const matchingDates = and71(...filters, eq85(sql30`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
89222
- 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);
89223
- 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;
89395
+ const allDates = and72(...filters);
89396
+ const matchingDates = and72(...filters, eq86(sql30`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
89397
+ 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);
89398
+ 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;
89224
89399
  return {
89225
89400
  recentDates: readDates(allDates),
89226
89401
  totalDates: countDates(allDates),
@@ -89237,33 +89412,33 @@ async function technicalAeoRoutes(app, opts) {
89237
89412
  filters.push(gte22(siteCrawlSnapshots.createdAt, requestedDate), lt13(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
89238
89413
  }
89239
89414
  const selectionFilters = scope.scoredOnly ? [...filters, sql30`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
89240
- 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(
89241
- desc39(sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
89242
- desc39(siteCrawlSnapshots.complete),
89243
- desc39(siteCrawlSnapshots.pagesFetched),
89244
- desc39(siteCrawlSnapshots.createdAt),
89245
- desc39(siteCrawlSnapshots.runId)
89415
+ 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(
89416
+ desc40(sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
89417
+ desc40(siteCrawlSnapshots.complete),
89418
+ desc40(siteCrawlSnapshots.pagesFetched),
89419
+ desc40(siteCrawlSnapshots.createdAt),
89420
+ desc40(siteCrawlSnapshots.runId)
89246
89421
  ).limit(1).get();
89247
89422
  if (!selected) return void 0;
89248
89423
  const date = selected.snapshot.createdAt.slice(0, 10);
89249
89424
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
89250
89425
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
89251
- const sameDate = and71(
89426
+ const sameDate = and72(
89252
89427
  ...filters,
89253
89428
  gte22(siteCrawlSnapshots.createdAt, date),
89254
89429
  lt13(siteCrawlSnapshots.createdAt, nextDate.toISOString())
89255
89430
  );
89256
- const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89431
+ const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89257
89432
  const candidates = app.db.select({
89258
89433
  runId: siteCrawlSnapshots.runId,
89259
89434
  createdAt: siteCrawlSnapshots.createdAt,
89260
89435
  complete: siteCrawlSnapshots.complete,
89261
89436
  pages: siteCrawlSnapshots.pagesFetched
89262
- }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
89263
- desc39(siteCrawlSnapshots.complete),
89264
- desc39(siteCrawlSnapshots.pagesFetched),
89265
- desc39(siteCrawlSnapshots.createdAt),
89266
- desc39(siteCrawlSnapshots.runId)
89437
+ }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
89438
+ desc40(siteCrawlSnapshots.complete),
89439
+ desc40(siteCrawlSnapshots.pagesFetched),
89440
+ desc40(siteCrawlSnapshots.createdAt),
89441
+ desc40(siteCrawlSnapshots.runId)
89267
89442
  ).limit(10).all();
89268
89443
  const runSelection = {
89269
89444
  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",
@@ -89277,20 +89452,20 @@ async function technicalAeoRoutes(app, opts) {
89277
89452
  };
89278
89453
  const resolveAudit = (projectId, runId, recordedBefore) => {
89279
89454
  const filters = [
89280
- eq85(siteAuditSnapshots.projectId, projectId),
89281
- eq85(runs.projectId, projectId),
89282
- eq85(runs.kind, RunKinds["site-audit"]),
89283
- inArray34(runs.status, SURFACEABLE_STATUSES),
89455
+ eq86(siteAuditSnapshots.projectId, projectId),
89456
+ eq86(runs.projectId, projectId),
89457
+ eq86(runs.kind, RunKinds["site-audit"]),
89458
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89284
89459
  notProbeRun(),
89285
89460
  ...recordedBefore ? [lt13(siteAuditSnapshots.createdAt, recordedBefore)] : []
89286
89461
  ];
89287
89462
  const complete = sql30`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
89288
- 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(
89289
- desc39(sql30`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
89290
- desc39(complete),
89291
- desc39(siteAuditSnapshots.pagesAudited),
89292
- desc39(siteAuditSnapshots.createdAt),
89293
- desc39(siteAuditSnapshots.runId)
89463
+ 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(
89464
+ desc40(sql30`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
89465
+ desc40(complete),
89466
+ desc40(siteAuditSnapshots.pagesAudited),
89467
+ desc40(siteAuditSnapshots.createdAt),
89468
+ desc40(siteAuditSnapshots.runId)
89294
89469
  ).limit(1).get();
89295
89470
  const auditOfCrawl = (target) => {
89296
89471
  const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
@@ -89306,22 +89481,22 @@ async function technicalAeoRoutes(app, opts) {
89306
89481
  const date = selected.snap.createdAt.slice(0, 10);
89307
89482
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
89308
89483
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
89309
- const sameDate = and71(
89484
+ const sameDate = and72(
89310
89485
  ...filters,
89311
89486
  gte22(siteAuditSnapshots.createdAt, date),
89312
89487
  lt13(siteAuditSnapshots.createdAt, nextDate.toISOString())
89313
89488
  );
89314
- const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89489
+ const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
89315
89490
  const candidates = app.db.select({
89316
89491
  runId: siteAuditSnapshots.runId,
89317
89492
  createdAt: siteAuditSnapshots.createdAt,
89318
89493
  complete,
89319
89494
  pages: siteAuditSnapshots.pagesAudited
89320
- }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
89321
- desc39(complete),
89322
- desc39(siteAuditSnapshots.pagesAudited),
89323
- desc39(siteAuditSnapshots.createdAt),
89324
- desc39(siteAuditSnapshots.runId)
89495
+ }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
89496
+ desc40(complete),
89497
+ desc40(siteAuditSnapshots.pagesAudited),
89498
+ desc40(siteAuditSnapshots.createdAt),
89499
+ desc40(siteAuditSnapshots.runId)
89325
89500
  ).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
89326
89501
  const runSelection = {
89327
89502
  // A crawl exists here only when none stored a scorecard: this score is a fallback.
@@ -89383,10 +89558,10 @@ async function technicalAeoRoutes(app, opts) {
89383
89558
  };
89384
89559
  const inventorySummaryFor = (scope) => {
89385
89560
  if (!scope) return null;
89386
- const groups = crawlReasonGroups(and71(
89387
- eq85(siteCrawlPages.projectId, scope.projectId),
89388
- eq85(siteCrawlPages.runId, scope.runId),
89389
- eq85(siteCrawlPages.attemptId, scope.attemptId)
89561
+ const groups = crawlReasonGroups(and72(
89562
+ eq86(siteCrawlPages.projectId, scope.projectId),
89563
+ eq86(siteCrawlPages.runId, scope.runId),
89564
+ eq86(siteCrawlPages.attemptId, scope.attemptId)
89390
89565
  ));
89391
89566
  const total = groups.reduce((sum, group) => sum + group.pages, 0);
89392
89567
  const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
@@ -89406,34 +89581,34 @@ async function technicalAeoRoutes(app, opts) {
89406
89581
  const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
89407
89582
  const pageInScope = (scope, selector) => {
89408
89583
  if (!selector.nodeKey && !selector.url) return null;
89409
- return app.db.select().from(siteCrawlPages).where(and71(
89410
- eq85(siteCrawlPages.projectId, scope.projectId),
89411
- eq85(siteCrawlPages.runId, scope.runId),
89412
- eq85(siteCrawlPages.attemptId, scope.attemptId),
89413
- selector.nodeKey ? eq85(siteCrawlPages.nodeKey, selector.nodeKey) : eq85(siteCrawlPages.url, selector.url)
89584
+ return app.db.select().from(siteCrawlPages).where(and72(
89585
+ eq86(siteCrawlPages.projectId, scope.projectId),
89586
+ eq86(siteCrawlPages.runId, scope.runId),
89587
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
89588
+ selector.nodeKey ? eq86(siteCrawlPages.nodeKey, selector.nodeKey) : eq86(siteCrawlPages.url, selector.url)
89414
89589
  )).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
89415
89590
  };
89416
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and71(
89417
- eq85(siteCrawlPages.projectId, scope.projectId),
89418
- eq85(siteCrawlPages.runId, scope.runId),
89419
- eq85(siteCrawlPages.attemptId, scope.attemptId),
89420
- eq85(siteCrawlPages.depth, 0)
89591
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and72(
89592
+ eq86(siteCrawlPages.projectId, scope.projectId),
89593
+ eq86(siteCrawlPages.runId, scope.runId),
89594
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
89595
+ eq86(siteCrawlPages.depth, 0)
89421
89596
  )).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
89422
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and71(
89423
- eq85(runs.id, runId),
89424
- eq85(runs.projectId, projectId),
89425
- eq85(runs.kind, RunKinds["site-audit"]),
89426
- inArray34(runs.status, SURFACEABLE_STATUSES),
89597
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and72(
89598
+ eq86(runs.id, runId),
89599
+ eq86(runs.projectId, projectId),
89600
+ eq86(runs.kind, RunKinds["site-audit"]),
89601
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89427
89602
  notProbeRun()
89428
89603
  )).limit(1).get());
89429
89604
  const assertKnownAuditRun = (projectId, runId) => {
89430
89605
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
89431
89606
  };
89432
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(and71(
89433
- eq85(siteAuditSnapshots.projectId, projectId),
89434
- eq85(runs.projectId, projectId),
89435
- eq85(runs.kind, RunKinds["site-audit"]),
89436
- inArray34(runs.status, SURFACEABLE_STATUSES),
89607
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
89608
+ eq86(siteAuditSnapshots.projectId, projectId),
89609
+ eq86(runs.projectId, projectId),
89610
+ eq86(runs.kind, RunKinds["site-audit"]),
89611
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89437
89612
  notProbeRun()
89438
89613
  )).limit(1).get());
89439
89614
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -89534,14 +89709,14 @@ async function technicalAeoRoutes(app, opts) {
89534
89709
  };
89535
89710
  }
89536
89711
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
89537
- const conds = [eq85(siteAuditPages.projectId, project.id), eq85(siteAuditPages.runId, latest.snap.runId)];
89538
- if (statusFilter) conds.push(eq85(siteAuditPages.status, statusFilter));
89539
- const where = and71(...conds);
89712
+ const conds = [eq86(siteAuditPages.projectId, project.id), eq86(siteAuditPages.runId, latest.snap.runId)];
89713
+ if (statusFilter) conds.push(eq86(siteAuditPages.status, statusFilter));
89714
+ const where = and72(...conds);
89540
89715
  const totalRow = app.db.select({ value: count4() }).from(siteAuditPages).where(where).get();
89541
89716
  const total = totalRow?.value ?? 0;
89542
89717
  const limit = parsePositiveInt(request.query.limit, 100, 500);
89543
89718
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
89544
- const orderBy = request.query.sort === "score-desc" ? desc39(siteAuditPages.overallScore) : request.query.sort === "url" ? asc16(siteAuditPages.url) : asc16(siteAuditPages.overallScore);
89719
+ const orderBy = request.query.sort === "score-desc" ? desc40(siteAuditPages.overallScore) : request.query.sort === "url" ? asc16(siteAuditPages.url) : asc16(siteAuditPages.overallScore);
89545
89720
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
89546
89721
  const pages = rows.map((row) => ({
89547
89722
  url: row.url,
@@ -89560,13 +89735,13 @@ async function technicalAeoRoutes(app, opts) {
89560
89735
  auditedAt: siteAuditSnapshots.auditedAt,
89561
89736
  aggregateScore: siteAuditSnapshots.aggregateScore,
89562
89737
  pagesAudited: siteAuditSnapshots.pagesAudited
89563
- }).from(siteAuditSnapshots).innerJoin(runs, eq85(siteAuditSnapshots.runId, runs.id)).where(and71(
89564
- eq85(siteAuditSnapshots.projectId, project.id),
89565
- eq85(runs.projectId, project.id),
89566
- eq85(runs.kind, RunKinds["site-audit"]),
89567
- inArray34(runs.status, SURFACEABLE_STATUSES),
89738
+ }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
89739
+ eq86(siteAuditSnapshots.projectId, project.id),
89740
+ eq86(runs.projectId, project.id),
89741
+ eq86(runs.kind, RunKinds["site-audit"]),
89742
+ inArray35(runs.status, SURFACEABLE_STATUSES),
89568
89743
  notProbeRun()
89569
- )).orderBy(desc39(siteAuditSnapshots.createdAt)).limit(limit).all();
89744
+ )).orderBy(desc40(siteAuditSnapshots.createdAt)).limit(limit).all();
89570
89745
  return { project: project.name, points: rows.reverse() };
89571
89746
  });
89572
89747
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -89666,10 +89841,10 @@ async function technicalAeoRoutes(app, opts) {
89666
89841
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
89667
89842
  snapshot.rootUrl
89668
89843
  )?.nodeKey ?? null;
89669
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and71(
89670
- eq85(siteCrawlGraphLayouts.projectId, project.id),
89671
- eq85(siteCrawlGraphLayouts.runId, snapshot.runId),
89672
- eq85(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
89844
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and72(
89845
+ eq86(siteCrawlGraphLayouts.projectId, project.id),
89846
+ eq86(siteCrawlGraphLayouts.runId, snapshot.runId),
89847
+ eq86(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
89673
89848
  )).limit(1).get();
89674
89849
  if (!persistedLayout) {
89675
89850
  return {
@@ -89722,9 +89897,9 @@ async function technicalAeoRoutes(app, opts) {
89722
89897
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
89723
89898
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
89724
89899
  const graphScope = [
89725
- eq85(siteCrawlGraphNodes.projectId, project.id),
89726
- eq85(siteCrawlGraphNodes.runId, snapshot.runId),
89727
- eq85(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
89900
+ eq86(siteCrawlGraphNodes.projectId, project.id),
89901
+ eq86(siteCrawlGraphNodes.runId, snapshot.runId),
89902
+ eq86(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
89728
89903
  ];
89729
89904
  const nodeRows = app.db.select({
89730
89905
  nodeKey: siteCrawlPages.nodeKey,
@@ -89743,18 +89918,18 @@ async function technicalAeoRoutes(app, opts) {
89743
89918
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
89744
89919
  x: siteCrawlGraphNodes.x,
89745
89920
  y: siteCrawlGraphNodes.y
89746
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and71(
89747
- eq85(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
89748
- eq85(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
89749
- eq85(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
89750
- eq85(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
89751
- )).where(and71(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc16(siteCrawlGraphNodes.sampleRank)).all();
89921
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and72(
89922
+ eq86(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
89923
+ eq86(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
89924
+ eq86(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
89925
+ eq86(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
89926
+ )).where(and72(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc16(siteCrawlGraphNodes.sampleRank)).all();
89752
89927
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
89753
89928
  ...row,
89754
89929
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
89755
89930
  healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
89756
89931
  }));
89757
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq85(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
89932
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq86(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
89758
89933
  const edges = app.db.select({
89759
89934
  edgeKey: siteCrawlGraphEdges.edgeKey,
89760
89935
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -89762,22 +89937,22 @@ async function technicalAeoRoutes(app, opts) {
89762
89937
  followable: siteCrawlGraphEdges.followable,
89763
89938
  occurrences: siteCrawlGraphEdges.occurrences,
89764
89939
  isTemplate: siteCrawlGraphEdges.isTemplate
89765
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and71(
89766
- eq85(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
89767
- eq85(graphSourceNode.runId, siteCrawlGraphEdges.runId),
89768
- eq85(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
89769
- eq85(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
89940
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and72(
89941
+ eq86(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
89942
+ eq86(graphSourceNode.runId, siteCrawlGraphEdges.runId),
89943
+ eq86(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
89944
+ eq86(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
89770
89945
  lt13(graphSourceNode.sampleRank, maxNodes)
89771
- )).innerJoin(graphTargetNode, and71(
89772
- eq85(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
89773
- eq85(graphTargetNode.runId, siteCrawlGraphEdges.runId),
89774
- eq85(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
89775
- eq85(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
89946
+ )).innerJoin(graphTargetNode, and72(
89947
+ eq86(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
89948
+ eq86(graphTargetNode.runId, siteCrawlGraphEdges.runId),
89949
+ eq86(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
89950
+ eq86(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
89776
89951
  lt13(graphTargetNode.sampleRank, maxNodes)
89777
- )).where(and71(
89778
- eq85(siteCrawlGraphEdges.projectId, project.id),
89779
- eq85(siteCrawlGraphEdges.runId, snapshot.runId),
89780
- eq85(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
89952
+ )).where(and72(
89953
+ eq86(siteCrawlGraphEdges.projectId, project.id),
89954
+ eq86(siteCrawlGraphEdges.runId, snapshot.runId),
89955
+ eq86(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
89781
89956
  lt13(siteCrawlGraphEdges.sampleRank, maxEdges),
89782
89957
  graphLinkKindFilter
89783
89958
  )).orderBy(asc16(siteCrawlGraphEdges.sampleRank)).all();
@@ -89872,15 +90047,15 @@ async function technicalAeoRoutes(app, opts) {
89872
90047
  let frontier = [focus.nodeKey];
89873
90048
  let queryTruncated = false;
89874
90049
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
89875
- const candidates = app.db.select().from(siteCrawlEdges).where(and71(
89876
- eq85(siteCrawlEdges.projectId, scope.projectId),
89877
- eq85(siteCrawlEdges.runId, scope.runId),
89878
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
89879
- eq85(siteCrawlEdges.internal, true),
89880
- isNotNull8(siteCrawlEdges.targetNodeKey),
89881
- or20(
89882
- inArray34(siteCrawlEdges.sourceNodeKey, frontier),
89883
- inArray34(siteCrawlEdges.targetNodeKey, frontier)
90050
+ const candidates = app.db.select().from(siteCrawlEdges).where(and72(
90051
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90052
+ eq86(siteCrawlEdges.runId, scope.runId),
90053
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90054
+ eq86(siteCrawlEdges.internal, true),
90055
+ isNotNull9(siteCrawlEdges.targetNodeKey),
90056
+ or21(
90057
+ inArray35(siteCrawlEdges.sourceNodeKey, frontier),
90058
+ inArray35(siteCrawlEdges.targetNodeKey, frontier)
89884
90059
  )
89885
90060
  )).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
89886
90061
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
@@ -89913,22 +90088,22 @@ async function technicalAeoRoutes(app, opts) {
89913
90088
  frontier = [...next].sort();
89914
90089
  }
89915
90090
  const nodeKeys = [...distances.keys()];
89916
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and71(
89917
- eq85(siteCrawlPages.projectId, scope.projectId),
89918
- eq85(siteCrawlPages.runId, scope.runId),
89919
- eq85(siteCrawlPages.attemptId, scope.attemptId),
89920
- inArray34(siteCrawlPages.nodeKey, nodeKeys)
90091
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
90092
+ eq86(siteCrawlPages.projectId, scope.projectId),
90093
+ eq86(siteCrawlPages.runId, scope.runId),
90094
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90095
+ inArray35(siteCrawlPages.nodeKey, nodeKeys)
89921
90096
  )).all();
89922
90097
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
89923
90098
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
89924
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and71(
89925
- eq85(siteCrawlEdges.projectId, scope.projectId),
89926
- eq85(siteCrawlEdges.runId, scope.runId),
89927
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
89928
- eq85(siteCrawlEdges.internal, true),
89929
- isNotNull8(siteCrawlEdges.targetNodeKey),
89930
- inArray34(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
89931
- inArray34(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
90099
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and72(
90100
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90101
+ eq86(siteCrawlEdges.runId, scope.runId),
90102
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90103
+ eq86(siteCrawlEdges.internal, true),
90104
+ isNotNull9(siteCrawlEdges.targetNodeKey),
90105
+ inArray35(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
90106
+ inArray35(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
89932
90107
  )).limit(1).get();
89933
90108
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
89934
90109
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -90021,23 +90196,23 @@ async function technicalAeoRoutes(app, opts) {
90021
90196
  let found = fromPage.nodeKey === toPage.nodeKey;
90022
90197
  let truncated = false;
90023
90198
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
90024
- const candidates = app.db.select().from(siteCrawlEdges).where(and71(
90025
- eq85(siteCrawlEdges.projectId, scope.projectId),
90026
- eq85(siteCrawlEdges.runId, scope.runId),
90027
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
90028
- eq85(siteCrawlEdges.internal, true),
90029
- eq85(siteCrawlEdges.followable, true),
90030
- eq85(siteCrawlEdges.relation, "anchor"),
90031
- isNotNull8(siteCrawlEdges.targetNodeKey),
90032
- inArray34(siteCrawlEdges.sourceNodeKey, frontier)
90199
+ const candidates = app.db.select().from(siteCrawlEdges).where(and72(
90200
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90201
+ eq86(siteCrawlEdges.runId, scope.runId),
90202
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90203
+ eq86(siteCrawlEdges.internal, true),
90204
+ eq86(siteCrawlEdges.followable, true),
90205
+ eq86(siteCrawlEdges.relation, "anchor"),
90206
+ isNotNull9(siteCrawlEdges.targetNodeKey),
90207
+ inArray35(siteCrawlEdges.sourceNodeKey, frontier)
90033
90208
  )).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
90034
90209
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
90035
90210
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
90036
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and71(
90037
- eq85(siteCrawlPages.projectId, scope.projectId),
90038
- eq85(siteCrawlPages.runId, scope.runId),
90039
- eq85(siteCrawlPages.attemptId, scope.attemptId),
90040
- inArray34(siteCrawlPages.nodeKey, candidateTargetKeys)
90211
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and72(
90212
+ eq86(siteCrawlPages.projectId, scope.projectId),
90213
+ eq86(siteCrawlPages.runId, scope.runId),
90214
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90215
+ inArray35(siteCrawlPages.nodeKey, candidateTargetKeys)
90041
90216
  )).all().map((row) => row.nodeKey));
90042
90217
  const next = /* @__PURE__ */ new Set();
90043
90218
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -90078,11 +90253,11 @@ async function technicalAeoRoutes(app, opts) {
90078
90253
  pathEdges.unshift(step.edge);
90079
90254
  pathKeys.unshift(step.nodeKey);
90080
90255
  }
90081
- const pathRows = app.db.select().from(siteCrawlPages).where(and71(
90082
- eq85(siteCrawlPages.projectId, scope.projectId),
90083
- eq85(siteCrawlPages.runId, scope.runId),
90084
- eq85(siteCrawlPages.attemptId, scope.attemptId),
90085
- inArray34(siteCrawlPages.nodeKey, pathKeys)
90256
+ const pathRows = app.db.select().from(siteCrawlPages).where(and72(
90257
+ eq86(siteCrawlPages.projectId, scope.projectId),
90258
+ eq86(siteCrawlPages.runId, scope.runId),
90259
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90260
+ inArray35(siteCrawlPages.nodeKey, pathKeys)
90086
90261
  )).all();
90087
90262
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
90088
90263
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -90110,25 +90285,25 @@ async function technicalAeoRoutes(app, opts) {
90110
90285
  throw validationError("change must be all, added, removed, or changed");
90111
90286
  }
90112
90287
  const completeSnapshotFilters = [
90113
- eq85(siteCrawlSnapshots.projectId, project.id),
90114
- eq85(runs.projectId, project.id),
90115
- eq85(runs.kind, RunKinds["site-audit"]),
90116
- eq85(runs.status, RunStatuses.completed),
90117
- eq85(siteCrawlSnapshots.complete, true),
90288
+ eq86(siteCrawlSnapshots.projectId, project.id),
90289
+ eq86(runs.projectId, project.id),
90290
+ eq86(runs.kind, RunKinds["site-audit"]),
90291
+ eq86(runs.status, RunStatuses.completed),
90292
+ eq86(siteCrawlSnapshots.complete, true),
90118
90293
  notProbeRun()
90119
90294
  ];
90120
- 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();
90295
+ 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();
90121
90296
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
90122
- 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(
90297
+ 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(
90123
90298
  ...completeSnapshotFilters,
90124
- or20(
90299
+ or21(
90125
90300
  lt13(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
90126
- and71(
90127
- eq85(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
90301
+ and72(
90302
+ eq86(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
90128
90303
  lt13(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
90129
90304
  )
90130
90305
  )
90131
- )).orderBy(desc39(siteCrawlSnapshots.createdAt), desc39(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
90306
+ )).orderBy(desc40(siteCrawlSnapshots.createdAt), desc40(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
90132
90307
  if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
90133
90308
  if (!afterTarget) {
90134
90309
  const latestCrawl = resolveCrawl(project.id);
@@ -90369,18 +90544,18 @@ async function technicalAeoRoutes(app, opts) {
90369
90544
  const visibleKeys = keyRows.slice(0, limit);
90370
90545
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
90371
90546
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
90372
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and71(
90373
- eq85(siteCrawlPages.projectId, scope.projectId),
90374
- eq85(siteCrawlPages.runId, scope.runId),
90375
- eq85(siteCrawlPages.attemptId, scope.attemptId),
90376
- inArray34(siteCrawlPages.nodeKey, pageKeys)
90547
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
90548
+ eq86(siteCrawlPages.projectId, scope.projectId),
90549
+ eq86(siteCrawlPages.runId, scope.runId),
90550
+ eq86(siteCrawlPages.attemptId, scope.attemptId),
90551
+ inArray35(siteCrawlPages.nodeKey, pageKeys)
90377
90552
  )).all();
90378
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and71(
90379
- eq85(siteCrawlEdges.projectId, scope.projectId),
90380
- eq85(siteCrawlEdges.runId, scope.runId),
90381
- eq85(siteCrawlEdges.attemptId, scope.attemptId),
90382
- eq85(siteCrawlEdges.internal, true),
90383
- inArray34(siteCrawlEdges.edgeKey, linkKeys)
90553
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and72(
90554
+ eq86(siteCrawlEdges.projectId, scope.projectId),
90555
+ eq86(siteCrawlEdges.runId, scope.runId),
90556
+ eq86(siteCrawlEdges.attemptId, scope.attemptId),
90557
+ eq86(siteCrawlEdges.internal, true),
90558
+ inArray35(siteCrawlEdges.edgeKey, linkKeys)
90384
90559
  )).all();
90385
90560
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
90386
90561
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -90533,16 +90708,16 @@ async function technicalAeoRoutes(app, opts) {
90533
90708
  }
90534
90709
  const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
90535
90710
  const filters = [
90536
- eq85(siteCrawlPages.projectId, project.id),
90537
- eq85(siteCrawlPages.runId, snapshot.runId),
90538
- eq85(siteCrawlPages.attemptId, snapshot.attemptId)
90711
+ eq86(siteCrawlPages.projectId, project.id),
90712
+ eq86(siteCrawlPages.runId, snapshot.runId),
90713
+ eq86(siteCrawlPages.attemptId, snapshot.attemptId)
90539
90714
  ];
90540
- if (request.query.nodeKey) filters.push(eq85(siteCrawlPages.nodeKey, request.query.nodeKey));
90715
+ if (request.query.nodeKey) filters.push(eq86(siteCrawlPages.nodeKey, request.query.nodeKey));
90541
90716
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
90542
- if (inventoryEligible != null) filters.push(eq85(siteCrawlPages.inventoryEligible, inventoryEligible));
90543
- if (request.query.fetchState) filters.push(eq85(siteCrawlPages.fetchState, request.query.fetchState));
90544
- if (request.query.indexabilityState) filters.push(eq85(siteCrawlPages.indexabilityState, request.query.indexabilityState));
90545
- if (request.query.auditState) filters.push(eq85(siteCrawlPages.auditState, request.query.auditState));
90717
+ if (inventoryEligible != null) filters.push(eq86(siteCrawlPages.inventoryEligible, inventoryEligible));
90718
+ if (request.query.fetchState) filters.push(eq86(siteCrawlPages.fetchState, request.query.fetchState));
90719
+ if (request.query.indexabilityState) filters.push(eq86(siteCrawlPages.indexabilityState, request.query.indexabilityState));
90720
+ if (request.query.auditState) filters.push(eq86(siteCrawlPages.auditState, request.query.auditState));
90546
90721
  const healthState = parseSiteHealthState(request.query.healthState);
90547
90722
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
90548
90723
  const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
@@ -90560,17 +90735,17 @@ async function technicalAeoRoutes(app, opts) {
90560
90735
  sort
90561
90736
  })).digest("base64url");
90562
90737
  const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
90563
- 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)];
90738
+ 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)];
90564
90739
  let healthStateFilter = null;
90565
90740
  if (healthState) {
90566
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and71(
90567
- eq85(siteCrawlPages.projectId, project.id),
90568
- eq85(siteCrawlPages.runId, snapshot.runId),
90569
- eq85(siteCrawlPages.attemptId, snapshot.attemptId),
90741
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and72(
90742
+ eq86(siteCrawlPages.projectId, project.id),
90743
+ eq86(siteCrawlPages.runId, snapshot.runId),
90744
+ eq86(siteCrawlPages.attemptId, snapshot.attemptId),
90570
90745
  isNull16(siteCrawlPages.healthState)
90571
90746
  )).limit(1).get();
90572
90747
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
90573
- if (healthStateFilter === "applied") filters.push(eq85(siteCrawlPages.healthState, healthState));
90748
+ if (healthStateFilter === "applied") filters.push(eq86(siteCrawlPages.healthState, healthState));
90574
90749
  }
90575
90750
  if (healthStateFilter === "unavailable-legacy-scan") {
90576
90751
  return {
@@ -90587,7 +90762,7 @@ async function technicalAeoRoutes(app, opts) {
90587
90762
  pages: []
90588
90763
  };
90589
90764
  }
90590
- const where = and71(...filters);
90765
+ const where = and72(...filters);
90591
90766
  const total = app.db.select({ value: count4() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
90592
90767
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
90593
90768
  const nextOffset = offset + rows.length;
@@ -90622,10 +90797,10 @@ async function technicalAeoRoutes(app, opts) {
90622
90797
  url: siteCrawlPages.url,
90623
90798
  inventoryEligible: siteCrawlPages.inventoryEligible,
90624
90799
  fetchState: siteCrawlPages.fetchState
90625
- }).from(siteCrawlPages).where(and71(
90626
- eq85(siteCrawlPages.projectId, project.id),
90627
- eq85(siteCrawlPages.runId, snapshot.runId),
90628
- eq85(siteCrawlPages.attemptId, snapshot.attemptId)
90800
+ }).from(siteCrawlPages).where(and72(
90801
+ eq86(siteCrawlPages.projectId, project.id),
90802
+ eq86(siteCrawlPages.runId, snapshot.runId),
90803
+ eq86(siteCrawlPages.attemptId, snapshot.attemptId)
90629
90804
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
90630
90805
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
90631
90806
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -90707,17 +90882,17 @@ async function technicalAeoRoutes(app, opts) {
90707
90882
  };
90708
90883
  }
90709
90884
  const filters = [
90710
- eq85(siteCrawlEdges.projectId, project.id),
90711
- eq85(siteCrawlEdges.runId, snapshot.runId),
90712
- eq85(siteCrawlEdges.attemptId, snapshot.attemptId),
90713
- eq85(siteCrawlEdges.internal, true),
90885
+ eq86(siteCrawlEdges.projectId, project.id),
90886
+ eq86(siteCrawlEdges.runId, snapshot.runId),
90887
+ eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
90888
+ eq86(siteCrawlEdges.internal, true),
90714
90889
  linkKindFilter(linkKind)
90715
90890
  ];
90716
- if (request.query.sourceUrl) filters.push(eq85(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
90717
- if (request.query.targetUrl) filters.push(eq85(siteCrawlEdges.targetUrl, request.query.targetUrl));
90891
+ if (request.query.sourceUrl) filters.push(eq86(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
90892
+ if (request.query.targetUrl) filters.push(eq86(siteCrawlEdges.targetUrl, request.query.targetUrl));
90718
90893
  const followable = parseBoolean2(request.query.followable);
90719
- if (followable != null) filters.push(eq85(siteCrawlEdges.followable, followable));
90720
- const where = and71(...filters);
90894
+ if (followable != null) filters.push(eq86(siteCrawlEdges.followable, followable));
90895
+ const where = and72(...filters);
90721
90896
  const total = app.db.select({ value: count4() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
90722
90897
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
90723
90898
  const offset = decodeCursor(request.query.cursor);
@@ -90778,17 +90953,17 @@ async function technicalAeoRoutes(app, opts) {
90778
90953
  };
90779
90954
  }
90780
90955
  const scope = [
90781
- eq85(siteCrawlEdges.projectId, project.id),
90782
- eq85(siteCrawlEdges.runId, snapshot.runId),
90783
- eq85(siteCrawlEdges.attemptId, snapshot.attemptId),
90784
- eq85(siteCrawlEdges.internal, true),
90956
+ eq86(siteCrawlEdges.projectId, project.id),
90957
+ eq86(siteCrawlEdges.runId, snapshot.runId),
90958
+ eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
90959
+ eq86(siteCrawlEdges.internal, true),
90785
90960
  linkKindFilter(linkKind)
90786
90961
  ];
90787
- 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);
90788
- 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);
90962
+ 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);
90963
+ 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);
90789
90964
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
90790
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and71(...scope, inboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90791
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and71(...scope, outboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90965
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, inboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90966
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, outboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
90792
90967
  return {
90793
90968
  project: project.name,
90794
90969
  hasCrawlData: true,
@@ -90822,11 +90997,11 @@ async function technicalAeoRoutes(app, opts) {
90822
90997
  if (!snapshot.attemptId) {
90823
90998
  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 };
90824
90999
  }
90825
- const where = and71(
90826
- eq85(siteCrawlFindings.projectId, project.id),
90827
- eq85(siteCrawlFindings.runId, snapshot.runId),
90828
- eq85(siteCrawlFindings.attemptId, snapshot.attemptId),
90829
- eq85(siteCrawlFindings.findingType, "dead-link"),
91000
+ const where = and72(
91001
+ eq86(siteCrawlFindings.projectId, project.id),
91002
+ eq86(siteCrawlFindings.runId, snapshot.runId),
91003
+ eq86(siteCrawlFindings.attemptId, snapshot.attemptId),
91004
+ eq86(siteCrawlFindings.findingType, "dead-link"),
90830
91005
  // Belt and braces on the client-facing surface: a row without a status
90831
91006
  // code is not evidence of a broken link, and this response is where such
90832
91007
  // a row would be read as one. Migration 140 removed every stored row that
@@ -90836,7 +91011,7 @@ async function technicalAeoRoutes(app, opts) {
90836
91011
  // a fabricated broken link shown to a client is not worth the saved
90837
91012
  // predicate. Applied to `total` as well as the rows, or the count would
90838
91013
  // promise a page the list cannot fill.
90839
- isNotNull8(sql30`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
91014
+ isNotNull9(sql30`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
90840
91015
  );
90841
91016
  const total = app.db.select({ value: count4() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
90842
91017
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
@@ -90876,19 +91051,19 @@ async function technicalAeoRoutes(app, opts) {
90876
91051
  createdAt: runs.createdAt,
90877
91052
  startedAt: runs.startedAt,
90878
91053
  finishedAt: runs.finishedAt
90879
- }).from(runs).where(and71(
90880
- eq85(runs.projectId, project.id),
90881
- eq85(runs.kind, RunKinds["site-audit"]),
91054
+ }).from(runs).where(and72(
91055
+ eq86(runs.projectId, project.id),
91056
+ eq86(runs.kind, RunKinds["site-audit"]),
90882
91057
  notProbeRun()
90883
- )).orderBy(desc39(runs.createdAt), desc39(runs.id)).limit(limit).all();
91058
+ )).orderBy(desc40(runs.createdAt), desc40(runs.id)).limit(limit).all();
90884
91059
  const runIds = rows.map((row) => row.runId);
90885
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq85(siteCrawlSnapshots.runId, runs.id)).where(and71(
90886
- eq85(siteCrawlSnapshots.projectId, project.id),
90887
- eq85(runs.projectId, project.id),
90888
- eq85(runs.kind, RunKinds["site-audit"]),
90889
- inArray34(runs.status, SURFACEABLE_STATUSES),
91060
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(
91061
+ eq86(siteCrawlSnapshots.projectId, project.id),
91062
+ eq86(runs.projectId, project.id),
91063
+ eq86(runs.kind, RunKinds["site-audit"]),
91064
+ inArray35(runs.status, SURFACEABLE_STATUSES),
90890
91065
  notProbeRun(),
90891
- inArray34(siteCrawlSnapshots.runId, runIds)
91066
+ inArray35(siteCrawlSnapshots.runId, runIds)
90892
91067
  )).all().map((row) => row.runId));
90893
91068
  return {
90894
91069
  project: project.name,
@@ -90902,21 +91077,21 @@ async function technicalAeoRoutes(app, opts) {
90902
91077
  });
90903
91078
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
90904
91079
  const project = resolveProject(app.db, request.params.name);
90905
- const run = app.db.select().from(runs).where(and71(
90906
- eq85(runs.id, request.params.runId),
90907
- eq85(runs.projectId, project.id),
90908
- eq85(runs.kind, RunKinds["site-audit"]),
91080
+ const run = app.db.select().from(runs).where(and72(
91081
+ eq86(runs.id, request.params.runId),
91082
+ eq86(runs.projectId, project.id),
91083
+ eq86(runs.kind, RunKinds["site-audit"]),
90909
91084
  notProbeRun()
90910
91085
  )).get();
90911
91086
  if (!run) throw notFound("Site audit run", request.params.runId);
90912
- const attempt = app.db.select().from(siteCrawlAttempts).where(and71(
90913
- eq85(siteCrawlAttempts.projectId, project.id),
90914
- eq85(siteCrawlAttempts.runId, run.id)
90915
- )).orderBy(desc39(siteCrawlAttempts.attemptNumber), desc39(siteCrawlAttempts.updatedAt)).limit(1).get();
90916
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and71(
90917
- eq85(siteCrawlGraphLayouts.projectId, project.id),
90918
- eq85(siteCrawlGraphLayouts.runId, run.id),
90919
- eq85(siteCrawlGraphLayouts.attemptId, attempt.id)
91087
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and72(
91088
+ eq86(siteCrawlAttempts.projectId, project.id),
91089
+ eq86(siteCrawlAttempts.runId, run.id)
91090
+ )).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
91091
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and72(
91092
+ eq86(siteCrawlGraphLayouts.projectId, project.id),
91093
+ eq86(siteCrawlGraphLayouts.runId, run.id),
91094
+ eq86(siteCrawlGraphLayouts.attemptId, attempt.id)
90920
91095
  )).get() : void 0;
90921
91096
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
90922
91097
  return {
@@ -90947,19 +91122,19 @@ async function technicalAeoRoutes(app, opts) {
90947
91122
  };
90948
91123
  });
90949
91124
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
90950
- const project = tx.select().from(projects).where(eq85(projects.name, request.params.name)).get();
91125
+ const project = tx.select().from(projects).where(eq86(projects.name, request.params.name)).get();
90951
91126
  if (!project) throw notFound("Project", request.params.name);
90952
- const run = tx.select().from(runs).where(and71(
90953
- eq85(runs.id, request.params.runId),
90954
- eq85(runs.projectId, project.id),
90955
- eq85(runs.kind, RunKinds["site-audit"]),
91127
+ const run = tx.select().from(runs).where(and72(
91128
+ eq86(runs.id, request.params.runId),
91129
+ eq86(runs.projectId, project.id),
91130
+ eq86(runs.kind, RunKinds["site-audit"]),
90956
91131
  notProbeRun()
90957
91132
  )).get();
90958
91133
  if (!run) throw notFound("Site audit run", request.params.runId);
90959
- const attempt = tx.select().from(siteCrawlAttempts).where(and71(
90960
- eq85(siteCrawlAttempts.projectId, project.id),
90961
- eq85(siteCrawlAttempts.runId, run.id)
90962
- )).orderBy(desc39(siteCrawlAttempts.attemptNumber), desc39(siteCrawlAttempts.updatedAt)).limit(1).get();
91134
+ const attempt = tx.select().from(siteCrawlAttempts).where(and72(
91135
+ eq86(siteCrawlAttempts.projectId, project.id),
91136
+ eq86(siteCrawlAttempts.runId, run.id)
91137
+ )).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
90963
91138
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
90964
91139
  const base = {
90965
91140
  project: project.name,
@@ -90972,11 +91147,11 @@ async function technicalAeoRoutes(app, opts) {
90972
91147
  if (!attempt) {
90973
91148
  return { ...base, pagesAudited: 0, examples: [] };
90974
91149
  }
90975
- const auditedWhere = and71(
90976
- eq85(siteCrawlPages.projectId, project.id),
90977
- eq85(siteCrawlPages.runId, run.id),
90978
- eq85(siteCrawlPages.attemptId, attempt.id),
90979
- eq85(siteCrawlPages.auditState, "success")
91150
+ const auditedWhere = and72(
91151
+ eq86(siteCrawlPages.projectId, project.id),
91152
+ eq86(siteCrawlPages.runId, run.id),
91153
+ eq86(siteCrawlPages.attemptId, attempt.id),
91154
+ eq86(siteCrawlPages.auditState, "success")
90980
91155
  );
90981
91156
  const pagesAudited = tx.select({ value: count4() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
90982
91157
  if (state !== "collecting") {
@@ -90987,9 +91162,9 @@ async function technicalAeoRoutes(app, opts) {
90987
91162
  url: siteCrawlPages.url,
90988
91163
  auditScore: siteCrawlPages.auditScore,
90989
91164
  auditFields: siteCrawlPages.auditFields
90990
- }).from(siteCrawlPages).where(and71(
91165
+ }).from(siteCrawlPages).where(and72(
90991
91166
  auditedWhere,
90992
- isNotNull8(siteCrawlPages.auditScore),
91167
+ isNotNull9(siteCrawlPages.auditScore),
90993
91168
  lt13(siteCrawlPages.auditScore, 70)
90994
91169
  )).orderBy(asc16(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
90995
91170
  const examples = candidates.flatMap((candidate) => {
@@ -91023,13 +91198,13 @@ async function technicalAeoRoutes(app, opts) {
91023
91198
  status: runs.status,
91024
91199
  identityKey: siteCrawlRunRequests.identityKey,
91025
91200
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
91026
- }).from(runs).leftJoin(siteCrawlRunRequests, and71(
91027
- eq85(siteCrawlRunRequests.projectId, runs.projectId),
91028
- eq85(siteCrawlRunRequests.runId, runs.id)
91029
- )).where(and71(
91030
- eq85(runs.projectId, project.id),
91031
- eq85(runs.kind, RunKinds["site-audit"]),
91032
- inArray34(runs.status, [RunStatuses.queued, RunStatuses.running])
91201
+ }).from(runs).leftJoin(siteCrawlRunRequests, and72(
91202
+ eq86(siteCrawlRunRequests.projectId, runs.projectId),
91203
+ eq86(siteCrawlRunRequests.runId, runs.id)
91204
+ )).where(and72(
91205
+ eq86(runs.projectId, project.id),
91206
+ eq86(runs.kind, RunKinds["site-audit"]),
91207
+ inArray35(runs.status, [RunStatuses.queued, RunStatuses.running])
91033
91208
  )).get();
91034
91209
  if (existing) {
91035
91210
  if (existing.identityKey === identityKey) {
@@ -91083,7 +91258,7 @@ async function technicalAeoRoutes(app, opts) {
91083
91258
  // ../api-routes/src/research.ts
91084
91259
  import crypto53 from "crypto";
91085
91260
  import { z as z5 } from "zod";
91086
- 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";
91261
+ 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";
91087
91262
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
91088
91263
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
91089
91264
  async function researchRoutes(app, opts) {
@@ -91101,7 +91276,7 @@ async function researchRoutes(app, opts) {
91101
91276
  const input = parsed.data;
91102
91277
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
91103
91278
  const requestHash2 = directResearchRequestHash(input);
91104
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and72(eq86(researchRuns.projectId, project.id), eq86(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
91279
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
91105
91280
  if (existingReceipt) {
91106
91281
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
91107
91282
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -91133,7 +91308,7 @@ async function researchRoutes(app, opts) {
91133
91308
  const initiatedBy = researchPrincipal(request);
91134
91309
  const decision = app.db.transaction((tx) => {
91135
91310
  if (input.idempotencyKey) {
91136
- const existing = tx.select().from(researchRuns).where(and72(eq86(researchRuns.projectId, project.id), eq86(researchRuns.idempotencyKey, input.idempotencyKey))).get();
91311
+ const existing = tx.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get();
91137
91312
  if (existing) {
91138
91313
  assertSameDirectResearchRequest(existing, input, requestHash2);
91139
91314
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -91141,8 +91316,8 @@ async function researchRoutes(app, opts) {
91141
91316
  }
91142
91317
  if (initiatedBy?.limited) {
91143
91318
  const { start, end, date } = utcDayBounds(now);
91144
- const used = tx.select({ value: count5() }).from(researchRuns).where(and72(
91145
- eq86(researchRuns.projectId, project.id),
91319
+ const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
91320
+ eq87(researchRuns.projectId, project.id),
91146
91321
  gte23(researchRuns.createdAt, start),
91147
91322
  lt14(researchRuns.createdAt, end),
91148
91323
  sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -91173,15 +91348,15 @@ async function researchRoutes(app, opts) {
91173
91348
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
91174
91349
  const requestHash2 = researchBatchRequestHash(input);
91175
91350
  const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
91176
- const firstReceipt = app.db.select().from(researchRuns).where(and72(
91177
- eq86(researchRuns.projectId, project.id),
91178
- eq86(researchRuns.idempotencyKey, receiptKeys[0])
91351
+ const firstReceipt = app.db.select().from(researchRuns).where(and73(
91352
+ eq87(researchRuns.projectId, project.id),
91353
+ eq87(researchRuns.idempotencyKey, receiptKeys[0])
91179
91354
  )).get();
91180
91355
  if (firstReceipt) {
91181
91356
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
91182
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and72(
91183
- eq86(researchRuns.projectId, project.id),
91184
- eq86(researchRuns.idempotencyKey, key)
91357
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and73(
91358
+ eq87(researchRuns.projectId, project.id),
91359
+ eq87(researchRuns.idempotencyKey, key)
91185
91360
  )).get());
91186
91361
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
91187
91362
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -91196,8 +91371,8 @@ async function researchRoutes(app, opts) {
91196
91371
  const decision = app.db.transaction((tx) => {
91197
91372
  if (initiatedBy?.limited) {
91198
91373
  const { start, end, date } = utcDayBounds(now);
91199
- const used = tx.select({ value: count5() }).from(researchRuns).where(and72(
91200
- eq86(researchRuns.projectId, project.id),
91374
+ const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
91375
+ eq87(researchRuns.projectId, project.id),
91201
91376
  gte23(researchRuns.createdAt, start),
91202
91377
  lt14(researchRuns.createdAt, end),
91203
91378
  sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -91251,10 +91426,10 @@ async function researchRoutes(app, opts) {
91251
91426
  const requested = Number.parseInt(request.query.limit ?? "", 10);
91252
91427
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
91253
91428
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
91254
- const rows = app.db.select().from(researchRuns).where(and72(
91255
- eq86(researchRuns.projectId, project.id),
91256
- cursor ? or21(lt14(researchRuns.createdAt, cursor.createdAt), and72(eq86(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
91257
- )).orderBy(desc40(researchRuns.createdAt), desc40(researchRuns.id)).limit(limit + 1).all();
91429
+ const rows = app.db.select().from(researchRuns).where(and73(
91430
+ eq87(researchRuns.projectId, project.id),
91431
+ cursor ? or22(lt14(researchRuns.createdAt, cursor.createdAt), and73(eq87(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
91432
+ )).orderBy(desc41(researchRuns.createdAt), desc41(researchRuns.id)).limit(limit + 1).all();
91258
91433
  const runs2 = rows.slice(0, limit).map(serializeRun);
91259
91434
  const last = runs2.at(-1);
91260
91435
  const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
@@ -91380,9 +91555,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
91380
91555
  return stored;
91381
91556
  }
91382
91557
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
91383
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and72(
91384
- eq86(measurementQueryTemplates.projectId, projectId),
91385
- eq86(measurementQueryTemplates.id, selection.templateId)
91558
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and73(
91559
+ eq87(measurementQueryTemplates.projectId, projectId),
91560
+ eq87(measurementQueryTemplates.id, selection.templateId)
91386
91561
  )).get();
91387
91562
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
91388
91563
  const templateVersion = savedTemplate.updatedAt;
@@ -91391,7 +91566,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
91391
91566
  }
91392
91567
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
91393
91568
  if (bindingLocation) {
91394
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq86(projects.id, projectId)).get();
91569
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq87(projects.id, projectId)).get();
91395
91570
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
91396
91571
  }
91397
91572
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -91404,9 +91579,9 @@ function classifyResearchQueries(project, plan, queries2) {
91404
91579
  return queries2.map((query) => classifier?.classify(query) ?? null);
91405
91580
  }
91406
91581
  function getDetail(app, projectId, id) {
91407
- const row = app.db.select().from(researchRuns).where(and72(eq86(researchRuns.id, id), eq86(researchRuns.projectId, projectId))).get();
91582
+ const row = app.db.select().from(researchRuns).where(and73(eq87(researchRuns.id, id), eq87(researchRuns.projectId, projectId))).get();
91408
91583
  if (!row) throw notFound("Research run", id);
91409
- const queries2 = app.db.select().from(researchRunQueries).where(eq86(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
91584
+ const queries2 = app.db.select().from(researchRunQueries).where(eq87(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
91410
91585
  return { ...serializeRun(row), queries: queries2 };
91411
91586
  }
91412
91587
  function serializeRun(row) {
@@ -91436,7 +91611,7 @@ function utcDayBounds(now) {
91436
91611
 
91437
91612
  // ../api-routes/src/snapshot-competitor-refresh.ts
91438
91613
  import { setImmediate as yieldToEventLoop2 } from "timers/promises";
91439
- import { and as and73, asc as asc17, eq as eq87, inArray as inArray35 } from "drizzle-orm";
91614
+ import { and as and74, asc as asc17, eq as eq88, inArray as inArray36 } from "drizzle-orm";
91440
91615
  var ANSWER_MENTIONS_CHUNK_SIZE = 50;
91441
91616
  var ANSWER_MENTIONS_MAX_RESTARTS = 3;
91442
91617
  function backfillProjectAnswerMentions(db, projectId, opts) {
@@ -91486,14 +91661,14 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
91486
91661
  examined = 0;
91487
91662
  wouldUpdate = 0;
91488
91663
  mentioned = 0;
91489
- const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and73(
91490
- eq87(runs.kind, RunKinds["answer-visibility"]),
91491
- eq87(runs.projectId, projectId),
91492
- opts?.runId === void 0 ? void 0 : eq87(runs.id, opts.runId)
91664
+ const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and74(
91665
+ eq88(runs.kind, RunKinds["answer-visibility"]),
91666
+ eq88(runs.projectId, projectId),
91667
+ opts?.runId === void 0 ? void 0 : eq88(runs.id, opts.runId)
91493
91668
  )).all();
91494
91669
  const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
91495
91670
  for (const run of runRows) {
91496
- const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq87(querySnapshots.runId, run.id)).orderBy(asc17(querySnapshots.id)).all().map((row) => row.id);
91671
+ const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq88(querySnapshots.runId, run.id)).orderBy(asc17(querySnapshots.id)).all().map((row) => row.id);
91497
91672
  for (let offset = 0; offset < snapshotIds.length; offset += ANSWER_MENTIONS_CHUNK_SIZE) {
91498
91673
  if (chunks++ > 0 && driver.recheckIdentity) {
91499
91674
  yield;
@@ -91519,7 +91694,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
91519
91694
  competitorOverlap: querySnapshots.competitorOverlap,
91520
91695
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
91521
91696
  rawResponse: querySnapshots.rawResponse
91522
- }).from(querySnapshots).where(inArray35(querySnapshots.id, snapshotIds.slice(offset, offset + ANSWER_MENTIONS_CHUNK_SIZE))).all();
91697
+ }).from(querySnapshots).where(inArray36(querySnapshots.id, snapshotIds.slice(offset, offset + ANSWER_MENTIONS_CHUNK_SIZE))).all();
91523
91698
  const pendingUpdates = [];
91524
91699
  for (const snapshot of snapshotRows) {
91525
91700
  if (competitorFieldsOnly && snapshot.answerText == null) continue;
@@ -91569,7 +91744,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
91569
91744
  } else {
91570
91745
  db.transaction((tx) => {
91571
91746
  for (const update of pendingUpdates) {
91572
- tx.update(querySnapshots).set(update.patch).where(eq87(querySnapshots.id, update.id)).run();
91747
+ tx.update(querySnapshots).set(update.patch).where(eq88(querySnapshots.id, update.id)).run();
91573
91748
  }
91574
91749
  });
91575
91750
  updated += pendingUpdates.length;
@@ -91599,7 +91774,7 @@ function readStoredGroundingSources(rawResponse) {
91599
91774
 
91600
91775
  // ../api-routes/src/simple-measurement-definitions.ts
91601
91776
  import crypto54 from "crypto";
91602
- import { and as and74, eq as eq88 } from "drizzle-orm";
91777
+ import { and as and75, eq as eq89 } from "drizzle-orm";
91603
91778
  function captureSimpleMeasurementDefinition(db, input) {
91604
91779
  return db.transaction((tx) => {
91605
91780
  const run = tx.select({
@@ -91607,7 +91782,7 @@ function captureSimpleMeasurementDefinition(db, input) {
91607
91782
  trigger: runs.trigger,
91608
91783
  status: runs.status,
91609
91784
  measurementPlanVersionId: runs.measurementPlanVersionId
91610
- }).from(runs).where(and74(eq88(runs.projectId, input.projectId), eq88(runs.id, input.runId))).get();
91785
+ }).from(runs).where(and75(eq89(runs.projectId, input.projectId), eq89(runs.id, input.runId))).get();
91611
91786
  if (!run) throw notFound("Run", input.runId);
91612
91787
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
91613
91788
  return null;
@@ -91628,7 +91803,7 @@ function captureSimpleMeasurementDefinition(db, input) {
91628
91803
  throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
91629
91804
  }
91630
91805
  }
91631
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and74(eq88(simpleMeasurementDefinitions.projectId, input.projectId), eq88(simpleMeasurementDefinitions.runId, input.runId))).get();
91806
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and75(eq89(simpleMeasurementDefinitions.projectId, input.projectId), eq89(simpleMeasurementDefinitions.runId, input.runId))).get();
91632
91807
  if (existing) {
91633
91808
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
91634
91809
  const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition.identity;
@@ -91654,15 +91829,15 @@ function captureSimpleMeasurementDefinition(db, input) {
91654
91829
  if (run.status !== RunStatuses.running) {
91655
91830
  throw validationError("Only a running run can capture a new measurement definition.");
91656
91831
  }
91657
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq88(querySnapshots.runId, input.runId)).limit(1).get()) {
91832
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq89(querySnapshots.runId, input.runId)).limit(1).get()) {
91658
91833
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
91659
91834
  }
91660
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq88(queries.projectId, input.projectId)).all().map((query) => query.id));
91835
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq89(queries.projectId, input.projectId)).all().map((query) => query.id));
91661
91836
  if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
91662
91837
  throw validationError("Every captured query must belong to the run project.");
91663
91838
  }
91664
91839
  if (definition.competitors !== void 0) {
91665
- 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));
91840
+ 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));
91666
91841
  const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
91667
91842
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
91668
91843
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
@@ -92530,10 +92705,10 @@ var log = createLogger("IntelligenceService");
92530
92705
  var runChronologyKey = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
92531
92706
  function analyzableRunPredicates(projectId) {
92532
92707
  return [
92533
- eq89(runs.projectId, projectId),
92708
+ eq90(runs.projectId, projectId),
92534
92709
  // Only this kind writes query_snapshots.
92535
- eq89(runs.kind, RunKinds["answer-visibility"]),
92536
- or22(eq89(runs.status, RunStatuses.completed), eq89(runs.status, RunStatuses.partial)),
92710
+ eq90(runs.kind, RunKinds["answer-visibility"]),
92711
+ or23(eq90(runs.status, RunStatuses.completed), eq90(runs.status, RunStatuses.partial)),
92537
92712
  // Defensive: RunCoordinator already skips probes before analyzeAndPersist
92538
92713
  // is called, but a future call site invoking it directly for a probe must
92539
92714
  // still not pollute the intelligence window.
@@ -92578,16 +92753,16 @@ var IntelligenceService = class {
92578
92753
  * Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
92579
92754
  */
92580
92755
  analyzeAndPersist(runId, projectId) {
92581
- const currentRunRecord = this.db.select().from(runs).where(and75(eq89(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
92756
+ const currentRunRecord = this.db.select().from(runs).where(and76(eq90(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
92582
92757
  if (!currentRunRecord) {
92583
92758
  log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
92584
92759
  return null;
92585
92760
  }
92586
92761
  const currentLocation = currentRunRecord.location ?? null;
92587
92762
  const recentRuns = this.db.select().from(runs).where(
92588
- and75(
92763
+ and76(
92589
92764
  ...analyzableRunPredicates(projectId),
92590
- currentLocation === null ? isNull17(runs.location) : eq89(runs.location, currentLocation),
92765
+ currentLocation === null ? isNull17(runs.location) : eq90(runs.location, currentLocation),
92591
92766
  // Never look forward. The window is this run's own past, so
92592
92767
  // re-analyzing a historical run compares it against its true
92593
92768
  // predecessor rather than against sweeps that came after it.
@@ -92595,10 +92770,10 @@ var IntelligenceService = class {
92595
92770
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
92596
92771
  // Index seek on idx_snapshots_run, short-circuited on the first row.
92597
92772
  exists2(
92598
- this.db.select({ one: sql32`1` }).from(querySnapshots).where(eq89(querySnapshots.runId, runs.id))
92773
+ this.db.select({ one: sql32`1` }).from(querySnapshots).where(eq90(querySnapshots.runId, runs.id))
92599
92774
  )
92600
92775
  )
92601
- ).orderBy(desc41(runs.finishedAt), desc41(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
92776
+ ).orderBy(desc42(runs.finishedAt), desc42(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
92602
92777
  const trackedCompetitors = this.loadTrackedCompetitors(projectId);
92603
92778
  const trackedCompetitorDomains = trackedCompetitors.map((competitor) => competitor.domain);
92604
92779
  const currentRun = this.buildRunData(
@@ -92671,7 +92846,7 @@ var IntelligenceService = class {
92671
92846
  * Returns the persisted insights so the coordinator can count critical/high.
92672
92847
  */
92673
92848
  analyzeAndPersistGbp(runId, projectId) {
92674
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq89(runs.id, runId)).get();
92849
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq90(runs.id, runId)).get();
92675
92850
  if (!runRow) {
92676
92851
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
92677
92852
  this.persistGbpInsights(runId, projectId, [], []);
@@ -92679,9 +92854,9 @@ var IntelligenceService = class {
92679
92854
  }
92680
92855
  const windowStart = runRow.startedAt ?? runRow.createdAt;
92681
92856
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
92682
- const selected = this.db.select().from(gbpLocations).where(and75(
92683
- eq89(gbpLocations.projectId, projectId),
92684
- eq89(gbpLocations.selected, true),
92857
+ const selected = this.db.select().from(gbpLocations).where(and76(
92858
+ eq90(gbpLocations.projectId, projectId),
92859
+ eq90(gbpLocations.selected, true),
92685
92860
  gte24(gbpLocations.syncedAt, windowStart),
92686
92861
  lte20(gbpLocations.syncedAt, windowEnd)
92687
92862
  )).all();
@@ -92716,12 +92891,12 @@ var IntelligenceService = class {
92716
92891
  }
92717
92892
  /** Build the per-location signal bundle the GBP analyzer consumes. */
92718
92893
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
92719
- 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();
92720
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and75(eq89(gbpPlaceActions.projectId, projectId), eq89(gbpPlaceActions.locationName, locationName))).all();
92721
- 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();
92722
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and75(eq89(gbpLocations.projectId, projectId), eq89(gbpLocations.locationName, locationName))).get();
92894
+ 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();
92895
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and76(eq90(gbpPlaceActions.projectId, projectId), eq90(gbpPlaceActions.locationName, locationName))).all();
92896
+ 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();
92897
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and76(eq90(gbpLocations.projectId, projectId), eq90(gbpLocations.locationName, locationName))).get();
92723
92898
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
92724
- 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();
92899
+ 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();
92725
92900
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
92726
92901
  const summary3 = buildGbpSummary({
92727
92902
  locationName,
@@ -92757,7 +92932,7 @@ var IntelligenceService = class {
92757
92932
  /** Build the month-over-month keyword series for a location from the
92758
92933
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
92759
92934
  buildGbpKeywordTrend(projectId, locationName) {
92760
- 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();
92935
+ 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();
92761
92936
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
92762
92937
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
92763
92938
  const recentMonth = months[0] ?? null;
@@ -92788,7 +92963,7 @@ var IntelligenceService = class {
92788
92963
  */
92789
92964
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
92790
92965
  const covered = new Set(coveredLocationNames);
92791
- 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();
92966
+ 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();
92792
92967
  const staleIds = [];
92793
92968
  const dismissedSlots = /* @__PURE__ */ new Set();
92794
92969
  for (const row of existing) {
@@ -92799,7 +92974,7 @@ var IntelligenceService = class {
92799
92974
  }
92800
92975
  this.db.transaction((tx) => {
92801
92976
  for (const id of staleIds) {
92802
- tx.delete(insights).where(eq89(insights.id, id)).run();
92977
+ tx.delete(insights).where(eq90(insights.id, id)).run();
92803
92978
  }
92804
92979
  for (const insight of gbpInsights) {
92805
92980
  const parsed = parseGbpInsightId(insight.id);
@@ -92886,7 +93061,7 @@ var IntelligenceService = class {
92886
93061
  * create per run + aggregate). DB is left untouched.
92887
93062
  */
92888
93063
  backfill(projectName, opts, onProgress) {
92889
- const project = this.db.select().from(projects).where(eq89(projects.name, projectName)).get();
93064
+ const project = this.db.select().from(projects).where(eq90(projects.name, projectName)).get();
92890
93065
  if (!project) {
92891
93066
  throw new Error(`Project "${projectName}" not found`);
92892
93067
  }
@@ -92898,7 +93073,7 @@ var IntelligenceService = class {
92898
93073
  }
92899
93074
  sinceTimestamp = parsed;
92900
93075
  }
92901
- const allRuns = this.db.select().from(runs).where(and75(...analyzableRunPredicates(project.id))).orderBy(asc18(runs.finishedAt)).all();
93076
+ const allRuns = this.db.select().from(runs).where(and76(...analyzableRunPredicates(project.id))).orderBy(asc18(runs.finishedAt)).all();
92902
93077
  const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
92903
93078
  const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
92904
93079
  let startIdx = 0;
@@ -92929,7 +93104,7 @@ var IntelligenceService = class {
92929
93104
  let wouldDeleteTotal = 0;
92930
93105
  const existingByRunId = /* @__PURE__ */ new Map();
92931
93106
  if (isDryRun && targetRuns.length > 0) {
92932
- const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray36(insights.runId, targetRuns.map((r) => r.id))).all();
93107
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray37(insights.runId, targetRuns.map((r) => r.id))).all();
92933
93108
  for (const r of rows) {
92934
93109
  if (r.runId == null) continue;
92935
93110
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -92980,7 +93155,7 @@ var IntelligenceService = class {
92980
93155
  * signals match the same identities the dashboard and reports do.
92981
93156
  */
92982
93157
  loadTrackedCompetitors(projectId) {
92983
- return this.db.select(competitorIdentityColumns).from(competitors).where(eq89(competitors.projectId, projectId)).orderBy(competitors.domain).all();
93158
+ return this.db.select(competitorIdentityColumns).from(competitors).where(eq90(competitors.projectId, projectId)).orderBy(competitors.domain).all();
92984
93159
  }
92985
93160
  /**
92986
93161
  * Which of `runIds` actually wrote query snapshots. One grouped read, so
@@ -92989,7 +93164,7 @@ var IntelligenceService = class {
92989
93164
  */
92990
93165
  runIdsWithSnapshots(runIds) {
92991
93166
  if (runIds.length === 0) return /* @__PURE__ */ new Set();
92992
- const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray36(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
93167
+ const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray37(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
92993
93168
  return new Set(rows.map((r) => r.runId));
92994
93169
  }
92995
93170
  /**
@@ -93011,15 +93186,15 @@ var IntelligenceService = class {
93011
93186
  }
93012
93187
  persistResult(result, runId, projectId) {
93013
93188
  const previouslyDismissed = /* @__PURE__ */ new Set();
93014
- 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();
93189
+ 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();
93015
93190
  for (const row of existingInsights) {
93016
93191
  if (row.dismissed) {
93017
93192
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
93018
93193
  }
93019
93194
  }
93020
93195
  this.db.transaction((tx) => {
93021
- tx.delete(insights).where(eq89(insights.runId, runId)).run();
93022
- tx.delete(healthSnapshots).where(eq89(healthSnapshots.runId, runId)).run();
93196
+ tx.delete(insights).where(eq90(insights.runId, runId)).run();
93197
+ tx.delete(healthSnapshots).where(eq90(healthSnapshots.runId, runId)).run();
93023
93198
  const now = (/* @__PURE__ */ new Date()).toISOString();
93024
93199
  for (const insight of result.insights) {
93025
93200
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -93066,8 +93241,8 @@ var IntelligenceService = class {
93066
93241
  clicks: gscSearchData.clicks,
93067
93242
  impressions: gscSearchData.impressions,
93068
93243
  position: gscSearchData.position
93069
- }).from(gscSearchData).where(and75(
93070
- eq89(gscSearchData.projectId, projectId),
93244
+ }).from(gscSearchData).where(and76(
93245
+ eq90(gscSearchData.projectId, projectId),
93071
93246
  gte24(gscSearchData.date, window.startDate),
93072
93247
  lte20(gscSearchData.date, window.endDate)
93073
93248
  )).all();
@@ -93120,21 +93295,21 @@ var IntelligenceService = class {
93120
93295
  const key = row.query.toLowerCase();
93121
93296
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
93122
93297
  }
93123
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq89(projects.id, projectId)).get();
93298
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq90(projects.id, projectId)).get();
93124
93299
  const locationCount = Math.max(
93125
93300
  1,
93126
93301
  (projectRow?.locations ?? []).length
93127
93302
  );
93128
93303
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
93129
93304
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
93130
- and75(
93131
- eq89(runs.projectId, projectId),
93132
- eq89(runs.kind, RunKinds["answer-visibility"]),
93133
- or22(eq89(runs.status, "completed"), eq89(runs.status, "partial")),
93305
+ and76(
93306
+ eq90(runs.projectId, projectId),
93307
+ eq90(runs.kind, RunKinds["answer-visibility"]),
93308
+ or23(eq90(runs.status, "completed"), eq90(runs.status, "partial")),
93134
93309
  // Defensive — see top of file.
93135
93310
  ne16(runs.trigger, RunTriggers.probe)
93136
93311
  )
93137
- ).orderBy(desc41(runs.createdAt), desc41(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
93312
+ ).orderBy(desc42(runs.createdAt), desc42(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
93138
93313
  const recentGroups = groupRunsByCreatedAt(recentRunRows);
93139
93314
  const recentRunIds = [];
93140
93315
  const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
@@ -93150,7 +93325,7 @@ var IntelligenceService = class {
93150
93325
  const haveHistory = recentRunIds.length > 0;
93151
93326
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
93152
93327
  if (haveHistory) {
93153
- 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();
93328
+ 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();
93154
93329
  const regressionGroups = /* @__PURE__ */ new Map();
93155
93330
  for (const row of priorRows) {
93156
93331
  if (!row.runId) continue;
@@ -93179,7 +93354,7 @@ var IntelligenceService = class {
93179
93354
  });
93180
93355
  }
93181
93356
  buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
93182
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq89(projects.id, projectId)).get();
93357
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq90(projects.id, projectId)).get();
93183
93358
  const projectDomains = projectDomainRow ? effectiveDomains({
93184
93359
  canonicalDomain: projectDomainRow.canonicalDomain,
93185
93360
  ownedDomains: projectDomainRow.ownedDomains
@@ -93197,7 +93372,7 @@ var IntelligenceService = class {
93197
93372
  citedDomains: querySnapshots.citedDomains,
93198
93373
  rawResponse: querySnapshots.rawResponse,
93199
93374
  snapshotLocation: querySnapshots.location
93200
- }).from(querySnapshots).leftJoin(queries, eq89(querySnapshots.queryId, queries.id)).where(eq89(querySnapshots.runId, runId)).all();
93375
+ }).from(querySnapshots).leftJoin(queries, eq90(querySnapshots.queryId, queries.id)).where(eq90(querySnapshots.runId, runId)).all();
93201
93376
  const snapshots = [];
93202
93377
  let orphanCount = 0;
93203
93378
  for (const r of rows) {