@canonry/canonry 7.43.2 → 7.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/assets/agent-workspace/skills/aero/references/agent-operations.md +9 -0
  2. package/assets/agent-workspace/skills/aero/references/site-health.md +12 -0
  3. package/assets/agent-workspace/skills/canonry/SKILL.md +9 -0
  4. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +3 -0
  5. package/assets/assets/{AuditHistoryPanel-C5oZsvsR.js → AuditHistoryPanel-CvANw3ds.js} +1 -1
  6. package/assets/assets/{BacklinksPage-CF5sGSTQ.js → BacklinksPage-C6x9ELjk.js} +1 -1
  7. package/assets/assets/{HistoryPage-DumL6cl5.js → HistoryPage-r36HX1cC.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-DCegOp_w.js → MeasurementPropertyPage-ComoCzR4.js} +1 -1
  9. package/assets/assets/{ProjectPage-CWDk48Y6.js → ProjectPage-CIeU-buk.js} +1 -1
  10. package/assets/assets/{RunsPage-Bb1VMWYu.js → RunsPage-DI6eJATZ.js} +1 -1
  11. package/assets/assets/{SettingsPage-CHGh169l.js → SettingsPage-Bw7GdXxs.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-bGzH9m9-.js → SiteHealthSection-C-JaNKpS.js} +3 -3
  13. package/assets/assets/{TrafficPage-cfnEZ2Mu.js → TrafficPage-Cnsp276v.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-BQS0MAXE.js → TrafficSourceDetailPage-CBIaJk7e.js} +1 -1
  15. package/assets/assets/{extract-error-message-IWhACkO_.js → extract-error-message-C_Owa0n9.js} +1 -1
  16. package/assets/assets/{index-BDY3-V1S.js → index-ZS9sSTMS.js} +20 -20
  17. package/assets/assets/{react-sigma_core.esm.min-BQt189AI.js → react-sigma_core.esm.min-ugyh-ooq.js} +1 -1
  18. package/assets/assets/{v2-overview-adapter-ZC3VuoTT.js → v2-overview-adapter-BLSrVQ7K.js} +1 -1
  19. package/assets/index.html +1 -1
  20. package/dist/{chunk-WU2BRWXY.js → chunk-3JVF3ULD.js} +5 -1
  21. package/dist/{chunk-WRUKKEGR.js → chunk-45Z5RJZW.js} +3 -3
  22. package/dist/{chunk-KOIOZ7QW.js → chunk-5LY7EG4U.js} +47 -3
  23. package/dist/{chunk-C2I7M3B2.js → chunk-EJRV7RBZ.js} +127 -9
  24. package/dist/{chunk-65BHDVYO.js → chunk-G47XJVAS.js} +830 -465
  25. package/dist/{chunk-E43JM3QV.js → chunk-GZ2KIONF.js} +92 -1
  26. package/dist/{chunk-BWR5MNL5.js → chunk-JVBI6VBT.js} +2 -2
  27. package/dist/{chunk-LYKDNJXR.js → chunk-MPWBFKGK.js} +44 -1
  28. package/dist/{chunk-WXX3NL4C.js → chunk-QQYQDTFZ.js} +40 -12
  29. package/dist/{chunk-BPFRGC7K.js → chunk-SW54HXSZ.js} +3 -3
  30. package/dist/{cli-catalog-J3O6W726.js → cli-catalog-PNJEQV5H.js} +3 -3
  31. package/dist/cli-commands-4UT2CRXX.js +15 -0
  32. package/dist/cli.js +10 -10
  33. package/dist/{demo-server-KA7DWTY6.js → demo-server-WRHR25HF.js} +4 -4
  34. package/dist/index.js +6 -6
  35. package/dist/{intelligence-service-LHZZTJPH.js → intelligence-service-QXXRUB2G.js} +2 -2
  36. package/dist/mcp.js +4 -4
  37. package/package.json +9 -9
  38. package/dist/cli-commands-QST2CPDG.js +0 -15
@@ -154,6 +154,7 @@ import {
154
154
  SENTIMENT_INTERVAL_LIMITATION,
155
155
  SENTIMENT_MODEL,
156
156
  SENTIMENT_QUERY_PAGE_DEFAULT,
157
+ SITE_AUDIT_ISSUES_DEFAULT_LIMIT,
157
158
  SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES,
158
159
  SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES,
159
160
  SITE_CRAWL_GRAPH_MAX_EDGES,
@@ -808,6 +809,9 @@ import {
808
809
  shiftIsoCalendarDate,
809
810
  simpleMeasurementCompetitorNames,
810
811
  simpleMeasurementDefinitionSchema,
812
+ siteAuditIssueListQuerySchema,
813
+ siteAuditIssuePagesResponseSchema,
814
+ siteAuditIssuesResponseSchema,
811
815
  siteAuditLivePageHealthSchema,
812
816
  siteAuditPageFactorSchema,
813
817
  siteAuditPagesResponseSchema,
@@ -818,10 +822,12 @@ import {
818
822
  siteAuditScoreSchema,
819
823
  siteAuditTrendResponseSchema,
820
824
  siteCrawlAuditFactorSchema,
825
+ siteCrawlAuditFindingSchema,
821
826
  siteCrawlCriticalDefectSchema,
822
827
  siteCrawlDeadLinksResponseSchema,
823
828
  siteCrawlGraphResponseSchema,
824
829
  siteCrawlInternalLinksResponseSchema,
830
+ siteCrawlIssueEvidenceSchema,
825
831
  siteCrawlNeighborsResponseSchema,
826
832
  siteCrawlPageAuditSchema,
827
833
  siteCrawlPagesResponseSchema,
@@ -905,10 +911,10 @@ import {
905
911
  wordpressSchemaDeployResultDtoSchema,
906
912
  wordpressSchemaStatusResultDtoSchema,
907
913
  wordpressStatusDtoSchema
908
- } from "./chunk-E43JM3QV.js";
914
+ } from "./chunk-GZ2KIONF.js";
909
915
 
910
916
  // src/intelligence-service.ts
911
- import { eq as eq98, desc as desc45, asc as asc19, and as and81, ne as ne17, or as or24, inArray as inArray40, gte as gte26, lte as lte23, isNull as isNull19, sql as sql36, exists as exists2 } from "drizzle-orm";
917
+ import { eq as eq99, desc as desc45, asc as asc20, and as and82, ne as ne17, or as or24, inArray as inArray40, gte as gte26, lte as lte23, isNull as isNull19, sql as sql37, exists as exists2 } from "drizzle-orm";
912
918
 
913
919
  // ../db/src/client.ts
914
920
  import { mkdirSync } from "fs";
@@ -2094,6 +2100,8 @@ var siteCrawlSnapshots = sqliteTable("site_crawl_snapshots", {
2094
2100
  pagesErrored: integer("pages_errored").notNull().default(0),
2095
2101
  edgesDiscovered: integer("edges_discovered").notNull().default(0),
2096
2102
  findingsCount: integer("findings_count").notNull().default(0),
2103
+ /** Null when cross-page issue evidence was not recorded; a measured empty list is distinct. */
2104
+ issueEvidence: text("issue_evidence", { mode: "json" }).$type(),
2097
2105
  deadLinkState: text("dead_link_state").notNull().default("disabled"),
2098
2106
  deadLinksChecked: integer("dead_links_checked").notNull().default(0),
2099
2107
  deadLinksFound: integer("dead_links_found").notNull().default(0),
@@ -8617,6 +8625,13 @@ var MIGRATION_VERSIONS = [
8617
8625
  statements: [
8618
8626
  `ALTER TABLE doctor_health_state ADD COLUMN pending_signature TEXT`
8619
8627
  ]
8628
+ },
8629
+ {
8630
+ version: 180,
8631
+ name: "site-crawl-issue-evidence",
8632
+ statements: [
8633
+ `ALTER TABLE site_crawl_snapshots ADD COLUMN issue_evidence TEXT`
8634
+ ]
8620
8635
  }
8621
8636
  ];
8622
8637
  var AERO_LEDGER_REBUILDS = [
@@ -16038,16 +16053,16 @@ function pruneAutoAliases(entries, project, marketPins) {
16038
16053
  const autoPlan = planCompetitorAutoAliases(entries.map((entry) => ({
16039
16054
  domain: entry.domain,
16040
16055
  aliases: entry.aliases,
16041
- autoAliases: (entry.row?.autoAliases ?? []).map((record) => record.name),
16056
+ autoAliases: (entry.row?.autoAliases ?? []).map((record2) => record2.name),
16042
16057
  blockedAliases: entry.row?.blockedAliases ?? []
16043
16058
  })), project, marketPins);
16044
16059
  const autoAliasChanges = [];
16045
16060
  const autoAliases = entries.map((entry, index2) => {
16046
- const before = (entry.row?.autoAliases ?? []).map((record) => record.name);
16061
+ const before = (entry.row?.autoAliases ?? []).map((record2) => record2.name);
16047
16062
  const keptKeys = new Set(autoPlan.competitors[index2].autoAliases.map(brandKeyFromText));
16048
- const kept = (entry.row?.autoAliases ?? []).filter((record) => keptKeys.has(brandKeyFromText(record.name)));
16063
+ const kept = (entry.row?.autoAliases ?? []).filter((record2) => keptKeys.has(brandKeyFromText(record2.name)));
16049
16064
  if (kept.length !== before.length) {
16050
- autoAliasChanges.push({ domain: entry.domain, before, after: kept.map((record) => record.name) });
16065
+ autoAliasChanges.push({ domain: entry.domain, before, after: kept.map((record2) => record2.name) });
16051
16066
  }
16052
16067
  return kept;
16053
16068
  });
@@ -16183,7 +16198,7 @@ function deletedAliasesDiff(rows) {
16183
16198
  const withBlocked = rows.filter((row) => row.blockedAliases.length > 0);
16184
16199
  return {
16185
16200
  ...withAliases.length ? { deletedAliases: Object.fromEntries(withAliases.map((row) => [row.domain, row.aliases])) } : {},
16186
- ...withAuto.length ? { deletedAutoAliases: Object.fromEntries(withAuto.map((row) => [row.domain, row.autoAliases.map((record) => record.name)])) } : {},
16201
+ ...withAuto.length ? { deletedAutoAliases: Object.fromEntries(withAuto.map((row) => [row.domain, row.autoAliases.map((record2) => record2.name)])) } : {},
16187
16202
  ...withBlocked.length ? { deletedBlockedAliases: Object.fromEntries(withBlocked.map((row) => [row.domain, row.blockedAliases])) } : {}
16188
16203
  };
16189
16204
  }
@@ -16224,13 +16239,13 @@ function readMarketCompetitorNames(db, projectId, hypotheticalTracked) {
16224
16239
  ...tracked.map((row) => ({
16225
16240
  domain: row.domain,
16226
16241
  aliases: row.aliases,
16227
- autoAliases: row.autoAliases.map((record) => record.name),
16242
+ autoAliases: row.autoAliases.map((record2) => record2.name),
16228
16243
  blockedAliases: row.blockedAliases
16229
16244
  })),
16230
16245
  ...learned.map((entry) => ({
16231
16246
  domain: entry.domain,
16232
16247
  aliases: entry.names,
16233
- autoAliases: (entry.stored?.autoAliases ?? []).map((record) => record.name),
16248
+ autoAliases: (entry.stored?.autoAliases ?? []).map((record2) => record2.name),
16234
16249
  blockedAliases: entry.stored?.blockedAliases ?? []
16235
16250
  }))
16236
16251
  ], competitorAliasProjectIdentity(project), readMarketCompetitorPins(db, projectId));
@@ -20260,8 +20275,8 @@ async function scanCompetitorAutoAliasEvidence(db, project, opts = {}) {
20260
20275
  pause,
20261
20276
  {
20262
20277
  storedNames: [
20263
- ...tracked.flatMap((row) => row.autoAliases.map((record) => ({ domain: row.domain, name: record.name }))),
20264
- ...market.flatMap((entry) => (entry.stored?.autoAliases ?? []).map((record) => ({ domain: entry.domain, name: record.name })))
20278
+ ...tracked.flatMap((row) => row.autoAliases.map((record2) => ({ domain: row.domain, name: record2.name }))),
20279
+ ...market.flatMap((entry) => (entry.stored?.autoAliases ?? []).map((record2) => ({ domain: entry.domain, name: record2.name })))
20265
20280
  ]
20266
20281
  }
20267
20282
  ),
@@ -22155,8 +22170,8 @@ async function competitorRoutes(app, opts = {}) {
22155
22170
  });
22156
22171
  }
22157
22172
  const blockedKeys = new Set(blockedAliases.map(brandKeyFromText));
22158
- const autoAliases = target2.autoAliases.filter((record) => !blockedKeys.has(brandKeyFromText(record.name)));
22159
- const removed = target2.autoAliases.filter((record) => blockedKeys.has(brandKeyFromText(record.name))).map((record) => record.name);
22173
+ const autoAliases = target2.autoAliases.filter((record2) => !blockedKeys.has(brandKeyFromText(record2.name)));
22174
+ const removed = target2.autoAliases.filter((record2) => blockedKeys.has(brandKeyFromText(record2.name))).map((record2) => record2.name);
22160
22175
  if (blockedAliases.length === target2.blockedAliases.length && removed.length === 0) {
22161
22176
  return { target: target2, namesChanged: false };
22162
22177
  }
@@ -25854,7 +25869,7 @@ function parseCsvRecords(value) {
25854
25869
  }
25855
25870
  if (inQuotes) csvError("csv-malformed", "CSV has an unterminated quoted field.");
25856
25871
  if (recordHasInput || fields.length > 0 || field.length > 0) endRecord();
25857
- return records.filter((record) => !record.physicalBlank);
25872
+ return records.filter((record2) => !record2.physicalBlank);
25858
25873
  }
25859
25874
  function parseHeader(records) {
25860
25875
  const header2 = records.at(0);
@@ -25902,10 +25917,10 @@ function parseGroupMembershipCsv(csv) {
25902
25917
  const propertyIndex = header2.indices.get("property");
25903
25918
  const groupIndex = header2.indices.get("group");
25904
25919
  const urlIndex = header2.indices.get("url");
25905
- const rows = dataRecords.map((record, index2) => {
25906
- const property = record.fields[propertyIndex] ?? "";
25907
- const group = record.fields[groupIndex] ?? "";
25908
- const rawUrl = urlIndex === void 0 ? "" : record.fields[urlIndex] ?? "";
25920
+ const rows = dataRecords.map((record2, index2) => {
25921
+ const property = record2.fields[propertyIndex] ?? "";
25922
+ const group = record2.fields[groupIndex] ?? "";
25923
+ const rawUrl = urlIndex === void 0 ? "" : record2.fields[urlIndex] ?? "";
25909
25924
  const url = rawUrl.trim() || null;
25910
25925
  return {
25911
25926
  dataRow: index2 + 1,
@@ -25914,7 +25929,7 @@ function parseGroupMembershipCsv(csv) {
25914
25929
  url,
25915
25930
  normalizedProperty: normalizeGroupMembershipLabel(property),
25916
25931
  normalizedGroupLabel: normalizeGroupMembershipLabel(group),
25917
- columnCountValid: record.fields.length === header2.length
25932
+ columnCountValid: record2.fields.length === header2.length
25918
25933
  };
25919
25934
  });
25920
25935
  return { sourceChecksum: sourceChecksum(csv), rows };
@@ -26650,8 +26665,8 @@ function pinCompetitorInAuthoring(authoring, input, trackedAliases = () => []) {
26650
26665
  }
26651
26666
  function nextCompetitorStableKey(group, domain) {
26652
26667
  const rawBase = `competitor-${domain}`;
26653
- const digest = crypto21.createHash("sha256").update(domain).digest("hex").slice(0, 12);
26654
- const base = rawBase.length <= 128 ? rawBase : `${rawBase.slice(0, 128 - digest.length - 1)}-${digest}`;
26668
+ const digest2 = crypto21.createHash("sha256").update(domain).digest("hex").slice(0, 12);
26669
+ const base = rawBase.length <= 128 ? rawBase : `${rawBase.slice(0, 128 - digest2.length - 1)}-${digest2}`;
26655
26670
  const used = new Set(group.competitors.map((competitor) => competitor.stableKey));
26656
26671
  if (!used.has(base)) return base;
26657
26672
  let suffix = 2;
@@ -32676,7 +32691,7 @@ function buildSimpleCandidate(db, state, mutation, opts, manualCapturedAt) {
32676
32691
  assertSimpleAddition(addition);
32677
32692
  const audience = { targetKeys: [], groupKeys: [], marketKeys: [] };
32678
32693
  const records = addition.input.source === "template" ? templateRecords(db, state, addition, audience) : sourceRecord(db, state.project.id, addition, manualCapturedAt);
32679
- for (const record of records) addSimpleSource(state, rows, record, reusedQueryIds);
32694
+ for (const record2 of records) addSimpleSource(state, rows, record2, reusedQueryIds);
32680
32695
  }
32681
32696
  const planChanged = false;
32682
32697
  const rowChanged = !rowsEqual(new Map(state.queryRows.map((row) => [row.id, row])), rows);
@@ -32798,13 +32813,13 @@ function applyAdvancedEdit(state, plan, rows, edit, capturedAt, reusedQueryIds,
32798
32813
  return { edge, assignment, node, target, queryClass: proposed, classificationSource, scopes };
32799
32814
  });
32800
32815
  if (!textChanged) {
32801
- for (const record of records) {
32802
- if (record.assignment.queryClass === record.queryClass && record.assignment.classificationSource === record.classificationSource) continue;
32803
- if (marketKeys.size > 0 && record.scopes.some((scope) => !marketKeys.has(scope.stableKey))) {
32816
+ for (const record2 of records) {
32817
+ if (record2.assignment.queryClass === record2.queryClass && record2.assignment.classificationSource === record2.classificationSource) continue;
32818
+ if (marketKeys.size > 0 && record2.scopes.some((scope) => !marketKeys.has(scope.stableKey))) {
32804
32819
  throw validationError("This assignment is shared with another market. Change its classification from the Property or Whole site scope.");
32805
32820
  }
32806
- record.assignment.queryClass = record.queryClass;
32807
- record.assignment.classificationSource = record.classificationSource;
32821
+ record2.assignment.queryClass = record2.queryClass;
32822
+ record2.assignment.classificationSource = record2.classificationSource;
32808
32823
  mutatedQueryIds.add(edit.queryId);
32809
32824
  }
32810
32825
  return plan;
@@ -32818,22 +32833,22 @@ function applyAdvancedEdit(state, plan, rows, edit, capturedAt, reusedQueryIds,
32818
32833
  const selectedTargets = new Set(selectedEdges.map((edge) => edge.targetKey));
32819
32834
  plan = marketKeys.size > 0 ? removeMarketMembership(plan, edit.queryId, selectedTargets, marketKeys) : removeAdvancedAssignments(plan, edit.queryId, selectedTargets);
32820
32835
  if (!planSnapshot(plan, newQueryId)) plan.querySnapshots.push({ queryId: newQueryId, queryText, provenance });
32821
- for (const record of records) {
32822
- const existingNode = plan.executionNodes.find((node) => node.queryId === newQueryId && contextKey(node.context) === contextKey(record.node.context));
32823
- const existingAssignment = existingNode ? plan.assignments.find((candidate) => assignmentKey(candidate.targetKey, candidate.queryId, candidate.executionNodeKey) === assignmentKey(record.target.stableKey, newQueryId, existingNode.stableKey)) : void 0;
32824
- if (existingAssignment && (existingAssignment.queryClass !== record.queryClass || existingAssignment.classificationSource !== record.classificationSource)) {
32836
+ for (const record2 of records) {
32837
+ const existingNode = plan.executionNodes.find((node) => node.queryId === newQueryId && contextKey(node.context) === contextKey(record2.node.context));
32838
+ const existingAssignment = existingNode ? plan.assignments.find((candidate) => assignmentKey(candidate.targetKey, candidate.queryId, candidate.executionNodeKey) === assignmentKey(record2.target.stableKey, newQueryId, existingNode.stableKey)) : void 0;
32839
+ if (existingAssignment && (existingAssignment.queryClass !== record2.queryClass || existingAssignment.classificationSource !== record2.classificationSource)) {
32825
32840
  throw validationError("The destination query uses different classification settings. Review that query separately.");
32826
32841
  }
32827
- const edge = existingNode ? { queryId: newQueryId, targetKey: record.target.stableKey, executionNodeKey: existingNode.stableKey } : ensureAdvancedEdge(plan, state, record.target, newQueryId, queryText, record.queryClass, record.classificationSource ?? "server", record.node.context);
32828
- if (existingNode && !existingAssignment) plan.assignments.push({ ...edge, queryClass: record.queryClass, ...record.classificationSource ? { classificationSource: record.classificationSource } : {} });
32842
+ const edge = existingNode ? { queryId: newQueryId, targetKey: record2.target.stableKey, executionNodeKey: existingNode.stableKey } : ensureAdvancedEdge(plan, state, record2.target, newQueryId, queryText, record2.queryClass, record2.classificationSource ?? "server", record2.node.context);
32843
+ if (existingNode && !existingAssignment) plan.assignments.push({ ...edge, queryClass: record2.queryClass, ...record2.classificationSource ? { classificationSource: record2.classificationSource } : {} });
32829
32844
  if (!existingNode) {
32830
32845
  const createdNode = plan.executionNodes.find((node) => node.stableKey === edge.executionNodeKey);
32831
- if (contextKey(createdNode.context) !== contextKey(record.node.context)) throw validationError("The edited query cannot preserve its stored execution context.");
32846
+ if (contextKey(createdNode.context) !== contextKey(record2.node.context)) throw validationError("The edited query cannot preserve its stored execution context.");
32832
32847
  const createdAssignment = plan.assignments.find((candidate) => assignmentKey(candidate.targetKey, candidate.queryId, candidate.executionNodeKey) === assignmentKey(edge.targetKey, edge.queryId, edge.executionNodeKey));
32833
- if (record.classificationSource === void 0) delete createdAssignment.classificationSource;
32848
+ if (record2.classificationSource === void 0) delete createdAssignment.classificationSource;
32834
32849
  }
32835
32850
  if (!plan.usageEdges.some((candidate) => edgeKey(candidate) === edgeKey(edge))) plan.usageEdges.push(edge);
32836
- for (const scope of record.scopes) {
32851
+ for (const scope of record2.scopes) {
32837
32852
  if (marketKeys.size === 0 || marketKeys.has(scope.stableKey)) addMarketEdge(plan, scope.stableKey, edge);
32838
32853
  }
32839
32854
  }
@@ -32874,9 +32889,9 @@ function buildAdvancedCandidate(db, state, mutation, manualCapturedAt) {
32874
32889
  }
32875
32890
  for (const addition of mutation.additions) {
32876
32891
  const audience = resolveAudience(active.plan, addition.audience);
32877
- const records = addition.input.source === "template" ? templateRecords(db, state, addition, audience) : sourceRecord(db, state.project.id, addition, manualCapturedAt).map((record) => ({ ...record, audience }));
32878
- for (const record of records) {
32879
- addAdvancedSource(state, plan, rows, addition, record, reusedQueryIds, mutatedQueryIds);
32892
+ const records = addition.input.source === "template" ? templateRecords(db, state, addition, audience) : sourceRecord(db, state.project.id, addition, manualCapturedAt).map((record2) => ({ ...record2, audience }));
32893
+ for (const record2 of records) {
32894
+ addAdvancedSource(state, plan, rows, addition, record2, reusedQueryIds, mutatedQueryIds);
32880
32895
  }
32881
32896
  }
32882
32897
  for (const queryId of fullRemovals) {
@@ -38198,41 +38213,41 @@ function csvCell(value) {
38198
38213
  return /[",\r\n]/.test(text2) ? `"${text2.replace(/"/g, '""')}"` : text2;
38199
38214
  }
38200
38215
  function renderResultsExportCsv(report) {
38201
- const rows = report.records.map((record) => [
38216
+ const rows = report.records.map((record2) => [
38202
38217
  report.schemaVersion,
38203
38218
  report.project.id,
38204
38219
  report.project.name,
38205
38220
  report.project.displayName,
38206
38221
  report.project.canonicalDomain,
38207
- record.runId,
38208
- record.runKind,
38209
- record.runStatus,
38210
- record.runTrigger,
38211
- record.runCreatedAt,
38212
- record.runStartedAt,
38213
- record.runFinishedAt,
38214
- record.snapshotId,
38215
- record.snapshotCreatedAt,
38216
- record.queryId,
38217
- record.query,
38218
- record.provider,
38219
- record.model,
38220
- record.location,
38221
- record.citationState,
38222
- record.cited,
38223
- record.answerMentioned,
38224
- record.mentionState,
38225
- JSON.stringify(record.citedDomains),
38226
- JSON.stringify(record.citedUrls),
38227
- record.captureStatus,
38228
- record.sourceCount,
38229
- record.resolvedCount,
38230
- record.captureVersion,
38231
- JSON.stringify(record.competitorOverlap),
38232
- JSON.stringify(record.recommendedCompetitors),
38233
- record.answerText,
38234
- JSON.stringify(record.groundingSources),
38235
- JSON.stringify(record.searchQueries)
38222
+ record2.runId,
38223
+ record2.runKind,
38224
+ record2.runStatus,
38225
+ record2.runTrigger,
38226
+ record2.runCreatedAt,
38227
+ record2.runStartedAt,
38228
+ record2.runFinishedAt,
38229
+ record2.snapshotId,
38230
+ record2.snapshotCreatedAt,
38231
+ record2.queryId,
38232
+ record2.query,
38233
+ record2.provider,
38234
+ record2.model,
38235
+ record2.location,
38236
+ record2.citationState,
38237
+ record2.cited,
38238
+ record2.answerMentioned,
38239
+ record2.mentionState,
38240
+ JSON.stringify(record2.citedDomains),
38241
+ JSON.stringify(record2.citedUrls),
38242
+ record2.captureStatus,
38243
+ record2.sourceCount,
38244
+ record2.resolvedCount,
38245
+ record2.captureVersion,
38246
+ JSON.stringify(record2.competitorOverlap),
38247
+ JSON.stringify(record2.recommendedCompetitors),
38248
+ record2.answerText,
38249
+ JSON.stringify(record2.groundingSources),
38250
+ JSON.stringify(record2.searchQueries)
38236
38251
  ].map(csvCell).join(","));
38237
38252
  return [CSV_COLUMNS.join(","), ...rows].join("\r\n") + "\r\n";
38238
38253
  }
@@ -40432,6 +40447,8 @@ var SCHEMA_TABLE = {
40432
40447
  CalendarRecurrence: calendarRecurrenceSchema,
40433
40448
  ScheduleDto: scheduleDtoSchema,
40434
40449
  SettingsDto: settingsDtoSchema,
40450
+ SiteAuditIssuePagesResponseDto: siteAuditIssuePagesResponseSchema,
40451
+ SiteAuditIssuesResponseDto: siteAuditIssuesResponseSchema,
40435
40452
  SiteAuditLivePageHealthDto: siteAuditLivePageHealthSchema,
40436
40453
  SiteAuditPagesResponseDto: siteAuditPagesResponseSchema,
40437
40454
  SiteAuditRunProgressDto: siteAuditRunProgressSchema,
@@ -40511,12 +40528,12 @@ function addNullToNullableEnums(node) {
40511
40528
  return;
40512
40529
  }
40513
40530
  if (node === null || typeof node !== "object") return;
40514
- const record = node;
40515
- if (record.nullable === true && Array.isArray(record.enum)) {
40516
- const values = record.enum;
40517
- if (!values.includes(null)) record.enum = [...values, null];
40531
+ const record2 = node;
40532
+ if (record2.nullable === true && Array.isArray(record2.enum)) {
40533
+ const values = record2.enum;
40534
+ if (!values.includes(null)) record2.enum = [...values, null];
40518
40535
  }
40519
- for (const value of Object.values(record)) addNullToNullableEnums(value);
40536
+ for (const value of Object.values(record2)) addNullToNullableEnums(value);
40520
40537
  }
40521
40538
  var INPUT_FORM_SCHEMAS = /* @__PURE__ */ new Set(["ProjectConfig"]);
40522
40539
  function buildComponentSchemas() {
@@ -47971,6 +47988,43 @@ var routeCatalog = [
47971
47988
  404: errorResponse("Project or selected crawl-bearing run not found.")
47972
47989
  }
47973
47990
  },
47991
+ {
47992
+ method: "get",
47993
+ path: "/api/v1/projects/{name}/technical-aeo/issues",
47994
+ summary: "List stored audit issue groups and representative URLs",
47995
+ description: "Read-only retained evidence, without provider calls. Includes coded findings and independent critical defects at every page score and retained cross-page groups; mirrored duplicate annotations appear once. Returns stored run/date/attempt, crawl completeness, and separate page-finding/cross-page coverage so legacy absence and malformed evidence cannot imply a clean site. Disposition is a prioritization hint, not permission to edit or proof of score impact. Recommendations distinguish whole-factor guidance from independently issue-scoped critical-defect guidance. Page-finding identities use factor/code/type; duplicate metadata identities use the full exact metadata value. Unknown cross-page identities are run-local. Reads fail explicitly above 128 MiB evidence or 4096 groups; they never sample an apparently complete total.",
47996
+ tags: ["technical-aeo"],
47997
+ parameters: [
47998
+ nameParameter,
47999
+ { name: "runId", in: "query", description: "Stored non-probe audit run. Omit for the standard latest crawl selection.", schema: stringSchema },
48000
+ { name: "cursor", in: "query", description: "Opaque continuation bound to project, run, published attempt, issue, and exact retained evidence.", schema: { type: "string", maxLength: 2048 } },
48001
+ { name: "limit", in: "query", description: "Maximum rows; the 16 KiB serialized response budget may return fewer. Follow nextCursor until null.", schema: { type: "integer", minimum: 1, maximum: 100, default: 25 } }
48002
+ ],
48003
+ responses: {
48004
+ 200: jsonResponse("Stored issue groups and coverage returned.", "SiteAuditIssuesResponseDto"),
48005
+ 400: errorResponse("Invalid query/cursor or evidence/response read budget exceeded."),
48006
+ 404: errorResponse("Project or explicitly selected non-probe audit run not found.")
48007
+ }
48008
+ },
48009
+ {
48010
+ method: "get",
48011
+ path: "/api/v1/projects/{name}/technical-aeo/issues/{issueId}/pages",
48012
+ summary: "List affected URLs for one stored audit issue",
48013
+ description: "Read-only bounded membership of one issue in the selected published audit attempt. Preserve runId from the issue list when following historical evidence. Messages and factor-scoped recommendations expose explicit truncation; URLs are preserved. Missing legacy evidence remains unavailable, never a measured empty result.",
48014
+ tags: ["technical-aeo"],
48015
+ parameters: [
48016
+ nameParameter,
48017
+ { name: "runId", in: "query", description: "Stored non-probe audit run. Omit for the standard latest crawl selection.", schema: stringSchema },
48018
+ { name: "cursor", in: "query", description: "Opaque continuation bound to project, run, published attempt, issue, and exact retained evidence.", schema: { type: "string", maxLength: 2048 } },
48019
+ { name: "limit", in: "query", description: "Maximum rows; the 16 KiB serialized response budget may return fewer. Follow nextCursor until null.", schema: { type: "integer", minimum: 1, maximum: 100, default: 25 } },
48020
+ { name: "issueId", in: "path", required: true, description: "Exact issue ID from the stored issue list.", schema: stringSchema }
48021
+ ],
48022
+ responses: {
48023
+ 200: jsonResponse("Stored affected page evidence and coverage returned.", "SiteAuditIssuePagesResponseDto"),
48024
+ 400: errorResponse("Invalid query/cursor or evidence/response read budget exceeded."),
48025
+ 404: errorResponse("Project, selected non-probe audit run, or issue not found.")
48026
+ }
48027
+ },
47974
48028
  {
47975
48029
  method: "get",
47976
48030
  path: "/api/v1/projects/{name}/technical-aeo/crawl/pages/audit",
@@ -64629,7 +64683,7 @@ async function queryBacklinks(opts) {
64629
64683
  const reversed = opts.targets.map(reverseDomain);
64630
64684
  const targetList = reversed.map(quote).join(", ");
64631
64685
  const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
64632
- const sql37 = `
64686
+ const sql38 = `
64633
64687
  WITH vertices AS (
64634
64688
  SELECT * FROM read_csv(
64635
64689
  ${quote(opts.vertexPath)},
@@ -64665,7 +64719,7 @@ async function queryBacklinks(opts) {
64665
64719
  const conn = await instance.connect();
64666
64720
  let rows;
64667
64721
  try {
64668
- const reader = await conn.runAndReadAll(sql37);
64722
+ const reader = await conn.runAndReadAll(sql38);
64669
64723
  rows = reader.getRowObjects();
64670
64724
  } finally {
64671
64725
  conn.disconnectSync?.();
@@ -79363,9 +79417,9 @@ var RULE_ID_TO_RANGES = {
79363
79417
  };
79364
79418
  function manifestMetadata(raw) {
79365
79419
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
79366
- const record = raw;
79367
- const source = typeof record._source === "string" ? record._source.trim() : "";
79368
- const version = typeof record.creationTime === "string" ? record.creationTime.trim() : "";
79420
+ const record2 = raw;
79421
+ const source = typeof record2._source === "string" ? record2._source.trim() : "";
79422
+ const version = typeof record2.creationTime === "string" ? record2.creationTime.trim() : "";
79369
79423
  if (!source || !version) return null;
79370
79424
  const contentHash2 = ipRangeManifestContentHash(raw);
79371
79425
  return { id: `${source}#${version}#sha256:${contentHash2}`, source, version };
@@ -80911,10 +80965,10 @@ function canonicalizeCloudflareJson(value) {
80911
80965
  return `[${value.map((item2) => canonicalizeCloudflareJson(item2)).join(",")}]`;
80912
80966
  }
80913
80967
  if (typeof value === "object") {
80914
- const record = value;
80968
+ const record2 = value;
80915
80969
  const fields = [];
80916
- for (const key of Object.keys(record).sort()) {
80917
- const fieldValue = record[key];
80970
+ for (const key of Object.keys(record2).sort()) {
80971
+ const fieldValue = record2[key];
80918
80972
  if (fieldValue === void 0) continue;
80919
80973
  fields.push(`${JSON.stringify(key)}:${canonicalizeCloudflareJson(fieldValue)}`);
80920
80974
  }
@@ -81587,22 +81641,22 @@ function asRecord(value, label) {
81587
81641
  }
81588
81642
  return value;
81589
81643
  }
81590
- function requiredString2(record, key, label) {
81591
- const value = record[key];
81644
+ function requiredString2(record2, key, label) {
81645
+ const value = record2[key];
81592
81646
  if (typeof value !== "string" || !value.trim()) {
81593
81647
  throw new CloudflareQueueApiError(`Cloudflare Queue ${label} is malformed`, 502);
81594
81648
  }
81595
81649
  return value;
81596
81650
  }
81597
- function stringField(record, key, label) {
81598
- const value = record[key];
81651
+ function stringField(record2, key, label) {
81652
+ const value = record2[key];
81599
81653
  if (typeof value !== "string") {
81600
81654
  throw new CloudflareQueueApiError(`Cloudflare Queue ${label} is malformed`, 502);
81601
81655
  }
81602
81656
  return value;
81603
81657
  }
81604
- function metadata(record) {
81605
- const value = asRecord(record.metadata, "message metadata");
81658
+ function metadata(record2) {
81659
+ const value = asRecord(record2.metadata, "message metadata");
81606
81660
  const parsed = {};
81607
81661
  for (const [key, entry] of Object.entries(value)) {
81608
81662
  if (typeof entry !== "string") {
@@ -81639,8 +81693,8 @@ function decodeJson(value) {
81639
81693
  return parseJson(decodeUtf8(decodeBase64(value)));
81640
81694
  }
81641
81695
  }
81642
- function safeMessageBase(record, leaseId) {
81643
- const rawMetadata = record.metadata;
81696
+ function safeMessageBase(record2, leaseId) {
81697
+ const rawMetadata = record2.metadata;
81644
81698
  const safeMetadata = {};
81645
81699
  let metadataIsSafe = rawMetadata != null && typeof rawMetadata === "object" && !Array.isArray(rawMetadata);
81646
81700
  if (metadataIsSafe) {
@@ -81653,30 +81707,30 @@ function safeMessageBase(record, leaseId) {
81653
81707
  }
81654
81708
  }
81655
81709
  return {
81656
- id: typeof record.id === "string" && record.id.trim() ? record.id : "<malformed>",
81710
+ id: typeof record2.id === "string" && record2.id.trim() ? record2.id : "<malformed>",
81657
81711
  leaseId,
81658
- timestampMs: Number.isInteger(record.timestamp_ms) && record.timestamp_ms >= 0 ? record.timestamp_ms : 0,
81659
- attempts: Number.isInteger(record.attempts) && record.attempts >= 0 ? record.attempts : 0,
81712
+ timestampMs: Number.isInteger(record2.timestamp_ms) && record2.timestamp_ms >= 0 ? record2.timestamp_ms : 0,
81713
+ attempts: Number.isInteger(record2.attempts) && record2.attempts >= 0 ? record2.attempts : 0,
81660
81714
  metadata: metadataIsSafe ? safeMetadata : {}
81661
81715
  };
81662
81716
  }
81663
- function decodeMessage(record) {
81664
- const leaseId = requiredString2(record, "lease_id", "message");
81717
+ function decodeMessage(record2) {
81718
+ const leaseId = requiredString2(record2, "lease_id", "message");
81665
81719
  let base;
81666
81720
  let rawBody;
81667
81721
  try {
81668
81722
  base = {
81669
- id: requiredString2(record, "id", "message"),
81723
+ id: requiredString2(record2, "id", "message"),
81670
81724
  leaseId,
81671
- timestampMs: nonNegativeInteger(record.timestamp_ms, "message timestamp_ms"),
81672
- attempts: nonNegativeInteger(record.attempts, "message attempts"),
81673
- metadata: metadata(record)
81725
+ timestampMs: nonNegativeInteger(record2.timestamp_ms, "message timestamp_ms"),
81726
+ attempts: nonNegativeInteger(record2.attempts, "message attempts"),
81727
+ metadata: metadata(record2)
81674
81728
  };
81675
- rawBody = stringField(record, "body", "message");
81729
+ rawBody = stringField(record2, "body", "message");
81676
81730
  } catch (error) {
81677
81731
  if (!(error instanceof CloudflareQueueApiError)) throw error;
81678
81732
  return {
81679
- ...safeMessageBase(record, leaseId),
81733
+ ...safeMessageBase(record2, leaseId),
81680
81734
  contentType: "poison",
81681
81735
  reason: "malformed-envelope"
81682
81736
  };
@@ -81735,9 +81789,9 @@ function parsePullEnvelope(value) {
81735
81789
  }
81736
81790
  return { messageBacklogCount, messages, skippedUnleasedMessageCount };
81737
81791
  }
81738
- function optionalAckCount(record, key) {
81739
- if (!(key in record)) return void 0;
81740
- return nonNegativeInteger(record[key], `ack response ${key}`);
81792
+ function optionalAckCount(record2, key) {
81793
+ if (!(key in record2)) return void 0;
81794
+ return nonNegativeInteger(record2[key], `ack response ${key}`);
81741
81795
  }
81742
81796
  function ackWarningCount(value) {
81743
81797
  if (value == null) return 0;
@@ -82486,12 +82540,12 @@ function rowToDto(row) {
82486
82540
  updatedAt: row.updatedAt
82487
82541
  };
82488
82542
  }
82489
- async function defaultResolveAccessToken(record) {
82490
- if (record.authMode === TrafficSourceAuthModes["service-account"]) {
82491
- if (!record.clientEmail || !record.privateKey) {
82543
+ async function defaultResolveAccessToken(record2) {
82544
+ if (record2.authMode === TrafficSourceAuthModes["service-account"]) {
82545
+ if (!record2.clientEmail || !record2.privateKey) {
82492
82546
  throw validationError("Service-account credentials missing client_email or private_key");
82493
82547
  }
82494
- return getCloudLoggingAccessToken(record.clientEmail, record.privateKey);
82548
+ return getCloudLoggingAccessToken(record2.clientEmail, record2.privateKey);
82495
82549
  }
82496
82550
  throw validationError(
82497
82551
  "OAuth-mode Cloud Run sync is not yet supported in v1. Provide a service-account key file."
@@ -87155,16 +87209,16 @@ async function probeApiHostUncached(host, options) {
87155
87209
  const result2 = { kind: kind2, host, addresses: [], code, durationMs: now() - started };
87156
87210
  return result2;
87157
87211
  }
87158
- const addresses = records.map((record) => record.address);
87159
- const publicAddresses = records.filter((record) => net5.isIP(record.address) > 0 && !classifyBlocked(record.address)).slice(0, 2);
87160
- const blocked = records.map((record) => classifyBlocked(record.address)).find(Boolean);
87212
+ const addresses = records.map((record2) => record2.address);
87213
+ const publicAddresses = records.filter((record2) => net5.isIP(record2.address) > 0 && !classifyBlocked(record2.address)).slice(0, 2);
87214
+ const blocked = records.map((record2) => classifyBlocked(record2.address)).find(Boolean);
87161
87215
  if (publicAddresses.length === 0) {
87162
87216
  const result2 = { kind: blocked ?? "no-address", host, addresses, durationMs: now() - started };
87163
87217
  return result2;
87164
87218
  }
87165
- const attempts = await Promise.all(publicAddresses.map(async (record) => {
87219
+ const attempts = await Promise.all(publicAddresses.map(async (record2) => {
87166
87220
  try {
87167
- return await connect(host, record.address, options.timeoutMs ?? DEFAULT_TIMEOUT_MS6);
87221
+ return await connect(host, record2.address, options.timeoutMs ?? DEFAULT_TIMEOUT_MS6);
87168
87222
  } catch (error) {
87169
87223
  return isTlsProbeError(error) ? "tls-failed" : "connect-failed";
87170
87224
  }
@@ -90613,8 +90667,299 @@ function dedupeStrings(input) {
90613
90667
 
90614
90668
  // ../api-routes/src/technical-aeo.ts
90615
90669
  import crypto53 from "crypto";
90616
- import { and as and77, asc as asc17, count as count7, desc as desc43, eq as eq94, gte as gte24, inArray as inArray38, isNotNull as isNotNull9, isNull as isNull18, lt as lt13, or as or22, sql as sql34 } from "drizzle-orm";
90670
+ import { and as and78, asc as asc18, count as count7, desc as desc43, eq as eq95, gte as gte24, inArray as inArray38, isNotNull as isNotNull9, isNull as isNull18, lt as lt13, or as or22, sql as sql35 } from "drizzle-orm";
90617
90671
  import { alias } from "drizzle-orm/sqlite-core";
90672
+
90673
+ // ../api-routes/src/technical-aeo-issues.ts
90674
+ import { createHash as createHash11 } from "crypto";
90675
+ import { and as and77, asc as asc17, eq as eq94, gt as gt8, sql as sql34 } from "drizzle-orm";
90676
+ var ADVISORY_CODES = /* @__PURE__ */ new Set([
90677
+ "entity-consistency.title.short",
90678
+ "site.duplicate-title",
90679
+ "site.duplicate-meta-description"
90680
+ ]);
90681
+ var DISPOSITION_ORDER = { actionable: 0, advisory: 1, unverified: 2, informational: 3 };
90682
+ var MAX_EVIDENCE_BYTES = 128 * 1024 * 1024;
90683
+ var MAX_GROUPS = 4096;
90684
+ var MAX_RESPONSE_BYTES = 16 * 1024;
90685
+ function digest(value) {
90686
+ return createHash11("sha256").update(JSON.stringify(value)).digest("hex");
90687
+ }
90688
+ function record(value) {
90689
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
90690
+ }
90691
+ function clip2(message) {
90692
+ return { message: message.slice(0, 400), messageTruncated: message.length > 400 };
90693
+ }
90694
+ function parseSiteAuditIssueQuery(query) {
90695
+ const limit = query.limit;
90696
+ const parsed = siteAuditIssueListQuerySchema.safeParse({
90697
+ ...query,
90698
+ ...limit === void 0 ? {} : { limit: typeof limit === "string" && limit.trim() !== "" ? Number(limit) : limit }
90699
+ });
90700
+ if (!parsed.success) throw validationError(parsed.error.issues.map((issue) => issue.message).join("; "));
90701
+ return parsed.data;
90702
+ }
90703
+ function cursorOffset(cursor) {
90704
+ if (!cursor) return 0;
90705
+ try {
90706
+ const decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
90707
+ const value = record(decoded);
90708
+ if (!value || value.v !== 1 || !Number.isSafeInteger(value.offset) || Number(value.offset) < 1 || typeof value.check !== "string" || Object.keys(value).length !== 3) throw new Error("cursor");
90709
+ if (Buffer.from(JSON.stringify(value)).toString("base64url") !== cursor) throw new Error("cursor");
90710
+ return Number(value.offset);
90711
+ } catch {
90712
+ throw validationError("Invalid stored audit issue cursor");
90713
+ }
90714
+ }
90715
+ function encodeCursor2(basis, offset) {
90716
+ return Buffer.from(JSON.stringify({ v: 1, offset, check: digest([basis, offset]) })).toString("base64url");
90717
+ }
90718
+ function pageEvidence(nodeKey, url, message, factor) {
90719
+ const recommendations = Array.isArray(factor?.recommendations) ? factor.recommendations.filter((value) => typeof value === "string") : [];
90720
+ return {
90721
+ nodeKey,
90722
+ url,
90723
+ ...clip2(message),
90724
+ recommendations: recommendations.slice(0, 3).map((value) => value.slice(0, 400)),
90725
+ recommendationsScope: factor ? "factor" : "none",
90726
+ recommendationsTruncated: recommendations.length > 3 || recommendations.some((value) => value.length > 400)
90727
+ };
90728
+ }
90729
+ function readSiteAuditIssues(input) {
90730
+ const { db, project, target, query, issueId } = input;
90731
+ const limit = query.limit ?? SITE_AUDIT_ISSUES_DEFAULT_LIMIT;
90732
+ const offset = cursorOffset(query.cursor);
90733
+ const coverage = {
90734
+ pageFindings: "unavailable",
90735
+ crossPage: "unavailable",
90736
+ auditedPages: 0,
90737
+ pagesWithFindingEvidence: 0,
90738
+ pagesWithoutFindingEvidence: 0,
90739
+ omittedFindings: 0
90740
+ };
90741
+ const provenance = {
90742
+ project: project.name,
90743
+ runId: target?.snapshot.runId ?? null,
90744
+ attemptId: target?.snapshot.attemptId ?? null,
90745
+ auditedAt: target?.snapshot.createdAt ?? null,
90746
+ runStatus: target?.runStatus ?? null,
90747
+ complete: target?.snapshot.complete ?? false,
90748
+ termination: target?.snapshot.termination ?? null,
90749
+ ...target ? { runSelection: target.runSelection } : {},
90750
+ state: target ? "unavailable" : "no-crawl",
90751
+ coverage,
90752
+ total: null,
90753
+ nextCursor: null
90754
+ };
90755
+ if (!target) {
90756
+ if (query.cursor) throw validationError("Stored audit issue cursor no longer identifies a crawl");
90757
+ const response2 = issueId === void 0 ? { ...provenance, issues: [] } : { ...provenance, issueId, pages: [] };
90758
+ if (Buffer.byteLength(JSON.stringify(response2, null, 2)) > MAX_RESPONSE_BYTES) throw validationError("Stored audit issue exceeds the 16 KiB response budget");
90759
+ return response2;
90760
+ }
90761
+ const snapshot = target.snapshot;
90762
+ const crossJson = JSON.stringify(snapshot.issueEvidence);
90763
+ const crossBytes = Buffer.byteLength(crossJson);
90764
+ if (crossBytes > MAX_EVIDENCE_BYTES) throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
90765
+ const groups = /* @__PURE__ */ new Map();
90766
+ const members = [];
90767
+ let selectedMembers = 0;
90768
+ const evidenceHash = createHash11("sha256").update(JSON.stringify([
90769
+ project.id,
90770
+ snapshot.runId,
90771
+ snapshot.attemptId,
90772
+ snapshot.createdAt,
90773
+ snapshot.updatedAt,
90774
+ snapshot.complete,
90775
+ snapshot.termination,
90776
+ snapshot.detailsAvailable,
90777
+ issueId ?? null
90778
+ ])).update(crossJson);
90779
+ const addMember = (group, page3) => {
90780
+ group.affectedPages += 1;
90781
+ if (group.examples.length < 3) group.examples.push(page3);
90782
+ else group.examplesTruncated = true;
90783
+ if (group.id === issueId) {
90784
+ if (selectedMembers >= offset && members.length < limit + 1) members.push(page3);
90785
+ selectedMembers += 1;
90786
+ }
90787
+ };
90788
+ const addGroup = (group) => {
90789
+ const existing = groups.get(group.id);
90790
+ if (existing) return existing;
90791
+ if (groups.size >= MAX_GROUPS) throw validationError("Stored audit exceeds the 4096 issue-group read budget");
90792
+ groups.set(group.id, group);
90793
+ return group;
90794
+ };
90795
+ const cross = siteCrawlIssueEvidenceSchema.safeParse(snapshot.issueEvidence);
90796
+ const mirroredCodes = /* @__PURE__ */ new Set();
90797
+ if (cross.success) {
90798
+ coverage.crossPage = "available";
90799
+ for (const [index2, issue] of cross.data.siteIssues.entries()) {
90800
+ mirroredCodes.add(issue.code);
90801
+ const id = issue.key ?? `run-issue:${digest([snapshot.runId, snapshot.attemptId, index2, issue])}`;
90802
+ const group = addGroup({
90803
+ id,
90804
+ stableIdentity: issue.key !== null,
90805
+ code: issue.code,
90806
+ source: "cross-page",
90807
+ factorId: null,
90808
+ severity: null,
90809
+ findingType: "info",
90810
+ disposition: ADVISORY_CODES.has(issue.code) ? "advisory" : "informational",
90811
+ ...clip2(issue.message),
90812
+ affectedPages: 0,
90813
+ examples: [],
90814
+ examplesTruncated: false
90815
+ });
90816
+ for (const url of [...new Set(issue.affectedUrls)].sort()) addMember(group, pageEvidence(null, url, issue.message));
90817
+ }
90818
+ }
90819
+ if (snapshot.detailsAvailable && snapshot.attemptId) {
90820
+ const scope = and77(
90821
+ eq94(siteCrawlPages.projectId, project.id),
90822
+ eq94(siteCrawlPages.runId, snapshot.runId),
90823
+ eq94(siteCrawlPages.attemptId, snapshot.attemptId)
90824
+ );
90825
+ const size = db.select({ bytes: sql34`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
90826
+ if (size + crossBytes > MAX_EVIDENCE_BYTES) {
90827
+ throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
90828
+ }
90829
+ let after;
90830
+ while (true) {
90831
+ const pages = db.select({
90832
+ nodeKey: siteCrawlPages.nodeKey,
90833
+ url: siteCrawlPages.url,
90834
+ auditScore: siteCrawlPages.auditScore,
90835
+ auditFields: siteCrawlPages.auditFields
90836
+ }).from(siteCrawlPages).where(and77(scope, after === void 0 ? void 0 : gt8(siteCrawlPages.nodeKey, after))).orderBy(asc17(siteCrawlPages.nodeKey)).limit(200).all();
90837
+ for (const page3 of pages) {
90838
+ evidenceHash.update(JSON.stringify(page3));
90839
+ const fields = record(page3.auditFields);
90840
+ if (page3.auditScore !== null || fields?.schemaVersion === "1.0") coverage.auditedPages += 1;
90841
+ if (fields?.schemaVersion !== "1.0") {
90842
+ if (page3.auditScore !== null) coverage.pagesWithoutFindingEvidence += 1;
90843
+ continue;
90844
+ }
90845
+ let pageComplete = Array.isArray(fields.factors) && Array.isArray(fields.criticalDefects);
90846
+ if (!Array.isArray(fields.factors)) coverage.omittedFindings += 1;
90847
+ if (!Array.isArray(fields.criticalDefects)) coverage.omittedFindings += 1;
90848
+ const seen2 = /* @__PURE__ */ new Set();
90849
+ for (const rawFactor of Array.isArray(fields.factors) ? fields.factors : []) {
90850
+ const factor = record(rawFactor);
90851
+ if (!factor || typeof factor.id !== "string" || !factor.id || !Array.isArray(factor.findings)) {
90852
+ pageComplete = false;
90853
+ coverage.omittedFindings += 1;
90854
+ continue;
90855
+ }
90856
+ for (const rawFinding of factor.findings) {
90857
+ const parsed = siteCrawlAuditFindingSchema.safeParse(rawFinding);
90858
+ if (!parsed.success) {
90859
+ coverage.omittedFindings += 1;
90860
+ pageComplete = false;
90861
+ continue;
90862
+ }
90863
+ const finding2 = parsed.data;
90864
+ if (factor.applicable === false) continue;
90865
+ if (finding2.type === "found" || finding2.type === "info" && mirroredCodes.has(finding2.code)) continue;
90866
+ const id = `page-issue:${digest(["page-finding-v1", factor.id, finding2.code, finding2.type])}`;
90867
+ if (seen2.has(id)) continue;
90868
+ seen2.add(id);
90869
+ const disposition = finding2.type === "missing" ? "actionable" : finding2.type === "timeout" || finding2.type === "unreachable" ? "unverified" : ADVISORY_CODES.has(finding2.code) ? "advisory" : "informational";
90870
+ const group = addGroup({
90871
+ id,
90872
+ stableIdentity: true,
90873
+ code: finding2.code,
90874
+ source: "page-finding",
90875
+ severity: null,
90876
+ factorId: factor.id,
90877
+ findingType: finding2.type,
90878
+ disposition,
90879
+ ...clip2(finding2.message),
90880
+ affectedPages: 0,
90881
+ examples: [],
90882
+ examplesTruncated: false
90883
+ });
90884
+ addMember(group, pageEvidence(page3.nodeKey, page3.url, finding2.message, factor));
90885
+ }
90886
+ }
90887
+ for (const rawDefect of Array.isArray(fields.criticalDefects) ? fields.criticalDefects : []) {
90888
+ const parsed = siteCrawlCriticalDefectSchema.safeParse(rawDefect);
90889
+ if (!parsed.success) {
90890
+ coverage.omittedFindings += 1;
90891
+ pageComplete = false;
90892
+ continue;
90893
+ }
90894
+ const defect = parsed.data;
90895
+ const id = `critical-issue:${digest(["critical-defect-v1", defect.id])}`;
90896
+ if (seen2.has(id)) continue;
90897
+ seen2.add(id);
90898
+ const group = addGroup({
90899
+ id,
90900
+ stableIdentity: true,
90901
+ code: defect.id,
90902
+ source: "critical-defect",
90903
+ factorId: null,
90904
+ severity: defect.severity,
90905
+ findingType: "missing",
90906
+ disposition: "actionable",
90907
+ ...clip2(defect.detail),
90908
+ affectedPages: 0,
90909
+ examples: [],
90910
+ examplesTruncated: false
90911
+ });
90912
+ if (defect.severity === "critical") group.severity = "critical";
90913
+ addMember(group, {
90914
+ ...pageEvidence(page3.nodeKey, page3.url, defect.detail),
90915
+ recommendations: [defect.recommendation.slice(0, 400)],
90916
+ recommendationsScope: "issue",
90917
+ recommendationsTruncated: defect.recommendation.length > 400
90918
+ });
90919
+ }
90920
+ if (pageComplete) coverage.pagesWithFindingEvidence += 1;
90921
+ else coverage.pagesWithoutFindingEvidence += 1;
90922
+ }
90923
+ if (pages.length < 200) break;
90924
+ after = pages.at(-1).nodeKey;
90925
+ }
90926
+ if (coverage.pagesWithFindingEvidence > 0 || groups.size > 0 && coverage.omittedFindings > 0) {
90927
+ coverage.pageFindings = coverage.pagesWithoutFindingEvidence > 0 || coverage.omittedFindings > 0 ? "partial" : "available";
90928
+ }
90929
+ }
90930
+ const usable = coverage.pageFindings !== "unavailable" || coverage.crossPage === "available" || groups.size > 0;
90931
+ provenance.state = !usable ? "unavailable" : snapshot.complete && coverage.pageFindings === "available" && coverage.crossPage === "available" ? "available" : "partial";
90932
+ const basis = evidenceHash.digest("hex");
90933
+ if (query.cursor && query.cursor !== encodeCursor2(basis, offset)) {
90934
+ throw validationError("Stored audit issue cursor does not match this project, run, attempt, issue, or retained evidence");
90935
+ }
90936
+ const issues = [...groups.values()].sort((a, b) => DISPOSITION_ORDER[a.disposition] - DISPOSITION_ORDER[b.disposition] || (a.severity === "critical" ? 0 : a.severity === "warning" ? 1 : 2) - (b.severity === "critical" ? 0 : b.severity === "warning" ? 1 : 2) || b.affectedPages - a.affectedPages || a.id.localeCompare(b.id));
90937
+ if (issueId !== void 0 && !groups.has(issueId) && usable) throw notFound("Stored audit issue", issueId);
90938
+ provenance.total = usable ? issueId === void 0 ? issues.length : selectedMembers : null;
90939
+ if (offset > 0 && (provenance.total === null || offset >= provenance.total)) throw validationError("Stored audit issue cursor is out of range");
90940
+ const candidates = issueId === void 0 ? issues.slice(offset, offset + limit) : members.slice(0, limit);
90941
+ const responseForLength = (length2) => {
90942
+ const nextCursor = provenance.total !== null && offset + length2 < provenance.total ? encodeCursor2(basis, offset + length2) : null;
90943
+ return issueId === void 0 ? { ...provenance, nextCursor, issues: issues.slice(offset, offset + length2) } : { ...provenance, nextCursor, issueId, pages: members.slice(0, length2) };
90944
+ };
90945
+ let length = candidates.length;
90946
+ while (length > 0 && Buffer.byteLength(JSON.stringify(responseForLength(length), null, 2)) > MAX_RESPONSE_BYTES) length -= 1;
90947
+ if (length === 0 && candidates.length && issueId === void 0) {
90948
+ const first = issues[offset];
90949
+ while (first.examples.length > 1 && Buffer.byteLength(JSON.stringify(responseForLength(1), null, 2)) > MAX_RESPONSE_BYTES) {
90950
+ first.examples.pop();
90951
+ first.examplesTruncated = true;
90952
+ }
90953
+ if (Buffer.byteLength(JSON.stringify(responseForLength(1), null, 2)) <= MAX_RESPONSE_BYTES) length = 1;
90954
+ }
90955
+ const response = responseForLength(length);
90956
+ if (candidates.length && length === 0 || Buffer.byteLength(JSON.stringify(response, null, 2)) > MAX_RESPONSE_BYTES) {
90957
+ throw validationError("Stored audit issue exceeds the 16 KiB response budget");
90958
+ }
90959
+ return response;
90960
+ }
90961
+
90962
+ // ../api-routes/src/technical-aeo.ts
90618
90963
  var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
90619
90964
  ...SiteCrawlFetchedStates,
90620
90965
  // Snapshots published before the full-crawl contract used this aggregate
@@ -90683,7 +91028,7 @@ function parseBoolean2(value) {
90683
91028
  if (value === false || value === "false" || value === "0") return false;
90684
91029
  return null;
90685
91030
  }
90686
- function encodeCursor2(offset) {
91031
+ function encodeCursor3(offset) {
90687
91032
  return Buffer.from(JSON.stringify({ offset })).toString("base64url");
90688
91033
  }
90689
91034
  function decodeCursor(value) {
@@ -90850,13 +91195,13 @@ function parseLinkKind(value) {
90850
91195
  return allowed.data;
90851
91196
  }
90852
91197
  function linkKindFilter(linkKind) {
90853
- if (linkKind === SiteHealthLinkKinds.content) return eq94(siteCrawlEdges.isTemplate, false);
90854
- if (linkKind === SiteHealthLinkKinds.template) return eq94(siteCrawlEdges.isTemplate, true);
91198
+ if (linkKind === SiteHealthLinkKinds.content) return eq95(siteCrawlEdges.isTemplate, false);
91199
+ if (linkKind === SiteHealthLinkKinds.template) return eq95(siteCrawlEdges.isTemplate, true);
90855
91200
  return void 0;
90856
91201
  }
90857
91202
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
90858
91203
  var graphTargetNode = alias(siteCrawlGraphNodes, "site_crawl_graph_target_node");
90859
- var PAGE_CHANGE_PREDICATE = sql34`(
91204
+ var PAGE_CHANGE_PREDICATE = sql35`(
90860
91205
  current.url IS NOT previous.url
90861
91206
  OR current.final_url IS NOT previous.final_url
90862
91207
  OR current.path IS NOT previous.path
@@ -90878,7 +91223,7 @@ var PAGE_CHANGE_PREDICATE = sql34`(
90878
91223
  OR current.link_score_raw IS NOT previous.link_score_raw
90879
91224
  OR current.link_score_normalized IS NOT previous.link_score_normalized
90880
91225
  )`;
90881
- var LINK_CHANGE_PREDICATE = sql34`(
91226
+ var LINK_CHANGE_PREDICATE = sql35`(
90882
91227
  current.source_node_key IS NOT previous.source_node_key
90883
91228
  OR current.source_url IS NOT previous.source_url
90884
91229
  OR current.target_node_key IS NOT previous.target_node_key
@@ -90955,20 +91300,20 @@ function changedFields(before, after, fields) {
90955
91300
  }
90956
91301
  async function technicalAeoRoutes(app, opts) {
90957
91302
  const visibleCrawlFilters = (projectId) => [
90958
- eq94(siteCrawlSnapshots.projectId, projectId),
90959
- eq94(runs.projectId, projectId),
90960
- eq94(runs.kind, RunKinds["site-audit"]),
91303
+ eq95(siteCrawlSnapshots.projectId, projectId),
91304
+ eq95(runs.projectId, projectId),
91305
+ eq95(runs.kind, RunKinds["site-audit"]),
90961
91306
  inArray38(runs.status, SURFACEABLE_STATUSES),
90962
91307
  notProbeRun()
90963
91308
  ];
90964
91309
  const availableScanDatesFor = (projectId, requestedDate) => {
90965
91310
  if (!requestedDate) return void 0;
90966
- const date = sql34`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
91311
+ const date = sql35`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
90967
91312
  const filters = visibleCrawlFilters(projectId);
90968
- const allDates = and77(...filters);
90969
- const matchingDates = and77(...filters, eq94(sql34`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
90970
- const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc43(date)).limit(10).all().map((row) => row.date);
90971
- const countDates = (where) => app.db.select({ value: sql34`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
91313
+ const allDates = and78(...filters);
91314
+ const matchingDates = and78(...filters, eq95(sql35`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
91315
+ const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc43(date)).limit(10).all().map((row) => row.date);
91316
+ const countDates = (where) => app.db.select({ value: sql35`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
90972
91317
  return {
90973
91318
  recentDates: readDates(allDates),
90974
91319
  totalDates: countDates(allDates),
@@ -90984,9 +91329,9 @@ async function technicalAeoRoutes(app, opts) {
90984
91329
  nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
90985
91330
  filters.push(gte24(siteCrawlSnapshots.createdAt, requestedDate), lt13(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
90986
91331
  }
90987
- const selectionFilters = scope.scoredOnly ? [...filters, sql34`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
90988
- const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(and77(...selectionFilters, runId ? eq94(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
90989
- desc43(sql34`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
91332
+ const selectionFilters = scope.scoredOnly ? [...filters, sql35`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
91333
+ const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(...selectionFilters, runId ? eq95(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
91334
+ desc43(sql35`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
90990
91335
  desc43(siteCrawlSnapshots.complete),
90991
91336
  desc43(siteCrawlSnapshots.pagesFetched),
90992
91337
  desc43(siteCrawlSnapshots.createdAt),
@@ -90996,18 +91341,18 @@ async function technicalAeoRoutes(app, opts) {
90996
91341
  const date = selected.snapshot.createdAt.slice(0, 10);
90997
91342
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
90998
91343
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
90999
- const sameDate = and77(
91344
+ const sameDate = and78(
91000
91345
  ...filters,
91001
91346
  gte24(siteCrawlSnapshots.createdAt, date),
91002
91347
  lt13(siteCrawlSnapshots.createdAt, nextDate.toISOString())
91003
91348
  );
91004
- const sameDateRunCount = app.db.select({ value: count7() }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
91349
+ const sameDateRunCount = app.db.select({ value: count7() }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
91005
91350
  const candidates = app.db.select({
91006
91351
  runId: siteCrawlSnapshots.runId,
91007
91352
  createdAt: siteCrawlSnapshots.createdAt,
91008
91353
  complete: siteCrawlSnapshots.complete,
91009
91354
  pages: siteCrawlSnapshots.pagesFetched
91010
- }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
91355
+ }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
91011
91356
  desc43(siteCrawlSnapshots.complete),
91012
91357
  desc43(siteCrawlSnapshots.pagesFetched),
91013
91358
  desc43(siteCrawlSnapshots.createdAt),
@@ -91025,16 +91370,16 @@ async function technicalAeoRoutes(app, opts) {
91025
91370
  };
91026
91371
  const resolveAudit = (projectId, runId, recordedBefore) => {
91027
91372
  const filters = [
91028
- eq94(siteAuditSnapshots.projectId, projectId),
91029
- eq94(runs.projectId, projectId),
91030
- eq94(runs.kind, RunKinds["site-audit"]),
91373
+ eq95(siteAuditSnapshots.projectId, projectId),
91374
+ eq95(runs.projectId, projectId),
91375
+ eq95(runs.kind, RunKinds["site-audit"]),
91031
91376
  inArray38(runs.status, SURFACEABLE_STATUSES),
91032
91377
  notProbeRun(),
91033
91378
  ...recordedBefore ? [lt13(siteAuditSnapshots.createdAt, recordedBefore)] : []
91034
91379
  ];
91035
- const complete = sql34`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
91036
- const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq94(siteAuditSnapshots.runId, runs.id)).where(and77(...filters, pinnedRunId ? eq94(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
91037
- desc43(sql34`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
91380
+ const complete = sql35`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
91381
+ const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(and78(...filters, pinnedRunId ? eq95(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
91382
+ desc43(sql35`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
91038
91383
  desc43(complete),
91039
91384
  desc43(siteAuditSnapshots.pagesAudited),
91040
91385
  desc43(siteAuditSnapshots.createdAt),
@@ -91054,18 +91399,18 @@ async function technicalAeoRoutes(app, opts) {
91054
91399
  const date = selected.snap.createdAt.slice(0, 10);
91055
91400
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
91056
91401
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
91057
- const sameDate = and77(
91402
+ const sameDate = and78(
91058
91403
  ...filters,
91059
91404
  gte24(siteAuditSnapshots.createdAt, date),
91060
91405
  lt13(siteAuditSnapshots.createdAt, nextDate.toISOString())
91061
91406
  );
91062
- const sameDateRunCount = app.db.select({ value: count7() }).from(siteAuditSnapshots).innerJoin(runs, eq94(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
91407
+ const sameDateRunCount = app.db.select({ value: count7() }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
91063
91408
  const candidates = app.db.select({
91064
91409
  runId: siteAuditSnapshots.runId,
91065
91410
  createdAt: siteAuditSnapshots.createdAt,
91066
91411
  complete,
91067
91412
  pages: siteAuditSnapshots.pagesAudited
91068
- }).from(siteAuditSnapshots).innerJoin(runs, eq94(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
91413
+ }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
91069
91414
  desc43(complete),
91070
91415
  desc43(siteAuditSnapshots.pagesAudited),
91071
91416
  desc43(siteAuditSnapshots.createdAt),
@@ -91084,9 +91429,9 @@ async function technicalAeoRoutes(app, opts) {
91084
91429
  };
91085
91430
  const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
91086
91431
  const crawlReasonGroups = (where) => {
91087
- const canonicalOther = sql34`case when ${siteCrawlPages.canonicalNodeKey} is not null
91432
+ const canonicalOther = sql35`case when ${siteCrawlPages.canonicalNodeKey} is not null
91088
91433
  and ${siteCrawlPages.canonicalNodeKey} <> ${siteCrawlPages.nodeKey} then 1 else 0 end`;
91089
- const canonicalReason = sql34`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
91434
+ const canonicalReason = sql35`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
91090
91435
  where value = ${SiteCrawlIndexabilityReasons.canonicalToOther})`;
91091
91436
  const groups = app.db.select({
91092
91437
  fetchState: siteCrawlPages.fetchState,
@@ -91095,7 +91440,7 @@ async function technicalAeoRoutes(app, opts) {
91095
91440
  canonicalOther,
91096
91441
  canonicalReason,
91097
91442
  pages: count7(),
91098
- exampleUrl: sql34`min(${siteCrawlPages.url})`
91443
+ exampleUrl: sql35`min(${siteCrawlPages.url})`
91099
91444
  }).from(siteCrawlPages).where(where).groupBy(
91100
91445
  siteCrawlPages.fetchState,
91101
91446
  siteCrawlPages.indexabilityState,
@@ -91131,10 +91476,10 @@ async function technicalAeoRoutes(app, opts) {
91131
91476
  };
91132
91477
  const inventorySummaryFor = (scope) => {
91133
91478
  if (!scope) return null;
91134
- const groups = crawlReasonGroups(and77(
91135
- eq94(siteCrawlPages.projectId, scope.projectId),
91136
- eq94(siteCrawlPages.runId, scope.runId),
91137
- eq94(siteCrawlPages.attemptId, scope.attemptId)
91479
+ const groups = crawlReasonGroups(and78(
91480
+ eq95(siteCrawlPages.projectId, scope.projectId),
91481
+ eq95(siteCrawlPages.runId, scope.runId),
91482
+ eq95(siteCrawlPages.attemptId, scope.attemptId)
91138
91483
  ));
91139
91484
  const total = groups.reduce((sum, group) => sum + group.pages, 0);
91140
91485
  const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
@@ -91154,33 +91499,33 @@ async function technicalAeoRoutes(app, opts) {
91154
91499
  const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
91155
91500
  const pageInScope = (scope, selector) => {
91156
91501
  if (!selector.nodeKey && !selector.url) return null;
91157
- return app.db.select().from(siteCrawlPages).where(and77(
91158
- eq94(siteCrawlPages.projectId, scope.projectId),
91159
- eq94(siteCrawlPages.runId, scope.runId),
91160
- eq94(siteCrawlPages.attemptId, scope.attemptId),
91161
- selector.nodeKey ? eq94(siteCrawlPages.nodeKey, selector.nodeKey) : eq94(siteCrawlPages.url, selector.url)
91162
- )).orderBy(asc17(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
91163
- };
91164
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and77(
91165
- eq94(siteCrawlPages.projectId, scope.projectId),
91166
- eq94(siteCrawlPages.runId, scope.runId),
91167
- eq94(siteCrawlPages.attemptId, scope.attemptId),
91168
- eq94(siteCrawlPages.depth, 0)
91169
- )).orderBy(asc17(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
91170
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and77(
91171
- eq94(runs.id, runId),
91172
- eq94(runs.projectId, projectId),
91173
- eq94(runs.kind, RunKinds["site-audit"]),
91502
+ return app.db.select().from(siteCrawlPages).where(and78(
91503
+ eq95(siteCrawlPages.projectId, scope.projectId),
91504
+ eq95(siteCrawlPages.runId, scope.runId),
91505
+ eq95(siteCrawlPages.attemptId, scope.attemptId),
91506
+ selector.nodeKey ? eq95(siteCrawlPages.nodeKey, selector.nodeKey) : eq95(siteCrawlPages.url, selector.url)
91507
+ )).orderBy(asc18(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
91508
+ };
91509
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and78(
91510
+ eq95(siteCrawlPages.projectId, scope.projectId),
91511
+ eq95(siteCrawlPages.runId, scope.runId),
91512
+ eq95(siteCrawlPages.attemptId, scope.attemptId),
91513
+ eq95(siteCrawlPages.depth, 0)
91514
+ )).orderBy(asc18(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
91515
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and78(
91516
+ eq95(runs.id, runId),
91517
+ eq95(runs.projectId, projectId),
91518
+ eq95(runs.kind, RunKinds["site-audit"]),
91174
91519
  inArray38(runs.status, SURFACEABLE_STATUSES),
91175
91520
  notProbeRun()
91176
91521
  )).limit(1).get());
91177
91522
  const assertKnownAuditRun = (projectId, runId) => {
91178
91523
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
91179
91524
  };
91180
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq94(siteAuditSnapshots.runId, runs.id)).where(and77(
91181
- eq94(siteAuditSnapshots.projectId, projectId),
91182
- eq94(runs.projectId, projectId),
91183
- eq94(runs.kind, RunKinds["site-audit"]),
91525
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(and78(
91526
+ eq95(siteAuditSnapshots.projectId, projectId),
91527
+ eq95(runs.projectId, projectId),
91528
+ eq95(runs.kind, RunKinds["site-audit"]),
91184
91529
  inArray38(runs.status, SURFACEABLE_STATUSES),
91185
91530
  notProbeRun()
91186
91531
  )).limit(1).get());
@@ -91282,14 +91627,14 @@ async function technicalAeoRoutes(app, opts) {
91282
91627
  };
91283
91628
  }
91284
91629
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
91285
- const conds = [eq94(siteAuditPages.projectId, project.id), eq94(siteAuditPages.runId, latest.snap.runId)];
91286
- if (statusFilter) conds.push(eq94(siteAuditPages.status, statusFilter));
91287
- const where = and77(...conds);
91630
+ const conds = [eq95(siteAuditPages.projectId, project.id), eq95(siteAuditPages.runId, latest.snap.runId)];
91631
+ if (statusFilter) conds.push(eq95(siteAuditPages.status, statusFilter));
91632
+ const where = and78(...conds);
91288
91633
  const totalRow = app.db.select({ value: count7() }).from(siteAuditPages).where(where).get();
91289
91634
  const total = totalRow?.value ?? 0;
91290
91635
  const limit = parsePositiveInt(request.query.limit, 100, 500);
91291
91636
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
91292
- const orderBy = request.query.sort === "score-desc" ? desc43(siteAuditPages.overallScore) : request.query.sort === "url" ? asc17(siteAuditPages.url) : asc17(siteAuditPages.overallScore);
91637
+ const orderBy = request.query.sort === "score-desc" ? desc43(siteAuditPages.overallScore) : request.query.sort === "url" ? asc18(siteAuditPages.url) : asc18(siteAuditPages.overallScore);
91293
91638
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
91294
91639
  const pages = rows.map((row) => ({
91295
91640
  url: row.url,
@@ -91308,10 +91653,10 @@ async function technicalAeoRoutes(app, opts) {
91308
91653
  auditedAt: siteAuditSnapshots.auditedAt,
91309
91654
  aggregateScore: siteAuditSnapshots.aggregateScore,
91310
91655
  pagesAudited: siteAuditSnapshots.pagesAudited
91311
- }).from(siteAuditSnapshots).innerJoin(runs, eq94(siteAuditSnapshots.runId, runs.id)).where(and77(
91312
- eq94(siteAuditSnapshots.projectId, project.id),
91313
- eq94(runs.projectId, project.id),
91314
- eq94(runs.kind, RunKinds["site-audit"]),
91656
+ }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(and78(
91657
+ eq95(siteAuditSnapshots.projectId, project.id),
91658
+ eq95(runs.projectId, project.id),
91659
+ eq95(runs.kind, RunKinds["site-audit"]),
91315
91660
  inArray38(runs.status, SURFACEABLE_STATUSES),
91316
91661
  notProbeRun()
91317
91662
  )).orderBy(desc43(siteAuditSnapshots.createdAt)).limit(limit).all();
@@ -91414,10 +91759,10 @@ async function technicalAeoRoutes(app, opts) {
91414
91759
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
91415
91760
  snapshot.rootUrl
91416
91761
  )?.nodeKey ?? null;
91417
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and77(
91418
- eq94(siteCrawlGraphLayouts.projectId, project.id),
91419
- eq94(siteCrawlGraphLayouts.runId, snapshot.runId),
91420
- eq94(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
91762
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and78(
91763
+ eq95(siteCrawlGraphLayouts.projectId, project.id),
91764
+ eq95(siteCrawlGraphLayouts.runId, snapshot.runId),
91765
+ eq95(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
91421
91766
  )).limit(1).get();
91422
91767
  if (!persistedLayout) {
91423
91768
  return {
@@ -91470,9 +91815,9 @@ async function technicalAeoRoutes(app, opts) {
91470
91815
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
91471
91816
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
91472
91817
  const graphScope = [
91473
- eq94(siteCrawlGraphNodes.projectId, project.id),
91474
- eq94(siteCrawlGraphNodes.runId, snapshot.runId),
91475
- eq94(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
91818
+ eq95(siteCrawlGraphNodes.projectId, project.id),
91819
+ eq95(siteCrawlGraphNodes.runId, snapshot.runId),
91820
+ eq95(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
91476
91821
  ];
91477
91822
  const nodeRows = app.db.select({
91478
91823
  nodeKey: siteCrawlPages.nodeKey,
@@ -91491,18 +91836,18 @@ async function technicalAeoRoutes(app, opts) {
91491
91836
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
91492
91837
  x: siteCrawlGraphNodes.x,
91493
91838
  y: siteCrawlGraphNodes.y
91494
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and77(
91495
- eq94(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
91496
- eq94(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
91497
- eq94(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
91498
- eq94(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
91499
- )).where(and77(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc17(siteCrawlGraphNodes.sampleRank)).all();
91839
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and78(
91840
+ eq95(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
91841
+ eq95(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
91842
+ eq95(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
91843
+ eq95(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
91844
+ )).where(and78(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc18(siteCrawlGraphNodes.sampleRank)).all();
91500
91845
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
91501
91846
  ...row,
91502
91847
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
91503
91848
  healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
91504
91849
  }));
91505
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq94(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
91850
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq95(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
91506
91851
  const edges = app.db.select({
91507
91852
  edgeKey: siteCrawlGraphEdges.edgeKey,
91508
91853
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -91510,25 +91855,25 @@ async function technicalAeoRoutes(app, opts) {
91510
91855
  followable: siteCrawlGraphEdges.followable,
91511
91856
  occurrences: siteCrawlGraphEdges.occurrences,
91512
91857
  isTemplate: siteCrawlGraphEdges.isTemplate
91513
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and77(
91514
- eq94(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
91515
- eq94(graphSourceNode.runId, siteCrawlGraphEdges.runId),
91516
- eq94(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
91517
- eq94(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
91858
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and78(
91859
+ eq95(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
91860
+ eq95(graphSourceNode.runId, siteCrawlGraphEdges.runId),
91861
+ eq95(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
91862
+ eq95(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
91518
91863
  lt13(graphSourceNode.sampleRank, maxNodes)
91519
- )).innerJoin(graphTargetNode, and77(
91520
- eq94(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
91521
- eq94(graphTargetNode.runId, siteCrawlGraphEdges.runId),
91522
- eq94(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
91523
- eq94(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
91864
+ )).innerJoin(graphTargetNode, and78(
91865
+ eq95(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
91866
+ eq95(graphTargetNode.runId, siteCrawlGraphEdges.runId),
91867
+ eq95(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
91868
+ eq95(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
91524
91869
  lt13(graphTargetNode.sampleRank, maxNodes)
91525
- )).where(and77(
91526
- eq94(siteCrawlGraphEdges.projectId, project.id),
91527
- eq94(siteCrawlGraphEdges.runId, snapshot.runId),
91528
- eq94(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
91870
+ )).where(and78(
91871
+ eq95(siteCrawlGraphEdges.projectId, project.id),
91872
+ eq95(siteCrawlGraphEdges.runId, snapshot.runId),
91873
+ eq95(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
91529
91874
  lt13(siteCrawlGraphEdges.sampleRank, maxEdges),
91530
91875
  graphLinkKindFilter
91531
- )).orderBy(asc17(siteCrawlGraphEdges.sampleRank)).all();
91876
+ )).orderBy(asc18(siteCrawlGraphEdges.sampleRank)).all();
91532
91877
  const totalNodes = persistedLayout.totalNodes;
91533
91878
  const totalEdges = persistedLayout.totalEdges;
91534
91879
  const totalTemplateEdges = persistedLayout.totalTemplateEdges;
@@ -91620,17 +91965,17 @@ async function technicalAeoRoutes(app, opts) {
91620
91965
  let frontier = [focus.nodeKey];
91621
91966
  let queryTruncated = false;
91622
91967
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
91623
- const candidates = app.db.select().from(siteCrawlEdges).where(and77(
91624
- eq94(siteCrawlEdges.projectId, scope.projectId),
91625
- eq94(siteCrawlEdges.runId, scope.runId),
91626
- eq94(siteCrawlEdges.attemptId, scope.attemptId),
91627
- eq94(siteCrawlEdges.internal, true),
91968
+ const candidates = app.db.select().from(siteCrawlEdges).where(and78(
91969
+ eq95(siteCrawlEdges.projectId, scope.projectId),
91970
+ eq95(siteCrawlEdges.runId, scope.runId),
91971
+ eq95(siteCrawlEdges.attemptId, scope.attemptId),
91972
+ eq95(siteCrawlEdges.internal, true),
91628
91973
  isNotNull9(siteCrawlEdges.targetNodeKey),
91629
91974
  or22(
91630
91975
  inArray38(siteCrawlEdges.sourceNodeKey, frontier),
91631
91976
  inArray38(siteCrawlEdges.targetNodeKey, frontier)
91632
91977
  )
91633
- )).orderBy(asc17(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
91978
+ )).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
91634
91979
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
91635
91980
  const next = /* @__PURE__ */ new Set();
91636
91981
  for (const edge of candidates.slice(0, maxEdges + maxNodes)) {
@@ -91661,19 +92006,19 @@ async function technicalAeoRoutes(app, opts) {
91661
92006
  frontier = [...next].sort();
91662
92007
  }
91663
92008
  const nodeKeys = [...distances.keys()];
91664
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and77(
91665
- eq94(siteCrawlPages.projectId, scope.projectId),
91666
- eq94(siteCrawlPages.runId, scope.runId),
91667
- eq94(siteCrawlPages.attemptId, scope.attemptId),
92009
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and78(
92010
+ eq95(siteCrawlPages.projectId, scope.projectId),
92011
+ eq95(siteCrawlPages.runId, scope.runId),
92012
+ eq95(siteCrawlPages.attemptId, scope.attemptId),
91668
92013
  inArray38(siteCrawlPages.nodeKey, nodeKeys)
91669
92014
  )).all();
91670
92015
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
91671
92016
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
91672
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and77(
91673
- eq94(siteCrawlEdges.projectId, scope.projectId),
91674
- eq94(siteCrawlEdges.runId, scope.runId),
91675
- eq94(siteCrawlEdges.attemptId, scope.attemptId),
91676
- eq94(siteCrawlEdges.internal, true),
92017
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and78(
92018
+ eq95(siteCrawlEdges.projectId, scope.projectId),
92019
+ eq95(siteCrawlEdges.runId, scope.runId),
92020
+ eq95(siteCrawlEdges.attemptId, scope.attemptId),
92021
+ eq95(siteCrawlEdges.internal, true),
91677
92022
  isNotNull9(siteCrawlEdges.targetNodeKey),
91678
92023
  inArray38(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
91679
92024
  inArray38(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
@@ -91769,22 +92114,22 @@ async function technicalAeoRoutes(app, opts) {
91769
92114
  let found = fromPage.nodeKey === toPage.nodeKey;
91770
92115
  let truncated = false;
91771
92116
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
91772
- const candidates = app.db.select().from(siteCrawlEdges).where(and77(
91773
- eq94(siteCrawlEdges.projectId, scope.projectId),
91774
- eq94(siteCrawlEdges.runId, scope.runId),
91775
- eq94(siteCrawlEdges.attemptId, scope.attemptId),
91776
- eq94(siteCrawlEdges.internal, true),
91777
- eq94(siteCrawlEdges.followable, true),
91778
- eq94(siteCrawlEdges.relation, "anchor"),
92117
+ const candidates = app.db.select().from(siteCrawlEdges).where(and78(
92118
+ eq95(siteCrawlEdges.projectId, scope.projectId),
92119
+ eq95(siteCrawlEdges.runId, scope.runId),
92120
+ eq95(siteCrawlEdges.attemptId, scope.attemptId),
92121
+ eq95(siteCrawlEdges.internal, true),
92122
+ eq95(siteCrawlEdges.followable, true),
92123
+ eq95(siteCrawlEdges.relation, "anchor"),
91779
92124
  isNotNull9(siteCrawlEdges.targetNodeKey),
91780
92125
  inArray38(siteCrawlEdges.sourceNodeKey, frontier)
91781
- )).orderBy(asc17(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
92126
+ )).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
91782
92127
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
91783
92128
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
91784
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and77(
91785
- eq94(siteCrawlPages.projectId, scope.projectId),
91786
- eq94(siteCrawlPages.runId, scope.runId),
91787
- eq94(siteCrawlPages.attemptId, scope.attemptId),
92129
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and78(
92130
+ eq95(siteCrawlPages.projectId, scope.projectId),
92131
+ eq95(siteCrawlPages.runId, scope.runId),
92132
+ eq95(siteCrawlPages.attemptId, scope.attemptId),
91788
92133
  inArray38(siteCrawlPages.nodeKey, candidateTargetKeys)
91789
92134
  )).all().map((row) => row.nodeKey));
91790
92135
  const next = /* @__PURE__ */ new Set();
@@ -91826,10 +92171,10 @@ async function technicalAeoRoutes(app, opts) {
91826
92171
  pathEdges.unshift(step.edge);
91827
92172
  pathKeys.unshift(step.nodeKey);
91828
92173
  }
91829
- const pathRows = app.db.select().from(siteCrawlPages).where(and77(
91830
- eq94(siteCrawlPages.projectId, scope.projectId),
91831
- eq94(siteCrawlPages.runId, scope.runId),
91832
- eq94(siteCrawlPages.attemptId, scope.attemptId),
92174
+ const pathRows = app.db.select().from(siteCrawlPages).where(and78(
92175
+ eq95(siteCrawlPages.projectId, scope.projectId),
92176
+ eq95(siteCrawlPages.runId, scope.runId),
92177
+ eq95(siteCrawlPages.attemptId, scope.attemptId),
91833
92178
  inArray38(siteCrawlPages.nodeKey, pathKeys)
91834
92179
  )).all();
91835
92180
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
@@ -91858,21 +92203,21 @@ async function technicalAeoRoutes(app, opts) {
91858
92203
  throw validationError("change must be all, added, removed, or changed");
91859
92204
  }
91860
92205
  const completeSnapshotFilters = [
91861
- eq94(siteCrawlSnapshots.projectId, project.id),
91862
- eq94(runs.projectId, project.id),
91863
- eq94(runs.kind, RunKinds["site-audit"]),
91864
- eq94(runs.status, RunStatuses.completed),
91865
- eq94(siteCrawlSnapshots.complete, true),
92206
+ eq95(siteCrawlSnapshots.projectId, project.id),
92207
+ eq95(runs.projectId, project.id),
92208
+ eq95(runs.kind, RunKinds["site-audit"]),
92209
+ eq95(runs.status, RunStatuses.completed),
92210
+ eq95(siteCrawlSnapshots.complete, true),
91866
92211
  notProbeRun()
91867
92212
  ];
91868
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(and77(...completeSnapshotFilters)).orderBy(desc43(siteCrawlSnapshots.createdAt), desc43(siteCrawlSnapshots.runId)).limit(1).get();
92213
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(...completeSnapshotFilters)).orderBy(desc43(siteCrawlSnapshots.createdAt), desc43(siteCrawlSnapshots.runId)).limit(1).get();
91869
92214
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
91870
- const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(and77(
92215
+ const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(
91871
92216
  ...completeSnapshotFilters,
91872
92217
  or22(
91873
92218
  lt13(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
91874
- and77(
91875
- eq94(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
92219
+ and78(
92220
+ eq95(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
91876
92221
  lt13(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
91877
92222
  )
91878
92223
  )
@@ -91960,9 +92305,9 @@ async function technicalAeoRoutes(app, opts) {
91960
92305
  const summary4 = { pages: zero2(), links: zero2() };
91961
92306
  if (scopeFilter !== "links") {
91962
92307
  if (includeAdded || includeChanged) {
91963
- const addedExpression = includeAdded ? sql34`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql34`0`;
91964
- const changedExpression = includeChanged ? sql34`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${PAGE_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql34`0`;
91965
- const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql34`
92308
+ const addedExpression = includeAdded ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql35`0`;
92309
+ const changedExpression = includeChanged ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${PAGE_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql35`0`;
92310
+ const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql35`
91966
92311
  SELECT ${addedExpression} AS added, ${changedExpression} AS changed
91967
92312
  FROM site_crawl_pages AS current
91968
92313
  LEFT JOIN site_crawl_pages AS previous
@@ -91978,7 +92323,7 @@ async function technicalAeoRoutes(app, opts) {
91978
92323
  summary4.pages.changed = Number(counts2.changed);
91979
92324
  }
91980
92325
  if (includeRemoved) {
91981
- const [counts2 = { removed: 0 }] = app.db.all(sql34`
92326
+ const [counts2 = { removed: 0 }] = app.db.all(sql35`
91982
92327
  SELECT COUNT(*) AS removed
91983
92328
  FROM site_crawl_pages AS previous
91984
92329
  LEFT JOIN site_crawl_pages AS current
@@ -91996,9 +92341,9 @@ async function technicalAeoRoutes(app, opts) {
91996
92341
  }
91997
92342
  if (scopeFilter !== "pages") {
91998
92343
  if (includeAdded || includeChanged) {
91999
- const addedExpression = includeAdded ? sql34`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql34`0`;
92000
- const changedExpression = includeChanged ? sql34`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${LINK_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql34`0`;
92001
- const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql34`
92344
+ const addedExpression = includeAdded ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql35`0`;
92345
+ const changedExpression = includeChanged ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${LINK_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql35`0`;
92346
+ const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql35`
92002
92347
  SELECT ${addedExpression} AS added, ${changedExpression} AS changed
92003
92348
  FROM site_crawl_edges AS current
92004
92349
  LEFT JOIN site_crawl_edges AS previous
@@ -92016,7 +92361,7 @@ async function technicalAeoRoutes(app, opts) {
92016
92361
  summary4.links.changed = Number(counts2.changed);
92017
92362
  }
92018
92363
  if (includeRemoved) {
92019
- const [counts2 = { removed: 0 }] = app.db.all(sql34`
92364
+ const [counts2 = { removed: 0 }] = app.db.all(sql35`
92020
92365
  SELECT COUNT(*) AS removed
92021
92366
  FROM site_crawl_edges AS previous
92022
92367
  LEFT JOIN site_crawl_edges AS current
@@ -92046,48 +92391,48 @@ async function technicalAeoRoutes(app, opts) {
92046
92391
  const includeRemoved = changeFilter === "all" || changeFilter === "removed";
92047
92392
  const table = entity === "page" ? "site_crawl_pages" : "site_crawl_edges";
92048
92393
  const keyColumn = entity === "page" ? "node_key" : "edge_key";
92049
- const internalCurrent = entity === "link" ? sql34`AND current.internal = 1` : sql34``;
92050
- const internalPreviousJoin = entity === "link" ? sql34`AND previous.internal = 1` : sql34``;
92051
- const internalPrevious = entity === "link" ? sql34`AND previous.internal = 1` : sql34``;
92052
- const internalCurrentJoin = entity === "link" ? sql34`AND current.internal = 1` : sql34``;
92394
+ const internalCurrent = entity === "link" ? sql35`AND current.internal = 1` : sql35``;
92395
+ const internalPreviousJoin = entity === "link" ? sql35`AND previous.internal = 1` : sql35``;
92396
+ const internalPrevious = entity === "link" ? sql35`AND previous.internal = 1` : sql35``;
92397
+ const internalCurrentJoin = entity === "link" ? sql35`AND current.internal = 1` : sql35``;
92053
92398
  const difference2 = entity === "page" ? PAGE_CHANGE_PREDICATE : LINK_CHANGE_PREDICATE;
92054
- const afterKind = changeFilter === "added" ? sql34`previous.id IS NULL` : changeFilter === "changed" ? sql34`previous.id IS NOT NULL AND ${difference2}` : sql34`previous.id IS NULL OR (previous.id IS NOT NULL AND ${difference2})`;
92055
- const afterRows = includeAfter ? app.db.all(sql34`
92399
+ const afterKind = changeFilter === "added" ? sql35`previous.id IS NULL` : changeFilter === "changed" ? sql35`previous.id IS NOT NULL AND ${difference2}` : sql35`previous.id IS NULL OR (previous.id IS NOT NULL AND ${difference2})`;
92400
+ const afterRows = includeAfter ? app.db.all(sql35`
92056
92401
  SELECT ${entity} AS entity,
92057
92402
  CASE WHEN previous.id IS NULL THEN 'added' ELSE 'changed' END AS change,
92058
- current.${sql34.raw(keyColumn)} AS key
92059
- FROM ${sql34.raw(table)} AS current
92060
- LEFT JOIN ${sql34.raw(table)} AS previous
92403
+ current.${sql35.raw(keyColumn)} AS key
92404
+ FROM ${sql35.raw(table)} AS current
92405
+ LEFT JOIN ${sql35.raw(table)} AS previous
92061
92406
  ON previous.project_id = ${beforeScope.projectId}
92062
92407
  AND previous.run_id = ${beforeScope.runId}
92063
92408
  AND previous.attempt_id = ${beforeScope.attemptId}
92064
92409
  ${internalPreviousJoin}
92065
- AND previous.${sql34.raw(keyColumn)} = current.${sql34.raw(keyColumn)}
92410
+ AND previous.${sql35.raw(keyColumn)} = current.${sql35.raw(keyColumn)}
92066
92411
  WHERE current.project_id = ${afterScope.projectId}
92067
92412
  AND current.run_id = ${afterScope.runId}
92068
92413
  AND current.attempt_id = ${afterScope.attemptId}
92069
92414
  ${internalCurrent}
92070
- AND current.${sql34.raw(keyColumn)} > ${afterKey}
92415
+ AND current.${sql35.raw(keyColumn)} > ${afterKey}
92071
92416
  AND (${afterKind})
92072
- ORDER BY current.${sql34.raw(keyColumn)} ASC
92417
+ ORDER BY current.${sql35.raw(keyColumn)} ASC
92073
92418
  LIMIT ${wanted2}
92074
92419
  `) : [];
92075
- const removedRows = includeRemoved ? app.db.all(sql34`
92076
- SELECT ${entity} AS entity, 'removed' AS change, previous.${sql34.raw(keyColumn)} AS key
92077
- FROM ${sql34.raw(table)} AS previous
92078
- LEFT JOIN ${sql34.raw(table)} AS current
92420
+ const removedRows = includeRemoved ? app.db.all(sql35`
92421
+ SELECT ${entity} AS entity, 'removed' AS change, previous.${sql35.raw(keyColumn)} AS key
92422
+ FROM ${sql35.raw(table)} AS previous
92423
+ LEFT JOIN ${sql35.raw(table)} AS current
92079
92424
  ON current.project_id = ${afterScope.projectId}
92080
92425
  AND current.run_id = ${afterScope.runId}
92081
92426
  AND current.attempt_id = ${afterScope.attemptId}
92082
92427
  ${internalCurrentJoin}
92083
- AND current.${sql34.raw(keyColumn)} = previous.${sql34.raw(keyColumn)}
92428
+ AND current.${sql35.raw(keyColumn)} = previous.${sql35.raw(keyColumn)}
92084
92429
  WHERE previous.project_id = ${beforeScope.projectId}
92085
92430
  AND previous.run_id = ${beforeScope.runId}
92086
92431
  AND previous.attempt_id = ${beforeScope.attemptId}
92087
92432
  ${internalPrevious}
92088
- AND previous.${sql34.raw(keyColumn)} > ${afterKey}
92433
+ AND previous.${sql35.raw(keyColumn)} > ${afterKey}
92089
92434
  AND current.id IS NULL
92090
- ORDER BY previous.${sql34.raw(keyColumn)} ASC
92435
+ ORDER BY previous.${sql35.raw(keyColumn)} ASC
92091
92436
  LIMIT ${wanted2}
92092
92437
  `) : [];
92093
92438
  const merged = [];
@@ -92117,17 +92462,17 @@ async function technicalAeoRoutes(app, opts) {
92117
92462
  const visibleKeys = keyRows.slice(0, limit);
92118
92463
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
92119
92464
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
92120
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and77(
92121
- eq94(siteCrawlPages.projectId, scope.projectId),
92122
- eq94(siteCrawlPages.runId, scope.runId),
92123
- eq94(siteCrawlPages.attemptId, scope.attemptId),
92465
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and78(
92466
+ eq95(siteCrawlPages.projectId, scope.projectId),
92467
+ eq95(siteCrawlPages.runId, scope.runId),
92468
+ eq95(siteCrawlPages.attemptId, scope.attemptId),
92124
92469
  inArray38(siteCrawlPages.nodeKey, pageKeys)
92125
92470
  )).all();
92126
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and77(
92127
- eq94(siteCrawlEdges.projectId, scope.projectId),
92128
- eq94(siteCrawlEdges.runId, scope.runId),
92129
- eq94(siteCrawlEdges.attemptId, scope.attemptId),
92130
- eq94(siteCrawlEdges.internal, true),
92471
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and78(
92472
+ eq95(siteCrawlEdges.projectId, scope.projectId),
92473
+ eq95(siteCrawlEdges.runId, scope.runId),
92474
+ eq95(siteCrawlEdges.attemptId, scope.attemptId),
92475
+ eq95(siteCrawlEdges.internal, true),
92131
92476
  inArray38(siteCrawlEdges.edgeKey, linkKeys)
92132
92477
  )).all();
92133
92478
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -92189,6 +92534,26 @@ async function technicalAeoRoutes(app, opts) {
92189
92534
  changes
92190
92535
  };
92191
92536
  });
92537
+ app.get(
92538
+ "/projects/:name/technical-aeo/issues",
92539
+ async (request) => {
92540
+ const project = resolveProject(app.db, request.params.name);
92541
+ const query = parseSiteAuditIssueQuery(request.query);
92542
+ const target = resolveCrawl(project.id, query.runId);
92543
+ if (!target) assertKnownAuditRun(project.id, query.runId);
92544
+ return readSiteAuditIssues({ db: app.db, project, target, query });
92545
+ }
92546
+ );
92547
+ app.get(
92548
+ "/projects/:name/technical-aeo/issues/:issueId/pages",
92549
+ async (request) => {
92550
+ const project = resolveProject(app.db, request.params.name);
92551
+ const query = parseSiteAuditIssueQuery(request.query);
92552
+ const target = resolveCrawl(project.id, query.runId);
92553
+ if (!target) assertKnownAuditRun(project.id, query.runId);
92554
+ return readSiteAuditIssues({ db: app.db, project, target, query, issueId: request.params.issueId });
92555
+ }
92556
+ );
92192
92557
  app.get("/projects/:name/technical-aeo/crawl/pages/audit", async (request) => {
92193
92558
  const project = resolveProject(app.db, request.params.name);
92194
92559
  const scalarQueryString = (value, name) => {
@@ -92281,16 +92646,16 @@ async function technicalAeoRoutes(app, opts) {
92281
92646
  }
92282
92647
  const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
92283
92648
  const filters = [
92284
- eq94(siteCrawlPages.projectId, project.id),
92285
- eq94(siteCrawlPages.runId, snapshot.runId),
92286
- eq94(siteCrawlPages.attemptId, snapshot.attemptId)
92649
+ eq95(siteCrawlPages.projectId, project.id),
92650
+ eq95(siteCrawlPages.runId, snapshot.runId),
92651
+ eq95(siteCrawlPages.attemptId, snapshot.attemptId)
92287
92652
  ];
92288
- if (request.query.nodeKey) filters.push(eq94(siteCrawlPages.nodeKey, request.query.nodeKey));
92653
+ if (request.query.nodeKey) filters.push(eq95(siteCrawlPages.nodeKey, request.query.nodeKey));
92289
92654
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
92290
- if (inventoryEligible != null) filters.push(eq94(siteCrawlPages.inventoryEligible, inventoryEligible));
92291
- if (request.query.fetchState) filters.push(eq94(siteCrawlPages.fetchState, request.query.fetchState));
92292
- if (request.query.indexabilityState) filters.push(eq94(siteCrawlPages.indexabilityState, request.query.indexabilityState));
92293
- if (request.query.auditState) filters.push(eq94(siteCrawlPages.auditState, request.query.auditState));
92655
+ if (inventoryEligible != null) filters.push(eq95(siteCrawlPages.inventoryEligible, inventoryEligible));
92656
+ if (request.query.fetchState) filters.push(eq95(siteCrawlPages.fetchState, request.query.fetchState));
92657
+ if (request.query.indexabilityState) filters.push(eq95(siteCrawlPages.indexabilityState, request.query.indexabilityState));
92658
+ if (request.query.auditState) filters.push(eq95(siteCrawlPages.auditState, request.query.auditState));
92294
92659
  const healthState = parseSiteHealthState(request.query.healthState);
92295
92660
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
92296
92661
  const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
@@ -92308,17 +92673,17 @@ async function technicalAeoRoutes(app, opts) {
92308
92673
  sort
92309
92674
  })).digest("base64url");
92310
92675
  const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
92311
- const orderBy = sort === "score-desc" ? [desc43(siteCrawlPages.auditScore), asc17(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc17(siteCrawlPages.auditScore), asc17(siteCrawlPages.nodeKey)] : sort === "path" ? [asc17(siteCrawlPages.path), asc17(siteCrawlPages.nodeKey)] : [asc17(siteCrawlPages.url), asc17(siteCrawlPages.nodeKey)];
92676
+ const orderBy = sort === "score-desc" ? [desc43(siteCrawlPages.auditScore), asc18(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc18(siteCrawlPages.auditScore), asc18(siteCrawlPages.nodeKey)] : sort === "path" ? [asc18(siteCrawlPages.path), asc18(siteCrawlPages.nodeKey)] : [asc18(siteCrawlPages.url), asc18(siteCrawlPages.nodeKey)];
92312
92677
  let healthStateFilter = null;
92313
92678
  if (healthState) {
92314
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and77(
92315
- eq94(siteCrawlPages.projectId, project.id),
92316
- eq94(siteCrawlPages.runId, snapshot.runId),
92317
- eq94(siteCrawlPages.attemptId, snapshot.attemptId),
92679
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and78(
92680
+ eq95(siteCrawlPages.projectId, project.id),
92681
+ eq95(siteCrawlPages.runId, snapshot.runId),
92682
+ eq95(siteCrawlPages.attemptId, snapshot.attemptId),
92318
92683
  isNull18(siteCrawlPages.healthState)
92319
92684
  )).limit(1).get();
92320
92685
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
92321
- if (healthStateFilter === "applied") filters.push(eq94(siteCrawlPages.healthState, healthState));
92686
+ if (healthStateFilter === "applied") filters.push(eq95(siteCrawlPages.healthState, healthState));
92322
92687
  }
92323
92688
  if (healthStateFilter === "unavailable-legacy-scan") {
92324
92689
  return {
@@ -92335,7 +92700,7 @@ async function technicalAeoRoutes(app, opts) {
92335
92700
  pages: []
92336
92701
  };
92337
92702
  }
92338
- const where = and77(...filters);
92703
+ const where = and78(...filters);
92339
92704
  const total = app.db.select({ value: count7() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
92340
92705
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
92341
92706
  const nextOffset = offset + rows.length;
@@ -92370,10 +92735,10 @@ async function technicalAeoRoutes(app, opts) {
92370
92735
  url: siteCrawlPages.url,
92371
92736
  inventoryEligible: siteCrawlPages.inventoryEligible,
92372
92737
  fetchState: siteCrawlPages.fetchState
92373
- }).from(siteCrawlPages).where(and77(
92374
- eq94(siteCrawlPages.projectId, project.id),
92375
- eq94(siteCrawlPages.runId, snapshot.runId),
92376
- eq94(siteCrawlPages.attemptId, snapshot.attemptId)
92738
+ }).from(siteCrawlPages).where(and78(
92739
+ eq95(siteCrawlPages.projectId, project.id),
92740
+ eq95(siteCrawlPages.runId, snapshot.runId),
92741
+ eq95(siteCrawlPages.attemptId, snapshot.attemptId)
92377
92742
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
92378
92743
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
92379
92744
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -92410,7 +92775,7 @@ async function technicalAeoRoutes(app, opts) {
92410
92775
  runId: snapshot.runId,
92411
92776
  ...completenessOf(target),
92412
92777
  parentPath,
92413
- nextCursor: hasMore ? encodeCursor2(offset + visible.length) : null,
92778
+ nextCursor: hasMore ? encodeCursor3(offset + visible.length) : null,
92414
92779
  children: visible.map(([path8, child]) => ({
92415
92780
  path: path8,
92416
92781
  url: child.url,
@@ -92455,21 +92820,21 @@ async function technicalAeoRoutes(app, opts) {
92455
92820
  };
92456
92821
  }
92457
92822
  const filters = [
92458
- eq94(siteCrawlEdges.projectId, project.id),
92459
- eq94(siteCrawlEdges.runId, snapshot.runId),
92460
- eq94(siteCrawlEdges.attemptId, snapshot.attemptId),
92461
- eq94(siteCrawlEdges.internal, true),
92823
+ eq95(siteCrawlEdges.projectId, project.id),
92824
+ eq95(siteCrawlEdges.runId, snapshot.runId),
92825
+ eq95(siteCrawlEdges.attemptId, snapshot.attemptId),
92826
+ eq95(siteCrawlEdges.internal, true),
92462
92827
  linkKindFilter(linkKind)
92463
92828
  ];
92464
- if (request.query.sourceUrl) filters.push(eq94(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
92465
- if (request.query.targetUrl) filters.push(eq94(siteCrawlEdges.targetUrl, request.query.targetUrl));
92829
+ if (request.query.sourceUrl) filters.push(eq95(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
92830
+ if (request.query.targetUrl) filters.push(eq95(siteCrawlEdges.targetUrl, request.query.targetUrl));
92466
92831
  const followable = parseBoolean2(request.query.followable);
92467
- if (followable != null) filters.push(eq94(siteCrawlEdges.followable, followable));
92468
- const where = and77(...filters);
92832
+ if (followable != null) filters.push(eq95(siteCrawlEdges.followable, followable));
92833
+ const where = and78(...filters);
92469
92834
  const total = app.db.select({ value: count7() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
92470
92835
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
92471
92836
  const offset = decodeCursor(request.query.cursor);
92472
- const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc17(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
92837
+ const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
92473
92838
  const nextOffset = offset + rows.length;
92474
92839
  return {
92475
92840
  project: project.name,
@@ -92477,7 +92842,7 @@ async function technicalAeoRoutes(app, opts) {
92477
92842
  runId: snapshot.runId,
92478
92843
  ...completenessOf(target),
92479
92844
  total,
92480
- nextCursor: nextOffset < total ? encodeCursor2(nextOffset) : null,
92845
+ nextCursor: nextOffset < total ? encodeCursor3(nextOffset) : null,
92481
92846
  templateDetection,
92482
92847
  linkKind,
92483
92848
  edges: rows.map((row) => mapCrawlEdge(row, templateDetection))
@@ -92526,17 +92891,17 @@ async function technicalAeoRoutes(app, opts) {
92526
92891
  };
92527
92892
  }
92528
92893
  const scope = [
92529
- eq94(siteCrawlEdges.projectId, project.id),
92530
- eq94(siteCrawlEdges.runId, snapshot.runId),
92531
- eq94(siteCrawlEdges.attemptId, snapshot.attemptId),
92532
- eq94(siteCrawlEdges.internal, true),
92894
+ eq95(siteCrawlEdges.projectId, project.id),
92895
+ eq95(siteCrawlEdges.runId, snapshot.runId),
92896
+ eq95(siteCrawlEdges.attemptId, snapshot.attemptId),
92897
+ eq95(siteCrawlEdges.internal, true),
92533
92898
  linkKindFilter(linkKind)
92534
92899
  ];
92535
- const inboundMatch = request.query.nodeKey && request.query.url ? or22(eq94(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq94(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq94(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq94(siteCrawlEdges.targetUrl, request.query.url);
92536
- const outboundMatch = request.query.nodeKey && request.query.url ? or22(eq94(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq94(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq94(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq94(siteCrawlEdges.sourceUrl, request.query.url);
92900
+ const inboundMatch = request.query.nodeKey && request.query.url ? or22(eq95(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq95(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq95(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq95(siteCrawlEdges.targetUrl, request.query.url);
92901
+ const outboundMatch = request.query.nodeKey && request.query.url ? or22(eq95(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq95(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq95(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq95(siteCrawlEdges.sourceUrl, request.query.url);
92537
92902
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
92538
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and77(...scope, inboundMatch)).orderBy(asc17(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
92539
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and77(...scope, outboundMatch)).orderBy(asc17(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
92903
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and78(...scope, inboundMatch)).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
92904
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and78(...scope, outboundMatch)).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
92540
92905
  return {
92541
92906
  project: project.name,
92542
92907
  hasCrawlData: true,
@@ -92570,11 +92935,11 @@ async function technicalAeoRoutes(app, opts) {
92570
92935
  if (!snapshot.attemptId) {
92571
92936
  return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [], runSelection: target.runSelection };
92572
92937
  }
92573
- const where = and77(
92574
- eq94(siteCrawlFindings.projectId, project.id),
92575
- eq94(siteCrawlFindings.runId, snapshot.runId),
92576
- eq94(siteCrawlFindings.attemptId, snapshot.attemptId),
92577
- eq94(siteCrawlFindings.findingType, "dead-link"),
92938
+ const where = and78(
92939
+ eq95(siteCrawlFindings.projectId, project.id),
92940
+ eq95(siteCrawlFindings.runId, snapshot.runId),
92941
+ eq95(siteCrawlFindings.attemptId, snapshot.attemptId),
92942
+ eq95(siteCrawlFindings.findingType, "dead-link"),
92578
92943
  // Belt and braces on the client-facing surface: a row without a status
92579
92944
  // code is not evidence of a broken link, and this response is where such
92580
92945
  // a row would be read as one. Migration 140 removed every stored row that
@@ -92584,12 +92949,12 @@ async function technicalAeoRoutes(app, opts) {
92584
92949
  // a fabricated broken link shown to a client is not worth the saved
92585
92950
  // predicate. Applied to `total` as well as the rows, or the count would
92586
92951
  // promise a page the list cannot fill.
92587
- isNotNull9(sql34`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
92952
+ isNotNull9(sql35`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
92588
92953
  );
92589
92954
  const total = app.db.select({ value: count7() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
92590
92955
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
92591
92956
  const offset = decodeCursor(request.query.cursor);
92592
- const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc17(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
92957
+ const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc18(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
92593
92958
  const nextOffset = offset + rows.length;
92594
92959
  const deadLinks = rows.map((row) => ({
92595
92960
  findingKey: row.findingKey,
@@ -92611,7 +92976,7 @@ async function technicalAeoRoutes(app, opts) {
92611
92976
  found: snapshot.deadLinksFound,
92612
92977
  unverified: snapshot.deadLinksUnverified,
92613
92978
  total,
92614
- nextCursor: nextOffset < total ? encodeCursor2(nextOffset) : null,
92979
+ nextCursor: nextOffset < total ? encodeCursor3(nextOffset) : null,
92615
92980
  deadLinks
92616
92981
  };
92617
92982
  });
@@ -92624,16 +92989,16 @@ async function technicalAeoRoutes(app, opts) {
92624
92989
  createdAt: runs.createdAt,
92625
92990
  startedAt: runs.startedAt,
92626
92991
  finishedAt: runs.finishedAt
92627
- }).from(runs).where(and77(
92628
- eq94(runs.projectId, project.id),
92629
- eq94(runs.kind, RunKinds["site-audit"]),
92992
+ }).from(runs).where(and78(
92993
+ eq95(runs.projectId, project.id),
92994
+ eq95(runs.kind, RunKinds["site-audit"]),
92630
92995
  notProbeRun()
92631
92996
  )).orderBy(desc43(runs.createdAt), desc43(runs.id)).limit(limit).all();
92632
92997
  const runIds = rows.map((row) => row.runId);
92633
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq94(siteCrawlSnapshots.runId, runs.id)).where(and77(
92634
- eq94(siteCrawlSnapshots.projectId, project.id),
92635
- eq94(runs.projectId, project.id),
92636
- eq94(runs.kind, RunKinds["site-audit"]),
92998
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(
92999
+ eq95(siteCrawlSnapshots.projectId, project.id),
93000
+ eq95(runs.projectId, project.id),
93001
+ eq95(runs.kind, RunKinds["site-audit"]),
92637
93002
  inArray38(runs.status, SURFACEABLE_STATUSES),
92638
93003
  notProbeRun(),
92639
93004
  inArray38(siteCrawlSnapshots.runId, runIds)
@@ -92650,21 +93015,21 @@ async function technicalAeoRoutes(app, opts) {
92650
93015
  });
92651
93016
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
92652
93017
  const project = resolveProject(app.db, request.params.name);
92653
- const run = app.db.select().from(runs).where(and77(
92654
- eq94(runs.id, request.params.runId),
92655
- eq94(runs.projectId, project.id),
92656
- eq94(runs.kind, RunKinds["site-audit"]),
93018
+ const run = app.db.select().from(runs).where(and78(
93019
+ eq95(runs.id, request.params.runId),
93020
+ eq95(runs.projectId, project.id),
93021
+ eq95(runs.kind, RunKinds["site-audit"]),
92657
93022
  notProbeRun()
92658
93023
  )).get();
92659
93024
  if (!run) throw notFound("Site audit run", request.params.runId);
92660
- const attempt = app.db.select().from(siteCrawlAttempts).where(and77(
92661
- eq94(siteCrawlAttempts.projectId, project.id),
92662
- eq94(siteCrawlAttempts.runId, run.id)
93025
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and78(
93026
+ eq95(siteCrawlAttempts.projectId, project.id),
93027
+ eq95(siteCrawlAttempts.runId, run.id)
92663
93028
  )).orderBy(desc43(siteCrawlAttempts.attemptNumber), desc43(siteCrawlAttempts.updatedAt)).limit(1).get();
92664
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and77(
92665
- eq94(siteCrawlGraphLayouts.projectId, project.id),
92666
- eq94(siteCrawlGraphLayouts.runId, run.id),
92667
- eq94(siteCrawlGraphLayouts.attemptId, attempt.id)
93029
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and78(
93030
+ eq95(siteCrawlGraphLayouts.projectId, project.id),
93031
+ eq95(siteCrawlGraphLayouts.runId, run.id),
93032
+ eq95(siteCrawlGraphLayouts.attemptId, attempt.id)
92668
93033
  )).get() : void 0;
92669
93034
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
92670
93035
  return {
@@ -92695,18 +93060,18 @@ async function technicalAeoRoutes(app, opts) {
92695
93060
  };
92696
93061
  });
92697
93062
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
92698
- const project = tx.select().from(projects).where(eq94(projects.name, request.params.name)).get();
93063
+ const project = tx.select().from(projects).where(eq95(projects.name, request.params.name)).get();
92699
93064
  if (!project) throw notFound("Project", request.params.name);
92700
- const run = tx.select().from(runs).where(and77(
92701
- eq94(runs.id, request.params.runId),
92702
- eq94(runs.projectId, project.id),
92703
- eq94(runs.kind, RunKinds["site-audit"]),
93065
+ const run = tx.select().from(runs).where(and78(
93066
+ eq95(runs.id, request.params.runId),
93067
+ eq95(runs.projectId, project.id),
93068
+ eq95(runs.kind, RunKinds["site-audit"]),
92704
93069
  notProbeRun()
92705
93070
  )).get();
92706
93071
  if (!run) throw notFound("Site audit run", request.params.runId);
92707
- const attempt = tx.select().from(siteCrawlAttempts).where(and77(
92708
- eq94(siteCrawlAttempts.projectId, project.id),
92709
- eq94(siteCrawlAttempts.runId, run.id)
93072
+ const attempt = tx.select().from(siteCrawlAttempts).where(and78(
93073
+ eq95(siteCrawlAttempts.projectId, project.id),
93074
+ eq95(siteCrawlAttempts.runId, run.id)
92710
93075
  )).orderBy(desc43(siteCrawlAttempts.attemptNumber), desc43(siteCrawlAttempts.updatedAt)).limit(1).get();
92711
93076
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
92712
93077
  const base = {
@@ -92720,11 +93085,11 @@ async function technicalAeoRoutes(app, opts) {
92720
93085
  if (!attempt) {
92721
93086
  return { ...base, pagesAudited: 0, examples: [] };
92722
93087
  }
92723
- const auditedWhere = and77(
92724
- eq94(siteCrawlPages.projectId, project.id),
92725
- eq94(siteCrawlPages.runId, run.id),
92726
- eq94(siteCrawlPages.attemptId, attempt.id),
92727
- eq94(siteCrawlPages.auditState, "success")
93088
+ const auditedWhere = and78(
93089
+ eq95(siteCrawlPages.projectId, project.id),
93090
+ eq95(siteCrawlPages.runId, run.id),
93091
+ eq95(siteCrawlPages.attemptId, attempt.id),
93092
+ eq95(siteCrawlPages.auditState, "success")
92728
93093
  );
92729
93094
  const pagesAudited = tx.select({ value: count7() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
92730
93095
  if (state !== "collecting") {
@@ -92735,11 +93100,11 @@ async function technicalAeoRoutes(app, opts) {
92735
93100
  url: siteCrawlPages.url,
92736
93101
  auditScore: siteCrawlPages.auditScore,
92737
93102
  auditFields: siteCrawlPages.auditFields
92738
- }).from(siteCrawlPages).where(and77(
93103
+ }).from(siteCrawlPages).where(and78(
92739
93104
  auditedWhere,
92740
93105
  isNotNull9(siteCrawlPages.auditScore),
92741
93106
  lt13(siteCrawlPages.auditScore, 70)
92742
- )).orderBy(asc17(siteCrawlPages.auditScore), asc17(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
93107
+ )).orderBy(asc18(siteCrawlPages.auditScore), asc18(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
92743
93108
  const examples = candidates.flatMap((candidate) => {
92744
93109
  if (candidate.auditScore == null) return [];
92745
93110
  const checksNeedingAttention = countLivePageHealthChecks(candidate.auditFields);
@@ -92771,12 +93136,12 @@ async function technicalAeoRoutes(app, opts) {
92771
93136
  status: runs.status,
92772
93137
  identityKey: siteCrawlRunRequests.identityKey,
92773
93138
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
92774
- }).from(runs).leftJoin(siteCrawlRunRequests, and77(
92775
- eq94(siteCrawlRunRequests.projectId, runs.projectId),
92776
- eq94(siteCrawlRunRequests.runId, runs.id)
92777
- )).where(and77(
92778
- eq94(runs.projectId, project.id),
92779
- eq94(runs.kind, RunKinds["site-audit"]),
93139
+ }).from(runs).leftJoin(siteCrawlRunRequests, and78(
93140
+ eq95(siteCrawlRunRequests.projectId, runs.projectId),
93141
+ eq95(siteCrawlRunRequests.runId, runs.id)
93142
+ )).where(and78(
93143
+ eq95(runs.projectId, project.id),
93144
+ eq95(runs.kind, RunKinds["site-audit"]),
92780
93145
  inArray38(runs.status, [RunStatuses.queued, RunStatuses.running])
92781
93146
  )).get();
92782
93147
  if (existing) {
@@ -92831,7 +93196,7 @@ async function technicalAeoRoutes(app, opts) {
92831
93196
  // ../api-routes/src/research.ts
92832
93197
  import crypto54 from "crypto";
92833
93198
  import { z as z5 } from "zod";
92834
- import { and as and78, count as count8, desc as desc44, eq as eq95, gte as gte25, lt as lt14, or as or23, sql as sql35 } from "drizzle-orm";
93199
+ import { and as and79, count as count8, desc as desc44, eq as eq96, gte as gte25, lt as lt14, or as or23, sql as sql36 } from "drizzle-orm";
92835
93200
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
92836
93201
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
92837
93202
  async function researchRoutes(app, opts) {
@@ -92849,7 +93214,7 @@ async function researchRoutes(app, opts) {
92849
93214
  const input = parsed.data;
92850
93215
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
92851
93216
  const requestHash2 = directResearchRequestHash(input);
92852
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and78(eq95(researchRuns.projectId, project.id), eq95(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
93217
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and79(eq96(researchRuns.projectId, project.id), eq96(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
92853
93218
  if (existingReceipt) {
92854
93219
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
92855
93220
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -92881,7 +93246,7 @@ async function researchRoutes(app, opts) {
92881
93246
  const initiatedBy = researchPrincipal(request);
92882
93247
  const decision = app.db.transaction((tx) => {
92883
93248
  if (input.idempotencyKey) {
92884
- const existing = tx.select().from(researchRuns).where(and78(eq95(researchRuns.projectId, project.id), eq95(researchRuns.idempotencyKey, input.idempotencyKey))).get();
93249
+ const existing = tx.select().from(researchRuns).where(and79(eq96(researchRuns.projectId, project.id), eq96(researchRuns.idempotencyKey, input.idempotencyKey))).get();
92885
93250
  if (existing) {
92886
93251
  assertSameDirectResearchRequest(existing, input, requestHash2);
92887
93252
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -92889,11 +93254,11 @@ async function researchRoutes(app, opts) {
92889
93254
  }
92890
93255
  if (initiatedBy?.limited) {
92891
93256
  const { start, end, date } = utcDayBounds(now);
92892
- const used = tx.select({ value: count8() }).from(researchRuns).where(and78(
92893
- eq95(researchRuns.projectId, project.id),
93257
+ const used = tx.select({ value: count8() }).from(researchRuns).where(and79(
93258
+ eq96(researchRuns.projectId, project.id),
92894
93259
  gte25(researchRuns.createdAt, start),
92895
93260
  lt14(researchRuns.createdAt, end),
92896
- sql35`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
93261
+ sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
92897
93262
  )).get()?.value ?? 0;
92898
93263
  if (used >= viewerDailyRunLimit) {
92899
93264
  throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
@@ -92921,15 +93286,15 @@ async function researchRoutes(app, opts) {
92921
93286
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
92922
93287
  const requestHash2 = researchBatchRequestHash(input);
92923
93288
  const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
92924
- const firstReceipt = app.db.select().from(researchRuns).where(and78(
92925
- eq95(researchRuns.projectId, project.id),
92926
- eq95(researchRuns.idempotencyKey, receiptKeys[0])
93289
+ const firstReceipt = app.db.select().from(researchRuns).where(and79(
93290
+ eq96(researchRuns.projectId, project.id),
93291
+ eq96(researchRuns.idempotencyKey, receiptKeys[0])
92927
93292
  )).get();
92928
93293
  if (firstReceipt) {
92929
93294
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
92930
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and78(
92931
- eq95(researchRuns.projectId, project.id),
92932
- eq95(researchRuns.idempotencyKey, key)
93295
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and79(
93296
+ eq96(researchRuns.projectId, project.id),
93297
+ eq96(researchRuns.idempotencyKey, key)
92933
93298
  )).get());
92934
93299
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
92935
93300
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -92944,11 +93309,11 @@ async function researchRoutes(app, opts) {
92944
93309
  const decision = app.db.transaction((tx) => {
92945
93310
  if (initiatedBy?.limited) {
92946
93311
  const { start, end, date } = utcDayBounds(now);
92947
- const used = tx.select({ value: count8() }).from(researchRuns).where(and78(
92948
- eq95(researchRuns.projectId, project.id),
93312
+ const used = tx.select({ value: count8() }).from(researchRuns).where(and79(
93313
+ eq96(researchRuns.projectId, project.id),
92949
93314
  gte25(researchRuns.createdAt, start),
92950
93315
  lt14(researchRuns.createdAt, end),
92951
- sql35`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
93316
+ sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
92952
93317
  )).get()?.value ?? 0;
92953
93318
  if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
92954
93319
  }
@@ -92999,9 +93364,9 @@ async function researchRoutes(app, opts) {
92999
93364
  const requested = Number.parseInt(request.query.limit ?? "", 10);
93000
93365
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
93001
93366
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
93002
- const rows = app.db.select().from(researchRuns).where(and78(
93003
- eq95(researchRuns.projectId, project.id),
93004
- cursor ? or23(lt14(researchRuns.createdAt, cursor.createdAt), and78(eq95(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
93367
+ const rows = app.db.select().from(researchRuns).where(and79(
93368
+ eq96(researchRuns.projectId, project.id),
93369
+ cursor ? or23(lt14(researchRuns.createdAt, cursor.createdAt), and79(eq96(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
93005
93370
  )).orderBy(desc44(researchRuns.createdAt), desc44(researchRuns.id)).limit(limit + 1).all();
93006
93371
  const runs2 = rows.slice(0, limit).map(serializeRun);
93007
93372
  const last = runs2.at(-1);
@@ -93128,9 +93493,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
93128
93493
  return stored;
93129
93494
  }
93130
93495
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
93131
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and78(
93132
- eq95(measurementQueryTemplates.projectId, projectId),
93133
- eq95(measurementQueryTemplates.id, selection.templateId)
93496
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and79(
93497
+ eq96(measurementQueryTemplates.projectId, projectId),
93498
+ eq96(measurementQueryTemplates.id, selection.templateId)
93134
93499
  )).get();
93135
93500
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
93136
93501
  const templateVersion = savedTemplate.updatedAt;
@@ -93139,7 +93504,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
93139
93504
  }
93140
93505
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
93141
93506
  if (bindingLocation) {
93142
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq95(projects.id, projectId)).get();
93507
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq96(projects.id, projectId)).get();
93143
93508
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
93144
93509
  }
93145
93510
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -93152,9 +93517,9 @@ function classifyResearchQueries(project, plan, queries2) {
93152
93517
  return queries2.map((query) => classifier?.classify(query) ?? null);
93153
93518
  }
93154
93519
  function getDetail(app, projectId, id) {
93155
- const row = app.db.select().from(researchRuns).where(and78(eq95(researchRuns.id, id), eq95(researchRuns.projectId, projectId))).get();
93520
+ const row = app.db.select().from(researchRuns).where(and79(eq96(researchRuns.id, id), eq96(researchRuns.projectId, projectId))).get();
93156
93521
  if (!row) throw notFound("Research run", id);
93157
- const queries2 = app.db.select().from(researchRunQueries).where(eq95(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
93522
+ const queries2 = app.db.select().from(researchRunQueries).where(eq96(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
93158
93523
  return { ...serializeRun(row), queries: queries2 };
93159
93524
  }
93160
93525
  function serializeRun(row) {
@@ -93184,7 +93549,7 @@ function utcDayBounds(now) {
93184
93549
 
93185
93550
  // ../api-routes/src/snapshot-competitor-refresh.ts
93186
93551
  import { setImmediate as yieldToEventLoop2 } from "timers/promises";
93187
- import { and as and79, asc as asc18, eq as eq96, inArray as inArray39 } from "drizzle-orm";
93552
+ import { and as and80, asc as asc19, eq as eq97, inArray as inArray39 } from "drizzle-orm";
93188
93553
  var ANSWER_MENTIONS_CHUNK_SIZE = 50;
93189
93554
  var ANSWER_MENTIONS_MAX_RESTARTS = 3;
93190
93555
  function backfillProjectAnswerMentions(db, projectId, opts) {
@@ -93234,14 +93599,14 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
93234
93599
  examined = 0;
93235
93600
  wouldUpdate = 0;
93236
93601
  mentioned = 0;
93237
- const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and79(
93238
- eq96(runs.kind, RunKinds["answer-visibility"]),
93239
- eq96(runs.projectId, projectId),
93240
- opts?.runId === void 0 ? void 0 : eq96(runs.id, opts.runId)
93602
+ const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and80(
93603
+ eq97(runs.kind, RunKinds["answer-visibility"]),
93604
+ eq97(runs.projectId, projectId),
93605
+ opts?.runId === void 0 ? void 0 : eq97(runs.id, opts.runId)
93241
93606
  )).all();
93242
93607
  const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
93243
93608
  for (const run of runRows) {
93244
- const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq96(querySnapshots.runId, run.id)).orderBy(asc18(querySnapshots.id)).all().map((row) => row.id);
93609
+ const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq97(querySnapshots.runId, run.id)).orderBy(asc19(querySnapshots.id)).all().map((row) => row.id);
93245
93610
  for (let offset = 0; offset < snapshotIds.length; offset += ANSWER_MENTIONS_CHUNK_SIZE) {
93246
93611
  if (chunks++ > 0 && driver.recheckIdentity) {
93247
93612
  yield;
@@ -93317,7 +93682,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
93317
93682
  } else {
93318
93683
  db.transaction((tx) => {
93319
93684
  for (const update of pendingUpdates) {
93320
- tx.update(querySnapshots).set(update.patch).where(eq96(querySnapshots.id, update.id)).run();
93685
+ tx.update(querySnapshots).set(update.patch).where(eq97(querySnapshots.id, update.id)).run();
93321
93686
  }
93322
93687
  });
93323
93688
  updated += pendingUpdates.length;
@@ -93347,7 +93712,7 @@ function readStoredGroundingSources(rawResponse) {
93347
93712
 
93348
93713
  // ../api-routes/src/simple-measurement-definitions.ts
93349
93714
  import crypto55 from "crypto";
93350
- import { and as and80, eq as eq97 } from "drizzle-orm";
93715
+ import { and as and81, eq as eq98 } from "drizzle-orm";
93351
93716
  function captureSimpleMeasurementDefinition(db, input) {
93352
93717
  return db.transaction((tx) => {
93353
93718
  const run = tx.select({
@@ -93355,7 +93720,7 @@ function captureSimpleMeasurementDefinition(db, input) {
93355
93720
  trigger: runs.trigger,
93356
93721
  status: runs.status,
93357
93722
  measurementPlanVersionId: runs.measurementPlanVersionId
93358
- }).from(runs).where(and80(eq97(runs.projectId, input.projectId), eq97(runs.id, input.runId))).get();
93723
+ }).from(runs).where(and81(eq98(runs.projectId, input.projectId), eq98(runs.id, input.runId))).get();
93359
93724
  if (!run) throw notFound("Run", input.runId);
93360
93725
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
93361
93726
  return null;
@@ -93376,7 +93741,7 @@ function captureSimpleMeasurementDefinition(db, input) {
93376
93741
  throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
93377
93742
  }
93378
93743
  }
93379
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and80(eq97(simpleMeasurementDefinitions.projectId, input.projectId), eq97(simpleMeasurementDefinitions.runId, input.runId))).get();
93744
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and81(eq98(simpleMeasurementDefinitions.projectId, input.projectId), eq98(simpleMeasurementDefinitions.runId, input.runId))).get();
93380
93745
  if (existing) {
93381
93746
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
93382
93747
  const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
@@ -93402,15 +93767,15 @@ function captureSimpleMeasurementDefinition(db, input) {
93402
93767
  if (run.status !== RunStatuses.running) {
93403
93768
  throw validationError("Only a running run can capture a new measurement definition.");
93404
93769
  }
93405
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq97(querySnapshots.runId, input.runId)).limit(1).get()) {
93770
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq98(querySnapshots.runId, input.runId)).limit(1).get()) {
93406
93771
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
93407
93772
  }
93408
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq97(queries.projectId, input.projectId)).all().map((query) => query.id));
93773
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq98(queries.projectId, input.projectId)).all().map((query) => query.id));
93409
93774
  if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
93410
93775
  throw validationError("Every captured query must belong to the run project.");
93411
93776
  }
93412
93777
  if (definition2.competitors !== void 0) {
93413
- const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq97(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
93778
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq98(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
93414
93779
  const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
93415
93780
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
93416
93781
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
@@ -93815,8 +94180,8 @@ function buildTrafficSourceValidators(opts) {
93815
94180
  const resolveToken = opts.resolveCloudRunAccessToken ?? defaultResolveAccessToken;
93816
94181
  validators["cloud-run"] = {
93817
94182
  validateCredentials: async (source) => {
93818
- const record = store.getConnection(source.projectName);
93819
- if (!record) {
94183
+ const record2 = store.getConnection(source.projectName);
94184
+ if (!record2) {
93820
94185
  return {
93821
94186
  status: CheckStatuses.fail,
93822
94187
  code: "traffic.credentials.missing",
@@ -93825,11 +94190,11 @@ function buildTrafficSourceValidators(opts) {
93825
94190
  };
93826
94191
  }
93827
94192
  try {
93828
- await resolveToken(record);
94193
+ await resolveToken(record2);
93829
94194
  return {
93830
94195
  status: CheckStatuses.ok,
93831
94196
  code: "traffic.credentials.resolved",
93832
- summary: `Cloud Run access token resolves for "${source.displayName}" (project ${record.gcpProjectId}).`
94197
+ summary: `Cloud Run access token resolves for "${source.displayName}" (project ${record2.gcpProjectId}).`
93833
94198
  };
93834
94199
  } catch (e) {
93835
94200
  const msg = describeError(e);
@@ -93855,8 +94220,8 @@ function buildTrafficSourceValidators(opts) {
93855
94220
  const wordpressTrafficFetch = createGuardedFetch({ allowLoopback: opts.allowLoopbackWebhooks === true });
93856
94221
  validators[TrafficSourceTypes.wordpress] = {
93857
94222
  validateCredentials: async (source) => {
93858
- const record = store.getConnection(source.projectName);
93859
- if (!record) {
94223
+ const record2 = store.getConnection(source.projectName);
94224
+ if (!record2) {
93860
94225
  return {
93861
94226
  status: CheckStatuses.fail,
93862
94227
  code: "traffic.credentials.missing",
@@ -93866,9 +94231,9 @@ function buildTrafficSourceValidators(opts) {
93866
94231
  }
93867
94232
  try {
93868
94233
  await pullEvents({
93869
- baseUrl: record.baseUrl,
93870
- username: record.username,
93871
- applicationPassword: record.applicationPassword,
94234
+ baseUrl: record2.baseUrl,
94235
+ username: record2.username,
94236
+ applicationPassword: record2.applicationPassword,
93872
94237
  pageSize: 1,
93873
94238
  maxPages: 1,
93874
94239
  // The probe carries Basic-auth credentials: check the stored URL
@@ -93878,7 +94243,7 @@ function buildTrafficSourceValidators(opts) {
93878
94243
  return {
93879
94244
  status: CheckStatuses.ok,
93880
94245
  code: "traffic.credentials.resolved",
93881
- summary: `WordPress endpoint responds for "${source.displayName}" (${new URL(record.baseUrl).host}).`
94246
+ summary: `WordPress endpoint responds for "${source.displayName}" (${new URL(record2.baseUrl).host}).`
93882
94247
  };
93883
94248
  } catch (e) {
93884
94249
  const httpStatus = e instanceof WordpressTrafficApiError ? e.status : null;
@@ -93902,8 +94267,8 @@ function buildTrafficSourceValidators(opts) {
93902
94267
  const pullEvents = opts.pullVercelTrafficEvents ?? listVercelTrafficEvents;
93903
94268
  validators[TrafficSourceTypes.vercel] = {
93904
94269
  validateCredentials: async (source) => {
93905
- const record = store.getConnection(source.projectName);
93906
- if (!record) {
94270
+ const record2 = store.getConnection(source.projectName);
94271
+ if (!record2) {
93907
94272
  return {
93908
94273
  status: CheckStatuses.fail,
93909
94274
  code: "traffic.credentials.missing",
@@ -93914,10 +94279,10 @@ function buildTrafficSourceValidators(opts) {
93914
94279
  try {
93915
94280
  const probeEnd = Date.now();
93916
94281
  await pullEvents({
93917
- token: record.token,
93918
- projectId: record.projectId,
93919
- teamId: record.teamId,
93920
- environment: record.environment,
94282
+ token: record2.token,
94283
+ projectId: record2.projectId,
94284
+ teamId: record2.teamId,
94285
+ environment: record2.environment,
93921
94286
  startDate: probeEnd - 60 * 6e4,
93922
94287
  endDate: probeEnd,
93923
94288
  maxPages: 1
@@ -93925,7 +94290,7 @@ function buildTrafficSourceValidators(opts) {
93925
94290
  return {
93926
94291
  status: CheckStatuses.ok,
93927
94292
  code: "traffic.credentials.resolved",
93928
- summary: `Vercel request-logs responds for "${source.displayName}" (project ${record.projectId}).`
94293
+ summary: `Vercel request-logs responds for "${source.displayName}" (project ${record2.projectId}).`
93929
94294
  };
93930
94295
  } catch (e) {
93931
94296
  const httpStatus = e instanceof VercelLogsApiError ? e.status : null;
@@ -93951,8 +94316,8 @@ function buildTrafficSourceValidators(opts) {
93951
94316
  validateCredentials: (source) => {
93952
94317
  const sourceMode = source.configJson.deliveryMode;
93953
94318
  const deliveryMode = sourceMode === "queue-pull" ? "queue-pull" : "direct-push";
93954
- const record = store.getConnectionBySourceId(source.id);
93955
- if (!record) {
94319
+ const record2 = store.getConnectionBySourceId(source.id);
94320
+ if (!record2) {
93956
94321
  const projectRecord = store.getConnection(source.projectName);
93957
94322
  if (projectRecord && projectRecord.sourceId !== source.id) {
93958
94323
  return {
@@ -93969,7 +94334,7 @@ function buildTrafficSourceValidators(opts) {
93969
94334
  remediation: "Re-run `canonry traffic connect cloudflare <project> --zone-id <id>` from the credential-owning host."
93970
94335
  };
93971
94336
  }
93972
- const recordMode = record.deliveryMode;
94337
+ const recordMode = record2.deliveryMode;
93973
94338
  if (recordMode !== deliveryMode) {
93974
94339
  return {
93975
94340
  status: CheckStatuses.fail,
@@ -93978,7 +94343,7 @@ function buildTrafficSourceValidators(opts) {
93978
94343
  remediation: "Reconnect the Cloudflare source from the credential-owning host."
93979
94344
  };
93980
94345
  }
93981
- if (record.sourceId !== source.id) {
94346
+ if (record2.sourceId !== source.id) {
93982
94347
  return {
93983
94348
  status: CheckStatuses.fail,
93984
94349
  code: "traffic.credentials.source-mismatch",
@@ -93986,9 +94351,9 @@ function buildTrafficSourceValidators(opts) {
93986
94351
  remediation: "Reconnect the Cloudflare source to pair the credential and source row."
93987
94352
  };
93988
94353
  }
93989
- if (record.deliveryMode === "queue-pull") {
94354
+ if (record2.deliveryMode === "queue-pull") {
93990
94355
  const queueConfig = source.configJson;
93991
- if (typeof record.apiToken !== "string" || record.apiToken.length === 0 || typeof record.accountId !== "string" || record.accountId.length === 0 || typeof record.queueId !== "string" || record.queueId.length === 0 || typeof record.queueName !== "string" || record.queueName.length === 0 || !Number.isInteger(record.retentionSeconds) || record.retentionSeconds < 60 || record.retentionSeconds > 1209600 || queueConfig.accountId !== record.accountId || queueConfig.queueId !== record.queueId || queueConfig.queueName !== record.queueName || queueConfig.retentionSeconds !== record.retentionSeconds) {
94356
+ if (typeof record2.apiToken !== "string" || record2.apiToken.length === 0 || typeof record2.accountId !== "string" || record2.accountId.length === 0 || typeof record2.queueId !== "string" || record2.queueId.length === 0 || typeof record2.queueName !== "string" || record2.queueName.length === 0 || !Number.isInteger(record2.retentionSeconds) || record2.retentionSeconds < 60 || record2.retentionSeconds > 1209600 || queueConfig.accountId !== record2.accountId || queueConfig.queueId !== record2.queueId || queueConfig.queueName !== record2.queueName || queueConfig.retentionSeconds !== record2.retentionSeconds) {
93992
94357
  return {
93993
94358
  status: CheckStatuses.fail,
93994
94359
  code: "traffic.credentials.queue-mismatch",
@@ -94002,7 +94367,7 @@ function buildTrafficSourceValidators(opts) {
94002
94367
  summary: `Cloudflare Queue credentials match source "${source.displayName}".`
94003
94368
  };
94004
94369
  }
94005
- if (typeof record.bearerToken !== "string" || record.bearerToken.length === 0 || typeof record.hmacSecret !== "string" || record.hmacSecret.length === 0 || !source.ingestTokenHash || hashCloudflareBearerToken(record.bearerToken) !== source.ingestTokenHash) {
94370
+ if (typeof record2.bearerToken !== "string" || record2.bearerToken.length === 0 || typeof record2.hmacSecret !== "string" || record2.hmacSecret.length === 0 || !source.ingestTokenHash || hashCloudflareBearerToken(record2.bearerToken) !== source.ingestTokenHash) {
94006
94371
  return {
94007
94372
  status: CheckStatuses.fail,
94008
94373
  code: "traffic.credentials.bearer-mismatch",
@@ -94321,13 +94686,13 @@ function computeCitedCompetitorDomains(citedDomains, competitorDomains) {
94321
94686
  var RECURRENCE_LOOKBACK_RUNS = 5;
94322
94687
  var HISTORY_WINDOW_RUNS = Math.max(PERSISTENT_GAP_THRESHOLD, 5);
94323
94688
  var log = createLogger("IntelligenceService");
94324
- var runChronologyKey = sql36`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
94689
+ var runChronologyKey = sql37`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
94325
94690
  function analyzableRunPredicates(projectId) {
94326
94691
  return [
94327
- eq98(runs.projectId, projectId),
94692
+ eq99(runs.projectId, projectId),
94328
94693
  // Only this kind writes query_snapshots.
94329
- eq98(runs.kind, RunKinds["answer-visibility"]),
94330
- or24(eq98(runs.status, RunStatuses.completed), eq98(runs.status, RunStatuses.partial)),
94694
+ eq99(runs.kind, RunKinds["answer-visibility"]),
94695
+ or24(eq99(runs.status, RunStatuses.completed), eq99(runs.status, RunStatuses.partial)),
94331
94696
  // Defensive: RunCoordinator already skips probes before analyzeAndPersist
94332
94697
  // is called, but a future call site invoking it directly for a probe must
94333
94698
  // still not pollute the intelligence window.
@@ -94372,16 +94737,16 @@ var IntelligenceService = class {
94372
94737
  * Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
94373
94738
  */
94374
94739
  analyzeAndPersist(runId, projectId) {
94375
- const currentRunRecord = this.db.select().from(runs).where(and81(eq98(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
94740
+ const currentRunRecord = this.db.select().from(runs).where(and82(eq99(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
94376
94741
  if (!currentRunRecord) {
94377
94742
  log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
94378
94743
  return null;
94379
94744
  }
94380
94745
  const currentLocation = currentRunRecord.location ?? null;
94381
94746
  const recentRuns = this.db.select().from(runs).where(
94382
- and81(
94747
+ and82(
94383
94748
  ...analyzableRunPredicates(projectId),
94384
- currentLocation === null ? isNull19(runs.location) : eq98(runs.location, currentLocation),
94749
+ currentLocation === null ? isNull19(runs.location) : eq99(runs.location, currentLocation),
94385
94750
  // Never look forward. The window is this run's own past, so
94386
94751
  // re-analyzing a historical run compares it against its true
94387
94752
  // predecessor rather than against sweeps that came after it.
@@ -94389,7 +94754,7 @@ var IntelligenceService = class {
94389
94754
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
94390
94755
  // Index seek on idx_snapshots_run, short-circuited on the first row.
94391
94756
  exists2(
94392
- this.db.select({ one: sql36`1` }).from(querySnapshots).where(eq98(querySnapshots.runId, runs.id))
94757
+ this.db.select({ one: sql37`1` }).from(querySnapshots).where(eq99(querySnapshots.runId, runs.id))
94393
94758
  )
94394
94759
  )
94395
94760
  ).orderBy(desc45(runs.finishedAt), desc45(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
@@ -94465,7 +94830,7 @@ var IntelligenceService = class {
94465
94830
  * Returns the persisted insights so the coordinator can count critical/high.
94466
94831
  */
94467
94832
  analyzeAndPersistGbp(runId, projectId) {
94468
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq98(runs.id, runId)).get();
94833
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq99(runs.id, runId)).get();
94469
94834
  if (!runRow) {
94470
94835
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
94471
94836
  this.persistGbpInsights(runId, projectId, [], []);
@@ -94473,9 +94838,9 @@ var IntelligenceService = class {
94473
94838
  }
94474
94839
  const windowStart = runRow.startedAt ?? runRow.createdAt;
94475
94840
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
94476
- const selected = this.db.select().from(gbpLocations).where(and81(
94477
- eq98(gbpLocations.projectId, projectId),
94478
- eq98(gbpLocations.selected, true),
94841
+ const selected = this.db.select().from(gbpLocations).where(and82(
94842
+ eq99(gbpLocations.projectId, projectId),
94843
+ eq99(gbpLocations.selected, true),
94479
94844
  gte26(gbpLocations.syncedAt, windowStart),
94480
94845
  lte23(gbpLocations.syncedAt, windowEnd)
94481
94846
  )).all();
@@ -94510,12 +94875,12 @@ var IntelligenceService = class {
94510
94875
  }
94511
94876
  /** Build the per-location signal bundle the GBP analyzer consumes. */
94512
94877
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
94513
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and81(eq98(gbpDailyMetrics.projectId, projectId), eq98(gbpDailyMetrics.locationName, locationName))).all();
94514
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and81(eq98(gbpPlaceActions.projectId, projectId), eq98(gbpPlaceActions.locationName, locationName))).all();
94515
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and81(eq98(gbpLodgingSnapshots.projectId, projectId), eq98(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc45(gbpLodgingSnapshots.syncedAt)).limit(1).get();
94516
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and81(eq98(gbpLocations.projectId, projectId), eq98(gbpLocations.locationName, locationName))).get();
94878
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and82(eq99(gbpDailyMetrics.projectId, projectId), eq99(gbpDailyMetrics.locationName, locationName))).all();
94879
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and82(eq99(gbpPlaceActions.projectId, projectId), eq99(gbpPlaceActions.locationName, locationName))).all();
94880
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and82(eq99(gbpLodgingSnapshots.projectId, projectId), eq99(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc45(gbpLodgingSnapshots.syncedAt)).limit(1).get();
94881
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and82(eq99(gbpLocations.projectId, projectId), eq99(gbpLocations.locationName, locationName))).get();
94517
94882
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
94518
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and81(eq98(gbpPlaceDetails.projectId, projectId), eq98(gbpPlaceDetails.locationName, locationName))).orderBy(desc45(gbpPlaceDetails.syncedAt)).limit(1).get();
94883
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and82(eq99(gbpPlaceDetails.projectId, projectId), eq99(gbpPlaceDetails.locationName, locationName))).orderBy(desc45(gbpPlaceDetails.syncedAt)).limit(1).get();
94519
94884
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
94520
94885
  const summary3 = buildGbpSummary({
94521
94886
  locationName,
@@ -94551,7 +94916,7 @@ var IntelligenceService = class {
94551
94916
  /** Build the month-over-month keyword series for a location from the
94552
94917
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
94553
94918
  buildGbpKeywordTrend(projectId, locationName) {
94554
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and81(eq98(gbpKeywordMonthly.projectId, projectId), eq98(gbpKeywordMonthly.locationName, locationName))).all();
94919
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and82(eq99(gbpKeywordMonthly.projectId, projectId), eq99(gbpKeywordMonthly.locationName, locationName))).all();
94555
94920
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
94556
94921
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
94557
94922
  const recentMonth = months[0] ?? null;
@@ -94582,7 +94947,7 @@ var IntelligenceService = class {
94582
94947
  */
94583
94948
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
94584
94949
  const covered = new Set(coveredLocationNames);
94585
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and81(eq98(insights.projectId, projectId), eq98(insights.provider, GBP_INSIGHT_PROVIDER))).all();
94950
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and82(eq99(insights.projectId, projectId), eq99(insights.provider, GBP_INSIGHT_PROVIDER))).all();
94586
94951
  const staleIds = [];
94587
94952
  const dismissedSlots = /* @__PURE__ */ new Set();
94588
94953
  for (const row of existing) {
@@ -94593,7 +94958,7 @@ var IntelligenceService = class {
94593
94958
  }
94594
94959
  this.db.transaction((tx) => {
94595
94960
  for (const id of staleIds) {
94596
- tx.delete(insights).where(eq98(insights.id, id)).run();
94961
+ tx.delete(insights).where(eq99(insights.id, id)).run();
94597
94962
  }
94598
94963
  for (const insight of gbpInsights) {
94599
94964
  const parsed = parseGbpInsightId(insight.id);
@@ -94680,7 +95045,7 @@ var IntelligenceService = class {
94680
95045
  * create per run + aggregate). DB is left untouched.
94681
95046
  */
94682
95047
  backfill(projectName, opts, onProgress) {
94683
- const project = this.db.select().from(projects).where(eq98(projects.name, projectName)).get();
95048
+ const project = this.db.select().from(projects).where(eq99(projects.name, projectName)).get();
94684
95049
  if (!project) {
94685
95050
  throw new Error(`Project "${projectName}" not found`);
94686
95051
  }
@@ -94692,7 +95057,7 @@ var IntelligenceService = class {
94692
95057
  }
94693
95058
  sinceTimestamp = parsed;
94694
95059
  }
94695
- const allRuns = this.db.select().from(runs).where(and81(...analyzableRunPredicates(project.id))).orderBy(asc19(runs.finishedAt)).all();
95060
+ const allRuns = this.db.select().from(runs).where(and82(...analyzableRunPredicates(project.id))).orderBy(asc20(runs.finishedAt)).all();
94696
95061
  const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
94697
95062
  const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
94698
95063
  let startIdx = 0;
@@ -94774,7 +95139,7 @@ var IntelligenceService = class {
94774
95139
  * signals match the same identities the dashboard and reports do.
94775
95140
  */
94776
95141
  loadTrackedCompetitors(projectId) {
94777
- return this.db.select(competitorIdentityColumns).from(competitors).where(eq98(competitors.projectId, projectId)).orderBy(competitors.domain).all();
95142
+ return this.db.select(competitorIdentityColumns).from(competitors).where(eq99(competitors.projectId, projectId)).orderBy(competitors.domain).all();
94778
95143
  }
94779
95144
  /**
94780
95145
  * Which of `runIds` actually wrote query snapshots. One grouped read, so
@@ -94805,15 +95170,15 @@ var IntelligenceService = class {
94805
95170
  }
94806
95171
  persistResult(result, runId, projectId) {
94807
95172
  const previouslyDismissed = /* @__PURE__ */ new Set();
94808
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq98(insights.runId, runId)).all();
95173
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq99(insights.runId, runId)).all();
94809
95174
  for (const row of existingInsights) {
94810
95175
  if (row.dismissed) {
94811
95176
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
94812
95177
  }
94813
95178
  }
94814
95179
  this.db.transaction((tx) => {
94815
- tx.delete(insights).where(eq98(insights.runId, runId)).run();
94816
- tx.delete(healthSnapshots).where(eq98(healthSnapshots.runId, runId)).run();
95180
+ tx.delete(insights).where(eq99(insights.runId, runId)).run();
95181
+ tx.delete(healthSnapshots).where(eq99(healthSnapshots.runId, runId)).run();
94817
95182
  const now = (/* @__PURE__ */ new Date()).toISOString();
94818
95183
  for (const insight of result.insights) {
94819
95184
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -94860,8 +95225,8 @@ var IntelligenceService = class {
94860
95225
  clicks: gscSearchData.clicks,
94861
95226
  impressions: gscSearchData.impressions,
94862
95227
  position: gscSearchData.position
94863
- }).from(gscSearchData).where(and81(
94864
- eq98(gscSearchData.projectId, projectId),
95228
+ }).from(gscSearchData).where(and82(
95229
+ eq99(gscSearchData.projectId, projectId),
94865
95230
  gte26(gscSearchData.date, window.startDate),
94866
95231
  lte23(gscSearchData.date, window.endDate)
94867
95232
  )).all();
@@ -94914,17 +95279,17 @@ var IntelligenceService = class {
94914
95279
  const key = row.query.toLowerCase();
94915
95280
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
94916
95281
  }
94917
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq98(projects.id, projectId)).get();
95282
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq99(projects.id, projectId)).get();
94918
95283
  const locationCount = Math.max(
94919
95284
  1,
94920
95285
  (projectRow?.locations ?? []).length
94921
95286
  );
94922
95287
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
94923
95288
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
94924
- and81(
94925
- eq98(runs.projectId, projectId),
94926
- eq98(runs.kind, RunKinds["answer-visibility"]),
94927
- or24(eq98(runs.status, "completed"), eq98(runs.status, "partial")),
95289
+ and82(
95290
+ eq99(runs.projectId, projectId),
95291
+ eq99(runs.kind, RunKinds["answer-visibility"]),
95292
+ or24(eq99(runs.status, "completed"), eq99(runs.status, "partial")),
94928
95293
  // Defensive — see top of file.
94929
95294
  ne17(runs.trigger, RunTriggers.probe)
94930
95295
  )
@@ -94944,7 +95309,7 @@ var IntelligenceService = class {
94944
95309
  const haveHistory = recentRunIds.length > 0;
94945
95310
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
94946
95311
  if (haveHistory) {
94947
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and81(eq98(insights.type, "regression"), inArray40(insights.runId, recentRunIds))).all();
95312
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and82(eq99(insights.type, "regression"), inArray40(insights.runId, recentRunIds))).all();
94948
95313
  const regressionGroups = /* @__PURE__ */ new Map();
94949
95314
  for (const row of priorRows) {
94950
95315
  if (!row.runId) continue;
@@ -94973,7 +95338,7 @@ var IntelligenceService = class {
94973
95338
  });
94974
95339
  }
94975
95340
  buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
94976
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq98(projects.id, projectId)).get();
95341
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq99(projects.id, projectId)).get();
94977
95342
  const projectDomains = projectDomainRow ? effectiveDomains({
94978
95343
  canonicalDomain: projectDomainRow.canonicalDomain,
94979
95344
  ownedDomains: projectDomainRow.ownedDomains
@@ -94991,7 +95356,7 @@ var IntelligenceService = class {
94991
95356
  citedDomains: querySnapshots.citedDomains,
94992
95357
  rawResponse: querySnapshots.rawResponse,
94993
95358
  snapshotLocation: querySnapshots.location
94994
- }).from(querySnapshots).leftJoin(queries, eq98(querySnapshots.queryId, queries.id)).where(eq98(querySnapshots.runId, runId)).all();
95359
+ }).from(querySnapshots).leftJoin(queries, eq99(querySnapshots.queryId, queries.id)).where(eq99(querySnapshots.runId, runId)).all();
94995
95360
  const snapshots = [];
94996
95361
  let orphanCount = 0;
94997
95362
  for (const r of rows) {