@canonry/canonry 7.46.0 → 7.46.1

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.
@@ -31,7 +31,7 @@ import {
31
31
  trackFeatureCompleted,
32
32
  trackIntegrationConnection,
33
33
  waitForServerRuntimeStartup
34
- } from "./chunk-AYMS4LCP.js";
34
+ } from "./chunk-OO64BWNB.js";
35
35
  import {
36
36
  clearDashboardPassword,
37
37
  configExists,
@@ -122,7 +122,7 @@ import {
122
122
  rawEventSamples,
123
123
  readMarketCompetitorNames,
124
124
  runs
125
- } from "./chunk-ZOTA3MOL.js";
125
+ } from "./chunk-IBRZ23VQ.js";
126
126
  import {
127
127
  AdsDeliverySnapshotStatuses,
128
128
  AdsHistoricalCampaignRollupStatuses,
@@ -586,7 +586,7 @@ function parseDemoTrustedProxies(values) {
586
586
  async function demoCommand(options) {
587
587
  const { host, port } = parseDemoListenOptions(options);
588
588
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
589
- const { createDemoServer } = await import("./demo-server-7YFRG3JM.js");
589
+ const { createDemoServer } = await import("./demo-server-5TX4MJIS.js");
590
590
  const app = await createDemoServer({ trustProxy });
591
591
  let stopping = false;
592
592
  const stop = () => {
@@ -1000,7 +1000,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
1000
1000
  }
1001
1001
  }
1002
1002
  async function backfillInsightsCommand(project2, opts) {
1003
- const { IntelligenceService } = await import("./intelligence-service-P3UJNCVS.js");
1003
+ const { IntelligenceService } = await import("./intelligence-service-5BYJSFLW.js");
1004
1004
  const config = loadConfig();
1005
1005
  const db = createClient(config.database);
1006
1006
  migrate(db);
@@ -19378,7 +19378,7 @@ var SYSTEM_CLI_COMMANDS = [
19378
19378
  options: { access: stringOption() },
19379
19379
  run: async (input) => {
19380
19380
  if (input.positionals.length > 1) throw usageError("Expected at most one command group");
19381
- const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-TFCCKIE4.js"), import("./cli-commands-GYWXFE3P.js")]);
19381
+ const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-TFCCKIE4.js"), import("./cli-commands-JEN27DWN.js")]);
19382
19382
  printCliCatalog(REGISTERED_CLI_COMMANDS2, input.format, input.positionals[0], getString(input.values, "access"));
19383
19383
  }
19384
19384
  },
@@ -888,6 +888,7 @@ import {
888
888
  unsupportedKind,
889
889
  upgradeCaveatFor,
890
890
  usableBrandAliases,
891
+ usageSurfaceSchema,
891
892
  userDtoSchema,
892
893
  userListDtoSchema,
893
894
  validationError,
@@ -46517,6 +46518,7 @@ var routeCatalog = [
46517
46518
  // TODO: Add `GaConnectResponse` Zod schema in contracts.
46518
46519
  200: rawJsonResponse("GA4 connection established.", looseObjectSchema),
46519
46520
  400: errorResponse("Invalid GA4 connection request."),
46521
+ 403: errorResponse("Google refused the property (`details.reason` is `ga4-permission-missing`). Retrying will not help until access is granted; repeated refusals are answered without asking Google again until `details.nextCheckAt`."),
46520
46522
  404: errorResponse("Project not found.")
46521
46523
  }
46522
46524
  },
@@ -60100,11 +60102,76 @@ async function adsRoutes(app, opts) {
60100
60102
  }
60101
60103
 
60102
60104
  // ../api-routes/src/bing.ts
60103
- import crypto42 from "crypto";
60105
+ import crypto43 from "crypto";
60104
60106
  import { eq as eq66, and as and54, desc as desc32 } from "drizzle-orm";
60105
60107
 
60106
- // ../integration-bing/src/bing-client.ts
60108
+ // ../api-routes/src/connection-verify-cache.ts
60107
60109
  import crypto41 from "crypto";
60110
+ var CONNECTION_VERIFY_TTL_MS = 15 * 60 * 1e3;
60111
+ var REFUSALS_BEFORE_BACKOFF = 3;
60112
+ var FIRST_BACKOFF_MS = 5 * 60 * 1e3;
60113
+ var MAX_BACKOFF_MS = 60 * 60 * 1e3;
60114
+ var MAX_ENTRIES = 500;
60115
+ function remember(entries, key, value) {
60116
+ entries.delete(key);
60117
+ entries.set(key, value);
60118
+ if (entries.size > MAX_ENTRIES) entries.delete(entries.keys().next().value);
60119
+ }
60120
+ function isInteractiveCaller() {
60121
+ const attribution = currentOutcomeAttribution();
60122
+ const label = usageSurfaceSchema.safeParse(attribution?.surfaceLabel);
60123
+ if (label.success) return label.data === "cli" || label.data === "dashboard";
60124
+ const ua = attribution?.userAgent ?? "";
60125
+ return ua.startsWith("canonry-cli") || ua.startsWith("Mozilla/");
60126
+ }
60127
+ function verifyCacheKey(...parts) {
60128
+ return crypto41.createHash("sha256").update(parts.join("\0")).digest("hex");
60129
+ }
60130
+ function createVerifyCache(ttlMs = CONNECTION_VERIFY_TTL_MS) {
60131
+ const entries = /* @__PURE__ */ new Map();
60132
+ return {
60133
+ get(key) {
60134
+ const entry = entries.get(key);
60135
+ if (!entry) return void 0;
60136
+ if (Date.now() - entry.at < ttlMs) return entry.value;
60137
+ entries.delete(key);
60138
+ return void 0;
60139
+ },
60140
+ set(key, value) {
60141
+ remember(entries, key, { value, at: Date.now() });
60142
+ }
60143
+ };
60144
+ }
60145
+ function createRefusalBackoff() {
60146
+ const entries = /* @__PURE__ */ new Map();
60147
+ return {
60148
+ /** The refusal to answer with instead of asking the upstream, while this key is backing off. */
60149
+ replay(key) {
60150
+ const entry = entries.get(key);
60151
+ if (!entry || entry.count < REFUSALS_BEFORE_BACKOFF) return void 0;
60152
+ const wait = Math.min(FIRST_BACKOFF_MS * 2 ** (entry.count - REFUSALS_BEFORE_BACKOFF), MAX_BACKOFF_MS);
60153
+ const nextCheckAt = entry.at + wait;
60154
+ if (Date.now() >= nextCheckAt) return void 0;
60155
+ const { error } = entry;
60156
+ return {
60157
+ error: new AppError(error.code, error.message, error.statusCode, {
60158
+ ...error.details,
60159
+ nextCheckAt: new Date(nextCheckAt).toISOString()
60160
+ }),
60161
+ cause: entry.cause
60162
+ };
60163
+ },
60164
+ refused(key, error, cause) {
60165
+ remember(entries, key, { count: (entries.get(key)?.count ?? 0) + 1, at: Date.now(), error, cause });
60166
+ },
60167
+ cleared(key) {
60168
+ entries.delete(key);
60169
+ }
60170
+ };
60171
+ }
60172
+
60173
+ // ../integration-bing/src/bing-client.ts
60174
+ import crypto42 from "crypto";
60108
60175
 
60109
60176
  // ../integration-bing/src/constants.ts
60110
60177
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -60259,7 +60326,7 @@ async function bingFetchOnce(apiKey, endpoint, opts) {
60259
60326
  var throttleCooldownUntil = /* @__PURE__ */ new Map();
60260
60327
  var cooldownNow = () => Date.now();
60261
60328
  function cooldownKey(apiKey) {
60262
- return crypto41.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
60329
+ return crypto42.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
60263
60330
  }
60264
60331
  function throttleCooldownRemainingMs(apiKey) {
60265
60332
  const until = throttleCooldownUntil.get(cooldownKey(apiKey));
@@ -60403,6 +60470,7 @@ async function loadBlockingCrawlIssues(apiKey, siteUrl, domain) {
60403
60470
  return blockedUrls;
60404
60471
  }
60405
60472
  async function bingRoutes(app, opts) {
60473
+ const keyChecks = createVerifyCache();
60406
60474
  function requireConnectionStore() {
60407
60475
  if (opts.bingConnectionStore) return opts.bingConnectionStore;
60408
60476
  throw validationError("Bing connection storage is not configured for this deployment");
@@ -60421,16 +60489,21 @@ async function bingRoutes(app, opts) {
60421
60489
  throw validationError("apiKey is required");
60422
60490
  }
60423
60491
  const project = resolveProject(app.db, request.params.name);
60424
- let sites;
60425
- try {
60426
- sites = await getSites(apiKey);
60427
- bingLog("info", "connect.verify-key", { domain: project.canonicalDomain, siteCount: sites.length });
60428
- } catch (e) {
60429
- const msg = describeError(e);
60430
- bingLog("error", "connect.verify-key-failed", { domain: project.canonicalDomain, error: msg });
60431
- attempt.failed(e);
60432
- throw validationError(`Failed to verify Bing API key: ${msg}`);
60492
+ const keyCheck = verifyCacheKey("bing-key", apiKey);
60493
+ let sites = isInteractiveCaller() ? void 0 : keyChecks.get(keyCheck);
60494
+ if (!sites) {
60495
+ try {
60496
+ sites = await getSites(apiKey);
60497
+ keyChecks.set(keyCheck, sites);
60498
+ bingLog("info", "connect.verify-key", { domain: project.canonicalDomain, siteCount: sites.length });
60499
+ } catch (e) {
60500
+ const msg = describeError(e);
60501
+ bingLog("error", "connect.verify-key-failed", { domain: project.canonicalDomain, error: msg });
60502
+ attempt.failed(e);
60503
+ throw validationError(`Failed to verify Bing API key: ${msg}`);
60504
+ }
60433
60505
  }
60506
+ const availableSites = sites.map((s) => ({ url: s.Url, verified: s.IsVerified ?? false }));
60434
60507
  const now = (/* @__PURE__ */ new Date()).toISOString();
60435
60508
  const existing = store.getConnection(project.canonicalDomain);
60436
60509
  if (existing) attempt.update({ action: "reauth" });
@@ -60440,6 +60513,9 @@ async function bingRoutes(app, opts) {
60440
60513
  `This domain already has a Bing connection owned by another project. Disconnect it from that project first before re-connecting from "${project.name}".`
60441
60514
  );
60442
60515
  }
60516
+ if (existing?.apiKey === apiKey && existing.createdByProjectId) {
60517
+ return { connected: true, domain: project.canonicalDomain, siteUrl: existing.siteUrl ?? null, availableSites };
60518
+ }
60443
60519
  store.upsertConnection({
60444
60520
  domain: project.canonicalDomain,
60445
60521
  apiKey,
@@ -60459,7 +60535,7 @@ async function bingRoutes(app, opts) {
60459
60535
  connected: true,
60460
60536
  domain: project.canonicalDomain,
60461
60537
  siteUrl: existing?.siteUrl ?? null,
60462
- availableSites: sites.map((s) => ({ url: s.Url, verified: s.IsVerified ?? false }))
60538
+ availableSites
60463
60539
  };
60464
60540
  }));
60465
60541
  app.delete("/projects/:name/bing/disconnect", connectionRoute(app, () => ({ integration: "bing", action: "disconnect" }), async (request, reply, attempt) => {
@@ -60589,7 +60665,7 @@ async function bingRoutes(app, opts) {
60589
60665
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
60590
60666
  const now = (/* @__PURE__ */ new Date()).toISOString();
60591
60667
  app.db.insert(bingCoverageSnapshots).values({
60592
- id: crypto42.randomUUID(),
60668
+ id: crypto43.randomUUID(),
60593
60669
  projectId: project.id,
60594
60670
  syncRunId: snapshotRunId,
60595
60671
  date: snapshotDate,
@@ -60664,7 +60740,7 @@ async function bingRoutes(app, opts) {
60664
60740
  throw validationError("url is required");
60665
60741
  }
60666
60742
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
60667
- const runId = crypto42.randomUUID();
60743
+ const runId = crypto43.randomUUID();
60668
60744
  app.db.insert(runs).values({
60669
60745
  id: runId,
60670
60746
  projectId: project.id,
@@ -60685,7 +60761,7 @@ async function bingRoutes(app, opts) {
60685
60761
  discoveryDate: result.DiscoveryDate ?? null
60686
60762
  });
60687
60763
  const now = (/* @__PURE__ */ new Date()).toISOString();
60688
- const id = crypto42.randomUUID();
60764
+ const id = crypto43.randomUUID();
60689
60765
  const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
60690
60766
  const lastCrawledDate = parseBingDate(result.LastCrawledDate);
60691
60767
  const inIndexDate = parseBingDate(result.InIndexDate);
@@ -60762,7 +60838,7 @@ async function bingRoutes(app, opts) {
60762
60838
  throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
60763
60839
  }
60764
60840
  const now = (/* @__PURE__ */ new Date()).toISOString();
60765
- const runId = crypto42.randomUUID();
60841
+ const runId = crypto43.randomUUID();
60766
60842
  app.db.insert(runs).values({
60767
60843
  id: runId,
60768
60844
  projectId: project.id,
@@ -61060,7 +61136,7 @@ async function cdpRoutes(app, opts) {
61060
61136
  }
61061
61137
 
61062
61138
  // ../api-routes/src/ga.ts
61063
- import crypto44 from "crypto";
61139
+ import crypto45 from "crypto";
61064
61140
  import { eq as eq69, desc as desc34, and as and57, sql as sql27 } from "drizzle-orm";
61065
61141
 
61066
61142
  // ../api-routes/src/ga-session-history.ts
@@ -61120,14 +61196,14 @@ function findBiggestMover(current, prior) {
61120
61196
  }
61121
61197
 
61122
61198
  // ../api-routes/src/ga-search-landing-pages.ts
61123
- import crypto43 from "crypto";
61199
+ import crypto44 from "crypto";
61124
61200
  import { and as and56, asc as asc13, count as count4, desc as desc33, eq as eq68 } from "drizzle-orm";
61125
61201
  function persistSearchLandingSnapshot(db, input) {
61126
61202
  db.transaction((tx) => {
61127
61203
  deleteSearchLandingRows(tx, input.projectId);
61128
61204
  for (const window of input.windows) {
61129
61205
  tx.insert(gaSearchLandingWindows).values({
61130
- id: crypto43.randomUUID(),
61206
+ id: crypto44.randomUUID(),
61131
61207
  projectId: input.projectId,
61132
61208
  propertyId: input.propertyId,
61133
61209
  windowKey: window.window,
@@ -61149,7 +61225,7 @@ function persistSearchLandingSnapshot(db, input) {
61149
61225
  }).run();
61150
61226
  for (const row of window.rows) {
61151
61227
  tx.insert(gaSearchLandingPages).values({
61152
- id: crypto43.randomUUID(),
61228
+ id: crypto44.randomUUID(),
61153
61229
  projectId: input.projectId,
61154
61230
  windowKey: window.window,
61155
61231
  landingPage: row.landingPage,
@@ -61381,7 +61457,7 @@ function persistAcquisitionMeasurement(db, input) {
61381
61457
  )).run();
61382
61458
  for (const row of input.report.rows) {
61383
61459
  tx.insert(gaAcquisitionDaily).values({
61384
- id: crypto44.randomUUID(),
61460
+ id: crypto45.randomUUID(),
61385
61461
  projectId: input.projectId,
61386
61462
  date: row.date,
61387
61463
  channelGroup: row.channelGroup,
@@ -61423,7 +61499,7 @@ function persistLeadMeasurement(db, input) {
61423
61499
  )).run();
61424
61500
  for (const row of input.report.rows) {
61425
61501
  tx.insert(gaLeadEventsDaily).values({
61426
- id: crypto44.randomUUID(),
61502
+ id: crypto45.randomUUID(),
61427
61503
  projectId: input.projectId,
61428
61504
  date: row.date,
61429
61505
  eventName: row.eventName,
@@ -61533,6 +61609,18 @@ function ga4ErrorToAppError(err, context) {
61533
61609
  }
61534
61610
  return providerError(`${context}: ${describeError(err)}`);
61535
61611
  }
61612
+ function ga4ConnectError(err, propertyId, principal, context) {
61613
+ if (err instanceof GA4ApiError && err.status === 403) {
61614
+ return forbidden(
61615
+ `Google refused access to GA4 property ${propertyId} for ${principal}. Add ${principal} as a Viewer in Google Analytics (Admin > Property access management), then connect again. Retrying will not help until access is granted.`,
61616
+ { reason: "ga4-permission-missing", propertyId, retryable: false }
61617
+ );
61618
+ }
61619
+ return validationError(`${context}: ${describeError(err)}`);
61620
+ }
61621
+ function isPermissionRefusal(error) {
61622
+ return error.details?.reason === "ga4-permission-missing";
61623
+ }
61536
61624
  async function resolveGa4AccessToken(opts, projectName, canonicalDomain) {
61537
61625
  const saConn = opts.ga4CredentialStore?.getConnection(projectName);
61538
61626
  if (saConn?.clientEmail && saConn?.privateKey && saConn?.propertyId) {
@@ -61583,6 +61671,25 @@ function requireGa4Connection(opts, projectName, canonicalDomain) {
61583
61671
  }
61584
61672
  }
61585
61673
  async function ga4Routes(app, opts) {
61674
+ const connectPasses = createVerifyCache();
61675
+ const connectRefusals = createRefusalBackoff();
61676
+ async function checkGa4Access(key, verify, toError) {
61677
+ if (!isInteractiveCaller()) {
61678
+ if (connectPasses.get(key)) return { ok: true };
61679
+ const replay = connectRefusals.replay(key);
61680
+ if (replay) return { ok: false, ...replay };
61681
+ }
61682
+ try {
61683
+ await verify();
61684
+ connectPasses.set(key, true);
61685
+ connectRefusals.cleared(key);
61686
+ return { ok: true };
61687
+ } catch (cause) {
61688
+ const error = toError(cause);
61689
+ if (isPermissionRefusal(error)) connectRefusals.refused(key, error, cause);
61690
+ return { ok: false, error, cause };
61691
+ }
61692
+ }
61586
61693
  app.post("/projects/:name/ga/connect", connectionRoute(app, (request) => {
61587
61694
  const body = request.body;
61588
61695
  return { integration: "ga4", action: typeof body?.keyJson === "string" && body.keyJson ? "connect" : "select" };
@@ -61607,18 +61714,23 @@ async function ga4Routes(app, opts) {
61607
61714
  }
61608
61715
  const clientEmail = parsed.client_email;
61609
61716
  const privateKey = parsed.private_key;
61610
- try {
61611
- await verifyConnection(clientEmail, privateKey, propertyId);
61612
- gaLog("info", "connect.verified.service-account", { projectId: project.id, propertyId });
61613
- } catch (e) {
61614
- const msg = describeError(e);
61615
- gaLog("error", "connect.verify-failed", { projectId: project.id, propertyId, error: msg });
61616
- attempt.failed(e);
61617
- throw validationError(`Failed to verify GA4 credentials: ${msg}`);
61717
+ const check3 = await checkGa4Access(
61718
+ verifyCacheKey("ga4-service-account", project.id, propertyId, clientEmail, privateKey),
61719
+ () => verifyConnection(clientEmail, privateKey, propertyId),
61720
+ (err) => ga4ConnectError(err, propertyId, clientEmail, "Failed to verify GA4 credentials")
61721
+ );
61722
+ if (!check3.ok) {
61723
+ gaLog("error", "connect.verify-failed", { projectId: project.id, propertyId, error: check3.error.message });
61724
+ attempt.failed(check3.cause);
61725
+ throw check3.error;
61618
61726
  }
61727
+ gaLog("info", "connect.verified.service-account", { projectId: project.id, propertyId });
61619
61728
  const now = (/* @__PURE__ */ new Date()).toISOString();
61620
61729
  const existing = opts.ga4CredentialStore.getConnection(project.name);
61621
61730
  if (existing) attempt.update({ action: "reauth" });
61731
+ if (existing?.clientEmail === clientEmail && existing.privateKey === privateKey && existing.propertyId === propertyId) {
61732
+ return { connected: true, propertyId, authMethod: "service-account", clientEmail };
61733
+ }
61622
61734
  const previousPropertyId2 = resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain);
61623
61735
  opts.ga4CredentialStore.upsertConnection({
61624
61736
  projectName: project.name,
@@ -61655,19 +61767,27 @@ async function ga4Routes(app, opts) {
61655
61767
  'No GA4 OAuth token found. Run "canonry google connect <project> --type ga4" first, or pass --key-file to use a service account.'
61656
61768
  );
61657
61769
  }
61658
- const accessToken = await refreshOAuthTokenIfNeeded(googleStore, authConfig, project.canonicalDomain, {
61659
- accessToken: oauthConn.accessToken,
61660
- refreshToken: oauthConn.refreshToken,
61661
- tokenExpiresAt: oauthConn.tokenExpiresAt
61662
- });
61663
- try {
61664
- await verifyConnectionWithToken(accessToken, propertyId);
61665
- gaLog("info", "connect.verified.oauth", { projectId: project.id, propertyId });
61666
- } catch (e) {
61667
- const msg = describeError(e);
61668
- gaLog("error", "connect.verify-failed.oauth", { projectId: project.id, propertyId, error: msg });
61669
- attempt.failed(e);
61670
- throw validationError(`Failed to verify GA4 access: ${msg}`);
61770
+ const { accessToken: storedAccessToken, refreshToken } = oauthConn;
61771
+ const check2 = await checkGa4Access(
61772
+ verifyCacheKey("ga4-oauth", project.id, propertyId, refreshToken),
61773
+ async () => {
61774
+ const accessToken = await refreshOAuthTokenIfNeeded(googleStore, authConfig, project.canonicalDomain, {
61775
+ accessToken: storedAccessToken,
61776
+ refreshToken,
61777
+ tokenExpiresAt: oauthConn.tokenExpiresAt
61778
+ });
61779
+ return verifyConnectionWithToken(accessToken, propertyId);
61780
+ },
61781
+ (err) => err instanceof AppError ? err : ga4ConnectError(err, propertyId, "the connected Google account", "Failed to verify GA4 access")
61782
+ );
61783
+ if (!check2.ok) {
61784
+ gaLog("error", "connect.verify-failed.oauth", { projectId: project.id, propertyId, error: check2.error.message });
61785
+ attempt.failed(check2.cause);
61786
+ throw check2.error;
61787
+ }
61788
+ gaLog("info", "connect.verified.oauth", { projectId: project.id, propertyId });
61789
+ if (oauthConn.propertyId === propertyId) {
61790
+ return { connected: true, propertyId, authMethod: "oauth" };
61671
61791
  }
61672
61792
  const previousPropertyId = resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain);
61673
61793
  const connectedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -61777,7 +61897,7 @@ async function ga4Routes(app, opts) {
61777
61897
  const leadDays = measurementState?.leadSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
61778
61898
  const leadEventNames = project.measurement.leadEventNames;
61779
61899
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
61780
- const runId = crypto44.randomUUID();
61900
+ const runId = crypto45.randomUUID();
61781
61901
  app.db.insert(runs).values({
61782
61902
  id: runId,
61783
61903
  projectId: project.id,
@@ -61837,7 +61957,7 @@ async function ga4Routes(app, opts) {
61837
61957
  ).run();
61838
61958
  for (const row of rows) {
61839
61959
  tx.insert(gaTrafficSnapshots).values({
61840
- id: crypto44.randomUUID(),
61960
+ id: crypto45.randomUUID(),
61841
61961
  projectId: project.id,
61842
61962
  date: row.date,
61843
61963
  landingPage: row.landingPage,
@@ -61860,7 +61980,7 @@ async function ga4Routes(app, opts) {
61860
61980
  ).run();
61861
61981
  for (const row of dailyTotals) {
61862
61982
  tx.insert(gaDailyTotals).values({
61863
- id: crypto44.randomUUID(),
61983
+ id: crypto45.randomUUID(),
61864
61984
  projectId: project.id,
61865
61985
  date: row.date,
61866
61986
  sessions: row.sessions,
@@ -61886,7 +62006,7 @@ async function ga4Routes(app, opts) {
61886
62006
  ).run();
61887
62007
  for (const row of aiReferrals) {
61888
62008
  tx.insert(gaAiReferrals).values({
61889
- id: crypto44.randomUUID(),
62009
+ id: crypto45.randomUUID(),
61890
62010
  projectId: project.id,
61891
62011
  date: row.date,
61892
62012
  source: row.source,
@@ -61918,7 +62038,7 @@ async function ga4Routes(app, opts) {
61918
62038
  ).run();
61919
62039
  for (const row of socialReferrals) {
61920
62040
  tx.insert(gaSocialReferrals).values({
61921
- id: crypto44.randomUUID(),
62041
+ id: crypto45.randomUUID(),
61922
62042
  projectId: project.id,
61923
62043
  date: row.date,
61924
62044
  source: row.source,
@@ -61934,7 +62054,7 @@ async function ga4Routes(app, opts) {
61934
62054
  if (syncSummary) {
61935
62055
  tx.delete(gaTrafficSummaries).where(eq69(gaTrafficSummaries.projectId, project.id)).run();
61936
62056
  tx.insert(gaTrafficSummaries).values({
61937
- id: crypto44.randomUUID(),
62057
+ id: crypto45.randomUUID(),
61938
62058
  projectId: project.id,
61939
62059
  periodStart: summary3.periodStart,
61940
62060
  periodEnd: summary3.periodEnd,
@@ -61947,7 +62067,7 @@ async function ga4Routes(app, opts) {
61947
62067
  tx.delete(gaTrafficWindowSummaries).where(eq69(gaTrafficWindowSummaries.projectId, project.id)).run();
61948
62068
  for (const ws of windowSummaries) {
61949
62069
  tx.insert(gaTrafficWindowSummaries).values({
61950
- id: crypto44.randomUUID(),
62070
+ id: crypto45.randomUUID(),
61951
62071
  projectId: project.id,
61952
62072
  windowKey: ws.windowKey,
61953
62073
  periodStart: ws.periodStart,
@@ -62744,7 +62864,7 @@ function parseSchemaPageEntry(entry) {
62744
62864
  }
62745
62865
 
62746
62866
  // ../integration-wordpress/src/wordpress-client.ts
62747
- import crypto45 from "crypto";
62867
+ import crypto46 from "crypto";
62748
62868
  function validateUsername(username) {
62749
62869
  if (!username || typeof username !== "string" || username.trim().length === 0) {
62750
62870
  throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
@@ -62964,7 +63084,7 @@ function buildSnippet(content) {
62964
63084
  return `${text2.slice(0, 157)}...`;
62965
63085
  }
62966
63086
  function contentHash(content) {
62967
- return crypto45.createHash("sha256").update(content).digest("hex");
63087
+ return crypto46.createHash("sha256").update(content).digest("hex");
62968
63088
  }
62969
63089
  function buildAmbiguousSlugMessage(slug, pages) {
62970
63090
  const candidates = pages.map((page3) => {
@@ -64457,7 +64577,7 @@ async function wordpressRoutes(app, opts) {
64457
64577
  }
64458
64578
 
64459
64579
  // ../api-routes/src/backlinks.ts
64460
- import crypto46 from "crypto";
64580
+ import crypto47 from "crypto";
64461
64581
  import { and as and61, asc as asc15, desc as desc36, eq as eq72, sql as sql29 } from "drizzle-orm";
64462
64582
 
64463
64583
  // ../integration-commoncrawl/src/constants.ts
@@ -65110,7 +65230,7 @@ async function backlinksRoutes(app, opts) {
65110
65230
  const refreshed = app.db.select().from(ccReleaseSyncs).where(eq72(ccReleaseSyncs.id, existing.id)).get();
65111
65231
  return reply.status(200).send(mapSyncRow(refreshed));
65112
65232
  }
65113
- const id = crypto46.randomUUID();
65233
+ const id = crypto47.randomUUID();
65114
65234
  app.db.insert(ccReleaseSyncs).values({
65115
65235
  id,
65116
65236
  release,
@@ -65167,7 +65287,7 @@ async function backlinksRoutes(app, opts) {
65167
65287
  throw validationError("Invalid release id");
65168
65288
  }
65169
65289
  const now = (/* @__PURE__ */ new Date()).toISOString();
65170
- const runId = crypto46.randomUUID();
65290
+ const runId = crypto47.randomUUID();
65171
65291
  app.db.insert(runs).values({
65172
65292
  id: runId,
65173
65293
  projectId: project.id,
@@ -65256,7 +65376,7 @@ async function backlinksRoutes(app, opts) {
65256
65376
  }
65257
65377
 
65258
65378
  // ../api-routes/src/traffic.ts
65259
- import crypto49 from "crypto";
65379
+ import crypto50 from "crypto";
65260
65380
  import { isIP } from "net";
65261
65381
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
65262
65382
  import { and as and65, desc as desc37, eq as eq76, gte as gte18, inArray as inArray31, lte as lte20, sql as sql32 } from "drizzle-orm";
@@ -65284,7 +65404,7 @@ function resolveVercelSyncDeadlineMs(env = process.env) {
65284
65404
  }
65285
65405
 
65286
65406
  // ../integration-cloud-run/src/auth.ts
65287
- import crypto47 from "crypto";
65407
+ import crypto48 from "crypto";
65288
65408
  var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
65289
65409
  var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
65290
65410
  var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
@@ -65315,7 +65435,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
65315
65435
  const headerB64 = encode(header2);
65316
65436
  const payloadB64 = encode(payload);
65317
65437
  const signingInput = `${headerB64}.${payloadB64}`;
65318
- const sign = crypto47.createSign("RSA-SHA256");
65438
+ const sign = crypto48.createSign("RSA-SHA256");
65319
65439
  sign.update(signingInput);
65320
65440
  const signature = sign.sign(privateKey, "base64url");
65321
65441
  return `${signingInput}.${signature}`;
@@ -81924,7 +82044,7 @@ async function ackCloudflareQueueMessages(client, options) {
81924
82044
  }
81925
82045
 
81926
82046
  // ../api-routes/src/traffic-event-ingest.ts
81927
- import crypto48 from "crypto";
82047
+ import crypto49 from "crypto";
81928
82048
  import { and as and63, eq as eq74, gte as gte17, lt as lt12, lte as lte18, sql as sql31 } from "drizzle-orm";
81929
82049
 
81930
82050
  // ../api-routes/src/raw-event-sample-retention.ts
@@ -82223,7 +82343,7 @@ function writeTrafficEventBatch(opts) {
82223
82343
  }
82224
82344
  })();
82225
82345
  tx.insert(rawEventSamples).values({
82226
- id: crypto48.randomUUID(),
82346
+ id: crypto49.randomUUID(),
82227
82347
  projectId: opts.projectId,
82228
82348
  sourceId: opts.sourceId,
82229
82349
  ts: sampleTimestamp,
@@ -82324,7 +82444,7 @@ var WORDPRESS_SYNC_LEASE_TTL_MS = 5 * 6e4;
82324
82444
  var DEFAULT_CLOUDFLARE_INGEST_RATE_LIMIT_MAX = 6e3;
82325
82445
  var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
82326
82446
  function sha256Hex3(value) {
82327
- return crypto49.createHash("sha256").update(value).digest("hex");
82447
+ return crypto50.createHash("sha256").update(value).digest("hex");
82328
82448
  }
82329
82449
  function hashCloudflareBearerToken(value) {
82330
82450
  return sha256Hex3(value);
@@ -82334,7 +82454,7 @@ function timingSafeEqualHex(a, b) {
82334
82454
  if (!SHA256_HEX_PATTERN.test(a) || !SHA256_HEX_PATTERN.test(b)) return false;
82335
82455
  const left = Buffer.from(a, "hex");
82336
82456
  const right = Buffer.from(b, "hex");
82337
- return left.length === right.length && crypto49.timingSafeEqual(left, right);
82457
+ return left.length === right.length && crypto50.timingSafeEqual(left, right);
82338
82458
  }
82339
82459
  function parseDirectPushCloudflareSourceConfig(config) {
82340
82460
  const parsed = cloudflareTrafficSourceConfigSchema.safeParse(config);
@@ -82652,7 +82772,7 @@ function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing =
82652
82772
  }
82653
82773
  if (!createIfMissing) return { changed: false, created: false };
82654
82774
  tx.insert(schedules).values({
82655
- id: crypto49.randomUUID(),
82775
+ id: crypto50.randomUUID(),
82656
82776
  projectId,
82657
82777
  kind: SchedulableRunKinds["traffic-sync"],
82658
82778
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -82973,7 +83093,7 @@ async function runBackfillTask(options) {
82973
83093
  }
82974
83094
  })();
82975
83095
  tx.insert(rawEventSamples).values({
82976
- id: crypto49.randomUUID(),
83096
+ id: crypto50.randomUUID(),
82977
83097
  projectId: project.id,
82978
83098
  sourceId: sourceRow2.id,
82979
83099
  ts: sampleTimestamp,
@@ -83164,7 +83284,7 @@ async function trafficRoutes(app, opts) {
83164
83284
  const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
83165
83285
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
83166
83286
  const activeSource = tx.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
83167
- const sourceId = activeSource?.id ?? crypto49.randomUUID();
83287
+ const sourceId = activeSource?.id ?? crypto50.randomUUID();
83168
83288
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
83169
83289
  if (activeSource) {
83170
83290
  tx.update(trafficSources).set({
@@ -83256,7 +83376,7 @@ async function trafficRoutes(app, opts) {
83256
83376
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
83257
83377
  const activeSource = tx.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
83258
83378
  const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
83259
- const sourceId = endpointChanged ? crypto49.randomUUID() : activeSource?.id ?? crypto49.randomUUID();
83379
+ const sourceId = endpointChanged ? crypto50.randomUUID() : activeSource?.id ?? crypto50.randomUUID();
83260
83380
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
83261
83381
  if (activeSource && !endpointChanged) {
83262
83382
  tx.update(trafficSources).set({
@@ -83368,7 +83488,7 @@ async function trafficRoutes(app, opts) {
83368
83488
  }).where(eq76(trafficSources.id, activeSource.id)).run();
83369
83489
  row = tx.select().from(trafficSources).where(eq76(trafficSources.id, activeSource.id)).get();
83370
83490
  } else {
83371
- const newId = crypto49.randomUUID();
83491
+ const newId = crypto50.randomUUID();
83372
83492
  tx.insert(trafficSources).values({
83373
83493
  id: newId,
83374
83494
  projectId: project.id,
@@ -83438,7 +83558,7 @@ async function trafficRoutes(app, opts) {
83438
83558
  const cloudflareSources = app.db.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
83439
83559
  const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
83440
83560
  if (sameModeSource) attempt.update({ action: "reauth" });
83441
- const sourceId2 = sameModeSource?.id ?? crypto49.randomUUID();
83561
+ const sourceId2 = sameModeSource?.id ?? crypto50.randomUUID();
83442
83562
  const previousCredential2 = credentialStore.getConnectionBySourceId(sourceId2);
83443
83563
  const workerVersion2 = CURRENT_CLOUDFLARE_WORKER_VERSION;
83444
83564
  const workerScript2 = generateWorkerScript({
@@ -83574,7 +83694,7 @@ async function trafficRoutes(app, opts) {
83574
83694
  const now = (/* @__PURE__ */ new Date()).toISOString();
83575
83695
  const activeSource = app.db.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
83576
83696
  if (activeSource) attempt.update({ action: "reauth" });
83577
- const sourceId = activeSource?.id ?? crypto49.randomUUID();
83697
+ const sourceId = activeSource?.id ?? crypto50.randomUUID();
83578
83698
  const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
83579
83699
  if (activeSource && !parsedActiveConfig) {
83580
83700
  throw validationError("Existing Cloudflare source is not configured for direct-push delivery");
@@ -83590,8 +83710,8 @@ async function trafficRoutes(app, opts) {
83590
83710
  const previousAccountId = reusableCredential?.accountId ?? parsedActiveConfig?.accountId ?? null;
83591
83711
  const effectiveZoneId = zoneId ?? previousZoneId;
83592
83712
  const effectiveAccountId = accountId ?? previousAccountId;
83593
- const bearerToken = reusableCredential?.bearerToken ?? `cnry_cfw_${crypto49.randomBytes(32).toString("hex")}`;
83594
- const hmacSecret = reusableCredential?.hmacSecret ?? crypto49.randomBytes(32).toString("hex");
83713
+ const bearerToken = reusableCredential?.bearerToken ?? `cnry_cfw_${crypto50.randomBytes(32).toString("hex")}`;
83714
+ const hmacSecret = reusableCredential?.hmacSecret ?? crypto50.randomBytes(32).toString("hex");
83595
83715
  const ingestTokenHash = sha256Hex3(bearerToken);
83596
83716
  const ingestUrl = opts.cloudflareTrafficIngestUrl.replace("{name}", encodeURIComponent(project.name));
83597
83717
  assertCloudflareIngestUrlIsPublicHttps(ingestUrl);
@@ -83937,7 +84057,7 @@ async function trafficRoutes(app, opts) {
83937
84057
  apiToken: credential.apiToken
83938
84058
  };
83939
84059
  const now = (/* @__PURE__ */ new Date()).toISOString();
83940
- const leaseOwner = crypto49.randomUUID();
84060
+ const leaseOwner = crypto50.randomUUID();
83941
84061
  if (!tryClaimTrafficSyncLease({
83942
84062
  db: app.db,
83943
84063
  sourceId: sourceRow2.id,
@@ -83947,7 +84067,7 @@ async function trafficRoutes(app, opts) {
83947
84067
  })) {
83948
84068
  throw operationInProgress("Cloudflare Queue source sync is already in progress", { sourceId: sourceRow2.id });
83949
84069
  }
83950
- const runId = crypto49.randomUUID();
84070
+ const runId = crypto50.randomUUID();
83951
84071
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
83952
84072
  const startedMs = Date.now();
83953
84073
  try {
@@ -84187,7 +84307,7 @@ async function trafficRoutes(app, opts) {
84187
84307
  if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
84188
84308
  throw validationError("Traffic source must be active before it can sync");
84189
84309
  }
84190
- const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ? crypto49.randomUUID() : void 0;
84310
+ const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ? crypto50.randomUUID() : void 0;
84191
84311
  if (wordpressLeaseOwner && !tryClaimTrafficSyncLease({
84192
84312
  db: app.db,
84193
84313
  sourceId: sourceRow2.id,
@@ -84202,7 +84322,7 @@ async function trafficRoutes(app, opts) {
84202
84322
  let windowEnd = syncWindowEnd;
84203
84323
  const startedAt = syncWindowEnd.toISOString();
84204
84324
  const syncStartedAtMs = syncWindowEnd.getTime();
84205
- const runId = crypto49.randomUUID();
84325
+ const runId = crypto50.randomUUID();
84206
84326
  app.db.insert(runs).values({
84207
84327
  id: runId,
84208
84328
  projectId: project.id,
@@ -84751,7 +84871,7 @@ async function trafficRoutes(app, opts) {
84751
84871
  }
84752
84872
  })();
84753
84873
  tx.insert(rawEventSamples).values({
84754
- id: crypto49.randomUUID(),
84874
+ id: crypto50.randomUUID(),
84755
84875
  projectId: project.id,
84756
84876
  sourceId: sourceRow2.id,
84757
84877
  ts: sampleTimestamp,
@@ -84983,7 +85103,7 @@ async function trafficRoutes(app, opts) {
84983
85103
  };
84984
85104
  }
84985
85105
  const startedAt = windowEnd.toISOString();
84986
- const runId = crypto49.randomUUID();
85106
+ const runId = crypto50.randomUUID();
84987
85107
  app.db.transaction((tx) => {
84988
85108
  const latestSource = tx.select().from(trafficSources).where(eq76(trafficSources.id, sourceRow2.id)).get();
84989
85109
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
@@ -85864,7 +85984,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
85864
85984
  }];
85865
85985
 
85866
85986
  // ../api-routes/src/doctor/checks/agent.ts
85867
- import crypto50 from "crypto";
85987
+ import crypto51 from "crypto";
85868
85988
  import fs6 from "fs";
85869
85989
  import path7 from "path";
85870
85990
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -86098,7 +86218,7 @@ function isInstalled(dir) {
86098
86218
  }
86099
86219
  function hashInstalledFile(filePath) {
86100
86220
  try {
86101
- return crypto50.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
86221
+ return crypto51.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
86102
86222
  } catch {
86103
86223
  return void 0;
86104
86224
  }
@@ -89981,7 +90101,7 @@ async function statusRoutes(app, opts) {
89981
90101
  }
89982
90102
 
89983
90103
  // ../api-routes/src/discovery/routes.ts
89984
- import crypto51 from "crypto";
90104
+ import crypto52 from "crypto";
89985
90105
  import { and as and76, desc as desc42, eq as eq92, gte as gte23, inArray as inArray37, isNull as isNull17, or as or21 } from "drizzle-orm";
89986
90106
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
89987
90107
  async function discoveryRoutes(app, opts) {
@@ -90047,8 +90167,8 @@ async function discoveryRoutes(app, opts) {
90047
90167
  if (existing && existing.runId) {
90048
90168
  return { reused: true, sessionId: existing.id, runId: existing.runId };
90049
90169
  }
90050
- const sessionId = crypto51.randomUUID();
90051
- const runId = crypto51.randomUUID();
90170
+ const sessionId = crypto52.randomUUID();
90171
+ const runId = crypto52.randomUUID();
90052
90172
  tx.insert(discoverySessions).values({
90053
90173
  id: sessionId,
90054
90174
  projectId: project.id,
@@ -90303,7 +90423,7 @@ async function discoveryRoutes(app, opts) {
90303
90423
  const marketNames = beginMarketNameWrite(tx, project.id);
90304
90424
  for (const query of promotedQueries) {
90305
90425
  tx.insert(queries).values({
90306
- id: crypto51.randomUUID(),
90426
+ id: crypto52.randomUUID(),
90307
90427
  projectId: project.id,
90308
90428
  query,
90309
90429
  provenance,
@@ -90484,7 +90604,7 @@ function planCompetitorPromotion(input) {
90484
90604
  }
90485
90605
 
90486
90606
  // ../api-routes/src/discovery/orchestrate.ts
90487
- import crypto52 from "crypto";
90607
+ import crypto53 from "crypto";
90488
90608
  import { eq as eq93 } from "drizzle-orm";
90489
90609
  var DEFAULT_MAX_PROBES = 100;
90490
90610
  var ABSOLUTE_MAX_PROBES = 500;
@@ -90650,7 +90770,7 @@ async function executeDiscovery(opts) {
90650
90770
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
90651
90771
  buckets[bucket]++;
90652
90772
  return {
90653
- id: crypto52.randomUUID(),
90773
+ id: crypto53.randomUUID(),
90654
90774
  sessionId: opts.sessionId,
90655
90775
  projectId: opts.project.id,
90656
90776
  query: probedCanonicals[index2],
@@ -90702,7 +90822,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
90702
90822
  const domain = hostOf(entry.domain) ?? "";
90703
90823
  if (!domain) continue;
90704
90824
  db.insert(domainClassifications).values({
90705
- id: crypto52.randomUUID(),
90825
+ id: crypto53.randomUUID(),
90706
90826
  projectId,
90707
90827
  domain,
90708
90828
  competitorType: entry.competitorType,
@@ -90742,7 +90862,7 @@ function dedupeStrings(input) {
90742
90862
  }
90743
90863
 
90744
90864
  // ../api-routes/src/technical-aeo.ts
90745
- import crypto53 from "crypto";
90865
+ import crypto54 from "crypto";
90746
90866
  import { and as and78, asc as asc18, count as count7, desc as desc43, eq as eq95, gte as gte24, inArray as inArray38, isNotNull as isNotNull9, isNull as isNull18, lt as lt13, or as or22, sql as sql35 } from "drizzle-orm";
90747
90867
  import { alias } from "drizzle-orm/sqlite-core";
90748
90868
 
@@ -92735,7 +92855,7 @@ async function technicalAeoRoutes(app, opts) {
92735
92855
  const healthState = parseSiteHealthState(request.query.healthState);
92736
92856
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
92737
92857
  const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
92738
- const selectionKey = crypto53.createHash("sha256").update(JSON.stringify({
92858
+ const selectionKey = crypto54.createHash("sha256").update(JSON.stringify({
92739
92859
  projectId: project.id,
92740
92860
  runId: snapshot.runId,
92741
92861
  attemptId: snapshot.attemptId,
@@ -93235,7 +93355,7 @@ async function technicalAeoRoutes(app, opts) {
93235
93355
  );
93236
93356
  }
93237
93357
  const now = (/* @__PURE__ */ new Date()).toISOString();
93238
- const runId = crypto53.randomUUID();
93358
+ const runId = crypto54.randomUUID();
93239
93359
  tx.insert(runs).values({
93240
93360
  id: runId,
93241
93361
  projectId: project.id,
@@ -93272,7 +93392,7 @@ async function technicalAeoRoutes(app, opts) {
93272
93392
  }
93273
93393
 
93274
93394
  // ../api-routes/src/research.ts
93275
- import crypto54 from "crypto";
93395
+ import crypto55 from "crypto";
93276
93396
  import { z as z5 } from "zod";
93277
93397
  import { and as and79, count as count8, desc as desc44, eq as eq96, gte as gte25, lt as lt14, or as or23, sql as sql36 } from "drizzle-orm";
93278
93398
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
@@ -93342,9 +93462,9 @@ async function researchRoutes(app, opts) {
93342
93462
  throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
93343
93463
  }
93344
93464
  }
93345
- const id = crypto54.randomUUID();
93465
+ const id = crypto55.randomUUID();
93346
93466
  tx.insert(researchRuns).values({ id, projectId: project.id, status: ResearchRunStatuses.queued, provider: providerName, requestedModel: requestedModel2, resolvedModel, location: location ?? null, totalQueries: input.queries.length, scope, template, idempotencyKey: input.idempotencyKey ?? null, requestHash: input.idempotencyKey ? requestHash2 : null, initiatedBy, createdAt: now }).run();
93347
- for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto54.randomUUID(), researchRunId: id, position, queryText: query, queryClass: queryClasses[position] ?? null, status: ResearchQueryStatuses.queued, requestedModel: requestedModel2, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
93467
+ for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto55.randomUUID(), researchRunId: id, position, queryText: query, queryClass: queryClasses[position] ?? null, status: ResearchQueryStatuses.queued, requestedModel: requestedModel2, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
93348
93468
  writeAuditLog(tx, { projectId: project.id, actor: "api", action: "research.created", entityType: "research_run", entityId: id });
93349
93469
  return { reused: false, id, shouldDispatch: true };
93350
93470
  });
@@ -93397,7 +93517,7 @@ async function researchRoutes(app, opts) {
93397
93517
  }
93398
93518
  const ids = [];
93399
93519
  for (const [index2, run] of prepared.entries()) {
93400
- const id = crypto54.randomUUID();
93520
+ const id = crypto55.randomUUID();
93401
93521
  ids.push(id);
93402
93522
  tx.insert(researchRuns).values({
93403
93523
  id,
@@ -93416,7 +93536,7 @@ async function researchRoutes(app, opts) {
93416
93536
  createdAt: now
93417
93537
  }).run();
93418
93538
  for (const [position, query] of run.input.queries.entries()) tx.insert(researchRunQueries).values({
93419
- id: crypto54.randomUUID(),
93539
+ id: crypto55.randomUUID(),
93420
93540
  researchRunId: id,
93421
93541
  position,
93422
93542
  queryText: query,
@@ -93475,7 +93595,7 @@ async function researchRoutes(app, opts) {
93475
93595
  var DIRECT_REQUEST_HASH_VERSION = "direct-v2:";
93476
93596
  function directResearchRequestHash(input) {
93477
93597
  const { idempotencyKey: _key, ...request } = input;
93478
- return DIRECT_REQUEST_HASH_VERSION + crypto54.createHash("sha256").update(JSON.stringify(request)).digest("hex");
93598
+ return DIRECT_REQUEST_HASH_VERSION + crypto55.createHash("sha256").update(JSON.stringify(request)).digest("hex");
93479
93599
  }
93480
93600
  function assertSameDirectResearchRequest(existing, input, hash2) {
93481
93601
  if (existing.requestHash?.startsWith(DIRECT_REQUEST_HASH_VERSION)) {
@@ -93493,7 +93613,7 @@ function assertSameDirectResearchRequest(existing, input, hash2) {
93493
93613
  ...scope ? { scope } : {},
93494
93614
  ...template ? { template } : {}
93495
93615
  };
93496
- if (crypto54.createHash("sha256").update(JSON.stringify(normalized)).digest("hex") !== existing.requestHash) throw alreadyExists("Research idempotency key", input.idempotencyKey);
93616
+ if (crypto55.createHash("sha256").update(JSON.stringify(normalized)).digest("hex") !== existing.requestHash) throw alreadyExists("Research idempotency key", input.idempotencyKey);
93497
93617
  }
93498
93618
  var researchCursorSchema = z5.object({ projectId: z5.string().min(1), createdAt: z5.string().datetime({ offset: true }), id: z5.string().min(1).max(256) }).strict();
93499
93619
  function parseResearchCursor(value, projectId) {
@@ -93513,11 +93633,11 @@ function resolveStoredResearchScope(stored, selection, idempotencyKey) {
93513
93633
  return stored;
93514
93634
  }
93515
93635
  function batchChildReceiptKey(rootKey, index2) {
93516
- const derived = crypto54.createHash("sha256").update(`${rootKey}\0${index2}`).digest("hex");
93636
+ const derived = crypto55.createHash("sha256").update(`${rootKey}\0${index2}`).digest("hex");
93517
93637
  return `${BATCH_RECEIPT_PREFIX}${derived}:${index2}`;
93518
93638
  }
93519
93639
  function researchBatchRequestHash(input) {
93520
- return crypto54.createHash("sha256").update(JSON.stringify({ runs: input.runs })).digest("hex");
93640
+ return crypto55.createHash("sha256").update(JSON.stringify({ runs: input.runs })).digest("hex");
93521
93641
  }
93522
93642
  function prepareBatchRun(app, opts, project, active, input) {
93523
93643
  const adapters = opts.providerAdapters ?? [];
@@ -93789,7 +93909,7 @@ function readStoredGroundingSources(rawResponse) {
93789
93909
  }
93790
93910
 
93791
93911
  // ../api-routes/src/simple-measurement-definitions.ts
93792
- import crypto55 from "crypto";
93912
+ import crypto56 from "crypto";
93793
93913
  import { and as and81, eq as eq98 } from "drizzle-orm";
93794
93914
  function captureSimpleMeasurementDefinition(db, input) {
93795
93915
  return db.transaction((tx) => {
@@ -93831,11 +93951,11 @@ function captureSimpleMeasurementDefinition(db, input) {
93831
93951
  ...frozen.identity.qualifiedAliases?.length ? { qualifiedAliases: frozen.identity.qualifiedAliases } : {}
93832
93952
  }
93833
93953
  };
93834
- const replayChecksum = crypto55.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(replayDefinition)).digest("hex");
93954
+ const replayChecksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(replayDefinition)).digest("hex");
93835
93955
  if (existing.checksum !== replayChecksum) {
93836
93956
  if (frozen.competitors === void 0) {
93837
93957
  const { competitors: _currentCompetitors, ...legacyReplayDefinition } = replayDefinition;
93838
- const legacyReplayChecksum = crypto55.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(legacyReplayDefinition)).digest("hex");
93958
+ const legacyReplayChecksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(legacyReplayDefinition)).digest("hex");
93839
93959
  if (existing.checksum === legacyReplayChecksum) return frozen;
93840
93960
  }
93841
93961
  throw validationError("This run already has a captured measurement definition. Start a new run for changed inputs.");
@@ -93859,7 +93979,7 @@ function captureSimpleMeasurementDefinition(db, input) {
93859
93979
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
93860
93980
  }
93861
93981
  }
93862
- const checksum = crypto55.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(definition2)).digest("hex");
93982
+ const checksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(definition2)).digest("hex");
93863
93983
  tx.insert(simpleMeasurementDefinitions).values({
93864
93984
  projectId: input.projectId,
93865
93985
  runId: input.runId,
@@ -94474,7 +94594,7 @@ function buildTrafficSourceValidators(opts) {
94474
94594
  }
94475
94595
 
94476
94596
  // src/intelligence-service.ts
94477
- import crypto56 from "crypto";
94597
+ import crypto57 from "crypto";
94478
94598
 
94479
94599
  // ../api-routes/src/runtime-logger.ts
94480
94600
  import { format } from "util";
@@ -95276,7 +95396,7 @@ var IntelligenceService = class {
95276
95396
  }).run();
95277
95397
  }
95278
95398
  tx.insert(healthSnapshots).values({
95279
- id: crypto56.randomUUID(),
95399
+ id: crypto57.randomUUID(),
95280
95400
  projectId,
95281
95401
  runId,
95282
95402
  overallCitedRate: String(result.health.overallCitedRate),
@@ -28,7 +28,7 @@ import {
28
28
  persistSiteCrawlGraphLayout,
29
29
  prepareSiteCrawlGraphLayout,
30
30
  recordedSharePct
31
- } from "./chunk-KPOZR6MV.js";
31
+ } from "./chunk-QWLKYIQ4.js";
32
32
  import {
33
33
  CanonryMcpToolNames,
34
34
  PACKAGE_VERSION,
@@ -240,7 +240,7 @@ import {
240
240
  webhookResponseReason,
241
241
  webhookTargetRefusalReason,
242
242
  writeAuditLog
243
- } from "./chunk-ZOTA3MOL.js";
243
+ } from "./chunk-IBRZ23VQ.js";
244
244
  import {
245
245
  AGENT_MEMORY_VALUE_MAX_BYTES,
246
246
  AGENT_NONE,
@@ -8379,7 +8379,7 @@ function nonBlank(value) {
8379
8379
  return trimmed ? trimmed : void 0;
8380
8380
  }
8381
8381
  function resolveBuildCommit(env = process.env) {
8382
- const embedded = true ? "9ce35e04ebca5b27f629e69d2e2776b1203ac5ee" : void 0;
8382
+ const embedded = true ? "d2f4f0349b43d56e3cb425dfe64da3dcefa565ea" : void 0;
8383
8383
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
8384
8384
  }
8385
8385
  function resolveInstanceIdentity(env = process.env) {
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-ZOTA3MOL.js";
8
+ } from "./chunk-IBRZ23VQ.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  REGISTERED_CLI_COMMANDS
3
- } from "./chunk-2UQ6WVWP.js";
4
- import "./chunk-AYMS4LCP.js";
3
+ } from "./chunk-7R2HFAG4.js";
4
+ import "./chunk-OO64BWNB.js";
5
5
  import "./chunk-WMG365JI.js";
6
6
  import "./chunk-QBZPKXFW.js";
7
7
  import "./chunk-RB2F4D3Z.js";
8
- import "./chunk-KPOZR6MV.js";
8
+ import "./chunk-QWLKYIQ4.js";
9
9
  import "./chunk-PCZXWOYX.js";
10
10
  import "./chunk-HGMYEDAQ.js";
11
- import "./chunk-ZOTA3MOL.js";
11
+ import "./chunk-IBRZ23VQ.js";
12
12
  import "./chunk-V4TITINC.js";
13
13
  export {
14
14
  REGISTERED_CLI_COMMANDS
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  buildSetupState,
14
14
  consumePendingServeHandoff,
15
15
  serveCommand
16
- } from "./chunk-2UQ6WVWP.js";
16
+ } from "./chunk-7R2HFAG4.js";
17
17
  import {
18
18
  checkLatestVersionForCli,
19
19
  cliRuntimeContext,
@@ -27,13 +27,13 @@ import {
27
27
  showFirstRunNotice,
28
28
  trackCliCommandFinished,
29
29
  trackEvent
30
- } from "./chunk-AYMS4LCP.js";
30
+ } from "./chunk-OO64BWNB.js";
31
31
  import "./chunk-WMG365JI.js";
32
32
  import {
33
33
  resolveCliAccess
34
34
  } from "./chunk-QBZPKXFW.js";
35
35
  import "./chunk-RB2F4D3Z.js";
36
- import "./chunk-KPOZR6MV.js";
36
+ import "./chunk-QWLKYIQ4.js";
37
37
  import "./chunk-PCZXWOYX.js";
38
38
  import {
39
39
  CliError,
@@ -42,7 +42,7 @@ import {
42
42
  printCliError,
43
43
  usageError
44
44
  } from "./chunk-HGMYEDAQ.js";
45
- import "./chunk-ZOTA3MOL.js";
45
+ import "./chunk-IBRZ23VQ.js";
46
46
  import {
47
47
  OperationAccesses
48
48
  } from "./chunk-V4TITINC.js";
@@ -5,7 +5,7 @@ import {
5
5
  computeFactorAverages,
6
6
  layoutSiteCrawlGraphInput,
7
7
  persistSiteCrawlGraphLayout
8
- } from "./chunk-KPOZR6MV.js";
8
+ } from "./chunk-QWLKYIQ4.js";
9
9
  import {
10
10
  PACKAGE_VERSION,
11
11
  canonryMcpTools
@@ -86,7 +86,7 @@ import {
86
86
  siteCrawlPages,
87
87
  siteCrawlSnapshots,
88
88
  trafficSources
89
- } from "./chunk-ZOTA3MOL.js";
89
+ } from "./chunk-IBRZ23VQ.js";
90
90
  import {
91
91
  RunKinds,
92
92
  SiteCrawlFetchStates,
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-AYMS4LCP.js";
6
+ } from "./chunk-OO64BWNB.js";
7
7
  import {
8
8
  loadConfig
9
9
  } from "./chunk-WMG365JI.js";
10
- import "./chunk-KPOZR6MV.js";
10
+ import "./chunk-QWLKYIQ4.js";
11
11
  import "./chunk-PCZXWOYX.js";
12
12
  import "./chunk-HGMYEDAQ.js";
13
- import "./chunk-ZOTA3MOL.js";
13
+ import "./chunk-IBRZ23VQ.js";
14
14
  import "./chunk-V4TITINC.js";
15
15
  export {
16
16
  GoogleMarketingRuntimeError,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HISTORY_WINDOW_RUNS,
3
3
  IntelligenceService
4
- } from "./chunk-ZOTA3MOL.js";
4
+ } from "./chunk-IBRZ23VQ.js";
5
5
  import "./chunk-V4TITINC.js";
6
6
  export {
7
7
  HISTORY_WINDOW_RUNS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "7.46.0",
3
+ "version": "7.46.1",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "keywords": [
@@ -86,33 +86,33 @@
86
86
  "tsup": "^8.5.1",
87
87
  "tsx": "^4.19.0",
88
88
  "@canonry/canonry-web": "0.0.0",
89
- "@canonry/canonry-contracts": "0.0.0",
90
- "@canonry/canonry-config": "0.0.0",
91
89
  "@canonry/canonry-api-client": "0.0.0",
92
- "@canonry/canonry-db": "0.0.0",
93
90
  "@canonry/canonry-api-routes": "0.0.0",
91
+ "@canonry/canonry-config": "0.0.0",
92
+ "@canonry/canonry-contracts": "0.0.0",
93
+ "@canonry/canonry-db": "0.0.0",
94
94
  "@canonry/canonry-integration-bing": "0.0.0",
95
- "@canonry/canonry-integration-cloudflare-worker": "0.0.0",
95
+ "@canonry/canonry-integration-cloud-run": "0.0.0",
96
96
  "@canonry/canonry-integration-cloudflare-queue": "0.0.0",
97
+ "@canonry/canonry-integration-cloudflare-worker": "0.0.0",
97
98
  "@canonry/canonry-integration-commoncrawl": "0.0.0",
98
- "@canonry/canonry-integration-google": "0.0.0",
99
- "@canonry/canonry-integration-cloud-run": "0.0.0",
100
- "@canonry/canonry-integration-google-places": "0.0.0",
101
99
  "@canonry/canonry-integration-google-ads": "0.0.0",
102
100
  "@canonry/canonry-integration-google-business-profile": "0.0.0",
103
101
  "@canonry/canonry-integration-google-tag-manager": "0.0.0",
102
+ "@canonry/canonry-integration-google": "0.0.0",
103
+ "@canonry/canonry-integration-google-places": "0.0.0",
104
+ "@canonry/canonry-integration-traffic": "0.0.0",
105
+ "@canonry/canonry-integration-wordpress": "0.0.0",
104
106
  "@canonry/canonry-integration-openai-ads": "0.0.0",
105
107
  "@canonry/canonry-intelligence": "0.0.0",
106
108
  "@canonry/canonry-provider-cdp": "0.0.0",
107
- "@canonry/canonry-integration-wordpress": "0.0.0",
108
- "@canonry/canonry-integration-traffic": "0.0.0",
109
109
  "@canonry/canonry-provider-claude": "0.0.0",
110
- "@canonry/canonry-provider-muse": "0.0.0",
111
110
  "@canonry/canonry-provider-gemini": "0.0.0",
112
- "@canonry/canonry-provider-openai": "0.0.0",
111
+ "@canonry/canonry-provider-muse": "0.0.0",
113
112
  "@canonry/canonry-provider-local": "0.0.0",
114
- "@canonry/canonry-provider-perplexity": "0.0.0",
115
- "@canonry/canonry-integration-typesafe": "0.0.0"
113
+ "@canonry/canonry-provider-openai": "0.0.0",
114
+ "@canonry/canonry-integration-typesafe": "0.0.0",
115
+ "@canonry/canonry-provider-perplexity": "0.0.0"
116
116
  },
117
117
  "scripts": {
118
118
  "build": "pnpm run build:cli && pnpm run build:web",