@canonry/canonry 4.152.0 → 4.154.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 (29) hide show
  1. package/assets/assets/{AuditHistoryPanel-DK52bxkQ.js → AuditHistoryPanel-WWT1n8jp.js} +1 -1
  2. package/assets/assets/{BacklinksPage-DYApSbsM.js → BacklinksPage-DIjUnsVL.js} +1 -1
  3. package/assets/assets/{HistoryPage-CksKbEHx.js → HistoryPage-CrIBhGuW.js} +1 -1
  4. package/assets/assets/{MeasurementPropertyPage-Ds5A7BiY.js → MeasurementPropertyPage-uN4BWigS.js} +1 -1
  5. package/assets/assets/ProjectPage-BR5ECwUP.js +12 -0
  6. package/assets/assets/{RunRow-C0oX3797.js → RunRow-OE5tiuih.js} +1 -1
  7. package/assets/assets/{RunsPage-0qr4V3pn.js → RunsPage-C6xBmjps.js} +1 -1
  8. package/assets/assets/{SettingsPage-BLMy_cfR.js → SettingsPage-CUjjr8U0.js} +1 -1
  9. package/assets/assets/{TrafficPage-CZj9XGfC.js → TrafficPage-DrEfsHiE.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-BfQAuIQM.js → TrafficSourceDetailPage-B3DX5BEk.js} +1 -1
  11. package/assets/assets/{arrow-left-DdovB6GZ.js → arrow-left-CLmqvCqU.js} +1 -1
  12. package/assets/assets/{extract-error-message-yI0U8t_A.js → extract-error-message-BEBd1a5v.js} +1 -1
  13. package/assets/assets/{index-DP74nMja.js → index-CsM-Xxia.js} +68 -68
  14. package/assets/assets/{index-CiidhUL0.css → index-DyL54s8x.css} +1 -1
  15. package/assets/assets/{refresh-cw-SR8k0mBu.js → refresh-cw-DYSS2Wd-.js} +1 -1
  16. package/assets/assets/{trash-2-DQF9iFSX.js → trash-2-VoiF_ckU.js} +1 -1
  17. package/assets/assets/v2-overview-adapter-COMP5UGt.js +1 -0
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-TS3AHKZG.js → chunk-ANSQ6JNO.js} +1 -1
  20. package/dist/{chunk-JWTFZG7Q.js → chunk-IFZS2TXV.js} +154 -83
  21. package/dist/{chunk-BUAVO37B.js → chunk-NNW7TJUZ.js} +15 -0
  22. package/dist/{chunk-G7P5CJJS.js → chunk-X5I7CAXH.js} +172 -77
  23. package/dist/cli.js +4 -4
  24. package/dist/index.js +4 -4
  25. package/dist/{intelligence-service-KBFVQOPO.js → intelligence-service-B5AETQ5N.js} +2 -2
  26. package/dist/mcp.js +2 -2
  27. package/package.json +7 -7
  28. package/assets/assets/ProjectPage-LzCmnrO2.js +0 -12
  29. package/assets/assets/v2-overview-adapter-Dz0UmhmS.js +0 -1
@@ -521,7 +521,7 @@ import {
521
521
  wordpressSchemaDeployResultDtoSchema,
522
522
  wordpressSchemaStatusResultDtoSchema,
523
523
  wordpressStatusDtoSchema
524
- } from "./chunk-BUAVO37B.js";
524
+ } from "./chunk-NNW7TJUZ.js";
525
525
 
526
526
  // src/intelligence-service.ts
527
527
  import { eq as eq57, desc as desc26, asc as asc11, and as and45, ne as ne7, or as or11, inArray as inArray19, gte as gte13, lte as lte10 } from "drizzle-orm";
@@ -15721,6 +15721,31 @@ function validatedCursor(query, activePlanVersionId) {
15721
15721
  }
15722
15722
  return cursor;
15723
15723
  }
15724
+ function measurementOutcomeCounts(rows) {
15725
+ const counts2 = {
15726
+ bothSignals: 0,
15727
+ mentionedOnly: 0,
15728
+ citedOnly: 0,
15729
+ neither: 0,
15730
+ notMeasured: 0,
15731
+ total: rows.length
15732
+ };
15733
+ for (const row of rows) {
15734
+ const mention = row.mentionCoverage;
15735
+ const citation = row.citationCoverage;
15736
+ if (mention.state !== "available" || citation.state !== "available") {
15737
+ counts2.notMeasured += 1;
15738
+ continue;
15739
+ }
15740
+ const mentioned = (mention.numerator ?? mention.value) > 0;
15741
+ const cited = (citation.numerator ?? citation.value) > 0;
15742
+ if (mentioned && cited) counts2.bothSignals += 1;
15743
+ else if (mentioned) counts2.mentionedOnly += 1;
15744
+ else if (cited) counts2.citedOnly += 1;
15745
+ else counts2.neither += 1;
15746
+ }
15747
+ return counts2;
15748
+ }
15724
15749
  function pageOf(rows, query, displayedRunId, activePlanVersionId, evidenceFingerprint) {
15725
15750
  const sort = query.sort ?? MEASUREMENT_OVERVIEW_DEFAULT_SORT;
15726
15751
  const ordered = [...rows].sort((left, right) => compareRows(left, right, sort));
@@ -15738,16 +15763,19 @@ function pageOf(rows, query, displayedRunId, activePlanVersionId, evidenceFinger
15738
15763
  const items = ordered.slice(offset, offset + limit);
15739
15764
  const last = items.at(-1);
15740
15765
  return {
15741
- items,
15742
- nextCursor: last === void 0 || ordered.at(offset + limit) === void 0 ? null : cursorOf(
15743
- last,
15744
- sort,
15745
- displayedRunId,
15746
- overviewFilterFingerprint(query),
15747
- activePlanVersionId,
15748
- evidenceFingerprint
15749
- ),
15750
- totalEstimate: ordered.length
15766
+ page: {
15767
+ items,
15768
+ nextCursor: last === void 0 || ordered.at(offset + limit) === void 0 ? null : cursorOf(
15769
+ last,
15770
+ sort,
15771
+ displayedRunId,
15772
+ overviewFilterFingerprint(query),
15773
+ activePlanVersionId,
15774
+ evidenceFingerprint
15775
+ ),
15776
+ totalEstimate: ordered.length
15777
+ },
15778
+ outcomes: measurementOutcomeCounts(ordered)
15751
15779
  };
15752
15780
  }
15753
15781
  function runRevisionMismatch(runId, runRevision, activeRevision) {
@@ -15879,7 +15907,7 @@ function runProgress(db, displayed, plan) {
15879
15907
  }
15880
15908
  function planV1Overview(db, projectId, active, query, scope) {
15881
15909
  const displayed = selectDisplayedRun(db, projectId, active, query);
15882
- const page = pageOf(
15910
+ const { page, outcomes } = pageOf(
15883
15911
  propertyLabels(active.plan, scope).filter((row) => matchesSearch(row, query.search)).map((row) => ({
15884
15912
  ...row,
15885
15913
  mentionCoverage: unavailable2("plan_v1"),
@@ -15913,6 +15941,7 @@ function planV1Overview(db, projectId, active, query, scope) {
15913
15941
  sov: unavailable2("plan_v1")
15914
15942
  },
15915
15943
  properties: page,
15944
+ outcomes,
15916
15945
  flags: { total: 0 }
15917
15946
  };
15918
15947
  }
@@ -15922,7 +15951,7 @@ function planV2Overview(db, projectId, active, plan, query, scope, cache) {
15922
15951
  const current = latestMeasurementRun(db, projectId, active.version.id, CURRENT_RUN_STATUSES);
15923
15952
  const currentDto = current ? { currentRunId: current.id } : {};
15924
15953
  if (!displayed) {
15925
- const page2 = pageOf(
15954
+ const { page: page2, outcomes: outcomes2 } = pageOf(
15926
15955
  propertyLabels(plan, scope).filter((row) => matchesSearch(row, query.search)).map((row) => ({
15927
15956
  ...row,
15928
15957
  mentionCoverage: unavailable2("no_completed_run"),
@@ -15954,6 +15983,7 @@ function planV2Overview(db, projectId, active, plan, query, scope, cache) {
15954
15983
  sov: unavailable2("no_completed_run")
15955
15984
  },
15956
15985
  properties: page2,
15986
+ outcomes: outcomes2,
15957
15987
  flags: { total: 0 }
15958
15988
  };
15959
15989
  }
@@ -15982,7 +16012,7 @@ function planV2Overview(db, projectId, active, plan, query, scope, cache) {
15982
16012
  const measured = new Map(
15983
16013
  overview.properties.map((row) => [row.targetId, row])
15984
16014
  );
15985
- const page = pageOf(
16015
+ const { page, outcomes } = pageOf(
15986
16016
  propertyLabels(plan, scope).filter((row) => matchesSearch(row, query.search)).map((row) => {
15987
16017
  const property = measured.get(row.targetKey);
15988
16018
  return {
@@ -16036,6 +16066,7 @@ function planV2Overview(db, projectId, active, plan, query, scope, cache) {
16036
16066
  sov: brandPresence
16037
16067
  },
16038
16068
  properties: page,
16069
+ outcomes,
16039
16070
  flags: { total: overview.flags },
16040
16071
  ...namedShareOfVoice === void 0 ? {} : { namedShareOfVoice }
16041
16072
  };
@@ -35470,8 +35501,9 @@ function isSitemapOwnedByProperty(sitemapUrl, propertyId, canonicalDomain) {
35470
35501
  function signState(payload, secret) {
35471
35502
  return crypto29.createHmac("sha256", secret).update(payload).digest("hex");
35472
35503
  }
35504
+ var OAUTH_STATE_MAX_AGE_MS = 15 * 60 * 1e3;
35473
35505
  function buildSignedState(data, secret) {
35474
- const payload = JSON.stringify(data);
35506
+ const payload = JSON.stringify({ ...data, issuedAt: Date.now() });
35475
35507
  const sig = signState(payload, secret);
35476
35508
  return Buffer.from(JSON.stringify({ payload, sig })).toString("base64url");
35477
35509
  }
@@ -35480,7 +35512,10 @@ function verifySignedState(encoded, secret) {
35480
35512
  const { payload, sig } = JSON.parse(Buffer.from(encoded, "base64url").toString());
35481
35513
  const expected = signState(payload, secret);
35482
35514
  if (!crypto29.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
35483
- return JSON.parse(payload);
35515
+ const parsed = JSON.parse(payload);
35516
+ const issuedAt = typeof parsed.issuedAt === "number" ? parsed.issuedAt : null;
35517
+ if (issuedAt === null || Date.now() - issuedAt > OAUTH_STATE_MAX_AGE_MS) return null;
35518
+ return parsed;
35484
35519
  } catch {
35485
35520
  return null;
35486
35521
  }
@@ -41766,9 +41801,12 @@ async function adsRoutes(app, opts) {
41766
41801
  }
41767
41802
 
41768
41803
  // ../api-routes/src/bing.ts
41769
- import crypto33 from "crypto";
41804
+ import crypto34 from "crypto";
41770
41805
  import { eq as eq42, and as and33, desc as desc19 } from "drizzle-orm";
41771
41806
 
41807
+ // ../integration-bing/src/bing-client.ts
41808
+ import crypto33 from "crypto";
41809
+
41772
41810
  // ../integration-bing/src/constants.ts
41773
41811
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
41774
41812
  var BING_SUBMIT_URL_BATCH_LIMIT = 500;
@@ -41777,6 +41815,7 @@ var BING_REQUEST_TIMEOUT_MS = 3e4;
41777
41815
  var BING_MAX_RETRIES = 4;
41778
41816
  var BING_RETRY_BASE_DELAY_MS = 2e3;
41779
41817
  var BING_RETRY_MAX_DELAY_MS = 3e4;
41818
+ var BING_THROTTLE_COOLDOWN_MS = 10 * 60 * 1e3;
41780
41819
 
41781
41820
  // ../integration-bing/src/types.ts
41782
41821
  var BING_THROTTLE_ERROR_CODES = /* @__PURE__ */ new Set([4, 5]);
@@ -41916,26 +41955,58 @@ async function bingFetchOnce(apiKey, endpoint, opts) {
41916
41955
  throw new BingApiError("Bing API returned invalid JSON", 502);
41917
41956
  }
41918
41957
  }
41958
+ var throttleCooldownUntil = /* @__PURE__ */ new Map();
41959
+ var cooldownNow = () => Date.now();
41960
+ function cooldownKey(apiKey) {
41961
+ return crypto33.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
41962
+ }
41963
+ function throttleCooldownRemainingMs(apiKey) {
41964
+ const until = throttleCooldownUntil.get(cooldownKey(apiKey));
41965
+ if (until == null) return 0;
41966
+ const remaining = until - cooldownNow();
41967
+ if (remaining <= 0) {
41968
+ throttleCooldownUntil.delete(cooldownKey(apiKey));
41969
+ return 0;
41970
+ }
41971
+ return remaining;
41972
+ }
41973
+ function openThrottleCooldown(apiKey) {
41974
+ throttleCooldownUntil.set(cooldownKey(apiKey), cooldownNow() + BING_THROTTLE_COOLDOWN_MS);
41975
+ bingClientLog("error", "http.cooldown-open", { cooldownMs: BING_THROTTLE_COOLDOWN_MS });
41976
+ }
41919
41977
  async function bingFetch(apiKey, endpoint, opts) {
41920
41978
  const isIdempotent = (opts?.method ?? "GET") === "GET";
41921
- return withRetry(() => bingFetchOnce(apiKey, endpoint, opts), {
41922
- maxRetries: BING_MAX_RETRIES,
41923
- baseDelayMs: BING_RETRY_BASE_DELAY_MS,
41924
- maxDelayMs: BING_RETRY_MAX_DELAY_MS,
41925
- isRetryable: (err) => {
41926
- if (err instanceof BingApiError && err.isThrottle) return true;
41927
- return isIdempotent && isRetryableHttpError(err);
41928
- },
41929
- computeDelayMs: (_attempt, err, defaultMs) => retryAfterDelayMs(err) ?? defaultMs,
41930
- onRetry: ({ attempt, err, delayMs }) => {
41931
- bingClientLog("warn", "http.retry", {
41932
- endpoint,
41933
- attempt,
41934
- delayMs: Math.round(delayMs),
41935
- throttled: err instanceof BingApiError ? err.isThrottle : false
41936
- });
41937
- }
41938
- });
41979
+ const cooling = throttleCooldownRemainingMs(apiKey);
41980
+ if (cooling > 0) {
41981
+ bingClientLog("warn", "http.cooldown-skip", { endpoint, remainingMs: cooling });
41982
+ throw new BingApiError(
41983
+ `Bing API key is in a throttle cooldown for another ${Math.ceil(cooling / 1e3)}s; the account was still throttled after a full retry budget, so this call was not attempted.`,
41984
+ 429
41985
+ );
41986
+ }
41987
+ try {
41988
+ return await withRetry(() => bingFetchOnce(apiKey, endpoint, opts), {
41989
+ maxRetries: BING_MAX_RETRIES,
41990
+ baseDelayMs: BING_RETRY_BASE_DELAY_MS,
41991
+ maxDelayMs: BING_RETRY_MAX_DELAY_MS,
41992
+ isRetryable: (err) => {
41993
+ if (err instanceof BingApiError && err.isThrottle) return true;
41994
+ return isIdempotent && isRetryableHttpError(err);
41995
+ },
41996
+ computeDelayMs: (_attempt, err, defaultMs) => retryAfterDelayMs(err) ?? defaultMs,
41997
+ onRetry: ({ attempt, err, delayMs }) => {
41998
+ bingClientLog("warn", "http.retry", {
41999
+ endpoint,
42000
+ attempt,
42001
+ delayMs: Math.round(delayMs),
42002
+ throttled: err instanceof BingApiError ? err.isThrottle : false
42003
+ });
42004
+ }
42005
+ });
42006
+ } catch (err) {
42007
+ if (err instanceof BingApiError && err.isThrottle) openThrottleCooldown(apiKey);
42008
+ throw err;
42009
+ }
41939
42010
  }
41940
42011
  async function getSites(apiKey) {
41941
42012
  validateApiKey(apiKey);
@@ -42206,7 +42277,7 @@ async function bingRoutes(app, opts) {
42206
42277
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
42207
42278
  const now = (/* @__PURE__ */ new Date()).toISOString();
42208
42279
  app.db.insert(bingCoverageSnapshots).values({
42209
- id: crypto33.randomUUID(),
42280
+ id: crypto34.randomUUID(),
42210
42281
  projectId: project.id,
42211
42282
  syncRunId: snapshotRunId,
42212
42283
  date: snapshotDate,
@@ -42277,7 +42348,7 @@ async function bingRoutes(app, opts) {
42277
42348
  throw validationError("url is required");
42278
42349
  }
42279
42350
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
42280
- const runId = crypto33.randomUUID();
42351
+ const runId = crypto34.randomUUID();
42281
42352
  app.db.insert(runs).values({
42282
42353
  id: runId,
42283
42354
  projectId: project.id,
@@ -42298,7 +42369,7 @@ async function bingRoutes(app, opts) {
42298
42369
  discoveryDate: result.DiscoveryDate ?? null
42299
42370
  });
42300
42371
  const now = (/* @__PURE__ */ new Date()).toISOString();
42301
- const id = crypto33.randomUUID();
42372
+ const id = crypto34.randomUUID();
42302
42373
  const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
42303
42374
  const lastCrawledDate = parseBingDate(result.LastCrawledDate);
42304
42375
  const inIndexDate = parseBingDate(result.InIndexDate);
@@ -42368,7 +42439,7 @@ async function bingRoutes(app, opts) {
42368
42439
  throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
42369
42440
  }
42370
42441
  const now = (/* @__PURE__ */ new Date()).toISOString();
42371
- const runId = crypto33.randomUUID();
42442
+ const runId = crypto34.randomUUID();
42372
42443
  app.db.insert(runs).values({
42373
42444
  id: runId,
42374
42445
  projectId: project.id,
@@ -42653,7 +42724,7 @@ async function cdpRoutes(app, opts) {
42653
42724
  }
42654
42725
 
42655
42726
  // ../api-routes/src/ga.ts
42656
- import crypto34 from "crypto";
42727
+ import crypto35 from "crypto";
42657
42728
  import { eq as eq44, desc as desc20, and as and35, sql as sql16 } from "drizzle-orm";
42658
42729
 
42659
42730
  // ../api-routes/src/ga-session-history.ts
@@ -42855,7 +42926,7 @@ function persistAcquisitionMeasurement(db, input) {
42855
42926
  )).run();
42856
42927
  for (const row of input.report.rows) {
42857
42928
  tx.insert(gaAcquisitionDaily).values({
42858
- id: crypto34.randomUUID(),
42929
+ id: crypto35.randomUUID(),
42859
42930
  projectId: input.projectId,
42860
42931
  date: row.date,
42861
42932
  channelGroup: row.channelGroup,
@@ -42897,7 +42968,7 @@ function persistLeadMeasurement(db, input) {
42897
42968
  )).run();
42898
42969
  for (const row of input.report.rows) {
42899
42970
  tx.insert(gaLeadEventsDaily).values({
42900
- id: crypto34.randomUUID(),
42971
+ id: crypto35.randomUUID(),
42901
42972
  projectId: input.projectId,
42902
42973
  date: row.date,
42903
42974
  eventName: row.eventName,
@@ -43177,7 +43248,7 @@ async function ga4Routes(app, opts) {
43177
43248
  const leadDays = measurementState?.leadSyncedAt != null ? Math.min(Math.max(1, days), 90) : 90;
43178
43249
  const leadEventNames = project.measurement.leadEventNames;
43179
43250
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
43180
- const runId = crypto34.randomUUID();
43251
+ const runId = crypto35.randomUUID();
43181
43252
  app.db.insert(runs).values({
43182
43253
  id: runId,
43183
43254
  projectId: project.id,
@@ -43231,7 +43302,7 @@ async function ga4Routes(app, opts) {
43231
43302
  ).run();
43232
43303
  for (const row of rows) {
43233
43304
  tx.insert(gaTrafficSnapshots).values({
43234
- id: crypto34.randomUUID(),
43305
+ id: crypto35.randomUUID(),
43235
43306
  projectId: project.id,
43236
43307
  date: row.date,
43237
43308
  landingPage: row.landingPage,
@@ -43254,7 +43325,7 @@ async function ga4Routes(app, opts) {
43254
43325
  ).run();
43255
43326
  for (const row of dailyTotals) {
43256
43327
  tx.insert(gaDailyTotals).values({
43257
- id: crypto34.randomUUID(),
43328
+ id: crypto35.randomUUID(),
43258
43329
  projectId: project.id,
43259
43330
  date: row.date,
43260
43331
  sessions: row.sessions,
@@ -43280,7 +43351,7 @@ async function ga4Routes(app, opts) {
43280
43351
  ).run();
43281
43352
  for (const row of aiReferrals) {
43282
43353
  tx.insert(gaAiReferrals).values({
43283
- id: crypto34.randomUUID(),
43354
+ id: crypto35.randomUUID(),
43284
43355
  projectId: project.id,
43285
43356
  date: row.date,
43286
43357
  source: row.source,
@@ -43312,7 +43383,7 @@ async function ga4Routes(app, opts) {
43312
43383
  ).run();
43313
43384
  for (const row of socialReferrals) {
43314
43385
  tx.insert(gaSocialReferrals).values({
43315
- id: crypto34.randomUUID(),
43386
+ id: crypto35.randomUUID(),
43316
43387
  projectId: project.id,
43317
43388
  date: row.date,
43318
43389
  source: row.source,
@@ -43328,7 +43399,7 @@ async function ga4Routes(app, opts) {
43328
43399
  if (syncSummary) {
43329
43400
  tx.delete(gaTrafficSummaries).where(eq44(gaTrafficSummaries.projectId, project.id)).run();
43330
43401
  tx.insert(gaTrafficSummaries).values({
43331
- id: crypto34.randomUUID(),
43402
+ id: crypto35.randomUUID(),
43332
43403
  projectId: project.id,
43333
43404
  periodStart: summary.periodStart,
43334
43405
  periodEnd: summary.periodEnd,
@@ -43341,7 +43412,7 @@ async function ga4Routes(app, opts) {
43341
43412
  tx.delete(gaTrafficWindowSummaries).where(eq44(gaTrafficWindowSummaries.projectId, project.id)).run();
43342
43413
  for (const ws of windowSummaries) {
43343
43414
  tx.insert(gaTrafficWindowSummaries).values({
43344
- id: crypto34.randomUUID(),
43415
+ id: crypto35.randomUUID(),
43345
43416
  projectId: project.id,
43346
43417
  windowKey: ws.windowKey,
43347
43418
  periodStart: ws.periodStart,
@@ -44022,7 +44093,7 @@ function parseSchemaPageEntry(entry) {
44022
44093
  }
44023
44094
 
44024
44095
  // ../integration-wordpress/src/wordpress-client.ts
44025
- import crypto35 from "crypto";
44096
+ import crypto36 from "crypto";
44026
44097
  function validateUsername(username) {
44027
44098
  if (!username || typeof username !== "string" || username.trim().length === 0) {
44028
44099
  throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
@@ -44242,7 +44313,7 @@ function buildSnippet(content) {
44242
44313
  return `${text2.slice(0, 157)}...`;
44243
44314
  }
44244
44315
  function contentHash(content) {
44245
- return crypto35.createHash("sha256").update(content).digest("hex");
44316
+ return crypto36.createHash("sha256").update(content).digest("hex");
44246
44317
  }
44247
44318
  function buildAmbiguousSlugMessage(slug, pages) {
44248
44319
  const candidates = pages.map((page) => {
@@ -45542,7 +45613,7 @@ async function wordpressRoutes(app, opts) {
45542
45613
  }
45543
45614
 
45544
45615
  // ../api-routes/src/backlinks.ts
45545
- import crypto36 from "crypto";
45616
+ import crypto37 from "crypto";
45546
45617
  import { and as and37, asc as asc9, desc as desc21, eq as eq45, sql as sql17 } from "drizzle-orm";
45547
45618
 
45548
45619
  // ../integration-commoncrawl/src/constants.ts
@@ -46179,7 +46250,7 @@ async function backlinksRoutes(app, opts) {
46179
46250
  const refreshed = app.db.select().from(ccReleaseSyncs).where(eq45(ccReleaseSyncs.id, existing.id)).get();
46180
46251
  return reply.status(200).send(mapSyncRow(refreshed));
46181
46252
  }
46182
- const id = crypto36.randomUUID();
46253
+ const id = crypto37.randomUUID();
46183
46254
  app.db.insert(ccReleaseSyncs).values({
46184
46255
  id,
46185
46256
  release,
@@ -46236,7 +46307,7 @@ async function backlinksRoutes(app, opts) {
46236
46307
  throw validationError("Invalid release id");
46237
46308
  }
46238
46309
  const now = (/* @__PURE__ */ new Date()).toISOString();
46239
- const runId = crypto36.randomUUID();
46310
+ const runId = crypto37.randomUUID();
46240
46311
  app.db.insert(runs).values({
46241
46312
  id: runId,
46242
46313
  projectId: project.id,
@@ -46325,7 +46396,7 @@ async function backlinksRoutes(app, opts) {
46325
46396
  }
46326
46397
 
46327
46398
  // ../api-routes/src/traffic.ts
46328
- import crypto38 from "crypto";
46399
+ import crypto39 from "crypto";
46329
46400
  import { Agent as UndiciAgent } from "undici";
46330
46401
  import { and as and38, desc as desc22, eq as eq46, gte as gte10, lte as lte9, sql as sql18 } from "drizzle-orm";
46331
46402
 
@@ -46346,7 +46417,7 @@ function resolveVercelSyncDeadlineMs(env = process.env) {
46346
46417
  }
46347
46418
 
46348
46419
  // ../integration-cloud-run/src/auth.ts
46349
- import crypto37 from "crypto";
46420
+ import crypto38 from "crypto";
46350
46421
  var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
46351
46422
  var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
46352
46423
  var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
@@ -46377,7 +46448,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
46377
46448
  const headerB64 = encode(header);
46378
46449
  const payloadB64 = encode(payload);
46379
46450
  const signingInput = `${headerB64}.${payloadB64}`;
46380
- const sign = crypto37.createSign("RSA-SHA256");
46451
+ const sign = crypto38.createSign("RSA-SHA256");
46381
46452
  sign.update(signingInput);
46382
46453
  const signature = sign.sign(privateKey, "base64url");
46383
46454
  return `${signingInput}.${signature}`;
@@ -50354,7 +50425,7 @@ async function runBackfillTask(options) {
50354
50425
  }
50355
50426
  })();
50356
50427
  tx.insert(rawEventSamples).values({
50357
- id: crypto38.randomUUID(),
50428
+ id: crypto39.randomUUID(),
50358
50429
  projectId: project.id,
50359
50430
  sourceId: sourceRow2.id,
50360
50431
  ts: sample.observedAt,
@@ -50483,7 +50554,7 @@ async function trafficRoutes(app, opts) {
50483
50554
  }).where(eq46(trafficSources.id, activeSource.id)).run();
50484
50555
  sourceRow2 = app.db.select().from(trafficSources).where(eq46(trafficSources.id, activeSource.id)).get();
50485
50556
  } else {
50486
- const newId = crypto38.randomUUID();
50557
+ const newId = crypto39.randomUUID();
50487
50558
  app.db.insert(trafficSources).values({
50488
50559
  id: newId,
50489
50560
  projectId: project.id,
@@ -50564,7 +50635,7 @@ async function trafficRoutes(app, opts) {
50564
50635
  }).where(eq46(trafficSources.id, activeSource.id)).run();
50565
50636
  sourceRow2 = app.db.select().from(trafficSources).where(eq46(trafficSources.id, activeSource.id)).get();
50566
50637
  } else {
50567
- const newId = crypto38.randomUUID();
50638
+ const newId = crypto39.randomUUID();
50568
50639
  app.db.insert(trafficSources).values({
50569
50640
  id: newId,
50570
50641
  projectId: project.id,
@@ -50648,7 +50719,7 @@ async function trafficRoutes(app, opts) {
50648
50719
  }).where(eq46(trafficSources.id, activeSource.id)).run();
50649
50720
  row = tx.select().from(trafficSources).where(eq46(trafficSources.id, activeSource.id)).get();
50650
50721
  } else {
50651
- const newId = crypto38.randomUUID();
50722
+ const newId = crypto39.randomUUID();
50652
50723
  tx.insert(trafficSources).values({
50653
50724
  id: newId,
50654
50725
  projectId: project.id,
@@ -50682,7 +50753,7 @@ async function trafficRoutes(app, opts) {
50682
50753
  let created = false;
50683
50754
  if (!existingSchedule) {
50684
50755
  tx.insert(schedules).values({
50685
- id: crypto38.randomUUID(),
50756
+ id: crypto39.randomUUID(),
50686
50757
  projectId: project.id,
50687
50758
  kind: SchedulableRunKinds["traffic-sync"],
50688
50759
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -50737,7 +50808,7 @@ async function trafficRoutes(app, opts) {
50737
50808
  const windowEnd = /* @__PURE__ */ new Date();
50738
50809
  const startedAt = windowEnd.toISOString();
50739
50810
  const syncStartedAtMs = windowEnd.getTime();
50740
- const runId = crypto38.randomUUID();
50811
+ const runId = crypto39.randomUUID();
50741
50812
  app.db.insert(runs).values({
50742
50813
  id: runId,
50743
50814
  projectId: project.id,
@@ -51130,7 +51201,7 @@ async function trafficRoutes(app, opts) {
51130
51201
  }
51131
51202
  })();
51132
51203
  tx.insert(rawEventSamples).values({
51133
- id: crypto38.randomUUID(),
51204
+ id: crypto39.randomUUID(),
51134
51205
  projectId: project.id,
51135
51206
  sourceId: sourceRow2.id,
51136
51207
  ts: sample.observedAt,
@@ -51381,7 +51452,7 @@ async function trafficRoutes(app, opts) {
51381
51452
  };
51382
51453
  }
51383
51454
  const startedAt = windowEnd.toISOString();
51384
- const runId = crypto38.randomUUID();
51455
+ const runId = crypto39.randomUUID();
51385
51456
  app.db.insert(runs).values({
51386
51457
  id: runId,
51387
51458
  projectId: project.id,
@@ -51751,7 +51822,7 @@ async function trafficRoutes(app, opts) {
51751
51822
  }
51752
51823
 
51753
51824
  // ../api-routes/src/doctor/checks/agent.ts
51754
- import crypto39 from "crypto";
51825
+ import crypto40 from "crypto";
51755
51826
  import fs6 from "fs";
51756
51827
  import path7 from "path";
51757
51828
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -51982,7 +52053,7 @@ function isInstalled(dir) {
51982
52053
  }
51983
52054
  function hashInstalledFile(filePath) {
51984
52055
  try {
51985
- return crypto39.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
52056
+ return crypto40.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
51986
52057
  } catch {
51987
52058
  return void 0;
51988
52059
  }
@@ -54055,7 +54126,7 @@ async function doctorRoutes(app, opts) {
54055
54126
  }
54056
54127
 
54057
54128
  // ../api-routes/src/discovery/routes.ts
54058
- import crypto40 from "crypto";
54129
+ import crypto41 from "crypto";
54059
54130
  import { and as and42, desc as desc23, eq as eq53, gte as gte12, inArray as inArray17, isNull as isNull4, or as or10 } from "drizzle-orm";
54060
54131
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
54061
54132
  async function discoveryRoutes(app, opts) {
@@ -54121,8 +54192,8 @@ async function discoveryRoutes(app, opts) {
54121
54192
  if (existing && existing.runId) {
54122
54193
  return { reused: true, sessionId: existing.id, runId: existing.runId };
54123
54194
  }
54124
- const sessionId = crypto40.randomUUID();
54125
- const runId = crypto40.randomUUID();
54195
+ const sessionId = crypto41.randomUUID();
54196
+ const runId = crypto41.randomUUID();
54126
54197
  tx.insert(discoverySessions).values({
54127
54198
  id: sessionId,
54128
54199
  projectId: project.id,
@@ -54376,7 +54447,7 @@ async function discoveryRoutes(app, opts) {
54376
54447
  app.db.transaction((tx) => {
54377
54448
  for (const query of promotedQueries) {
54378
54449
  tx.insert(queries).values({
54379
- id: crypto40.randomUUID(),
54450
+ id: crypto41.randomUUID(),
54380
54451
  projectId: project.id,
54381
54452
  query,
54382
54453
  provenance,
@@ -54385,7 +54456,7 @@ async function discoveryRoutes(app, opts) {
54385
54456
  }
54386
54457
  for (const domain of promotedCompetitors) {
54387
54458
  tx.insert(competitors).values({
54388
- id: crypto40.randomUUID(),
54459
+ id: crypto41.randomUUID(),
54389
54460
  projectId: project.id,
54390
54461
  domain,
54391
54462
  provenance,
@@ -54475,7 +54546,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
54475
54546
  }
54476
54547
 
54477
54548
  // ../api-routes/src/discovery/orchestrate.ts
54478
- import crypto41 from "crypto";
54549
+ import crypto42 from "crypto";
54479
54550
  import { eq as eq54 } from "drizzle-orm";
54480
54551
  var DEFAULT_MAX_PROBES = 100;
54481
54552
  var ABSOLUTE_MAX_PROBES = 500;
@@ -54643,7 +54714,7 @@ async function executeDiscovery(opts) {
54643
54714
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
54644
54715
  buckets[bucket]++;
54645
54716
  return {
54646
- id: crypto41.randomUUID(),
54717
+ id: crypto42.randomUUID(),
54647
54718
  sessionId: opts.sessionId,
54648
54719
  projectId: opts.project.id,
54649
54720
  query: probedCanonicals[index2],
@@ -54695,7 +54766,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
54695
54766
  const domain = hostOf(entry.domain) ?? "";
54696
54767
  if (!domain) continue;
54697
54768
  db.insert(domainClassifications).values({
54698
- id: crypto41.randomUUID(),
54769
+ id: crypto42.randomUUID(),
54699
54770
  projectId,
54700
54771
  domain,
54701
54772
  competitorType: entry.competitorType,
@@ -54735,7 +54806,7 @@ function dedupeStrings(input) {
54735
54806
  }
54736
54807
 
54737
54808
  // ../api-routes/src/technical-aeo.ts
54738
- import crypto42 from "crypto";
54809
+ import crypto43 from "crypto";
54739
54810
  import { and as and43, asc as asc10, count, desc as desc24, eq as eq55, inArray as inArray18, lt as lt7 } from "drizzle-orm";
54740
54811
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
54741
54812
  function emptyScore(projectName) {
@@ -54871,7 +54942,7 @@ async function technicalAeoRoutes(app, opts) {
54871
54942
  return { runId: existing.id, status: existing.status };
54872
54943
  }
54873
54944
  const now = (/* @__PURE__ */ new Date()).toISOString();
54874
- const runId = crypto42.randomUUID();
54945
+ const runId = crypto43.randomUUID();
54875
54946
  app.db.insert(runs).values({
54876
54947
  id: runId,
54877
54948
  projectId: project.id,
@@ -54889,7 +54960,7 @@ async function technicalAeoRoutes(app, opts) {
54889
54960
  }
54890
54961
 
54891
54962
  // ../api-routes/src/research.ts
54892
- import crypto43 from "crypto";
54963
+ import crypto44 from "crypto";
54893
54964
  import { and as and44, desc as desc25, eq as eq56 } from "drizzle-orm";
54894
54965
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
54895
54966
  async function researchRoutes(app, opts) {
@@ -54916,7 +54987,7 @@ async function researchRoutes(app, opts) {
54916
54987
  if (!adapter.modelValidationPattern.test(resolvedModel)) throw validationError(`Invalid resolved model "${resolvedModel}" for provider "${providerName}".`, { provider: providerName, model: resolvedModel, hint: adapter.modelValidationHint });
54917
54988
  if (new Set(input.queries.map((query) => query.toLocaleLowerCase())).size !== input.queries.length) throw validationError("Research queries must be unique within a batch.");
54918
54989
  const normalized = { queries: input.queries, provider: providerName, model: requestedModel, location: location ?? null };
54919
- const requestHash2 = crypto43.createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
54990
+ const requestHash2 = crypto44.createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
54920
54991
  const now = (/* @__PURE__ */ new Date()).toISOString();
54921
54992
  const decision = app.db.transaction((tx) => {
54922
54993
  if (input.idempotencyKey) {
@@ -54926,9 +54997,9 @@ async function researchRoutes(app, opts) {
54926
54997
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
54927
54998
  }
54928
54999
  }
54929
- const id = crypto43.randomUUID();
55000
+ const id = crypto44.randomUUID();
54930
55001
  tx.insert(researchRuns).values({ id, projectId: project.id, status: ResearchRunStatuses.queued, provider: providerName, requestedModel, resolvedModel, location: location ?? null, totalQueries: input.queries.length, idempotencyKey: input.idempotencyKey ?? null, requestHash: input.idempotencyKey ? requestHash2 : null, createdAt: now }).run();
54931
- for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto43.randomUUID(), researchRunId: id, position, queryText: query, status: ResearchQueryStatuses.queued, requestedModel, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
55002
+ for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto44.randomUUID(), researchRunId: id, position, queryText: query, status: ResearchQueryStatuses.queued, requestedModel, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
54932
55003
  writeAuditLog(tx, { projectId: project.id, actor: "api", action: "research.created", entityType: "research_run", entityId: id });
54933
55004
  return { reused: false, id, shouldDispatch: true };
54934
55005
  });
@@ -55345,7 +55416,7 @@ function buildTrafficSourceValidators(opts) {
55345
55416
  }
55346
55417
 
55347
55418
  // src/intelligence-service.ts
55348
- import crypto44 from "crypto";
55419
+ import crypto45 from "crypto";
55349
55420
 
55350
55421
  // src/logger.ts
55351
55422
  var IS_TTY = process.stdout.isTTY === true;
@@ -55986,7 +56057,7 @@ var IntelligenceService = class {
55986
56057
  }).run();
55987
56058
  }
55988
56059
  tx.insert(healthSnapshots).values({
55989
- id: crypto44.randomUUID(),
56060
+ id: crypto45.randomUUID(),
55990
56061
  projectId,
55991
56062
  runId,
55992
56063
  overallCitedRate: String(result.health.overallCitedRate),
@@ -4019,6 +4019,14 @@ var measurementPropertyProviderRowSchema = z5.object({
4019
4019
  mentionCoverage: measurementMetricValueSchema,
4020
4020
  citationCoverage: measurementMetricValueSchema
4021
4021
  }).strict();
4022
+ var measurementOutcomeCountsSchema = z5.object({
4023
+ bothSignals: z5.number().int().nonnegative(),
4024
+ mentionedOnly: z5.number().int().nonnegative(),
4025
+ citedOnly: z5.number().int().nonnegative(),
4026
+ neither: z5.number().int().nonnegative(),
4027
+ notMeasured: z5.number().int().nonnegative(),
4028
+ total: z5.number().int().nonnegative()
4029
+ }).strict();
4022
4030
  var measurementPropertyRowSchema = z5.object({
4023
4031
  targetKey: measurementV2StableKeySchema,
4024
4032
  label: z5.string().min(1),
@@ -4076,6 +4084,13 @@ var measurementOverviewResponseSchema = z5.object({
4076
4084
  )
4077
4085
  }).strict(),
4078
4086
  properties: measurementCursorPageSchema(measurementPropertyRowSchema),
4087
+ /**
4088
+ * Outcome split over the whole RESULT SET, not the page — so paging through
4089
+ * does not move it. It narrows with `search` exactly as `properties.totalEstimate`
4090
+ * does, because both are computed from the same filtered rows; with no search
4091
+ * that result set is the entire scope.
4092
+ */
4093
+ outcomes: measurementOutcomeCountsSchema,
4079
4094
  flags: z5.object({ total: z5.number().int().nonnegative() }).strict(),
4080
4095
  namedShareOfVoice: measurementNamedShareOfVoiceSchema.optional()
4081
4096
  }).strict();