@canonry/canonry 7.46.0 → 7.47.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.
- package/assets/assets/{AuditHistoryPanel-u65pF2eT.js → AuditHistoryPanel-BTlvVW6p.js} +1 -1
- package/assets/assets/{BacklinksPage-CmYpSNGB.js → BacklinksPage-DQu_3poO.js} +1 -1
- package/assets/assets/{HistoryPage-CqzQSAil.js → HistoryPage-D_3qu4SQ.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-KfpmUisb.js → MeasurementPropertyPage-FKmEMw_G.js} +1 -1
- package/assets/assets/{ProjectPage-BCiospUp.js → ProjectPage-H7nsLkhG.js} +1 -1
- package/assets/assets/{RunsPage-CzPvTnwG.js → RunsPage-CE5wFNdP.js} +1 -1
- package/assets/assets/{SettingsPage-CZYckyPO.js → SettingsPage-D2zKpqOv.js} +1 -1
- package/assets/assets/{SiteHealthSection-Bm5nWAoZ.js → SiteHealthSection-ClZRxJ-A.js} +3 -3
- package/assets/assets/{TrafficPage-DZw_GvaO.js → TrafficPage-BDH4Dzk0.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CD20PJ4A.js → TrafficSourceDetailPage-DaVUcHXu.js} +1 -1
- package/assets/assets/{extract-error-message-Cpzj_AMZ.js → extract-error-message-YXzyZAEc.js} +1 -1
- package/assets/assets/{index-BHLYJHa-.js → index-Zj8hd7aE.js} +3 -3
- package/assets/assets/{react-sigma_core.esm.min-CK2U3T7S.js → react-sigma_core.esm.min-9j5NHFWR.js} +1 -1
- package/assets/assets/{v2-overview-adapter-DL1IsAM2.js → v2-overview-adapter-BsKpkQvx.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-KPOZR6MV.js → chunk-3F4KBEMY.js} +2 -2
- package/dist/{chunk-Y2QMWZT7.js → chunk-66K6U5H5.js} +3 -3
- package/dist/{chunk-WMG365JI.js → chunk-CIYCCA2I.js} +11 -2
- package/dist/{chunk-QBZPKXFW.js → chunk-DGKSQ5QD.js} +1 -1
- package/dist/{chunk-GWRIZSIL.js → chunk-HQS2Y7HO.js} +3 -3
- package/dist/{chunk-ZOTA3MOL.js → chunk-KCSU2OSL.js} +242 -112
- package/dist/{chunk-V4TITINC.js → chunk-QS262E4F.js} +9 -1
- package/dist/{chunk-AYMS4LCP.js → chunk-RUAQAQ7X.js} +225 -210
- package/dist/{chunk-PCZXWOYX.js → chunk-XHRJYIJQ.js} +1 -1
- package/dist/{chunk-2UQ6WVWP.js → chunk-XLKWD5WO.js} +9 -9
- package/dist/{cli-catalog-TFCCKIE4.js → cli-catalog-MRYQK7LI.js} +3 -3
- package/dist/cli-commands-RJF4TCEZ.js +15 -0
- package/dist/cli.js +10 -10
- package/dist/{demo-server-7YFRG3JM.js → demo-server-ASCEFIVC.js} +4 -4
- package/dist/index.d.ts +12 -0
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-P3UJNCVS.js → intelligence-service-VRWJUHNM.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +11 -11
- package/dist/cli-commands-GYWXFE3P.js +0 -15
|
@@ -147,6 +147,7 @@ import {
|
|
|
147
147
|
RatioUnits,
|
|
148
148
|
ResearchQueryStatuses,
|
|
149
149
|
ResearchRunStatuses,
|
|
150
|
+
RetrievalStatuses,
|
|
150
151
|
RunFillRefusalCodes,
|
|
151
152
|
RunKinds,
|
|
152
153
|
RunStatuses,
|
|
@@ -888,6 +889,7 @@ import {
|
|
|
888
889
|
unsupportedKind,
|
|
889
890
|
upgradeCaveatFor,
|
|
890
891
|
usableBrandAliases,
|
|
892
|
+
usageSurfaceSchema,
|
|
891
893
|
userDtoSchema,
|
|
892
894
|
userListDtoSchema,
|
|
893
895
|
validationError,
|
|
@@ -913,7 +915,7 @@ import {
|
|
|
913
915
|
wordpressSchemaDeployResultDtoSchema,
|
|
914
916
|
wordpressSchemaStatusResultDtoSchema,
|
|
915
917
|
wordpressStatusDtoSchema
|
|
916
|
-
} from "./chunk-
|
|
918
|
+
} from "./chunk-QS262E4F.js";
|
|
917
919
|
|
|
918
920
|
// src/intelligence-service.ts
|
|
919
921
|
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";
|
|
@@ -30901,6 +30903,7 @@ function visibilityComparisonPopulation(run, selection) {
|
|
|
30901
30903
|
answerMentioned: signals.mention,
|
|
30902
30904
|
mentionApplicable: candidate.edges.some((edge) => targets.get(edge.targetKey)?.mentionEligible === true),
|
|
30903
30905
|
citation: citationForCoverage(candidate, targets),
|
|
30906
|
+
retrievalStatus: candidate.observation.retrievalStatus ?? null,
|
|
30904
30907
|
answerText: candidate.observation.answerText,
|
|
30905
30908
|
competitorDomains: [...new Set(candidate.edges.flatMap((edge) => edge.competitorDomains))],
|
|
30906
30909
|
competitorMentions: candidate.observation.competitorMentionDomains.filter((domain) => candidate.edges.some((edge) => edge.competitorDomains.includes(domain))),
|
|
@@ -30965,6 +30968,7 @@ function loadVisibilitySnapshots(db, runIds) {
|
|
|
30965
30968
|
citedDomains: querySnapshots.citedDomains,
|
|
30966
30969
|
citedUrls: querySnapshots.citedUrls,
|
|
30967
30970
|
captureStatus: querySnapshots.captureStatus,
|
|
30971
|
+
retrievalStatus: querySnapshots.retrievalStatus,
|
|
30968
30972
|
location: querySnapshots.location,
|
|
30969
30973
|
measurementExecutionId: querySnapshots.measurementExecutionId,
|
|
30970
30974
|
requestedContext: querySnapshots.requestedContext,
|
|
@@ -31110,6 +31114,7 @@ function v2Observations(plan, revision, run, snapshots, includeEvidence = true)
|
|
|
31110
31114
|
unknownMentionTargetKeys: attribution?.unknownMentionTargetIds ?? [],
|
|
31111
31115
|
citedTargetKeys: attribution?.citedTargetIds ?? [],
|
|
31112
31116
|
citationComplete: attribution?.sourceComplete ?? false,
|
|
31117
|
+
retrievalStatus: raw.retrievalStatus ?? null,
|
|
31113
31118
|
competitorMentionDomains: signals.mentioned,
|
|
31114
31119
|
competitorCitationDomains: signals.cited,
|
|
31115
31120
|
observedCompetitorNames: includeEvidence ? observedCompetitorNames2(raw.recommendedCompetitors) : [],
|
|
@@ -31233,6 +31238,7 @@ function frozenSimpleRun(run, definition2, snapshots, includeEvidence = true) {
|
|
|
31233
31238
|
// Simple citation state comes from the stored cited domains, not from
|
|
31234
31239
|
// source-URL capture, so a saved Simple answer is never `unchecked`.
|
|
31235
31240
|
citationComplete: true,
|
|
31241
|
+
retrievalStatus: snapshot.retrievalStatus ?? null,
|
|
31236
31242
|
competitorMentionDomains: competitorSignalsForSnapshot.mentioned,
|
|
31237
31243
|
competitorCitationDomains: competitorSignalsForSnapshot.cited,
|
|
31238
31244
|
observedCompetitorNames: includeEvidence ? observedCompetitorNames2(snapshot.recommendedCompetitors) : [],
|
|
@@ -31316,6 +31322,7 @@ function legacySimpleRun(project, run, snapshots, includeEvidence = true) {
|
|
|
31316
31322
|
citedTargetKeys: snapshot.citationState === "cited" ? ["project"] : [],
|
|
31317
31323
|
// Legacy rows carry only the stored citation state (see the frozen-simple note).
|
|
31318
31324
|
citationComplete: true,
|
|
31325
|
+
retrievalStatus: snapshot.retrievalStatus ?? null,
|
|
31319
31326
|
competitorMentionDomains: [],
|
|
31320
31327
|
competitorCitationDomains: [],
|
|
31321
31328
|
observedCompetitorNames: includeEvidence ? observedCompetitorNames2(snapshot.recommendedCompetitors) : [],
|
|
@@ -37543,6 +37550,7 @@ function countPeriod(snaps, competitors2, queryClassOf, classificationAvailable,
|
|
|
37543
37550
|
let total = 0;
|
|
37544
37551
|
let cited = 0;
|
|
37545
37552
|
let citationUnknown = 0;
|
|
37553
|
+
let citationNotSearched = 0;
|
|
37546
37554
|
let competitorCited = 0;
|
|
37547
37555
|
const perProvider = /* @__PURE__ */ new Map();
|
|
37548
37556
|
const mentionedQueries = /* @__PURE__ */ new Set();
|
|
@@ -37555,9 +37563,11 @@ function countPeriod(snaps, competitors2, queryClassOf, classificationAvailable,
|
|
|
37555
37563
|
mentionedQueries.add(snap.queryId);
|
|
37556
37564
|
}
|
|
37557
37565
|
const citationKnown = snap.citationChecked !== false;
|
|
37558
|
-
|
|
37559
|
-
|
|
37560
|
-
|
|
37566
|
+
const noRetrieval = citationKnown && snap.retrievalStatus === RetrievalStatuses["not-used"];
|
|
37567
|
+
if (!citationKnown) citationUnknown += 1;
|
|
37568
|
+
else if (noRetrieval) citationNotSearched += 1;
|
|
37569
|
+
else total += 1;
|
|
37570
|
+
const isCited = citationKnown && !noRetrieval && snap.citationState === CitationStates.cited;
|
|
37561
37571
|
if (isCited) cited += 1;
|
|
37562
37572
|
const pp = perProvider.get(snap.provider) ?? { checked: 0, mentioned: 0, cited: 0 };
|
|
37563
37573
|
if (snap.answerMentioned === true || snap.answerMentioned === false) pp.checked += 1;
|
|
@@ -37602,6 +37612,7 @@ function countPeriod(snaps, competitors2, queryClassOf, classificationAvailable,
|
|
|
37602
37612
|
total,
|
|
37603
37613
|
cited,
|
|
37604
37614
|
citationUnknown,
|
|
37615
|
+
citationNotSearched,
|
|
37605
37616
|
citationEvidence: snaps.some((snapshot) => snapshot.citationChecked !== void 0),
|
|
37606
37617
|
projectCited: cited,
|
|
37607
37618
|
competitorCited,
|
|
@@ -37661,7 +37672,7 @@ function computeVisibilityCompare(input) {
|
|
|
37661
37672
|
});
|
|
37662
37673
|
const fromShare = shareCounts(fromShareCounts);
|
|
37663
37674
|
const toShare = shareCounts(toShareCounts);
|
|
37664
|
-
const citationPeriod = (c, value) => c.citationEvidence ? { ...value, excludedUnknown: c.citationUnknown } : value;
|
|
37675
|
+
const citationPeriod = (c, value) => c.citationEvidence ? { ...value, excludedUnknown: c.citationUnknown, ...c.citationNotSearched > 0 ? { excludedNoRetrieval: c.citationNotSearched } : {} } : value;
|
|
37665
37676
|
const metrics = [
|
|
37666
37677
|
metric(
|
|
37667
37678
|
"mention-share-of-voice",
|
|
@@ -37708,7 +37719,7 @@ function computeVisibilityCompare(input) {
|
|
|
37708
37719
|
const classPeriod = (snapshots, queryClass, signal) => {
|
|
37709
37720
|
if (!classificationAvailable) return { ...period(0, 0), availability: "classification-unavailable" };
|
|
37710
37721
|
const selected = snapshots.filter((snapshot) => queryClassOf(snapshot) === queryClass && (signal === "cited" || snapshot.mentionApplicable !== false));
|
|
37711
|
-
const checked = selected.filter((snapshot) => signal === "mention" ? typeof snapshot.answerMentioned === "boolean" : snapshot.citationChecked !== false);
|
|
37722
|
+
const checked = selected.filter((snapshot) => signal === "mention" ? typeof snapshot.answerMentioned === "boolean" : snapshot.citationChecked !== false && snapshot.retrievalStatus !== RetrievalStatuses["not-used"]);
|
|
37712
37723
|
const numerator = checked.filter((snapshot) => signal === "mention" ? snapshot.answerMentioned === true : snapshot.citationState === CitationStates.cited).length;
|
|
37713
37724
|
return { ...period(numerator, checked.length), excludedUnknown: selected.length - checked.length };
|
|
37714
37725
|
};
|
|
@@ -43354,7 +43365,7 @@ var routeCatalog = [
|
|
|
43354
43365
|
method: "get",
|
|
43355
43366
|
path: "/api/v1/projects/{name}/visibility-compare",
|
|
43356
43367
|
summary: "Compare AEO visibility month over month",
|
|
43357
|
-
description: "Statistically honest month-over-month AEO comparison in one call. PRIMARY metric is share of voice (brand vs competitor mentions in the same answers), which is less exposed to broad model-wide naming propensity than an absolute rate but never bypasses model continuity. Rates are pooled per-snapshot over each month (invariant to sweep count), restricted to query/provider pairs present in BOTH months, and then restricted again to providers with exactly one known, identical configured model id in both months. `continuity` reports every provider and its model evidence; changed, mixed mid-month, or legacy-unknown models are excluded. When no provider remains, metrics return a continuity-blocked verdict rather than a directional call. `from` must be a month strictly before `to`. Adds separate branded/non-brand mention and cited rates with classification-unavailable rather than pooled fallback. Advanced class metrics read frozen Property/market/execution assignments and reuse each answer once per class. Without a scope or marketKey the four original metrics keep the project frame (provider/location narrow it the same way for Simple and Advanced) and Advanced class metrics carry their own `classComparison` cohort. A scope or marketKey answers entirely from the frozen frame and is refused when either month holds runs whose plan cannot be reconstructed (schema v1). On frozen frames, answers with incomplete source capture leave every citation figure and are counted in `excludedUnknown`. A silent upstream version bump under an unchanged configured id remains undetectable. competitorIdentityChangedAt is when the project's competitor names last changed (a tracked competitor's curated or stored auto-detected names (a block counts only when it removes a stored name), a detection pass or a write that moved a market-only competitor's learned names, or a published revision that renames a market pin). Share of voice reads both months with the tracked competitors' current names only, so a comparison quoted before a change to those names was restated; a market pin or learned-name change moves the time without changing share of voice. A scoped comparison (a group, market or property scope, or a marketKey) reads frozen plan history and carries no competitorIdentityChangedAt.",
|
|
43368
|
+
description: "Statistically honest month-over-month AEO comparison in one call. PRIMARY metric is share of voice (brand vs competitor mentions in the same answers), which is less exposed to broad model-wide naming propensity than an absolute rate but never bypasses model continuity. Rates are pooled per-snapshot over each month (invariant to sweep count), restricted to query/provider pairs present in BOTH months, and then restricted again to providers with exactly one known, identical configured model id in both months. `continuity` reports every provider and its model evidence; changed, mixed mid-month, or legacy-unknown models are excluded. When no provider remains, metrics return a continuity-blocked verdict rather than a directional call. `from` must be a month strictly before `to`. Adds separate branded/non-brand mention and cited rates with classification-unavailable rather than pooled fallback. Advanced class metrics read frozen Property/market/execution assignments and reuse each answer once per class. Without a scope or marketKey the four original metrics keep the project frame (provider/location narrow it the same way for Simple and Advanced) and Advanced class metrics carry their own `classComparison` cohort. A scope or marketKey answers entirely from the frozen frame and is refused when either month holds runs whose plan cannot be reconstructed (schema v1). On frozen frames, answers with incomplete source capture leave every citation figure and are counted in `excludedUnknown`. An answer the provider wrote with no retrieval step at all (retrievalStatus of not-used) cites nothing by construction, so it also leaves the cited denominator and is counted in `excludedNoRetrieval`; it stays in the mention denominator, since brand recall does not need search. A silent upstream version bump under an unchanged configured id remains undetectable. competitorIdentityChangedAt is when the project's competitor names last changed (a tracked competitor's curated or stored auto-detected names (a block counts only when it removes a stored name), a detection pass or a write that moved a market-only competitor's learned names, or a published revision that renames a market pin). Share of voice reads both months with the tracked competitors' current names only, so a comparison quoted before a change to those names was restated; a market pin or learned-name change moves the time without changing share of voice. A scoped comparison (a group, market or property scope, or a marketKey) reads frozen plan history and carries no competitorIdentityChangedAt.",
|
|
43358
43369
|
tags: ["analytics"],
|
|
43359
43370
|
parameters: [
|
|
43360
43371
|
nameParameter,
|
|
@@ -46517,6 +46528,7 @@ var routeCatalog = [
|
|
|
46517
46528
|
// TODO: Add `GaConnectResponse` Zod schema in contracts.
|
|
46518
46529
|
200: rawJsonResponse("GA4 connection established.", looseObjectSchema),
|
|
46519
46530
|
400: errorResponse("Invalid GA4 connection request."),
|
|
46531
|
+
403: errorResponse("Google refused the property (`details.reason` is `ga4-permission-missing`). Retrying will not help until access is granted; repeated refusals are answered without asking Google again until `details.nextCheckAt`."),
|
|
46520
46532
|
404: errorResponse("Project not found.")
|
|
46521
46533
|
}
|
|
46522
46534
|
},
|
|
@@ -60100,11 +60112,76 @@ async function adsRoutes(app, opts) {
|
|
|
60100
60112
|
}
|
|
60101
60113
|
|
|
60102
60114
|
// ../api-routes/src/bing.ts
|
|
60103
|
-
import
|
|
60115
|
+
import crypto43 from "crypto";
|
|
60104
60116
|
import { eq as eq66, and as and54, desc as desc32 } from "drizzle-orm";
|
|
60105
60117
|
|
|
60106
|
-
// ../
|
|
60118
|
+
// ../api-routes/src/connection-verify-cache.ts
|
|
60107
60119
|
import crypto41 from "crypto";
|
|
60120
|
+
var CONNECTION_VERIFY_TTL_MS = 15 * 60 * 1e3;
|
|
60121
|
+
var REFUSALS_BEFORE_BACKOFF = 3;
|
|
60122
|
+
var FIRST_BACKOFF_MS = 5 * 60 * 1e3;
|
|
60123
|
+
var MAX_BACKOFF_MS = 60 * 60 * 1e3;
|
|
60124
|
+
var MAX_ENTRIES = 500;
|
|
60125
|
+
function remember(entries, key, value) {
|
|
60126
|
+
entries.delete(key);
|
|
60127
|
+
entries.set(key, value);
|
|
60128
|
+
if (entries.size > MAX_ENTRIES) entries.delete(entries.keys().next().value);
|
|
60129
|
+
}
|
|
60130
|
+
function isInteractiveCaller() {
|
|
60131
|
+
const attribution = currentOutcomeAttribution();
|
|
60132
|
+
const label = usageSurfaceSchema.safeParse(attribution?.surfaceLabel);
|
|
60133
|
+
if (label.success) return label.data === "cli" || label.data === "dashboard";
|
|
60134
|
+
const ua = attribution?.userAgent ?? "";
|
|
60135
|
+
return ua.startsWith("canonry-cli") || ua.startsWith("Mozilla/");
|
|
60136
|
+
}
|
|
60137
|
+
function verifyCacheKey(...parts) {
|
|
60138
|
+
return crypto41.createHash("sha256").update(parts.join("\0")).digest("hex");
|
|
60139
|
+
}
|
|
60140
|
+
function createVerifyCache(ttlMs = CONNECTION_VERIFY_TTL_MS) {
|
|
60141
|
+
const entries = /* @__PURE__ */ new Map();
|
|
60142
|
+
return {
|
|
60143
|
+
get(key) {
|
|
60144
|
+
const entry = entries.get(key);
|
|
60145
|
+
if (!entry) return void 0;
|
|
60146
|
+
if (Date.now() - entry.at < ttlMs) return entry.value;
|
|
60147
|
+
entries.delete(key);
|
|
60148
|
+
return void 0;
|
|
60149
|
+
},
|
|
60150
|
+
set(key, value) {
|
|
60151
|
+
remember(entries, key, { value, at: Date.now() });
|
|
60152
|
+
}
|
|
60153
|
+
};
|
|
60154
|
+
}
|
|
60155
|
+
function createRefusalBackoff() {
|
|
60156
|
+
const entries = /* @__PURE__ */ new Map();
|
|
60157
|
+
return {
|
|
60158
|
+
/** The refusal to answer with instead of asking the upstream, while this key is backing off. */
|
|
60159
|
+
replay(key) {
|
|
60160
|
+
const entry = entries.get(key);
|
|
60161
|
+
if (!entry || entry.count < REFUSALS_BEFORE_BACKOFF) return void 0;
|
|
60162
|
+
const wait = Math.min(FIRST_BACKOFF_MS * 2 ** (entry.count - REFUSALS_BEFORE_BACKOFF), MAX_BACKOFF_MS);
|
|
60163
|
+
const nextCheckAt = entry.at + wait;
|
|
60164
|
+
if (Date.now() >= nextCheckAt) return void 0;
|
|
60165
|
+
const { error } = entry;
|
|
60166
|
+
return {
|
|
60167
|
+
error: new AppError(error.code, error.message, error.statusCode, {
|
|
60168
|
+
...error.details,
|
|
60169
|
+
nextCheckAt: new Date(nextCheckAt).toISOString()
|
|
60170
|
+
}),
|
|
60171
|
+
cause: entry.cause
|
|
60172
|
+
};
|
|
60173
|
+
},
|
|
60174
|
+
refused(key, error, cause) {
|
|
60175
|
+
remember(entries, key, { count: (entries.get(key)?.count ?? 0) + 1, at: Date.now(), error, cause });
|
|
60176
|
+
},
|
|
60177
|
+
cleared(key) {
|
|
60178
|
+
entries.delete(key);
|
|
60179
|
+
}
|
|
60180
|
+
};
|
|
60181
|
+
}
|
|
60182
|
+
|
|
60183
|
+
// ../integration-bing/src/bing-client.ts
|
|
60184
|
+
import crypto42 from "crypto";
|
|
60108
60185
|
|
|
60109
60186
|
// ../integration-bing/src/constants.ts
|
|
60110
60187
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -60259,7 +60336,7 @@ async function bingFetchOnce(apiKey, endpoint, opts) {
|
|
|
60259
60336
|
var throttleCooldownUntil = /* @__PURE__ */ new Map();
|
|
60260
60337
|
var cooldownNow = () => Date.now();
|
|
60261
60338
|
function cooldownKey(apiKey) {
|
|
60262
|
-
return
|
|
60339
|
+
return crypto42.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
|
|
60263
60340
|
}
|
|
60264
60341
|
function throttleCooldownRemainingMs(apiKey) {
|
|
60265
60342
|
const until = throttleCooldownUntil.get(cooldownKey(apiKey));
|
|
@@ -60403,6 +60480,7 @@ async function loadBlockingCrawlIssues(apiKey, siteUrl, domain) {
|
|
|
60403
60480
|
return blockedUrls;
|
|
60404
60481
|
}
|
|
60405
60482
|
async function bingRoutes(app, opts) {
|
|
60483
|
+
const keyChecks = createVerifyCache();
|
|
60406
60484
|
function requireConnectionStore() {
|
|
60407
60485
|
if (opts.bingConnectionStore) return opts.bingConnectionStore;
|
|
60408
60486
|
throw validationError("Bing connection storage is not configured for this deployment");
|
|
@@ -60421,16 +60499,21 @@ async function bingRoutes(app, opts) {
|
|
|
60421
60499
|
throw validationError("apiKey is required");
|
|
60422
60500
|
}
|
|
60423
60501
|
const project = resolveProject(app.db, request.params.name);
|
|
60424
|
-
|
|
60425
|
-
|
|
60426
|
-
|
|
60427
|
-
|
|
60428
|
-
|
|
60429
|
-
|
|
60430
|
-
|
|
60431
|
-
|
|
60432
|
-
|
|
60502
|
+
const keyCheck = verifyCacheKey("bing-key", apiKey);
|
|
60503
|
+
let sites = isInteractiveCaller() ? void 0 : keyChecks.get(keyCheck);
|
|
60504
|
+
if (!sites) {
|
|
60505
|
+
try {
|
|
60506
|
+
sites = await getSites(apiKey);
|
|
60507
|
+
keyChecks.set(keyCheck, sites);
|
|
60508
|
+
bingLog("info", "connect.verify-key", { domain: project.canonicalDomain, siteCount: sites.length });
|
|
60509
|
+
} catch (e) {
|
|
60510
|
+
const msg = describeError(e);
|
|
60511
|
+
bingLog("error", "connect.verify-key-failed", { domain: project.canonicalDomain, error: msg });
|
|
60512
|
+
attempt.failed(e);
|
|
60513
|
+
throw validationError(`Failed to verify Bing API key: ${msg}`);
|
|
60514
|
+
}
|
|
60433
60515
|
}
|
|
60516
|
+
const availableSites = sites.map((s) => ({ url: s.Url, verified: s.IsVerified ?? false }));
|
|
60434
60517
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60435
60518
|
const existing = store.getConnection(project.canonicalDomain);
|
|
60436
60519
|
if (existing) attempt.update({ action: "reauth" });
|
|
@@ -60440,6 +60523,9 @@ async function bingRoutes(app, opts) {
|
|
|
60440
60523
|
`This domain already has a Bing connection owned by another project. Disconnect it from that project first before re-connecting from "${project.name}".`
|
|
60441
60524
|
);
|
|
60442
60525
|
}
|
|
60526
|
+
if (existing?.apiKey === apiKey && existing.createdByProjectId) {
|
|
60527
|
+
return { connected: true, domain: project.canonicalDomain, siteUrl: existing.siteUrl ?? null, availableSites };
|
|
60528
|
+
}
|
|
60443
60529
|
store.upsertConnection({
|
|
60444
60530
|
domain: project.canonicalDomain,
|
|
60445
60531
|
apiKey,
|
|
@@ -60459,7 +60545,7 @@ async function bingRoutes(app, opts) {
|
|
|
60459
60545
|
connected: true,
|
|
60460
60546
|
domain: project.canonicalDomain,
|
|
60461
60547
|
siteUrl: existing?.siteUrl ?? null,
|
|
60462
|
-
availableSites
|
|
60548
|
+
availableSites
|
|
60463
60549
|
};
|
|
60464
60550
|
}));
|
|
60465
60551
|
app.delete("/projects/:name/bing/disconnect", connectionRoute(app, () => ({ integration: "bing", action: "disconnect" }), async (request, reply, attempt) => {
|
|
@@ -60589,7 +60675,7 @@ async function bingRoutes(app, opts) {
|
|
|
60589
60675
|
const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
60590
60676
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60591
60677
|
app.db.insert(bingCoverageSnapshots).values({
|
|
60592
|
-
id:
|
|
60678
|
+
id: crypto43.randomUUID(),
|
|
60593
60679
|
projectId: project.id,
|
|
60594
60680
|
syncRunId: snapshotRunId,
|
|
60595
60681
|
date: snapshotDate,
|
|
@@ -60664,7 +60750,7 @@ async function bingRoutes(app, opts) {
|
|
|
60664
60750
|
throw validationError("url is required");
|
|
60665
60751
|
}
|
|
60666
60752
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
60667
|
-
const runId =
|
|
60753
|
+
const runId = crypto43.randomUUID();
|
|
60668
60754
|
app.db.insert(runs).values({
|
|
60669
60755
|
id: runId,
|
|
60670
60756
|
projectId: project.id,
|
|
@@ -60685,7 +60771,7 @@ async function bingRoutes(app, opts) {
|
|
|
60685
60771
|
discoveryDate: result.DiscoveryDate ?? null
|
|
60686
60772
|
});
|
|
60687
60773
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60688
|
-
const id =
|
|
60774
|
+
const id = crypto43.randomUUID();
|
|
60689
60775
|
const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
|
|
60690
60776
|
const lastCrawledDate = parseBingDate(result.LastCrawledDate);
|
|
60691
60777
|
const inIndexDate = parseBingDate(result.InIndexDate);
|
|
@@ -60762,7 +60848,7 @@ async function bingRoutes(app, opts) {
|
|
|
60762
60848
|
throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
60763
60849
|
}
|
|
60764
60850
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60765
|
-
const runId =
|
|
60851
|
+
const runId = crypto43.randomUUID();
|
|
60766
60852
|
app.db.insert(runs).values({
|
|
60767
60853
|
id: runId,
|
|
60768
60854
|
projectId: project.id,
|
|
@@ -61060,7 +61146,7 @@ async function cdpRoutes(app, opts) {
|
|
|
61060
61146
|
}
|
|
61061
61147
|
|
|
61062
61148
|
// ../api-routes/src/ga.ts
|
|
61063
|
-
import
|
|
61149
|
+
import crypto45 from "crypto";
|
|
61064
61150
|
import { eq as eq69, desc as desc34, and as and57, sql as sql27 } from "drizzle-orm";
|
|
61065
61151
|
|
|
61066
61152
|
// ../api-routes/src/ga-session-history.ts
|
|
@@ -61120,14 +61206,14 @@ function findBiggestMover(current, prior) {
|
|
|
61120
61206
|
}
|
|
61121
61207
|
|
|
61122
61208
|
// ../api-routes/src/ga-search-landing-pages.ts
|
|
61123
|
-
import
|
|
61209
|
+
import crypto44 from "crypto";
|
|
61124
61210
|
import { and as and56, asc as asc13, count as count4, desc as desc33, eq as eq68 } from "drizzle-orm";
|
|
61125
61211
|
function persistSearchLandingSnapshot(db, input) {
|
|
61126
61212
|
db.transaction((tx) => {
|
|
61127
61213
|
deleteSearchLandingRows(tx, input.projectId);
|
|
61128
61214
|
for (const window of input.windows) {
|
|
61129
61215
|
tx.insert(gaSearchLandingWindows).values({
|
|
61130
|
-
id:
|
|
61216
|
+
id: crypto44.randomUUID(),
|
|
61131
61217
|
projectId: input.projectId,
|
|
61132
61218
|
propertyId: input.propertyId,
|
|
61133
61219
|
windowKey: window.window,
|
|
@@ -61149,7 +61235,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
61149
61235
|
}).run();
|
|
61150
61236
|
for (const row of window.rows) {
|
|
61151
61237
|
tx.insert(gaSearchLandingPages).values({
|
|
61152
|
-
id:
|
|
61238
|
+
id: crypto44.randomUUID(),
|
|
61153
61239
|
projectId: input.projectId,
|
|
61154
61240
|
windowKey: window.window,
|
|
61155
61241
|
landingPage: row.landingPage,
|
|
@@ -61381,7 +61467,7 @@ function persistAcquisitionMeasurement(db, input) {
|
|
|
61381
61467
|
)).run();
|
|
61382
61468
|
for (const row of input.report.rows) {
|
|
61383
61469
|
tx.insert(gaAcquisitionDaily).values({
|
|
61384
|
-
id:
|
|
61470
|
+
id: crypto45.randomUUID(),
|
|
61385
61471
|
projectId: input.projectId,
|
|
61386
61472
|
date: row.date,
|
|
61387
61473
|
channelGroup: row.channelGroup,
|
|
@@ -61423,7 +61509,7 @@ function persistLeadMeasurement(db, input) {
|
|
|
61423
61509
|
)).run();
|
|
61424
61510
|
for (const row of input.report.rows) {
|
|
61425
61511
|
tx.insert(gaLeadEventsDaily).values({
|
|
61426
|
-
id:
|
|
61512
|
+
id: crypto45.randomUUID(),
|
|
61427
61513
|
projectId: input.projectId,
|
|
61428
61514
|
date: row.date,
|
|
61429
61515
|
eventName: row.eventName,
|
|
@@ -61533,6 +61619,18 @@ function ga4ErrorToAppError(err, context) {
|
|
|
61533
61619
|
}
|
|
61534
61620
|
return providerError(`${context}: ${describeError(err)}`);
|
|
61535
61621
|
}
|
|
61622
|
+
function ga4ConnectError(err, propertyId, principal, context) {
|
|
61623
|
+
if (err instanceof GA4ApiError && err.status === 403) {
|
|
61624
|
+
return forbidden(
|
|
61625
|
+
`Google refused access to GA4 property ${propertyId} for ${principal}. Add ${principal} as a Viewer in Google Analytics (Admin > Property access management), then connect again. Retrying will not help until access is granted.`,
|
|
61626
|
+
{ reason: "ga4-permission-missing", propertyId, retryable: false }
|
|
61627
|
+
);
|
|
61628
|
+
}
|
|
61629
|
+
return validationError(`${context}: ${describeError(err)}`);
|
|
61630
|
+
}
|
|
61631
|
+
function isPermissionRefusal(error) {
|
|
61632
|
+
return error.details?.reason === "ga4-permission-missing";
|
|
61633
|
+
}
|
|
61536
61634
|
async function resolveGa4AccessToken(opts, projectName, canonicalDomain) {
|
|
61537
61635
|
const saConn = opts.ga4CredentialStore?.getConnection(projectName);
|
|
61538
61636
|
if (saConn?.clientEmail && saConn?.privateKey && saConn?.propertyId) {
|
|
@@ -61583,6 +61681,25 @@ function requireGa4Connection(opts, projectName, canonicalDomain) {
|
|
|
61583
61681
|
}
|
|
61584
61682
|
}
|
|
61585
61683
|
async function ga4Routes(app, opts) {
|
|
61684
|
+
const connectPasses = createVerifyCache();
|
|
61685
|
+
const connectRefusals = createRefusalBackoff();
|
|
61686
|
+
async function checkGa4Access(key, verify, toError) {
|
|
61687
|
+
if (!isInteractiveCaller()) {
|
|
61688
|
+
if (connectPasses.get(key)) return { ok: true };
|
|
61689
|
+
const replay = connectRefusals.replay(key);
|
|
61690
|
+
if (replay) return { ok: false, ...replay };
|
|
61691
|
+
}
|
|
61692
|
+
try {
|
|
61693
|
+
await verify();
|
|
61694
|
+
connectPasses.set(key, true);
|
|
61695
|
+
connectRefusals.cleared(key);
|
|
61696
|
+
return { ok: true };
|
|
61697
|
+
} catch (cause) {
|
|
61698
|
+
const error = toError(cause);
|
|
61699
|
+
if (isPermissionRefusal(error)) connectRefusals.refused(key, error, cause);
|
|
61700
|
+
return { ok: false, error, cause };
|
|
61701
|
+
}
|
|
61702
|
+
}
|
|
61586
61703
|
app.post("/projects/:name/ga/connect", connectionRoute(app, (request) => {
|
|
61587
61704
|
const body = request.body;
|
|
61588
61705
|
return { integration: "ga4", action: typeof body?.keyJson === "string" && body.keyJson ? "connect" : "select" };
|
|
@@ -61607,18 +61724,23 @@ async function ga4Routes(app, opts) {
|
|
|
61607
61724
|
}
|
|
61608
61725
|
const clientEmail = parsed.client_email;
|
|
61609
61726
|
const privateKey = parsed.private_key;
|
|
61610
|
-
|
|
61611
|
-
|
|
61612
|
-
|
|
61613
|
-
|
|
61614
|
-
|
|
61615
|
-
|
|
61616
|
-
|
|
61617
|
-
|
|
61727
|
+
const check3 = await checkGa4Access(
|
|
61728
|
+
verifyCacheKey("ga4-service-account", project.id, propertyId, clientEmail, privateKey),
|
|
61729
|
+
() => verifyConnection(clientEmail, privateKey, propertyId),
|
|
61730
|
+
(err) => ga4ConnectError(err, propertyId, clientEmail, "Failed to verify GA4 credentials")
|
|
61731
|
+
);
|
|
61732
|
+
if (!check3.ok) {
|
|
61733
|
+
gaLog("error", "connect.verify-failed", { projectId: project.id, propertyId, error: check3.error.message });
|
|
61734
|
+
attempt.failed(check3.cause);
|
|
61735
|
+
throw check3.error;
|
|
61618
61736
|
}
|
|
61737
|
+
gaLog("info", "connect.verified.service-account", { projectId: project.id, propertyId });
|
|
61619
61738
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
61620
61739
|
const existing = opts.ga4CredentialStore.getConnection(project.name);
|
|
61621
61740
|
if (existing) attempt.update({ action: "reauth" });
|
|
61741
|
+
if (existing?.clientEmail === clientEmail && existing.privateKey === privateKey && existing.propertyId === propertyId) {
|
|
61742
|
+
return { connected: true, propertyId, authMethod: "service-account", clientEmail };
|
|
61743
|
+
}
|
|
61622
61744
|
const previousPropertyId2 = resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain);
|
|
61623
61745
|
opts.ga4CredentialStore.upsertConnection({
|
|
61624
61746
|
projectName: project.name,
|
|
@@ -61655,19 +61777,27 @@ async function ga4Routes(app, opts) {
|
|
|
61655
61777
|
'No GA4 OAuth token found. Run "canonry google connect <project> --type ga4" first, or pass --key-file to use a service account.'
|
|
61656
61778
|
);
|
|
61657
61779
|
}
|
|
61658
|
-
const accessToken
|
|
61659
|
-
|
|
61660
|
-
|
|
61661
|
-
|
|
61662
|
-
|
|
61663
|
-
|
|
61664
|
-
|
|
61665
|
-
|
|
61666
|
-
|
|
61667
|
-
|
|
61668
|
-
|
|
61669
|
-
|
|
61670
|
-
|
|
61780
|
+
const { accessToken: storedAccessToken, refreshToken } = oauthConn;
|
|
61781
|
+
const check2 = await checkGa4Access(
|
|
61782
|
+
verifyCacheKey("ga4-oauth", project.id, propertyId, refreshToken),
|
|
61783
|
+
async () => {
|
|
61784
|
+
const accessToken = await refreshOAuthTokenIfNeeded(googleStore, authConfig, project.canonicalDomain, {
|
|
61785
|
+
accessToken: storedAccessToken,
|
|
61786
|
+
refreshToken,
|
|
61787
|
+
tokenExpiresAt: oauthConn.tokenExpiresAt
|
|
61788
|
+
});
|
|
61789
|
+
return verifyConnectionWithToken(accessToken, propertyId);
|
|
61790
|
+
},
|
|
61791
|
+
(err) => err instanceof AppError ? err : ga4ConnectError(err, propertyId, "the connected Google account", "Failed to verify GA4 access")
|
|
61792
|
+
);
|
|
61793
|
+
if (!check2.ok) {
|
|
61794
|
+
gaLog("error", "connect.verify-failed.oauth", { projectId: project.id, propertyId, error: check2.error.message });
|
|
61795
|
+
attempt.failed(check2.cause);
|
|
61796
|
+
throw check2.error;
|
|
61797
|
+
}
|
|
61798
|
+
gaLog("info", "connect.verified.oauth", { projectId: project.id, propertyId });
|
|
61799
|
+
if (oauthConn.propertyId === propertyId) {
|
|
61800
|
+
return { connected: true, propertyId, authMethod: "oauth" };
|
|
61671
61801
|
}
|
|
61672
61802
|
const previousPropertyId = resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain);
|
|
61673
61803
|
const connectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -61777,7 +61907,7 @@ async function ga4Routes(app, opts) {
|
|
|
61777
61907
|
const leadDays = measurementState?.leadSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
|
|
61778
61908
|
const leadEventNames = project.measurement.leadEventNames;
|
|
61779
61909
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
61780
|
-
const runId =
|
|
61910
|
+
const runId = crypto45.randomUUID();
|
|
61781
61911
|
app.db.insert(runs).values({
|
|
61782
61912
|
id: runId,
|
|
61783
61913
|
projectId: project.id,
|
|
@@ -61837,7 +61967,7 @@ async function ga4Routes(app, opts) {
|
|
|
61837
61967
|
).run();
|
|
61838
61968
|
for (const row of rows) {
|
|
61839
61969
|
tx.insert(gaTrafficSnapshots).values({
|
|
61840
|
-
id:
|
|
61970
|
+
id: crypto45.randomUUID(),
|
|
61841
61971
|
projectId: project.id,
|
|
61842
61972
|
date: row.date,
|
|
61843
61973
|
landingPage: row.landingPage,
|
|
@@ -61860,7 +61990,7 @@ async function ga4Routes(app, opts) {
|
|
|
61860
61990
|
).run();
|
|
61861
61991
|
for (const row of dailyTotals) {
|
|
61862
61992
|
tx.insert(gaDailyTotals).values({
|
|
61863
|
-
id:
|
|
61993
|
+
id: crypto45.randomUUID(),
|
|
61864
61994
|
projectId: project.id,
|
|
61865
61995
|
date: row.date,
|
|
61866
61996
|
sessions: row.sessions,
|
|
@@ -61886,7 +62016,7 @@ async function ga4Routes(app, opts) {
|
|
|
61886
62016
|
).run();
|
|
61887
62017
|
for (const row of aiReferrals) {
|
|
61888
62018
|
tx.insert(gaAiReferrals).values({
|
|
61889
|
-
id:
|
|
62019
|
+
id: crypto45.randomUUID(),
|
|
61890
62020
|
projectId: project.id,
|
|
61891
62021
|
date: row.date,
|
|
61892
62022
|
source: row.source,
|
|
@@ -61918,7 +62048,7 @@ async function ga4Routes(app, opts) {
|
|
|
61918
62048
|
).run();
|
|
61919
62049
|
for (const row of socialReferrals) {
|
|
61920
62050
|
tx.insert(gaSocialReferrals).values({
|
|
61921
|
-
id:
|
|
62051
|
+
id: crypto45.randomUUID(),
|
|
61922
62052
|
projectId: project.id,
|
|
61923
62053
|
date: row.date,
|
|
61924
62054
|
source: row.source,
|
|
@@ -61934,7 +62064,7 @@ async function ga4Routes(app, opts) {
|
|
|
61934
62064
|
if (syncSummary) {
|
|
61935
62065
|
tx.delete(gaTrafficSummaries).where(eq69(gaTrafficSummaries.projectId, project.id)).run();
|
|
61936
62066
|
tx.insert(gaTrafficSummaries).values({
|
|
61937
|
-
id:
|
|
62067
|
+
id: crypto45.randomUUID(),
|
|
61938
62068
|
projectId: project.id,
|
|
61939
62069
|
periodStart: summary3.periodStart,
|
|
61940
62070
|
periodEnd: summary3.periodEnd,
|
|
@@ -61947,7 +62077,7 @@ async function ga4Routes(app, opts) {
|
|
|
61947
62077
|
tx.delete(gaTrafficWindowSummaries).where(eq69(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
61948
62078
|
for (const ws of windowSummaries) {
|
|
61949
62079
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
61950
|
-
id:
|
|
62080
|
+
id: crypto45.randomUUID(),
|
|
61951
62081
|
projectId: project.id,
|
|
61952
62082
|
windowKey: ws.windowKey,
|
|
61953
62083
|
periodStart: ws.periodStart,
|
|
@@ -62744,7 +62874,7 @@ function parseSchemaPageEntry(entry) {
|
|
|
62744
62874
|
}
|
|
62745
62875
|
|
|
62746
62876
|
// ../integration-wordpress/src/wordpress-client.ts
|
|
62747
|
-
import
|
|
62877
|
+
import crypto46 from "crypto";
|
|
62748
62878
|
function validateUsername(username) {
|
|
62749
62879
|
if (!username || typeof username !== "string" || username.trim().length === 0) {
|
|
62750
62880
|
throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
|
|
@@ -62964,7 +63094,7 @@ function buildSnippet(content) {
|
|
|
62964
63094
|
return `${text2.slice(0, 157)}...`;
|
|
62965
63095
|
}
|
|
62966
63096
|
function contentHash(content) {
|
|
62967
|
-
return
|
|
63097
|
+
return crypto46.createHash("sha256").update(content).digest("hex");
|
|
62968
63098
|
}
|
|
62969
63099
|
function buildAmbiguousSlugMessage(slug, pages) {
|
|
62970
63100
|
const candidates = pages.map((page3) => {
|
|
@@ -64457,7 +64587,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
64457
64587
|
}
|
|
64458
64588
|
|
|
64459
64589
|
// ../api-routes/src/backlinks.ts
|
|
64460
|
-
import
|
|
64590
|
+
import crypto47 from "crypto";
|
|
64461
64591
|
import { and as and61, asc as asc15, desc as desc36, eq as eq72, sql as sql29 } from "drizzle-orm";
|
|
64462
64592
|
|
|
64463
64593
|
// ../integration-commoncrawl/src/constants.ts
|
|
@@ -65110,7 +65240,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
65110
65240
|
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq72(ccReleaseSyncs.id, existing.id)).get();
|
|
65111
65241
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
65112
65242
|
}
|
|
65113
|
-
const id =
|
|
65243
|
+
const id = crypto47.randomUUID();
|
|
65114
65244
|
app.db.insert(ccReleaseSyncs).values({
|
|
65115
65245
|
id,
|
|
65116
65246
|
release,
|
|
@@ -65167,7 +65297,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
65167
65297
|
throw validationError("Invalid release id");
|
|
65168
65298
|
}
|
|
65169
65299
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
65170
|
-
const runId =
|
|
65300
|
+
const runId = crypto47.randomUUID();
|
|
65171
65301
|
app.db.insert(runs).values({
|
|
65172
65302
|
id: runId,
|
|
65173
65303
|
projectId: project.id,
|
|
@@ -65256,7 +65386,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
65256
65386
|
}
|
|
65257
65387
|
|
|
65258
65388
|
// ../api-routes/src/traffic.ts
|
|
65259
|
-
import
|
|
65389
|
+
import crypto50 from "crypto";
|
|
65260
65390
|
import { isIP } from "net";
|
|
65261
65391
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
65262
65392
|
import { and as and65, desc as desc37, eq as eq76, gte as gte18, inArray as inArray31, lte as lte20, sql as sql32 } from "drizzle-orm";
|
|
@@ -65284,7 +65414,7 @@ function resolveVercelSyncDeadlineMs(env = process.env) {
|
|
|
65284
65414
|
}
|
|
65285
65415
|
|
|
65286
65416
|
// ../integration-cloud-run/src/auth.ts
|
|
65287
|
-
import
|
|
65417
|
+
import crypto48 from "crypto";
|
|
65288
65418
|
var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
|
|
65289
65419
|
var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
|
|
65290
65420
|
var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -65315,7 +65445,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
|
|
|
65315
65445
|
const headerB64 = encode(header2);
|
|
65316
65446
|
const payloadB64 = encode(payload);
|
|
65317
65447
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
65318
|
-
const sign =
|
|
65448
|
+
const sign = crypto48.createSign("RSA-SHA256");
|
|
65319
65449
|
sign.update(signingInput);
|
|
65320
65450
|
const signature = sign.sign(privateKey, "base64url");
|
|
65321
65451
|
return `${signingInput}.${signature}`;
|
|
@@ -81924,7 +82054,7 @@ async function ackCloudflareQueueMessages(client, options) {
|
|
|
81924
82054
|
}
|
|
81925
82055
|
|
|
81926
82056
|
// ../api-routes/src/traffic-event-ingest.ts
|
|
81927
|
-
import
|
|
82057
|
+
import crypto49 from "crypto";
|
|
81928
82058
|
import { and as and63, eq as eq74, gte as gte17, lt as lt12, lte as lte18, sql as sql31 } from "drizzle-orm";
|
|
81929
82059
|
|
|
81930
82060
|
// ../api-routes/src/raw-event-sample-retention.ts
|
|
@@ -82223,7 +82353,7 @@ function writeTrafficEventBatch(opts) {
|
|
|
82223
82353
|
}
|
|
82224
82354
|
})();
|
|
82225
82355
|
tx.insert(rawEventSamples).values({
|
|
82226
|
-
id:
|
|
82356
|
+
id: crypto49.randomUUID(),
|
|
82227
82357
|
projectId: opts.projectId,
|
|
82228
82358
|
sourceId: opts.sourceId,
|
|
82229
82359
|
ts: sampleTimestamp,
|
|
@@ -82324,7 +82454,7 @@ var WORDPRESS_SYNC_LEASE_TTL_MS = 5 * 6e4;
|
|
|
82324
82454
|
var DEFAULT_CLOUDFLARE_INGEST_RATE_LIMIT_MAX = 6e3;
|
|
82325
82455
|
var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
|
|
82326
82456
|
function sha256Hex3(value) {
|
|
82327
|
-
return
|
|
82457
|
+
return crypto50.createHash("sha256").update(value).digest("hex");
|
|
82328
82458
|
}
|
|
82329
82459
|
function hashCloudflareBearerToken(value) {
|
|
82330
82460
|
return sha256Hex3(value);
|
|
@@ -82334,7 +82464,7 @@ function timingSafeEqualHex(a, b) {
|
|
|
82334
82464
|
if (!SHA256_HEX_PATTERN.test(a) || !SHA256_HEX_PATTERN.test(b)) return false;
|
|
82335
82465
|
const left = Buffer.from(a, "hex");
|
|
82336
82466
|
const right = Buffer.from(b, "hex");
|
|
82337
|
-
return left.length === right.length &&
|
|
82467
|
+
return left.length === right.length && crypto50.timingSafeEqual(left, right);
|
|
82338
82468
|
}
|
|
82339
82469
|
function parseDirectPushCloudflareSourceConfig(config) {
|
|
82340
82470
|
const parsed = cloudflareTrafficSourceConfigSchema.safeParse(config);
|
|
@@ -82652,7 +82782,7 @@ function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing =
|
|
|
82652
82782
|
}
|
|
82653
82783
|
if (!createIfMissing) return { changed: false, created: false };
|
|
82654
82784
|
tx.insert(schedules).values({
|
|
82655
|
-
id:
|
|
82785
|
+
id: crypto50.randomUUID(),
|
|
82656
82786
|
projectId,
|
|
82657
82787
|
kind: SchedulableRunKinds["traffic-sync"],
|
|
82658
82788
|
cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
|
|
@@ -82973,7 +83103,7 @@ async function runBackfillTask(options) {
|
|
|
82973
83103
|
}
|
|
82974
83104
|
})();
|
|
82975
83105
|
tx.insert(rawEventSamples).values({
|
|
82976
|
-
id:
|
|
83106
|
+
id: crypto50.randomUUID(),
|
|
82977
83107
|
projectId: project.id,
|
|
82978
83108
|
sourceId: sourceRow2.id,
|
|
82979
83109
|
ts: sampleTimestamp,
|
|
@@ -83164,7 +83294,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83164
83294
|
const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
|
|
83165
83295
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
83166
83296
|
const activeSource = tx.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
83167
|
-
const sourceId = activeSource?.id ??
|
|
83297
|
+
const sourceId = activeSource?.id ?? crypto50.randomUUID();
|
|
83168
83298
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
83169
83299
|
if (activeSource) {
|
|
83170
83300
|
tx.update(trafficSources).set({
|
|
@@ -83256,7 +83386,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83256
83386
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
83257
83387
|
const activeSource = tx.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
83258
83388
|
const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
|
|
83259
|
-
const sourceId = endpointChanged ?
|
|
83389
|
+
const sourceId = endpointChanged ? crypto50.randomUUID() : activeSource?.id ?? crypto50.randomUUID();
|
|
83260
83390
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
83261
83391
|
if (activeSource && !endpointChanged) {
|
|
83262
83392
|
tx.update(trafficSources).set({
|
|
@@ -83368,7 +83498,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83368
83498
|
}).where(eq76(trafficSources.id, activeSource.id)).run();
|
|
83369
83499
|
row = tx.select().from(trafficSources).where(eq76(trafficSources.id, activeSource.id)).get();
|
|
83370
83500
|
} else {
|
|
83371
|
-
const newId =
|
|
83501
|
+
const newId = crypto50.randomUUID();
|
|
83372
83502
|
tx.insert(trafficSources).values({
|
|
83373
83503
|
id: newId,
|
|
83374
83504
|
projectId: project.id,
|
|
@@ -83438,7 +83568,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83438
83568
|
const cloudflareSources = app.db.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
|
|
83439
83569
|
const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
|
|
83440
83570
|
if (sameModeSource) attempt.update({ action: "reauth" });
|
|
83441
|
-
const sourceId2 = sameModeSource?.id ??
|
|
83571
|
+
const sourceId2 = sameModeSource?.id ?? crypto50.randomUUID();
|
|
83442
83572
|
const previousCredential2 = credentialStore.getConnectionBySourceId(sourceId2);
|
|
83443
83573
|
const workerVersion2 = CURRENT_CLOUDFLARE_WORKER_VERSION;
|
|
83444
83574
|
const workerScript2 = generateWorkerScript({
|
|
@@ -83574,7 +83704,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83574
83704
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83575
83705
|
const activeSource = app.db.select().from(trafficSources).where(eq76(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
|
|
83576
83706
|
if (activeSource) attempt.update({ action: "reauth" });
|
|
83577
|
-
const sourceId = activeSource?.id ??
|
|
83707
|
+
const sourceId = activeSource?.id ?? crypto50.randomUUID();
|
|
83578
83708
|
const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
|
|
83579
83709
|
if (activeSource && !parsedActiveConfig) {
|
|
83580
83710
|
throw validationError("Existing Cloudflare source is not configured for direct-push delivery");
|
|
@@ -83590,8 +83720,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83590
83720
|
const previousAccountId = reusableCredential?.accountId ?? parsedActiveConfig?.accountId ?? null;
|
|
83591
83721
|
const effectiveZoneId = zoneId ?? previousZoneId;
|
|
83592
83722
|
const effectiveAccountId = accountId ?? previousAccountId;
|
|
83593
|
-
const bearerToken = reusableCredential?.bearerToken ?? `cnry_cfw_${
|
|
83594
|
-
const hmacSecret = reusableCredential?.hmacSecret ??
|
|
83723
|
+
const bearerToken = reusableCredential?.bearerToken ?? `cnry_cfw_${crypto50.randomBytes(32).toString("hex")}`;
|
|
83724
|
+
const hmacSecret = reusableCredential?.hmacSecret ?? crypto50.randomBytes(32).toString("hex");
|
|
83595
83725
|
const ingestTokenHash = sha256Hex3(bearerToken);
|
|
83596
83726
|
const ingestUrl = opts.cloudflareTrafficIngestUrl.replace("{name}", encodeURIComponent(project.name));
|
|
83597
83727
|
assertCloudflareIngestUrlIsPublicHttps(ingestUrl);
|
|
@@ -83937,7 +84067,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83937
84067
|
apiToken: credential.apiToken
|
|
83938
84068
|
};
|
|
83939
84069
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83940
|
-
const leaseOwner =
|
|
84070
|
+
const leaseOwner = crypto50.randomUUID();
|
|
83941
84071
|
if (!tryClaimTrafficSyncLease({
|
|
83942
84072
|
db: app.db,
|
|
83943
84073
|
sourceId: sourceRow2.id,
|
|
@@ -83947,7 +84077,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83947
84077
|
})) {
|
|
83948
84078
|
throw operationInProgress("Cloudflare Queue source sync is already in progress", { sourceId: sourceRow2.id });
|
|
83949
84079
|
}
|
|
83950
|
-
const runId =
|
|
84080
|
+
const runId = crypto50.randomUUID();
|
|
83951
84081
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
83952
84082
|
const startedMs = Date.now();
|
|
83953
84083
|
try {
|
|
@@ -84187,7 +84317,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84187
84317
|
if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
|
|
84188
84318
|
throw validationError("Traffic source must be active before it can sync");
|
|
84189
84319
|
}
|
|
84190
|
-
const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ?
|
|
84320
|
+
const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ? crypto50.randomUUID() : void 0;
|
|
84191
84321
|
if (wordpressLeaseOwner && !tryClaimTrafficSyncLease({
|
|
84192
84322
|
db: app.db,
|
|
84193
84323
|
sourceId: sourceRow2.id,
|
|
@@ -84202,7 +84332,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84202
84332
|
let windowEnd = syncWindowEnd;
|
|
84203
84333
|
const startedAt = syncWindowEnd.toISOString();
|
|
84204
84334
|
const syncStartedAtMs = syncWindowEnd.getTime();
|
|
84205
|
-
const runId =
|
|
84335
|
+
const runId = crypto50.randomUUID();
|
|
84206
84336
|
app.db.insert(runs).values({
|
|
84207
84337
|
id: runId,
|
|
84208
84338
|
projectId: project.id,
|
|
@@ -84751,7 +84881,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84751
84881
|
}
|
|
84752
84882
|
})();
|
|
84753
84883
|
tx.insert(rawEventSamples).values({
|
|
84754
|
-
id:
|
|
84884
|
+
id: crypto50.randomUUID(),
|
|
84755
84885
|
projectId: project.id,
|
|
84756
84886
|
sourceId: sourceRow2.id,
|
|
84757
84887
|
ts: sampleTimestamp,
|
|
@@ -84983,7 +85113,7 @@ async function trafficRoutes(app, opts) {
|
|
|
84983
85113
|
};
|
|
84984
85114
|
}
|
|
84985
85115
|
const startedAt = windowEnd.toISOString();
|
|
84986
|
-
const runId =
|
|
85116
|
+
const runId = crypto50.randomUUID();
|
|
84987
85117
|
app.db.transaction((tx) => {
|
|
84988
85118
|
const latestSource = tx.select().from(trafficSources).where(eq76(trafficSources.id, sourceRow2.id)).get();
|
|
84989
85119
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
@@ -85864,7 +85994,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
|
85864
85994
|
}];
|
|
85865
85995
|
|
|
85866
85996
|
// ../api-routes/src/doctor/checks/agent.ts
|
|
85867
|
-
import
|
|
85997
|
+
import crypto51 from "crypto";
|
|
85868
85998
|
import fs6 from "fs";
|
|
85869
85999
|
import path7 from "path";
|
|
85870
86000
|
var REQUIRED_SKILLS = ["canonry", "aero"];
|
|
@@ -86098,7 +86228,7 @@ function isInstalled(dir) {
|
|
|
86098
86228
|
}
|
|
86099
86229
|
function hashInstalledFile(filePath) {
|
|
86100
86230
|
try {
|
|
86101
|
-
return
|
|
86231
|
+
return crypto51.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
|
|
86102
86232
|
} catch {
|
|
86103
86233
|
return void 0;
|
|
86104
86234
|
}
|
|
@@ -89981,7 +90111,7 @@ async function statusRoutes(app, opts) {
|
|
|
89981
90111
|
}
|
|
89982
90112
|
|
|
89983
90113
|
// ../api-routes/src/discovery/routes.ts
|
|
89984
|
-
import
|
|
90114
|
+
import crypto52 from "crypto";
|
|
89985
90115
|
import { and as and76, desc as desc42, eq as eq92, gte as gte23, inArray as inArray37, isNull as isNull17, or as or21 } from "drizzle-orm";
|
|
89986
90116
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
89987
90117
|
async function discoveryRoutes(app, opts) {
|
|
@@ -90047,8 +90177,8 @@ async function discoveryRoutes(app, opts) {
|
|
|
90047
90177
|
if (existing && existing.runId) {
|
|
90048
90178
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
90049
90179
|
}
|
|
90050
|
-
const sessionId =
|
|
90051
|
-
const runId =
|
|
90180
|
+
const sessionId = crypto52.randomUUID();
|
|
90181
|
+
const runId = crypto52.randomUUID();
|
|
90052
90182
|
tx.insert(discoverySessions).values({
|
|
90053
90183
|
id: sessionId,
|
|
90054
90184
|
projectId: project.id,
|
|
@@ -90303,7 +90433,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
90303
90433
|
const marketNames = beginMarketNameWrite(tx, project.id);
|
|
90304
90434
|
for (const query of promotedQueries) {
|
|
90305
90435
|
tx.insert(queries).values({
|
|
90306
|
-
id:
|
|
90436
|
+
id: crypto52.randomUUID(),
|
|
90307
90437
|
projectId: project.id,
|
|
90308
90438
|
query,
|
|
90309
90439
|
provenance,
|
|
@@ -90484,7 +90614,7 @@ function planCompetitorPromotion(input) {
|
|
|
90484
90614
|
}
|
|
90485
90615
|
|
|
90486
90616
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
90487
|
-
import
|
|
90617
|
+
import crypto53 from "crypto";
|
|
90488
90618
|
import { eq as eq93 } from "drizzle-orm";
|
|
90489
90619
|
var DEFAULT_MAX_PROBES = 100;
|
|
90490
90620
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
@@ -90650,7 +90780,7 @@ async function executeDiscovery(opts) {
|
|
|
90650
90780
|
probeRows.push({ citedDomains: probe.citedDomains, bucket });
|
|
90651
90781
|
buckets[bucket]++;
|
|
90652
90782
|
return {
|
|
90653
|
-
id:
|
|
90783
|
+
id: crypto53.randomUUID(),
|
|
90654
90784
|
sessionId: opts.sessionId,
|
|
90655
90785
|
projectId: opts.project.id,
|
|
90656
90786
|
query: probedCanonicals[index2],
|
|
@@ -90702,7 +90832,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
|
|
|
90702
90832
|
const domain = hostOf(entry.domain) ?? "";
|
|
90703
90833
|
if (!domain) continue;
|
|
90704
90834
|
db.insert(domainClassifications).values({
|
|
90705
|
-
id:
|
|
90835
|
+
id: crypto53.randomUUID(),
|
|
90706
90836
|
projectId,
|
|
90707
90837
|
domain,
|
|
90708
90838
|
competitorType: entry.competitorType,
|
|
@@ -90742,7 +90872,7 @@ function dedupeStrings(input) {
|
|
|
90742
90872
|
}
|
|
90743
90873
|
|
|
90744
90874
|
// ../api-routes/src/technical-aeo.ts
|
|
90745
|
-
import
|
|
90875
|
+
import crypto54 from "crypto";
|
|
90746
90876
|
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";
|
|
90747
90877
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
90748
90878
|
|
|
@@ -92735,7 +92865,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92735
92865
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
92736
92866
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
92737
92867
|
const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
|
|
92738
|
-
const selectionKey =
|
|
92868
|
+
const selectionKey = crypto54.createHash("sha256").update(JSON.stringify({
|
|
92739
92869
|
projectId: project.id,
|
|
92740
92870
|
runId: snapshot.runId,
|
|
92741
92871
|
attemptId: snapshot.attemptId,
|
|
@@ -93235,7 +93365,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
93235
93365
|
);
|
|
93236
93366
|
}
|
|
93237
93367
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
93238
|
-
const runId =
|
|
93368
|
+
const runId = crypto54.randomUUID();
|
|
93239
93369
|
tx.insert(runs).values({
|
|
93240
93370
|
id: runId,
|
|
93241
93371
|
projectId: project.id,
|
|
@@ -93272,7 +93402,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
93272
93402
|
}
|
|
93273
93403
|
|
|
93274
93404
|
// ../api-routes/src/research.ts
|
|
93275
|
-
import
|
|
93405
|
+
import crypto55 from "crypto";
|
|
93276
93406
|
import { z as z5 } from "zod";
|
|
93277
93407
|
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";
|
|
93278
93408
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
@@ -93342,9 +93472,9 @@ async function researchRoutes(app, opts) {
|
|
|
93342
93472
|
throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
93343
93473
|
}
|
|
93344
93474
|
}
|
|
93345
|
-
const id =
|
|
93475
|
+
const id = crypto55.randomUUID();
|
|
93346
93476
|
tx.insert(researchRuns).values({ id, projectId: project.id, status: ResearchRunStatuses.queued, provider: providerName, requestedModel: requestedModel2, resolvedModel, location: location ?? null, totalQueries: input.queries.length, scope, template, idempotencyKey: input.idempotencyKey ?? null, requestHash: input.idempotencyKey ? requestHash2 : null, initiatedBy, createdAt: now }).run();
|
|
93347
|
-
for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id:
|
|
93477
|
+
for (const [position, query] of input.queries.entries()) tx.insert(researchRunQueries).values({ id: crypto55.randomUUID(), researchRunId: id, position, queryText: query, queryClass: queryClasses[position] ?? null, status: ResearchQueryStatuses.queued, requestedModel: requestedModel2, resolvedModel, groundingSources: [], citedDomains: [], searchQueries: [], createdAt: now }).run();
|
|
93348
93478
|
writeAuditLog(tx, { projectId: project.id, actor: "api", action: "research.created", entityType: "research_run", entityId: id });
|
|
93349
93479
|
return { reused: false, id, shouldDispatch: true };
|
|
93350
93480
|
});
|
|
@@ -93397,7 +93527,7 @@ async function researchRoutes(app, opts) {
|
|
|
93397
93527
|
}
|
|
93398
93528
|
const ids = [];
|
|
93399
93529
|
for (const [index2, run] of prepared.entries()) {
|
|
93400
|
-
const id =
|
|
93530
|
+
const id = crypto55.randomUUID();
|
|
93401
93531
|
ids.push(id);
|
|
93402
93532
|
tx.insert(researchRuns).values({
|
|
93403
93533
|
id,
|
|
@@ -93416,7 +93546,7 @@ async function researchRoutes(app, opts) {
|
|
|
93416
93546
|
createdAt: now
|
|
93417
93547
|
}).run();
|
|
93418
93548
|
for (const [position, query] of run.input.queries.entries()) tx.insert(researchRunQueries).values({
|
|
93419
|
-
id:
|
|
93549
|
+
id: crypto55.randomUUID(),
|
|
93420
93550
|
researchRunId: id,
|
|
93421
93551
|
position,
|
|
93422
93552
|
queryText: query,
|
|
@@ -93475,7 +93605,7 @@ async function researchRoutes(app, opts) {
|
|
|
93475
93605
|
var DIRECT_REQUEST_HASH_VERSION = "direct-v2:";
|
|
93476
93606
|
function directResearchRequestHash(input) {
|
|
93477
93607
|
const { idempotencyKey: _key, ...request } = input;
|
|
93478
|
-
return DIRECT_REQUEST_HASH_VERSION +
|
|
93608
|
+
return DIRECT_REQUEST_HASH_VERSION + crypto55.createHash("sha256").update(JSON.stringify(request)).digest("hex");
|
|
93479
93609
|
}
|
|
93480
93610
|
function assertSameDirectResearchRequest(existing, input, hash2) {
|
|
93481
93611
|
if (existing.requestHash?.startsWith(DIRECT_REQUEST_HASH_VERSION)) {
|
|
@@ -93493,7 +93623,7 @@ function assertSameDirectResearchRequest(existing, input, hash2) {
|
|
|
93493
93623
|
...scope ? { scope } : {},
|
|
93494
93624
|
...template ? { template } : {}
|
|
93495
93625
|
};
|
|
93496
|
-
if (
|
|
93626
|
+
if (crypto55.createHash("sha256").update(JSON.stringify(normalized)).digest("hex") !== existing.requestHash) throw alreadyExists("Research idempotency key", input.idempotencyKey);
|
|
93497
93627
|
}
|
|
93498
93628
|
var researchCursorSchema = z5.object({ projectId: z5.string().min(1), createdAt: z5.string().datetime({ offset: true }), id: z5.string().min(1).max(256) }).strict();
|
|
93499
93629
|
function parseResearchCursor(value, projectId) {
|
|
@@ -93513,11 +93643,11 @@ function resolveStoredResearchScope(stored, selection, idempotencyKey) {
|
|
|
93513
93643
|
return stored;
|
|
93514
93644
|
}
|
|
93515
93645
|
function batchChildReceiptKey(rootKey, index2) {
|
|
93516
|
-
const derived =
|
|
93646
|
+
const derived = crypto55.createHash("sha256").update(`${rootKey}\0${index2}`).digest("hex");
|
|
93517
93647
|
return `${BATCH_RECEIPT_PREFIX}${derived}:${index2}`;
|
|
93518
93648
|
}
|
|
93519
93649
|
function researchBatchRequestHash(input) {
|
|
93520
|
-
return
|
|
93650
|
+
return crypto55.createHash("sha256").update(JSON.stringify({ runs: input.runs })).digest("hex");
|
|
93521
93651
|
}
|
|
93522
93652
|
function prepareBatchRun(app, opts, project, active, input) {
|
|
93523
93653
|
const adapters = opts.providerAdapters ?? [];
|
|
@@ -93789,7 +93919,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
93789
93919
|
}
|
|
93790
93920
|
|
|
93791
93921
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
93792
|
-
import
|
|
93922
|
+
import crypto56 from "crypto";
|
|
93793
93923
|
import { and as and81, eq as eq98 } from "drizzle-orm";
|
|
93794
93924
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
93795
93925
|
return db.transaction((tx) => {
|
|
@@ -93831,11 +93961,11 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
93831
93961
|
...frozen.identity.qualifiedAliases?.length ? { qualifiedAliases: frozen.identity.qualifiedAliases } : {}
|
|
93832
93962
|
}
|
|
93833
93963
|
};
|
|
93834
|
-
const replayChecksum =
|
|
93964
|
+
const replayChecksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(replayDefinition)).digest("hex");
|
|
93835
93965
|
if (existing.checksum !== replayChecksum) {
|
|
93836
93966
|
if (frozen.competitors === void 0) {
|
|
93837
93967
|
const { competitors: _currentCompetitors, ...legacyReplayDefinition } = replayDefinition;
|
|
93838
|
-
const legacyReplayChecksum =
|
|
93968
|
+
const legacyReplayChecksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(legacyReplayDefinition)).digest("hex");
|
|
93839
93969
|
if (existing.checksum === legacyReplayChecksum) return frozen;
|
|
93840
93970
|
}
|
|
93841
93971
|
throw validationError("This run already has a captured measurement definition. Start a new run for changed inputs.");
|
|
@@ -93859,7 +93989,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
93859
93989
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
93860
93990
|
}
|
|
93861
93991
|
}
|
|
93862
|
-
const checksum =
|
|
93992
|
+
const checksum = crypto56.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(definition2)).digest("hex");
|
|
93863
93993
|
tx.insert(simpleMeasurementDefinitions).values({
|
|
93864
93994
|
projectId: input.projectId,
|
|
93865
93995
|
runId: input.runId,
|
|
@@ -94474,7 +94604,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
94474
94604
|
}
|
|
94475
94605
|
|
|
94476
94606
|
// src/intelligence-service.ts
|
|
94477
|
-
import
|
|
94607
|
+
import crypto57 from "crypto";
|
|
94478
94608
|
|
|
94479
94609
|
// ../api-routes/src/runtime-logger.ts
|
|
94480
94610
|
import { format } from "util";
|
|
@@ -95276,7 +95406,7 @@ var IntelligenceService = class {
|
|
|
95276
95406
|
}).run();
|
|
95277
95407
|
}
|
|
95278
95408
|
tx.insert(healthSnapshots).values({
|
|
95279
|
-
id:
|
|
95409
|
+
id: crypto57.randomUUID(),
|
|
95280
95410
|
projectId,
|
|
95281
95411
|
runId,
|
|
95282
95412
|
overallCitedRate: String(result.health.overallCitedRate),
|