@canonry/canonry 7.63.0 → 7.63.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.
@@ -295,6 +295,16 @@ Run `cnry traffic backfill <project> --source <id> --days N` (capped at
295
295
  history. It's an explicit operator action; the connect flow never pulls
296
296
  it implicitly.
297
297
 
298
+ A backfill holds the source's sync lease until it finishes, however long the
299
+ pull takes. Scheduled syncs for that source skip while it runs and resume
300
+ from the same watermark afterwards, so nothing commits into the window the
301
+ backfill replaces. A backfill requested while a sync is running returns
302
+ `OPERATION_IN_PROGRESS`; retry when that sync ends. A reset, reconnect or
303
+ deactivation during a backfill stops it within a minute: it releases the
304
+ source, stops pulling, and fails without writing. The CLI stops waiting
305
+ after 5 minutes, but the run continues on the server: follow it with
306
+ `cnry runs <project> --kind traffic-sync`.
307
+
298
308
  ## Connecting a Cloudflare source
299
309
 
300
310
  Both Cloudflare modes run the same small ES-module Worker on the site's exact
@@ -10,7 +10,7 @@ import {
10
10
  backfillSnapshotAttributionCommand,
11
11
  backfillTrafficClassificationCommand,
12
12
  replayQueryAuditLog
13
- } from "./chunk-XWDCLX55.js";
13
+ } from "./chunk-6ABOEN5D.js";
14
14
  import "./chunk-UYPBR3SF.js";
15
15
  import "./chunk-A57HYHJ3.js";
16
16
  import "./chunk-HGMYEDAQ.js";
@@ -18,7 +18,7 @@ import "./chunk-EHTSCLEL.js";
18
18
  import {
19
19
  backfillProjectAnswerMentions,
20
20
  backfillProjectAnswerMentionsInChunks
21
- } from "./chunk-Q4U2GJME.js";
21
+ } from "./chunk-22ABGRB7.js";
22
22
  import "./chunk-SG2OPR7J.js";
23
23
  import "./chunk-QNFX7ZCT.js";
24
24
  import "./chunk-HNZQVIPS.js";
@@ -62187,6 +62187,9 @@ var CLOUDFLARE_QUEUE_RETENTION_READ_TIMEOUT_MS = 1e4;
62187
62187
  var CLOUDFLARE_QUEUE_VISIBILITY_TIMEOUT_MS = 5 * 6e4;
62188
62188
  var CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS = 5 * 6e4;
62189
62189
  var WORDPRESS_SYNC_LEASE_TTL_MS = 5 * 6e4;
62190
+ var PULL_SYNC_LEASE_TTL_MS = 10 * 6e4;
62191
+ var TRAFFIC_BACKFILL_LEASE_TTL_MS = 5 * 6e4;
62192
+ var TRAFFIC_BACKFILL_LEASE_RENEW_MS = 6e4;
62190
62193
  var DEFAULT_CLOUDFLARE_INGEST_RATE_LIMIT_MAX = 6e3;
62191
62194
  var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
62192
62195
  function sha256Hex3(value) {
@@ -62533,6 +62536,9 @@ function isAuthoritativeTrafficSource(db, source) {
62533
62536
  function trafficConnectStatus(tx, projectId, existingSource) {
62534
62537
  return existingSource?.status === TrafficSourceStatuses.paused || hasConnectedTrafficSourceSibling(tx, projectId, existingSource?.id ?? "") ? TrafficSourceStatuses.paused : TrafficSourceStatuses.connected;
62535
62538
  }
62539
+ function isSameBackfillSource(current, started, leaseOwner) {
62540
+ return current.syncLeaseOwner === leaseOwner && isSameTrafficSourceGeneration(current, started);
62541
+ }
62536
62542
  function isSameTrafficSourceGeneration(current, started) {
62537
62543
  return isDeepStrictEqual(current.configJson, started.configJson) && current.updatedAt === started.updatedAt && current.lastSyncedAt === started.lastSyncedAt && current.lastCursor === started.lastCursor && current.wordpressPendingUntil === started.wordpressPendingUntil;
62538
62544
  }
@@ -62586,6 +62592,42 @@ function vercelRetentionClampError(requestedStartMs, effectiveStartMs) {
62586
62592
  return new VercelRetentionClampError(requestedStartMs, effectiveStartMs);
62587
62593
  }
62588
62594
  async function runBackfillTask(options) {
62595
+ const { app, sourceRow: sourceRow2, leaseOwner } = options;
62596
+ const stopped = new AbortController();
62597
+ const releaseLease = () => {
62598
+ try {
62599
+ releaseTrafficSyncLease({ db: app.db, sourceId: sourceRow2.id, owner: leaseOwner, now: (/* @__PURE__ */ new Date()).toISOString() });
62600
+ } catch {
62601
+ }
62602
+ };
62603
+ const renewLease2 = setInterval(() => {
62604
+ try {
62605
+ const latest = app.db.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
62606
+ if (!latest || !isAuthoritativeTrafficSource(app.db, latest) || !isSameTrafficSourceGeneration(latest, sourceRow2)) {
62607
+ clearInterval(renewLease2);
62608
+ stopped.abort();
62609
+ releaseLease();
62610
+ return;
62611
+ }
62612
+ tryClaimTrafficSyncLease({
62613
+ db: app.db,
62614
+ sourceId: sourceRow2.id,
62615
+ owner: leaseOwner,
62616
+ now: (/* @__PURE__ */ new Date()).toISOString(),
62617
+ ttlMs: TRAFFIC_BACKFILL_LEASE_TTL_MS
62618
+ });
62619
+ } catch {
62620
+ }
62621
+ }, options.leaseRenewMs);
62622
+ renewLease2.unref?.();
62623
+ try {
62624
+ await runBackfillTaskHoldingLease(options, stopped.signal);
62625
+ } finally {
62626
+ clearInterval(renewLease2);
62627
+ releaseLease();
62628
+ }
62629
+ }
62630
+ async function runBackfillTaskHoldingLease(options, signal) {
62589
62631
  const {
62590
62632
  app,
62591
62633
  runId,
@@ -62595,7 +62637,8 @@ async function runBackfillTask(options) {
62595
62637
  windowEnd,
62596
62638
  journeyKeySecret,
62597
62639
  pullForBackfill,
62598
- pullErrorPrefix
62640
+ pullErrorPrefix,
62641
+ leaseOwner
62599
62642
  } = options;
62600
62643
  const backfillStartedAt = Date.now();
62601
62644
  const target = { feature: "server_traffic", operation: "backfill", attribution: currentOutcomeAttribution() };
@@ -62609,7 +62652,7 @@ async function runBackfillTask(options) {
62609
62652
  app.db.transaction((tx) => {
62610
62653
  tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq80(runs.id, runId)).run();
62611
62654
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
62612
- if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
62655
+ if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
62613
62656
  tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq80(trafficSources.id, sourceRow2.id)).run();
62614
62657
  }
62615
62658
  });
@@ -62623,8 +62666,13 @@ async function runBackfillTask(options) {
62623
62666
  }
62624
62667
  let allEvents;
62625
62668
  try {
62626
- allEvents = await pullForBackfill();
62669
+ allEvents = await pullForBackfill(signal);
62627
62670
  } catch (e) {
62671
+ if (signal.aborted) {
62672
+ markFailed("Backfill stopped: the traffic source was reset, reconnected or deactivated while it ran", true);
62673
+ reportBackfill({ status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED });
62674
+ return;
62675
+ }
62628
62676
  markFailed(
62629
62677
  `${pullErrorPrefix}: ${describeError(e)}`,
62630
62678
  e instanceof VercelRetentionClampError
@@ -62638,7 +62686,7 @@ async function runBackfillTask(options) {
62638
62686
  try {
62639
62687
  const completed = app.db.transaction((tx) => {
62640
62688
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
62641
- const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2);
62689
+ const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner);
62642
62690
  if (stillAuthoritative) {
62643
62691
  enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
62644
62692
  }
@@ -62681,7 +62729,7 @@ async function runBackfillTask(options) {
62681
62729
  }).where(eq80(runs.id, runId)).run();
62682
62730
  return "wordpress-backfill-unsupported";
62683
62731
  }
62684
- if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
62732
+ if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
62685
62733
  tx.update(runs).set({
62686
62734
  status: RunStatuses.failed,
62687
62735
  error: "Traffic source was deactivated or reconfigured during backfill",
@@ -64205,15 +64253,19 @@ async function trafficRoutes(app, opts) {
64205
64253
  if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
64206
64254
  throw validationError("Traffic source must be active before it can sync");
64207
64255
  }
64208
- const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ? crypto52.randomUUID() : void 0;
64209
- if (wordpressLeaseOwner && !tryClaimTrafficSyncLease({
64256
+ const isWordpressSource = sourceRow2.sourceType === TrafficSourceTypes.wordpress;
64257
+ const syncLeaseOwner = crypto52.randomUUID();
64258
+ if (!tryClaimTrafficSyncLease({
64210
64259
  db: app.db,
64211
64260
  sourceId: sourceRow2.id,
64212
- owner: wordpressLeaseOwner,
64261
+ owner: syncLeaseOwner,
64213
64262
  now: (/* @__PURE__ */ new Date()).toISOString(),
64214
- ttlMs: WORDPRESS_SYNC_LEASE_TTL_MS
64263
+ ttlMs: isWordpressSource ? WORDPRESS_SYNC_LEASE_TTL_MS : PULL_SYNC_LEASE_TTL_MS
64215
64264
  })) {
64216
- throw operationInProgress("WordPress source sync is already in progress", { sourceId: sourceRow2.id });
64265
+ throw operationInProgress(
64266
+ isWordpressSource ? "WordPress source sync is already in progress" : "A sync or backfill is already running for this traffic source",
64267
+ { sourceId: sourceRow2.id }
64268
+ );
64217
64269
  }
64218
64270
  try {
64219
64271
  const syncWindowEnd = /* @__PURE__ */ new Date();
@@ -64236,7 +64288,7 @@ async function trafficRoutes(app, opts) {
64236
64288
  app.db.transaction((tx) => {
64237
64289
  tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq80(runs.id, runId)).run();
64238
64290
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
64239
- if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && (wordpressLeaseOwner === void 0 || latestSource.syncLeaseOwner === wordpressLeaseOwner)) {
64291
+ if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && latestSource.syncLeaseOwner === syncLeaseOwner) {
64240
64292
  tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq80(trafficSources.id, sourceRow2.id)).run();
64241
64293
  }
64242
64294
  });
@@ -64257,11 +64309,11 @@ async function trafficRoutes(app, opts) {
64257
64309
  }
64258
64310
  };
64259
64311
  const renewWordpressLease = () => {
64260
- if (!wordpressLeaseOwner) return;
64312
+ if (!isWordpressSource) return;
64261
64313
  if (!tryClaimTrafficSyncLease({
64262
64314
  db: app.db,
64263
64315
  sourceId: sourceRow2.id,
64264
- owner: wordpressLeaseOwner,
64316
+ owner: syncLeaseOwner,
64265
64317
  now: (/* @__PURE__ */ new Date()).toISOString(),
64266
64318
  ttlMs: WORDPRESS_SYNC_LEASE_TTL_MS
64267
64319
  })) {
@@ -64366,7 +64418,7 @@ async function trafficRoutes(app, opts) {
64366
64418
  const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
64367
64419
  const reservedSource = app.db.transaction((tx) => {
64368
64420
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
64369
- if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== wordpressLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
64421
+ if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== syncLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
64370
64422
  tx.update(trafficSources).set({
64371
64423
  lastSyncedAt: windowStartIso2,
64372
64424
  wordpressPendingUntil: windowEndIso2,
@@ -64550,7 +64602,7 @@ async function trafficRoutes(app, opts) {
64550
64602
  const commitOutcome = app.db.transaction((tx) => {
64551
64603
  const latestRow = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
64552
64604
  const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq80(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
64553
- if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || wordpressLeaseOwner !== void 0 && latestRow.syncLeaseOwner !== wordpressLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
64605
+ if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || latestRow.syncLeaseOwner !== syncLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
64554
64606
  const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
64555
64607
  tx.update(runs).set({
64556
64608
  status: RunStatuses.failed,
@@ -64880,17 +64932,15 @@ async function trafficRoutes(app, opts) {
64880
64932
  };
64881
64933
  return response;
64882
64934
  } finally {
64883
- if (wordpressLeaseOwner) {
64884
- try {
64885
- releaseTrafficSyncLease({
64886
- db: app.db,
64887
- sourceId: sourceRow2.id,
64888
- owner: wordpressLeaseOwner,
64889
- now: (/* @__PURE__ */ new Date()).toISOString()
64890
- });
64891
- } catch (error) {
64892
- request.log.warn({ sourceId: sourceRow2.id, error: describeError(error) }, "Failed to release WordPress sync lease");
64893
- }
64935
+ try {
64936
+ releaseTrafficSyncLease({
64937
+ db: app.db,
64938
+ sourceId: sourceRow2.id,
64939
+ owner: syncLeaseOwner,
64940
+ now: (/* @__PURE__ */ new Date()).toISOString()
64941
+ });
64942
+ } catch (error) {
64943
+ request.log.warn({ sourceId: sourceRow2.id, error: describeError(error) }, "Failed to release traffic sync lease");
64894
64944
  }
64895
64945
  }
64896
64946
  });
@@ -64976,11 +65026,12 @@ async function trafficRoutes(app, opts) {
64976
65026
  const vercelTeamId = config.teamId ?? credential.teamId;
64977
65027
  const vercelEnvironment = config.environment ?? credential.environment;
64978
65028
  pullErrorPrefix = "Vercel pull failed";
64979
- pullForBackfill = async () => {
65029
+ pullForBackfill = async (signal) => {
64980
65030
  const collected = [];
64981
65031
  const seenEventIds = /* @__PURE__ */ new Set();
64982
65032
  const backfillEndMs = windowEnd.getTime();
64983
65033
  for (let chunkStartMs = windowStart.getTime(); chunkStartMs < backfillEndMs; chunkStartMs += VERCEL_BACKFILL_CHUNK_MS) {
65034
+ signal.throwIfAborted();
64984
65035
  const chunkEndMs = Math.min(chunkStartMs + VERCEL_BACKFILL_CHUNK_MS, backfillEndMs);
64985
65036
  const drained = await drainVercelTrafficEvents({
64986
65037
  pull: pullVercelEvents,
@@ -65008,25 +65059,42 @@ async function trafficRoutes(app, opts) {
65008
65059
  }
65009
65060
  const startedAt = windowEnd.toISOString();
65010
65061
  const runId = crypto52.randomUUID();
65011
- app.db.transaction((tx) => {
65012
- const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
65013
- if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
65014
- throw validationError("Traffic source must remain active and unchanged before it can backfill");
65015
- }
65016
- tx.insert(runs).values({
65017
- id: runId,
65018
- projectId: project.id,
65019
- kind: RunKinds["traffic-sync"],
65020
- status: RunStatuses.running,
65021
- trigger: RunTriggers.backfill,
65022
- sourceId: sourceRow2.id,
65023
- startedAt,
65024
- createdAt: startedAt
65025
- }).run();
65026
- });
65062
+ const leaseOwner = crypto52.randomUUID();
65063
+ if (!tryClaimTrafficSyncLease({
65064
+ db: app.db,
65065
+ sourceId: sourceRow2.id,
65066
+ owner: leaseOwner,
65067
+ now: (/* @__PURE__ */ new Date()).toISOString(),
65068
+ ttlMs: TRAFFIC_BACKFILL_LEASE_TTL_MS
65069
+ })) {
65070
+ throw operationInProgress("A sync or backfill is already running for this traffic source; retry when it finishes", { sourceId: sourceRow2.id });
65071
+ }
65072
+ try {
65073
+ app.db.transaction((tx) => {
65074
+ const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
65075
+ if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
65076
+ throw validationError("Traffic source must remain active and unchanged before it can backfill");
65077
+ }
65078
+ tx.insert(runs).values({
65079
+ id: runId,
65080
+ projectId: project.id,
65081
+ kind: RunKinds["traffic-sync"],
65082
+ status: RunStatuses.running,
65083
+ trigger: RunTriggers.backfill,
65084
+ sourceId: sourceRow2.id,
65085
+ startedAt,
65086
+ createdAt: startedAt
65087
+ }).run();
65088
+ });
65089
+ } catch (error) {
65090
+ releaseTrafficSyncLease({ db: app.db, sourceId: sourceRow2.id, owner: leaseOwner, now: (/* @__PURE__ */ new Date()).toISOString() });
65091
+ throw error;
65092
+ }
65027
65093
  void runBackfillTask({
65028
65094
  app,
65029
65095
  runId,
65096
+ leaseOwner,
65097
+ leaseRenewMs: opts.trafficBackfillLeaseRenewMs ?? TRAFFIC_BACKFILL_LEASE_RENEW_MS,
65030
65098
  project,
65031
65099
  sourceRow: sourceRow2,
65032
65100
  journeyKeySecret: opts.journeyKeySecret,
@@ -74342,6 +74410,7 @@ async function apiRoutes(app, opts) {
74342
74410
  vercelTrafficCredentialStore: opts.vercelTrafficCredentialStore,
74343
74411
  pullVercelTrafficEvents: opts.pullVercelTrafficEvents,
74344
74412
  vercelSyncDeadlineMs: opts.vercelSyncDeadlineMs,
74413
+ trafficBackfillLeaseRenewMs: opts.trafficBackfillLeaseRenewMs,
74345
74414
  cloudflareTrafficCredentialStore: opts.cloudflareTrafficCredentialStore,
74346
74415
  pullCloudflareQueueMessages: opts.pullCloudflareQueueMessages,
74347
74416
  ackCloudflareQueueMessages: opts.ackCloudflareQueueMessages,
@@ -599,7 +599,7 @@ var DEMO_CLI_COMMANDS = [{
599
599
  options: { host: stringOption(), port: stringOption(), "trust-proxy": multiStringOption() },
600
600
  allowPositionals: false,
601
601
  run: async (input) => {
602
- const { demoCommand } = await import("./demo-X7LQ24GS.js");
602
+ const { demoCommand } = await import("./demo-ZRFZ5EBH.js");
603
603
  await demoCommand({
604
604
  host: getString(input.values, "host"),
605
605
  port: getString(input.values, "port"),
@@ -620,7 +620,7 @@ var BACKFILL_CLI_COMMANDS = [
620
620
  allowPositionals: false,
621
621
  supportsDryRun: true,
622
622
  run: async (input) => {
623
- const { backfillAnswerVisibilityCommand } = await import("./backfill-HZU3BBOH.js");
623
+ const { backfillAnswerVisibilityCommand } = await import("./backfill-2BVFKQ6J.js");
624
624
  await backfillAnswerVisibilityCommand({
625
625
  project: getString(input.values, "project"),
626
626
  dryRun: input.dryRun,
@@ -637,7 +637,7 @@ var BACKFILL_CLI_COMMANDS = [
637
637
  allowPositionals: false,
638
638
  supportsDryRun: true,
639
639
  run: async (input) => {
640
- const { backfillAnswerMentionsCommand } = await import("./backfill-HZU3BBOH.js");
640
+ const { backfillAnswerMentionsCommand } = await import("./backfill-2BVFKQ6J.js");
641
641
  await backfillAnswerMentionsCommand({
642
642
  project: getString(input.values, "project"),
643
643
  dryRun: input.dryRun,
@@ -655,7 +655,7 @@ var BACKFILL_CLI_COMMANDS = [
655
655
  },
656
656
  supportsDryRun: true,
657
657
  run: async (input) => {
658
- const { backfillInsightsCommand } = await import("./backfill-HZU3BBOH.js");
658
+ const { backfillInsightsCommand } = await import("./backfill-2BVFKQ6J.js");
659
659
  const usage = "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--since <date>] [--dry-run] [--format json]";
660
660
  const project3 = requireProject(input, "backfill insights", usage);
661
661
  await backfillInsightsCommand(project3, {
@@ -675,7 +675,7 @@ var BACKFILL_CLI_COMMANDS = [
675
675
  },
676
676
  allowPositionals: false,
677
677
  run: async (input) => {
678
- const { backfillNormalizedPathsCommand } = await import("./backfill-HZU3BBOH.js");
678
+ const { backfillNormalizedPathsCommand } = await import("./backfill-2BVFKQ6J.js");
679
679
  await backfillNormalizedPathsCommand({
680
680
  project: getString(input.values, "project"),
681
681
  format: input.format
@@ -690,7 +690,7 @@ var BACKFILL_CLI_COMMANDS = [
690
690
  },
691
691
  allowPositionals: false,
692
692
  run: async (input) => {
693
- const { backfillAiReferralPathsCommand } = await import("./backfill-HZU3BBOH.js");
693
+ const { backfillAiReferralPathsCommand } = await import("./backfill-2BVFKQ6J.js");
694
694
  await backfillAiReferralPathsCommand({
695
695
  project: getString(input.values, "project"),
696
696
  format: input.format
@@ -702,7 +702,7 @@ var BACKFILL_CLI_COMMANDS = [
702
702
  usage: "canonry backfill snapshot-attribution <project> [--dry-run] [--format json]",
703
703
  supportsDryRun: true,
704
704
  run: async (input) => {
705
- const { backfillSnapshotAttributionCommand } = await import("./backfill-HZU3BBOH.js");
705
+ const { backfillSnapshotAttributionCommand } = await import("./backfill-2BVFKQ6J.js");
706
706
  const usage = "canonry backfill snapshot-attribution <project> [--dry-run] [--format json]";
707
707
  const project3 = requireProject(input, "backfill snapshot-attribution", usage);
708
708
  await backfillSnapshotAttributionCommand({
@@ -721,7 +721,7 @@ var BACKFILL_CLI_COMMANDS = [
721
721
  allowPositionals: false,
722
722
  supportsDryRun: true,
723
723
  run: async (input) => {
724
- const { backfillTrafficClassificationCommand } = await import("./backfill-HZU3BBOH.js");
724
+ const { backfillTrafficClassificationCommand } = await import("./backfill-2BVFKQ6J.js");
725
725
  await backfillTrafficClassificationCommand({
726
726
  project: getString(input.values, "project"),
727
727
  dryRun: input.dryRun,
@@ -1163,7 +1163,7 @@ var DASHBOARD_CLI_COMMANDS = [
1163
1163
  usage: "canonry dashboard reset-password [--format json]",
1164
1164
  help: "Clears the dashboard password from config.yaml, so the dashboard asks for a new one after a restart.\nLocal only: it edits the config file under CANONRY_CONFIG_DIR and does not call the server.",
1165
1165
  run: async (input) => {
1166
- const { resetDashboardPassword } = await import("./dashboard-XJJ6NZ2L.js");
1166
+ const { resetDashboardPassword } = await import("./dashboard-BC4NKAO2.js");
1167
1167
  await resetDashboardPassword({ format: input.format });
1168
1168
  }
1169
1169
  },
@@ -6720,7 +6720,7 @@ var SYSTEM_CLI_COMMANDS = [
6720
6720
  options: { access: stringOption() },
6721
6721
  run: async (input) => {
6722
6722
  if (input.positionals.length > 1) throw usageError("Expected at most one command group");
6723
- const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-VRJS66GR.js"), import("./cli-commands-6LZ5OIIP.js")]);
6723
+ const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-VRJS66GR.js"), import("./cli-commands-ONFE63OB.js")]);
6724
6724
  printCliCatalog(REGISTERED_CLI_COMMANDS2, input.format, input.positionals[0], getString(input.values, "access"));
6725
6725
  }
6726
6726
  },
@@ -6769,7 +6769,7 @@ var SYSTEM_CLI_COMMANDS = [
6769
6769
  },
6770
6770
  allowPositionals: false,
6771
6771
  run: async (input) => {
6772
- const { initCommand } = await import("./init-JRGRHLYU.js");
6772
+ const { initCommand } = await import("./init-TU4VSGVU.js");
6773
6773
  await initCommand({
6774
6774
  force: getBoolean(input.values, "force"),
6775
6775
  ref: getString(input.values, "ref"),
@@ -6824,7 +6824,7 @@ var SYSTEM_CLI_COMMANDS = [
6824
6824
  allowPositionals: false,
6825
6825
  run: async (input) => {
6826
6826
  applyServerEnv(input.values);
6827
- const { serveCommand } = await import("./serve-FTAFEEJD.js");
6827
+ const { serveCommand } = await import("./serve-VIEGFGSK.js");
6828
6828
  await serveCommand(input.format);
6829
6829
  }
6830
6830
  },
@@ -8132,8 +8132,8 @@ var AGENT_CLI_COMMANDS = [
8132
8132
  "timeout-ms": stringOption()
8133
8133
  },
8134
8134
  run: async (input) => {
8135
- const { coerceAgentProvider } = await import("./session-E4V5MRUE.js");
8136
- const { AERO_TOOL_PROFILES, isAeroToolProfile } = await import("./tools-XKGEC56T.js");
8135
+ const { coerceAgentProvider } = await import("./session-JLH72BF5.js");
8136
+ const { AERO_TOOL_PROFILES, isAeroToolProfile } = await import("./tools-YOV2KHF2.js");
8137
8137
  const { agentAsk } = await import("./agent-ask-UEYN2SQX.js");
8138
8138
  const usage = `canonry agent ask <project> "<prompt>" [--provider ${AGENT_PROVIDER_IDS.join("|")}] [--model <id>] [--scope all|read-only] [--profile default|ads-operator] [--context <json>] [--conversation <id>] [--max-tool-calls <n>] [--timeout-ms <ms>] [--format json]`;
8139
8139
  const project3 = requireProject(input, "agent.ask", usage);
@@ -14,7 +14,7 @@ import {
14
14
  mergeGscQueryTotalsWithFallback,
15
15
  readGscQueryDailyRows,
16
16
  readLatestGscDataDate
17
- } from "./chunk-Q4U2GJME.js";
17
+ } from "./chunk-22ABGRB7.js";
18
18
  import {
19
19
  getRequestContext
20
20
  } from "./chunk-QNFX7ZCT.js";
@@ -23,7 +23,7 @@ import {
23
23
  createRunCompetitorResolver,
24
24
  readMarketCompetitorNames,
25
25
  refreshSnapshotMentionIdentity
26
- } from "./chunk-Q4U2GJME.js";
26
+ } from "./chunk-22ABGRB7.js";
27
27
  import {
28
28
  classifyAiUserFetch,
29
29
  classifyCrawler
@@ -437,7 +437,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
437
437
  }
438
438
  }
439
439
  async function backfillInsightsCommand(project, opts) {
440
- const { IntelligenceService } = await import("./intelligence-service-7LCTJQDL.js");
440
+ const { IntelligenceService } = await import("./intelligence-service-MWTHUE7S.js");
441
441
  const config = loadConfig();
442
442
  const db = createClient(config.database);
443
443
  migrate(db);
@@ -27,14 +27,14 @@ import {
27
27
  resolveConfiguredAgentProvider,
28
28
  resolveSessionProviderAndModel,
29
29
  setAeroSystemPrompt
30
- } from "./chunk-WYTRXYUR.js";
30
+ } from "./chunk-V3QMOGZE.js";
31
31
  import {
32
32
  AeroToolProfiles,
33
33
  AeroToolScopes,
34
34
  buildAeroStateTools,
35
35
  isAeroToolProfile,
36
36
  truncateToolResult
37
- } from "./chunk-QPTSZDHE.js";
37
+ } from "./chunk-JM6OFCLX.js";
38
38
  import {
39
39
  getGoogleAuthConfig,
40
40
  getGoogleConnection,
@@ -162,7 +162,7 @@ import {
162
162
  addLogListener,
163
163
  createFastifyLogger,
164
164
  createLogger
165
- } from "./chunk-TDWNONBU.js";
165
+ } from "./chunk-5GMYD7UZ.js";
166
166
  import {
167
167
  computeCitedCompetitorDomains,
168
168
  determineCitationState
@@ -285,7 +285,7 @@ import {
285
285
  webhookResponseReason,
286
286
  webhookTargetRefusalReason,
287
287
  writeAuditLog
288
- } from "./chunk-Q4U2GJME.js";
288
+ } from "./chunk-22ABGRB7.js";
289
289
  import {
290
290
  OperationalLogStore,
291
291
  SentimentIdempotencyConflict,
@@ -5658,7 +5658,7 @@ function nonBlank(value) {
5658
5658
  return trimmed ? trimmed : void 0;
5659
5659
  }
5660
5660
  function resolveBuildCommit(env = process.env) {
5661
- const embedded = true ? "c84ec8de1e654959b9c19d25c2d2e0c6b3da20a5" : void 0;
5661
+ const embedded = true ? "bf661eb5ab6eb02b455254b13b5cc8d0d8ca6536" : void 0;
5662
5662
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
5663
5663
  }
5664
5664
  function resolveInstanceIdentity(env = process.env) {
@@ -19723,6 +19723,10 @@ async function createServer(opts) {
19723
19723
  schedulerClient.trafficSync(projectName, sourceId).finally(() => {
19724
19724
  scheduledSyncClaims.delete(syncRunClaimKey(projectName, RunKinds["traffic-sync"], sourceId));
19725
19725
  }).catch((err) => {
19726
+ if (err instanceof CliError && err.code === "OPERATION_IN_PROGRESS") {
19727
+ app.log.info({ projectName, sourceId }, "Scheduled traffic sync skipped: the source is busy");
19728
+ return;
19729
+ }
19726
19730
  app.log.error(
19727
19731
  {
19728
19732
  projectName,
@@ -47,7 +47,7 @@ function queueServeHandoff() {
47
47
  async function runPendingServeHandoff(format) {
48
48
  if (!pendingServeHandoff) return;
49
49
  pendingServeHandoff = false;
50
- const { serveCommand } = await import("./serve-FTAFEEJD.js");
50
+ const { serveCommand } = await import("./serve-VIEGFGSK.js");
51
51
  await serveCommand(format);
52
52
  }
53
53
 
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-HGMYEDAQ.js";
11
11
  import {
12
12
  buildOpenApiDocument
13
- } from "./chunk-Q4U2GJME.js";
13
+ } from "./chunk-22ABGRB7.js";
14
14
  import {
15
15
  DEFAULT_COMPETITOR_AUTO_ALIAS_MODE,
16
16
  MeasurementPortfolioLists,
@@ -5,7 +5,7 @@ import {
5
5
  isStoredPageReadTool,
6
6
  isStoredReadTool,
7
7
  truncateToolResult
8
- } from "./chunk-QPTSZDHE.js";
8
+ } from "./chunk-JM6OFCLX.js";
9
9
  import {
10
10
  CANONRY_MCP_TOOLKITS
11
11
  } from "./chunk-NCIPNVD6.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  REGISTERED_CLI_COMMANDS
3
- } from "./chunk-I5QSS3CY.js";
3
+ } from "./chunk-32KNDPD3.js";
4
4
  import "./chunk-6GO5VVAC.js";
5
5
  import "./chunk-2LAL5W6J.js";
6
6
  import "./chunk-VJWDBFSL.js";
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node --import tsx
2
2
  import {
3
3
  REGISTERED_CLI_COMMANDS
4
- } from "./chunk-I5QSS3CY.js";
4
+ } from "./chunk-32KNDPD3.js";
5
5
  import {
6
6
  buildSetupState,
7
7
  runPendingServeHandoff
8
- } from "./chunk-W5TIRS4U.js";
8
+ } from "./chunk-IRRONWF7.js";
9
9
  import "./chunk-6GO5VVAC.js";
10
10
  import "./chunk-2LAL5W6J.js";
11
11
  import "./chunk-VJWDBFSL.js";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-HGMYEDAQ.js";
8
8
  import {
9
9
  anyUsersExist
10
- } from "./chunk-Q4U2GJME.js";
10
+ } from "./chunk-22ABGRB7.js";
11
11
  import "./chunk-SG2OPR7J.js";
12
12
  import "./chunk-QNFX7ZCT.js";
13
13
  import {
@@ -40,7 +40,7 @@ function parseDemoTrustedProxies(values) {
40
40
  async function demoCommand(options) {
41
41
  const { host, port } = parseDemoListenOptions(options);
42
42
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
43
- const { createDemoServer } = await import("./demo-server-MBPBTJXG.js");
43
+ const { createDemoServer } = await import("./demo-server-7R7NGWJA.js");
44
44
  const app = await createDemoServer({ trustProxy });
45
45
  let stopping = false;
46
46
  const stop = () => {
@@ -18,14 +18,14 @@ import "./chunk-HGMYEDAQ.js";
18
18
  import {
19
19
  HISTORY_WINDOW_RUNS,
20
20
  IntelligenceService
21
- } from "./chunk-TDWNONBU.js";
21
+ } from "./chunk-5GMYD7UZ.js";
22
22
  import {
23
23
  determineCitationState
24
24
  } from "./chunk-EHTSCLEL.js";
25
25
  import {
26
26
  apiRoutes,
27
27
  assessConversionTrackingIntegrity
28
- } from "./chunk-Q4U2GJME.js";
28
+ } from "./chunk-22ABGRB7.js";
29
29
  import "./chunk-SG2OPR7J.js";
30
30
  import "./chunk-QNFX7ZCT.js";
31
31
  import {