@canonry/canonry 6.1.5 → 6.3.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/agent-workspace/skills/canonry/references/canonry-cli.md +13 -1
  2. package/assets/assets/{AuditHistoryPanel-D-W_kwEY.js → AuditHistoryPanel-BUYWIjlG.js} +1 -1
  3. package/assets/assets/{BacklinksPage-BeqCFRO3.js → BacklinksPage-aBcxpN_9.js} +1 -1
  4. package/assets/assets/{HistoryPage-wAPbY0z4.js → HistoryPage-PoBuxiY_.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-BSOrkHCB.js → MeasurementPropertyPage-DyjyqHac.js} +1 -1
  6. package/assets/assets/{ProjectPage-v0C7CwyA.js → ProjectPage-Mmv5YW-K.js} +1 -1
  7. package/assets/assets/{RunsPage-CxolaIps.js → RunsPage-CHHxOpzo.js} +1 -1
  8. package/assets/assets/{SettingsPage-BQqJz0Ms.js → SettingsPage-CNKnrLXC.js} +1 -1
  9. package/assets/assets/{SiteHealthSection-C56kNcQQ.js → SiteHealthSection-C9RivT7G.js} +3 -3
  10. package/assets/assets/{TrafficPage-BDmWdYWf.js → TrafficPage-CY-ibXnb.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-DZl3YNBs.js → TrafficSourceDetailPage-BgiPXubs.js} +1 -1
  12. package/assets/assets/{extract-error-message-D0mBVEGn.js → extract-error-message-H1dzzfBP.js} +1 -1
  13. package/assets/assets/{index-DZ__RzGa.js → index-DSCun3I9.js} +22 -22
  14. package/assets/assets/{react-sigma_core.esm.min-BQNp33-g.js → react-sigma_core.esm.min-uiYtR_DU.js} +1 -1
  15. package/assets/assets/{v2-overview-adapter-XZbD7swG.js → v2-overview-adapter-BQokN2eb.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-PAYC6TQ2.js → chunk-5GY5WBAF.js} +3 -3
  18. package/dist/{chunk-5QH72CTN.js → chunk-DERWXLHX.js} +15 -1
  19. package/dist/{chunk-J7LV4CS2.js → chunk-EV5G5QRR.js} +335 -280
  20. package/dist/{chunk-MSFBNFTI.js → chunk-FSRVG2H7.js} +2 -2
  21. package/dist/{chunk-4UEX44C3.js → chunk-OTROA7CN.js} +395 -173
  22. package/dist/{chunk-MTEVU6KG.js → chunk-QVGG76UW.js} +79 -24
  23. package/dist/{chunk-FRBKYQN7.js → chunk-ZZEPF3A5.js} +24 -4
  24. package/dist/cli.js +55 -10
  25. package/dist/{demo-server-UGILQVEC.js → demo-server-5MNLI6GC.js} +4 -4
  26. package/dist/index.js +6 -6
  27. package/dist/{intelligence-service-JYGP6LME.js → intelligence-service-5J36RBEM.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +11 -11
@@ -19,21 +19,21 @@ import {
19
19
  runWithUsageTags,
20
20
  saveConfigPatch,
21
21
  sentimentInstallReadiness
22
- } from "./chunk-FRBKYQN7.js";
22
+ } from "./chunk-ZZEPF3A5.js";
23
23
  import {
24
24
  computeFactorAverages,
25
25
  deleteSiteCrawlGraphLayout,
26
26
  persistSiteCrawlGraphLayout,
27
27
  prepareSiteCrawlGraphLayout,
28
28
  recordedSharePct
29
- } from "./chunk-MSFBNFTI.js";
29
+ } from "./chunk-FSRVG2H7.js";
30
30
  import {
31
31
  CanonryMcpToolNames,
32
32
  CliError,
33
33
  PACKAGE_VERSION,
34
34
  canonryMcpTools,
35
35
  isMachineFormat
36
- } from "./chunk-5QH72CTN.js";
36
+ } from "./chunk-DERWXLHX.js";
37
37
  import {
38
38
  CC_CACHE_DIR,
39
39
  DUCKDB_SPEC,
@@ -214,7 +214,7 @@ import {
214
214
  siteLivenessState,
215
215
  toAlertView,
216
216
  usageCounters
217
- } from "./chunk-MTEVU6KG.js";
217
+ } from "./chunk-QVGG76UW.js";
218
218
  import {
219
219
  AGENT_MEMORY_VALUE_MAX_BYTES,
220
220
  AGENT_NONE,
@@ -232,6 +232,7 @@ import {
232
232
  CheckNotificationPolicies,
233
233
  DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT,
234
234
  DiscoveryCompetitorTypes,
235
+ FEEDBACK_LIMITS,
235
236
  GOOGLE_ADS_CAMPAIGN_METRICS_MAX_CAMPAIGNS,
236
237
  GOOGLE_ADS_CAMPAIGN_METRICS_MAX_DAYS,
237
238
  GOOGLE_ADS_CAMPAIGN_METRICS_MAX_ROWS,
@@ -304,6 +305,7 @@ import {
304
305
  canonicalizeGtmAccountId,
305
306
  canonicalizeGtmContainerId,
306
307
  canonicalizeGtmResourceSelection,
308
+ classifyProviderErrorMessage,
307
309
  classifyProviderErrorMessages,
308
310
  classifyTemplateLinkEdge,
309
311
  cloudflareQueueNameSchema,
@@ -315,6 +317,7 @@ import {
315
317
  contentBriefDtoSchema,
316
318
  createTemplateLinkDetectionTally,
317
319
  createTemplateLinkPairIndex,
320
+ deliveryFailed,
318
321
  deriveCitedUrlCandidates,
319
322
  deriveGoogleAdsEffectiveGoalGraph,
320
323
  deriveIndexCoverage,
@@ -327,6 +330,7 @@ import {
327
330
  effectiveBrandNames,
328
331
  effectiveDomains,
329
332
  embedClientConfigForRequest,
333
+ extractProviderHttpStatus,
330
334
  factorStatusFromScore,
331
335
  filterCapturedCitedUrls,
332
336
  forbidden,
@@ -383,7 +387,9 @@ import {
383
387
  percentOf,
384
388
  providerError,
385
389
  providerSupportsLocationContext,
390
+ quotaExceeded,
386
391
  ratioUnitSchema,
392
+ redactFeedbackText,
387
393
  registrableDomain,
388
394
  reportMonthsForDoctor,
389
395
  resolveNegativeReviewMaxStars,
@@ -414,7 +420,7 @@ import {
414
420
  visibilityReportRequestSchema,
415
421
  winnabilityClassLabel,
416
422
  withRetry
417
- } from "./chunk-J7LV4CS2.js";
423
+ } from "./chunk-EV5G5QRR.js";
418
424
 
419
425
  // src/runtime-context.ts
420
426
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -3629,7 +3635,7 @@ function createGoogleMarketingRuntime(options) {
3629
3635
 
3630
3636
  // src/server.ts
3631
3637
  import { createRequire as createRequire3 } from "module";
3632
- import crypto28 from "crypto";
3638
+ import crypto29 from "crypto";
3633
3639
  import fs9 from "fs";
3634
3640
  import path8 from "path";
3635
3641
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -4053,6 +4059,19 @@ import os4 from "os";
4053
4059
  import { GoogleGenAI } from "@google/genai";
4054
4060
 
4055
4061
  // ../provider-gemini/src/utils.ts
4062
+ var GEMINI_MAX_RATE_LIMIT_WAIT_MS = 65e3;
4063
+ function geminiRetryDelayMs(err) {
4064
+ const match = /"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/.exec(describeError(err));
4065
+ return match ? Math.round(Number(match[1]) * 1e3) : null;
4066
+ }
4067
+ function isGeminiDailyQuotaError(err) {
4068
+ return /"quotaId"\s*:\s*"[^"]*PerDay/i.test(describeError(err));
4069
+ }
4070
+ function isGeminiRetryable(err) {
4071
+ if (!isRetryableHttpError(err) || isGeminiDailyQuotaError(err)) return false;
4072
+ const asked = geminiRetryDelayMs(err);
4073
+ return asked === null || asked <= GEMINI_MAX_RATE_LIMIT_WAIT_MS;
4074
+ }
4056
4075
  async function withRetry2(fn, options = {}) {
4057
4076
  return withRetry(fn, {
4058
4077
  maxRetries: options.maxRetries ?? 3,
@@ -4061,7 +4080,12 @@ async function withRetry2(fn, options = {}) {
4061
4080
  // provider has shipped to date (1000, 2000, 4000ms). Enable per-caller
4062
4081
  // if you need it.
4063
4082
  jitter: false,
4064
- isRetryable: isRetryableHttpError,
4083
+ isRetryable: isGeminiRetryable,
4084
+ computeDelayMs: (_attempt, err, defaultMs) => {
4085
+ const asked = geminiRetryDelayMs(err);
4086
+ return asked === null ? defaultMs : asked + 500;
4087
+ },
4088
+ ...options.sleep ? { sleep: options.sleep } : {},
4065
4089
  onRetry: ({ attempt, err, delayMs }) => {
4066
4090
  console.warn(
4067
4091
  `[provider] Attempt ${attempt + 1} failed, retrying in ${delayMs}ms...`,
@@ -4072,7 +4096,7 @@ async function withRetry2(fn, options = {}) {
4072
4096
  }
4073
4097
 
4074
4098
  // ../provider-gemini/src/normalize.ts
4075
- var DEFAULT_MODEL = "gemini-2.5-flash";
4099
+ var DEFAULT_MODEL = "gemini-flash-latest";
4076
4100
  function isVertexConfig(config) {
4077
4101
  return !!config.vertexProject;
4078
4102
  }
@@ -4515,10 +4539,20 @@ var geminiAdapter = {
4515
4539
  keyUrl: "https://aistudio.google.com/apikey",
4516
4540
  // Upstream model list: https://ai.google.dev/gemini-api/docs/models
4517
4541
  modelRegistry: {
4518
- defaultModel: "gemini-2.5-flash",
4542
+ defaultModel: "gemini-flash-latest",
4519
4543
  validationPattern: /./,
4520
- validationHint: "any valid Google model name (e.g. gemini-2.5-flash, learnlm-1.5-pro-experimental)",
4544
+ validationHint: "any valid Google model name (e.g. gemini-flash-latest, gemini-3.8-flash)",
4521
4545
  knownModels: [
4546
+ // Google's moving aliases: always the newest Flash / Flash-Lite, with
4547
+ // 2 weeks' email notice before a breaking change. The model actually
4548
+ // served is recorded per snapshot (`servedModel`), so trends still show
4549
+ // when the model behind an alias changed.
4550
+ { id: "gemini-flash-latest", displayName: "Gemini Flash (latest)", tier: "standard" },
4551
+ { id: "gemini-flash-lite-latest", displayName: "Gemini Flash-Lite (latest)", tier: "economy" },
4552
+ { id: "gemini-3.8-flash", displayName: "Gemini 3.8 Flash", tier: "standard" },
4553
+ { id: "gemini-3.5-flash-lite", displayName: "Gemini 3.5 Flash-Lite", tier: "economy" },
4554
+ // Google limits the 2.5 models to projects that already used them, so a
4555
+ // new key may be refused. Listed for installs that still pin them.
4522
4556
  { id: "gemini-2.5-pro", displayName: "Gemini 2.5 Pro", tier: "flagship" },
4523
4557
  { id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", tier: "standard" },
4524
4558
  { id: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash-Lite", tier: "economy" },
@@ -7824,13 +7858,101 @@ function createApiUsageTelemetry(options = {}) {
7824
7858
  };
7825
7859
  }
7826
7860
 
7861
+ // src/feedback.ts
7862
+ import crypto4 from "crypto";
7863
+ var FEEDBACK_ENDPOINT = "https://canonry.ai/api/feedback";
7864
+ var TIMEOUT_MS2 = 1e4;
7865
+ var FEEDBACK_MAX_BODY_BYTES = 8e3;
7866
+ function feedbackSource(context) {
7867
+ const surface = classifyUsageSurface({
7868
+ userAgent: context.userAgent,
7869
+ usageLabels: { surface: context.surface }
7870
+ });
7871
+ if (surface === "cli" || surface === "dashboard") return surface;
7872
+ if (surface === "mcp-stdio" || surface === "mcp-http") return "mcp";
7873
+ return "api";
7874
+ }
7875
+ async function sendFeedback(submission, context, deps = {}) {
7876
+ const doFetch = deps.fetch ?? fetch;
7877
+ const telemetryOn = (deps.telemetryEnabled ?? isTelemetryEnabled)();
7878
+ const anonymousId = telemetryOn ? (deps.anonymousId ?? getOrCreateAnonymousId)() : void 0;
7879
+ const agent = [context.agent, context.mcpClient].map((label) => normalizeAgentSlug(label)).find((slug) => !!slug && slug !== "none");
7880
+ const payload = {
7881
+ feedbackId: crypto4.randomUUID(),
7882
+ ...anonymousId ? { anonymousId } : {},
7883
+ kind: submission.kind,
7884
+ summary: redactFeedbackText(submission.summary, FEEDBACK_LIMITS.summary),
7885
+ ...submission.details ? { details: redactFeedbackText(submission.details, FEEDBACK_LIMITS.details) } : {},
7886
+ ...submission.area ? { area: redactFeedbackText(submission.area, FEEDBACK_LIMITS.area) } : {},
7887
+ ...submission.command ? { command: redactFeedbackText(submission.command, FEEDBACK_LIMITS.command) } : {},
7888
+ ...submission.errorCode ? { errorCode: redactFeedbackText(submission.errorCode, FEEDBACK_LIMITS.errorCode) } : {},
7889
+ source: feedbackSource(context),
7890
+ ...agent ? { agent } : {},
7891
+ version: PACKAGE_VERSION.slice(0, 20),
7892
+ nodeVersion: process.versions.node.slice(0, 20),
7893
+ os: process.platform.slice(0, 20),
7894
+ arch: process.arch.slice(0, 20),
7895
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7896
+ };
7897
+ const body = fitToByteBudget(payload);
7898
+ let response;
7899
+ try {
7900
+ response = await doFetch(FEEDBACK_ENDPOINT, {
7901
+ method: "POST",
7902
+ headers: { "Content-Type": "application/json" },
7903
+ body,
7904
+ signal: AbortSignal.timeout(TIMEOUT_MS2)
7905
+ });
7906
+ } catch {
7907
+ throw deliveryFailed("Could not reach canonry.ai to send feedback. Check the network and try again.");
7908
+ }
7909
+ if (response.status === 202 || response.ok) {
7910
+ const body2 = await response.json().catch(() => ({}));
7911
+ return { accepted: body2.accepted ?? true, id: typeof body2.id === "string" ? body2.id : payload.feedbackId };
7912
+ }
7913
+ if (response.status === 429) throw quotaExceeded("feedback submissions", { retryLater: true });
7914
+ if (response.status === 400 || response.status === 413) {
7915
+ const body2 = await response.json().catch(() => ({}));
7916
+ throw validationError("canonry.ai rejected the feedback", body2.issues ? { issues: body2.issues } : void 0);
7917
+ }
7918
+ throw deliveryFailed(`canonry.ai could not store the feedback (HTTP ${response.status}). Try again later.`);
7919
+ }
7920
+ function truncateCodePoints(value, maxCodePoints) {
7921
+ return Array.from(value).slice(0, Math.max(0, maxCodePoints)).join("");
7922
+ }
7923
+ function fitToByteBudget(payload) {
7924
+ let current = { ...payload };
7925
+ for (const field of ["details", "summary"]) {
7926
+ let serialized = JSON.stringify(current);
7927
+ let bytes = Buffer.byteLength(serialized, "utf8");
7928
+ while (bytes > FEEDBACK_MAX_BODY_BYTES) {
7929
+ const text = current[field];
7930
+ if (typeof text !== "string" || text.length === 0) break;
7931
+ const points = Array.from(text).length;
7932
+ const target = points - Math.max(1, Math.ceil((bytes - FEEDBACK_MAX_BODY_BYTES) / 4));
7933
+ if (field === "summary" && target < 1) break;
7934
+ const shorter = truncateCodePoints(text, target);
7935
+ if (field === "details" && shorter.length === 0) {
7936
+ const { details: _dropped, ...rest } = current;
7937
+ current = rest;
7938
+ } else {
7939
+ current = { ...current, [field]: shorter };
7940
+ }
7941
+ serialized = JSON.stringify(current);
7942
+ bytes = Buffer.byteLength(serialized, "utf8");
7943
+ }
7944
+ if (bytes <= FEEDBACK_MAX_BODY_BYTES) return serialized;
7945
+ }
7946
+ return JSON.stringify(current);
7947
+ }
7948
+
7827
7949
  // src/instance-identity.ts
7828
7950
  function nonBlank(value) {
7829
7951
  const trimmed = value?.trim();
7830
7952
  return trimmed ? trimmed : void 0;
7831
7953
  }
7832
7954
  function resolveBuildCommit(env = process.env) {
7833
- const embedded = true ? "07710f04d6bdd8ec8b4374a93f0f3c3d1b56f498" : void 0;
7955
+ const embedded = true ? "a0351a3c7ac5eb8e0e4028ee004ddd5a12874575" : void 0;
7834
7956
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
7835
7957
  }
7836
7958
  function resolveInstanceIdentity(env = process.env) {
@@ -7841,15 +7963,15 @@ function resolveInstanceIdentity(env = process.env) {
7841
7963
  }
7842
7964
 
7843
7965
  // src/job-runner.ts
7844
- import crypto6 from "crypto";
7966
+ import crypto7 from "crypto";
7845
7967
  import fs4 from "fs";
7846
7968
  import path3 from "path";
7847
7969
  import os3 from "os";
7848
7970
  import { setImmediate as yieldToEventLoop } from "timers/promises";
7849
- import { and as and3, asc as asc2, count, eq as eq3, inArray, isNotNull, isNull, ne, sql as sql2 } from "drizzle-orm";
7971
+ import { and as and3, asc as asc2, count, desc, eq as eq3, inArray, isNotNull, isNull, lt, ne, sql as sql2 } from "drizzle-orm";
7850
7972
 
7851
7973
  // src/run-telemetry.ts
7852
- import crypto4 from "crypto";
7974
+ import crypto5 from "crypto";
7853
7975
  function extractRegistrableHost(input) {
7854
7976
  if (!input) return null;
7855
7977
  const trimmed = input.trim();
@@ -7869,7 +7991,7 @@ function extractRegistrableHost(input) {
7869
7991
  function hashDomain(input) {
7870
7992
  const host = extractRegistrableHost(input);
7871
7993
  if (!host) return null;
7872
- return crypto4.createHash("sha256").update(host).digest("hex");
7994
+ return crypto5.createHash("sha256").update(host).digest("hex");
7873
7995
  }
7874
7996
  function buildRunCompletedProps(input) {
7875
7997
  const totalMs = input.phases?.total_ms ?? Date.now() - input.startTime;
@@ -7911,6 +8033,51 @@ function buildSiteAuditCompletedProps(input) {
7911
8033
  if (crawl.checkDeadLinks) props.deadLinksFound = crawl.deadLinksFound;
7912
8034
  return props;
7913
8035
  }
8036
+ var MAX_NESTED_KEYS = 12;
8037
+ function buildProviderOutcomeProps(providers, providerErrors) {
8038
+ if (providerErrors.size === 0) return {};
8039
+ const names = [.../* @__PURE__ */ new Set([...providers, ...providerErrors.keys()])].slice(0, MAX_NESTED_KEYS);
8040
+ const providerOutcomes = {};
8041
+ const providerHttpStatus = {};
8042
+ for (const name of names) {
8043
+ const message = providerErrors.get(name);
8044
+ if (message === void 0) {
8045
+ providerOutcomes[name] = "ok";
8046
+ continue;
8047
+ }
8048
+ providerOutcomes[name] = classifyProviderErrorMessage(message);
8049
+ const status = extractProviderHttpStatus(message);
8050
+ if (status !== void 0) providerHttpStatus[name] = status;
8051
+ }
8052
+ return Object.keys(providerHttpStatus).length > 0 ? { providerOutcomes, providerHttpStatus } : { providerOutcomes };
8053
+ }
8054
+ function runFailureSite(providerCallStart, providerCallEnd) {
8055
+ if (providerCallStart === void 0) return "setup";
8056
+ return providerCallEnd === void 0 ? "provider_call" : "finalize";
8057
+ }
8058
+ var ERROR_NAME_RE = /^[a-z_$][\w$]{0,39}$/i;
8059
+ var ERROR_SYS_CODE_RE = /^[A-Z][A-Z0-9_]{1,39}$/;
8060
+ function describeRunFailure(err, site) {
8061
+ const props = site ? { errorSite: site } : {};
8062
+ const name = err instanceof Error ? err.name : typeof err;
8063
+ if (ERROR_NAME_RE.test(name)) props.errorName = name;
8064
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
8065
+ if (typeof code === "string" && ERROR_SYS_CODE_RE.test(code)) props.errorSysCode = code;
8066
+ return props;
8067
+ }
8068
+ var FAILURE_STREAK_SAMPLE_AFTER = 5;
8069
+ var FAILURE_STREAK_SAMPLE_RATE = 20;
8070
+ function failureStreakSampling(status, priorStreak, runId) {
8071
+ if (status !== "failed" && status !== "partial") return { report: true, props: {} };
8072
+ if (priorStreak < FAILURE_STREAK_SAMPLE_AFTER) {
8073
+ return { report: true, props: { failureStreak: priorStreak } };
8074
+ }
8075
+ const bucket = crypto5.createHash("sha256").update(runId).digest().readUInt32BE(0) % FAILURE_STREAK_SAMPLE_RATE;
8076
+ return {
8077
+ report: bucket === 0,
8078
+ props: { failureStreak: priorStreak, sampleRate: FAILURE_STREAK_SAMPLE_RATE }
8079
+ };
8080
+ }
7914
8081
 
7915
8082
  // src/provider-execution-gate.ts
7916
8083
  var ProviderExecutionGate = class {
@@ -7978,7 +8145,7 @@ function getSharedProviderExecutionGate(providerName, maxConcurrency, maxPerMinu
7978
8145
  }
7979
8146
 
7980
8147
  // src/usage-quota.ts
7981
- import crypto5 from "crypto";
8148
+ import crypto6 from "crypto";
7982
8149
  import { and as and2, eq as eq2, lte, sql } from "drizzle-orm";
7983
8150
  function getCurrentUsageDay() {
7984
8151
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -7995,7 +8162,7 @@ function reserveDailyQueryQuota(db, input) {
7995
8162
  )).run();
7996
8163
  if (reserveExisting().changes === 1) return { reserved: true, used: 0 };
7997
8164
  const inserted = tx.insert(usageCounters).values({
7998
- id: crypto5.randomUUID(),
8165
+ id: crypto6.randomUUID(),
7999
8166
  scope: input.scope,
8000
8167
  period: input.period,
8001
8168
  metric: "queries",
@@ -8259,6 +8426,7 @@ function classifyRunAbortReason(message) {
8259
8426
  if (/^Run [^ ]+ is not executable\b/.test(message)) return "run_not_executable";
8260
8427
  return void 0;
8261
8428
  }
8429
+ var FAILURE_STREAK_LOOKBACK = 50;
8262
8430
  function classifyProviderErrors(errors) {
8263
8431
  return classifyProviderErrorMessages(errors.values());
8264
8432
  }
@@ -8589,7 +8757,7 @@ var JobRunner = class {
8589
8757
  const answerLocation = runLocation && isSearchLocationIgnored(providerName, normalized.retrievalStatus) ? { location: null, requestedContext: runLocation, supportedContext: { status: "ignored" } } : { location: runLocation?.label ?? null };
8590
8758
  let screenshotRelPath = null;
8591
8759
  if (raw.screenshotPath && fs4.existsSync(raw.screenshotPath)) {
8592
- const snapshotId = crypto6.randomUUID();
8760
+ const snapshotId = crypto7.randomUUID();
8593
8761
  const screenshotDir = path3.join(os3.homedir(), ".canonry", "screenshots", runId);
8594
8762
  if (!fs4.existsSync(screenshotDir)) fs4.mkdirSync(screenshotDir, { recursive: true });
8595
8763
  const destPath = path3.join(screenshotDir, `${snapshotId}.png`);
@@ -8633,7 +8801,7 @@ var JobRunner = class {
8633
8801
  }).run();
8634
8802
  } else {
8635
8803
  this.db.insert(querySnapshots).values({
8636
- id: crypto6.randomUUID(),
8804
+ id: crypto7.randomUUID(),
8637
8805
  runId,
8638
8806
  queryId: q.id,
8639
8807
  queryText: q.query,
@@ -8853,21 +9021,28 @@ var JobRunner = class {
8853
9021
  ...executionContext.location ? { location: executionContext.location } : {}
8854
9022
  });
8855
9023
  } else {
8856
- trackEvent(
8857
- "run.completed",
8858
- buildRunCompletedProps({
8859
- status: "failed",
8860
- providerCount: executionContext.providerCount,
8861
- providers: executionContext.providers,
8862
- queryCount: executionContext.queryCount,
8863
- startTime,
8864
- trigger: executionContext.trigger,
8865
- canonicalDomain: executionContext.canonicalDomain,
8866
- phases,
8867
- location: executionContext.location
8868
- }),
8869
- { errorCode: "UNKNOWN" }
8870
- );
9024
+ const sampling = failureStreakSampling("failed", this.priorFailureStreak(projectId, runId), runId);
9025
+ if (sampling.report) {
9026
+ trackEvent(
9027
+ "run.completed",
9028
+ {
9029
+ ...buildRunCompletedProps({
9030
+ status: "failed",
9031
+ providerCount: executionContext.providerCount,
9032
+ providers: executionContext.providers,
9033
+ queryCount: executionContext.queryCount,
9034
+ startTime,
9035
+ trigger: executionContext.trigger,
9036
+ canonicalDomain: executionContext.canonicalDomain,
9037
+ phases,
9038
+ location: executionContext.location
9039
+ }),
9040
+ ...describeRunFailure(err, runFailureSite(providerCallStart, providerCallEnd)),
9041
+ ...sampling.props
9042
+ },
9043
+ { errorCode: "INTERNAL" }
9044
+ );
9045
+ }
8871
9046
  }
8872
9047
  if (this.onRunCompleted) {
8873
9048
  this.onRunCompleted(runId, projectId).catch((notifErr) => {
@@ -8931,21 +9106,28 @@ var JobRunner = class {
8931
9106
  reportFinalizedRun(input, finalStatus) {
8932
9107
  const { runId, projectId, providerErrors, executionContext } = input;
8933
9108
  const failureCode = providerErrors.size > 0 ? classifyProviderErrors(providerErrors) : void 0;
8934
- trackEvent(
8935
- "run.completed",
8936
- buildRunCompletedProps({
8937
- status: finalStatus,
8938
- providerCount: executionContext.providerCount,
8939
- providers: executionContext.providers,
8940
- queryCount: executionContext.queryCount,
8941
- startTime: input.startTime,
8942
- trigger: executionContext.trigger,
8943
- canonicalDomain: executionContext.canonicalDomain,
8944
- phases: input.phases,
8945
- location: executionContext.location
8946
- }),
8947
- failureCode ? { errorCode: failureCode } : void 0
8948
- );
9109
+ const sampling = failureStreakSampling(finalStatus, this.priorFailureStreak(projectId, runId), runId);
9110
+ if (sampling.report) {
9111
+ trackEvent(
9112
+ "run.completed",
9113
+ {
9114
+ ...buildRunCompletedProps({
9115
+ status: finalStatus,
9116
+ providerCount: executionContext.providerCount,
9117
+ providers: executionContext.providers,
9118
+ queryCount: executionContext.queryCount,
9119
+ startTime: input.startTime,
9120
+ trigger: executionContext.trigger,
9121
+ canonicalDomain: executionContext.canonicalDomain,
9122
+ phases: input.phases,
9123
+ location: executionContext.location
9124
+ }),
9125
+ ...buildProviderOutcomeProps(executionContext.providers, providerErrors),
9126
+ ...sampling.props
9127
+ },
9128
+ failureCode ? { errorCode: failureCode } : void 0
9129
+ );
9130
+ }
8949
9131
  if (input.kind === RunKinds["answer-visibility"] && executionContext.trigger !== RunTriggers.probe && input.inserted > 0 && !this.hasPriorActivation(projectId, runId)) {
8950
9132
  trackEvent("activation.completed", {
8951
9133
  flowVersion: ONBOARDING_FLOW_VERSION,
@@ -9159,7 +9341,7 @@ var JobRunner = class {
9159
9341
  syncUnits.push(unit);
9160
9342
  continue;
9161
9343
  }
9162
- const customId = crypto6.randomUUID().replace(/-/g, "");
9344
+ const customId = crypto7.randomUUID().replace(/-/g, "");
9163
9345
  const lines = linesByModel.get(model) ?? [];
9164
9346
  lines.push({ unit, requestedModel, customId, request, bytes: Buffer.byteLength(JSON.stringify({ custom_id: customId, params: request.body })) });
9165
9347
  linesByModel.set(model, lines);
@@ -9195,7 +9377,7 @@ var JobRunner = class {
9195
9377
  if (!capability || !reservation) throw new Error(`Provider ${providerName} cannot submit a batch for run ${ctx.runId}`);
9196
9378
  const deadlineMs = (config.batch?.deadlineHours ?? capability.defaultDeadlineHours) * HOUR_MS;
9197
9379
  const created = /* @__PURE__ */ new Date();
9198
- const rowId = crypto6.randomUUID();
9380
+ const rowId = crypto7.randomUUID();
9199
9381
  const ledger = lines.map((line) => ({
9200
9382
  id: line.customId,
9201
9383
  batchId: rowId,
@@ -9888,7 +10070,7 @@ var JobRunner = class {
9888
10070
  competitorAliases
9889
10071
  );
9890
10072
  const answerContext = supportedContext && isSearchLocationIgnored(providerName, normalized.retrievalStatus) ? { status: "ignored" } : supportedContext;
9891
- const snapshotId = crypto6.randomUUID();
10073
+ const snapshotId = crypto7.randomUUID();
9892
10074
  let screenshotRelPath = null;
9893
10075
  if (raw.screenshotPath && fs4.existsSync(raw.screenshotPath)) {
9894
10076
  const screenshotDir = path3.join(os3.homedir(), ".canonry", "screenshots", ctx.runId);
@@ -9990,7 +10172,7 @@ var JobRunner = class {
9990
10172
  const now = (/* @__PURE__ */ new Date()).toISOString();
9991
10173
  const period = now.slice(0, 10);
9992
10174
  this.db.insert(usageCounters).values({
9993
- id: crypto6.randomUUID(),
10175
+ id: crypto7.randomUUID(),
9994
10176
  scope,
9995
10177
  period,
9996
10178
  metric,
@@ -10008,6 +10190,36 @@ var JobRunner = class {
10008
10190
  }
10009
10191
  providerReservations.clear();
10010
10192
  }
10193
+ /**
10194
+ * How many runs of the same kind, created before this one, failed or came
10195
+ * back partial in a row. Telemetry only: it feeds `failureStreak` and the
10196
+ * sampling of runs stuck failing the same way. Probes do not count, other run
10197
+ * kinds (syncs, audits) neither break nor extend a streak, and a run aborted
10198
+ * before any provider was called (`run.aborted`, never a failed
10199
+ * `run.completed`) is skipped, so every failure counted was also reported.
10200
+ */
10201
+ priorFailureStreak(projectId, currentRunId) {
10202
+ try {
10203
+ const current = this.db.select({ kind: runs.kind, createdAt: runs.createdAt }).from(runs).where(eq3(runs.id, currentRunId)).get();
10204
+ if (!current) return 0;
10205
+ const recent = this.db.select({ status: runs.status, error: runs.error }).from(runs).where(and3(
10206
+ eq3(runs.projectId, projectId),
10207
+ eq3(runs.kind, current.kind),
10208
+ lt(runs.createdAt, current.createdAt),
10209
+ ne(runs.trigger, RunTriggers.probe),
10210
+ inArray(runs.status, ["completed", "partial", "failed"])
10211
+ )).orderBy(desc(runs.createdAt)).limit(FAILURE_STREAK_LOOKBACK).all();
10212
+ let streak = 0;
10213
+ for (const row of recent) {
10214
+ if (row.status === "completed") break;
10215
+ if (row.status === "failed" && row.error && classifyRunAbortReason(row.error)) continue;
10216
+ streak++;
10217
+ }
10218
+ return streak;
10219
+ } catch {
10220
+ return 0;
10221
+ }
10222
+ }
10011
10223
  hasPriorActivation(projectId, currentRunId) {
10012
10224
  return this.db.select({ id: querySnapshots.id }).from(querySnapshots).innerJoin(runs, eq3(querySnapshots.runId, runs.id)).where(and3(
10013
10225
  eq3(runs.projectId, projectId),
@@ -10094,12 +10306,12 @@ function maybeShowActivationNotice(io = {}) {
10094
10306
  }
10095
10307
 
10096
10308
  // src/gsc-sync.ts
10097
- import crypto8 from "crypto";
10309
+ import crypto9 from "crypto";
10098
10310
  import { eq as eq5, and as and5, sql as sql3 } from "drizzle-orm";
10099
10311
 
10100
10312
  // src/gsc-coverage-snapshot.ts
10101
- import crypto7 from "crypto";
10102
- import { and as and4, desc, eq as eq4, gte } from "drizzle-orm";
10313
+ import crypto8 from "crypto";
10314
+ import { and as and4, desc as desc2, eq as eq4, gte } from "drizzle-orm";
10103
10315
  var COVERAGE_IMPRESSION_WINDOW_DAYS = 30;
10104
10316
  function pageKey(raw) {
10105
10317
  if (!raw) return null;
@@ -10123,9 +10335,9 @@ function writeCoverageSnapshot(db, projectId, runId, opts = {}) {
10123
10335
  eq4(siteCrawlSnapshots.projectId, projectId),
10124
10336
  eq4(siteCrawlSnapshots.complete, true),
10125
10337
  eq4(runs.status, "completed")
10126
- )).orderBy(desc(siteCrawlSnapshots.createdAt)).limit(1).get();
10338
+ )).orderBy(desc2(siteCrawlSnapshots.createdAt)).limit(1).get();
10127
10339
  const hasCrawlHistory = Boolean(db.select({ id: siteCrawlSnapshots.id }).from(siteCrawlSnapshots).where(eq4(siteCrawlSnapshots.projectId, projectId)).limit(1).get());
10128
- const latestAudit = db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).where(eq4(siteAuditSnapshots.projectId, projectId)).orderBy(desc(siteAuditSnapshots.createdAt)).limit(1).get();
10340
+ const latestAudit = db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).where(eq4(siteAuditSnapshots.projectId, projectId)).orderBy(desc2(siteAuditSnapshots.createdAt)).limit(1).get();
10129
10341
  const sitemapUrls = latestCrawl?.attemptId ? db.select({ url: siteCrawlPages.url }).from(siteCrawlPages).where(and4(
10130
10342
  eq4(siteCrawlPages.projectId, projectId),
10131
10343
  eq4(siteCrawlPages.runId, latestCrawl.runId),
@@ -10160,7 +10372,7 @@ function writeCoverageSnapshot(db, projectId, runId, opts = {}) {
10160
10372
  const snapshotDate = now.toISOString().split("T")[0];
10161
10373
  db.delete(gscCoverageSnapshots).where(and4(eq4(gscCoverageSnapshots.projectId, projectId), eq4(gscCoverageSnapshots.date, snapshotDate))).run();
10162
10374
  db.insert(gscCoverageSnapshots).values({
10163
- id: crypto7.randomUUID(),
10375
+ id: crypto8.randomUUID(),
10164
10376
  projectId,
10165
10377
  syncRunId: runId,
10166
10378
  date: snapshotDate,
@@ -10239,7 +10451,7 @@ async function executeGscSync(db, runId, projectId, opts) {
10239
10451
  for (const row of batch) {
10240
10452
  const [query, page, country, device, date] = row.keys;
10241
10453
  db.insert(gscSearchData).values({
10242
- id: crypto8.randomUUID(),
10454
+ id: crypto9.randomUUID(),
10243
10455
  projectId,
10244
10456
  syncRunId: runId,
10245
10457
  date: date ?? "",
@@ -10271,7 +10483,7 @@ async function executeGscSync(db, runId, projectId, opts) {
10271
10483
  for (const row of totalRows) {
10272
10484
  const [date] = row.keys;
10273
10485
  db.insert(gscDailyTotals).values({
10274
- id: crypto8.randomUUID(),
10486
+ id: crypto9.randomUUID(),
10275
10487
  projectId,
10276
10488
  date: date ?? "",
10277
10489
  clicks: row.clicks,
@@ -10320,7 +10532,7 @@ async function executeGscSync(db, runId, projectId, opts) {
10320
10532
  const [date, query] = row.keys;
10321
10533
  if (!date || !query) continue;
10322
10534
  db.insert(gscQueryDailyTotals).values({
10323
- id: crypto8.randomUUID(),
10535
+ id: crypto9.randomUUID(),
10324
10536
  projectId,
10325
10537
  date,
10326
10538
  query,
@@ -10345,12 +10557,12 @@ async function executeGscSync(db, runId, projectId, opts) {
10345
10557
  }
10346
10558
 
10347
10559
  // src/gbp-sync.ts
10348
- import crypto10 from "crypto";
10349
- import { eq as eq7, and as and7, desc as desc3, inArray as inArray3, lt } from "drizzle-orm";
10560
+ import crypto11 from "crypto";
10561
+ import { eq as eq7, and as and7, desc as desc4, inArray as inArray3, lt as lt2 } from "drizzle-orm";
10350
10562
 
10351
10563
  // src/gbp-reviews.ts
10352
- import crypto9 from "crypto";
10353
- import { and as and6, desc as desc2, eq as eq6, inArray as inArray2, sql as sql4 } from "drizzle-orm";
10564
+ import crypto10 from "crypto";
10565
+ import { and as and6, desc as desc3, eq as eq6, inArray as inArray2, sql as sql4 } from "drizzle-orm";
10354
10566
  var log4 = createLogger("GbpReviews");
10355
10567
  var MS_PER_DAY = 864e5;
10356
10568
  var REVIEW_ALERT_MAX_AGE_DAYS = 30;
@@ -10382,7 +10594,7 @@ function placesReviewsDue(db, projectId, locationName, now) {
10382
10594
  eq6(gbpReviewRatings.projectId, projectId),
10383
10595
  eq6(gbpReviewRatings.locationName, locationName),
10384
10596
  eq6(gbpReviewRatings.origin, "places")
10385
- )).orderBy(desc2(gbpReviewRatings.observedAt)).limit(1).get();
10597
+ )).orderBy(desc3(gbpReviewRatings.observedAt)).limit(1).get();
10386
10598
  if (!latest) return true;
10387
10599
  return now.getTime() - Date.parse(latest.observedAt) >= PLACES_REVIEW_MIN_INTERVAL_HOURS * 36e5;
10388
10600
  }
@@ -10463,12 +10675,12 @@ function persistReviewObservation(tx, projectId, runId, obs, ctx) {
10463
10675
  eq6(gbpReviewRatings.projectId, projectId),
10464
10676
  eq6(gbpReviewRatings.locationName, obs.locationName),
10465
10677
  eq6(gbpReviewRatings.origin, obs.origin)
10466
- )).orderBy(desc2(gbpReviewRatings.observedAt)).limit(1).get();
10678
+ )).orderBy(desc3(gbpReviewRatings.observedAt)).limit(1).get();
10467
10679
  const baseline = latestRating === void 0;
10468
10680
  let queuedRatingDrop = false;
10469
10681
  if (!latestRating) {
10470
10682
  tx.insert(gbpReviewRatings).values({
10471
- id: crypto9.randomUUID(),
10683
+ id: crypto10.randomUUID(),
10472
10684
  projectId,
10473
10685
  locationName: obs.locationName,
10474
10686
  origin: obs.origin,
@@ -10488,7 +10700,7 @@ function persistReviewObservation(tx, projectId, runId, obs, ctx) {
10488
10700
  const alertState = ratingAlertState(obs.origin, latestRating.rating, obs.rating);
10489
10701
  queuedRatingDrop = alertState === "pending";
10490
10702
  tx.insert(gbpReviewRatings).values({
10491
- id: crypto9.randomUUID(),
10703
+ id: crypto10.randomUUID(),
10492
10704
  projectId,
10493
10705
  locationName: obs.locationName,
10494
10706
  origin: obs.origin,
@@ -10538,7 +10750,7 @@ function persistReviewObservation(tx, projectId, runId, obs, ctx) {
10538
10750
  const alertState = reviewAlertState(review, { baseline, now, negativeMaxStars });
10539
10751
  if (alertState === "pending") queuedReviews++;
10540
10752
  tx.insert(gbpReviews).values({
10541
- id: crypto9.randomUUID(),
10753
+ id: crypto10.randomUUID(),
10542
10754
  projectId,
10543
10755
  origin: obs.origin,
10544
10756
  reviewName: review.reviewName,
@@ -10669,15 +10881,15 @@ async function executeGbpSync(db, runId, projectId, opts) {
10669
10881
  fetchLocationReviews(db, projectId, loc, reviewContext)
10670
10882
  ]);
10671
10883
  const lodgingHash = lodging ? hashLodging(lodging) : null;
10672
- const latestLodging = lodging ? db.select().from(gbpLodgingSnapshots).where(and7(eq7(gbpLodgingSnapshots.projectId, projectId), eq7(gbpLodgingSnapshots.locationName, loc.locationName))).orderBy(desc3(gbpLodgingSnapshots.syncedAt)).limit(1).get() : void 0;
10884
+ const latestLodging = lodging ? db.select().from(gbpLodgingSnapshots).where(and7(eq7(gbpLodgingSnapshots.projectId, projectId), eq7(gbpLodgingSnapshots.locationName, loc.locationName))).orderBy(desc4(gbpLodgingSnapshots.syncedAt)).limit(1).get() : void 0;
10673
10885
  const lodgingChanged = lodging !== null && latestLodging?.contentHash !== lodgingHash;
10674
10886
  const attributesHash = hashAttributes(attributes);
10675
- const latestAttributes = db.select().from(gbpAttributesSnapshots).where(and7(eq7(gbpAttributesSnapshots.projectId, projectId), eq7(gbpAttributesSnapshots.locationName, loc.locationName))).orderBy(desc3(gbpAttributesSnapshots.syncedAt)).limit(1).get();
10887
+ const latestAttributes = db.select().from(gbpAttributesSnapshots).where(and7(eq7(gbpAttributesSnapshots.projectId, projectId), eq7(gbpAttributesSnapshots.locationName, loc.locationName))).orderBy(desc4(gbpAttributesSnapshots.syncedAt)).limit(1).get();
10676
10888
  const attributesChanged = latestAttributes?.contentHash !== attributesHash;
10677
10889
  let placeToWrite = null;
10678
10890
  let placeToTouch = null;
10679
10891
  if (placesTier !== "off" && placesApiKey && lodging !== null && loc.placeId) {
10680
- const latestPlace = db.select().from(gbpPlaceDetails).where(and7(eq7(gbpPlaceDetails.projectId, projectId), eq7(gbpPlaceDetails.locationName, loc.locationName))).orderBy(desc3(gbpPlaceDetails.syncedAt)).limit(1).get();
10892
+ const latestPlace = db.select().from(gbpPlaceDetails).where(and7(eq7(gbpPlaceDetails.projectId, projectId), eq7(gbpPlaceDetails.locationName, loc.locationName))).orderBy(desc4(gbpPlaceDetails.syncedAt)).limit(1).get();
10681
10893
  const ageDays = latestPlace ? (Date.now() - new Date(latestPlace.syncedAt).getTime()) / MS_PER_DAY2 : Infinity;
10682
10894
  if (ageDays >= placesRefreshDays) {
10683
10895
  try {
@@ -10699,7 +10911,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
10699
10911
  tx.delete(gbpDailyMetrics).where(and7(eq7(gbpDailyMetrics.projectId, projectId), eq7(gbpDailyMetrics.locationName, loc.locationName))).run();
10700
10912
  for (const row of metricRows) {
10701
10913
  tx.insert(gbpDailyMetrics).values({
10702
- id: crypto10.randomUUID(),
10914
+ id: crypto11.randomUUID(),
10703
10915
  projectId,
10704
10916
  locationName: loc.locationName,
10705
10917
  date: row.date,
@@ -10711,7 +10923,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
10711
10923
  tx.delete(gbpKeywordImpressions).where(and7(eq7(gbpKeywordImpressions.projectId, projectId), eq7(gbpKeywordImpressions.locationName, loc.locationName))).run();
10712
10924
  for (const row of keywordRows) {
10713
10925
  tx.insert(gbpKeywordImpressions).values({
10714
- id: crypto10.randomUUID(),
10926
+ id: crypto11.randomUUID(),
10715
10927
  projectId,
10716
10928
  locationName: loc.locationName,
10717
10929
  periodStart: monthKey(keywordsStart),
@@ -10732,7 +10944,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
10732
10944
  for (const { month, rows } of monthlyKeywordResults) {
10733
10945
  for (const row of rows) {
10734
10946
  tx.insert(gbpKeywordMonthly).values({
10735
- id: crypto10.randomUUID(),
10947
+ id: crypto11.randomUUID(),
10736
10948
  projectId,
10737
10949
  locationName: loc.locationName,
10738
10950
  month,
@@ -10747,13 +10959,13 @@ async function executeGbpSync(db, runId, projectId, opts) {
10747
10959
  tx.delete(gbpKeywordMonthly).where(and7(
10748
10960
  eq7(gbpKeywordMonthly.projectId, projectId),
10749
10961
  eq7(gbpKeywordMonthly.locationName, loc.locationName),
10750
- lt(gbpKeywordMonthly.month, keywordRetentionCutoff)
10962
+ lt2(gbpKeywordMonthly.month, keywordRetentionCutoff)
10751
10963
  )).run();
10752
10964
  }
10753
10965
  tx.delete(gbpPlaceActions).where(and7(eq7(gbpPlaceActions.projectId, projectId), eq7(gbpPlaceActions.locationName, loc.locationName))).run();
10754
10966
  for (const row of placeActionRows) {
10755
10967
  tx.insert(gbpPlaceActions).values({
10756
- id: crypto10.randomUUID(),
10968
+ id: crypto11.randomUUID(),
10757
10969
  projectId,
10758
10970
  locationName: loc.locationName,
10759
10971
  placeActionLinkName: row.placeActionLinkName,
@@ -10766,7 +10978,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
10766
10978
  }
10767
10979
  if (lodging !== null && lodgingChanged) {
10768
10980
  tx.insert(gbpLodgingSnapshots).values({
10769
- id: crypto10.randomUUID(),
10981
+ id: crypto11.randomUUID(),
10770
10982
  projectId,
10771
10983
  locationName: loc.locationName,
10772
10984
  contentHash: lodgingHash,
@@ -10780,7 +10992,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
10780
10992
  }
10781
10993
  if (attributesChanged) {
10782
10994
  tx.insert(gbpAttributesSnapshots).values({
10783
- id: crypto10.randomUUID(),
10995
+ id: crypto11.randomUUID(),
10784
10996
  projectId,
10785
10997
  locationName: loc.locationName,
10786
10998
  contentHash: attributesHash,
@@ -10794,7 +11006,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
10794
11006
  }
10795
11007
  if (placeToWrite) {
10796
11008
  tx.insert(gbpPlaceDetails).values({
10797
- id: crypto10.randomUUID(),
11009
+ id: crypto11.randomUUID(),
10798
11010
  projectId,
10799
11011
  locationName: loc.locationName,
10800
11012
  placeId: placeToWrite.placeId,
@@ -10899,7 +11111,7 @@ async function refreshSelectedLocationProfiles(db, projectId, accessToken, locat
10899
11111
  }
10900
11112
 
10901
11113
  // src/ads-sync.ts
10902
- import crypto11 from "crypto";
11114
+ import crypto12 from "crypto";
10903
11115
  import { and as and8, eq as eq8, inArray as inArray4, notInArray } from "drizzle-orm";
10904
11116
 
10905
11117
  // ../integration-openai-ads/src/constants.ts
@@ -11674,7 +11886,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
11674
11886
  }
11675
11887
  for (const upsert of insightUpserts) {
11676
11888
  tx.insert(adsInsightsDaily).values({
11677
- id: crypto11.randomUUID(),
11889
+ id: crypto12.randomUUID(),
11678
11890
  projectId,
11679
11891
  level: upsert.level,
11680
11892
  entityId: upsert.entityId,
@@ -11742,7 +11954,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
11742
11954
  }
11743
11955
 
11744
11956
  // src/gsc-inspect-sitemap.ts
11745
- import crypto13 from "crypto";
11957
+ import crypto14 from "crypto";
11746
11958
  import { eq as eq9 } from "drizzle-orm";
11747
11959
 
11748
11960
  // src/sitemap-parser.ts
@@ -11842,7 +12054,7 @@ async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
11842
12054
  }
11843
12055
 
11844
12056
  // src/inspect-rate-gate.ts
11845
- import crypto12 from "crypto";
12057
+ import crypto13 from "crypto";
11846
12058
  var gates = /* @__PURE__ */ new Map();
11847
12059
  function sharedRateGate(key) {
11848
12060
  let entry = gates.get(key);
@@ -11861,7 +12073,7 @@ function sharedRateGate(key) {
11861
12073
  };
11862
12074
  }
11863
12075
  function credentialGateKey(prefix, secret) {
11864
- const digest = crypto12.createHash("sha256").update(secret).digest("hex").slice(0, 12);
12076
+ const digest = crypto13.createHash("sha256").update(secret).digest("hex").slice(0, 12);
11865
12077
  return `${prefix}:${digest}`;
11866
12078
  }
11867
12079
 
@@ -12016,7 +12228,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
12016
12228
  const rich = ir.richResultsResult;
12017
12229
  const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
12018
12230
  db.insert(gscUrlInspections).values({
12019
- id: crypto13.randomUUID(),
12231
+ id: crypto14.randomUUID(),
12020
12232
  projectId,
12021
12233
  syncRunId: runId,
12022
12234
  url: pageUrl,
@@ -12074,8 +12286,8 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
12074
12286
  }
12075
12287
 
12076
12288
  // src/bing-inspect-sitemap.ts
12077
- import crypto14 from "crypto";
12078
- import { eq as eq10, desc as desc4 } from "drizzle-orm";
12289
+ import crypto15 from "crypto";
12290
+ import { eq as eq10, desc as desc5 } from "drizzle-orm";
12079
12291
  var log9 = createLogger("BingInspectSitemap");
12080
12292
  var BING_INSPECT_SPACING_MS = 2e3;
12081
12293
  function parseBingDate(value) {
@@ -12179,7 +12391,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
12179
12391
  derivedInIndex = false;
12180
12392
  }
12181
12393
  db.insert(bingUrlInspections).values({
12182
- id: crypto14.randomUUID(),
12394
+ id: crypto15.randomUUID(),
12183
12395
  projectId,
12184
12396
  url: pageUrl,
12185
12397
  httpCode,
@@ -12239,7 +12451,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
12239
12451
  `Bing inspection failed for every URL attempted (${errors} of ${targetUrls.length}); coverage was left unchanged rather than re-dated from stored inspections. Last error: ${detail}`
12240
12452
  );
12241
12453
  }
12242
- const allInspections = db.select().from(bingUrlInspections).where(eq10(bingUrlInspections.projectId, projectId)).orderBy(desc4(bingUrlInspections.inspectedAt)).all();
12454
+ const allInspections = db.select().from(bingUrlInspections).where(eq10(bingUrlInspections.projectId, projectId)).orderBy(desc5(bingUrlInspections.inspectedAt)).all();
12243
12455
  const latestByUrl = /* @__PURE__ */ new Map();
12244
12456
  const definitiveByUrl = /* @__PURE__ */ new Map();
12245
12457
  for (const row of allInspections) {
@@ -12263,7 +12475,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
12263
12475
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
12264
12476
  const snapNow = (/* @__PURE__ */ new Date()).toISOString();
12265
12477
  db.insert(bingCoverageSnapshots).values({
12266
- id: crypto14.randomUUID(),
12478
+ id: crypto15.randomUUID(),
12267
12479
  projectId,
12268
12480
  syncRunId: runId,
12269
12481
  date: snapshotDate,
@@ -12304,8 +12516,8 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
12304
12516
  }
12305
12517
 
12306
12518
  // src/coverage-refresh.ts
12307
- import crypto15 from "crypto";
12308
- import { and as and9, desc as desc5, eq as eq11, inArray as inArray5 } from "drizzle-orm";
12519
+ import crypto16 from "crypto";
12520
+ import { and as and9, desc as desc6, eq as eq11, inArray as inArray5 } from "drizzle-orm";
12309
12521
  var log10 = createLogger("CoverageRefresh");
12310
12522
  var COVERAGE_REFRESH_MIN_INTERVAL_MS = 60 * 60 * 1e3;
12311
12523
  var COVERAGE_REFRESH_MANUAL_MIN_INTERVAL_MS = 2 * 60 * 1e3;
@@ -12334,7 +12546,7 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
12334
12546
  eq11(runs.kind, RunKinds["inspect-sitemap"]),
12335
12547
  inArray5(runs.status, IN_FLIGHT_STATUSES)
12336
12548
  )
12337
- ).orderBy(desc5(runs.createdAt)).limit(1).get();
12549
+ ).orderBy(desc6(runs.createdAt)).limit(1).get();
12338
12550
  if (inFlight2) {
12339
12551
  log10.info("skip.in-flight", { projectId });
12340
12552
  return null;
@@ -12346,7 +12558,7 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
12346
12558
  eq11(runs.kind, RunKinds["inspect-sitemap"]),
12347
12559
  inArray5(runs.status, ACTIVE_OR_DONE_STATUSES)
12348
12560
  )
12349
- ).orderBy(desc5(runs.createdAt)).limit(1).get();
12561
+ ).orderBy(desc6(runs.createdAt)).limit(1).get();
12350
12562
  if (recent) {
12351
12563
  const ageMs = nowMs - Date.parse(recent.createdAt);
12352
12564
  if (Number.isFinite(ageMs) && ageMs < windowMs) {
@@ -12354,7 +12566,7 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
12354
12566
  return null;
12355
12567
  }
12356
12568
  }
12357
- const runId = crypto15.randomUUID();
12569
+ const runId = crypto16.randomUUID();
12358
12570
  db.insert(runs).values({
12359
12571
  id: runId,
12360
12572
  projectId,
@@ -12377,7 +12589,7 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
12377
12589
  }
12378
12590
 
12379
12591
  // src/commoncrawl-sync.ts
12380
- import crypto16 from "crypto";
12592
+ import crypto17 from "crypto";
12381
12593
  import path4 from "path";
12382
12594
  import { and as and10, eq as eq12, sql as sql5 } from "drizzle-orm";
12383
12595
  var log11 = createLogger("CommonCrawlSync");
@@ -12452,7 +12664,7 @@ async function executeReleaseSync(db, syncId, opts) {
12452
12664
  if (!projectIds) continue;
12453
12665
  for (const projectId of projectIds) {
12454
12666
  expanded.push({
12455
- id: crypto16.randomUUID(),
12667
+ id: crypto17.randomUUID(),
12456
12668
  projectId,
12457
12669
  releaseSyncId: syncId,
12458
12670
  release,
@@ -12472,7 +12684,7 @@ async function executeReleaseSync(db, syncId, opts) {
12472
12684
  const projectRows = rowsByProject.get(p.id) ?? [];
12473
12685
  const summary = computeSummary(projectRows);
12474
12686
  tx.insert(backlinkSummaries).values({
12475
- id: crypto16.randomUUID(),
12687
+ id: crypto17.randomUUID(),
12476
12688
  projectId: p.id,
12477
12689
  releaseSyncId: syncId,
12478
12690
  source: BacklinkSources.commoncrawl,
@@ -12569,9 +12781,9 @@ function computeSummary(rows) {
12569
12781
  }
12570
12782
 
12571
12783
  // src/backlink-extract.ts
12572
- import crypto17 from "crypto";
12784
+ import crypto18 from "crypto";
12573
12785
  import fs5 from "fs";
12574
- import { and as and11, desc as desc6, eq as eq13 } from "drizzle-orm";
12786
+ import { and as and11, desc as desc7, eq as eq13 } from "drizzle-orm";
12575
12787
  var log12 = createLogger("BacklinkExtract");
12576
12788
  function defaultDeps3() {
12577
12789
  return {
@@ -12589,7 +12801,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
12589
12801
  if (!project) {
12590
12802
  throw new Error(`Project not found: ${projectId}`);
12591
12803
  }
12592
- const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc6(ccReleaseSyncs.createdAt)).limit(1).get();
12804
+ const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc7(ccReleaseSyncs.createdAt)).limit(1).get();
12593
12805
  if (!sync) {
12594
12806
  throw new Error("No ready release sync available \u2014 run `canonry backlinks sync` first");
12595
12807
  }
@@ -12625,7 +12837,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
12625
12837
  ).run();
12626
12838
  if (rows.length > 0) {
12627
12839
  const values = rows.map((r) => ({
12628
- id: crypto17.randomUUID(),
12840
+ id: crypto18.randomUUID(),
12629
12841
  projectId,
12630
12842
  releaseSyncId: syncId,
12631
12843
  source: BacklinkSources.commoncrawl,
@@ -12639,7 +12851,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
12639
12851
  }
12640
12852
  const summary = computeSummary2(rows);
12641
12853
  tx.insert(backlinkSummaries).values({
12642
- id: crypto17.randomUUID(),
12854
+ id: crypto18.randomUUID(),
12643
12855
  projectId,
12644
12856
  releaseSyncId: syncId,
12645
12857
  source: BacklinkSources.commoncrawl,
@@ -12682,7 +12894,7 @@ function computeSummary2(rows) {
12682
12894
  }
12683
12895
 
12684
12896
  // src/discovery-run.ts
12685
- import crypto18 from "crypto";
12897
+ import crypto19 from "crypto";
12686
12898
  import { and as and12, eq as eq14 } from "drizzle-orm";
12687
12899
  var log13 = createLogger("DiscoveryRun");
12688
12900
  var DEFAULT_SEED_COUNT = 30;
@@ -13037,7 +13249,7 @@ function writeDiscoveryInsight(db, input) {
13037
13249
  eq14(insights.dismissed, false)
13038
13250
  )).run();
13039
13251
  tx.insert(insights).values({
13040
- id: crypto18.randomUUID(),
13252
+ id: crypto19.randomUUID(),
13041
13253
  projectId: input.projectId,
13042
13254
  runId: input.runId,
13043
13255
  type: "discovery.basket-divergence",
@@ -13073,7 +13285,7 @@ function buildDiscoveryInsightTitle(input) {
13073
13285
  }
13074
13286
 
13075
13287
  // src/execute-site-audit.ts
13076
- import crypto19 from "crypto";
13288
+ import crypto20 from "crypto";
13077
13289
  import { and as and14, eq as eq16, ne as ne2, sql as sql6 } from "drizzle-orm";
13078
13290
  import { runSiteCrawl } from "@canonry/aeo-audit";
13079
13291
 
@@ -13493,10 +13705,13 @@ function deadLinkCheckedCount(edges, pages) {
13493
13705
  }
13494
13706
  function trackSiteAuditOutcome(db, input) {
13495
13707
  try {
13496
- const errorCode = input.status === "cancelled" ? "RUN_CANCELLED" : input.status === "failed" ? "UNKNOWN" : void 0;
13708
+ const errorCode = input.status === "cancelled" ? "RUN_CANCELLED" : input.status === "failed" ? "INTERNAL" : void 0;
13497
13709
  trackEvent(
13498
13710
  "site_audit.completed",
13499
- buildSiteAuditCompletedProps(input),
13711
+ {
13712
+ ...buildSiteAuditCompletedProps(input),
13713
+ ...input.status === "failed" ? describeRunFailure(input.error) : {}
13714
+ },
13500
13715
  errorCode ? { errorCode } : void 0
13501
13716
  );
13502
13717
  const pagesAudited = input.crawl?.pagesAudited ?? 0;
@@ -13521,7 +13736,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13521
13736
  const trigger = db.select({ trigger: runs.trigger }).from(runs).where(eq16(runs.id, runId)).get()?.trigger ?? null;
13522
13737
  let canonicalDomain = null;
13523
13738
  const attemptNumber = (db.select({ value: sql6`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq16(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
13524
- const attemptId = crypto19.randomUUID();
13739
+ const attemptId = crypto20.randomUUID();
13525
13740
  db.insert(siteCrawlAttempts).values({
13526
13741
  id: attemptId,
13527
13742
  projectId,
@@ -13599,7 +13814,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13599
13814
  nodeKey: page.key
13600
13815
  });
13601
13816
  tx.insert(siteCrawlPages).values({
13602
- id: crypto19.randomUUID(),
13817
+ id: crypto20.randomUUID(),
13603
13818
  projectId,
13604
13819
  runId,
13605
13820
  attemptId,
@@ -13692,7 +13907,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13692
13907
  if (isSelfLink(edge.from, edge.to)) return;
13693
13908
  observedEdges.set(edge.key, edge);
13694
13909
  tx.insert(siteCrawlEdges).values({
13695
- id: crypto19.randomUUID(),
13910
+ id: crypto20.randomUUID(),
13696
13911
  projectId,
13697
13912
  runId,
13698
13913
  attemptId,
@@ -13762,7 +13977,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13762
13977
  return;
13763
13978
  }
13764
13979
  tx.insert(siteCrawlEventReceipts).values({
13765
- id: crypto19.randomUUID(),
13980
+ id: crypto20.randomUUID(),
13766
13981
  projectId,
13767
13982
  runId,
13768
13983
  attemptId,
@@ -13898,7 +14113,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13898
14113
  if (claim2.changes === 0) return false;
13899
14114
  if (hasAuditedPages) {
13900
14115
  tx.insert(siteAuditSnapshots).values({
13901
- id: crypto19.randomUUID(),
14116
+ id: crypto20.randomUUID(),
13902
14117
  projectId,
13903
14118
  runId,
13904
14119
  sitemapUrl: opts.sitemapUrl ?? new URL("/sitemap.xml", crawlRootUrl).href,
@@ -13916,7 +14131,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13916
14131
  for (const page of observedPages.values()) {
13917
14132
  if (!isLegacyScorecardPage(page)) continue;
13918
14133
  tx.insert(siteAuditPages).values({
13919
- id: crypto19.randomUUID(),
14134
+ id: crypto20.randomUUID(),
13920
14135
  projectId,
13921
14136
  runId,
13922
14137
  url: page.finalUrl ?? page.requestedUrl,
@@ -13929,7 +14144,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13929
14144
  }
13930
14145
  }
13931
14146
  tx.insert(siteCrawlSnapshots).values({
13932
- id: crypto19.randomUUID(),
14147
+ id: crypto20.randomUUID(),
13933
14148
  projectId,
13934
14149
  runId,
13935
14150
  attemptId,
@@ -13975,7 +14190,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13975
14190
  if (opts.checkDeadLinks) {
13976
14191
  for (const finding of deadLinkFindings) {
13977
14192
  tx.insert(siteCrawlFindings).values({
13978
- id: crypto19.randomUUID(),
14193
+ id: crypto20.randomUUID(),
13979
14194
  projectId,
13980
14195
  runId,
13981
14196
  attemptId,
@@ -14063,7 +14278,8 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
14063
14278
  status: cancelled ? "cancelled" : "failed",
14064
14279
  startTime,
14065
14280
  trigger,
14066
- canonicalDomain
14281
+ canonicalDomain,
14282
+ error
14067
14283
  });
14068
14284
  throw error;
14069
14285
  }
@@ -14071,7 +14287,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
14071
14287
 
14072
14288
  // src/commands/backfill.ts
14073
14289
  import { setImmediate as yieldToEventLoop2 } from "timers/promises";
14074
- import { and as and15, asc as asc4, desc as desc7, eq as eq17, gt as gt3, inArray as inArray7, isNull as isNull2, lte as lte2, sql as sql7 } from "drizzle-orm";
14290
+ import { and as and15, asc as asc4, desc as desc8, eq as eq17, gt as gt3, inArray as inArray7, isNull as isNull2, lte as lte2, sql as sql7 } from "drizzle-orm";
14075
14291
  var SNAPSHOT_BATCH_SIZE = 500;
14076
14292
  var PATH_REPAIR_PAGE_SIZE = 128;
14077
14293
  async function backfillAnswerVisibilityCommand(opts) {
@@ -14259,7 +14475,7 @@ function backfillNormalizedPaths(db, opts) {
14259
14475
  async function repairStoredPaths(db, table, opts) {
14260
14476
  const pageSize = opts?.pageSize ?? PATH_REPAIR_PAGE_SIZE;
14261
14477
  const projectScope = opts?.projectId ? sql7`+${table.projectId} = ${opts.projectId}` : void 0;
14262
- const lastId = db.select({ id: table.id }).from(table).where(projectScope).orderBy(desc7(table.id)).limit(1).get()?.id;
14478
+ const lastId = db.select({ id: table.id }).from(table).where(projectScope).orderBy(desc8(table.id)).limit(1).get()?.id;
14263
14479
  if (lastId === void 0) return { examined: 0, updated: 0, unchanged: 0 };
14264
14480
  let cursor;
14265
14481
  let examined = 0;
@@ -14549,7 +14765,7 @@ function readStoredGroundingSources(rawResponse) {
14549
14765
  return result2;
14550
14766
  }
14551
14767
  async function backfillInsightsCommand(project, opts) {
14552
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-JYGP6LME.js");
14768
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-5J36RBEM.js");
14553
14769
  const config = loadConfig();
14554
14770
  const db = createClient(config.database);
14555
14771
  migrate(db);
@@ -15139,7 +15355,7 @@ var ProviderRegistry = class {
15139
15355
  import { createHash as createHash4 } from "crypto";
15140
15356
  var TTL_MS = 60 * 60 * 1e3;
15141
15357
  var RETRY_MS = 60 * 1e3;
15142
- var TIMEOUT_MS2 = 3e3;
15358
+ var TIMEOUT_MS3 = 3e3;
15143
15359
  function createProviderModelCatalog(registry) {
15144
15360
  const cache = /* @__PURE__ */ new Map();
15145
15361
  const read = async (name) => {
@@ -15165,7 +15381,7 @@ function createProviderModelCatalog(registry) {
15165
15381
  timer = setTimeout(() => {
15166
15382
  controller.abort();
15167
15383
  reject(new Error("Model discovery timed out"));
15168
- }, TIMEOUT_MS2);
15384
+ }, TIMEOUT_MS3);
15169
15385
  });
15170
15386
  current.pending = Promise.race([
15171
15387
  Promise.resolve().then(() => provider.adapter.listModels(provider.config, controller.signal)),
@@ -15196,14 +15412,14 @@ function createProviderModelCatalog(registry) {
15196
15412
  }
15197
15413
 
15198
15414
  // src/scheduler.ts
15199
- import crypto20 from "crypto";
15415
+ import crypto21 from "crypto";
15200
15416
  import cron from "node-cron";
15201
15417
  import { and as and16, eq as eq18, inArray as inArray8, notExists, sql as sql8 } from "drizzle-orm";
15202
15418
  var log15 = createLogger("Scheduler");
15203
15419
  var DEFAULT_HEALTH_CRON = "0 */6 * * *";
15204
15420
  function ensureDefaultHealthSchedule(db, projectId, now = (/* @__PURE__ */ new Date()).toISOString()) {
15205
15421
  const result2 = db.insert(schedules).values({
15206
- id: crypto20.randomUUID(),
15422
+ id: crypto21.randomUUID(),
15207
15423
  projectId,
15208
15424
  kind: SchedulableRunKinds.doctor,
15209
15425
  cronExpr: DEFAULT_HEALTH_CRON,
@@ -15521,7 +15737,7 @@ var Scheduler = class {
15521
15737
  log15.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
15522
15738
  return;
15523
15739
  }
15524
- const runId2 = crypto20.randomUUID();
15740
+ const runId2 = crypto21.randomUUID();
15525
15741
  this.db.insert(runs).values({
15526
15742
  id: runId2,
15527
15743
  projectId,
@@ -15553,7 +15769,7 @@ var Scheduler = class {
15553
15769
  this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
15554
15770
  return;
15555
15771
  }
15556
- const runId2 = crypto20.randomUUID();
15772
+ const runId2 = crypto21.randomUUID();
15557
15773
  this.db.insert(runs).values({
15558
15774
  id: runId2,
15559
15775
  projectId,
@@ -15624,7 +15840,7 @@ var Scheduler = class {
15624
15840
  this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
15625
15841
  return;
15626
15842
  }
15627
- const runId2 = crypto20.randomUUID();
15843
+ const runId2 = crypto21.randomUUID();
15628
15844
  const effectiveRequest = normalizeSiteAuditRunRequest({ maxPages: SITE_AUDIT_MAX_PAGE_LIMIT });
15629
15845
  this.db.transaction((tx) => {
15630
15846
  tx.insert(runs).values({
@@ -15966,8 +16182,8 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
15966
16182
  }
15967
16183
 
15968
16184
  // src/notifier.ts
15969
- import { eq as eq20, desc as desc8, and as and18, inArray as inArray10, or } from "drizzle-orm";
15970
- import crypto21 from "crypto";
16185
+ import { eq as eq20, desc as desc9, and as and18, inArray as inArray10, or } from "drizzle-orm";
16186
+ import crypto22 from "crypto";
15971
16187
  var log19 = createLogger("Notifier");
15972
16188
  var MATERIAL_MAGNITUDE_DELTA = 20;
15973
16189
  var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
@@ -16507,7 +16723,7 @@ var Notifier = class {
16507
16723
  eq20(runs.kind, thisRun.kind),
16508
16724
  or(eq20(runs.status, "completed"), eq20(runs.status, "partial"))
16509
16725
  )
16510
- ).orderBy(desc8(runs.createdAt), desc8(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16726
+ ).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16511
16727
  const groups = groupRunsByCreatedAt(recentRuns);
16512
16728
  const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
16513
16729
  if (currentGroupIdx < 0) return [];
@@ -16594,7 +16810,7 @@ var Notifier = class {
16594
16810
  }
16595
16811
  logDelivery(projectId, notificationId, event, status, error) {
16596
16812
  this.db.insert(auditLog).values({
16597
- id: crypto21.randomUUID(),
16813
+ id: crypto22.randomUUID(),
16598
16814
  projectId,
16599
16815
  actor: "scheduler",
16600
16816
  action: `notification.${status}`,
@@ -16770,7 +16986,7 @@ function analysisResultFromInsights(insights2) {
16770
16986
  }
16771
16987
 
16772
16988
  // src/agent/session-registry.ts
16773
- import crypto25 from "crypto";
16989
+ import crypto26 from "crypto";
16774
16990
  import { eq as eq24 } from "drizzle-orm";
16775
16991
 
16776
16992
  // src/agent/session.ts
@@ -16828,12 +17044,17 @@ var PROVIDER_MODELS = {
16828
17044
  [LlmCapabilities.classify]: "gpt-5-nano"
16829
17045
  },
16830
17046
  [AgentProviderIds.gemini]: {
16831
- // Gemini's 2.5-flash is already cheap + capable; flash-lite is the
17047
+ // Gemini's Flash is already cheap + capable; Flash-Lite is the
16832
17048
  // dedicated micro tier for classify. Analyze stays on flash because
16833
17049
  // flash-lite drops too much quality for structured synthesis.
16834
- [LlmCapabilities.agent]: "gemini-2.5-flash",
16835
- [LlmCapabilities.analyze]: "gemini-2.5-flash",
16836
- [LlmCapabilities.classify]: "gemini-2.5-flash-lite"
17050
+ // Exact IDs, never Google's `-latest` aliases: pi-ai derives request
17051
+ // options (thinking level) from the catalog entry for the ID it is given,
17052
+ // and an alias entry can lag the model it serves. `gemini-flash-latest`
17053
+ // allows MINIMAL thinking while the 3.8 Flash it resolves to rejects it,
17054
+ // so every Aero turn failed. Sweeps can use the alias; Aero cannot.
17055
+ [LlmCapabilities.agent]: "gemini-3.8-flash",
17056
+ [LlmCapabilities.analyze]: "gemini-3.8-flash",
17057
+ [LlmCapabilities.classify]: "gemini-3.5-flash-lite"
16837
17058
  },
16838
17059
  [AgentProviderIds.zai]: {
16839
17060
  // GLM lineage: 5.2 replaced 5.1, which pi-ai 0.87 dropped from its
@@ -18018,7 +18239,7 @@ function buildAeroStateTools(ctx, opts = {}) {
18018
18239
  }
18019
18240
 
18020
18241
  // src/agent/llm-usage.ts
18021
- import crypto22 from "crypto";
18242
+ import crypto23 from "crypto";
18022
18243
  var AeroLlmUsageFeatures = {
18023
18244
  turn: "aero.turn"
18024
18245
  };
@@ -18037,7 +18258,7 @@ function recordLlmUsageEvent(args) {
18037
18258
  const usage = args.message.usage;
18038
18259
  const now = (/* @__PURE__ */ new Date()).toISOString();
18039
18260
  args.db.insert(llmUsageEvents).values({
18040
- id: crypto22.randomUUID(),
18261
+ id: crypto23.randomUUID(),
18041
18262
  projectId: args.projectId,
18042
18263
  runId: args.runId,
18043
18264
  agentSessionId: args.agentSessionId,
@@ -18407,7 +18628,7 @@ This context applies to this turn. Explicit user scope takes precedence; do not
18407
18628
  }
18408
18629
 
18409
18630
  // src/agent/tool-usage.ts
18410
- import crypto23 from "crypto";
18631
+ import crypto24 from "crypto";
18411
18632
  var AeroToolEventStatuses = {
18412
18633
  success: "success",
18413
18634
  error: "error"
@@ -18470,7 +18691,7 @@ function createAeroToolUsageHooks(args) {
18470
18691
  function recordAgentToolEvent(args) {
18471
18692
  try {
18472
18693
  args.db.insert(agentToolEvents).values({
18473
- id: crypto23.randomUUID(),
18694
+ id: crypto24.randomUUID(),
18474
18695
  projectId: args.projectId,
18475
18696
  agentSessionId: args.agentSessionId,
18476
18697
  toolCallId: args.toolCallId,
@@ -18725,8 +18946,8 @@ async function loadExternalMcpTools(servers, opts = {}) {
18725
18946
  }
18726
18947
 
18727
18948
  // src/agent/memory-store.ts
18728
- import crypto24 from "crypto";
18729
- import { and as and19, desc as desc9, eq as eq22, like, sql as sql10, or as or2 } from "drizzle-orm";
18949
+ import crypto25 from "crypto";
18950
+ import { and as and19, desc as desc10, eq as eq22, like, sql as sql10, or as or2 } from "drizzle-orm";
18730
18951
  var COMPACTION_KEY_PREFIX = "compaction:";
18731
18952
  var COMPACTION_NOTES_PER_SESSION = 3;
18732
18953
  function rowToDto(row) {
@@ -18740,7 +18961,7 @@ function rowToDto(row) {
18740
18961
  };
18741
18962
  }
18742
18963
  function listMemoryEntries(db, projectId, opts = {}) {
18743
- const query = db.select().from(agentMemory).where(eq22(agentMemory.projectId, projectId)).orderBy(desc9(agentMemory.updatedAt));
18964
+ const query = db.select().from(agentMemory).where(eq22(agentMemory.projectId, projectId)).orderBy(desc10(agentMemory.updatedAt));
18744
18965
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
18745
18966
  return rows.map(rowToDto);
18746
18967
  }
@@ -18754,7 +18975,7 @@ function upsertMemoryEntry(db, args) {
18754
18975
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
18755
18976
  }
18756
18977
  const now = (/* @__PURE__ */ new Date()).toISOString();
18757
- const id = crypto24.randomUUID();
18978
+ const id = crypto25.randomUUID();
18758
18979
  db.insert(agentMemory).values({
18759
18980
  id,
18760
18981
  projectId: args.projectId,
@@ -18786,7 +19007,7 @@ function loadRecentForHydrate(db, projectId, limit, sessionId) {
18786
19007
  return db.select().from(agentMemory).where(and19(
18787
19008
  eq22(agentMemory.projectId, projectId),
18788
19009
  or2(sql10`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql10`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
18789
- )).orderBy(desc9(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
19010
+ )).orderBy(desc10(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
18790
19011
  }
18791
19012
  function writeCompactionNote(db, args) {
18792
19013
  if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
@@ -18796,7 +19017,7 @@ function writeCompactionNote(db, args) {
18796
19017
  }
18797
19018
  const now = (/* @__PURE__ */ new Date()).toISOString();
18798
19019
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
18799
- const id = crypto24.randomUUID();
19020
+ const id = crypto25.randomUUID();
18800
19021
  let inserted;
18801
19022
  db.transaction((tx) => {
18802
19023
  tx.insert(agentMemory).values({
@@ -18814,7 +19035,7 @@ function writeCompactionNote(db, args) {
18814
19035
  eq22(agentMemory.projectId, args.projectId),
18815
19036
  like(agentMemory.key, `${sessionPrefix}%`)
18816
19037
  )
18817
- ).orderBy(desc9(agentMemory.updatedAt)).all();
19038
+ ).orderBy(desc10(agentMemory.updatedAt)).all();
18818
19039
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
18819
19040
  if (stale.length > 0) {
18820
19041
  tx.delete(agentMemory).where(sql10`${agentMemory.id} IN (${sql10.join(stale.map((s) => sql10`${s}`), sql10`, `)})`).run();
@@ -19168,7 +19389,7 @@ var SessionRegistry = class {
19168
19389
  return agent2;
19169
19390
  }
19170
19391
  const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
19171
- const sessionId = crypto25.randomUUID();
19392
+ const sessionId = crypto26.randomUUID();
19172
19393
  const agent = createAeroSession({
19173
19394
  projectName,
19174
19395
  client: this.opts.client,
@@ -19620,7 +19841,7 @@ ${lines.join("\n")}
19620
19841
  insertRow(params) {
19621
19842
  const now = (/* @__PURE__ */ new Date()).toISOString();
19622
19843
  this.opts.db.insert(agentSessions).values({
19623
- id: params.id ?? crypto25.randomUUID(),
19844
+ id: params.id ?? crypto26.randomUUID(),
19624
19845
  projectId: params.projectId,
19625
19846
  systemPrompt: params.systemPrompt,
19626
19847
  modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
@@ -19647,7 +19868,7 @@ ${lines.join("\n")}
19647
19868
  };
19648
19869
 
19649
19870
  // src/agent/viewer-sessions.ts
19650
- import crypto26 from "crypto";
19871
+ import crypto27 from "crypto";
19651
19872
  import { and as and21, eq as eq25, isNotNull as isNotNull3, like as like2 } from "drizzle-orm";
19652
19873
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
19653
19874
  CanonryMcpToolNames.canonry_ads_account,
@@ -19701,8 +19922,8 @@ function trimViewerTranscript(messages, max = MAX_VIEWER_MESSAGES) {
19701
19922
  }
19702
19923
  var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
19703
19924
  function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
19704
- const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
19705
- const id = crypto26.randomUUID();
19925
+ const raw = `cnry_${crypto27.randomBytes(24).toString("hex")}`;
19926
+ const id = crypto27.randomUUID();
19706
19927
  db.insert(apiKeys).values({
19707
19928
  id,
19708
19929
  name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
@@ -19854,15 +20075,15 @@ var ViewerAeroSessions = class {
19854
20075
  };
19855
20076
 
19856
20077
  // src/mcp-http.ts
19857
- import crypto27 from "crypto";
20078
+ import crypto28 from "crypto";
19858
20079
  import { eq as eq26 } from "drizzle-orm";
19859
20080
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
19860
20081
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
19861
20082
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
19862
20083
  var SWEEP_INTERVAL_MS = 60 * 1e3;
19863
20084
  function mintSessionKey(db, scopes, userId) {
19864
- const raw = `cnry_${crypto27.randomBytes(24).toString("hex")}`;
19865
- const id = crypto27.randomUUID();
20085
+ const raw = `cnry_${crypto28.randomBytes(24).toString("hex")}`;
20086
+ const id = crypto28.randomUUID();
19866
20087
  db.insert(apiKeys).values({
19867
20088
  id,
19868
20089
  name: `mcp-session:${userId}`,
@@ -19946,7 +20167,7 @@ function registerMcpHttpRoutes(scope, opts) {
19946
20167
  clientName: `canonry-mcp/${PACKAGE_VERSION}`,
19947
20168
  surface: "mcp-http",
19948
20169
  // Correlation only: this is never accepted as caller identity or authority.
19949
- actorSession: crypto27.randomUUID()
20170
+ actorSession: crypto28.randomUUID()
19950
20171
  });
19951
20172
  let server;
19952
20173
  try {
@@ -19963,7 +20184,7 @@ function registerMcpHttpRoutes(scope, opts) {
19963
20184
  throw error;
19964
20185
  }
19965
20186
  const transport = new StreamableHTTPServerTransport({
19966
- sessionIdGenerator: () => crypto27.randomUUID(),
20187
+ sessionIdGenerator: () => crypto28.randomUUID(),
19967
20188
  onsessioninitialized: (id) => {
19968
20189
  sessions.set(id, { transport, close, authorizationId, segmentId: segment.id, lastSeenAt: now() });
19969
20190
  }
@@ -21672,14 +21893,14 @@ function summarizeProviderConfig(config) {
21672
21893
  };
21673
21894
  }
21674
21895
  function hashApiKey2(key) {
21675
- return crypto28.createHash("sha256").update(key).digest("hex");
21896
+ return crypto29.createHash("sha256").update(key).digest("hex");
21676
21897
  }
21677
21898
  var DASHBOARD_SCRYPT_KEYLEN = 64;
21678
21899
  var DASHBOARD_SCRYPT_COST = 1 << 15;
21679
21900
  var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
21680
21901
  function hashDashboardPassword(password) {
21681
- const salt = crypto28.randomBytes(16);
21682
- const derived = crypto28.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
21902
+ const salt = crypto29.randomBytes(16);
21903
+ const derived = crypto29.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
21683
21904
  N: DASHBOARD_SCRYPT_COST,
21684
21905
  maxmem: DASHBOARD_SCRYPT_MAXMEM
21685
21906
  });
@@ -21700,14 +21921,14 @@ function verifyDashboardPassword(password, storedHash) {
21700
21921
  } catch {
21701
21922
  return { ok: false, needsRehash: false };
21702
21923
  }
21703
- const derived = crypto28.scryptSync(password, salt, expected.length, {
21924
+ const derived = crypto29.scryptSync(password, salt, expected.length, {
21704
21925
  N: DASHBOARD_SCRYPT_COST,
21705
21926
  maxmem: DASHBOARD_SCRYPT_MAXMEM
21706
21927
  });
21707
21928
  if (derived.length !== expected.length)
21708
21929
  return { ok: false, needsRehash: false };
21709
21930
  return {
21710
- ok: crypto28.timingSafeEqual(derived, expected),
21931
+ ok: crypto29.timingSafeEqual(derived, expected),
21711
21932
  needsRehash: false
21712
21933
  };
21713
21934
  }
@@ -21716,7 +21937,7 @@ function verifyDashboardPassword(password, storedHash) {
21716
21937
  const expected = Buffer.from(storedHash, "hex");
21717
21938
  if (candidate.length !== expected.length)
21718
21939
  return { ok: false, needsRehash: false };
21719
- const ok = crypto28.timingSafeEqual(candidate, expected);
21940
+ const ok = crypto29.timingSafeEqual(candidate, expected);
21720
21941
  return { ok, needsRehash: ok };
21721
21942
  }
21722
21943
  return { ok: false, needsRehash: false };
@@ -21852,7 +22073,7 @@ function cloneGtmConfig(config) {
21852
22073
  function createGoogleMarketingConfigCredentialStore(input) {
21853
22074
  const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
21854
22075
  const env = input.env ?? process.env;
21855
- const randomUUID3 = input.randomUUID ?? crypto28.randomUUID;
22076
+ const randomUUID3 = input.randomUUID ?? crypto29.randomUUID;
21856
22077
  const snapshot = () => ({
21857
22078
  googleAds: cloneGoogleAdsConfig(input.config.googleAds),
21858
22079
  gtm: cloneGtmConfig(input.config.gtm)
@@ -21955,7 +22176,7 @@ async function createServer(opts) {
21955
22176
  const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
21956
22177
  const app = Fastify({
21957
22178
  loggerInstance: logger,
21958
- genReqId: () => crypto28.randomUUID(),
22179
+ genReqId: () => crypto29.randomUUID(),
21959
22180
  trustProxy
21960
22181
  });
21961
22182
  const registry = new ProviderRegistry();
@@ -22722,7 +22943,7 @@ async function createServer(opts) {
22722
22943
  return removed;
22723
22944
  }
22724
22945
  };
22725
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto28.randomBytes(32).toString("hex");
22946
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto29.randomBytes(32).toString("hex");
22726
22947
  const googleConnectionStore = {
22727
22948
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
22728
22949
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -22816,7 +23037,7 @@ async function createServer(opts) {
22816
23037
  if (!existing) {
22817
23038
  const prefix = opts.config.apiKey.slice(0, 12);
22818
23039
  opts.db.insert(apiKeys).values({
22819
- id: `key_${crypto28.randomBytes(8).toString("hex")}`,
23040
+ id: `key_${crypto29.randomBytes(8).toString("hex")}`,
22820
23041
  name: "default",
22821
23042
  keyHash,
22822
23043
  keyPrefix: prefix,
@@ -22840,7 +23061,7 @@ async function createServer(opts) {
22840
23061
  };
22841
23062
  const createSession = (apiKeyId) => {
22842
23063
  pruneExpiredSessions();
22843
- const sessionId = crypto28.randomBytes(32).toString("hex");
23064
+ const sessionId = crypto29.randomBytes(32).toString("hex");
22844
23065
  sessions.set(sessionId, {
22845
23066
  apiKeyId,
22846
23067
  expiresAt: Date.now() + SESSION_TTL_MS
@@ -23313,7 +23534,7 @@ async function createServer(opts) {
23313
23534
  deps: {
23314
23535
  enqueueAutoExtract: ({ projectId, release: r }) => {
23315
23536
  const now = (/* @__PURE__ */ new Date()).toISOString();
23316
- const runId = crypto28.randomUUID();
23537
+ const runId = crypto29.randomUUID();
23317
23538
  opts.db.insert(runs).values({
23318
23539
  id: runId,
23319
23540
  projectId,
@@ -23557,7 +23778,7 @@ async function createServer(opts) {
23557
23778
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
23558
23779
  opts.db.insert(auditLog).values(
23559
23780
  targetProjectIds.map((projectId) => ({
23560
- id: crypto28.randomUUID(),
23781
+ id: crypto29.randomUUID(),
23561
23782
  projectId,
23562
23783
  actor: auditContext?.actor ?? "api",
23563
23784
  userAgent: auditContext?.userAgent ?? null,
@@ -23654,6 +23875,7 @@ async function createServer(opts) {
23654
23875
  const { event: eventName, eventId, ...properties } = event;
23655
23876
  trackEvent(eventName, properties, { source: "dashboard", eventId });
23656
23877
  },
23878
+ submitFeedback: (submission, context) => sendFeedback(submission, context),
23657
23879
  onCdpConfigure: async (host, port2) => {
23658
23880
  if (!opts.config.cdp) opts.config.cdp = {};
23659
23881
  opts.config.cdp.host = host;