@canonry/canonry 4.133.2 → 4.134.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 (26) hide show
  1. package/README.md +0 -13
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +25 -0
  3. package/assets/assets/{AuditHistoryPanel-CVDg3GXg.js → AuditHistoryPanel-DBUWbfNk.js} +1 -1
  4. package/assets/assets/{BacklinksPage-Dh4RIvS7.js → BacklinksPage-BFm0VdUO.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-BK89G5H_.js → ChartPrimitives-oV_nduTH.js} +1 -1
  6. package/assets/assets/{HistoryPage-D8rMu_Js.js → HistoryPage-IF2FMem3.js} +1 -1
  7. package/assets/assets/{ProjectPage-CiTDveNK.js → ProjectPage-ClwxX9Ga.js} +2 -2
  8. package/assets/assets/{RunRow-DVOwbmTp.js → RunRow-De5zdch5.js} +1 -1
  9. package/assets/assets/{RunsPage-tz3Oil7N.js → RunsPage-xtRF78r8.js} +1 -1
  10. package/assets/assets/{SettingsPage-C698YidM.js → SettingsPage-B_GAxaWu.js} +1 -1
  11. package/assets/assets/{TrafficPage-XDXWMtey.js → TrafficPage-E8I5ineJ.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-Clz8kOR8.js → TrafficSourceDetailPage-DGk2MqSp.js} +1 -1
  13. package/assets/assets/{arrow-left-GWLbh0pE.js → arrow-left-D-4QMxi9.js} +1 -1
  14. package/assets/assets/{extract-error-message-Dk8w-sb1.js → extract-error-message-DuqIFU8Z.js} +1 -1
  15. package/assets/assets/{index-jTshRRh0.js → index-De1MKbc7.js} +90 -90
  16. package/assets/assets/{trash-2-DwnzsuH1.js → trash-2-BVh__r6G.js} +1 -1
  17. package/assets/index.html +1 -1
  18. package/dist/{chunk-IEZTETUZ.js → chunk-LIXWDIDR.js} +787 -42
  19. package/dist/{chunk-ZPQX5ZQ2.js → chunk-PC7AYXND.js} +325 -2
  20. package/dist/{chunk-V2YR3YK6.js → chunk-TKXWTDV6.js} +73 -7
  21. package/dist/{chunk-JTJQZ4UC.js → chunk-ZUQPHGEN.js} +73 -2
  22. package/dist/cli.js +109 -4
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-WZNE23TV.js → intelligence-service-4XOFUN5E.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +10 -10
@@ -8,11 +8,20 @@ import {
8
8
  AdsActivationEntityTypes,
9
9
  AdsActivationGrantStates,
10
10
  AdsActivationVersionPolicies,
11
+ AdsActivityAssessmentStates,
11
12
  AdsAdGroupBillingEventTypes,
12
13
  AdsCampaignBiddingTypes,
14
+ AdsDeliveryConfigurationBases,
15
+ AdsDeliverySnapshotIssues,
16
+ AdsDeliverySnapshotStatuses,
13
17
  AdsEntityStatuses,
14
18
  AdsEntityTypes,
19
+ AdsHistoricalCampaignRollupStatuses,
20
+ AdsInsightLevels,
15
21
  AdsIntegrityDecisions,
22
+ AdsLiveDeliveryBases,
23
+ AdsLiveEntityTypes,
24
+ AdsLivePresences,
16
25
  AdsOperationKinds,
17
26
  AdsOperationStates,
18
27
  AdsOperationStepStates,
@@ -83,12 +92,15 @@ import {
83
92
  adsConversionPixelListResponseSchema,
84
93
  adsCpcMicros,
85
94
  adsCtr,
95
+ adsDeliveryDiagnosticsDtoSchema,
86
96
  adsDisconnectResponseSchema,
87
97
  adsGeoSearchQuerySchema,
88
98
  adsGeoSearchResponseSchema,
89
99
  adsImageUploadRequestSchema,
90
100
  adsInsightLevelSchema,
91
101
  adsInsightsResponseSchema,
102
+ adsLiveDeliveryDtoSchema,
103
+ adsLiveDeliveryQuerySchema,
92
104
  adsOperationReconcileResponseSchema,
93
105
  adsOperationResponseSchema,
94
106
  adsOperationStepDtoSchema,
@@ -176,6 +188,7 @@ import {
176
188
  discoverySessionDetailDtoSchema,
177
189
  discoverySessionDtoSchema,
178
190
  doctorReportSchema,
191
+ dollarsToMicros,
179
192
  domainClassificationsResponseDtoSchema,
180
193
  effectiveBrandNames,
181
194
  effectiveDomains,
@@ -192,6 +205,7 @@ import {
192
205
  formatDateRange,
193
206
  formatDeltaCopy,
194
207
  formatIsoDate,
208
+ formatIsoDateInTimeZone,
195
209
  formatNumber,
196
210
  formatRatio,
197
211
  formatWindowCountDelta,
@@ -235,6 +249,7 @@ import {
235
249
  internalError,
236
250
  isBrowserProvider,
237
251
  isReadOnlyKey,
252
+ isoDateDaysBeforeInTimeZone,
238
253
  keywordDtoSchema,
239
254
  keywordGenerateRequestSchema,
240
255
  latestProjectRunDtoSchema,
@@ -349,10 +364,10 @@ import {
349
364
  wordpressSchemaDeployResultDtoSchema,
350
365
  wordpressSchemaStatusResultDtoSchema,
351
366
  wordpressStatusDtoSchema
352
- } from "./chunk-ZPQX5ZQ2.js";
367
+ } from "./chunk-PC7AYXND.js";
353
368
 
354
369
  // src/intelligence-service.ts
355
- import { eq as eq44, desc as desc20, asc as asc9, and as and35, ne as ne6, or as or10, inArray as inArray15, gte as gte12, lte as lte8 } from "drizzle-orm";
370
+ import { eq as eq44, desc as desc21, asc as asc9, and as and35, ne as ne6, or as or10, inArray as inArray15, gte as gte12, lte as lte8 } from "drizzle-orm";
356
371
 
357
372
  // ../db/src/client.ts
358
373
  import { mkdirSync } from "fs";
@@ -1455,6 +1470,8 @@ var researchRunQueries = sqliteTable("research_run_queries", {
1455
1470
  groundingSources: text("grounding_sources", { mode: "json" }).$type().notNull().default([]),
1456
1471
  citedDomains: text("cited_domains", { mode: "json" }).$type().notNull().default([]),
1457
1472
  searchQueries: text("search_queries", { mode: "json" }).$type().notNull().default([]),
1473
+ namedCompetitors: text("named_competitors", { mode: "json" }).$type().notNull().default([]),
1474
+ citedCompetitorDomains: text("cited_competitor_domains", { mode: "json" }).$type().notNull().default([]),
1458
1475
  answerMentioned: integer("answer_mentioned", { mode: "boolean" }),
1459
1476
  citationState: text("citation_state"),
1460
1477
  rawResponse: text("raw_response", { mode: "json" }).$type(),
@@ -4259,6 +4276,16 @@ var MIGRATION_VERSIONS = [
4259
4276
  updated_at TEXT NOT NULL
4260
4277
  )`
4261
4278
  ]
4279
+ },
4280
+ {
4281
+ // Keep research batches useful as a competitor-comparison workspace while
4282
+ // retaining their strict isolation from tracked visibility snapshots.
4283
+ version: 110,
4284
+ name: "research-query-named-and-cited-competitors",
4285
+ statements: [
4286
+ `ALTER TABLE research_run_queries ADD COLUMN named_competitors TEXT NOT NULL DEFAULT '[]'`,
4287
+ `ALTER TABLE research_run_queries ADD COLUMN cited_competitor_domains TEXT NOT NULL DEFAULT '[]'`
4288
+ ]
4262
4289
  }
4263
4290
  ];
4264
4291
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -17379,11 +17406,13 @@ var SCHEMA_TABLE = {
17379
17406
  AdsActivationGrantResponse: adsActivationGrantResponseSchema,
17380
17407
  AdsCampaignListResponse: adsCampaignListResponseSchema,
17381
17408
  AdsConnectionStatusDto: adsConnectionStatusDtoSchema,
17409
+ AdsDeliveryDiagnosticsDto: adsDeliveryDiagnosticsDtoSchema,
17382
17410
  AdsConversionEventSettingListResponse: adsConversionEventSettingListResponseSchema,
17383
17411
  AdsConversionPixelListResponse: adsConversionPixelListResponseSchema,
17384
17412
  AdsDisconnectResponse: adsDisconnectResponseSchema,
17385
17413
  AdsGeoSearchResponse: adsGeoSearchResponseSchema,
17386
17414
  AdsInsightsResponse: adsInsightsResponseSchema,
17415
+ AdsLiveDeliveryDto: adsLiveDeliveryDtoSchema,
17387
17416
  AdsOperationReconcileResponse: adsOperationReconcileResponseSchema,
17388
17417
  AdsOperationResponse: adsOperationResponseSchema,
17389
17418
  AdsSummaryDto: adsSummaryDtoSchema,
@@ -20375,6 +20404,47 @@ var routeCatalog = [
20375
20404
  404: errorResponse("Project not found.")
20376
20405
  }
20377
20406
  },
20407
+ {
20408
+ method: "get",
20409
+ path: "/api/v1/projects/{name}/ads/delivery-diagnostics",
20410
+ summary: "Stored ads snapshot provenance, configuration facts, and historical campaign activity",
20411
+ description: "Read-only local evidence. It does not call OpenAI and never determines provider eligibility or serving.",
20412
+ tags: ["ads"],
20413
+ parameters: [nameParameter],
20414
+ responses: {
20415
+ 200: jsonResponse("Stored diagnostics returned.", "AdsDeliveryDiagnosticsDto"),
20416
+ 404: errorResponse("Project not found.")
20417
+ }
20418
+ },
20419
+ {
20420
+ method: "get",
20421
+ path: "/api/v1/projects/{name}/ads/live-delivery",
20422
+ summary: "Live provider read of ads status and metrics, with the local snapshot delta",
20423
+ description: "Calls the OpenAI Ads API right now with the stored connection and returns the provider's current status and metrics unaggregated, alongside the corresponding local snapshot values and an explicit per-entity delta. Dates on both sides are the ad account's local calendar dates. The first day of the window is measured from 00:00 in the account timezone on both sides, so it is a whole day either way and a difference on it is real; the current local day is in progress on both sides, so a difference there is snapshot staleness. Read-only: it never creates, updates, pauses, or activates anything, and it never waits for a sync run. The window counts calendar days in the account timezone, not fixed 24-hour periods, so a daylight-saving transition cannot move the first day. The walk is bounded by per-level caps and a budget of 40 reader calls (see bounds). Admission has two rules: a project may have only one live read running at a time (a second request is refused for as long as the first is actually walking, which can exceed any interval), and consecutive reads are spaced by at least one minute. The interval is counted as soon as the provider is called, so a read that fails upstream still holds it; only a read rejected before the provider was called leaves the next one free. A reader call is not one HTTP request: each list or insight read auto-paginates up to 100 pages, so the worst-case upstream cost of a single request is about 4000 HTTP requests, reported as bounds.maxUpstreamHttpRequests.",
20424
+ tags: ["ads"],
20425
+ parameters: [
20426
+ nameParameter,
20427
+ {
20428
+ name: "campaignId",
20429
+ in: "query",
20430
+ description: "Scope the walk to a single campaign.",
20431
+ schema: stringSchema
20432
+ },
20433
+ {
20434
+ name: "lookbackDays",
20435
+ in: "query",
20436
+ description: "Metrics window in calendar days of the ad account timezone (1-30, default 7).",
20437
+ schema: integerSchema
20438
+ }
20439
+ ],
20440
+ responses: {
20441
+ 200: jsonResponse("Live provider state and snapshot delta returned.", "AdsLiveDeliveryDto"),
20442
+ 400: errorResponse("Invalid query, or no ads connection for this project."),
20443
+ 404: errorResponse("Project not found."),
20444
+ 429: errorResponse("Another live read for this project is still running, or the minimum interval since the last provider read has not elapsed. error.details.reason says which."),
20445
+ 502: errorResponse("The OpenAI Ads API read failed.")
20446
+ }
20447
+ },
20378
20448
  {
20379
20449
  method: "post",
20380
20450
  path: "/api/v1/projects/{name}/bing/connect",
@@ -26008,7 +26078,7 @@ async function googleRoutes(app, opts) {
26008
26078
 
26009
26079
  // ../api-routes/src/ads.ts
26010
26080
  import crypto22 from "crypto";
26011
- import { eq as eq28, and as and22, asc as asc6, gt as gt2, gte as gte8, lt as lt3, lte as lte6, ne as ne3, inArray as inArray12, or as or8, isNull as isNull2, isNotNull as isNotNull2, sql as sql14 } from "drizzle-orm";
26081
+ import { eq as eq28, and as and22, asc as asc6, desc as desc13, gt as gt2, gte as gte8, lt as lt3, lte as lte6, ne as ne3, inArray as inArray12, or as or8, isNull as isNull2, isNotNull as isNotNull2, sql as sql14 } from "drizzle-orm";
26012
26082
 
26013
26083
  // ../api-routes/src/ads-activation-routes.ts
26014
26084
  import crypto21 from "crypto";
@@ -28445,7 +28515,256 @@ function registerAdsActivationRoutes(app, opts) {
28445
28515
  });
28446
28516
  }
28447
28517
 
28518
+ // ../api-routes/src/ads-live-delivery.ts
28519
+ function liveComparisonWindow(fetchedAtMs, lookbackDays, timezone) {
28520
+ const fetchedAtIso = new Date(fetchedAtMs).toISOString();
28521
+ return {
28522
+ startDate: isoDateDaysBeforeInTimeZone(fetchedAtIso, lookbackDays, timezone),
28523
+ endDate: formatIsoDateInTimeZone(fetchedAtIso, timezone)
28524
+ };
28525
+ }
28526
+ var EMPTY_METRIC_VALUES = {
28527
+ impressions: 0,
28528
+ clicks: 0,
28529
+ spendMicros: 0,
28530
+ conversions: 0
28531
+ };
28532
+ function presenceOf(live, stored) {
28533
+ if (live && stored) return AdsLivePresences.both;
28534
+ if (live) return AdsLivePresences["live-only"];
28535
+ return AdsLivePresences["stored-only"];
28536
+ }
28537
+ function fieldDelta(field, live, stored) {
28538
+ return live === stored ? null : { field, live, stored };
28539
+ }
28540
+ function buildFieldDeltas(live, stored) {
28541
+ if (!live || !stored) return [];
28542
+ const deltas = [
28543
+ fieldDelta("status", live.status, stored.status),
28544
+ fieldDelta("reviewStatus", live.reviewStatus, stored.reviewStatus),
28545
+ fieldDelta("name", live.name, stored.name)
28546
+ ];
28547
+ return deltas.filter((delta) => delta !== null);
28548
+ }
28549
+ function foldLiveRows(rows) {
28550
+ let values = EMPTY_METRIC_VALUES;
28551
+ for (const row of rows) {
28552
+ values = {
28553
+ impressions: values.impressions + Math.round(row.impressions ?? 0),
28554
+ clicks: values.clicks + Math.round(row.clicks ?? 0),
28555
+ spendMicros: values.spendMicros + dollarsToMicros(row.spend ?? 0),
28556
+ conversions: values.conversions + Math.round(row.conversions ?? 0)
28557
+ };
28558
+ }
28559
+ return values;
28560
+ }
28561
+ function sameMetrics(a, b) {
28562
+ return a.impressions === b.impressions && a.clicks === b.clicks && a.spendMicros === b.spendMicros && a.conversions === b.conversions;
28563
+ }
28564
+ function buildMetricDeltas(liveRows, storedRows, window) {
28565
+ if (liveRows === null && storedRows === null) return null;
28566
+ const liveByDate = /* @__PURE__ */ new Map();
28567
+ for (const row of liveRows ?? []) {
28568
+ if (row.date === null) continue;
28569
+ const bucket = liveByDate.get(row.date) ?? [];
28570
+ bucket.push(row);
28571
+ liveByDate.set(row.date, bucket);
28572
+ }
28573
+ const storedByDate = /* @__PURE__ */ new Map();
28574
+ for (const row of storedRows ?? []) {
28575
+ if (row.date < window.startDate || row.date > window.endDate) continue;
28576
+ storedByDate.set(row.date, row);
28577
+ }
28578
+ const dates = [.../* @__PURE__ */ new Set([...liveByDate.keys(), ...storedByDate.keys()])].sort();
28579
+ return dates.map((date) => {
28580
+ const liveBucket = liveByDate.get(date);
28581
+ const storedRow = storedByDate.get(date);
28582
+ const live = liveBucket ? foldLiveRows(liveBucket) : null;
28583
+ const stored = storedRow ? {
28584
+ impressions: storedRow.impressions,
28585
+ clicks: storedRow.clicks,
28586
+ spendMicros: storedRow.spendMicros,
28587
+ conversions: storedRow.conversions
28588
+ } : null;
28589
+ const drifted = live === null || stored === null || !sameMetrics(live, stored);
28590
+ return { date, live, stored, drifted };
28591
+ });
28592
+ }
28593
+ function buildLiveEntityComparison(observation, window) {
28594
+ const presence = presenceOf(observation.live, observation.stored);
28595
+ const fieldDeltas = buildFieldDeltas(observation.live, observation.stored);
28596
+ const metricDeltas = buildMetricDeltas(observation.liveMetrics, observation.storedMetrics, window);
28597
+ const drifted = presence !== AdsLivePresences.both || fieldDeltas.length > 0 || (metricDeltas?.some((delta) => delta.drifted) ?? false);
28598
+ return {
28599
+ entityType: observation.entityType,
28600
+ id: observation.id,
28601
+ parentId: observation.parentId,
28602
+ presence,
28603
+ live: observation.live,
28604
+ stored: observation.stored,
28605
+ fieldDeltas,
28606
+ liveMetrics: observation.liveMetrics,
28607
+ metricDeltas,
28608
+ drifted
28609
+ };
28610
+ }
28611
+ function summarizeLiveDrift(entities) {
28612
+ let driftedEntities = 0;
28613
+ let statusDrifted = 0;
28614
+ let metricsDrifted = 0;
28615
+ for (const entity of entities) {
28616
+ if (entity.drifted) driftedEntities += 1;
28617
+ if (entity.fieldDeltas.some((delta) => delta.field === "status")) statusDrifted += 1;
28618
+ if (entity.metricDeltas?.some((delta) => delta.drifted)) metricsDrifted += 1;
28619
+ }
28620
+ return {
28621
+ entitiesCompared: entities.length,
28622
+ driftedEntities,
28623
+ statusDrifted,
28624
+ metricsDrifted
28625
+ };
28626
+ }
28627
+
28448
28628
  // ../api-routes/src/ads.ts
28629
+ function runSourceDto(row) {
28630
+ const parsed = runStatusSchema.safeParse(row.status);
28631
+ return parsed.success ? { runId: row.id, status: parsed.data } : null;
28632
+ }
28633
+ function storedSnapshotProvenance(input) {
28634
+ if (!input.connected) {
28635
+ return {
28636
+ status: AdsDeliverySnapshotStatuses.unavailable,
28637
+ issue: AdsDeliverySnapshotIssues.no_ads_connection,
28638
+ sourceSync: null
28639
+ };
28640
+ }
28641
+ if (input.connectionLastSyncedAt === null) {
28642
+ return {
28643
+ status: input.hasEntityRows ? AdsDeliverySnapshotStatuses.partial : AdsDeliverySnapshotStatuses.unavailable,
28644
+ issue: AdsDeliverySnapshotIssues.connection_not_synced,
28645
+ sourceSync: null
28646
+ };
28647
+ }
28648
+ if (!input.hasEntityRows) {
28649
+ if (!input.latestAdsSync) {
28650
+ return {
28651
+ status: AdsDeliverySnapshotStatuses.unavailable,
28652
+ issue: AdsDeliverySnapshotIssues.no_ads_sync,
28653
+ sourceSync: null
28654
+ };
28655
+ }
28656
+ const sourceSync2 = runSourceDto(input.latestAdsSync);
28657
+ if (!sourceSync2 || sourceSync2.status !== RunStatuses.completed) {
28658
+ return {
28659
+ status: AdsDeliverySnapshotStatuses.partial,
28660
+ issue: AdsDeliverySnapshotIssues.source_sync_not_completed,
28661
+ sourceSync: sourceSync2
28662
+ };
28663
+ }
28664
+ return {
28665
+ status: AdsDeliverySnapshotStatuses.complete,
28666
+ issue: null,
28667
+ sourceSync: sourceSync2
28668
+ };
28669
+ }
28670
+ if (input.hasEntityWithoutSyncRunId) {
28671
+ return {
28672
+ status: AdsDeliverySnapshotStatuses.partial,
28673
+ issue: AdsDeliverySnapshotIssues.entity_rows_missing_sync_run_id,
28674
+ sourceSync: null
28675
+ };
28676
+ }
28677
+ if (input.entitySyncRunIds.size !== 1) {
28678
+ return {
28679
+ status: AdsDeliverySnapshotStatuses.partial,
28680
+ issue: AdsDeliverySnapshotIssues.entity_rows_span_multiple_sync_runs,
28681
+ sourceSync: null
28682
+ };
28683
+ }
28684
+ if (!input.sourceSync) {
28685
+ return {
28686
+ status: AdsDeliverySnapshotStatuses.partial,
28687
+ issue: AdsDeliverySnapshotIssues.source_sync_missing,
28688
+ sourceSync: null
28689
+ };
28690
+ }
28691
+ const sourceSync = runSourceDto(input.sourceSync);
28692
+ if (input.sourceSync.kind !== RunKinds["ads-sync"]) {
28693
+ return {
28694
+ status: AdsDeliverySnapshotStatuses.partial,
28695
+ issue: AdsDeliverySnapshotIssues.source_sync_not_ads_sync,
28696
+ sourceSync
28697
+ };
28698
+ }
28699
+ if (!sourceSync || sourceSync.status !== RunStatuses.completed) {
28700
+ return {
28701
+ status: AdsDeliverySnapshotStatuses.partial,
28702
+ issue: AdsDeliverySnapshotIssues.source_sync_not_completed,
28703
+ sourceSync
28704
+ };
28705
+ }
28706
+ return {
28707
+ status: AdsDeliverySnapshotStatuses.complete,
28708
+ issue: null,
28709
+ sourceSync
28710
+ };
28711
+ }
28712
+ function historicalCampaignRollups(rows) {
28713
+ if (rows.length === 0) {
28714
+ return {
28715
+ status: AdsHistoricalCampaignRollupStatuses.unavailable,
28716
+ window: { from: null, to: null },
28717
+ totals: null
28718
+ };
28719
+ }
28720
+ let impressions = 0;
28721
+ let clicks = 0;
28722
+ let spendMicros = 0;
28723
+ let conversions = 0;
28724
+ let fromDate = null;
28725
+ let toDate = null;
28726
+ for (const row of rows) {
28727
+ impressions += row.impressions;
28728
+ clicks += row.clicks;
28729
+ spendMicros += row.spendMicros;
28730
+ conversions += row.conversions;
28731
+ if (fromDate === null || row.date < fromDate) fromDate = row.date;
28732
+ if (toDate === null || row.date > toDate) toDate = row.date;
28733
+ }
28734
+ return {
28735
+ status: AdsHistoricalCampaignRollupStatuses.reported,
28736
+ window: { from: fromDate, to: toDate },
28737
+ totals: {
28738
+ impressions,
28739
+ clicks,
28740
+ spendMicros,
28741
+ conversions,
28742
+ ctr: adsCtr(clicks, impressions),
28743
+ cpcMicros: adsCpcMicros(spendMicros, clicks)
28744
+ }
28745
+ };
28746
+ }
28747
+ function storedActivityAssessment(snapshotStatus, rollupStatus, rollupImpressions) {
28748
+ switch (snapshotStatus) {
28749
+ case AdsDeliverySnapshotStatuses.unavailable:
28750
+ return AdsActivityAssessmentStates.unavailable;
28751
+ case AdsDeliverySnapshotStatuses.partial:
28752
+ return AdsActivityAssessmentStates.partial_snapshot;
28753
+ case AdsDeliverySnapshotStatuses.complete:
28754
+ switch (rollupStatus) {
28755
+ case AdsHistoricalCampaignRollupStatuses.unavailable:
28756
+ return AdsActivityAssessmentStates.metrics_unavailable;
28757
+ case AdsHistoricalCampaignRollupStatuses.reported:
28758
+ return (rollupImpressions ?? 0) > 0 ? AdsActivityAssessmentStates.observed_activity : AdsActivityAssessmentStates.no_observed_activity;
28759
+ }
28760
+ }
28761
+ }
28762
+ var ADS_LIVE_MAX_CAMPAIGNS = 5;
28763
+ var ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN = 10;
28764
+ var ADS_LIVE_MAX_ADS_PER_AD_GROUP = 20;
28765
+ var ADS_LIVE_MAX_READER_CALLS = 40;
28766
+ var DEFAULT_ADS_LIVE_MAX_PAGES_PER_READER_CALL = 100;
28767
+ var DEFAULT_ADS_LIVE_DELIVERY_MIN_INTERVAL_MS = 6e4;
28449
28768
  var DEFAULT_RECONCILE_SWEEP_INTERVAL_MS = 6e4;
28450
28769
  var DEFAULT_RECONCILE_PENDING_STALE_MS = 5 * 6e4;
28451
28770
  var DEFAULT_RECONCILE_BACKOFF_BASE_MS = 5 * 6e4;
@@ -28808,6 +29127,47 @@ async function executeAdsRead(surface, read) {
28808
29127
  });
28809
29128
  }
28810
29129
  }
29130
+ function resolveAdsLiveDeliveryReader(opts, projectName) {
29131
+ const apiKey = opts.adsCredentialStore?.getConnection(projectName)?.apiKey;
29132
+ if (!apiKey) {
29133
+ throw validationError("No OpenAI Ads API key configured for this project");
29134
+ }
29135
+ if (!opts.adsLiveDeliveryReader) {
29136
+ throw validationError("Ads live delivery reads are not configured for this deployment");
29137
+ }
29138
+ return { apiKey, reader: opts.adsLiveDeliveryReader };
29139
+ }
29140
+ function upstreamStatusOf(error) {
29141
+ return typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" && Number.isInteger(error.status) ? error.status : null;
29142
+ }
29143
+ function liveReadFailure(surface, entityId, error) {
29144
+ return { surface, entityId, upstreamStatus: upstreamStatusOf(error) };
29145
+ }
29146
+ function liveReadProviderError(surface, error) {
29147
+ const upstreamStatus = upstreamStatusOf(error);
29148
+ return providerError(
29149
+ `OpenAI Ads API ${surface} read failed`,
29150
+ upstreamStatus === null ? void 0 : { upstreamStatus }
29151
+ );
29152
+ }
29153
+ function liveEntityState(entity) {
29154
+ return {
29155
+ name: entity.name,
29156
+ status: entity.status,
29157
+ reviewStatus: entity.reviewStatus,
29158
+ mode: entity.mode,
29159
+ updatedAt: entity.updatedAt
29160
+ };
29161
+ }
29162
+ function storedEntityState(row) {
29163
+ return {
29164
+ name: row.name,
29165
+ status: row.status,
29166
+ reviewStatus: row.reviewStatus ?? null,
29167
+ upstreamUpdatedAt: row.upstreamUpdatedAt,
29168
+ syncedAt: row.syncedAt
29169
+ };
29170
+ }
28811
29171
  async function executeAdsOperation(app, request, input) {
28812
29172
  const hash = requestHash({ kind: input.kind, entityType: input.entityType, payload: input.payload });
28813
29173
  const existing = readOperationByKey(app, input.projectId, input.operationKey);
@@ -29446,6 +29806,21 @@ async function adsRoutes(app, opts) {
29446
29806
  opts.adsAccountVerificationCacheTtlMs ?? DEFAULT_ADS_ACCOUNT_VERIFICATION_CACHE_TTL_MS
29447
29807
  );
29448
29808
  const verificationCache = /* @__PURE__ */ new Map();
29809
+ const liveDeliveryMinIntervalMs = Math.max(
29810
+ 0,
29811
+ opts.adsLiveDeliveryMinIntervalMs ?? DEFAULT_ADS_LIVE_DELIVERY_MIN_INTERVAL_MS
29812
+ );
29813
+ const liveDeliveryMaxPagesPerReaderCall = Math.max(
29814
+ 1,
29815
+ opts.adsLiveDeliveryMaxPagesPerReaderCall ?? DEFAULT_ADS_LIVE_MAX_PAGES_PER_READER_CALL
29816
+ );
29817
+ const liveDeliveryInFlight = /* @__PURE__ */ new Set();
29818
+ const liveDeliveryLastAttemptAtMs = /* @__PURE__ */ new Map();
29819
+ const pruneLiveDeliveryAttempts = (nowMs) => {
29820
+ for (const [projectId, lastAtMs] of liveDeliveryLastAttemptAtMs) {
29821
+ if (nowMs - lastAtMs >= liveDeliveryMinIntervalMs) liveDeliveryLastAttemptAtMs.delete(projectId);
29822
+ }
29823
+ };
29449
29824
  const reconcilePolicy = {
29450
29825
  pendingMinIdleMs: pendingStaleMs,
29451
29826
  backoffBaseMs,
@@ -29571,6 +29946,10 @@ async function adsRoutes(app, opts) {
29571
29946
  reviewStatus: account.reviewStatus,
29572
29947
  integrityReviewStatus: account.integrityReviewStatus,
29573
29948
  integrityDecision: account.integrityDecision,
29949
+ // A credential rotation for the same account preserves its
29950
+ // snapshot watermark. A different account invalidates all stored
29951
+ // entity/insight provenance until the new binding is synced.
29952
+ lastSyncedAt: existingRow.adAccountId === account.id ? existingRow.lastSyncedAt : null,
29574
29953
  updatedAt: now
29575
29954
  }).where(eq28(adsConnections.id, existingRow.id)).run();
29576
29955
  } else {
@@ -30126,6 +30505,359 @@ async function adsRoutes(app, opts) {
30126
30505
  const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
30127
30506
  return response;
30128
30507
  });
30508
+ app.get("/projects/:name/ads/delivery-diagnostics", async (request) => {
30509
+ const project = resolveProject(app.db, request.params.name);
30510
+ const connection = app.db.select().from(adsConnections).where(eq28(adsConnections.projectId, project.id)).get();
30511
+ const campaignRows = app.db.select().from(adsCampaigns).where(eq28(adsCampaigns.projectId, project.id)).all();
30512
+ const adGroupRows = app.db.select().from(adsAdGroups).where(eq28(adsAdGroups.projectId, project.id)).all();
30513
+ const adRows = app.db.select().from(adsAds).where(eq28(adsAds.projectId, project.id)).all();
30514
+ const entityRows = [...campaignRows, ...adGroupRows, ...adRows];
30515
+ const hasEntityWithoutSyncRunId = entityRows.some((row) => row.syncRunId === null);
30516
+ const entitySyncRunIds = new Set(entityRows.flatMap((row) => row.syncRunId === null ? [] : [row.syncRunId]));
30517
+ const sourceSync = entitySyncRunIds.size === 1 ? app.db.select({ id: runs.id, kind: runs.kind, status: runs.status }).from(runs).where(and22(
30518
+ eq28(runs.id, [...entitySyncRunIds][0]),
30519
+ eq28(runs.projectId, project.id)
30520
+ )).get() : void 0;
30521
+ const latestAdsSync = app.db.select({ id: runs.id, kind: runs.kind, status: runs.status }).from(runs).where(and22(
30522
+ eq28(runs.projectId, project.id),
30523
+ eq28(runs.kind, RunKinds["ads-sync"])
30524
+ )).orderBy(desc13(runs.createdAt)).get();
30525
+ const snapshot = storedSnapshotProvenance({
30526
+ connected: Boolean(connection),
30527
+ connectionLastSyncedAt: connection?.lastSyncedAt ?? null,
30528
+ hasEntityRows: entityRows.length > 0,
30529
+ hasEntityWithoutSyncRunId,
30530
+ entitySyncRunIds,
30531
+ sourceSync,
30532
+ latestAdsSync
30533
+ });
30534
+ const rollups = historicalCampaignRollups(app.db.select({
30535
+ date: adsInsightsDaily.date,
30536
+ impressions: adsInsightsDaily.impressions,
30537
+ clicks: adsInsightsDaily.clicks,
30538
+ spendMicros: adsInsightsDaily.spendMicros,
30539
+ conversions: adsInsightsDaily.conversions
30540
+ }).from(adsInsightsDaily).where(and22(
30541
+ eq28(adsInsightsDaily.projectId, project.id),
30542
+ eq28(adsInsightsDaily.level, AdsInsightLevels.campaign)
30543
+ )).all());
30544
+ const adsByGroup = /* @__PURE__ */ new Map();
30545
+ for (const ad of adRows) {
30546
+ const list = adsByGroup.get(ad.adGroupId) ?? [];
30547
+ list.push({ id: ad.id, name: ad.name, status: ad.status, reviewStatus: ad.reviewStatus });
30548
+ adsByGroup.set(ad.adGroupId, list);
30549
+ }
30550
+ const groupsByCampaign = /* @__PURE__ */ new Map();
30551
+ for (const adGroup of adGroupRows) {
30552
+ const list = groupsByCampaign.get(adGroup.campaignId) ?? [];
30553
+ list.push({
30554
+ id: adGroup.id,
30555
+ name: adGroup.name,
30556
+ status: adGroup.status,
30557
+ billingEventType: adGroupBillingEventTypeDto(adGroup.billingEventType),
30558
+ maxBidMicros: adGroup.maxBidMicros,
30559
+ contextHints: adGroup.contextHints,
30560
+ ads: adsByGroup.get(adGroup.id) ?? []
30561
+ });
30562
+ groupsByCampaign.set(adGroup.campaignId, list);
30563
+ }
30564
+ const response = {
30565
+ snapshot: {
30566
+ ...snapshot,
30567
+ lastSyncedAt: connection?.lastSyncedAt ?? null,
30568
+ campaignCount: campaignRows.length,
30569
+ adGroupCount: adGroupRows.length,
30570
+ adCount: adRows.length
30571
+ },
30572
+ historicalCampaignRollups: rollups,
30573
+ storedConfiguration: {
30574
+ basis: AdsDeliveryConfigurationBases.storedSnapshot,
30575
+ connection: connection === void 0 ? null : {
30576
+ status: connection.status,
30577
+ reviewStatus: connection.reviewStatus,
30578
+ integrityReviewStatus: connection.integrityReviewStatus,
30579
+ integrityDecision: connection.integrityDecision,
30580
+ conversionTrackingConfigured: connection.conversionTrackingConfigured
30581
+ },
30582
+ campaigns: campaignRows.map((campaign) => ({
30583
+ id: campaign.id,
30584
+ name: campaign.name,
30585
+ status: campaign.status,
30586
+ biddingType: campaignBiddingTypeDto(campaign.biddingType),
30587
+ dailySpendLimitMicros: campaign.dailySpendLimitMicros,
30588
+ lifetimeSpendLimitMicros: campaign.lifetimeSpendLimitMicros,
30589
+ conversionEventSettingIds: campaign.conversionEventSettingIds,
30590
+ adGroups: groupsByCampaign.get(campaign.id) ?? []
30591
+ }))
30592
+ },
30593
+ assessment: {
30594
+ state: storedActivityAssessment(
30595
+ snapshot.status,
30596
+ rollups.status,
30597
+ rollups.totals === null ? null : rollups.totals.impressions
30598
+ )
30599
+ }
30600
+ };
30601
+ return response;
30602
+ });
30603
+ app.get("/projects/:name/ads/live-delivery", async (request) => {
30604
+ const project = resolveProject(app.db, request.params.name);
30605
+ const parsed = adsLiveDeliveryQuerySchema.safeParse(request.query);
30606
+ if (!parsed.success) {
30607
+ throw validationError("Invalid ads live delivery query", { issues: parsed.error.issues });
30608
+ }
30609
+ const { campaignId, lookbackDays } = parsed.data;
30610
+ const connection = app.db.select().from(adsConnections).where(eq28(adsConnections.projectId, project.id)).get();
30611
+ if (!connection) {
30612
+ throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
30613
+ }
30614
+ const { apiKey, reader } = resolveAdsLiveDeliveryReader(opts, project.name);
30615
+ const verifyAdsAccount = opts.verifyAdsAccount;
30616
+ if (!verifyAdsAccount) {
30617
+ throw validationError("Ads account verification is not configured for this deployment");
30618
+ }
30619
+ const fetchedAtMs = Date.now();
30620
+ const lastAttemptAtMs = liveDeliveryLastAttemptAtMs.get(project.id);
30621
+ const remainingIntervalMs = liveDeliveryMinIntervalMs > 0 && lastAttemptAtMs !== void 0 ? lastAttemptAtMs + liveDeliveryMinIntervalMs - fetchedAtMs : 0;
30622
+ if (liveDeliveryInFlight.has(project.id)) {
30623
+ throw quotaExceeded("OpenAI Ads live delivery reads", {
30624
+ reason: "read-in-flight",
30625
+ minIntervalMs: liveDeliveryMinIntervalMs,
30626
+ // A LOWER bound, and only when the interval itself still blocks. How
30627
+ // much longer the running walk needs is unknowable from here, so once
30628
+ // it has outlived the interval there is no honest number to give and
30629
+ // the field is absent rather than guessed.
30630
+ ...remainingIntervalMs > 0 ? { retryAfterMs: remainingIntervalMs } : {}
30631
+ });
30632
+ }
30633
+ if (remainingIntervalMs > 0) {
30634
+ throw quotaExceeded("OpenAI Ads live delivery reads", {
30635
+ reason: "min-interval",
30636
+ minIntervalMs: liveDeliveryMinIntervalMs,
30637
+ retryAfterMs: remainingIntervalMs
30638
+ });
30639
+ }
30640
+ liveDeliveryInFlight.add(project.id);
30641
+ const spendLiveDeliveryAttempt = () => {
30642
+ if (liveDeliveryMinIntervalMs <= 0) return;
30643
+ pruneLiveDeliveryAttempts(fetchedAtMs);
30644
+ liveDeliveryLastAttemptAtMs.set(project.id, fetchedAtMs);
30645
+ };
30646
+ try {
30647
+ let readerCalls = 0;
30648
+ let truncated = false;
30649
+ const errors = [];
30650
+ const callProvider = async (surface, entityId, run) => {
30651
+ if (readerCalls >= ADS_LIVE_MAX_READER_CALLS) {
30652
+ truncated = true;
30653
+ return null;
30654
+ }
30655
+ readerCalls += 1;
30656
+ try {
30657
+ return await run();
30658
+ } catch (error) {
30659
+ errors.push(liveReadFailure(surface, entityId, error));
30660
+ return null;
30661
+ }
30662
+ };
30663
+ readerCalls += 1;
30664
+ let account;
30665
+ spendLiveDeliveryAttempt();
30666
+ try {
30667
+ account = await verifyAdsAccount(apiKey);
30668
+ } catch (error) {
30669
+ throw liveReadProviderError("account identity", error);
30670
+ }
30671
+ if (account.id !== connection.adAccountId) {
30672
+ throw validationError("The configured key belongs to a different OpenAI ad account; reconnect this project");
30673
+ }
30674
+ readerCalls += 1;
30675
+ let liveCampaigns;
30676
+ try {
30677
+ liveCampaigns = await reader.listCampaigns(apiKey);
30678
+ } catch (error) {
30679
+ throw liveReadProviderError("campaign list", error);
30680
+ }
30681
+ const scopedCampaigns = campaignId === void 0 ? liveCampaigns : liveCampaigns.filter((campaign) => campaign.id === campaignId);
30682
+ if (scopedCampaigns.length > ADS_LIVE_MAX_CAMPAIGNS) truncated = true;
30683
+ const walkedCampaigns = scopedCampaigns.slice(0, ADS_LIVE_MAX_CAMPAIGNS);
30684
+ const storedCampaignRows = app.db.select().from(adsCampaigns).where(and22(
30685
+ eq28(adsCampaigns.projectId, project.id),
30686
+ campaignId === void 0 ? void 0 : eq28(adsCampaigns.id, campaignId)
30687
+ )).all();
30688
+ const storedAdGroupRows = app.db.select().from(adsAdGroups).where(and22(
30689
+ eq28(adsAdGroups.projectId, project.id),
30690
+ campaignId === void 0 ? void 0 : eq28(adsAdGroups.campaignId, campaignId)
30691
+ )).all();
30692
+ const storedAdGroupIds = new Set(storedAdGroupRows.map((row) => row.id));
30693
+ const storedAdRows = app.db.select().from(adsAds).where(eq28(adsAds.projectId, project.id)).all().filter((row) => campaignId === void 0 || storedAdGroupIds.has(row.adGroupId));
30694
+ const storedInsightRows = app.db.select({
30695
+ level: adsInsightsDaily.level,
30696
+ entityId: adsInsightsDaily.entityId,
30697
+ date: adsInsightsDaily.date,
30698
+ impressions: adsInsightsDaily.impressions,
30699
+ clicks: adsInsightsDaily.clicks,
30700
+ spendMicros: adsInsightsDaily.spendMicros,
30701
+ conversions: adsInsightsDaily.conversions
30702
+ }).from(adsInsightsDaily).where(eq28(adsInsightsDaily.projectId, project.id)).all();
30703
+ const storedCampaignById = new Map(storedCampaignRows.map((row) => [row.id, row]));
30704
+ const storedAdGroupById = new Map(storedAdGroupRows.map((row) => [row.id, row]));
30705
+ const storedAdById = new Map(storedAdRows.map((row) => [row.id, row]));
30706
+ const storedMetricsByEntity = /* @__PURE__ */ new Map();
30707
+ for (const row of storedInsightRows) {
30708
+ const key = `${row.level}:${row.entityId}`;
30709
+ const bucket = storedMetricsByEntity.get(key) ?? [];
30710
+ bucket.push({
30711
+ date: row.date,
30712
+ impressions: row.impressions,
30713
+ clicks: row.clicks,
30714
+ spendMicros: row.spendMicros,
30715
+ conversions: row.conversions
30716
+ });
30717
+ storedMetricsByEntity.set(key, bucket);
30718
+ }
30719
+ const storedMetricsFor = (level, entityId) => storedMetricsByEntity.get(`${level}:${entityId}`) ?? [];
30720
+ const accountTimezone = account.timezone ?? "UTC";
30721
+ const window = liveComparisonWindow(fetchedAtMs, lookbackDays, accountTimezone);
30722
+ const insightsRequest = {
30723
+ startDate: window.startDate,
30724
+ fetchedAtMs,
30725
+ timezone: accountTimezone
30726
+ };
30727
+ const observations = [];
30728
+ const walkedCampaignIds = /* @__PURE__ */ new Set();
30729
+ const walkedAdGroupIds = /* @__PURE__ */ new Set();
30730
+ const walkedAdIds = /* @__PURE__ */ new Set();
30731
+ for (const campaign of walkedCampaigns) {
30732
+ walkedCampaignIds.add(campaign.id);
30733
+ const campaignMetrics = await callProvider(
30734
+ "campaign insights",
30735
+ campaign.id,
30736
+ () => reader.campaignInsights(apiKey, campaign.id, insightsRequest)
30737
+ );
30738
+ observations.push({
30739
+ entityType: AdsLiveEntityTypes.campaign,
30740
+ id: campaign.id,
30741
+ parentId: null,
30742
+ live: liveEntityState(campaign),
30743
+ stored: storedCampaignById.has(campaign.id) ? storedEntityState(storedCampaignById.get(campaign.id)) : null,
30744
+ // A metrics call that did not happen is not evidence about metrics, so
30745
+ // both sides stay null and no delta is claimed.
30746
+ liveMetrics: campaignMetrics,
30747
+ storedMetrics: campaignMetrics === null ? null : storedMetricsFor(AdsInsightLevels.campaign, campaign.id)
30748
+ });
30749
+ const liveAdGroups = await callProvider(
30750
+ "ad group list",
30751
+ campaign.id,
30752
+ () => reader.listAdGroups(apiKey, campaign.id)
30753
+ );
30754
+ if (liveAdGroups === null) continue;
30755
+ if (liveAdGroups.length > ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN) truncated = true;
30756
+ for (const adGroup of liveAdGroups.slice(0, ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN)) {
30757
+ walkedAdGroupIds.add(adGroup.id);
30758
+ const adGroupMetrics = await callProvider(
30759
+ "ad group insights",
30760
+ adGroup.id,
30761
+ () => reader.adGroupInsights(apiKey, adGroup.id, insightsRequest)
30762
+ );
30763
+ observations.push({
30764
+ entityType: AdsLiveEntityTypes.ad_group,
30765
+ id: adGroup.id,
30766
+ parentId: campaign.id,
30767
+ live: liveEntityState(adGroup),
30768
+ stored: storedAdGroupById.has(adGroup.id) ? storedEntityState(storedAdGroupById.get(adGroup.id)) : null,
30769
+ liveMetrics: adGroupMetrics,
30770
+ storedMetrics: adGroupMetrics === null ? null : storedMetricsFor(AdsInsightLevels.ad_group, adGroup.id)
30771
+ });
30772
+ const liveAds = await callProvider(
30773
+ "ad list",
30774
+ adGroup.id,
30775
+ () => reader.listAds(apiKey, adGroup.id)
30776
+ );
30777
+ if (liveAds === null) continue;
30778
+ if (liveAds.length > ADS_LIVE_MAX_ADS_PER_AD_GROUP) truncated = true;
30779
+ for (const ad of liveAds.slice(0, ADS_LIVE_MAX_ADS_PER_AD_GROUP)) {
30780
+ walkedAdIds.add(ad.id);
30781
+ observations.push({
30782
+ entityType: AdsLiveEntityTypes.ad,
30783
+ id: ad.id,
30784
+ parentId: adGroup.id,
30785
+ live: liveEntityState(ad),
30786
+ stored: storedAdById.has(ad.id) ? storedEntityState(storedAdById.get(ad.id)) : null,
30787
+ // The provider has no per-ad insights surface (same reason ads-sync
30788
+ // stores no ad-level rollups), so ads carry status evidence only.
30789
+ liveMetrics: null,
30790
+ storedMetrics: null
30791
+ });
30792
+ }
30793
+ }
30794
+ }
30795
+ if (!truncated && errors.length === 0) {
30796
+ for (const row of storedCampaignRows) {
30797
+ if (walkedCampaignIds.has(row.id)) continue;
30798
+ observations.push({
30799
+ entityType: AdsLiveEntityTypes.campaign,
30800
+ id: row.id,
30801
+ parentId: null,
30802
+ live: null,
30803
+ stored: storedEntityState(row),
30804
+ liveMetrics: null,
30805
+ storedMetrics: null
30806
+ });
30807
+ }
30808
+ for (const row of storedAdGroupRows) {
30809
+ if (walkedAdGroupIds.has(row.id)) continue;
30810
+ observations.push({
30811
+ entityType: AdsLiveEntityTypes.ad_group,
30812
+ id: row.id,
30813
+ parentId: row.campaignId,
30814
+ live: null,
30815
+ stored: storedEntityState(row),
30816
+ liveMetrics: null,
30817
+ storedMetrics: null
30818
+ });
30819
+ }
30820
+ for (const row of storedAdRows) {
30821
+ if (walkedAdIds.has(row.id)) continue;
30822
+ observations.push({
30823
+ entityType: AdsLiveEntityTypes.ad,
30824
+ id: row.id,
30825
+ parentId: row.adGroupId,
30826
+ live: null,
30827
+ stored: storedEntityState(row),
30828
+ liveMetrics: null,
30829
+ storedMetrics: null
30830
+ });
30831
+ }
30832
+ }
30833
+ const entities = observations.map(
30834
+ (observation) => buildLiveEntityComparison(observation, window)
30835
+ );
30836
+ const response = {
30837
+ basis: AdsLiveDeliveryBases.liveProviderRead,
30838
+ fetchedAt: new Date(fetchedAtMs).toISOString(),
30839
+ adAccountId: account.id,
30840
+ storedSnapshotSyncedAt: connection.lastSyncedAt ?? null,
30841
+ metricsWindow: { lookbackDays },
30842
+ bounds: {
30843
+ maxCampaigns: ADS_LIVE_MAX_CAMPAIGNS,
30844
+ maxAdGroupsPerCampaign: ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN,
30845
+ maxAdsPerAdGroup: ADS_LIVE_MAX_ADS_PER_AD_GROUP,
30846
+ maxReaderCalls: ADS_LIVE_MAX_READER_CALLS,
30847
+ readerCalls,
30848
+ maxPagesPerReaderCall: liveDeliveryMaxPagesPerReaderCall,
30849
+ maxUpstreamHttpRequests: ADS_LIVE_MAX_READER_CALLS * liveDeliveryMaxPagesPerReaderCall,
30850
+ truncated
30851
+ },
30852
+ entities,
30853
+ drift: summarizeLiveDrift(entities),
30854
+ errors
30855
+ };
30856
+ return response;
30857
+ } finally {
30858
+ liveDeliveryInFlight.delete(project.id);
30859
+ }
30860
+ });
30129
30861
  app.get("/projects/:name/ads/summary", async (request) => {
30130
30862
  const project = resolveProject(app.db, request.params.name);
30131
30863
  const row = app.db.select().from(adsConnections).where(eq28(adsConnections.projectId, project.id)).get();
@@ -30174,7 +30906,7 @@ async function adsRoutes(app, opts) {
30174
30906
 
30175
30907
  // ../api-routes/src/bing.ts
30176
30908
  import crypto23 from "crypto";
30177
- import { eq as eq29, and as and23, desc as desc13 } from "drizzle-orm";
30909
+ import { eq as eq29, and as and23, desc as desc14 } from "drizzle-orm";
30178
30910
 
30179
30911
  // ../integration-bing/src/constants.ts
30180
30912
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -30548,7 +31280,7 @@ async function bingRoutes(app, opts) {
30548
31280
  const store = requireConnectionStore();
30549
31281
  const project = resolveProject(app.db, request.params.name);
30550
31282
  requireConnection(store, project.canonicalDomain);
30551
- const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(desc13(bingUrlInspections.inspectedAt)).all();
31283
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(desc14(bingUrlInspections.inspectedAt)).all();
30552
31284
  const latestByUrl = /* @__PURE__ */ new Map();
30553
31285
  const definitiveByUrl = /* @__PURE__ */ new Map();
30554
31286
  for (const row of allInspections) {
@@ -30637,7 +31369,7 @@ async function bingRoutes(app, opts) {
30637
31369
  const project = resolveProject(app.db, request.params.name);
30638
31370
  const parsed = parseInt(request.query.limit ?? "90", 10);
30639
31371
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
30640
- const rows = app.db.select().from(bingCoverageSnapshots).where(eq29(bingCoverageSnapshots.projectId, project.id)).orderBy(desc13(bingCoverageSnapshots.date)).limit(limit).all();
31372
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq29(bingCoverageSnapshots.projectId, project.id)).orderBy(desc14(bingCoverageSnapshots.date)).limit(limit).all();
30641
31373
  return rows.map((r) => ({
30642
31374
  date: r.date,
30643
31375
  indexed: r.indexed,
@@ -30650,7 +31382,7 @@ async function bingRoutes(app, opts) {
30650
31382
  const project = resolveProject(app.db, request.params.name);
30651
31383
  const { url, limit } = request.query;
30652
31384
  const whereClause = url ? and23(eq29(bingUrlInspections.projectId, project.id), eq29(bingUrlInspections.url, url)) : eq29(bingUrlInspections.projectId, project.id);
30653
- const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc13(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
31385
+ const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc14(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
30654
31386
  return filtered.map((r) => ({
30655
31387
  id: r.id,
30656
31388
  url: r.url,
@@ -30794,7 +31526,7 @@ async function bingRoutes(app, opts) {
30794
31526
  }
30795
31527
  let urlsToSubmit = request.body?.urls ?? [];
30796
31528
  if (request.body?.allUnindexed) {
30797
- const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(desc13(bingUrlInspections.inspectedAt)).all();
31529
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(desc14(bingUrlInspections.inspectedAt)).all();
30798
31530
  const latestByUrl = /* @__PURE__ */ new Map();
30799
31531
  for (const row of allInspections) {
30800
31532
  if (!latestByUrl.has(row.url)) {
@@ -31053,7 +31785,7 @@ async function cdpRoutes(app, opts) {
31053
31785
 
31054
31786
  // ../api-routes/src/ga.ts
31055
31787
  import crypto24 from "crypto";
31056
- import { eq as eq31, desc as desc14, and as and25, sql as sql15 } from "drizzle-orm";
31788
+ import { eq as eq31, desc as desc15, and as and25, sql as sql15 } from "drizzle-orm";
31057
31789
 
31058
31790
  // ../api-routes/src/ga-session-history.ts
31059
31791
  function buildSessionHistory(landingPageDays, dailyTotals) {
@@ -31500,7 +32232,7 @@ async function ga4Routes(app, opts) {
31500
32232
  if (!connected) {
31501
32233
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
31502
32234
  }
31503
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(desc14(gaTrafficSummaries.syncedAt)).limit(1).get();
32235
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(desc15(gaTrafficSummaries.syncedAt)).limit(1).get();
31504
32236
  return {
31505
32237
  connected: true,
31506
32238
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -31906,7 +32638,7 @@ async function ga4Routes(app, opts) {
31906
32638
  sessions: sql15`SUM(${gaSocialReferrals.sessions})`,
31907
32639
  users: sql15`SUM(${gaSocialReferrals.users})`
31908
32640
  }).from(gaSocialReferrals).where(and25(...socialConditions)).get();
31909
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(desc14(gaTrafficSummaries.syncedAt)).limit(1).get();
32641
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(desc15(gaTrafficSummaries.syncedAt)).limit(1).get();
31910
32642
  const total = summaryRow?.totalSessions ?? 0;
31911
32643
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
31912
32644
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -33851,7 +34583,7 @@ async function wordpressRoutes(app, opts) {
33851
34583
 
33852
34584
  // ../api-routes/src/backlinks.ts
33853
34585
  import crypto26 from "crypto";
33854
- import { and as and27, asc as asc7, desc as desc15, eq as eq32, sql as sql16 } from "drizzle-orm";
34586
+ import { and as and27, asc as asc7, desc as desc16, eq as eq32, sql as sql16 } from "drizzle-orm";
33855
34587
 
33856
34588
  // ../integration-commoncrawl/src/constants.ts
33857
34589
  import os2 from "os";
@@ -34352,7 +35084,7 @@ function latestSummaryForProject(db, projectId, source, release) {
34352
35084
  eq32(backlinkSummaries.source, source)
34353
35085
  ];
34354
35086
  if (release) conditions.push(eq32(backlinkSummaries.release, release));
34355
- return db.select().from(backlinkSummaries).where(and27(...conditions)).orderBy(desc15(backlinkSummaries.queriedAt)).limit(1).get();
35087
+ return db.select().from(backlinkSummaries).where(and27(...conditions)).orderBy(desc16(backlinkSummaries.queriedAt)).limit(1).get();
34356
35088
  }
34357
35089
  function parseExcludeCrawlers(value) {
34358
35090
  if (!value) return false;
@@ -34374,7 +35106,7 @@ function computeFilteredSummary(db, base) {
34374
35106
  count: sql16`count(*)`,
34375
35107
  total: sql16`coalesce(sum(${backlinkDomains.numHosts}), 0)`
34376
35108
  }).from(backlinkDomains).where(filteredCondition).get();
34377
- const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc15(backlinkDomains.numHosts)).limit(10).all();
35109
+ const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc16(backlinkDomains.numHosts)).limit(10).all();
34378
35110
  const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
34379
35111
  const totalHosts = Number(filteredAgg?.total ?? 0);
34380
35112
  const unfilteredLinkingDomains = Number(unfilteredAgg?.count ?? 0);
@@ -34395,7 +35127,7 @@ function computeFilteredSummary(db, base) {
34395
35127
  };
34396
35128
  }
34397
35129
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
34398
- const summary = db.select().from(backlinkSummaries).where(and27(eq32(backlinkSummaries.projectId, projectId), eq32(backlinkSummaries.source, source))).orderBy(desc15(backlinkSummaries.queriedAt)).limit(1).get();
35130
+ const summary = db.select().from(backlinkSummaries).where(and27(eq32(backlinkSummaries.projectId, projectId), eq32(backlinkSummaries.source, source))).orderBy(desc16(backlinkSummaries.queriedAt)).limit(1).get();
34399
35131
  let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
34400
35132
  if (summary && excludeCrawlers) {
34401
35133
  const filtered = db.select({ count: sql16`count(*)` }).from(backlinkDomains).where(and27(
@@ -34501,11 +35233,11 @@ async function backlinksRoutes(app, opts) {
34501
35233
  return reply.status(201).send(mapSyncRow(inserted));
34502
35234
  });
34503
35235
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
34504
- const row = app.db.select().from(ccReleaseSyncs).orderBy(desc15(ccReleaseSyncs.updatedAt)).limit(1).get();
35236
+ const row = app.db.select().from(ccReleaseSyncs).orderBy(desc16(ccReleaseSyncs.updatedAt)).limit(1).get();
34505
35237
  return reply.send(row ? mapSyncRow(row) : null);
34506
35238
  });
34507
35239
  app.get("/backlinks/syncs", async (_request, reply) => {
34508
- const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc15(ccReleaseSyncs.updatedAt)).all();
35240
+ const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc16(ccReleaseSyncs.updatedAt)).all();
34509
35241
  return reply.send(rows.map(mapSyncRow));
34510
35242
  });
34511
35243
  app.get("/backlinks/releases", async (_request, reply) => {
@@ -34592,7 +35324,7 @@ async function backlinksRoutes(app, opts) {
34592
35324
  linkingDomain: backlinkDomains.linkingDomain,
34593
35325
  numHosts: backlinkDomains.numHosts,
34594
35326
  source: backlinkDomains.source
34595
- }).from(backlinkDomains).where(domainCondition).orderBy(desc15(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
35327
+ }).from(backlinkDomains).where(domainCondition).orderBy(desc16(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
34596
35328
  let summary = null;
34597
35329
  if (summaryRow) {
34598
35330
  summary = excludeCrawlers ? computeFilteredSummary(app.db, summaryRow) : mapSummaryRow(summaryRow);
@@ -34666,7 +35398,7 @@ async function backlinksRoutes(app, opts) {
34666
35398
  // ../api-routes/src/traffic.ts
34667
35399
  import crypto28 from "crypto";
34668
35400
  import { Agent as UndiciAgent } from "undici";
34669
- import { and as and28, desc as desc16, eq as eq33, gte as gte9, lte as lte7, sql as sql17 } from "drizzle-orm";
35401
+ import { and as and28, desc as desc17, eq as eq33, gte as gte9, lte as lte7, sql as sql17 } from "drizzle-orm";
34670
35402
 
34671
35403
  // ../integration-cloud-run/src/auth.ts
34672
35404
  import crypto27 from "crypto";
@@ -39768,7 +40500,7 @@ async function trafficRoutes(app, opts) {
39768
40500
  eq33(runs.kind, RunKinds["traffic-sync"]),
39769
40501
  eq33(runs.sourceId, row.id)
39770
40502
  )
39771
- ).orderBy(desc16(runs.startedAt)).limit(1).get();
40503
+ ).orderBy(desc17(runs.startedAt)).limit(1).get();
39772
40504
  return {
39773
40505
  ...rowToDto(row),
39774
40506
  totals24h: {
@@ -39828,14 +40560,14 @@ async function trafficRoutes(app, opts) {
39828
40560
  });
39829
40561
  app.get("/projects/:name/traffic/sources", async (request) => {
39830
40562
  const project = resolveProject(app.db, request.params.name);
39831
- const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(desc16(trafficSources.createdAt)).all();
40563
+ const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(desc17(trafficSources.createdAt)).all();
39832
40564
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
39833
40565
  const response = { sources };
39834
40566
  return response;
39835
40567
  });
39836
40568
  app.get("/projects/:name/traffic/status", async (request) => {
39837
40569
  const project = resolveProject(app.db, request.params.name);
39838
- const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(desc16(trafficSources.createdAt)).all();
40570
+ const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(desc17(trafficSources.createdAt)).all();
39839
40571
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
39840
40572
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
39841
40573
  const response = { sources };
@@ -39934,7 +40666,7 @@ async function trafficRoutes(app, opts) {
39934
40666
  for (const row of crawlerSeries) {
39935
40667
  trafficSeriesPoint(seriesByBucket, row.bucket).crawlerHits = Number(row.hits);
39936
40668
  }
39937
- const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc16(crawlerEventsHourly.tsHour)).limit(limit).all();
40669
+ const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc17(crawlerEventsHourly.tsHour)).limit(limit).all();
39938
40670
  for (const r of rows) {
39939
40671
  events.push({
39940
40672
  kind: TrafficEventKinds.crawler,
@@ -39972,7 +40704,7 @@ async function trafficRoutes(app, opts) {
39972
40704
  for (const row of userFetchSeries) {
39973
40705
  trafficSeriesPoint(seriesByBucket, row.bucket).aiUserFetchHits = Number(row.hits);
39974
40706
  }
39975
- const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc16(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
40707
+ const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc17(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
39976
40708
  for (const r of rows) {
39977
40709
  events.push({
39978
40710
  kind: TrafficEventKinds["ai-user-fetch"],
@@ -40015,7 +40747,7 @@ async function trafficRoutes(app, opts) {
40015
40747
  for (const row of referralSeries) {
40016
40748
  trafficSeriesPoint(seriesByBucket, row.bucket).aiReferralHits = Number(row.hits);
40017
40749
  }
40018
- const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc16(aiReferralEventsHourly.tsHour)).limit(limit).all();
40750
+ const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc17(aiReferralEventsHourly.tsHour)).limit(limit).all();
40019
40751
  for (const r of rows) {
40020
40752
  const counts2 = aiReferralClassCounts(r.sessionsOrHits, r.paidSessionsOrHits, r.organicSessionsOrHits);
40021
40753
  events.push({
@@ -42251,7 +42983,7 @@ async function doctorRoutes(app, opts) {
42251
42983
 
42252
42984
  // ../api-routes/src/discovery/routes.ts
42253
42985
  import crypto30 from "crypto";
42254
- import { and as and32, desc as desc17, eq as eq40, gte as gte11, inArray as inArray13, isNull as isNull3, or as or9 } from "drizzle-orm";
42986
+ import { and as and32, desc as desc18, eq as eq40, gte as gte11, inArray as inArray13, isNull as isNull3, or as or9 } from "drizzle-orm";
42255
42987
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
42256
42988
  async function discoveryRoutes(app, opts) {
42257
42989
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -42312,7 +43044,7 @@ async function discoveryRoutes(app, opts) {
42312
43044
  DiscoverySessionStatuses.probing
42313
43045
  ]),
42314
43046
  gte11(discoverySessions.createdAt, ageFloorIso)
42315
- )).orderBy(desc17(discoverySessions.createdAt)).get();
43047
+ )).orderBy(desc18(discoverySessions.createdAt)).get();
42316
43048
  if (existing && existing.runId) {
42317
43049
  return { reused: true, sessionId: existing.id, runId: existing.runId };
42318
43050
  }
@@ -42381,7 +43113,7 @@ async function discoveryRoutes(app, opts) {
42381
43113
  const project = resolveProject(app.db, request.params.name);
42382
43114
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
42383
43115
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
42384
- const rows = app.db.select().from(discoverySessions).where(eq40(discoverySessions.projectId, project.id)).orderBy(desc17(discoverySessions.createdAt)).limit(limit).all();
43116
+ const rows = app.db.select().from(discoverySessions).where(eq40(discoverySessions.projectId, project.id)).orderBy(desc18(discoverySessions.createdAt)).limit(limit).all();
42385
43117
  return reply.send(rows.map(serializeSession));
42386
43118
  }
42387
43119
  );
@@ -42931,7 +43663,7 @@ function dedupeStrings(input) {
42931
43663
 
42932
43664
  // ../api-routes/src/technical-aeo.ts
42933
43665
  import crypto32 from "crypto";
42934
- import { and as and33, asc as asc8, count, desc as desc18, eq as eq42, inArray as inArray14, lt as lt4 } from "drizzle-orm";
43666
+ import { and as and33, asc as asc8, count, desc as desc19, eq as eq42, inArray as inArray14, lt as lt4 } from "drizzle-orm";
42935
43667
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
42936
43668
  function emptyScore(projectName) {
42937
43669
  return {
@@ -42970,13 +43702,13 @@ async function technicalAeoRoutes(app, opts) {
42970
43702
  notProbeRun()
42971
43703
  ];
42972
43704
  const targetFilters = request.query.runId ? [...baseFilters, eq42(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
42973
- const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(desc18(siteAuditSnapshots.createdAt)).limit(1).get();
43705
+ const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(1).get();
42974
43706
  if (!latest) {
42975
43707
  if (request.query.runId) throw notFound("Site audit run", request.query.runId);
42976
43708
  return emptyScore(project.name);
42977
43709
  }
42978
43710
  const snap = latest.snap;
42979
- const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...baseFilters, lt4(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc18(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
43711
+ const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...baseFilters, lt4(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
42980
43712
  const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
42981
43713
  const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
42982
43714
  return {
@@ -43009,7 +43741,7 @@ async function technicalAeoRoutes(app, opts) {
43009
43741
  notProbeRun()
43010
43742
  ];
43011
43743
  if (request.query.runId) targetFilters.push(eq42(siteAuditSnapshots.runId, request.query.runId));
43012
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(desc18(siteAuditSnapshots.createdAt)).limit(1).get();
43744
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(1).get();
43013
43745
  if (!latest && request.query.runId) {
43014
43746
  throw notFound("Site audit run", request.query.runId);
43015
43747
  }
@@ -43024,7 +43756,7 @@ async function technicalAeoRoutes(app, opts) {
43024
43756
  const total = totalRow?.value ?? 0;
43025
43757
  const limit = parsePositiveInt(request.query.limit, 100, 500);
43026
43758
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
43027
- const orderBy = request.query.sort === "score-desc" ? desc18(siteAuditPages.overallScore) : request.query.sort === "url" ? asc8(siteAuditPages.url) : asc8(siteAuditPages.overallScore);
43759
+ const orderBy = request.query.sort === "score-desc" ? desc19(siteAuditPages.overallScore) : request.query.sort === "url" ? asc8(siteAuditPages.url) : asc8(siteAuditPages.overallScore);
43028
43760
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
43029
43761
  const pages = rows.map((row) => ({
43030
43762
  url: row.url,
@@ -43048,7 +43780,7 @@ async function technicalAeoRoutes(app, opts) {
43048
43780
  eq42(runs.kind, RunKinds["site-audit"]),
43049
43781
  inArray14(runs.status, SURFACEABLE_STATUSES),
43050
43782
  notProbeRun()
43051
- )).orderBy(desc18(siteAuditSnapshots.createdAt)).limit(limit).all();
43783
+ )).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(limit).all();
43052
43784
  return { project: project.name, points: rows.reverse() };
43053
43785
  });
43054
43786
  app.post("/projects/:name/technical-aeo/runs", async (request) => {
@@ -43085,7 +43817,7 @@ async function technicalAeoRoutes(app, opts) {
43085
43817
 
43086
43818
  // ../api-routes/src/research.ts
43087
43819
  import crypto33 from "crypto";
43088
- import { and as and34, desc as desc19, eq as eq43 } from "drizzle-orm";
43820
+ import { and as and34, desc as desc20, eq as eq43 } from "drizzle-orm";
43089
43821
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
43090
43822
  async function researchRoutes(app, opts) {
43091
43823
  app.post("/projects/:name/research/runs", async (request, reply) => {
@@ -43136,7 +43868,7 @@ async function researchRoutes(app, opts) {
43136
43868
  const project = resolveProject(app.db, request.params.name);
43137
43869
  const requested = Number.parseInt(request.query.limit ?? "", 10);
43138
43870
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
43139
- const runs2 = app.db.select().from(researchRuns).where(eq43(researchRuns.projectId, project.id)).orderBy(desc19(researchRuns.createdAt)).limit(limit).all().map(serializeRun);
43871
+ const runs2 = app.db.select().from(researchRuns).where(eq43(researchRuns.projectId, project.id)).orderBy(desc20(researchRuns.createdAt)).limit(limit).all().map(serializeRun);
43140
43872
  return { runs: runs2 };
43141
43873
  });
43142
43874
  app.get("/projects/:name/research/runs/:runId", async (request) => {
@@ -43154,7 +43886,7 @@ function serializeRun(row) {
43154
43886
  return { id: row.id, projectId: row.projectId, status: row.status, provider: row.provider, requestedModel: row.requestedModel, resolvedModel: row.resolvedModel, location: row.location ?? null, totalQueries: row.totalQueries, completedQueries: row.completedQueries, failedQueries: row.failedQueries, error: row.error, startedAt: row.startedAt, finishedAt: row.finishedAt, createdAt: row.createdAt };
43155
43887
  }
43156
43888
  function serializeQuery(row) {
43157
- return { id: row.id, position: row.position, query: row.queryText, status: row.status, requestedModel: row.requestedModel, resolvedModel: row.resolvedModel, servedModel: row.servedModel, answerText: row.answerText, groundingSources: row.groundingSources, citedDomains: row.citedDomains, searchQueries: row.searchQueries, answerMentioned: row.answerMentioned, citationState: row.citationState, error: row.error, startedAt: row.startedAt, finishedAt: row.finishedAt, createdAt: row.createdAt };
43889
+ return { id: row.id, position: row.position, query: row.queryText, status: row.status, requestedModel: row.requestedModel, resolvedModel: row.resolvedModel, servedModel: row.servedModel, answerText: row.answerText, groundingSources: row.groundingSources, citedDomains: row.citedDomains, searchQueries: row.searchQueries, namedCompetitors: row.namedCompetitors, citedCompetitorDomains: row.citedCompetitorDomains, answerMentioned: row.answerMentioned, citationState: row.citationState, error: row.error, startedAt: row.startedAt, finishedAt: row.finishedAt, createdAt: row.createdAt };
43158
43890
  }
43159
43891
 
43160
43892
  // ../api-routes/src/index.ts
@@ -43275,6 +44007,9 @@ async function apiRoutes(app, opts) {
43275
44007
  adsCredentialStore: opts.adsCredentialStore,
43276
44008
  verifyAdsAccount: opts.verifyAdsAccount,
43277
44009
  adsReader: opts.adsReader,
44010
+ adsLiveDeliveryReader: opts.adsLiveDeliveryReader,
44011
+ adsLiveDeliveryMinIntervalMs: opts.adsLiveDeliveryMinIntervalMs,
44012
+ adsLiveDeliveryMaxPagesPerReaderCall: opts.adsLiveDeliveryMaxPagesPerReaderCall,
43278
44013
  onAdsSyncRequested: opts.onAdsSyncRequested,
43279
44014
  adsOperator: opts.adsOperator,
43280
44015
  adsReconcileSweepIntervalMs: opts.adsReconcileSweepIntervalMs,
@@ -43618,6 +44353,15 @@ function computeCompetitorOverlap(normalized, competitorDomains) {
43618
44353
  }
43619
44354
  return [...overlapSet];
43620
44355
  }
44356
+ function computeCitedCompetitorDomains(citedDomains, competitorDomains) {
44357
+ const citedCompetitors = /* @__PURE__ */ new Set();
44358
+ for (const citedDomain of citedDomains) {
44359
+ for (const competitorDomain of competitorDomains) {
44360
+ if (domainMatches(citedDomain, competitorDomain)) citedCompetitors.add(competitorDomain);
44361
+ }
44362
+ }
44363
+ return [...citedCompetitors];
44364
+ }
43621
44365
  function escapeRegExp5(value) {
43622
44366
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
43623
44367
  }
@@ -43678,7 +44422,7 @@ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, com
43678
44422
  for (const pattern of candidatePatterns) {
43679
44423
  let match;
43680
44424
  while ((match = pattern.exec(answerText)) !== null) {
43681
- const candidate = cleanCandidateName(match[1] ?? "");
44425
+ const candidate = cleanCandidateName(match[1]);
43682
44426
  const candidateKey = brandKeyFromText(candidate);
43683
44427
  if (!candidateKey) continue;
43684
44428
  if (genericKeys.has(candidateKey)) continue;
@@ -43752,7 +44496,7 @@ var IntelligenceService = class {
43752
44496
  // intelligence window.
43753
44497
  ne6(runs.trigger, RunTriggers.probe)
43754
44498
  )
43755
- ).orderBy(desc20(runs.finishedAt), desc20(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
44499
+ ).orderBy(desc21(runs.finishedAt), desc21(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
43756
44500
  if (recentRuns.length === 0) {
43757
44501
  log.info("intelligence.skip", { runId, reason: "no completed runs" });
43758
44502
  return null;
@@ -43874,10 +44618,10 @@ var IntelligenceService = class {
43874
44618
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
43875
44619
  const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and35(eq44(gbpDailyMetrics.projectId, projectId), eq44(gbpDailyMetrics.locationName, locationName))).all();
43876
44620
  const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and35(eq44(gbpPlaceActions.projectId, projectId), eq44(gbpPlaceActions.locationName, locationName))).all();
43877
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and35(eq44(gbpLodgingSnapshots.projectId, projectId), eq44(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc20(gbpLodgingSnapshots.syncedAt)).limit(1).get();
44621
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and35(eq44(gbpLodgingSnapshots.projectId, projectId), eq44(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc21(gbpLodgingSnapshots.syncedAt)).limit(1).get();
43878
44622
  const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and35(eq44(gbpLocations.projectId, projectId), eq44(gbpLocations.locationName, locationName))).get();
43879
44623
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
43880
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and35(eq44(gbpPlaceDetails.projectId, projectId), eq44(gbpPlaceDetails.locationName, locationName))).orderBy(desc20(gbpPlaceDetails.syncedAt)).limit(1).get();
44624
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and35(eq44(gbpPlaceDetails.projectId, projectId), eq44(gbpPlaceDetails.locationName, locationName))).orderBy(desc21(gbpPlaceDetails.syncedAt)).limit(1).get();
43881
44625
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
43882
44626
  const summary = buildGbpSummary({
43883
44627
  locationName,
@@ -44230,7 +44974,7 @@ var IntelligenceService = class {
44230
44974
  // Defensive — see top of file.
44231
44975
  ne6(runs.trigger, RunTriggers.probe)
44232
44976
  )
44233
- ).orderBy(desc20(runs.createdAt), desc20(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
44977
+ ).orderBy(desc21(runs.createdAt), desc21(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
44234
44978
  const recentGroups = groupRunsByCreatedAt(recentRunRows);
44235
44979
  const recentRunIds = [];
44236
44980
  const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
@@ -44393,6 +45137,7 @@ export {
44393
45137
  classifyAiUserFetch,
44394
45138
  determineCitationState,
44395
45139
  computeCompetitorOverlap,
45140
+ computeCitedCompetitorDomains,
44396
45141
  extractRecommendedCompetitors,
44397
45142
  getPlaceDetails,
44398
45143
  hashPlaceDetails,