@canonry/canonry 7.61.2 → 7.62.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 (131) hide show
  1. package/assets/agent-workspace/skills/aero/SKILL.md +2 -2
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +8 -4
  3. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +14 -3
  4. package/assets/assets/{AgentSetupPage-CCMNQwRE.js → AgentSetupPage-DtPnVaeF.js} +1 -1
  5. package/assets/assets/{AuditHistoryPanel-B8mIRI3I.js → AuditHistoryPanel-Ce_oy4OV.js} +1 -1
  6. package/assets/assets/{BacklinksPage-BNLyIR97.js → BacklinksPage-CrqPWRd8.js} +1 -1
  7. package/assets/assets/{HistoryPage-DSr9KE16.js → HistoryPage-5v0JM7DN.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-BTg-SFW7.js → MeasurementPropertyPage-Bm5Mrd5a.js} +1 -1
  9. package/assets/assets/{ProjectPage-SqE0fkEq.js → ProjectPage-DaWUh6iw.js} +1 -1
  10. package/assets/assets/{RunsPage-gJR17ho1.js → RunsPage-CfdAR2v2.js} +1 -1
  11. package/assets/assets/{SettingsPage-DY5f_oN4.js → SettingsPage-BFooA7JG.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-BYx91hP-.js → SiteHealthSection-CerUQgAp.js} +2 -2
  13. package/assets/assets/{ToneBadge-C3S7aVf3.js → ToneBadge-DKvZcONW.js} +1 -1
  14. package/assets/assets/{TrafficPage-CCXH5nhh.js → TrafficPage-C5fhZGJ2.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-DTjCsu4-.js → TrafficSourceDetailPage-BoqARHYz.js} +1 -1
  16. package/assets/assets/{account-context-DozTQM44.js → account-context-D_CDYrs8.js} +1 -1
  17. package/assets/assets/{card-D8MU7RYV.js → card-C6z0XyOo.js} +1 -1
  18. package/assets/assets/{extract-error-message-CsVYPTyt.js → extract-error-message-By_wcU8g.js} +1 -1
  19. package/assets/assets/{format-helpers-CWBooMJJ.js → format-helpers-D3Ubm8vk.js} +1 -1
  20. package/assets/assets/{index-D5o-caTl.js → index-DSVFZqv6.js} +4 -4
  21. package/assets/assets/{query-client-DbxlHfUm.js → query-client-BETh6SO9.js} +1 -1
  22. package/assets/assets/{react-query-D4ZSMg7h.js → react-query-DzDr4w41.js} +1 -1
  23. package/assets/assets/{react-sigma_core.esm.min-CSRp4hew.js → react-sigma_core.esm.min-3kgtOWbQ.js} +1 -1
  24. package/assets/assets/{service-CYzJmCXU.js → service-BUaUaize.js} +1 -1
  25. package/assets/index.html +7 -7
  26. package/dist/{ads-Z2SNR2F5.js → ads-7WUY5CVC.js} +3 -3
  27. package/dist/{agent-UUDYXXRD.js → agent-NOA33LVM.js} +7 -7
  28. package/dist/{agent-ask-KCZKPBY7.js → agent-ask-PKQDFJ7K.js} +3 -3
  29. package/dist/{agent-conversations-CDQA7HOU.js → agent-conversations-7HVWPSFB.js} +3 -3
  30. package/dist/{agent-memory-MER75L4L.js → agent-memory-Y4M2EWKA.js} +3 -3
  31. package/dist/{agent-providers-RH7W5HYA.js → agent-providers-P67FWELV.js} +3 -3
  32. package/dist/{agent-transcript-VU2VHSZ6.js → agent-transcript-C2BYWPRT.js} +3 -3
  33. package/dist/{analytics-BDVOFX6O.js → analytics-XHYWNNW7.js} +3 -3
  34. package/dist/{apply-XJULCLGT.js → apply-6OIWDXQP.js} +3 -3
  35. package/dist/{backfill-7I5BMA6R.js → backfill-KQPXCUUJ.js} +9 -9
  36. package/dist/{backlinks-HCSL6KKT.js → backlinks-ASYIZFLQ.js} +3 -3
  37. package/dist/{bing-AEUPBLV6.js → bing-ZLKJ3MIL.js} +3 -3
  38. package/dist/{bootstrap-J73OTQWI.js → bootstrap-KMVQY5JD.js} +10 -10
  39. package/dist/{cdp-2UQER2VR.js → cdp-V2HX4ZLE.js} +7 -7
  40. package/dist/{chunk-5STJ5LXF.js → chunk-2HNTNKFC.js} +157 -57
  41. package/dist/{chunk-CZSHCZHW.js → chunk-2LY4FY4N.js} +1 -1
  42. package/dist/{chunk-COUDJ44G.js → chunk-3B2GBWIA.js} +5 -5
  43. package/dist/{chunk-RWHKTH75.js → chunk-3NICRFTU.js} +333 -333
  44. package/dist/{chunk-YHTU4XBR.js → chunk-5NYS3ABL.js} +2 -2
  45. package/dist/{chunk-H6QJN6RQ.js → chunk-7EWDQUNH.js} +8 -8
  46. package/dist/{chunk-XEM6TO4X.js → chunk-AO4P75DI.js} +5 -5
  47. package/dist/{chunk-GAA7JGB7.js → chunk-APQ5EAL2.js} +2 -2
  48. package/dist/{chunk-VHHFAAMI.js → chunk-B5CSCGVD.js} +1 -1
  49. package/dist/{chunk-SQ6AOE57.js → chunk-CJC4AQ2M.js} +2 -2
  50. package/dist/{chunk-VCSJHXW6.js → chunk-DAKJBAUN.js} +1 -1
  51. package/dist/{chunk-KLB3CTOQ.js → chunk-GMP7OZDW.js} +4 -4
  52. package/dist/{chunk-B5KK54RE.js → chunk-GSHHYR24.js} +3 -3
  53. package/dist/{chunk-P5DZ56CR.js → chunk-IALK4DQJ.js} +1 -1
  54. package/dist/{chunk-FWRHDY4R.js → chunk-IVZMXHXE.js} +1 -1
  55. package/dist/{chunk-EKY6GHJ4.js → chunk-KLGHY67V.js} +4 -4
  56. package/dist/{chunk-XCA546YC.js → chunk-KTQVRQDM.js} +1 -1
  57. package/dist/{chunk-KX5IRN55.js → chunk-LAILXR2F.js} +1 -1
  58. package/dist/{chunk-MRNMDQRB.js → chunk-MMRJUOE2.js} +1 -1
  59. package/dist/{chunk-5PYPNDIE.js → chunk-OJVXKPIN.js} +3 -3
  60. package/dist/{chunk-7VEXNHRX.js → chunk-Q7PTBHNG.js} +1 -1
  61. package/dist/{chunk-HD7AZIOP.js → chunk-RGD5SDT3.js} +17 -1
  62. package/dist/{chunk-SZXYGJMI.js → chunk-SDZKHJ6W.js} +2 -2
  63. package/dist/{chunk-34HGKT6I.js → chunk-SYQLUOR7.js} +1 -1
  64. package/dist/{chunk-G3ZNUINX.js → chunk-U2WPZZPQ.js} +1 -1
  65. package/dist/{chunk-IG6ISQYD.js → chunk-U6CNJC2Y.js} +6310 -6261
  66. package/dist/{chunk-ZB3YVCBL.js → chunk-UUGT7UMF.js} +4 -4
  67. package/dist/{chunk-SA4CG775.js → chunk-UVDBUMYV.js} +2 -2
  68. package/dist/{chunk-W5FR6AJP.js → chunk-WKZZILBI.js} +2 -2
  69. package/dist/{chunk-3A7LI5KI.js → chunk-X3GWBK7N.js} +3 -3
  70. package/dist/{chunk-5AV2OUCJ.js → chunk-YBDUZBGU.js} +1 -1
  71. package/dist/{chunk-TYWTIYYM.js → chunk-YJAN2ZDV.js} +3 -3
  72. package/dist/{chunk-RL7LPPFU.js → chunk-YQGVA64Q.js} +3 -3
  73. package/dist/{chunk-OWEIOEOY.js → chunk-YQMQWDTI.js} +20 -20
  74. package/dist/{citations-3X5EJNJ3.js → citations-NOCVQPPU.js} +3 -3
  75. package/dist/{cli-catalog-F3IOEPJK.js → cli-catalog-WOWYZ7Q2.js} +3 -3
  76. package/dist/{cli-commands-NVNMM4IE.js → cli-commands-4YSZAPKZ.js} +7 -7
  77. package/dist/cli.js +13 -13
  78. package/dist/{competitor-CUPGN4NZ.js → competitor-KQK66W3M.js} +3 -3
  79. package/dist/{content-FYQT7V5A.js → content-OHOQO5LP.js} +3 -3
  80. package/dist/{dashboard-4AEKE4XK.js → dashboard-W6MII62Z.js} +6 -6
  81. package/dist/{demo-GLYZQOTJ.js → demo-23H25IOO.js} +1 -1
  82. package/dist/{demo-server-24RESPMP.js → demo-server-WYYWTH4H.js} +11 -11
  83. package/dist/{discover-4IGXLRRE.js → discover-557WRGEK.js} +3 -3
  84. package/dist/{discover-eval-DGLZO5MD.js → discover-eval-GGRZZHHI.js} +3 -3
  85. package/dist/{doctor-3MFTUIEU.js → doctor-D5JNJKVE.js} +3 -3
  86. package/dist/{evidence-ANE4SFP5.js → evidence-DHYYVPWW.js} +3 -3
  87. package/dist/{export-cmd-X6S5F463.js → export-cmd-M24S3YBY.js} +3 -3
  88. package/dist/{ga-2UKFAVDJ.js → ga-XM6WXZNP.js} +3 -3
  89. package/dist/{gbp-Q3V3SQMV.js → gbp-M3S5XYPG.js} +3 -3
  90. package/dist/{google-D4O4JCM4.js → google-PULCSUOU.js} +4 -4
  91. package/dist/{google-marketing-6L5327YK.js → google-marketing-AA625HA4.js} +1 -1
  92. package/dist/{health-cmd-7TPQHGKK.js → health-cmd-LVEW6OBD.js} +3 -3
  93. package/dist/{history-NNUOPNA7.js → history-32ZYGZGQ.js} +3 -3
  94. package/dist/index.d.ts +110 -63
  95. package/dist/index.js +24 -24
  96. package/dist/{init-U7MG23AF.js → init-4YT2HMKY.js} +11 -11
  97. package/dist/{insights-DDAP3MRC.js → insights-RK3MJ4UM.js} +3 -3
  98. package/dist/intelligence-service-UNKFCG5N.js +15 -0
  99. package/dist/{journeys-I7H25PPF.js → journeys-S4CPNZYU.js} +3 -3
  100. package/dist/{keys-YUJNNNV3.js → keys-WCXE7WXV.js} +3 -3
  101. package/dist/{keyword-25DDYT45.js → keyword-HHVLC4HI.js} +3 -3
  102. package/dist/{logs-FKPJ4L5R.js → logs-VYESWGAN.js} +3 -3
  103. package/dist/{mcp-ZPLVNLL3.js → mcp-4LRFIOLC.js} +2 -2
  104. package/dist/mcp.js +9 -9
  105. package/dist/{measurement-plan-CDY63CLU.js → measurement-plan-AL3EHWRK.js} +5 -5
  106. package/dist/{notify-67IIWOSF.js → notify-LUDG5PWU.js} +3 -3
  107. package/dist/{organic-evidence-P2BQK2BV.js → organic-evidence-CIOHMKLD.js} +3 -3
  108. package/dist/{overview-WHSTSNS2.js → overview-NAZJ6PJG.js} +4 -4
  109. package/dist/{project-7AMXFZMD.js → project-N3MU5V6T.js} +3 -3
  110. package/dist/{query-RLKE6JHY.js → query-XMI2FV6G.js} +3 -3
  111. package/dist/{research-PDOE77HZ.js → research-MQ6QA5TV.js} +3 -3
  112. package/dist/{results-export-5VKTQLOR.js → results-export-OFEZXCJK.js} +3 -3
  113. package/dist/{run-QXCB4AWO.js → run-YQS3LCFT.js} +4 -4
  114. package/dist/{schedule-NDM2GBBW.js → schedule-DF6HCMXN.js} +3 -3
  115. package/dist/{search-DDRSJUWZ.js → search-MRDJKVOL.js} +3 -3
  116. package/dist/{serve-U6ZKXTKN.js → serve-5A4K242B.js} +25 -25
  117. package/dist/{session-PPYDP5UU.js → session-5OXLS7NP.js} +12 -12
  118. package/dist/{settings-Q2ZOEA7H.js → settings-NIF34QPH.js} +3 -3
  119. package/dist/{skills-PKSEX44J.js → skills-JOSNWG7B.js} +2 -2
  120. package/dist/{snapshot-URNBVDTA.js → snapshot-32DLVRIJ.js} +3 -3
  121. package/dist/{src-MAMJVGSJ.js → src-F63SHN5T.js} +2 -2
  122. package/dist/{status-DDXVX4KE.js → status-IERHHP4C.js} +4 -4
  123. package/dist/{technical-aeo-OS6KQXAP.js → technical-aeo-D7CA3CK6.js} +3 -3
  124. package/dist/{telemetry-G7GX2LIY.js → telemetry-DRURFCCJ.js} +4 -4
  125. package/dist/{tools-OOAF75Y5.js → tools-3IZMSHSY.js} +11 -11
  126. package/dist/{traffic-A4QW7455.js → traffic-AME5NDMN.js} +27 -5
  127. package/dist/{users-4WPDDCCR.js → users-4CQ6ATAG.js} +3 -3
  128. package/dist/{visibility-stats-5BY7GVUV.js → visibility-stats-R5JBISBX.js} +3 -3
  129. package/dist/{wordpress-T5EYN2WX.js → wordpress-GFK4GR3O.js} +3 -3
  130. package/package.json +1 -1
  131. package/dist/intelligence-service-ZJ5X6J3A.js +0 -15
@@ -9,14 +9,14 @@ import {
9
9
  normalizeCloudflareWorkerEvent,
10
10
  sessionizeJourneyRequests,
11
11
  verifyRequestSignature
12
- } from "./chunk-34HGKT6I.js";
12
+ } from "./chunk-SYQLUOR7.js";
13
13
  import {
14
14
  currentOutcomeAttribution,
15
15
  getRequestContext,
16
16
  registerRequestContext,
17
17
  requestAuditIdentity,
18
18
  sanitizeRequestContext
19
- } from "./chunk-KX5IRN55.js";
19
+ } from "./chunk-LAILXR2F.js";
20
20
  import {
21
21
  SentimentIdempotencyConflict,
22
22
  SentimentRepository,
@@ -154,7 +154,7 @@ import {
154
154
  userSessions,
155
155
  users,
156
156
  writeNegativeReviewMaxStars
157
- } from "./chunk-HD7AZIOP.js";
157
+ } from "./chunk-RGD5SDT3.js";
158
158
  import {
159
159
  ADS_ACTIVATE_SCOPE,
160
160
  ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
@@ -208,6 +208,7 @@ import {
208
208
  CheckScopes,
209
209
  CheckStatuses,
210
210
  CitationStates,
211
+ CloudflareQueueDrainStopReasons,
211
212
  CloudflareTrafficDeliveryModes,
212
213
  CompetitorChangeKinds,
213
214
  CompetitorShareImpactCaveats,
@@ -1157,7 +1158,7 @@ import {
1157
1158
  wordpressSchemaDeployResultDtoSchema,
1158
1159
  wordpressSchemaStatusResultDtoSchema,
1159
1160
  wordpressStatusDtoSchema
1160
- } from "./chunk-IG6ISQYD.js";
1161
+ } from "./chunk-U6CNJC2Y.js";
1161
1162
 
1162
1163
  // ../api-routes/src/user-session.ts
1163
1164
  import crypto2 from "crypto";
@@ -15943,6 +15944,8 @@ function formatRun(row) {
15943
15944
  } : {},
15944
15945
  // Only on a run that skipped a provider, so other runs read as before.
15945
15946
  ...row.skippedProviders && Object.keys(row.skippedProviders).length > 0 ? { skippedProviders: row.skippedProviders } : {},
15947
+ // Only on a sync that recorded one, so other runs read as before.
15948
+ ...row.syncSummary ? { syncSummary: row.syncSummary } : {},
15946
15949
  createdAt: row.createdAt
15947
15950
  };
15948
15951
  }
@@ -63707,8 +63710,33 @@ async function trafficRoutes(app, opts) {
63707
63710
  let committedAt = startedAt;
63708
63711
  let remainingBacklogCount = 0;
63709
63712
  let completedBatches = 0;
63710
- const finishSync = (status, error) => {
63711
- app.db.update(runs).set({ status, finishedAt: committedAt, ...error ? { error } : {} }).where(eq80(runs.id, runId)).run();
63713
+ let pulls = 0;
63714
+ let emptyPulls = 0;
63715
+ let messagesPulled = 0;
63716
+ let messagesAcknowledged = 0;
63717
+ let poisonMessages = 0;
63718
+ let skippedUnleasedMessages = 0;
63719
+ let duplicateEvents = 0;
63720
+ let backlogAtFirstPull = null;
63721
+ let backlogAtLastPull = null;
63722
+ const drainSummary = (stopReason) => ({
63723
+ type: "cloudflare-queue",
63724
+ stopReason,
63725
+ pulls,
63726
+ emptyPulls,
63727
+ messagesPulled,
63728
+ messagesAcknowledged,
63729
+ poisonMessages,
63730
+ skippedUnleasedMessages,
63731
+ acceptedEvents,
63732
+ duplicateEvents,
63733
+ backlogAtFirstPull,
63734
+ backlogAtLastPull,
63735
+ elapsedMs: Date.now() - startedMs
63736
+ });
63737
+ const finishSync = (status, error, stopReason) => {
63738
+ const queueDrain = drainSummary(stopReason);
63739
+ app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(eq80(runs.id, runId)).run();
63712
63740
  writeAuditLog(app.db, {
63713
63741
  projectId: project.id,
63714
63742
  actor: "api",
@@ -63745,6 +63773,7 @@ async function trafficRoutes(app, opts) {
63745
63773
  aiReferralBucketRows,
63746
63774
  sampleRows,
63747
63775
  remainingBacklogCount,
63776
+ queueDrain,
63748
63777
  windowStart: startedAt,
63749
63778
  windowEnd: committedAt
63750
63779
  };
@@ -63763,6 +63792,7 @@ async function trafficRoutes(app, opts) {
63763
63792
  const canonicalHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
63764
63793
  const drainDeadlineMs = startedMs + cloudflareQueueSyncDeadlineMs;
63765
63794
  let consecutiveEmptyPulls = 0;
63795
+ let stopReason = null;
63766
63796
  let receiptPolicy = null;
63767
63797
  const resolveReceiptPolicy = () => receiptPolicy ??= loadQueueReceiptPolicy(
63768
63798
  app.db,
@@ -63783,6 +63813,12 @@ async function trafficRoutes(app, opts) {
63783
63813
  visibilityTimeoutMs: CLOUDFLARE_QUEUE_VISIBILITY_TIMEOUT_MS
63784
63814
  });
63785
63815
  remainingBacklogCount = pulled.messageBacklogCount;
63816
+ pulls += 1;
63817
+ if (pulled.messages.length === 0) emptyPulls += 1;
63818
+ messagesPulled += pulled.messages.length;
63819
+ skippedUnleasedMessages += pulled.skippedUnleasedMessageCount;
63820
+ backlogAtFirstPull ??= pulled.messageBacklogCount;
63821
+ backlogAtLastPull = pulled.messageBacklogCount;
63786
63822
  if (pulled.skippedUnleasedMessageCount > 0) {
63787
63823
  request.log.warn({
63788
63824
  sourceId: sourceRow2.id,
@@ -63836,6 +63872,7 @@ async function trafficRoutes(app, opts) {
63836
63872
  }
63837
63873
  validLeases.push(message.leaseId);
63838
63874
  }
63875
+ poisonMessages += poisonLeases.length;
63839
63876
  const policy = normalized.length > 0 ? await resolveReceiptPolicy() : null;
63840
63877
  committedAt = (/* @__PURE__ */ new Date()).toISOString();
63841
63878
  const writeResult = writeTrafficEventBatch({
@@ -63878,6 +63915,7 @@ async function trafficRoutes(app, opts) {
63878
63915
  throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
63879
63916
  }
63880
63917
  const ackResult = await ackQueueMessages(queueClient, { acks: [...validLeases, ...poisonLeases] });
63918
+ messagesAcknowledged += validLeases.length + poisonLeases.length;
63881
63919
  if (ackResult.warningCount > 0) {
63882
63920
  request.log.warn({
63883
63921
  sourceId: sourceRow2.id,
@@ -63886,6 +63924,7 @@ async function trafficRoutes(app, opts) {
63886
63924
  }
63887
63925
  }
63888
63926
  acceptedEvents += writeResult.acceptedEvents;
63927
+ duplicateEvents += writeResult.duplicateEvents;
63889
63928
  selfTrafficExcluded += writeResult.selfTrafficExcluded;
63890
63929
  crawlerHits += writeResult.crawlerHits;
63891
63930
  aiUserFetchHits += writeResult.aiUserFetchHits;
@@ -63896,16 +63935,26 @@ async function trafficRoutes(app, opts) {
63896
63935
  aiReferralBucketRows += writeResult.aiReferralBucketRows;
63897
63936
  sampleRows += writeResult.sampleRows;
63898
63937
  completedBatches += 1;
63899
- if (remainingBacklogCount === 0) break;
63938
+ if (remainingBacklogCount === 0) {
63939
+ stopReason = CloudflareQueueDrainStopReasons["backlog-empty"];
63940
+ break;
63941
+ }
63900
63942
  if (pulled.messages.length === 0) {
63901
- if (pulled.skippedUnleasedMessageCount > 0 && pulled.skippedUnleasedMessageCount < CLOUDFLARE_QUEUE_BATCH_SIZE) break;
63943
+ if (pulled.skippedUnleasedMessageCount > 0 && pulled.skippedUnleasedMessageCount < CLOUDFLARE_QUEUE_BATCH_SIZE) {
63944
+ stopReason = CloudflareQueueDrainStopReasons["unleased-only"];
63945
+ break;
63946
+ }
63902
63947
  consecutiveEmptyPulls = pulled.skippedUnleasedMessageCount === 0 ? consecutiveEmptyPulls + 1 : 0;
63903
- if (consecutiveEmptyPulls >= 2) break;
63948
+ if (consecutiveEmptyPulls >= 2) {
63949
+ stopReason = CloudflareQueueDrainStopReasons["empty-pulls"];
63950
+ break;
63951
+ }
63904
63952
  } else {
63905
63953
  consecutiveEmptyPulls = 0;
63906
63954
  }
63907
63955
  }
63908
- return finishSync(RunStatuses.completed, null);
63956
+ stopReason ??= completedBatches >= cloudflareQueueMaxBatches ? CloudflareQueueDrainStopReasons["batch-cap"] : CloudflareQueueDrainStopReasons.deadline;
63957
+ return finishSync(RunStatuses.completed, null, stopReason);
63909
63958
  } catch (error) {
63910
63959
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
63911
63960
  const safeError = error instanceof CloudflareQueueApiError ? error.message : "Cloudflare Queue sync failed; retry the active source.";
@@ -63914,7 +63963,7 @@ async function trafficRoutes(app, opts) {
63914
63963
  { sourceId: sourceRow2.id, completedBatches, error: safeError },
63915
63964
  "Cloudflare Queue sync stopped after saving some batches"
63916
63965
  );
63917
- return finishSync(RunStatuses.partial, safeError);
63966
+ return finishSync(RunStatuses.partial, safeError, CloudflareQueueDrainStopReasons.error);
63918
63967
  }
63919
63968
  try {
63920
63969
  opts.onTrafficSynced?.({
@@ -63933,7 +63982,12 @@ async function trafficRoutes(app, opts) {
63933
63982
  } catch {
63934
63983
  }
63935
63984
  app.db.transaction((tx) => {
63936
- tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(eq80(runs.id, runId)).run();
63985
+ tx.update(runs).set({
63986
+ status: RunStatuses.failed,
63987
+ error: safeError,
63988
+ finishedAt: failedAt,
63989
+ syncSummary: drainSummary(CloudflareQueueDrainStopReasons.error)
63990
+ }).where(eq80(runs.id, runId)).run();
63937
63991
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
63938
63992
  if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
63939
63993
  tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and69(
@@ -64866,7 +64920,8 @@ async function trafficRoutes(app, opts) {
64866
64920
  status: latestRun.status,
64867
64921
  startedAt: latestRun.startedAt,
64868
64922
  finishedAt: latestRun.finishedAt ?? null,
64869
- error: latestRun.error ?? null
64923
+ error: latestRun.error ?? null,
64924
+ ...latestRun.syncSummary ? { syncSummary: latestRun.syncSummary } : {}
64870
64925
  } : null
64871
64926
  };
64872
64927
  }
@@ -68449,7 +68504,7 @@ var RUNTIME_STATE_CHECKS = [
68449
68504
  ];
68450
68505
 
68451
68506
  // ../api-routes/src/doctor/checks/traffic-source.ts
68452
- import { and as and78, eq as eq94, gte as gte24, inArray as inArray40, ne as ne16, sql as sql32 } from "drizzle-orm";
68507
+ import { and as and78, desc as desc44, eq as eq94, gte as gte24, inArray as inArray40, isNotNull as isNotNull9, ne as ne16, sql as sql32 } from "drizzle-orm";
68453
68508
  var RECENT_DATA_WARN_DAYS = 7;
68454
68509
  var RECENT_DATA_FAIL_DAYS = 30;
68455
68510
  function isCloudflareDirectPush(source) {
@@ -68994,6 +69049,48 @@ var syncLagCheck = {
68994
69049
  };
68995
69050
  }
68996
69051
  };
69052
+ function lastQueueStopReason(ctx, sourceId) {
69053
+ const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and78(
69054
+ eq94(runs.projectId, ctx.project.id),
69055
+ eq94(runs.kind, RunKinds["traffic-sync"]),
69056
+ eq94(runs.sourceId, sourceId),
69057
+ isNotNull9(runs.syncSummary)
69058
+ )).orderBy(desc44(runs.createdAt)).limit(1).get();
69059
+ return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
69060
+ }
69061
+ var QUEUE_LIMIT_STOPS = [
69062
+ CloudflareQueueDrainStopReasons.deadline,
69063
+ CloudflareQueueDrainStopReasons["batch-cap"]
69064
+ ];
69065
+ var QUEUE_NOT_VISIBLE_STOPS = [
69066
+ CloudflareQueueDrainStopReasons["empty-pulls"],
69067
+ CloudflareQueueDrainStopReasons["unleased-only"]
69068
+ ];
69069
+ function queueResidualCause(stopReasons) {
69070
+ const readRuns = "Read why each sync stopped with `canonry runs <project> --kind traffic-sync --format json` (`syncSummary.stopReason`).";
69071
+ if (stopReasons.every((reason) => reason !== null && QUEUE_LIMIT_STOPS.includes(reason))) {
69072
+ return {
69073
+ cause: "The drain stopped at its time limit or pull cap before the Queue was empty.",
69074
+ remediation: "If the backlog keeps growing, messages will pass the Queue's retention and be dropped. Shorten the traffic-sync schedule interval, or raise `CANONRY_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS` (default 4 minutes per sync)."
69075
+ };
69076
+ }
69077
+ if (stopReasons.every((reason) => reason !== null && QUEUE_NOT_VISIBLE_STOPS.includes(reason))) {
69078
+ return {
69079
+ cause: "Cloudflare returned no more messages to pull while still counting them, so they were leased or not yet visible. The drain stopped before its time limit, so raising the limit does not help.",
69080
+ remediation: "Later syncs pull them as they become visible. If the count does not fall across syncs, check the Queue in the Cloudflare dashboard for in-flight or delayed messages before they pass the Queue's retention."
69081
+ };
69082
+ }
69083
+ if (stopReasons.every((reason) => reason === CloudflareQueueDrainStopReasons.error)) {
69084
+ return {
69085
+ cause: "The last sync failed before the Queue was empty.",
69086
+ remediation: `Fix the sync error, then run \`canonry traffic sync <project> --source <id>\`. ${readRuns}`
69087
+ };
69088
+ }
69089
+ return {
69090
+ cause: "The last sync stopped before the Queue was empty.",
69091
+ remediation: `${readRuns} If it hit its time limit, shorten the traffic-sync schedule interval or raise \`CANONRY_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS\` (default 4 minutes per sync).`
69092
+ };
69093
+ }
68997
69094
  var queueBacklogCheck = {
68998
69095
  id: "traffic.source.queue-backlog",
68999
69096
  category: CheckCategories.integrations,
@@ -69012,12 +69109,14 @@ var queueBacklogCheck = {
69012
69109
  };
69013
69110
  }
69014
69111
  const measured = sources.filter((source) => source.queueBacklogCount !== null && source.queueBacklogCount !== void 0);
69112
+ const stopReasons = new Map(sources.map((source) => [source.id, lastQueueStopReason(ctx, source.id)]));
69015
69113
  const details = {
69016
69114
  sources: sources.map((source) => ({
69017
69115
  id: source.id,
69018
69116
  displayName: source.displayName,
69019
69117
  queueBacklogCount: source.queueBacklogCount ?? null,
69020
- queueBacklogObservedAt: source.queueBacklogObservedAt ?? null
69118
+ queueBacklogObservedAt: source.queueBacklogObservedAt ?? null,
69119
+ lastStopReason: stopReasons.get(source.id) ?? null
69021
69120
  }))
69022
69121
  };
69023
69122
  if (measured.length === 0) {
@@ -69032,11 +69131,12 @@ var queueBacklogCheck = {
69032
69131
  const remaining = measured.filter((source) => (source.queueBacklogCount ?? 0) > 0);
69033
69132
  const total = remaining.reduce((sum, source) => sum + source.queueBacklogCount, 0);
69034
69133
  if (total > CLOUDFLARE_QUEUE_RESIDUAL_WARN_COUNT) {
69134
+ const { cause, remediation } = queueResidualCause(remaining.map((source) => stopReasons.get(source.id) ?? null));
69035
69135
  return {
69036
69136
  status: CheckStatuses.warn,
69037
69137
  code: "traffic.queue-backlog.remaining",
69038
- summary: `${total.toLocaleString("en-US")} message(s) remained across ${remaining.length} Cloudflare Queue source(s) after the last sync, more than the ${CLOUDFLARE_QUEUE_RESIDUAL_WARN_COUNT.toLocaleString("en-US")} that can arrive while a drain runs. The drain stopped at its time limit or pull cap before the Queue was empty.`,
69039
- remediation: "If the backlog keeps growing, messages will pass the Queue's retention and be dropped. Shorten the traffic-sync schedule interval, or raise `CANONRY_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS` (default 4 minutes per sync).",
69138
+ summary: `${total.toLocaleString("en-US")} message(s) remained across ${remaining.length} Cloudflare Queue source(s) after the last sync, more than the ${CLOUDFLARE_QUEUE_RESIDUAL_WARN_COUNT.toLocaleString("en-US")} that can arrive while a drain runs. ${cause}`,
69139
+ remediation,
69040
69140
  details
69041
69141
  };
69042
69142
  }
@@ -69712,7 +69812,7 @@ async function doctorRoutes(app, opts) {
69712
69812
  }
69713
69813
 
69714
69814
  // ../api-routes/src/status.ts
69715
- import { and as and79, desc as desc44, eq as eq95, isNull as isNull15 } from "drizzle-orm";
69815
+ import { and as and79, desc as desc45, eq as eq95, isNull as isNull15 } from "drizzle-orm";
69716
69816
  function ageDays(iso) {
69717
69817
  if (!iso) return null;
69718
69818
  const ms = Date.now() - Date.parse(iso);
@@ -69762,7 +69862,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
69762
69862
  };
69763
69863
  }
69764
69864
  function latestDate2(db, table, projectId) {
69765
- const row = db.select({ date: table.date }).from(table).where(eq95(table.projectId, projectId)).orderBy(desc44(table.date)).limit(1).get();
69865
+ const row = db.select({ date: table.date }).from(table).where(eq95(table.projectId, projectId)).orderBy(desc45(table.date)).limit(1).get();
69766
69866
  return row?.date ?? null;
69767
69867
  }
69768
69868
  function projectRows(opts, project) {
@@ -69840,7 +69940,7 @@ async function statusRoutes(app, opts) {
69840
69940
 
69841
69941
  // ../api-routes/src/discovery/routes.ts
69842
69942
  import crypto54 from "crypto";
69843
- import { and as and80, desc as desc45, eq as eq96, gte as gte25, inArray as inArray41, isNull as isNull16, or as or20 } from "drizzle-orm";
69943
+ import { and as and80, desc as desc46, eq as eq96, gte as gte25, inArray as inArray41, isNull as isNull16, or as or20 } from "drizzle-orm";
69844
69944
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
69845
69945
  async function discoveryRoutes(app, opts) {
69846
69946
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -69901,7 +70001,7 @@ async function discoveryRoutes(app, opts) {
69901
70001
  DiscoverySessionStatuses.probing
69902
70002
  ]),
69903
70003
  gte25(discoverySessions.createdAt, ageFloorIso)
69904
- )).orderBy(desc45(discoverySessions.createdAt)).get();
70004
+ )).orderBy(desc46(discoverySessions.createdAt)).get();
69905
70005
  if (existing && existing.runId) {
69906
70006
  return { reused: true, sessionId: existing.id, runId: existing.runId };
69907
70007
  }
@@ -69970,7 +70070,7 @@ async function discoveryRoutes(app, opts) {
69970
70070
  const project = resolveProject(app.db, request.params.name);
69971
70071
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
69972
70072
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
69973
- const rows = app.db.select().from(discoverySessions).where(eq96(discoverySessions.projectId, project.id)).orderBy(desc45(discoverySessions.createdAt)).limit(limit).all();
70073
+ const rows = app.db.select().from(discoverySessions).where(eq96(discoverySessions.projectId, project.id)).orderBy(desc46(discoverySessions.createdAt)).limit(limit).all();
69974
70074
  return reply.send(rows.map(serializeSession));
69975
70075
  }
69976
70076
  );
@@ -70603,7 +70703,7 @@ function dedupeStrings(input) {
70603
70703
 
70604
70704
  // ../api-routes/src/technical-aeo.ts
70605
70705
  import crypto56 from "crypto";
70606
- import { and as and82, asc as asc21, count as count8, desc as desc46, eq as eq99, gte as gte26, inArray as inArray42, isNotNull as isNotNull9, isNull as isNull17, lt as lt14, or as or21, sql as sql34 } from "drizzle-orm";
70706
+ import { and as and82, asc as asc21, count as count8, desc as desc47, eq as eq99, gte as gte26, inArray as inArray42, isNotNull as isNotNull10, isNull as isNull17, lt as lt14, or as or21, sql as sql34 } from "drizzle-orm";
70607
70707
  import { alias } from "drizzle-orm/sqlite-core";
70608
70708
 
70609
70709
  // ../api-routes/src/technical-aeo-issues.ts
@@ -71248,7 +71348,7 @@ async function technicalAeoRoutes(app, opts) {
71248
71348
  const filters = visibleCrawlFilters(projectId);
71249
71349
  const allDates = and82(...filters);
71250
71350
  const matchingDates = and82(...filters, eq99(sql34`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
71251
- const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc46(date)).limit(10).all().map((row) => row.date);
71351
+ const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc47(date)).limit(10).all().map((row) => row.date);
71252
71352
  const countDates = (where) => app.db.select({ value: sql34`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
71253
71353
  return {
71254
71354
  recentDates: readDates(allDates),
@@ -71267,11 +71367,11 @@ async function technicalAeoRoutes(app, opts) {
71267
71367
  }
71268
71368
  const selectionFilters = scope.scoredOnly ? [...filters, sql34`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
71269
71369
  const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(...selectionFilters, runId ? eq99(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
71270
- desc46(sql34`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
71271
- desc46(siteCrawlSnapshots.complete),
71272
- desc46(siteCrawlSnapshots.pagesFetched),
71273
- desc46(siteCrawlSnapshots.createdAt),
71274
- desc46(siteCrawlSnapshots.runId)
71370
+ desc47(sql34`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
71371
+ desc47(siteCrawlSnapshots.complete),
71372
+ desc47(siteCrawlSnapshots.pagesFetched),
71373
+ desc47(siteCrawlSnapshots.createdAt),
71374
+ desc47(siteCrawlSnapshots.runId)
71275
71375
  ).limit(1).get();
71276
71376
  if (!selected) return void 0;
71277
71377
  const date = selected.snapshot.createdAt.slice(0, 10);
@@ -71289,10 +71389,10 @@ async function technicalAeoRoutes(app, opts) {
71289
71389
  complete: siteCrawlSnapshots.complete,
71290
71390
  pages: siteCrawlSnapshots.pagesFetched
71291
71391
  }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
71292
- desc46(siteCrawlSnapshots.complete),
71293
- desc46(siteCrawlSnapshots.pagesFetched),
71294
- desc46(siteCrawlSnapshots.createdAt),
71295
- desc46(siteCrawlSnapshots.runId)
71392
+ desc47(siteCrawlSnapshots.complete),
71393
+ desc47(siteCrawlSnapshots.pagesFetched),
71394
+ desc47(siteCrawlSnapshots.createdAt),
71395
+ desc47(siteCrawlSnapshots.runId)
71296
71396
  ).limit(10).all();
71297
71397
  const runSelection = {
71298
71398
  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",
@@ -71315,11 +71415,11 @@ async function technicalAeoRoutes(app, opts) {
71315
71415
  ];
71316
71416
  const complete = sql34`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
71317
71417
  const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(and82(...filters, pinnedRunId ? eq99(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
71318
- desc46(sql34`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
71319
- desc46(complete),
71320
- desc46(siteAuditSnapshots.pagesAudited),
71321
- desc46(siteAuditSnapshots.createdAt),
71322
- desc46(siteAuditSnapshots.runId)
71418
+ desc47(sql34`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
71419
+ desc47(complete),
71420
+ desc47(siteAuditSnapshots.pagesAudited),
71421
+ desc47(siteAuditSnapshots.createdAt),
71422
+ desc47(siteAuditSnapshots.runId)
71323
71423
  ).limit(1).get();
71324
71424
  const auditOfCrawl = (target) => {
71325
71425
  const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
@@ -71347,10 +71447,10 @@ async function technicalAeoRoutes(app, opts) {
71347
71447
  complete,
71348
71448
  pages: siteAuditSnapshots.pagesAudited
71349
71449
  }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
71350
- desc46(complete),
71351
- desc46(siteAuditSnapshots.pagesAudited),
71352
- desc46(siteAuditSnapshots.createdAt),
71353
- desc46(siteAuditSnapshots.runId)
71450
+ desc47(complete),
71451
+ desc47(siteAuditSnapshots.pagesAudited),
71452
+ desc47(siteAuditSnapshots.createdAt),
71453
+ desc47(siteAuditSnapshots.runId)
71354
71454
  ).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
71355
71455
  const runSelection = {
71356
71456
  // A crawl exists here only when none stored a scorecard: this score is a fallback.
@@ -71570,7 +71670,7 @@ async function technicalAeoRoutes(app, opts) {
71570
71670
  const total = totalRow?.value ?? 0;
71571
71671
  const limit = parsePositiveInt(request.query.limit, 100, 500);
71572
71672
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
71573
- const orderBy = request.query.sort === "score-desc" ? desc46(siteAuditPages.overallScore) : request.query.sort === "url" ? asc21(siteAuditPages.url) : asc21(siteAuditPages.overallScore);
71673
+ const orderBy = request.query.sort === "score-desc" ? desc47(siteAuditPages.overallScore) : request.query.sort === "url" ? asc21(siteAuditPages.url) : asc21(siteAuditPages.overallScore);
71574
71674
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
71575
71675
  const pages = rows.map((row) => ({
71576
71676
  url: row.url,
@@ -71595,7 +71695,7 @@ async function technicalAeoRoutes(app, opts) {
71595
71695
  eq99(runs.kind, RunKinds["site-audit"]),
71596
71696
  inArray42(runs.status, SURFACEABLE_STATUSES),
71597
71697
  notProbeRun()
71598
- )).orderBy(desc46(siteAuditSnapshots.createdAt)).limit(limit).all();
71698
+ )).orderBy(desc47(siteAuditSnapshots.createdAt)).limit(limit).all();
71599
71699
  return { project: project.name, points: rows.reverse() };
71600
71700
  });
71601
71701
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -71906,7 +72006,7 @@ async function technicalAeoRoutes(app, opts) {
71906
72006
  eq99(siteCrawlEdges.runId, scope.runId),
71907
72007
  eq99(siteCrawlEdges.attemptId, scope.attemptId),
71908
72008
  eq99(siteCrawlEdges.internal, true),
71909
- isNotNull9(siteCrawlEdges.targetNodeKey),
72009
+ isNotNull10(siteCrawlEdges.targetNodeKey),
71910
72010
  or21(
71911
72011
  inArray42(siteCrawlEdges.sourceNodeKey, frontier),
71912
72012
  inArray42(siteCrawlEdges.targetNodeKey, frontier)
@@ -71955,7 +72055,7 @@ async function technicalAeoRoutes(app, opts) {
71955
72055
  eq99(siteCrawlEdges.runId, scope.runId),
71956
72056
  eq99(siteCrawlEdges.attemptId, scope.attemptId),
71957
72057
  eq99(siteCrawlEdges.internal, true),
71958
- isNotNull9(siteCrawlEdges.targetNodeKey),
72058
+ isNotNull10(siteCrawlEdges.targetNodeKey),
71959
72059
  inArray42(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
71960
72060
  inArray42(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
71961
72061
  )).limit(1).get();
@@ -72057,7 +72157,7 @@ async function technicalAeoRoutes(app, opts) {
72057
72157
  eq99(siteCrawlEdges.internal, true),
72058
72158
  eq99(siteCrawlEdges.followable, true),
72059
72159
  eq99(siteCrawlEdges.relation, "anchor"),
72060
- isNotNull9(siteCrawlEdges.targetNodeKey),
72160
+ isNotNull10(siteCrawlEdges.targetNodeKey),
72061
72161
  inArray42(siteCrawlEdges.sourceNodeKey, frontier)
72062
72162
  )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
72063
72163
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
@@ -72146,7 +72246,7 @@ async function technicalAeoRoutes(app, opts) {
72146
72246
  eq99(siteCrawlSnapshots.complete, true),
72147
72247
  notProbeRun()
72148
72248
  ];
72149
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(...completeSnapshotFilters)).orderBy(desc46(siteCrawlSnapshots.createdAt), desc46(siteCrawlSnapshots.runId)).limit(1).get();
72249
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(...completeSnapshotFilters)).orderBy(desc47(siteCrawlSnapshots.createdAt), desc47(siteCrawlSnapshots.runId)).limit(1).get();
72150
72250
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
72151
72251
  const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(
72152
72252
  ...completeSnapshotFilters,
@@ -72157,7 +72257,7 @@ async function technicalAeoRoutes(app, opts) {
72157
72257
  lt14(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
72158
72258
  )
72159
72259
  )
72160
- )).orderBy(desc46(siteCrawlSnapshots.createdAt), desc46(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
72260
+ )).orderBy(desc47(siteCrawlSnapshots.createdAt), desc47(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
72161
72261
  if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
72162
72262
  if (!afterTarget) {
72163
72263
  const latestCrawl = resolveCrawl(project.id);
@@ -72609,7 +72709,7 @@ async function technicalAeoRoutes(app, opts) {
72609
72709
  sort
72610
72710
  })).digest("base64url");
72611
72711
  const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
72612
- const orderBy = sort === "score-desc" ? [desc46(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc21(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)] : sort === "path" ? [asc21(siteCrawlPages.path), asc21(siteCrawlPages.nodeKey)] : [asc21(siteCrawlPages.url), asc21(siteCrawlPages.nodeKey)];
72712
+ const orderBy = sort === "score-desc" ? [desc47(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc21(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)] : sort === "path" ? [asc21(siteCrawlPages.path), asc21(siteCrawlPages.nodeKey)] : [asc21(siteCrawlPages.url), asc21(siteCrawlPages.nodeKey)];
72613
72713
  let healthStateFilter = null;
72614
72714
  if (healthState) {
72615
72715
  const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and82(
@@ -72885,7 +72985,7 @@ async function technicalAeoRoutes(app, opts) {
72885
72985
  // a fabricated broken link shown to a client is not worth the saved
72886
72986
  // predicate. Applied to `total` as well as the rows, or the count would
72887
72987
  // promise a page the list cannot fill.
72888
- isNotNull9(sql34`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
72988
+ isNotNull10(sql34`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
72889
72989
  );
72890
72990
  const total = app.db.select({ value: count8() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
72891
72991
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
@@ -72929,7 +73029,7 @@ async function technicalAeoRoutes(app, opts) {
72929
73029
  eq99(runs.projectId, project.id),
72930
73030
  eq99(runs.kind, RunKinds["site-audit"]),
72931
73031
  notProbeRun()
72932
- )).orderBy(desc46(runs.createdAt), desc46(runs.id)).limit(limit).all();
73032
+ )).orderBy(desc47(runs.createdAt), desc47(runs.id)).limit(limit).all();
72933
73033
  const runIds = rows.map((row) => row.runId);
72934
73034
  const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(
72935
73035
  eq99(siteCrawlSnapshots.projectId, project.id),
@@ -72961,7 +73061,7 @@ async function technicalAeoRoutes(app, opts) {
72961
73061
  const attempt = app.db.select().from(siteCrawlAttempts).where(and82(
72962
73062
  eq99(siteCrawlAttempts.projectId, project.id),
72963
73063
  eq99(siteCrawlAttempts.runId, run.id)
72964
- )).orderBy(desc46(siteCrawlAttempts.attemptNumber), desc46(siteCrawlAttempts.updatedAt)).limit(1).get();
73064
+ )).orderBy(desc47(siteCrawlAttempts.attemptNumber), desc47(siteCrawlAttempts.updatedAt)).limit(1).get();
72965
73065
  const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and82(
72966
73066
  eq99(siteCrawlGraphLayouts.projectId, project.id),
72967
73067
  eq99(siteCrawlGraphLayouts.runId, run.id),
@@ -73008,7 +73108,7 @@ async function technicalAeoRoutes(app, opts) {
73008
73108
  const attempt = tx.select().from(siteCrawlAttempts).where(and82(
73009
73109
  eq99(siteCrawlAttempts.projectId, project.id),
73010
73110
  eq99(siteCrawlAttempts.runId, run.id)
73011
- )).orderBy(desc46(siteCrawlAttempts.attemptNumber), desc46(siteCrawlAttempts.updatedAt)).limit(1).get();
73111
+ )).orderBy(desc47(siteCrawlAttempts.attemptNumber), desc47(siteCrawlAttempts.updatedAt)).limit(1).get();
73012
73112
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
73013
73113
  const base = {
73014
73114
  project: project.name,
@@ -73038,7 +73138,7 @@ async function technicalAeoRoutes(app, opts) {
73038
73138
  auditFields: siteCrawlPages.auditFields
73039
73139
  }).from(siteCrawlPages).where(and82(
73040
73140
  auditedWhere,
73041
- isNotNull9(siteCrawlPages.auditScore),
73141
+ isNotNull10(siteCrawlPages.auditScore),
73042
73142
  lt14(siteCrawlPages.auditScore, 70)
73043
73143
  )).orderBy(asc21(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
73044
73144
  const examples = candidates.flatMap((candidate) => {
@@ -73134,7 +73234,7 @@ async function technicalAeoRoutes(app, opts) {
73134
73234
  // ../api-routes/src/research.ts
73135
73235
  import crypto57 from "crypto";
73136
73236
  import { z as z7 } from "zod";
73137
- import { and as and83, count as count9, desc as desc47, eq as eq100, gte as gte27, lt as lt15, or as or22, sql as sql35 } from "drizzle-orm";
73237
+ import { and as and83, count as count9, desc as desc48, eq as eq100, gte as gte27, lt as lt15, or as or22, sql as sql35 } from "drizzle-orm";
73138
73238
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
73139
73239
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
73140
73240
  async function researchRoutes(app, opts) {
@@ -73310,7 +73410,7 @@ async function researchRoutes(app, opts) {
73310
73410
  const rows = app.db.select().from(researchRuns).where(and83(
73311
73411
  eq100(researchRuns.projectId, project.id),
73312
73412
  cursor ? or22(lt15(researchRuns.createdAt, cursor.createdAt), and83(eq100(researchRuns.createdAt, cursor.createdAt), lt15(researchRuns.id, cursor.id))) : void 0
73313
- )).orderBy(desc47(researchRuns.createdAt), desc47(researchRuns.id)).limit(limit + 1).all();
73413
+ )).orderBy(desc48(researchRuns.createdAt), desc48(researchRuns.id)).limit(limit + 1).all();
73314
73414
  const runs2 = rows.slice(0, limit).map(serializeRun);
73315
73415
  const last = runs2.at(-1);
73316
73416
  const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
@@ -20,7 +20,7 @@ import {
20
20
  resolveProviderModel,
21
21
  schedulableRunKindSchema,
22
22
  sentimentEvaluatorProviderSchema
23
- } from "./chunk-IG6ISQYD.js";
23
+ } from "./chunk-U6CNJC2Y.js";
24
24
 
25
25
  // ../config/src/index.ts
26
26
  import { z as z4 } from "zod";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pickProjectCitedDomain
3
- } from "./chunk-G3ZNUINX.js";
3
+ } from "./chunk-U2WPZZPQ.js";
4
4
  import {
5
5
  GBP_INSIGHT_PROVIDER,
6
6
  PERSISTENT_GAP_THRESHOLD,
@@ -14,10 +14,10 @@ import {
14
14
  mergeGscQueryTotalsWithFallback,
15
15
  readGscQueryDailyRows,
16
16
  readLatestGscDataDate
17
- } from "./chunk-5STJ5LXF.js";
17
+ } from "./chunk-2HNTNKFC.js";
18
18
  import {
19
19
  getRequestContext
20
- } from "./chunk-KX5IRN55.js";
20
+ } from "./chunk-LAILXR2F.js";
21
21
  import {
22
22
  competitors,
23
23
  gbpDailyMetrics,
@@ -35,7 +35,7 @@ import {
35
35
  queries,
36
36
  querySnapshots,
37
37
  runs
38
- } from "./chunk-HD7AZIOP.js";
38
+ } from "./chunk-RGD5SDT3.js";
39
39
  import {
40
40
  CitationStates,
41
41
  RunKinds,
@@ -45,7 +45,7 @@ import {
45
45
  effectiveDomains,
46
46
  redactLogString,
47
47
  redactLogValue
48
- } from "./chunk-IG6ISQYD.js";
48
+ } from "./chunk-U6CNJC2Y.js";
49
49
 
50
50
  // src/intelligence-service.ts
51
51
  import { eq, desc, asc, and, ne, or, inArray, gte, lte, isNull, sql, exists } from "drizzle-orm";