@ainyc/canonry 4.15.2 → 4.17.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.
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-7SRKUAZO.js";
8
+ } from "./chunk-6TWKC3DP.js";
9
9
  import {
10
10
  DEFAULT_RUN_HISTORY_LIMIT,
11
11
  IntelligenceService,
@@ -66,7 +66,7 @@ import {
66
66
  schedules,
67
67
  trafficSources,
68
68
  usageCounters
69
- } from "./chunk-MI33SQL6.js";
69
+ } from "./chunk-PAZCY4FF.js";
70
70
  import {
71
71
  AGENT_MEMORY_VALUE_MAX_BYTES,
72
72
  AGENT_PROVIDER_IDS,
@@ -81,6 +81,7 @@ import {
81
81
  RunKinds,
82
82
  RunStatuses,
83
83
  RunTriggers,
84
+ SchedulableRunKinds,
84
85
  TrafficEventConfidences,
85
86
  TrafficEventKinds,
86
87
  TrafficEvidenceKinds,
@@ -144,6 +145,7 @@ import {
144
145
  runInProgress,
145
146
  runNotCancellable,
146
147
  runTriggerRequestSchema,
148
+ schedulableRunKindSchema,
147
149
  scheduleUpsertRequestSchema,
148
150
  serializeRunError,
149
151
  snapshotRequestSchema,
@@ -153,7 +155,7 @@ import {
153
155
  visibilityStateFromAnswerMentioned,
154
156
  windowCutoff,
155
157
  wordpressEnvSchema
156
- } from "./chunk-ONI3TX2A.js";
158
+ } from "./chunk-Q2OED5JQ.js";
157
159
 
158
160
  // src/telemetry.ts
159
161
  import crypto from "crypto";
@@ -1436,7 +1438,7 @@ function loadRunDetail(app, run) {
1436
1438
 
1437
1439
  // ../api-routes/src/apply.ts
1438
1440
  import crypto10 from "crypto";
1439
- import { eq as eq8 } from "drizzle-orm";
1441
+ import { and as and2, eq as eq8 } from "drizzle-orm";
1440
1442
 
1441
1443
  // ../api-routes/src/schedule-utils.ts
1442
1444
  var DAY_MAP = {
@@ -1841,8 +1843,9 @@ async function applyRoutes(app, opts) {
1841
1843
  entityType: "competitor",
1842
1844
  diff: { competitors: normalizedCompetitors }
1843
1845
  });
1846
+ const AV_KIND = SchedulableRunKinds["answer-visibility"];
1844
1847
  if (resolvedSchedule) {
1845
- const existingSched = tx.select().from(schedules).where(eq8(schedules.projectId, projectId)).get();
1848
+ const existingSched = tx.select().from(schedules).where(and2(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
1846
1849
  if (existingSched) {
1847
1850
  tx.update(schedules).set({
1848
1851
  cronExpr: resolvedSchedule.cronExpr,
@@ -1856,6 +1859,7 @@ async function applyRoutes(app, opts) {
1856
1859
  tx.insert(schedules).values({
1857
1860
  id: crypto10.randomUUID(),
1858
1861
  projectId,
1862
+ kind: AV_KIND,
1859
1863
  cronExpr: resolvedSchedule.cronExpr,
1860
1864
  preset: resolvedSchedule.preset,
1861
1865
  timezone: resolvedSchedule.timezone,
@@ -1867,9 +1871,9 @@ async function applyRoutes(app, opts) {
1867
1871
  }
1868
1872
  scheduleAction = "upsert";
1869
1873
  } else if (deleteSchedule) {
1870
- const existingSched = tx.select().from(schedules).where(eq8(schedules.projectId, projectId)).get();
1874
+ const existingSched = tx.select().from(schedules).where(and2(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
1871
1875
  if (existingSched) {
1872
- tx.delete(schedules).where(eq8(schedules.projectId, projectId)).run();
1876
+ tx.delete(schedules).where(and2(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).run();
1873
1877
  scheduleAction = "delete";
1874
1878
  }
1875
1879
  }
@@ -1897,7 +1901,7 @@ async function applyRoutes(app, opts) {
1897
1901
  }
1898
1902
  });
1899
1903
  if (scheduleAction) {
1900
- opts?.onScheduleUpdated?.(scheduleAction, projectId);
1904
+ opts?.onScheduleUpdated?.(scheduleAction, projectId, SchedulableRunKinds["answer-visibility"]);
1901
1905
  }
1902
1906
  if (!hasNotifications) {
1903
1907
  opts?.onProjectUpserted?.(projectId, config.metadata.name);
@@ -2559,7 +2563,7 @@ function buildCategoryCounts(counts) {
2559
2563
  }
2560
2564
 
2561
2565
  // ../api-routes/src/intelligence.ts
2562
- import { eq as eq11, desc as desc4, and as and2 } from "drizzle-orm";
2566
+ import { eq as eq11, desc as desc4, and as and3 } from "drizzle-orm";
2563
2567
  function emptyHealthSnapshot(projectId) {
2564
2568
  return {
2565
2569
  id: `no-data:${projectId}`,
@@ -2610,7 +2614,7 @@ async function intelligenceRoutes(app) {
2610
2614
  if (request.query.runId) {
2611
2615
  conditions.push(eq11(insights.runId, request.query.runId));
2612
2616
  }
2613
- const rows = app.db.select().from(insights).where(conditions.length === 1 ? conditions[0] : and2(...conditions)).orderBy(desc4(insights.createdAt)).all();
2617
+ const rows = app.db.select().from(insights).where(conditions.length === 1 ? conditions[0] : and3(...conditions)).orderBy(desc4(insights.createdAt)).all();
2614
2618
  const showDismissed = request.query.dismissed === "true";
2615
2619
  const result = rows.filter((r) => showDismissed || !r.dismissed).map(mapInsightRow);
2616
2620
  return reply.send(result);
@@ -2650,7 +2654,7 @@ async function intelligenceRoutes(app) {
2650
2654
  }
2651
2655
 
2652
2656
  // ../api-routes/src/report.ts
2653
- import { and as and4, desc as desc6, eq as eq13, inArray as inArray4, or as or2 } from "drizzle-orm";
2657
+ import { and as and5, desc as desc6, eq as eq13, inArray as inArray4, or as or2 } from "drizzle-orm";
2654
2658
 
2655
2659
  // ../api-routes/src/report-renderer.ts
2656
2660
  var COLORS = {
@@ -4725,7 +4729,7 @@ function renderReportHtml(report, opts = {}) {
4725
4729
  }
4726
4730
 
4727
4731
  // ../api-routes/src/content-data.ts
4728
- import { and as and3, eq as eq12, desc as desc5, inArray as inArray3 } from "drizzle-orm";
4732
+ import { and as and4, eq as eq12, desc as desc5, inArray as inArray3 } from "drizzle-orm";
4729
4733
  var RECENT_RUNS_WINDOW = 5;
4730
4734
  function loadOrchestratorInput(db, project, locationFilter = void 0) {
4731
4735
  const projectId = project.id;
@@ -4849,7 +4853,7 @@ function listCompetitorDomains(db, projectId) {
4849
4853
  }
4850
4854
  function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter) {
4851
4855
  const rows = db.select({ id: runs.id, location: runs.location }).from(runs).where(
4852
- and3(
4856
+ and4(
4853
4857
  eq12(runs.projectId, projectId),
4854
4858
  eq12(runs.kind, RunKinds["answer-visibility"]),
4855
4859
  // Queued/running/failed/cancelled runs may have partial or no
@@ -5193,7 +5197,7 @@ function buildGscSection(db, projectId, projectDisplayName, canonicalDomain, tra
5193
5197
  }
5194
5198
  function buildGaSection(db, projectId) {
5195
5199
  const windowSummary = db.select().from(gaTrafficWindowSummaries).where(
5196
- and4(
5200
+ and5(
5197
5201
  eq13(gaTrafficWindowSummaries.projectId, projectId),
5198
5202
  eq13(gaTrafficWindowSummaries.windowKey, "30d")
5199
5203
  )
@@ -5376,7 +5380,7 @@ function buildIndexingHealth(db, projectId) {
5376
5380
  return null;
5377
5381
  }
5378
5382
  function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
5379
- const visibilityRuns = db.select().from(runs).where(and4(eq13(runs.projectId, projectId), eq13(runs.kind, RunKinds["answer-visibility"]))).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
5383
+ const visibilityRuns = db.select().from(runs).where(and5(eq13(runs.projectId, projectId), eq13(runs.kind, RunKinds["answer-visibility"]))).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
5380
5384
  const totalQueries = queryLookup.byId.size;
5381
5385
  const points = [];
5382
5386
  for (const run of visibilityRuns) {
@@ -5422,14 +5426,14 @@ function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
5422
5426
  }
5423
5427
  function buildInsightList(db, projectId, locationFilter) {
5424
5428
  const recentRunIds = db.select({ id: runs.id, location: runs.location }).from(runs).where(
5425
- and4(
5429
+ and5(
5426
5430
  eq13(runs.projectId, projectId),
5427
5431
  eq13(runs.kind, RunKinds["answer-visibility"]),
5428
5432
  or2(eq13(runs.status, RunStatuses.completed), eq13(runs.status, RunStatuses.partial))
5429
5433
  )
5430
5434
  ).orderBy(desc6(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
5431
5435
  if (recentRunIds.length === 0) return [];
5432
- const rows = db.select().from(insights).where(and4(eq13(insights.projectId, projectId), inArray4(insights.runId, recentRunIds))).orderBy(desc6(insights.createdAt)).all();
5436
+ const rows = db.select().from(insights).where(and5(eq13(insights.projectId, projectId), inArray4(insights.runId, recentRunIds))).orderBy(desc6(insights.createdAt)).all();
5433
5437
  const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
5434
5438
  const flat = rows.filter((r) => !r.dismissed).map((r) => {
5435
5439
  const recommendation = parseJsonColumn(r.recommendation, null);
@@ -6351,7 +6355,7 @@ function normalizeDomain2(domain) {
6351
6355
  }
6352
6356
 
6353
6357
  // ../api-routes/src/composites.ts
6354
- import { eq as eq15, and as and5, desc as desc7, sql as sql3, like, or as or3, inArray as inArray6 } from "drizzle-orm";
6358
+ import { eq as eq15, and as and6, desc as desc7, sql as sql3, like, or as or3, inArray as inArray6 } from "drizzle-orm";
6355
6359
  var TOP_INSIGHT_LIMIT = 5;
6356
6360
  var SEARCH_HIT_HARD_LIMIT = 50;
6357
6361
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -6454,7 +6458,7 @@ async function compositeRoutes(app) {
6454
6458
  rawResponse: querySnapshots.rawResponse,
6455
6459
  createdAt: querySnapshots.createdAt
6456
6460
  }).from(querySnapshots).innerJoin(queries, eq15(querySnapshots.queryId, queries.id)).where(
6457
- and5(
6461
+ and6(
6458
6462
  eq15(queries.projectId, project.id),
6459
6463
  or3(
6460
6464
  sql3`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
@@ -6465,7 +6469,7 @@ async function compositeRoutes(app) {
6465
6469
  )
6466
6470
  ).orderBy(desc7(querySnapshots.createdAt)).limit(limit + 1).all();
6467
6471
  const insightMatches = app.db.select().from(insights).where(
6468
- and5(
6472
+ and6(
6469
6473
  eq15(insights.projectId, project.id),
6470
6474
  or3(
6471
6475
  like(insights.title, pattern),
@@ -7037,6 +7041,12 @@ var locationQueryParameter = {
7037
7041
  description: "Filter by location label. Use an empty value to request locationless results.",
7038
7042
  schema: stringSchema
7039
7043
  };
7044
+ var scheduleKindQueryParameter = {
7045
+ name: "kind",
7046
+ in: "query",
7047
+ description: 'Schedulable run kind. Defaults to "answer-visibility" for backward compatibility.',
7048
+ schema: { type: "string", enum: ["answer-visibility", "traffic-sync"] }
7049
+ };
7040
7050
  var reportAudienceQueryParameter = {
7041
7051
  name: "audience",
7042
7052
  in: "query",
@@ -7894,7 +7904,7 @@ var routeCatalog = [
7894
7904
  path: "/api/v1/projects/{name}/schedule",
7895
7905
  summary: "Create or update a schedule",
7896
7906
  tags: ["schedules"],
7897
- parameters: [nameParameter],
7907
+ parameters: [nameParameter, scheduleKindQueryParameter],
7898
7908
  requestBody: {
7899
7909
  required: true,
7900
7910
  content: {
@@ -7902,11 +7912,13 @@ var routeCatalog = [
7902
7912
  schema: {
7903
7913
  type: "object",
7904
7914
  properties: {
7915
+ kind: { type: "string", enum: ["answer-visibility", "traffic-sync"] },
7905
7916
  preset: stringSchema,
7906
7917
  cron: stringSchema,
7907
7918
  timezone: stringSchema,
7908
7919
  providers: stringArraySchema,
7909
- enabled: booleanSchema
7920
+ enabled: booleanSchema,
7921
+ sourceId: stringSchema
7910
7922
  }
7911
7923
  }
7912
7924
  }
@@ -7914,7 +7926,8 @@ var routeCatalog = [
7914
7926
  },
7915
7927
  responses: {
7916
7928
  200: { description: "Schedule updated." },
7917
- 201: { description: "Schedule created." }
7929
+ 201: { description: "Schedule created." },
7930
+ 400: { description: "Invalid payload (e.g. sourceId missing for kind=traffic-sync, or providers set for kind=traffic-sync)." }
7918
7931
  }
7919
7932
  },
7920
7933
  {
@@ -7922,7 +7935,7 @@ var routeCatalog = [
7922
7935
  path: "/api/v1/projects/{name}/schedule",
7923
7936
  summary: "Get a schedule",
7924
7937
  tags: ["schedules"],
7925
- parameters: [nameParameter],
7938
+ parameters: [nameParameter, scheduleKindQueryParameter],
7926
7939
  responses: {
7927
7940
  200: { description: "Schedule returned." },
7928
7941
  404: { description: "Schedule not found." }
@@ -7933,7 +7946,7 @@ var routeCatalog = [
7933
7946
  path: "/api/v1/projects/{name}/schedule",
7934
7947
  summary: "Delete a schedule",
7935
7948
  tags: ["schedules"],
7936
- parameters: [nameParameter],
7949
+ parameters: [nameParameter, scheduleKindQueryParameter],
7937
7950
  responses: {
7938
7951
  204: { description: "Schedule deleted." },
7939
7952
  404: { description: "Schedule not found." }
@@ -10089,7 +10102,15 @@ async function telemetryRoutes(app, opts) {
10089
10102
 
10090
10103
  // ../api-routes/src/schedules.ts
10091
10104
  import crypto11 from "crypto";
10092
- import { eq as eq16 } from "drizzle-orm";
10105
+ import { and as and7, eq as eq16 } from "drizzle-orm";
10106
+ function parseKindParam(raw) {
10107
+ if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
10108
+ const parsed = schedulableRunKindSchema.safeParse(raw);
10109
+ if (!parsed.success) {
10110
+ throw validationError(`Invalid kind "${String(raw)}". Must be one of: ${Object.values(SchedulableRunKinds).join(", ")}`);
10111
+ }
10112
+ return parsed.data;
10113
+ }
10093
10114
  async function scheduleRoutes(app, opts) {
10094
10115
  app.put("/projects/:name/schedule", async (request, reply) => {
10095
10116
  const project = resolveProject(app.db, request.params.name);
@@ -10102,7 +10123,22 @@ async function scheduleRoutes(app, opts) {
10102
10123
  }))
10103
10124
  });
10104
10125
  }
10105
- const { preset, cron: cron2, timezone, providers, enabled } = parsedBody.data;
10126
+ const kind = parsedBody.data.kind ?? parseKindParam(request.query?.kind);
10127
+ const { preset, cron: cron2, timezone, providers, enabled, sourceId } = parsedBody.data;
10128
+ if (kind === SchedulableRunKinds["traffic-sync"]) {
10129
+ if (!sourceId) {
10130
+ throw validationError('"sourceId" is required when kind is "traffic-sync"');
10131
+ }
10132
+ const sourceRow = app.db.select().from(trafficSources).where(eq16(trafficSources.id, sourceId)).get();
10133
+ if (!sourceRow || sourceRow.projectId !== project.id) {
10134
+ throw notFound("Traffic source", sourceId);
10135
+ }
10136
+ if (providers && providers.length > 0) {
10137
+ throw validationError('"providers" is not valid for kind "traffic-sync"');
10138
+ }
10139
+ } else if (sourceId) {
10140
+ throw validationError(`"sourceId" is only valid when kind is "traffic-sync"`);
10141
+ }
10106
10142
  const validNames = opts.validProviderNames ?? [];
10107
10143
  if (validNames.length && providers?.length) {
10108
10144
  const invalid = providers.filter((p) => !validNames.includes(p));
@@ -10132,13 +10168,14 @@ async function scheduleRoutes(app, opts) {
10132
10168
  }
10133
10169
  const now = (/* @__PURE__ */ new Date()).toISOString();
10134
10170
  const enabledInt = enabled === false ? 0 : 1;
10135
- const existing = app.db.select().from(schedules).where(eq16(schedules.projectId, project.id)).get();
10171
+ const existing = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
10136
10172
  if (existing) {
10137
10173
  app.db.update(schedules).set({
10138
10174
  cronExpr,
10139
10175
  preset: preset ?? null,
10140
10176
  timezone,
10141
- providers: JSON.stringify(providers),
10177
+ providers: JSON.stringify(providers ?? []),
10178
+ sourceId: sourceId ?? null,
10142
10179
  enabled: enabledInt,
10143
10180
  updatedAt: now
10144
10181
  }).where(eq16(schedules.id, existing.id)).run();
@@ -10146,11 +10183,13 @@ async function scheduleRoutes(app, opts) {
10146
10183
  app.db.insert(schedules).values({
10147
10184
  id: crypto11.randomUUID(),
10148
10185
  projectId: project.id,
10186
+ kind,
10149
10187
  cronExpr,
10150
10188
  preset: preset ?? null,
10151
10189
  timezone,
10152
10190
  enabled: enabledInt,
10153
- providers: JSON.stringify(providers),
10191
+ providers: JSON.stringify(providers ?? []),
10192
+ sourceId: sourceId ?? null,
10154
10193
  createdAt: now,
10155
10194
  updatedAt: now
10156
10195
  }).run();
@@ -10160,25 +10199,27 @@ async function scheduleRoutes(app, opts) {
10160
10199
  actor: "api",
10161
10200
  action: existing ? "schedule.updated" : "schedule.created",
10162
10201
  entityType: "schedule",
10163
- diff: { cronExpr, preset, timezone, providers }
10202
+ diff: { kind, cronExpr, preset, timezone, providers, sourceId }
10164
10203
  });
10165
- opts.onScheduleUpdated?.("upsert", project.id);
10166
- const schedule = app.db.select().from(schedules).where(eq16(schedules.projectId, project.id)).get();
10204
+ opts.onScheduleUpdated?.("upsert", project.id, kind);
10205
+ const schedule = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
10167
10206
  return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
10168
10207
  });
10169
10208
  app.get("/projects/:name/schedule", async (request, reply) => {
10170
10209
  const project = resolveProject(app.db, request.params.name);
10171
- const schedule = app.db.select().from(schedules).where(eq16(schedules.projectId, project.id)).get();
10210
+ const kind = parseKindParam(request.query?.kind);
10211
+ const schedule = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
10172
10212
  if (!schedule) {
10173
- throw notFound("Schedule", request.params.name);
10213
+ throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
10174
10214
  }
10175
10215
  return reply.send(formatSchedule(schedule));
10176
10216
  });
10177
10217
  app.delete("/projects/:name/schedule", async (request, reply) => {
10178
10218
  const project = resolveProject(app.db, request.params.name);
10179
- const schedule = app.db.select().from(schedules).where(eq16(schedules.projectId, project.id)).get();
10219
+ const kind = parseKindParam(request.query?.kind);
10220
+ const schedule = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
10180
10221
  if (!schedule) {
10181
- throw notFound("Schedule", request.params.name);
10222
+ throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
10182
10223
  }
10183
10224
  app.db.delete(schedules).where(eq16(schedules.id, schedule.id)).run();
10184
10225
  writeAuditLog(app.db, {
@@ -10186,9 +10227,10 @@ async function scheduleRoutes(app, opts) {
10186
10227
  actor: "api",
10187
10228
  action: "schedule.deleted",
10188
10229
  entityType: "schedule",
10189
- entityId: schedule.id
10230
+ entityId: schedule.id,
10231
+ diff: { kind }
10190
10232
  });
10191
- opts.onScheduleUpdated?.("delete", project.id);
10233
+ opts.onScheduleUpdated?.("delete", project.id, kind);
10192
10234
  return reply.status(204).send();
10193
10235
  });
10194
10236
  }
@@ -10196,11 +10238,13 @@ function formatSchedule(row) {
10196
10238
  return {
10197
10239
  id: row.id,
10198
10240
  projectId: row.projectId,
10241
+ kind: row.kind,
10199
10242
  cronExpr: row.cronExpr,
10200
10243
  preset: row.preset,
10201
10244
  timezone: row.timezone,
10202
10245
  enabled: row.enabled === 1,
10203
10246
  providers: parseJsonColumn(row.providers, []),
10247
+ sourceId: row.sourceId,
10204
10248
  lastRunAt: row.lastRunAt,
10205
10249
  nextRunAt: row.nextRunAt,
10206
10250
  createdAt: row.createdAt,
@@ -10329,7 +10373,7 @@ function formatNotification(row) {
10329
10373
 
10330
10374
  // ../api-routes/src/google.ts
10331
10375
  import crypto14 from "crypto";
10332
- import { eq as eq18, and as and6, desc as desc8, sql as sql4 } from "drizzle-orm";
10376
+ import { eq as eq18, and as and8, desc as desc8, sql as sql4 } from "drizzle-orm";
10333
10377
 
10334
10378
  // ../integration-google/src/constants.ts
10335
10379
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
@@ -11544,7 +11588,7 @@ async function googleRoutes(app, opts) {
11544
11588
  if (endDate) conditions.push(sql4`${gscSearchData.date} <= ${endDate}`);
11545
11589
  if (query) conditions.push(sql4`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
11546
11590
  if (page) conditions.push(sql4`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
11547
- const rows = app.db.select().from(gscSearchData).where(and6(...conditions)).orderBy(desc8(gscSearchData.date)).limit(parseInt(limit ?? "500", 10)).all();
11591
+ const rows = app.db.select().from(gscSearchData).where(and8(...conditions)).orderBy(desc8(gscSearchData.date)).limit(parseInt(limit ?? "500", 10)).all();
11548
11592
  return rows.map((r) => ({
11549
11593
  date: r.date,
11550
11594
  query: r.query,
@@ -11618,7 +11662,7 @@ async function googleRoutes(app, opts) {
11618
11662
  const { url, limit } = request.query;
11619
11663
  const conditions = [eq18(gscUrlInspections.projectId, project.id)];
11620
11664
  if (url) conditions.push(eq18(gscUrlInspections.url, url));
11621
- const rows = app.db.select().from(gscUrlInspections).where(and6(...conditions)).orderBy(desc8(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
11665
+ const rows = app.db.select().from(gscUrlInspections).where(and8(...conditions)).orderBy(desc8(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
11622
11666
  return rows.map((r) => ({
11623
11667
  id: r.id,
11624
11668
  url: r.url,
@@ -11970,7 +12014,7 @@ async function googleRoutes(app, opts) {
11970
12014
 
11971
12015
  // ../api-routes/src/bing.ts
11972
12016
  import crypto15 from "crypto";
11973
- import { eq as eq19, and as and7, desc as desc9 } from "drizzle-orm";
12017
+ import { eq as eq19, and as and9, desc as desc9 } from "drizzle-orm";
11974
12018
 
11975
12019
  // ../integration-bing/src/constants.ts
11976
12020
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -12384,7 +12428,7 @@ async function bingRoutes(app, opts) {
12384
12428
  requireConnectionStore();
12385
12429
  const project = resolveProject(app.db, request.params.name);
12386
12430
  const { url, limit } = request.query;
12387
- const whereClause = url ? and7(eq19(bingUrlInspections.projectId, project.id), eq19(bingUrlInspections.url, url)) : eq19(bingUrlInspections.projectId, project.id);
12431
+ const whereClause = url ? and9(eq19(bingUrlInspections.projectId, project.id), eq19(bingUrlInspections.url, url)) : eq19(bingUrlInspections.projectId, project.id);
12388
12432
  const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc9(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
12389
12433
  return filtered.map((r) => ({
12390
12434
  id: r.id,
@@ -12616,7 +12660,7 @@ async function bingRoutes(app, opts) {
12616
12660
  import fs from "fs";
12617
12661
  import path from "path";
12618
12662
  import os2 from "os";
12619
- import { eq as eq20, and as and8 } from "drizzle-orm";
12663
+ import { eq as eq20, and as and10 } from "drizzle-orm";
12620
12664
  function getScreenshotDir() {
12621
12665
  return path.join(os2.homedir(), ".canonry", "screenshots");
12622
12666
  }
@@ -12689,7 +12733,7 @@ async function cdpRoutes(app, opts) {
12689
12733
  async (request, reply) => {
12690
12734
  const project = resolveProject(app.db, request.params.name);
12691
12735
  const { runId } = request.params;
12692
- const run = app.db.select().from(runs).where(and8(eq20(runs.id, runId), eq20(runs.projectId, project.id))).get();
12736
+ const run = app.db.select().from(runs).where(and10(eq20(runs.id, runId), eq20(runs.projectId, project.id))).get();
12693
12737
  if (!run) {
12694
12738
  const err = notFound("Run", runId);
12695
12739
  return reply.code(err.statusCode).send(err.toJSON());
@@ -12786,7 +12830,7 @@ async function cdpRoutes(app, opts) {
12786
12830
 
12787
12831
  // ../api-routes/src/ga.ts
12788
12832
  import crypto16 from "crypto";
12789
- import { eq as eq21, desc as desc10, and as and9, sql as sql5 } from "drizzle-orm";
12833
+ import { eq as eq21, desc as desc10, and as and11, sql as sql5 } from "drizzle-orm";
12790
12834
  function gaLog(level, action, ctx) {
12791
12835
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
12792
12836
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -13081,7 +13125,7 @@ async function ga4Routes(app, opts) {
13081
13125
  app.db.transaction((tx) => {
13082
13126
  if (syncTraffic) {
13083
13127
  tx.delete(gaTrafficSnapshots).where(
13084
- and9(
13128
+ and11(
13085
13129
  eq21(gaTrafficSnapshots.projectId, project.id),
13086
13130
  sql5`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
13087
13131
  sql5`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
@@ -13105,7 +13149,7 @@ async function ga4Routes(app, opts) {
13105
13149
  }
13106
13150
  if (syncAi) {
13107
13151
  tx.delete(gaAiReferrals).where(
13108
- and9(
13152
+ and11(
13109
13153
  eq21(gaAiReferrals.projectId, project.id),
13110
13154
  sql5`${gaAiReferrals.date} >= ${summary.periodStart}`,
13111
13155
  sql5`${gaAiReferrals.date} <= ${summary.periodEnd}`
@@ -13131,7 +13175,7 @@ async function ga4Routes(app, opts) {
13131
13175
  }
13132
13176
  if (syncSocial) {
13133
13177
  tx.delete(gaSocialReferrals).where(
13134
- and9(
13178
+ and11(
13135
13179
  eq21(gaSocialReferrals.projectId, project.id),
13136
13180
  sql5`${gaSocialReferrals.date} >= ${summary.periodStart}`,
13137
13181
  sql5`${gaSocialReferrals.date} <= ${summary.periodEnd}`
@@ -13235,7 +13279,7 @@ async function ga4Routes(app, opts) {
13235
13279
  totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
13236
13280
  totalUsers: gaTrafficWindowSummaries.totalUsers
13237
13281
  }).from(gaTrafficWindowSummaries).where(
13238
- and9(
13282
+ and11(
13239
13283
  eq21(gaTrafficWindowSummaries.projectId, project.id),
13240
13284
  eq21(gaTrafficWindowSummaries.windowKey, window)
13241
13285
  )
@@ -13244,7 +13288,7 @@ async function ga4Routes(app, opts) {
13244
13288
  totalSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
13245
13289
  totalOrganicSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
13246
13290
  totalUsers: sql5`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
13247
- }).from(gaTrafficSnapshots).where(and9(...snapshotConditions)).get() : null;
13291
+ }).from(gaTrafficSnapshots).where(and11(...snapshotConditions)).get() : null;
13248
13292
  const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
13249
13293
  totalSessions: gaTrafficSummaries.totalSessions,
13250
13294
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
@@ -13252,7 +13296,7 @@ async function ga4Routes(app, opts) {
13252
13296
  }).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).get();
13253
13297
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
13254
13298
  totalDirectSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
13255
- }).from(gaTrafficSnapshots).where(and9(...snapshotConditions)).get();
13299
+ }).from(gaTrafficSnapshots).where(and11(...snapshotConditions)).get();
13256
13300
  const summaryMeta = app.db.select({
13257
13301
  periodStart: gaTrafficSummaries.periodStart,
13258
13302
  periodEnd: gaTrafficSummaries.periodEnd
@@ -13263,14 +13307,14 @@ async function ga4Routes(app, opts) {
13263
13307
  organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
13264
13308
  directSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
13265
13309
  users: sql5`SUM(${gaTrafficSnapshots.users})`
13266
- }).from(gaTrafficSnapshots).where(and9(...snapshotConditions)).groupBy(sql5`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql5`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
13310
+ }).from(gaTrafficSnapshots).where(and11(...snapshotConditions)).groupBy(sql5`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql5`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
13267
13311
  const aiReferralRows = app.db.select({
13268
13312
  source: gaAiReferrals.source,
13269
13313
  medium: gaAiReferrals.medium,
13270
13314
  sourceDimension: gaAiReferrals.sourceDimension,
13271
13315
  sessions: sql5`SUM(${gaAiReferrals.sessions})`,
13272
13316
  users: sql5`SUM(${gaAiReferrals.users})`
13273
- }).from(gaAiReferrals).where(and9(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
13317
+ }).from(gaAiReferrals).where(and11(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
13274
13318
  const aiReferralLandingPageRows = app.db.select({
13275
13319
  source: gaAiReferrals.source,
13276
13320
  medium: gaAiReferrals.medium,
@@ -13278,7 +13322,7 @@ async function ga4Routes(app, opts) {
13278
13322
  landingPage: sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
13279
13323
  sessions: sql5`SUM(${gaAiReferrals.sessions})`,
13280
13324
  users: sql5`SUM(${gaAiReferrals.users})`
13281
- }).from(gaAiReferrals).where(and9(...aiConditions)).groupBy(
13325
+ }).from(gaAiReferrals).where(and11(...aiConditions)).groupBy(
13282
13326
  gaAiReferrals.source,
13283
13327
  gaAiReferrals.medium,
13284
13328
  gaAiReferrals.sourceDimension,
@@ -13315,7 +13359,7 @@ async function ga4Routes(app, opts) {
13315
13359
  channelGroup: gaAiReferrals.channelGroup,
13316
13360
  sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
13317
13361
  users: sql5`COALESCE(SUM(${gaAiReferrals.users}), 0)`
13318
- }).from(gaAiReferrals).where(and9(...aiConditions, eq21(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
13362
+ }).from(gaAiReferrals).where(and11(...aiConditions, eq21(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
13319
13363
  const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
13320
13364
  let aiBySessionUsers = 0;
13321
13365
  for (const row of aiBySessionRows) {
@@ -13329,11 +13373,11 @@ async function ga4Routes(app, opts) {
13329
13373
  channelGroup: gaSocialReferrals.channelGroup,
13330
13374
  sessions: sql5`SUM(${gaSocialReferrals.sessions})`,
13331
13375
  users: sql5`SUM(${gaSocialReferrals.users})`
13332
- }).from(gaSocialReferrals).where(and9(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql5`SUM(${gaSocialReferrals.sessions}) DESC`).all();
13376
+ }).from(gaSocialReferrals).where(and11(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql5`SUM(${gaSocialReferrals.sessions}) DESC`).all();
13333
13377
  const socialTotals = app.db.select({
13334
13378
  sessions: sql5`SUM(${gaSocialReferrals.sessions})`,
13335
13379
  users: sql5`SUM(${gaSocialReferrals.users})`
13336
- }).from(gaSocialReferrals).where(and9(...socialConditions)).get();
13380
+ }).from(gaSocialReferrals).where(and11(...socialConditions)).get();
13337
13381
  const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).orderBy(desc10(gaTrafficSummaries.syncedAt)).limit(1).get();
13338
13382
  const total = summaryRow?.totalSessions ?? 0;
13339
13383
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
@@ -13424,7 +13468,7 @@ async function ga4Routes(app, opts) {
13424
13468
  sourceDimension: gaAiReferrals.sourceDimension,
13425
13469
  sessions: sql5`SUM(${gaAiReferrals.sessions})`,
13426
13470
  users: sql5`SUM(${gaAiReferrals.users})`
13427
- }).from(gaAiReferrals).where(and9(...conditions)).groupBy(
13471
+ }).from(gaAiReferrals).where(and11(...conditions)).groupBy(
13428
13472
  gaAiReferrals.date,
13429
13473
  gaAiReferrals.source,
13430
13474
  gaAiReferrals.medium,
@@ -13446,7 +13490,7 @@ async function ga4Routes(app, opts) {
13446
13490
  channelGroup: gaSocialReferrals.channelGroup,
13447
13491
  sessions: gaSocialReferrals.sessions,
13448
13492
  users: gaSocialReferrals.users
13449
- }).from(gaSocialReferrals).where(and9(...conditions)).orderBy(gaSocialReferrals.date).all();
13493
+ }).from(gaSocialReferrals).where(and11(...conditions)).orderBy(gaSocialReferrals.date).all();
13450
13494
  return rows;
13451
13495
  });
13452
13496
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -13459,7 +13503,7 @@ async function ga4Routes(app, opts) {
13459
13503
  d.setDate(d.getDate() - n);
13460
13504
  return fmt(d);
13461
13505
  };
13462
- const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and9(
13506
+ const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and11(
13463
13507
  eq21(gaSocialReferrals.projectId, project.id),
13464
13508
  sql5`${gaSocialReferrals.date} >= ${from}`,
13465
13509
  sql5`${gaSocialReferrals.date} < ${to}`
@@ -13472,7 +13516,7 @@ async function ga4Routes(app, opts) {
13472
13516
  const sourceCurrent = app.db.select({
13473
13517
  source: gaSocialReferrals.source,
13474
13518
  sessions: sql5`SUM(${gaSocialReferrals.sessions})`
13475
- }).from(gaSocialReferrals).where(and9(
13519
+ }).from(gaSocialReferrals).where(and11(
13476
13520
  eq21(gaSocialReferrals.projectId, project.id),
13477
13521
  sql5`${gaSocialReferrals.date} >= ${daysAgo2(7)}`,
13478
13522
  sql5`${gaSocialReferrals.date} < ${fmt(today)}`
@@ -13480,7 +13524,7 @@ async function ga4Routes(app, opts) {
13480
13524
  const sourcePrev = app.db.select({
13481
13525
  source: gaSocialReferrals.source,
13482
13526
  sessions: sql5`SUM(${gaSocialReferrals.sessions})`
13483
- }).from(gaSocialReferrals).where(and9(
13527
+ }).from(gaSocialReferrals).where(and11(
13484
13528
  eq21(gaSocialReferrals.projectId, project.id),
13485
13529
  sql5`${gaSocialReferrals.date} >= ${daysAgo2(14)}`,
13486
13530
  sql5`${gaSocialReferrals.date} < ${daysAgo2(7)}`
@@ -13522,16 +13566,16 @@ async function ga4Routes(app, opts) {
13522
13566
  return fmt(d);
13523
13567
  };
13524
13568
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
13525
- const sumTotal = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and9(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
13526
- const sumOrganic = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and9(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
13527
- const sumDirect = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and9(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
13528
- const sumAi = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and9(
13569
+ const sumTotal = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and11(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
13570
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and11(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
13571
+ const sumDirect = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and11(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
13572
+ const sumAi = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and11(
13529
13573
  eq21(gaAiReferrals.projectId, project.id),
13530
13574
  sql5`${gaAiReferrals.date} >= ${from}`,
13531
13575
  sql5`${gaAiReferrals.date} < ${to}`,
13532
13576
  eq21(gaAiReferrals.sourceDimension, "session")
13533
13577
  )).get();
13534
- const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and9(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${from}`, sql5`${gaSocialReferrals.date} < ${to}`)).get();
13578
+ const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and11(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${from}`, sql5`${gaSocialReferrals.date} < ${to}`)).get();
13535
13579
  const todayStr = fmt(today);
13536
13580
  const buildTrend = (sum) => {
13537
13581
  const c7 = sum(daysAgo2(7), todayStr)?.sessions ?? 0;
@@ -13540,13 +13584,13 @@ async function ga4Routes(app, opts) {
13540
13584
  const p30 = sum(daysAgo2(60), daysAgo2(30))?.sessions ?? 0;
13541
13585
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
13542
13586
  };
13543
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and9(
13587
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and11(
13544
13588
  eq21(gaAiReferrals.projectId, project.id),
13545
13589
  sql5`${gaAiReferrals.date} >= ${daysAgo2(7)}`,
13546
13590
  sql5`${gaAiReferrals.date} < ${todayStr}`,
13547
13591
  eq21(gaAiReferrals.sourceDimension, "session")
13548
13592
  )).groupBy(gaAiReferrals.source).all();
13549
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and9(
13593
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and11(
13550
13594
  eq21(gaAiReferrals.projectId, project.id),
13551
13595
  sql5`${gaAiReferrals.date} >= ${daysAgo2(14)}`,
13552
13596
  sql5`${gaAiReferrals.date} < ${daysAgo2(7)}`,
@@ -13566,8 +13610,8 @@ async function ga4Routes(app, opts) {
13566
13610
  }
13567
13611
  return mover;
13568
13612
  };
13569
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and9(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql5`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
13570
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and9(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql5`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
13613
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and11(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql5`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
13614
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and11(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql5`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
13571
13615
  return {
13572
13616
  total: buildTrend(sumTotal),
13573
13617
  organic: buildTrend(sumOrganic),
@@ -13589,7 +13633,7 @@ async function ga4Routes(app, opts) {
13589
13633
  sessions: sql5`SUM(${gaTrafficSnapshots.sessions})`,
13590
13634
  organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
13591
13635
  users: sql5`SUM(${gaTrafficSnapshots.users})`
13592
- }).from(gaTrafficSnapshots).where(and9(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
13636
+ }).from(gaTrafficSnapshots).where(and11(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
13593
13637
  return rows.map((r) => ({
13594
13638
  date: r.date,
13595
13639
  sessions: r.sessions ?? 0,
@@ -15242,7 +15286,7 @@ async function wordpressRoutes(app, opts) {
15242
15286
 
15243
15287
  // ../api-routes/src/backlinks.ts
15244
15288
  import crypto18 from "crypto";
15245
- import { and as and11, asc as asc2, desc as desc11, eq as eq22, sql as sql6 } from "drizzle-orm";
15289
+ import { and as and13, asc as asc2, desc as desc11, eq as eq22, sql as sql6 } from "drizzle-orm";
15246
15290
 
15247
15291
  // ../integration-commoncrawl/src/constants.ts
15248
15292
  import os3 from "os";
@@ -15639,7 +15683,7 @@ function pruneCachedRelease(release, opts = {}) {
15639
15683
  }
15640
15684
 
15641
15685
  // ../api-routes/src/backlinks-filter.ts
15642
- import { and as and10, ne, notLike } from "drizzle-orm";
15686
+ import { and as and12, ne, notLike } from "drizzle-orm";
15643
15687
  var BACKLINK_FILTER_PATTERNS = [
15644
15688
  "*.google.com",
15645
15689
  "*.googleusercontent.com",
@@ -15662,7 +15706,7 @@ function backlinkCrawlerExclusionClause() {
15662
15706
  conditions.push(ne(backlinkDomains.linkingDomain, pattern));
15663
15707
  }
15664
15708
  }
15665
- const combined = and10(...conditions);
15709
+ const combined = and12(...conditions);
15666
15710
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
15667
15711
  return combined;
15668
15712
  }
@@ -15723,7 +15767,7 @@ function mapRunRow(row) {
15723
15767
  };
15724
15768
  }
15725
15769
  function latestSummaryForProject(db, projectId, release) {
15726
- const condition = release ? and11(eq22(backlinkSummaries.projectId, projectId), eq22(backlinkSummaries.release, release)) : eq22(backlinkSummaries.projectId, projectId);
15770
+ const condition = release ? and13(eq22(backlinkSummaries.projectId, projectId), eq22(backlinkSummaries.release, release)) : eq22(backlinkSummaries.projectId, projectId);
15727
15771
  return db.select().from(backlinkSummaries).where(condition).orderBy(desc11(backlinkSummaries.queriedAt)).limit(1).get();
15728
15772
  }
15729
15773
  function parseExcludeCrawlers(value) {
@@ -15732,11 +15776,11 @@ function parseExcludeCrawlers(value) {
15732
15776
  return lower === "1" || lower === "true" || lower === "yes";
15733
15777
  }
15734
15778
  function computeFilteredSummary(db, base) {
15735
- const baseDomainCondition = and11(
15779
+ const baseDomainCondition = and13(
15736
15780
  eq22(backlinkDomains.projectId, base.projectId),
15737
15781
  eq22(backlinkDomains.release, base.release)
15738
15782
  );
15739
- const filteredCondition = and11(baseDomainCondition, backlinkCrawlerExclusionClause());
15783
+ const filteredCondition = and13(baseDomainCondition, backlinkCrawlerExclusionClause());
15740
15784
  const unfilteredAgg = db.select({
15741
15785
  count: sql6`count(*)`,
15742
15786
  total: sql6`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -15912,11 +15956,11 @@ async function backlinksRoutes(app, opts) {
15912
15956
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
15913
15957
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
15914
15958
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
15915
- const baseDomainCondition = and11(
15959
+ const baseDomainCondition = and13(
15916
15960
  eq22(backlinkDomains.projectId, project.id),
15917
15961
  eq22(backlinkDomains.release, targetRelease)
15918
15962
  );
15919
- const domainCondition = excludeCrawlers ? and11(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
15963
+ const domainCondition = excludeCrawlers ? and13(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
15920
15964
  const totalRow = app.db.select({ count: sql6`count(*)` }).from(backlinkDomains).where(domainCondition).get();
15921
15965
  const rows = app.db.select({
15922
15966
  linkingDomain: backlinkDomains.linkingDomain,
@@ -15952,7 +15996,7 @@ async function backlinksRoutes(app, opts) {
15952
15996
 
15953
15997
  // ../api-routes/src/traffic.ts
15954
15998
  import crypto20 from "crypto";
15955
- import { and as and12, desc as desc12, eq as eq23, gte, lte, sql as sql7 } from "drizzle-orm";
15999
+ import { and as and14, desc as desc12, eq as eq23, gte, lte, sql as sql7 } from "drizzle-orm";
15956
16000
 
15957
16001
  // ../integration-cloud-run/src/auth.ts
15958
16002
  import crypto19 from "crypto";
@@ -16909,25 +16953,25 @@ async function trafficRoutes(app, opts) {
16909
16953
  });
16910
16954
  function buildSourceDetail(projectId, row, since) {
16911
16955
  const crawlerTotals = app.db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
16912
- and12(
16956
+ and14(
16913
16957
  eq23(crawlerEventsHourly.sourceId, row.id),
16914
16958
  gte(crawlerEventsHourly.tsHour, since)
16915
16959
  )
16916
16960
  ).get();
16917
16961
  const aiTotals = app.db.select({ total: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
16918
- and12(
16962
+ and14(
16919
16963
  eq23(aiReferralEventsHourly.sourceId, row.id),
16920
16964
  gte(aiReferralEventsHourly.tsHour, since)
16921
16965
  )
16922
16966
  ).get();
16923
16967
  const sampleTotals = app.db.select({ total: sql7`COUNT(*)` }).from(rawEventSamples).where(
16924
- and12(
16968
+ and14(
16925
16969
  eq23(rawEventSamples.sourceId, row.id),
16926
16970
  gte(rawEventSamples.ts, since)
16927
16971
  )
16928
16972
  ).get();
16929
16973
  const latestRun = app.db.select().from(runs).where(
16930
- and12(
16974
+ and14(
16931
16975
  eq23(runs.projectId, projectId),
16932
16976
  eq23(runs.kind, RunKinds["traffic-sync"]),
16933
16977
  eq23(runs.sourceId, row.id)
@@ -17021,7 +17065,7 @@ async function trafficRoutes(app, opts) {
17021
17065
  lte(crawlerEventsHourly.tsHour, untilIso)
17022
17066
  ];
17023
17067
  if (sourceIdParam) crawlerFilters.push(eq23(crawlerEventsHourly.sourceId, sourceIdParam));
17024
- const crawlerWhere = and12(...crawlerFilters);
17068
+ const crawlerWhere = and14(...crawlerFilters);
17025
17069
  const total = app.db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
17026
17070
  crawlerTotal = Number(total?.total ?? 0);
17027
17071
  const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc12(crawlerEventsHourly.tsHour)).limit(limit).all();
@@ -17046,7 +17090,7 @@ async function trafficRoutes(app, opts) {
17046
17090
  lte(aiReferralEventsHourly.tsHour, untilIso)
17047
17091
  ];
17048
17092
  if (sourceIdParam) aiFilters.push(eq23(aiReferralEventsHourly.sourceId, sourceIdParam));
17049
- const aiWhere = and12(...aiFilters);
17093
+ const aiWhere = and14(...aiFilters);
17050
17094
  const total = app.db.select({ total: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
17051
17095
  aiReferralTotal = Number(total?.total ?? 0);
17052
17096
  const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc12(aiReferralEventsHourly.tsHour)).limit(limit).all();
@@ -20449,7 +20493,7 @@ import crypto22 from "crypto";
20449
20493
  import fs7 from "fs";
20450
20494
  import path9 from "path";
20451
20495
  import os5 from "os";
20452
- import { and as and13, eq as eq24, inArray as inArray7, sql as sql8 } from "drizzle-orm";
20496
+ import { and as and15, eq as eq24, inArray as inArray7, sql as sql8 } from "drizzle-orm";
20453
20497
 
20454
20498
  // src/run-telemetry.ts
20455
20499
  import crypto21 from "crypto";
@@ -20828,7 +20872,7 @@ var JobRunner = class {
20828
20872
  throw new Error(`Run ${runId} is not executable from status '${existingRun.status}'`);
20829
20873
  }
20830
20874
  if (existingRun.status === "queued") {
20831
- this.db.update(runs).set({ status: "running", startedAt: now }).where(and13(eq24(runs.id, runId), eq24(runs.status, "queued"))).run();
20875
+ this.db.update(runs).set({ status: "running", startedAt: now }).where(and15(eq24(runs.id, runId), eq24(runs.status, "queued"))).run();
20832
20876
  }
20833
20877
  this.throwIfRunCancelled(runId);
20834
20878
  const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
@@ -21187,7 +21231,7 @@ function buildPhases(input) {
21187
21231
 
21188
21232
  // src/gsc-sync.ts
21189
21233
  import crypto23 from "crypto";
21190
- import { eq as eq25, and as and14, sql as sql9 } from "drizzle-orm";
21234
+ import { eq as eq25, and as and16, sql as sql9 } from "drizzle-orm";
21191
21235
  var log2 = createLogger("GscSync");
21192
21236
  function formatDate3(d) {
21193
21237
  return d.toISOString().split("T")[0];
@@ -21239,7 +21283,7 @@ async function executeGscSync(db, runId, projectId, opts) {
21239
21283
  });
21240
21284
  log2.info("fetch.complete", { runId, projectId, rowCount: rows.length });
21241
21285
  db.delete(gscSearchData).where(
21242
- and14(
21286
+ and16(
21243
21287
  eq25(gscSearchData.projectId, projectId),
21244
21288
  sql9`${gscSearchData.date} >= ${startDate}`,
21245
21289
  sql9`${gscSearchData.date} <= ${endDate}`
@@ -21328,7 +21372,7 @@ async function executeGscSync(db, runId, projectId, opts) {
21328
21372
  }
21329
21373
  }
21330
21374
  const snapshotDate = formatDate3(/* @__PURE__ */ new Date());
21331
- db.delete(gscCoverageSnapshots).where(and14(eq25(gscCoverageSnapshots.projectId, projectId), eq25(gscCoverageSnapshots.date, snapshotDate))).run();
21375
+ db.delete(gscCoverageSnapshots).where(and16(eq25(gscCoverageSnapshots.projectId, projectId), eq25(gscCoverageSnapshots.date, snapshotDate))).run();
21332
21376
  db.insert(gscCoverageSnapshots).values({
21333
21377
  id: crypto23.randomUUID(),
21334
21378
  projectId,
@@ -21351,7 +21395,7 @@ async function executeGscSync(db, runId, projectId, opts) {
21351
21395
 
21352
21396
  // src/gsc-inspect-sitemap.ts
21353
21397
  import crypto24 from "crypto";
21354
- import { eq as eq26, and as and15 } from "drizzle-orm";
21398
+ import { eq as eq26, and as and17 } from "drizzle-orm";
21355
21399
 
21356
21400
  // src/sitemap-parser.ts
21357
21401
  var log3 = createLogger("SitemapParser");
@@ -21567,7 +21611,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
21567
21611
  }
21568
21612
  }
21569
21613
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
21570
- db.delete(gscCoverageSnapshots).where(and15(eq26(gscCoverageSnapshots.projectId, projectId), eq26(gscCoverageSnapshots.date, snapshotDate))).run();
21614
+ db.delete(gscCoverageSnapshots).where(and17(eq26(gscCoverageSnapshots.projectId, projectId), eq26(gscCoverageSnapshots.date, snapshotDate))).run();
21571
21615
  db.insert(gscCoverageSnapshots).values({
21572
21616
  id: crypto24.randomUUID(),
21573
21617
  projectId,
@@ -21778,7 +21822,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
21778
21822
  // src/commoncrawl-sync.ts
21779
21823
  import crypto26 from "crypto";
21780
21824
  import path10 from "path";
21781
- import { and as and16, eq as eq28, sql as sql10 } from "drizzle-orm";
21825
+ import { and as and18, eq as eq28, sql as sql10 } from "drizzle-orm";
21782
21826
  var log6 = createLogger("CommonCrawlSync");
21783
21827
  var INSERT_CHUNK_SIZE = 1e4;
21784
21828
  function defaultDeps() {
@@ -21969,7 +22013,7 @@ function computeSummary(rows) {
21969
22013
  // src/backlink-extract.ts
21970
22014
  import crypto27 from "crypto";
21971
22015
  import fs8 from "fs";
21972
- import { and as and17, desc as desc14, eq as eq29 } from "drizzle-orm";
22016
+ import { and as and19, desc as desc14, eq as eq29 } from "drizzle-orm";
21973
22017
  var log7 = createLogger("BacklinkExtract");
21974
22018
  function defaultDeps2() {
21975
22019
  return {
@@ -22015,7 +22059,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
22015
22059
  const targetDomain = project.canonicalDomain;
22016
22060
  db.transaction((tx) => {
22017
22061
  tx.delete(backlinkDomains).where(
22018
- and17(eq29(backlinkDomains.projectId, projectId), eq29(backlinkDomains.release, release))
22062
+ and19(eq29(backlinkDomains.projectId, projectId), eq29(backlinkDomains.release, release))
22019
22063
  ).run();
22020
22064
  if (rows.length > 0) {
22021
22065
  const values = rows.map((r) => ({
@@ -22137,8 +22181,11 @@ var ProviderRegistry = class {
22137
22181
 
22138
22182
  // src/scheduler.ts
22139
22183
  import cron from "node-cron";
22140
- import { eq as eq30 } from "drizzle-orm";
22184
+ import { and as and20, eq as eq30 } from "drizzle-orm";
22141
22185
  var log8 = createLogger("Scheduler");
22186
+ function taskKey(projectId, kind) {
22187
+ return `${projectId}::${kind}`;
22188
+ }
22142
22189
  var Scheduler = class {
22143
22190
  db;
22144
22191
  callbacks;
@@ -22154,78 +22201,110 @@ var Scheduler = class {
22154
22201
  const missedRunAt = schedule.nextRunAt;
22155
22202
  this.registerCronTask(schedule);
22156
22203
  if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
22157
- log8.info("run.catch-up", { projectId: schedule.projectId, missedRunAt });
22158
- this.triggerRun(schedule.id, schedule.projectId);
22204
+ log8.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
22205
+ this.triggerRun(schedule.id, schedule.projectId, schedule.kind);
22159
22206
  }
22160
22207
  }
22161
22208
  log8.info("started", { scheduleCount: allSchedules.length });
22162
22209
  }
22163
22210
  /** Stop all cron tasks for graceful shutdown. */
22164
22211
  stop() {
22165
- for (const [projectId, task] of this.tasks) {
22166
- this.stopTask(projectId, task, "Stopped");
22212
+ for (const [key, task] of this.tasks) {
22213
+ this.stopTask(key, task, "Stopped");
22167
22214
  }
22168
22215
  this.tasks.clear();
22169
22216
  }
22170
- /** Add or update a cron registration at runtime (called when schedule API is used). */
22171
- upsert(projectId) {
22172
- const existing = this.tasks.get(projectId);
22217
+ /**
22218
+ * Add or update a cron registration at runtime (called when schedule API
22219
+ * is used). Keyed by `(projectId, kind)` so a project's traffic-sync and
22220
+ * answer-visibility schedules can coexist independently.
22221
+ */
22222
+ upsert(projectId, kind) {
22223
+ const key = taskKey(projectId, kind);
22224
+ const existing = this.tasks.get(key);
22173
22225
  if (existing) {
22174
- this.stopTask(projectId, existing, "Stopped");
22175
- this.tasks.delete(projectId);
22226
+ this.stopTask(key, existing, "Stopped");
22227
+ this.tasks.delete(key);
22176
22228
  }
22177
- const schedule = this.db.select().from(schedules).where(eq30(schedules.projectId, projectId)).get();
22229
+ const schedule = this.db.select().from(schedules).where(and20(eq30(schedules.projectId, projectId), eq30(schedules.kind, kind))).get();
22178
22230
  if (schedule && schedule.enabled === 1) {
22179
22231
  this.registerCronTask(schedule);
22180
22232
  }
22181
22233
  }
22182
- /** Remove a cron registration (called when schedule is deleted). */
22183
- remove(projectId) {
22184
- const existing = this.tasks.get(projectId);
22234
+ /** Remove a single cron registration (kind-scoped). */
22235
+ remove(projectId, kind) {
22236
+ const key = taskKey(projectId, kind);
22237
+ const existing = this.tasks.get(key);
22185
22238
  if (existing) {
22186
- this.stopTask(projectId, existing, "Removed");
22187
- this.tasks.delete(projectId);
22239
+ this.stopTask(key, existing, "Removed");
22240
+ this.tasks.delete(key);
22188
22241
  }
22189
22242
  }
22190
- stopTask(projectId, task, verb) {
22243
+ /** Remove ALL cron registrations for a project (used on project delete). */
22244
+ removeAllForProject(projectId) {
22245
+ for (const kind of Object.values(SchedulableRunKinds)) {
22246
+ this.remove(projectId, kind);
22247
+ }
22248
+ }
22249
+ stopTask(key, task, verb) {
22191
22250
  task.stop();
22192
22251
  task.destroy();
22193
- log8.info(`task.${verb.toLowerCase()}`, { projectId });
22252
+ log8.info(`task.${verb.toLowerCase()}`, { key });
22194
22253
  }
22195
22254
  registerCronTask(schedule) {
22196
22255
  const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
22256
+ const kind = schedule.kind;
22197
22257
  if (!cron.validate(cronExpr)) {
22198
- log8.error("cron.invalid", { projectId, cronExpr });
22258
+ log8.error("cron.invalid", { projectId, kind, cronExpr });
22199
22259
  return;
22200
22260
  }
22201
22261
  const task = cron.schedule(cronExpr, () => {
22202
- this.triggerRun(scheduleId, projectId);
22262
+ this.triggerRun(scheduleId, projectId, kind);
22203
22263
  }, {
22204
22264
  timezone
22205
22265
  });
22206
- this.tasks.set(projectId, task);
22266
+ this.tasks.set(taskKey(projectId, kind), task);
22207
22267
  this.db.update(schedules).set({
22208
22268
  nextRunAt: task.getNextRun()?.toISOString() ?? null,
22209
22269
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
22210
22270
  }).where(eq30(schedules.id, scheduleId)).run();
22211
22271
  const label = schedule.preset ?? cronExpr;
22212
- log8.info("cron.registered", { projectId, schedule: label, timezone });
22272
+ log8.info("cron.registered", { projectId, kind, schedule: label, timezone });
22213
22273
  }
22214
- triggerRun(scheduleId, projectId) {
22274
+ triggerRun(scheduleId, projectId, kind) {
22215
22275
  try {
22216
22276
  const now = (/* @__PURE__ */ new Date()).toISOString();
22217
22277
  const currentSchedule = this.db.select().from(schedules).where(eq30(schedules.id, scheduleId)).get();
22218
22278
  if (!currentSchedule || currentSchedule.enabled !== 1) {
22219
- log8.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
22220
- this.remove(projectId);
22279
+ log8.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
22280
+ this.remove(projectId, kind);
22221
22281
  return;
22222
22282
  }
22223
- const task = this.tasks.get(projectId);
22283
+ const task = this.tasks.get(taskKey(projectId, kind));
22224
22284
  const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
22225
22285
  const project = this.db.select().from(projects).where(eq30(projects.id, projectId)).get();
22226
22286
  if (!project) {
22227
- log8.error("project.not-found", { projectId, msg: "skipping scheduled run" });
22228
- this.remove(projectId);
22287
+ log8.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
22288
+ this.remove(projectId, kind);
22289
+ return;
22290
+ }
22291
+ if (kind === SchedulableRunKinds["traffic-sync"]) {
22292
+ const sourceId = currentSchedule.sourceId;
22293
+ if (!sourceId) {
22294
+ log8.warn("traffic-sync.missing-source", { scheduleId, projectId });
22295
+ return;
22296
+ }
22297
+ if (!this.callbacks.onTrafficSyncRequested) {
22298
+ log8.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
22299
+ return;
22300
+ }
22301
+ this.db.update(schedules).set({
22302
+ lastRunAt: now,
22303
+ nextRunAt,
22304
+ updatedAt: now
22305
+ }).where(eq30(schedules.id, currentSchedule.id)).run();
22306
+ log8.info("traffic-sync.triggered", { projectName: project.name, sourceId });
22307
+ this.callbacks.onTrafficSyncRequested(project.name, sourceId);
22229
22308
  return;
22230
22309
  }
22231
22310
  const projectLocations = parseJsonColumn(project.locations, []);
@@ -22265,13 +22344,13 @@ var Scheduler = class {
22265
22344
  log8.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
22266
22345
  this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
22267
22346
  } catch (err) {
22268
- log8.error("trigger.error", { scheduleId, projectId, error: err instanceof Error ? err.message : String(err) });
22347
+ log8.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
22269
22348
  }
22270
22349
  }
22271
22350
  };
22272
22351
 
22273
22352
  // src/notifier.ts
22274
- import { eq as eq31, desc as desc15, and as and18, or as or4 } from "drizzle-orm";
22353
+ import { eq as eq31, desc as desc15, and as and21, or as or4 } from "drizzle-orm";
22275
22354
  import crypto28 from "crypto";
22276
22355
  var log9 = createLogger("Notifier");
22277
22356
  var Notifier = class {
@@ -22377,7 +22456,7 @@ var Notifier = class {
22377
22456
  }
22378
22457
  computeTransitions(runId, projectId) {
22379
22458
  const recentRuns = this.db.select().from(runs).where(
22380
- and18(
22459
+ and21(
22381
22460
  eq31(runs.projectId, projectId),
22382
22461
  or4(eq31(runs.status, "completed"), eq31(runs.status, "partial"))
22383
22462
  )
@@ -22863,7 +22942,7 @@ function resolveSessionProviderAndModel(config, opts) {
22863
22942
 
22864
22943
  // src/agent/memory-store.ts
22865
22944
  import crypto29 from "crypto";
22866
- import { and as and19, desc as desc16, eq as eq32, like as like2, sql as sql11 } from "drizzle-orm";
22945
+ import { and as and22, desc as desc16, eq as eq32, like as like2, sql as sql11 } from "drizzle-orm";
22867
22946
  var COMPACTION_KEY_PREFIX = "compaction:";
22868
22947
  var COMPACTION_NOTES_PER_SESSION = 3;
22869
22948
  function rowToDto2(row) {
@@ -22908,12 +22987,12 @@ function upsertMemoryEntry(db, args) {
22908
22987
  updatedAt: now
22909
22988
  }
22910
22989
  }).run();
22911
- const row = db.select().from(agentMemory).where(and19(eq32(agentMemory.projectId, args.projectId), eq32(agentMemory.key, args.key))).get();
22990
+ const row = db.select().from(agentMemory).where(and22(eq32(agentMemory.projectId, args.projectId), eq32(agentMemory.key, args.key))).get();
22912
22991
  if (!row) throw new Error("memory upsert produced no row");
22913
22992
  return rowToDto2(row);
22914
22993
  }
22915
22994
  function deleteMemoryEntry(db, projectId, key) {
22916
- const result = db.delete(agentMemory).where(and19(eq32(agentMemory.projectId, projectId), eq32(agentMemory.key, key))).run();
22995
+ const result = db.delete(agentMemory).where(and22(eq32(agentMemory.projectId, projectId), eq32(agentMemory.key, key))).run();
22917
22996
  const changes = result.changes ?? 0;
22918
22997
  return changes > 0;
22919
22998
  }
@@ -22942,7 +23021,7 @@ function writeCompactionNote(db, args) {
22942
23021
  }).run();
22943
23022
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
22944
23023
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
22945
- and19(
23024
+ and22(
22946
23025
  eq32(agentMemory.projectId, args.projectId),
22947
23026
  like2(agentMemory.key, `${sessionPrefix}%`)
22948
23027
  )
@@ -22951,7 +23030,7 @@ function writeCompactionNote(db, args) {
22951
23030
  if (stale.length > 0) {
22952
23031
  tx.delete(agentMemory).where(sql11`${agentMemory.id} IN (${sql11.join(stale.map((s) => sql11`${s}`), sql11`, `)})`).run();
22953
23032
  }
22954
- const row = tx.select().from(agentMemory).where(and19(eq32(agentMemory.projectId, args.projectId), eq32(agentMemory.key, key))).get();
23033
+ const row = tx.select().from(agentMemory).where(and22(eq32(agentMemory.projectId, args.projectId), eq32(agentMemory.key, key))).get();
22955
23034
  if (row) inserted = rowToDto2(row);
22956
23035
  });
22957
23036
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -24618,6 +24697,11 @@ async function createServer(opts) {
24618
24697
  jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
24619
24698
  app.log.error({ runId, err }, "Scheduled job runner failed");
24620
24699
  });
24700
+ },
24701
+ onTrafficSyncRequested: (projectName, sourceId) => {
24702
+ aeroClient.trafficSync(projectName, sourceId).catch((err) => {
24703
+ app.log.error({ projectName, sourceId, err: err instanceof Error ? err.message : String(err) }, "Scheduled traffic sync failed");
24704
+ });
24621
24705
  }
24622
24706
  });
24623
24707
  const providerSummary = API_ADAPTERS.map((adapter) => ({
@@ -25149,12 +25233,12 @@ async function createServer(opts) {
25149
25233
  return null;
25150
25234
  }
25151
25235
  },
25152
- onScheduleUpdated: (action, projectId) => {
25153
- if (action === "upsert") scheduler.upsert(projectId);
25154
- if (action === "delete") scheduler.remove(projectId);
25236
+ onScheduleUpdated: (action, projectId, kind) => {
25237
+ if (action === "upsert") scheduler.upsert(projectId, kind);
25238
+ if (action === "delete") scheduler.remove(projectId, kind);
25155
25239
  },
25156
25240
  onProjectDeleted: (projectId) => {
25157
- scheduler.remove(projectId);
25241
+ scheduler.removeAllForProject(projectId);
25158
25242
  },
25159
25243
  getTelemetryStatus: () => {
25160
25244
  const enabled = isTelemetryEnabled();