@canonry/canonry 7.0.0 → 7.2.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 (36) hide show
  1. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +5 -3
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +19 -2
  3. package/assets/assets/AuditHistoryPanel-Duvyi8vx.js +1 -0
  4. package/assets/assets/{BacklinksPage-HIM0fmV7.js → BacklinksPage-CY5AllQu.js} +1 -1
  5. package/assets/assets/{HistoryPage-BjEVp-fk.js → HistoryPage-C0Gr0gcj.js} +1 -1
  6. package/assets/assets/{MeasurementPropertyPage-BnVDR2y_.js → MeasurementPropertyPage-DrAB6VKX.js} +1 -1
  7. package/assets/assets/ProjectPage-DSxvvnAd.js +8 -0
  8. package/assets/assets/{RunsPage-fJQe2qJc.js → RunsPage-DMxOrtfl.js} +1 -1
  9. package/assets/assets/{SettingsPage-1yam5tl6.js → SettingsPage-sxiUNt2A.js} +1 -1
  10. package/assets/assets/SiteHealthSection--6tZe3hM.js +4 -0
  11. package/assets/assets/{TrafficPage-BdNEscz8.js → TrafficPage-CvSQDNTi.js} +2 -2
  12. package/assets/assets/{TrafficSourceDetailPage-CMEgr4Aw.js → TrafficSourceDetailPage-BaYeIBmc.js} +1 -1
  13. package/assets/assets/{extract-error-message-D2dhaJaW.js → extract-error-message-76IWhqQF.js} +1 -1
  14. package/assets/assets/index-BlrH2zRW.js +86 -0
  15. package/assets/assets/index-HWSQgVum.css +1 -0
  16. package/assets/assets/{react-sigma_core.esm.min-oP23IWpt.js → react-sigma_core.esm.min-CkAcKW2k.js} +1 -1
  17. package/assets/assets/{v2-overview-adapter-DYatWs7N.js → v2-overview-adapter-DnEE4E9w.js} +1 -1
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-TUHQ5BBQ.js → chunk-BYSSJEL7.js} +9 -8
  20. package/dist/{chunk-LXWY4NKP.js → chunk-EQBYK77W.js} +3 -3
  21. package/dist/{chunk-HQJ25LI7.js → chunk-FDBQOQLP.js} +2 -2
  22. package/dist/{chunk-EZ54X4XH.js → chunk-RCQJI3UX.js} +125 -86
  23. package/dist/{chunk-RRIZ5AKI.js → chunk-SVTVBPIT.js} +2 -2
  24. package/dist/{chunk-OOUDIVIU.js → chunk-TY3CJKKR.js} +458 -184
  25. package/dist/{chunk-A5PALGQD.js → chunk-UIDHHZH3.js} +94 -190
  26. package/dist/cli.js +32 -16
  27. package/dist/{demo-server-LHHDVXQW.js → demo-server-GWRYDPJC.js} +4 -4
  28. package/dist/index.js +6 -6
  29. package/dist/{intelligence-service-ZVKZLLKL.js → intelligence-service-3L2665AK.js} +2 -2
  30. package/dist/mcp.js +4 -4
  31. package/package.json +12 -12
  32. package/assets/assets/AuditHistoryPanel-DBYsgVsn.js +0 -1
  33. package/assets/assets/ProjectPage-BhCM47KA.js +0 -8
  34. package/assets/assets/SiteHealthSection-DysU2M6C.js +0 -4
  35. package/assets/assets/index-CgT2Btdn.css +0 -1
  36. package/assets/assets/index-D9ODLKpH.js +0 -86
@@ -609,6 +609,7 @@ import {
609
609
  queryTrackingProvenanceSchema,
610
610
  queryTrackingWorkspaceResponseSchema,
611
611
  quotaExceeded,
612
+ rankCriticizedProperties,
612
613
  recommendationBriefDtoSchema,
613
614
  recommendationExplainRequestSchema,
614
615
  recommendationExplanationDtoSchema,
@@ -726,6 +727,7 @@ import {
726
727
  stripCitationChips,
727
728
  sumInfraHits,
728
729
  summarizeCheckResults,
730
+ summarizeObservedQueryCounts,
729
731
  summarizeRunUsage,
730
732
  surfaceClassFromCompetitorType,
731
733
  surfaceClassLabel,
@@ -750,6 +752,7 @@ import {
750
752
  trafficSourceListResponseSchema,
751
753
  trafficStatusResponseSchema,
752
754
  trafficSyncResponseSchema,
755
+ uiTelemetryEventSchema,
753
756
  unsupportedKind,
754
757
  upgradeCaveatFor,
755
758
  userDtoSchema,
@@ -777,7 +780,7 @@ import {
777
780
  wordpressSchemaDeployResultDtoSchema,
778
781
  wordpressSchemaStatusResultDtoSchema,
779
782
  wordpressStatusDtoSchema
780
- } from "./chunk-OOUDIVIU.js";
783
+ } from "./chunk-TY3CJKKR.js";
781
784
 
782
785
  // src/intelligence-service.ts
783
786
  import { eq as eq82, desc as desc36, asc as asc15, and as and69, ne as ne14, or as or19, inArray as inArray31, gte as gte22, lte as lte20, isNull as isNull16, sql as sql31, exists } from "drizzle-orm";
@@ -13803,7 +13806,8 @@ var SentimentService = class {
13803
13806
  return { ...scoreAssessments(group.measured, group.eligible, options), queryId: group.queryId, queryText: group.queryText, queryClass: selection.queryClass, sourceSnapshotIds: [...new Set(group.eligible.map((item2) => item2.snapshotId))].sort(), assessments, locations, executionNodeKey: group.executionNodeKey };
13804
13807
  });
13805
13808
  const next = rows.limit !== null && from + rows.limit < ordered.length ? rowKey(page3.at(-1)) : null;
13806
- return { summary: { ...base, breakdowns }, queries: queries2, total: ordered.length, next };
13809
+ const criticizedProperties = selection.queryClass === "branded" && !disabled ? rankCriticizedProperties(breakdowns) : void 0;
13810
+ return { summary: { ...base, breakdowns, ...criticizedProperties ? { criticizedProperties } : {} }, queries: queries2, total: ordered.length, next };
13807
13811
  }
13808
13812
  readSelection(projectId, query, withInput = false) {
13809
13813
  if (query.runId && query.runIds) throw validationError("Choose runId or runIds, not both.");
@@ -13828,7 +13832,7 @@ var SentimentService = class {
13828
13832
  const unavailable5 = { state: "unsupported", reason: read.reason, provisional: true, score: aggregateSentiment([]).score };
13829
13833
  return {
13830
13834
  ...result,
13831
- summary: { ...result.summary, ...unavailable5, breakdowns: result.summary.breakdowns.map((row) => ({ ...row, ...unavailable5 })) },
13835
+ summary: { ...result.summary, ...unavailable5, breakdowns: result.summary.breakdowns.map((row) => ({ ...row, ...unavailable5 })), criticizedProperties: void 0 },
13832
13836
  queries: result.queries.map((row) => ({ ...row, ...unavailable5, assessments: row.assessments.map((assessment) => ({ ...assessment, state: "unsupported", outcome: null, reason: read.reason })), locations: row.locations.map((location) => ({ ...location, ...unavailable5 })) }))
13833
13837
  };
13834
13838
  }
@@ -13866,11 +13870,12 @@ var SentimentService = class {
13866
13870
  return { configured, branded, nonBrand, overall: overallSentiment(reads, !configured) };
13867
13871
  }
13868
13872
  evidence(projectId, query, limit, cursor) {
13869
- const { assessmentId, ...sourceSelection } = query;
13873
+ const { assessmentId, outcome: outcome2, ...sourceSelection } = query;
13874
+ const outcomes = outcome2?.length ? new Set(outcome2) : null;
13870
13875
  const settings = this.settings(projectId);
13871
13876
  const disabled = !settings.enabled || !settings.installEnabled;
13872
13877
  const read = disabled ? this.readSelection(projectId, sourceSelection) : this.readSelection(projectId, sourceSelection, true);
13873
- const selection = { ...read.selection, ...assessmentId ? { assessmentId } : {} };
13878
+ const selection = { ...read.selection, ...assessmentId ? { assessmentId } : {}, ...outcomes ? { outcome: [...outcomes].sort() } : {} };
13874
13879
  let after = "";
13875
13880
  if (cursor) {
13876
13881
  const token = this.verify(cursor);
@@ -13879,7 +13884,7 @@ var SentimentService = class {
13879
13884
  }
13880
13885
  const state = this.present(read, settings, null).summary.state;
13881
13886
  if (disabled) return { state, selection, items: [], nextCursor: null };
13882
- const items = read.items.filter((item2) => !assessmentId || item2.work.id === assessmentId).sort((a, b) => a.work.id.localeCompare(b.work.id)).filter((item2) => item2.work.id > after);
13887
+ const items = read.items.filter((item2) => (!assessmentId || item2.work.id === assessmentId) && (!outcomes || outcomes.has(item2.outcome))).sort((a, b) => a.work.id.localeCompare(b.work.id)).filter((item2) => item2.work.id > after);
13883
13888
  const page3 = items.slice(0, limit);
13884
13889
  return { state, selection, items: page3.map((item2) => item2.evidence), nextCursor: items.length > limit ? this.sign({ projectId, selection, fingerprint: read.fingerprint, after: page3.at(-1).work.id }) : null };
13885
13890
  }
@@ -13896,8 +13901,8 @@ var SentimentService = class {
13896
13901
  const fromUnits = units(fromItems), toUnits = units(toItems);
13897
13902
  const common = new Set([...fromUnits.byUnit.keys()].filter((key) => toUnits.byUnit.has(key)));
13898
13903
  const settings = this.settings(projectId);
13899
- const fromSummary = withoutNodeKeys(this.aggregate(from.selection, fromItems.filter((item2) => common.has(fromUnits.byItem.get(item2))), from.source, settings, COMPACT_ROWS, fromItems));
13900
- const toSummary = withoutNodeKeys(this.aggregate(to.selection, toItems.filter((item2) => common.has(toUnits.byItem.get(item2))), to.source, settings, COMPACT_ROWS, toItems));
13904
+ const fromSummary = withoutCriticizedProperties(withoutNodeKeys(this.aggregate(from.selection, fromItems.filter((item2) => common.has(fromUnits.byItem.get(item2))), from.source, settings, COMPACT_ROWS, fromItems)));
13905
+ const toSummary = withoutCriticizedProperties(withoutNodeKeys(this.aggregate(to.selection, toItems.filter((item2) => common.has(toUnits.byItem.get(item2))), to.source, settings, COMPACT_ROWS, toItems)));
13901
13906
  const refusalReasons = [];
13902
13907
  const expectedFrom = from.source.assessments.length, expectedTo = to.source.assessments.length;
13903
13908
  if (fromItems.length < expectedFrom || toItems.length < expectedTo) refusalReasons.push("classification-coverage-gap");
@@ -13970,6 +13975,10 @@ function append2(map, key, value) {
13970
13975
  function queryRowKey(edge) {
13971
13976
  return `${"queryKey" in edge ? edge.queryKey : edge.queryId}\0${edge.executionNodeKey ?? ""}`;
13972
13977
  }
13978
+ function withoutCriticizedProperties(summary3) {
13979
+ const { criticizedProperties: _criticized, ...rest } = summary3;
13980
+ return rest;
13981
+ }
13973
13982
  function withoutNodeKeys({ summary: summary3, queries: queries2 }) {
13974
13983
  return { ...summary3, queries: queries2.map(({ executionNodeKey: _node, ...row }) => row) };
13975
13984
  }
@@ -19061,7 +19070,7 @@ function evaluateRunFill(db, run, input = {}) {
19061
19070
  "The measurement plan was republished since this run, so answers added now would mix two revisions in one run."
19062
19071
  );
19063
19072
  }
19064
- const now = (input.now ?? /* @__PURE__ */ new Date()).getTime();
19073
+ const now = Date.now();
19065
19074
  const age = runFillAgeAnchor(run, { hadProviderBatch: runHadProviderBatch(db, run.id) });
19066
19075
  const anchoredAt = Date.parse(age.anchor);
19067
19076
  if (Number.isFinite(anchoredAt) && now - anchoredAt > RUN_FILL_MAX_AGE_MS) {
@@ -19195,7 +19204,7 @@ function queueRunFill(db, runId, input = {}) {
19195
19204
  if (activeFill) return { kind: "fill-in-progress", fillId: activeFill.id };
19196
19205
  const activeSweep = activeSweepFor(txDb, run.projectId);
19197
19206
  if (activeSweep) return { kind: "run-in-progress", activeRunId: activeSweep.id };
19198
- const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
19207
+ const now = (/* @__PURE__ */ new Date()).toISOString();
19199
19208
  const id = crypto14.randomUUID();
19200
19209
  txDb.insert(runFills).values({
19201
19210
  id,
@@ -19777,61 +19786,66 @@ function loadRunDetail(app, run) {
19777
19786
  rawResponse: querySnapshots.rawResponse,
19778
19787
  createdAt: querySnapshots.createdAt
19779
19788
  }).from(querySnapshots).leftJoin(queries, eq24(querySnapshots.queryId, queries.id)).where(eq24(querySnapshots.runId, run.id)).all();
19789
+ const mappedSnapshots = snapshots.map((s) => {
19790
+ const rawParsed = parseSnapshotRawResponse(s.rawResponse);
19791
+ const signalGroundingSources = rawParsed.groundingSources.filter(
19792
+ (source) => typeof source === "object" && source !== null && typeof source.uri === "string"
19793
+ );
19794
+ const competitiveSignals = competitiveSignalResolver.resolve({
19795
+ citedDomains: s.citedDomains,
19796
+ groundingSources: signalGroundingSources,
19797
+ answerText: s.answerText
19798
+ });
19799
+ const answerMentioned = project ? resolveSnapshotAnswerMentioned(s, project) : s.answerMentioned ?? false;
19800
+ return {
19801
+ id: s.id,
19802
+ runId: s.runId,
19803
+ queryId: s.queryId,
19804
+ query: s.query,
19805
+ provider: s.provider,
19806
+ citationState: s.citationState,
19807
+ answerMentioned,
19808
+ // Legacy alias of `mentionState`, retained for backwards compatibility.
19809
+ visibilityState: project ? resolveSnapshotVisibilityState(s, project) : answerMentioned ? "visible" : "not-visible",
19810
+ // Canonical vocabulary for answer-text presence; new consumers prefer this.
19811
+ mentionState: project ? resolveSnapshotMentionState(s, project) : answerMentioned ? "mentioned" : "not-mentioned",
19812
+ answerText: s.answerText,
19813
+ citedDomains: s.citedDomains,
19814
+ citedUrls: s.citedUrls,
19815
+ captureStatus: s.captureStatus,
19816
+ sourceCount: s.sourceCount,
19817
+ resolvedCount: s.resolvedCount,
19818
+ captureVersion: s.captureVersion,
19819
+ ...competitiveSignals,
19820
+ // Legacy mixed signal retained for backwards compatibility.
19821
+ competitorOverlap: s.competitorOverlap,
19822
+ recommendedCompetitors: s.recommendedCompetitors,
19823
+ matchedTerms: project ? resolveSnapshotMatchedTerms(s, project) : [],
19824
+ model: s.model ?? rawParsed.model,
19825
+ // Column only. `model` may fall back to the stored envelope because both
19826
+ // record the same requested value; a served id has no such equivalent —
19827
+ // an unrecoverable one stays null rather than echoing configuration.
19828
+ servedModel: s.servedModel,
19829
+ location: s.location,
19830
+ requestedContext: s.requestedContext,
19831
+ supportedContext: s.supportedContext,
19832
+ dispatchMode: s.dispatchMode,
19833
+ stopReason: s.stopReason,
19834
+ usage: s.usage,
19835
+ groundingSources: rawParsed.groundingSources,
19836
+ searchQueries: rawParsed.searchQueries,
19837
+ createdAt: s.createdAt
19838
+ };
19839
+ });
19840
+ const identifiedSignals = mappedSnapshots.flatMap(
19841
+ (snapshot) => typeof snapshot.queryId === "string" && snapshot.queryId.length > 0 ? [{ queryId: snapshot.queryId, citationState: snapshot.citationState, answerMentioned: snapshot.answerMentioned }] : []
19842
+ );
19780
19843
  return {
19781
19844
  ...formatRun(run),
19782
19845
  providerBatches: readRunProviderBatches(app.db, run.id),
19783
19846
  usage: summarizeRunUsage(snapshots),
19784
- snapshots: snapshots.map((s) => {
19785
- const rawParsed = parseSnapshotRawResponse(s.rawResponse);
19786
- const signalGroundingSources = rawParsed.groundingSources.filter(
19787
- (source) => typeof source === "object" && source !== null && typeof source.uri === "string"
19788
- );
19789
- const competitiveSignals = competitiveSignalResolver.resolve({
19790
- citedDomains: s.citedDomains,
19791
- groundingSources: signalGroundingSources,
19792
- answerText: s.answerText
19793
- });
19794
- const answerMentioned = project ? resolveSnapshotAnswerMentioned(s, project) : s.answerMentioned ?? false;
19795
- return {
19796
- id: s.id,
19797
- runId: s.runId,
19798
- queryId: s.queryId,
19799
- query: s.query,
19800
- provider: s.provider,
19801
- citationState: s.citationState,
19802
- answerMentioned,
19803
- // Legacy alias of `mentionState`, retained for backwards compatibility.
19804
- visibilityState: project ? resolveSnapshotVisibilityState(s, project) : answerMentioned ? "visible" : "not-visible",
19805
- // Canonical vocabulary for answer-text presence; new consumers prefer this.
19806
- mentionState: project ? resolveSnapshotMentionState(s, project) : answerMentioned ? "mentioned" : "not-mentioned",
19807
- answerText: s.answerText,
19808
- citedDomains: s.citedDomains,
19809
- citedUrls: s.citedUrls,
19810
- captureStatus: s.captureStatus,
19811
- sourceCount: s.sourceCount,
19812
- resolvedCount: s.resolvedCount,
19813
- captureVersion: s.captureVersion,
19814
- ...competitiveSignals,
19815
- // Legacy mixed signal retained for backwards compatibility.
19816
- competitorOverlap: s.competitorOverlap,
19817
- recommendedCompetitors: s.recommendedCompetitors,
19818
- matchedTerms: project ? resolveSnapshotMatchedTerms(s, project) : [],
19819
- model: s.model ?? rawParsed.model,
19820
- // Column only. `model` may fall back to the stored envelope because both
19821
- // record the same requested value; a served id has no such equivalent —
19822
- // an unrecoverable one stays null rather than echoing configuration.
19823
- servedModel: s.servedModel,
19824
- location: s.location,
19825
- requestedContext: s.requestedContext,
19826
- supportedContext: s.supportedContext,
19827
- dispatchMode: s.dispatchMode,
19828
- stopReason: s.stopReason,
19829
- usage: s.usage,
19830
- groundingSources: rawParsed.groundingSources,
19831
- searchQueries: rawParsed.searchQueries,
19832
- createdAt: s.createdAt
19833
- };
19834
- })
19847
+ snapshots: mappedSnapshots,
19848
+ queryCounts: identifiedSignals.length === mappedSnapshots.length ? summarizeObservedQueryCounts(identifiedSignals) : null
19835
19849
  };
19836
19850
  }
19837
19851
 
@@ -34050,6 +34064,11 @@ function readEarliestGscDataDate(db, projectId) {
34050
34064
  const dimensioned = db.select({ earliest: min(gscSearchData.date) }).from(gscSearchData).where(eq44(gscSearchData.projectId, projectId)).get()?.earliest ?? null;
34051
34065
  return [property, dimensioned].filter((d) => d !== null).reduce((earliest, d) => earliest === null || d < earliest ? d : earliest, null);
34052
34066
  }
34067
+ function readEarliestGscQueryDataDate(db, projectId) {
34068
+ const accurate = db.select({ earliest: min(gscQueryDailyTotals.date) }).from(gscQueryDailyTotals).where(and39(eq44(gscQueryDailyTotals.projectId, projectId), sql17`${gscQueryDailyTotals.date} <> ''`)).get()?.earliest ?? null;
34069
+ const dimensioned = db.select({ earliest: min(gscSearchData.date) }).from(gscSearchData).where(and39(eq44(gscSearchData.projectId, projectId), sql17`${gscSearchData.date} <> ''`)).get()?.earliest ?? null;
34070
+ return [accurate, dimensioned].filter((d) => d !== null).reduce((earliest, d) => earliest === null || d < earliest ? d : earliest, null);
34071
+ }
34053
34072
  function readGscDailyTotals(db, projectId, startDate, endDate) {
34054
34073
  const rows = db.select({
34055
34074
  date: gscDailyTotals.date,
@@ -34588,34 +34607,17 @@ function summarizeFromSnapshots(snapshots) {
34588
34607
  providers: []
34589
34608
  };
34590
34609
  if (snapshots.length === 0) return empty;
34591
- const perQuery = /* @__PURE__ */ new Map();
34592
- const perQueryMentioned = /* @__PURE__ */ new Map();
34610
+ const { totalQueries, citedQueries, mentionedQueries } = summarizeObservedQueryCounts(snapshots);
34593
34611
  const perProvider = /* @__PURE__ */ new Map();
34594
34612
  for (const snap of snapshots) {
34595
34613
  const cited = snap.citationState === CitationStates.cited;
34596
- if (!perQuery.has(snap.queryId) || cited) {
34597
- perQuery.set(snap.queryId, cited);
34598
- }
34599
- const mentioned = snap.answerMentioned === true;
34600
- if (!perQueryMentioned.has(snap.queryId) || mentioned) {
34601
- perQueryMentioned.set(snap.queryId, mentioned);
34602
- }
34603
34614
  const bucket = perProvider.get(snap.provider) ?? { cited: 0, total: 0 };
34604
34615
  bucket.total += 1;
34605
34616
  if (cited) bucket.cited += 1;
34606
34617
  perProvider.set(snap.provider, bucket);
34607
34618
  }
34608
- const totalQueries = perQuery.size;
34609
- let citedQueries = 0;
34610
- for (const wasCited of perQuery.values()) {
34611
- if (wasCited) citedQueries += 1;
34612
- }
34613
34619
  const notCitedQueries = totalQueries - citedQueries;
34614
34620
  const citedRate = totalQueries === 0 ? 0 : Number((citedQueries / totalQueries).toFixed(4));
34615
- let mentionedQueries = 0;
34616
- for (const wasMentioned of perQueryMentioned.values()) {
34617
- if (wasMentioned) mentionedQueries += 1;
34618
- }
34619
34621
  const notMentionedQueries = totalQueries - mentionedQueries;
34620
34622
  const mentionRate3 = totalQueries === 0 ? 0 : Number((mentionedQueries / totalQueries).toFixed(4));
34621
34623
  const providers = [...perProvider.entries()].map(([provider, { cited, total }]) => ({
@@ -36011,6 +36013,7 @@ var SCHEMA_TABLE = {
36011
36013
  MeasurementSetupResponse: measurementSetupResponseSchema,
36012
36014
  NotificationDto: notificationDtoSchema,
36013
36015
  OnboardingTelemetryEvent: onboardingTelemetryEventSchema,
36016
+ UiTelemetryEvent: uiTelemetryEventSchema,
36014
36017
  OrganicEvidenceDto: organicEvidenceDtoSchema,
36015
36018
  ProjectCreateRequest: projectCreateRequestSchema,
36016
36019
  ProjectDto: projectDtoSchema,
@@ -37296,7 +37299,7 @@ var routeCatalog = [
37296
37299
  path: "/api/v1/projects/{name}/sentiment/evidence",
37297
37300
  summary: "Read verbatim sentiment evidence",
37298
37301
  tags: ["sentiment"],
37299
- parameters: [nameParameter, ...sentimentSelectionParameters, { name: "assessmentId", in: "query", description: "Exact stored assessment within the authorized source, subject, scope, and evaluator selection. Does not widen access.", schema: stringSchema }, { name: "cursor", in: "query", description: "Opaque cursor bound to every selection field and resolved evaluator ID.", schema: stringSchema }, { name: "limit", in: "query", description: "Page size only; does not change assessment identity.", schema: { type: "integer", minimum: 1, maximum: 100, default: 50 } }],
37302
+ parameters: [nameParameter, ...sentimentSelectionParameters, { name: "assessmentId", in: "query", description: "Exact stored assessment within the authorized source, subject, scope, and evaluator selection. Does not widen access.", schema: stringSchema }, { name: "outcome", in: "query", description: "Outcomes to keep, repeated or comma-separated (for example mixed,unfavorable). Narrows the page to assessments with these outcomes; never changes how they were scored.", style: "form", explode: true, schema: { type: "array", items: { type: "string", enum: [...sentimentOutcomeSchema.options] }, minItems: 1 } }, { name: "cursor", in: "query", description: "Opaque cursor bound to every selection field and resolved evaluator ID.", schema: stringSchema }, { name: "limit", in: "query", description: "Page size only; does not change assessment identity.", schema: { type: "integer", minimum: 1, maximum: 100, default: 50 } }],
37300
37303
  responses: { 200: jsonResponse("Stored answer-subject evidence page.", "SentimentEvidencePage"), 400: errorResponse("Invalid cursor or selection."), 404: errorResponse("Project or selected source not found.") }
37301
37304
  },
37302
37305
  {
@@ -38386,6 +38389,7 @@ var routeCatalog = [
38386
38389
  method: "get",
38387
38390
  path: "/api/v1/runs/{id}",
38388
38391
  summary: "Get a run and its snapshots",
38392
+ description: "Includes independent citation and mention counts over distinct observed query IDs in this exact run. queryCounts is null when any snapshot lacks query identity; these counts are not planned-slot completeness.",
38389
38393
  tags: ["runs"],
38390
38394
  parameters: [runIdParameter],
38391
38395
  responses: {
@@ -39175,6 +39179,25 @@ var routeCatalog = [
39175
39179
  400: errorResponse("Invalid onboarding telemetry event.")
39176
39180
  }
39177
39181
  },
39182
+ {
39183
+ method: "post",
39184
+ path: "/api/v1/telemetry/ui",
39185
+ summary: "Record a privacy-safe dashboard usage event",
39186
+ description: "Page views (route patterns), allowlisted feature actions, UI errors (class names and route templates only), and web vitals ratings from the dashboard. Forwarded through the opt-in anonymous telemetry path; accepted:false when this host does not collect telemetry.",
39187
+ tags: ["telemetry"],
39188
+ requestBody: {
39189
+ required: true,
39190
+ content: {
39191
+ "application/json": {
39192
+ schema: { $ref: "#/components/schemas/UiTelemetryEvent" }
39193
+ }
39194
+ }
39195
+ },
39196
+ responses: {
39197
+ 202: jsonResponse("UI event accepted.", "TelemetryEventAcceptedDto"),
39198
+ 400: errorResponse("Invalid UI telemetry event.")
39199
+ }
39200
+ },
39178
39201
  {
39179
39202
  method: "post",
39180
39203
  path: "/api/v1/feedback",
@@ -39918,7 +39941,7 @@ var routeCatalog = [
39918
39941
  method: "get",
39919
39942
  path: "/api/v1/projects/{name}/google/gsc/query-totals",
39920
39943
  summary: "Get GSC totals per query for a date window",
39921
- description: "One row per search query over the window, read from stored sync data (no call to Google, no writes): clicks, impressions, CTR, impression-weighted average position (sum(position*impressions)/sum(impressions)) and the number of days the query appeared. Rows are ordered by clicks desc, impressions desc, then query ascending by code point, and paged with `limit` / `offset`; `totalMatching` counts every query and `truncated` says more rows follow. The rows cover the queries Google names only: Google leaves rare and anonymised queries out of per-query data, so their sum is below the property total (read `/google/gsc/performance/daily` for that). Each row carries `source`: `google` when every day came from Google's per-query fetch, `page-summed` when every day came from the legacy page-dimensioned table (impressions over-count), `mixed` when the window spans both.",
39944
+ description: "One row per search query over the window, read from stored sync data (no call to Google, no writes): clicks, impressions, CTR, impression-weighted average position (sum(position*impressions)/sum(impressions)) and the number of days the query appeared. Rows are ordered by clicks desc, impressions desc, then query ascending by code point, and paged with `limit` / `offset`; `totalMatching` counts every query and `truncated` says more rows follow. The rows cover the queries Google names only: Google leaves rare and anonymised queries out of per-query data, so their sum is below the property total (read `/google/gsc/performance/daily` for that). Each row carries `source`: `google` when every day came from Google's per-query fetch, `page-summed` when every day came from the legacy page-dimensioned table (impressions over-count), `mixed` when the window spans both. `window.earliestDataDate` is the first stored per-query date (null with none): a window that starts before it is only partly covered, so its missing days were never synced rather than zero traffic.",
39922
39945
  tags: ["google"],
39923
39946
  parameters: [
39924
39947
  nameParameter,
@@ -43927,6 +43950,19 @@ async function telemetryRoutes(app, opts) {
43927
43950
  opts.recordOnboardingEvent?.(normalizeOnboardingEventForCollection(parsed.data));
43928
43951
  return reply.status(202).send({ accepted: Boolean(opts.recordOnboardingEvent) });
43929
43952
  });
43953
+ app.post("/telemetry/ui", async (request, reply) => {
43954
+ const parsed = uiTelemetryEventSchema.safeParse(request.body);
43955
+ if (!parsed.success) {
43956
+ throw validationError("Invalid UI telemetry event", {
43957
+ issues: parsed.error.issues.map((issue) => ({
43958
+ code: issue.code,
43959
+ path: issue.path.join(".")
43960
+ }))
43961
+ });
43962
+ }
43963
+ const accepted = opts.recordUiEvent ? opts.recordUiEvent(parsed.data) : false;
43964
+ return reply.status(202).send({ accepted });
43965
+ });
43930
43966
  }
43931
43967
 
43932
43968
  // ../api-routes/src/feedback.ts
@@ -47362,7 +47398,9 @@ async function googleRoutes(app, opts) {
47362
47398
  // Measured from where this page ends, so a page past the end is not
47363
47399
  // reported as truncated.
47364
47400
  truncated: offsetVal + rows.length < totalMatching,
47365
- window
47401
+ // The floor beside the frontier, so a caller can tell a window the store
47402
+ // does not reach from one with little traffic.
47403
+ window: { ...window, earliestDataDate: readEarliestGscQueryDataDate(app.db, project.id) }
47366
47404
  };
47367
47405
  });
47368
47406
  app.post("/projects/:name/google/gsc/inspect", async (request) => {
@@ -59044,7 +59082,7 @@ async function installDuckdb(opts = {}) {
59044
59082
  const version = readInstalledVersion({ pluginPkgJson }) ?? "unknown";
59045
59083
  return { alreadyPresent: false, version, path: pluginDir };
59046
59084
  }
59047
- async function ensurePluginDir(pluginDir = PLUGIN_DIR, pluginPkgJson = PLUGIN_PKG_JSON) {
59085
+ async function ensurePluginDir(pluginDir, pluginPkgJson) {
59048
59086
  await fs4.mkdir(pluginDir, { recursive: true });
59049
59087
  try {
59050
59088
  await fs4.access(pluginPkgJson);
@@ -75230,7 +75268,6 @@ async function resolveRetainedStart(options, unservableStartMs, endMs) {
75230
75268
  async function drainVercelTrafficEvents(options) {
75231
75269
  const startMs = toMs(options.startDate);
75232
75270
  const endMs = toMs(options.endDate);
75233
- const now = options.now ?? (() => Date.now());
75234
75271
  const events = [];
75235
75272
  const seenEventIds = /* @__PURE__ */ new Set();
75236
75273
  if (endMs <= startMs) {
@@ -75249,7 +75286,7 @@ async function drainVercelTrafficEvents(options) {
75249
75286
  const truncatedSliceStartsMs = [];
75250
75287
  const deadlineSkippedSliceStartsMs = [];
75251
75288
  while (cursorMs < endMs) {
75252
- if (options.deadlineMs !== void 0 && now() >= options.deadlineMs) {
75289
+ if (options.deadlineMs !== void 0 && Date.now() >= options.deadlineMs) {
75253
75290
  deadlineReached = true;
75254
75291
  if (cursorMs === effectiveStartMs && subWindowCount > 0 && cursorMs < endMs) {
75255
75292
  deadlineSkippedSliceStartsMs.push(cursorMs);
@@ -86928,7 +86965,8 @@ async function apiRoutes(app, opts) {
86928
86965
  await api.register(telemetryRoutes, {
86929
86966
  getTelemetryStatus: opts.getTelemetryStatus,
86930
86967
  setTelemetryEnabled: opts.setTelemetryEnabled,
86931
- recordOnboardingEvent: opts.recordOnboardingEvent
86968
+ recordOnboardingEvent: opts.recordOnboardingEvent,
86969
+ recordUiEvent: opts.recordUiEvent
86932
86970
  });
86933
86971
  await api.register(feedbackRoutes, {
86934
86972
  submitFeedback: opts.submitFeedback
@@ -88520,6 +88558,7 @@ export {
88520
88558
  createCredentialChecker,
88521
88559
  requireInstanceAdministrator,
88522
88560
  hashApiKey,
88561
+ notProbeRun,
88523
88562
  registerAgentConversationRoutes,
88524
88563
  measurementSlotKey,
88525
88564
  measurementRunSlotState,
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-EZ54X4XH.js";
8
+ } from "./chunk-RCQJI3UX.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -13,7 +13,7 @@ import {
13
13
  TEMPLATE_LINK_RATIO_THRESHOLD,
14
14
  factorStatusFromScore,
15
15
  roundPreservingTotal
16
- } from "./chunk-OOUDIVIU.js";
16
+ } from "./chunk-TY3CJKKR.js";
17
17
 
18
18
  // src/site-audit-factors.ts
19
19
  function recordedSharePct(factor) {