@canonry/canonry 7.17.1 → 7.18.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/agent-workspace/skills/aero/references/portfolio-analysis.md +8 -6
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -1
- package/assets/assets/{AuditHistoryPanel-CzjskfMX.js → AuditHistoryPanel-CgKpjgpq.js} +1 -1
- package/assets/assets/BacklinksPage-BBqbRSeG.js +1 -0
- package/assets/assets/Hint-DICI5Jq9.js +1 -0
- package/assets/assets/{HistoryPage-BZgVoYGm.js → HistoryPage-D8nQ8E8_.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-EBI5sn-D.js → MeasurementPropertyPage-CQ8lLoeA.js} +1 -1
- package/assets/assets/{ProjectPage-lGGjM_qj.js → ProjectPage-CP4fvluh.js} +6 -6
- package/assets/assets/{RunsPage-CuHi8241.js → RunsPage-D0qkTwas.js} +1 -1
- package/assets/assets/{SettingsPage-D0fgAoVu.js → SettingsPage-DaqR1wKC.js} +1 -1
- package/assets/assets/SiteHealthSection-dGXxZ8IR.js +4 -0
- package/assets/assets/{TrafficPage-DD65RqeR.js → TrafficPage-DodOmnVv.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CFVCj13J.js → TrafficSourceDetailPage-C7b-BcKF.js} +1 -1
- package/assets/assets/{extract-error-message-D118msoE.js → extract-error-message-BYvzTsML.js} +1 -1
- package/assets/assets/index-BVzozur0.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-DdlHLMEz.js → react-sigma_core.esm.min-DXcq1O0r.js} +1 -1
- package/assets/assets/{v2-overview-adapter-CoCb2aB0.js → v2-overview-adapter-1MHnrdnr.js} +1 -1
- package/assets/assets/{vendor-lucide-N6vOpZmh.js → vendor-lucide-Y6jANGfG.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-ORA5R7QM.js → chunk-5M2RY5WU.js} +132 -104
- package/dist/{chunk-ZQ7C6IFA.js → chunk-COQOQYRZ.js} +749 -614
- package/dist/{chunk-FK7AWV57.js → chunk-EAAM47H6.js} +24 -9
- package/dist/{chunk-PLS4EM2Y.js → chunk-JMC7CEJB.js} +4 -4
- package/dist/{chunk-OPJCFDNR.js → chunk-JUOFNJLV.js} +3 -3
- package/dist/{chunk-C6GWKYGE.js → chunk-U7D7LNVJ.js} +5 -4
- package/dist/{chunk-MHB23E3N.js → chunk-X2S7D3MN.js} +123 -82
- package/dist/cli.js +30 -22
- package/dist/{demo-server-WQDHQG6P.js → demo-server-XWRIXME5.js} +6 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-JWZRZ6JZ.js → intelligence-service-332S7D7Y.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +12 -12
- package/assets/assets/BacklinksPage-ToENGBdp.js +0 -1
- package/assets/assets/SiteHealthSection-DhLxldgs.js +0 -4
- package/assets/assets/index-DxFd1t4G.js +0 -86
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
LOGIN_FAILED_MESSAGE,
|
|
87
87
|
MEASUREMENT_CHANGES_DEFAULT_SORT,
|
|
88
88
|
MEASUREMENT_CHANGES_NOISE_ANSWERS,
|
|
89
|
+
MEASUREMENT_DEFAULT_QUERY_CLASS,
|
|
89
90
|
MEASUREMENT_DRAFT_MAX_ASSIGNMENTS,
|
|
90
91
|
MEASUREMENT_DRAFT_MAX_ASSIGNMENTS_PER_ACTION,
|
|
91
92
|
MEASUREMENT_DRAFT_MAX_AUTHORING_BYTES,
|
|
@@ -303,6 +304,7 @@ import {
|
|
|
303
304
|
coerceSkillManifest,
|
|
304
305
|
compactDateToIso,
|
|
305
306
|
compareSemver,
|
|
307
|
+
compareText,
|
|
306
308
|
competitorAliasBlockRequestSchema,
|
|
307
309
|
competitorAliasProjectIdentity,
|
|
308
310
|
competitorAliasesRequestSchema,
|
|
@@ -376,6 +378,7 @@ import {
|
|
|
376
378
|
emptyCitationVisibility,
|
|
377
379
|
emptySentimentCounts,
|
|
378
380
|
escapeLikePattern,
|
|
381
|
+
escapeRegExp,
|
|
379
382
|
evaluateModelPointerExposure,
|
|
380
383
|
expandQueryTemplate,
|
|
381
384
|
expandResearchTemplate,
|
|
@@ -493,6 +496,7 @@ import {
|
|
|
493
496
|
isoDateDaysBeforeInTimeZone,
|
|
494
497
|
keywordDtoSchema,
|
|
495
498
|
keywordGenerateRequestSchema,
|
|
499
|
+
keywordGenerateResponseSchema,
|
|
496
500
|
latestProjectRunDtoSchema,
|
|
497
501
|
linearTrend,
|
|
498
502
|
locationContextSchema,
|
|
@@ -577,6 +581,7 @@ import {
|
|
|
577
581
|
measurementPropertyEvidenceResponseSchema,
|
|
578
582
|
measurementPropertyQuestionsQuerySchema,
|
|
579
583
|
measurementPropertyQuestionsResponseSchema,
|
|
584
|
+
measurementQueryClassFilterSchema,
|
|
580
585
|
measurementQuerySetDetailSchema,
|
|
581
586
|
measurementQuerySetListResponseSchema,
|
|
582
587
|
measurementQuerySetUpsertRequestSchema,
|
|
@@ -604,6 +609,7 @@ import {
|
|
|
604
609
|
normalizeCompetitorAliases,
|
|
605
610
|
normalizeCompetitorDomain,
|
|
606
611
|
normalizeIdTokens,
|
|
612
|
+
normalizeIdentityText,
|
|
607
613
|
normalizeMeasurementExactUrl,
|
|
608
614
|
normalizeMeasurementExecutionQueryText,
|
|
609
615
|
normalizeMeasurementHost,
|
|
@@ -643,8 +649,10 @@ import {
|
|
|
643
649
|
planCompetitorAliases,
|
|
644
650
|
planCompetitorAutoAliases,
|
|
645
651
|
prepareBrandMatchText,
|
|
652
|
+
projectConfigExportSchema,
|
|
646
653
|
projectConfigSchema,
|
|
647
654
|
projectCreateRequestSchema,
|
|
655
|
+
projectDefaultLocationResponseSchema,
|
|
648
656
|
projectDtoSchema,
|
|
649
657
|
projectOverviewDtoSchema,
|
|
650
658
|
projectSearchResponseSchema,
|
|
@@ -656,6 +664,7 @@ import {
|
|
|
656
664
|
providersFailing,
|
|
657
665
|
queryDtoSchema,
|
|
658
666
|
queryGenerateRequestSchema,
|
|
667
|
+
queryGenerateResponseSchema,
|
|
659
668
|
queryTrackingCommitRequestSchema,
|
|
660
669
|
queryTrackingCommitResponseSchema,
|
|
661
670
|
queryTrackingPreviewRequestSchema,
|
|
@@ -777,6 +786,7 @@ import {
|
|
|
777
786
|
snapshotReportSchema,
|
|
778
787
|
snapshotRequestInputSchema,
|
|
779
788
|
snapshotRequestSchema,
|
|
789
|
+
sortedUnique,
|
|
780
790
|
sourceBreakdownDtoSchema,
|
|
781
791
|
sourceBreakdownQuerySchema,
|
|
782
792
|
splitList,
|
|
@@ -833,12 +843,12 @@ import {
|
|
|
833
843
|
wordpressManualAssistDtoSchema,
|
|
834
844
|
wordpressOnboardResultDtoSchema,
|
|
835
845
|
wordpressPageDetailDtoSchema,
|
|
836
|
-
|
|
846
|
+
wordpressPageListDtoSchema,
|
|
837
847
|
wordpressSchemaBlockDtoSchema,
|
|
838
848
|
wordpressSchemaDeployResultDtoSchema,
|
|
839
849
|
wordpressSchemaStatusResultDtoSchema,
|
|
840
850
|
wordpressStatusDtoSchema
|
|
841
|
-
} from "./chunk-
|
|
851
|
+
} from "./chunk-X2S7D3MN.js";
|
|
842
852
|
|
|
843
853
|
// src/intelligence-service.ts
|
|
844
854
|
import { eq as eq89, desc as desc41, asc as asc18, and as and75, ne as ne16, or as or22, inArray as inArray36, gte as gte24, lte as lte20, isNull as isNull17, sql as sql32, exists as exists2 } from "drizzle-orm";
|
|
@@ -1040,7 +1050,7 @@ var projects = sqliteTable("projects", {
|
|
|
1040
1050
|
locations: text("locations", { mode: "json" }).$type().notNull().default([]),
|
|
1041
1051
|
defaultLocation: text("default_location"),
|
|
1042
1052
|
autoExtractBacklinks: integer("auto_extract_backlinks", { mode: "boolean" }).notNull().default(false),
|
|
1043
|
-
configSource: text("config_source").notNull().default("cli"),
|
|
1053
|
+
configSource: text("config_source").$type().notNull().default("cli"),
|
|
1044
1054
|
configRevision: integer("config_revision").notNull().default(1),
|
|
1045
1055
|
icpDescription: text("icp_description"),
|
|
1046
1056
|
createdAt: text("created_at").notNull(),
|
|
@@ -9682,11 +9692,13 @@ var SentimentRepository = class {
|
|
|
9682
9692
|
}
|
|
9683
9693
|
};
|
|
9684
9694
|
|
|
9685
|
-
// ../intelligence/src/
|
|
9695
|
+
// ../intelligence/src/snapshot-key.ts
|
|
9686
9696
|
function snapshotKey(snap) {
|
|
9687
9697
|
const loc = snap.location ?? "__none__";
|
|
9688
9698
|
return JSON.stringify([snap.query, snap.provider, loc]);
|
|
9689
9699
|
}
|
|
9700
|
+
|
|
9701
|
+
// ../intelligence/src/regressions.ts
|
|
9690
9702
|
function detectRegressions(currentRun, previousRun) {
|
|
9691
9703
|
if ((currentRun.location ?? null) !== (previousRun.location ?? null)) {
|
|
9692
9704
|
return [];
|
|
@@ -9726,24 +9738,20 @@ function observedKeys(run, keyOf) {
|
|
|
9726
9738
|
}
|
|
9727
9739
|
|
|
9728
9740
|
// ../intelligence/src/gains.ts
|
|
9729
|
-
function snapshotKey2(snap) {
|
|
9730
|
-
const loc = snap.location ?? "__none__";
|
|
9731
|
-
return JSON.stringify([snap.query, snap.provider, loc]);
|
|
9732
|
-
}
|
|
9733
9741
|
function detectGains(currentRun, previousRun) {
|
|
9734
9742
|
if ((currentRun.location ?? null) !== (previousRun.location ?? null)) {
|
|
9735
9743
|
return [];
|
|
9736
9744
|
}
|
|
9737
9745
|
const gains = [];
|
|
9738
|
-
const previousObserved = observedKeys(previousRun,
|
|
9746
|
+
const previousObserved = observedKeys(previousRun, snapshotKey);
|
|
9739
9747
|
const previousCited = /* @__PURE__ */ new Set();
|
|
9740
9748
|
for (const snap of previousRun.snapshots) {
|
|
9741
9749
|
if (snap.cited) {
|
|
9742
|
-
previousCited.add(
|
|
9750
|
+
previousCited.add(snapshotKey(snap));
|
|
9743
9751
|
}
|
|
9744
9752
|
}
|
|
9745
9753
|
for (const snap of currentRun.snapshots) {
|
|
9746
|
-
const key =
|
|
9754
|
+
const key = snapshotKey(snap);
|
|
9747
9755
|
if (snap.cited && previousObserved.has(key) && !previousCited.has(key)) {
|
|
9748
9756
|
gains.push({
|
|
9749
9757
|
query: snap.query,
|
|
@@ -12451,6 +12459,11 @@ var FORWARDED_HEADERS = ["x-forwarded-for", "forwarded", "x-real-ip"];
|
|
|
12451
12459
|
function hasForwardedHeaders(request) {
|
|
12452
12460
|
return FORWARDED_HEADERS.some((header2) => request.headers[header2] !== void 0);
|
|
12453
12461
|
}
|
|
12462
|
+
var PROXY_PATH_HEADERS = ["via", "cf-connecting-ip", "true-client-ip"];
|
|
12463
|
+
var FORWARDED_HEADER_PREFIX = "x-forwarded-";
|
|
12464
|
+
function hasProxyHeaders(request) {
|
|
12465
|
+
return hasForwardedHeaders(request) || PROXY_PATH_HEADERS.some((header2) => request.headers[header2] !== void 0) || Object.keys(request.headers).some((header2) => header2.startsWith(FORWARDED_HEADER_PREFIX));
|
|
12466
|
+
}
|
|
12454
12467
|
function resolveCallerKey(request, trustProxyConfigured) {
|
|
12455
12468
|
const socketAddress = request.socket.remoteAddress ?? request.ip;
|
|
12456
12469
|
if (!trustProxyConfigured) return socketAddress;
|
|
@@ -14418,8 +14431,7 @@ function rowKey(row) {
|
|
|
14418
14431
|
return { queryText: row.queryText, queryId: row.queryId, executionNodeKey: row.executionNodeKey };
|
|
14419
14432
|
}
|
|
14420
14433
|
function compareRows(left, right) {
|
|
14421
|
-
|
|
14422
|
-
return left.queryText.localeCompare(right.queryText, "en") || order(left.queryText, right.queryText) || order(left.queryId, right.queryId) || order(left.executionNodeKey ?? "", right.executionNodeKey ?? "");
|
|
14434
|
+
return left.queryText.localeCompare(right.queryText, "en") || compareText(left.queryText, right.queryText) || compareText(left.queryId, right.queryId) || compareText(left.executionNodeKey ?? "", right.executionNodeKey ?? "");
|
|
14423
14435
|
}
|
|
14424
14436
|
function withheldCounts(counts2) {
|
|
14425
14437
|
return Object.fromEntries(Object.entries(counts2).map(([key, value]) => [key, OPERATIONAL_OUTCOMES.has(key) ? value : 0]));
|
|
@@ -14987,9 +14999,6 @@ import { and as and7, desc as desc4, eq as eq12, exists, inArray as inArray4, is
|
|
|
14987
14999
|
import crypto7 from "crypto";
|
|
14988
15000
|
import { and as and6, eq as eq11, gt as gt2, lte as lte4 } from "drizzle-orm";
|
|
14989
15001
|
var MEASUREMENT_RECEIPT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
14990
|
-
function compareText(left, right) {
|
|
14991
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
14992
|
-
}
|
|
14993
15002
|
function canonicalJsonValue(value) {
|
|
14994
15003
|
if (Array.isArray(value)) return value.map(canonicalJsonValue);
|
|
14995
15004
|
if (value && typeof value === "object") {
|
|
@@ -15957,10 +15966,6 @@ import { and as and11, desc as desc7, eq as eq17, gt as gt3, inArray as inArray6
|
|
|
15957
15966
|
import { and as and9, desc as desc5, eq as eq15, gte as gte2, inArray as inArray5, isNull as isNull4, lt as lt4, lte as lte5, ne as ne4 } from "drizzle-orm";
|
|
15958
15967
|
|
|
15959
15968
|
// ../api-routes/src/measurement-report.ts
|
|
15960
|
-
var compareText2 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
15961
|
-
function sortedUnique(values) {
|
|
15962
|
-
return [...new Set(values)].sort(compareText2);
|
|
15963
|
-
}
|
|
15964
15969
|
function normalizedHost(value) {
|
|
15965
15970
|
try {
|
|
15966
15971
|
return normalizeMeasurementHost(value);
|
|
@@ -16042,7 +16047,7 @@ function classifySourceAttribution(value, routes, normalizedOwnedHosts) {
|
|
|
16042
16047
|
const claim = routeClaim(source, route);
|
|
16043
16048
|
if (claim) claims.push(claim);
|
|
16044
16049
|
}
|
|
16045
|
-
claims.sort((left, right) => right.modeRank - left.modeRank || right.pathLength - left.pathLength ||
|
|
16050
|
+
claims.sort((left, right) => right.modeRank - left.modeRank || right.pathLength - left.pathLength || compareText(left.targetId, right.targetId) || compareText(left.urlId, right.urlId));
|
|
16046
16051
|
const best = claims.at(0);
|
|
16047
16052
|
if (!best) {
|
|
16048
16053
|
return {
|
|
@@ -16083,8 +16088,7 @@ function classifySourceForUsageEdge(source, usageEdge) {
|
|
|
16083
16088
|
}
|
|
16084
16089
|
function normalizeMeasurementLocation(value) {
|
|
16085
16090
|
if (value === null) return null;
|
|
16086
|
-
|
|
16087
|
-
return normalized || null;
|
|
16091
|
+
return normalizeIdentityText(value) || null;
|
|
16088
16092
|
}
|
|
16089
16093
|
function words(value) {
|
|
16090
16094
|
return value.normalize("NFKC").toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
@@ -16142,7 +16146,7 @@ function mentionedTargetsForAliases(answerText, aliases, preparedWords) {
|
|
|
16142
16146
|
}
|
|
16143
16147
|
function identityAmbiguityPatterns(aliases) {
|
|
16144
16148
|
const patterns = aliases.flatMap((alias2) => {
|
|
16145
|
-
const name = words(alias2).join(" ")
|
|
16149
|
+
const name = escapeRegExp(words(alias2).join(" "));
|
|
16146
16150
|
if (!name) return [];
|
|
16147
16151
|
return [{
|
|
16148
16152
|
clarification: new RegExp(`(?:^| )which (?:specific )?${name} (?:(?:(?:do|did) )?you mean|(?:(?:are|were) you|you(?: re| are| were)?) (?:asking about|referring to|talking about))(?= |$)`, "u"),
|
|
@@ -16158,7 +16162,15 @@ function identityAmbiguityPatterns(aliases) {
|
|
|
16158
16162
|
multipleEntities: patterns.flatMap((pattern) => pattern.multipleEntities)
|
|
16159
16163
|
};
|
|
16160
16164
|
}
|
|
16161
|
-
|
|
16165
|
+
var NO_TARGETS = /* @__PURE__ */ new Set();
|
|
16166
|
+
function queryNamedTargetIds(queryText, brandedTargetIds, aliases) {
|
|
16167
|
+
if (!queryText || brandedTargetIds === void 0) return NO_TARGETS;
|
|
16168
|
+
const branded = new Set(brandedTargetIds);
|
|
16169
|
+
if (branded.size === 0) return NO_TARGETS;
|
|
16170
|
+
const named = [...mentionedTargetsForAliases(queryText, aliases).mentioned].filter((id) => branded.has(id));
|
|
16171
|
+
return named.length === 0 ? NO_TARGETS : new Set(named);
|
|
16172
|
+
}
|
|
16173
|
+
function resolveMentionIdentity(text2, targets, aliases, citedTargetIds, ambiguityPatterns = new Map(targets.map((target) => [target.id, identityAmbiguityPatterns(target.aliases)])), queryNamed = NO_TARGETS) {
|
|
16162
16174
|
const textWords = text2 === null ? [] : words(text2);
|
|
16163
16175
|
const state = mentionedTargetsForAliases(text2, aliases, textWords);
|
|
16164
16176
|
if (text2 === null) return state;
|
|
@@ -16166,8 +16178,10 @@ function resolveMentionIdentity(text2, targets, aliases, citedTargetIds, ambigui
|
|
|
16166
16178
|
for (const target of targets) {
|
|
16167
16179
|
if (!state.mentioned.has(target.id)) continue;
|
|
16168
16180
|
const patterns = ambiguityPatterns.get(target.id);
|
|
16169
|
-
const
|
|
16170
|
-
const
|
|
16181
|
+
const cited = citedTargetIds.has(target.id);
|
|
16182
|
+
const qualified = cited || (target.identityAliases?.length ?? 0) > 0 && containsAnyAlias(text2, target.identityAliases);
|
|
16183
|
+
const clarified = cited || queryNamed.has(target.id);
|
|
16184
|
+
const unresolved = !clarified && patterns?.clarification.some((pattern) => pattern.test(normalized)) || !qualified && (patterns?.multipleEntities.some((pattern) => pattern.test(normalized)) || (target.identityAliases?.length ?? 0) > 0);
|
|
16171
16185
|
if (unresolved) {
|
|
16172
16186
|
state.mentioned.delete(target.id);
|
|
16173
16187
|
state.unknown.add(target.id);
|
|
@@ -16182,13 +16196,14 @@ function createTargetMentionReader(targets, ownedHosts = []) {
|
|
|
16182
16196
|
const ambiguityPatterns = new Map(targets.map((target) => [target.id, identityAmbiguityPatterns(target.aliases)]));
|
|
16183
16197
|
const routes = compiledTargetRoutes(targets);
|
|
16184
16198
|
const normalizedOwnedHosts = ownedHosts.map(normalizedHost);
|
|
16185
|
-
return (answerText, targetIds, citedUrls = []) => {
|
|
16199
|
+
return (answerText, targetIds, citedUrls = [], query = null) => {
|
|
16186
16200
|
if (answerText === null || targetIds.length === 0) return null;
|
|
16187
16201
|
const citedTargetIds = new Set(citedUrls.flatMap((url) => {
|
|
16188
16202
|
const source = classifySourceAttribution(url, routes, normalizedOwnedHosts);
|
|
16189
16203
|
return source.classification === "matched" ? source.matchedTargetIds : [];
|
|
16190
16204
|
}));
|
|
16191
|
-
const
|
|
16205
|
+
const queryNamed = query === null ? NO_TARGETS : queryNamedTargetIds(query.text, query.brandedTargetIds.filter((id) => targetIds.includes(id)), aliases);
|
|
16206
|
+
const state = resolveMentionIdentity(answerProseForMentions(answerText), targets, aliases, citedTargetIds, ambiguityPatterns, queryNamed);
|
|
16192
16207
|
if (targetIds.some((id) => state.mentioned.has(id))) return true;
|
|
16193
16208
|
return targetIds.some((id) => !mentionableIds.has(id) || state.unknown.has(id)) ? null : false;
|
|
16194
16209
|
};
|
|
@@ -16245,7 +16260,7 @@ function prepareReport(input, options = {}, detailedEvidence = true) {
|
|
|
16245
16260
|
const observationsBySlot = /* @__PURE__ */ new Map();
|
|
16246
16261
|
const historical = /* @__PURE__ */ new Set();
|
|
16247
16262
|
const evidenceIncomplete = /* @__PURE__ */ new Set();
|
|
16248
|
-
for (const slot of [...input.expectedSlots].sort((left, right) =>
|
|
16263
|
+
for (const slot of [...input.expectedSlots].sort((left, right) => compareText(left.id, right.id))) {
|
|
16249
16264
|
const rows = candidates.get(slot.id) ?? [];
|
|
16250
16265
|
if (rows.length > 1) {
|
|
16251
16266
|
for (const row of rows) ambiguous.add(row.id);
|
|
@@ -16291,17 +16306,33 @@ function prepareReport(input, options = {}, detailedEvidence = true) {
|
|
|
16291
16306
|
const mentionableIds = new Set(
|
|
16292
16307
|
mentionAliases.map((alias2) => alias2.targetId)
|
|
16293
16308
|
);
|
|
16309
|
+
const queryNamedByExecution = /* @__PURE__ */ new Map();
|
|
16310
|
+
const queryNamedFor = (slot) => {
|
|
16311
|
+
const cached = queryNamedByExecution.get(slot.executionId);
|
|
16312
|
+
if (cached !== void 0) return cached;
|
|
16313
|
+
const branded = (edgesByExecution.get(slot.executionId) ?? []).flatMap((edge) => edge.type === "target" && edge.queryClass === "branded" ? [edge.targetId] : []);
|
|
16314
|
+
const named = queryNamedTargetIds(slot.queryText, branded, aliasesByFirstWord);
|
|
16315
|
+
queryNamedByExecution.set(slot.executionId, named);
|
|
16316
|
+
return named;
|
|
16317
|
+
};
|
|
16294
16318
|
for (const observation of observationsBySlot.values()) {
|
|
16295
16319
|
const citedTargetIds = new Set(observation.sourceUrls.flatMap((url) => {
|
|
16296
16320
|
const source = sourceAttribution(url);
|
|
16297
16321
|
return source.classification === "matched" ? source.matchedTargetIds : [];
|
|
16298
16322
|
}));
|
|
16299
|
-
const mentions = resolveMentionIdentity(
|
|
16323
|
+
const mentions = resolveMentionIdentity(
|
|
16324
|
+
observation.mentionText,
|
|
16325
|
+
input.targets,
|
|
16326
|
+
aliasesByFirstWord,
|
|
16327
|
+
citedTargetIds,
|
|
16328
|
+
ambiguityPatterns,
|
|
16329
|
+
queryNamedFor(observation.slot)
|
|
16330
|
+
);
|
|
16300
16331
|
observation.mentionedTargetIds = mentions.mentioned;
|
|
16301
16332
|
observation.unknownMentionTargetIds = mentions.unknown;
|
|
16302
16333
|
observation.citedTargetIds = citedTargetIds;
|
|
16303
16334
|
if (!detailedEvidence) continue;
|
|
16304
|
-
const edges = [...edgesByExecution.get(observation.slot.executionId) ?? []].sort((left, right) =>
|
|
16335
|
+
const edges = [...edgesByExecution.get(observation.slot.executionId) ?? []].sort((left, right) => compareText(left.id, right.id));
|
|
16305
16336
|
for (const edge of edges) {
|
|
16306
16337
|
const sources = observation.sourceUrls.map((sourceUrl) => {
|
|
16307
16338
|
const { classification, normalizedUrl, matchedTargetIds, matchedUrlIds } = classifySourceForUsageEdge(sourceAttribution(sourceUrl), edge);
|
|
@@ -16336,7 +16367,7 @@ function prepareReport(input, options = {}, detailedEvidence = true) {
|
|
|
16336
16367
|
});
|
|
16337
16368
|
}
|
|
16338
16369
|
}
|
|
16339
|
-
answers.sort((left, right) =>
|
|
16370
|
+
answers.sort((left, right) => compareText(left.expectedSlotId, right.expectedSlotId) || compareText(left.usageEdgeId, right.usageEdgeId));
|
|
16340
16371
|
const evidence = answers.flatMap((answer) => answer.sources.map((source) => ({
|
|
16341
16372
|
observationId: answer.observationId,
|
|
16342
16373
|
expectedSlotId: answer.expectedSlotId,
|
|
@@ -16355,7 +16386,7 @@ function prepareReport(input, options = {}, detailedEvidence = true) {
|
|
|
16355
16386
|
matchedTargetIds: source.matchedTargetIds,
|
|
16356
16387
|
matchedUrlIds: source.matchedUrlIds
|
|
16357
16388
|
})));
|
|
16358
|
-
evidence.sort((left, right) =>
|
|
16389
|
+
evidence.sort((left, right) => compareText(left.expectedSlotId, right.expectedSlotId) || compareText(left.usageEdgeId, right.usageEdgeId) || compareText(left.sourceUrl, right.sourceUrl) || compareText(left.classification, right.classification));
|
|
16359
16390
|
options.onSourceAttributionComputed?.(sourceAttributions.size);
|
|
16360
16391
|
return {
|
|
16361
16392
|
observationsBySlot,
|
|
@@ -16387,7 +16418,7 @@ function slotsForEdges(expectedSlots, edges) {
|
|
|
16387
16418
|
for (const slot of expectedSlots) {
|
|
16388
16419
|
if (executionIds.has(slot.executionId)) slots.set(slot.id, slot);
|
|
16389
16420
|
}
|
|
16390
|
-
return [...slots.values()].sort((left, right) =>
|
|
16421
|
+
return [...slots.values()].sort((left, right) => compareText(left.id, right.id));
|
|
16391
16422
|
}
|
|
16392
16423
|
function completeness(slots, prepared) {
|
|
16393
16424
|
const observations = slots.flatMap((slot) => {
|
|
@@ -16450,7 +16481,7 @@ function providersFor(slots) {
|
|
|
16450
16481
|
return sortedUnique(slots.map((slot) => slot.provider));
|
|
16451
16482
|
}
|
|
16452
16483
|
function buildSovForSlots(slots, competitors2, projectBrandNames, projectDomain, prepared) {
|
|
16453
|
-
const sortedCompetitors = [...competitors2].sort((left, right) =>
|
|
16484
|
+
const sortedCompetitors = [...competitors2].sort((left, right) => compareText(left.domain, right.domain));
|
|
16454
16485
|
const rows = [
|
|
16455
16486
|
{ domain: projectDomain, own: true, aliases: projectBrandNames },
|
|
16456
16487
|
...sortedCompetitors.map((competitor) => ({
|
|
@@ -16614,7 +16645,7 @@ function indexedSlotsForEdges(edges, indexes) {
|
|
|
16614
16645
|
for (const edge of edges) {
|
|
16615
16646
|
for (const slot of indexes.slotsByExecutionId.get(edge.executionId) ?? []) slots.set(slot.id, slot);
|
|
16616
16647
|
}
|
|
16617
|
-
return [...slots.values()].sort((left, right) =>
|
|
16648
|
+
return [...slots.values()].sort((left, right) => compareText(left.id, right.id));
|
|
16618
16649
|
}
|
|
16619
16650
|
function indexedAnsweredSlots(slots, indexes) {
|
|
16620
16651
|
return slots.filter((slot) => indexes.answeredSlotIds.has(slot.id));
|
|
@@ -16778,22 +16809,19 @@ function buildMeasurementReport(input, options = {}) {
|
|
|
16778
16809
|
const prepared = prepareReport(input, options);
|
|
16779
16810
|
return {
|
|
16780
16811
|
revision: input.revision,
|
|
16781
|
-
groups: [...input.groups].sort((left, right) =>
|
|
16782
|
-
targets: [...input.targets].sort((left, right) =>
|
|
16812
|
+
groups: [...input.groups].sort((left, right) => compareText(left.id, right.id)).map((group) => buildGroupReport(group, input, prepared)),
|
|
16813
|
+
targets: [...input.targets].sort((left, right) => compareText(left.id, right.id)).map((target) => buildTargetReport(target, input, prepared)),
|
|
16783
16814
|
evidence: prepared.evidence,
|
|
16784
16815
|
diagnostics: prepared.diagnostics
|
|
16785
16816
|
};
|
|
16786
16817
|
}
|
|
16787
16818
|
|
|
16788
16819
|
// ../api-routes/src/measurement-report-adapter.ts
|
|
16789
|
-
function compareText3(left, right) {
|
|
16790
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
16791
|
-
}
|
|
16792
16820
|
function normalizedProviders(values) {
|
|
16793
16821
|
const providers = values.map((value) => value.trim().toLocaleLowerCase("en"));
|
|
16794
16822
|
if (providers.some((value) => !value)) throw new Error("measurement manifest provider must be non-empty");
|
|
16795
16823
|
if (new Set(providers).size !== providers.length) throw new Error("measurement manifest providers must be unique");
|
|
16796
|
-
return providers.sort(
|
|
16824
|
+
return providers.sort(compareText);
|
|
16797
16825
|
}
|
|
16798
16826
|
function canonicalContext(value) {
|
|
16799
16827
|
if (value === null) return "null";
|
|
@@ -16826,7 +16854,7 @@ function frozenExecutionNodes(plan) {
|
|
|
16826
16854
|
}
|
|
16827
16855
|
function manifestFromNodes(nodes, providersFor2) {
|
|
16828
16856
|
const expectedSlots = [];
|
|
16829
|
-
for (const node of [...nodes].sort((left, right) =>
|
|
16857
|
+
for (const node of [...nodes].sort((left, right) => compareText(left.stableKey, right.stableKey))) {
|
|
16830
16858
|
const providers = providersFor2(node);
|
|
16831
16859
|
if (node.expectedSnapshots !== providers.length) {
|
|
16832
16860
|
throw new Error(`measurement manifest provider roster does not satisfy execution ${node.stableKey}`);
|
|
@@ -17069,9 +17097,10 @@ function buildMeasurementPlanV2ReportInput(revision, plan, manifest, snapshots)
|
|
|
17069
17097
|
}
|
|
17070
17098
|
};
|
|
17071
17099
|
}
|
|
17072
|
-
function responseFromReport(revision, report, run) {
|
|
17100
|
+
function responseFromReport(revision, report, run, queryClass) {
|
|
17073
17101
|
return {
|
|
17074
17102
|
revision,
|
|
17103
|
+
queryClass,
|
|
17075
17104
|
run,
|
|
17076
17105
|
groups: report.groups,
|
|
17077
17106
|
targets: report.targets,
|
|
@@ -17134,31 +17163,38 @@ function measurementRunExpectedSlots(run, plan) {
|
|
|
17134
17163
|
if (run.measurementManifest === null) manifestFailure(`missing for run ${run.id}`);
|
|
17135
17164
|
return parseManifest(run.measurementManifest, frozenExecutionNodes(plan), run.measurementScope === null);
|
|
17136
17165
|
}
|
|
17137
|
-
function storedMeasurementPlanV2Report(db, projectId, version, plan, runId) {
|
|
17166
|
+
function storedMeasurementPlanV2Report(db, projectId, version, plan, runId, queryClass) {
|
|
17138
17167
|
const run = runId ? pinnedMeasurementRun(db, projectId, version.id, runId, { exactVersion: true }) : latestMeasurementRun(db, projectId, version.id, [RunStatuses.completed, RunStatuses.partial], {}, { exactVersion: true });
|
|
17139
17168
|
if (!run) {
|
|
17140
17169
|
const empty = buildMeasurementPlanV2ReportInput(version.revision, plan, { schemaVersion: 1, expectedSlots: [] }, []);
|
|
17141
17170
|
return {
|
|
17142
17171
|
kind: "no-population",
|
|
17143
17172
|
reason: "no-run",
|
|
17144
|
-
report: responseFromReport(version.revision, buildMeasurementReport(empty
|
|
17173
|
+
report: responseFromReport(version.revision, buildMeasurementReport(classInput(empty, queryClass)), null, queryClass)
|
|
17145
17174
|
};
|
|
17146
17175
|
}
|
|
17147
17176
|
const manifest = measurementRunExpectedSlots(run, plan);
|
|
17148
17177
|
const snapshots = db.select().from(querySnapshots).where(eq15(querySnapshots.runId, run.id)).all();
|
|
17149
|
-
const
|
|
17178
|
+
const materialized = buildMeasurementPlanV2ReportInput(version.revision, plan, manifest, snapshots);
|
|
17150
17179
|
return {
|
|
17151
17180
|
kind: "report",
|
|
17152
|
-
report: responseFromReport(version.revision, buildMeasurementReport(
|
|
17181
|
+
report: responseFromReport(version.revision, buildMeasurementReport(classInput(materialized, queryClass)), {
|
|
17153
17182
|
id: run.id,
|
|
17154
17183
|
status: run.status === RunStatuses.completed ? RunStatuses.completed : RunStatuses.partial,
|
|
17155
17184
|
createdAt: run.createdAt,
|
|
17156
17185
|
startedAt: run.startedAt,
|
|
17157
17186
|
finishedAt: run.finishedAt
|
|
17158
|
-
})
|
|
17187
|
+
}, queryClass)
|
|
17159
17188
|
};
|
|
17160
17189
|
}
|
|
17161
|
-
function
|
|
17190
|
+
function classInput(materialized, queryClass) {
|
|
17191
|
+
if (queryClass === "all") return materialized.input;
|
|
17192
|
+
return {
|
|
17193
|
+
...materialized.input,
|
|
17194
|
+
usageEdges: materialized.input.usageEdges.filter((edge) => materialized.edgeQueryClass.get(edge.id) === queryClass)
|
|
17195
|
+
};
|
|
17196
|
+
}
|
|
17197
|
+
function buildStoredMeasurementReport(db, projectId, revision, runId, queryClass) {
|
|
17162
17198
|
const version = db.select().from(measurementPlanVersions).where(and9(
|
|
17163
17199
|
eq15(measurementPlanVersions.projectId, projectId),
|
|
17164
17200
|
eq15(measurementPlanVersions.revision, revision)
|
|
@@ -17166,7 +17202,12 @@ function buildStoredMeasurementReport(db, projectId, revision, runId) {
|
|
|
17166
17202
|
if (!version) return { kind: "no-plan", revision };
|
|
17167
17203
|
const stored = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
|
|
17168
17204
|
if (stored.schemaVersion === MEASUREMENT_PLAN_V2_SCHEMA_VERSION) {
|
|
17169
|
-
return storedMeasurementPlanV2Report(db, projectId, version, stored, runId);
|
|
17205
|
+
return storedMeasurementPlanV2Report(db, projectId, version, stored, runId, queryClass ?? MEASUREMENT_DEFAULT_QUERY_CLASS);
|
|
17206
|
+
}
|
|
17207
|
+
if (queryClass !== void 0 && queryClass !== "all") {
|
|
17208
|
+
throw validationError(
|
|
17209
|
+
`Revision ${revision} is a schema v1 plan, which records no query class. Omit queryClass to read every answer.`
|
|
17210
|
+
);
|
|
17170
17211
|
}
|
|
17171
17212
|
const plan = stored;
|
|
17172
17213
|
const run = runId ? pinnedMeasurementRun(db, projectId, version.id, runId, { exactVersion: true }) : db.select().from(runs).where(and9(
|
|
@@ -17193,7 +17234,7 @@ function buildStoredMeasurementReport(db, projectId, revision, runId) {
|
|
|
17193
17234
|
)).orderBy(desc5(runs.createdAt), desc5(runs.id)).get();
|
|
17194
17235
|
if (!selectedRun) {
|
|
17195
17236
|
const report2 = buildMeasurementReport(reportInput(version.revision, plan, { schemaVersion: 1, expectedSlots: [] }, [], false));
|
|
17196
|
-
return { kind: "no-population", reason: "no-run", report: responseFromReport(version.revision, report2, null) };
|
|
17237
|
+
return { kind: "no-population", reason: "no-run", report: responseFromReport(version.revision, report2, null, null) };
|
|
17197
17238
|
}
|
|
17198
17239
|
const legacySnapshots = db.select().from(querySnapshots).where(eq15(querySnapshots.runId, selectedRun.id)).all();
|
|
17199
17240
|
const providers = [...new Set(legacySnapshots.map((snapshot) => snapshot.provider.trim().toLocaleLowerCase("en")).filter(Boolean))];
|
|
@@ -17201,12 +17242,12 @@ function buildStoredMeasurementReport(db, projectId, revision, runId) {
|
|
|
17201
17242
|
manifest = buildMeasurementRunManifest(plan, providers);
|
|
17202
17243
|
} catch {
|
|
17203
17244
|
const report2 = buildMeasurementReport(reportInput(version.revision, plan, { schemaVersion: 1, expectedSlots: [] }, [], false));
|
|
17204
|
-
return { kind: "no-population", reason: "no-run", report: responseFromReport(version.revision, report2, null) };
|
|
17245
|
+
return { kind: "no-population", reason: "no-run", report: responseFromReport(version.revision, report2, null, null) };
|
|
17205
17246
|
}
|
|
17206
17247
|
legacy = true;
|
|
17207
17248
|
} else {
|
|
17208
17249
|
const report2 = buildMeasurementReport(reportInput(version.revision, plan, { schemaVersion: 1, expectedSlots: [] }, [], false));
|
|
17209
|
-
return { kind: "no-population", reason: "no-run", report: responseFromReport(version.revision, report2, null) };
|
|
17250
|
+
return { kind: "no-population", reason: "no-run", report: responseFromReport(version.revision, report2, null, null) };
|
|
17210
17251
|
}
|
|
17211
17252
|
const snapshots = db.select().from(querySnapshots).where(eq15(querySnapshots.runId, selectedRun.id)).all();
|
|
17212
17253
|
const report = buildMeasurementReport(reportInput(version.revision, plan, manifest, snapshots, legacy));
|
|
@@ -17218,7 +17259,7 @@ function buildStoredMeasurementReport(db, projectId, revision, runId) {
|
|
|
17218
17259
|
createdAt: selectedRun.createdAt,
|
|
17219
17260
|
startedAt: selectedRun.startedAt,
|
|
17220
17261
|
finishedAt: selectedRun.finishedAt
|
|
17221
|
-
})
|
|
17262
|
+
}, null)
|
|
17222
17263
|
};
|
|
17223
17264
|
}
|
|
17224
17265
|
|
|
@@ -17324,9 +17365,6 @@ function expectedSlotsFor(nodes, providers, models) {
|
|
|
17324
17365
|
...models[provider] ? { requestedModel: models[provider] } : {}
|
|
17325
17366
|
})));
|
|
17326
17367
|
}
|
|
17327
|
-
function compareText4(left, right) {
|
|
17328
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
17329
|
-
}
|
|
17330
17368
|
function normalizedLocationIdentity(value) {
|
|
17331
17369
|
if (!value) return null;
|
|
17332
17370
|
return [value.country, value.region, value.city, value.label, value.timezone ?? ""].map((part) => part.trim().toLocaleLowerCase("en")).join("\0");
|
|
@@ -17353,7 +17391,7 @@ function materializeV2ExecutionNodes(nodes, instanceModels) {
|
|
|
17353
17391
|
const answering = /* @__PURE__ */ new Map();
|
|
17354
17392
|
const retired = /* @__PURE__ */ new Set();
|
|
17355
17393
|
const claimed = /* @__PURE__ */ new Set();
|
|
17356
|
-
for (const node of [...nodes].sort((left, right) =>
|
|
17394
|
+
for (const node of [...nodes].sort((left, right) => compareText(left.stableKey, right.stableKey))) {
|
|
17357
17395
|
const nodeProviders = normalizeProviders(node.context.providers);
|
|
17358
17396
|
const declared = nodeProviderModels(node);
|
|
17359
17397
|
const resolved = /* @__PURE__ */ new Map();
|
|
@@ -17385,11 +17423,11 @@ function materializeV2ExecutionNodes(nodes, instanceModels) {
|
|
|
17385
17423
|
}
|
|
17386
17424
|
return {
|
|
17387
17425
|
expectedSlots,
|
|
17388
|
-
providers: [...providers].sort(
|
|
17426
|
+
providers: [...providers].sort(compareText),
|
|
17389
17427
|
models: Object.fromEntries([...models].flatMap(([provider, model]) => {
|
|
17390
17428
|
if (model) return [[provider, model]];
|
|
17391
17429
|
const current = answering.get(provider);
|
|
17392
|
-
return retired.has(provider) && current ? [[provider, [...current].sort(
|
|
17430
|
+
return retired.has(provider) && current ? [[provider, [...current].sort(compareText).join(" + ")]] : [];
|
|
17393
17431
|
}))
|
|
17394
17432
|
};
|
|
17395
17433
|
}
|
|
@@ -17405,12 +17443,9 @@ function sliceForV2(plan, params) {
|
|
|
17405
17443
|
function quotedList(values) {
|
|
17406
17444
|
return values.map((value) => `"${value}"`).join(", ");
|
|
17407
17445
|
}
|
|
17408
|
-
function sortedUnique2(values) {
|
|
17409
|
-
return [...new Set(values)].sort(compareText4);
|
|
17410
|
-
}
|
|
17411
17446
|
function resolveV2RunScope(plan, scope) {
|
|
17412
|
-
const requestedGroups =
|
|
17413
|
-
const requestedTargets =
|
|
17447
|
+
const requestedGroups = sortedUnique(scope.groups ?? []);
|
|
17448
|
+
const requestedTargets = sortedUnique(scope.targets ?? []);
|
|
17414
17449
|
const groupsByKey = new Map(plan.groups.map((group) => [group.stableKey, group]));
|
|
17415
17450
|
const targetKeys = new Set(plan.targets.map((target) => target.stableKey));
|
|
17416
17451
|
const unknownGroups = requestedGroups.filter((key) => !groupsByKey.has(key));
|
|
@@ -17429,7 +17464,7 @@ function resolveV2RunScope(plan, scope) {
|
|
|
17429
17464
|
for (const key of requestedGroups) {
|
|
17430
17465
|
for (const targetKey of groupsByKey.get(key).targetKeys) selected.add(targetKey);
|
|
17431
17466
|
}
|
|
17432
|
-
const resolvedTargets = [...selected].sort(
|
|
17467
|
+
const resolvedTargets = [...selected].sort(compareText);
|
|
17433
17468
|
const usedNodeKeys = new Set(plan.usageEdges.filter((edge) => selected.has(edge.targetKey)).map((edge) => edge.executionNodeKey));
|
|
17434
17469
|
const executionNodes = plan.executionNodes.filter((node) => usedNodeKeys.has(node.stableKey));
|
|
17435
17470
|
if (executionNodes.length === 0) {
|
|
@@ -17444,7 +17479,7 @@ function resolveV2RunScope(plan, scope) {
|
|
|
17444
17479
|
};
|
|
17445
17480
|
}
|
|
17446
17481
|
function resolveV2QueryScope(plan, queryTexts) {
|
|
17447
|
-
const requested =
|
|
17482
|
+
const requested = sortedUnique(queryTexts.map(normalizeMeasurementExecutionQueryText).filter(Boolean));
|
|
17448
17483
|
const measured = new Set(plan.executionNodes.map((node) => normalizeMeasurementExecutionQueryText(node.queryText)));
|
|
17449
17484
|
const unknown = requested.filter((text2) => !measured.has(text2));
|
|
17450
17485
|
if (unknown.length) {
|
|
@@ -19708,6 +19743,9 @@ var CURRENT_RUN_STATUSES = [
|
|
|
19708
19743
|
RunStatuses.partial,
|
|
19709
19744
|
RunStatuses.failed
|
|
19710
19745
|
];
|
|
19746
|
+
function servedQueryClass(query) {
|
|
19747
|
+
return query.queryClass ?? MEASUREMENT_DEFAULT_QUERY_CLASS;
|
|
19748
|
+
}
|
|
19711
19749
|
var MEASUREMENT_OVERVIEW_CACHE_MAX_ENTRIES = 32;
|
|
19712
19750
|
function cacheKeyOf(key) {
|
|
19713
19751
|
return JSON.stringify([
|
|
@@ -19750,6 +19788,9 @@ function parseOverviewQuery(raw) {
|
|
|
19750
19788
|
};
|
|
19751
19789
|
const parsed = measurementOverviewQuerySchema.safeParse(candidate);
|
|
19752
19790
|
if (!parsed.success) throw validationError("Invalid measurement overview query", { issues: parsed.error.issues });
|
|
19791
|
+
if (parsed.data.marketKey !== void 0 && parsed.data.scope !== "market") {
|
|
19792
|
+
throw validationError(`"marketKey" is accepted only when scope is "market"; scope "${parsed.data.scope}" does not narrow to a market.`);
|
|
19793
|
+
}
|
|
19753
19794
|
return parsed.data;
|
|
19754
19795
|
}
|
|
19755
19796
|
function activeMeasurementPlan(db, projectId) {
|
|
@@ -19804,20 +19845,18 @@ function countMetric(rate2) {
|
|
|
19804
19845
|
if (rate2.numerator === null) return unavailable2(metricReason(rate2.reason));
|
|
19805
19846
|
return { state: "available", value: rate2.numerator, numerator: rate2.numerator, denominator: rate2.denominator };
|
|
19806
19847
|
}
|
|
19807
|
-
function normalizedText(value) {
|
|
19808
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
|
|
19809
|
-
}
|
|
19810
19848
|
function overviewFilterFingerprint(query) {
|
|
19811
19849
|
const filters = {
|
|
19812
19850
|
scope: query.scope,
|
|
19813
19851
|
groupKey: query.groupKey ?? null,
|
|
19852
|
+
marketKey: query.marketKey ?? null,
|
|
19814
19853
|
targetKey: query.targetKey ?? null,
|
|
19815
|
-
queryClass: query
|
|
19816
|
-
provider: query.provider === void 0 ? null :
|
|
19817
|
-
location: query.location === void 0 ? null :
|
|
19854
|
+
queryClass: servedQueryClass(query),
|
|
19855
|
+
provider: query.provider === void 0 ? null : normalizeIdentityText(query.provider),
|
|
19856
|
+
location: query.location === void 0 ? null : normalizeIdentityText(query.location),
|
|
19818
19857
|
from: query.from ?? null,
|
|
19819
19858
|
to: query.to ?? null,
|
|
19820
|
-
search: query.search === void 0 ? null :
|
|
19859
|
+
search: query.search === void 0 ? null : normalizeIdentityText(query.search),
|
|
19821
19860
|
compact: query.compact ?? false
|
|
19822
19861
|
};
|
|
19823
19862
|
return createHash4("sha256").update(JSON.stringify(filters)).digest("base64url");
|
|
@@ -19826,29 +19865,30 @@ function overviewAggregateFingerprint(query) {
|
|
|
19826
19865
|
const filters = {
|
|
19827
19866
|
scope: query.scope,
|
|
19828
19867
|
groupKey: query.groupKey ?? null,
|
|
19868
|
+
marketKey: query.marketKey ?? null,
|
|
19829
19869
|
targetKey: query.targetKey ?? null,
|
|
19830
|
-
queryClass: query
|
|
19831
|
-
provider: query.provider === void 0 ? null :
|
|
19832
|
-
location: query.location === void 0 ? null :
|
|
19870
|
+
queryClass: servedQueryClass(query),
|
|
19871
|
+
provider: query.provider === void 0 ? null : normalizeIdentityText(query.provider),
|
|
19872
|
+
location: query.location === void 0 ? null : normalizeIdentityText(query.location),
|
|
19833
19873
|
from: query.from ?? null,
|
|
19834
19874
|
to: query.to ?? null
|
|
19835
19875
|
};
|
|
19836
19876
|
return createHash4("sha256").update(JSON.stringify(filters)).digest("base64url");
|
|
19837
19877
|
}
|
|
19838
19878
|
function compareLabels(left, right) {
|
|
19839
|
-
const leftLabel =
|
|
19840
|
-
const rightLabel =
|
|
19879
|
+
const leftLabel = normalizeIdentityText(left.label);
|
|
19880
|
+
const rightLabel = normalizeIdentityText(right.label);
|
|
19841
19881
|
if (leftLabel !== rightLabel) return leftLabel < rightLabel ? -1 : 1;
|
|
19842
|
-
return left.targetKey
|
|
19882
|
+
return compareText(left.targetKey, right.targetKey);
|
|
19843
19883
|
}
|
|
19844
19884
|
function compareLabelSort(left, right, descending) {
|
|
19845
|
-
const leftLabel =
|
|
19846
|
-
const rightLabel =
|
|
19885
|
+
const leftLabel = normalizeIdentityText(left.label);
|
|
19886
|
+
const rightLabel = normalizeIdentityText(right.label);
|
|
19847
19887
|
if (leftLabel !== rightLabel) {
|
|
19848
19888
|
const compared = leftLabel < rightLabel ? -1 : 1;
|
|
19849
19889
|
return descending ? -compared : compared;
|
|
19850
19890
|
}
|
|
19851
|
-
return left.targetKey
|
|
19891
|
+
return compareText(left.targetKey, right.targetKey);
|
|
19852
19892
|
}
|
|
19853
19893
|
function metricForSort(row, sort) {
|
|
19854
19894
|
switch (sort) {
|
|
@@ -19877,7 +19917,7 @@ function compareRows2(left, right, sort) {
|
|
|
19877
19917
|
return compareLabels(left, right);
|
|
19878
19918
|
}
|
|
19879
19919
|
function legacyCursorOf(row) {
|
|
19880
|
-
return Buffer.from(`${
|
|
19920
|
+
return Buffer.from(`${normalizeIdentityText(row.label)}:${row.targetKey}`, "utf8").toString("base64url");
|
|
19881
19921
|
}
|
|
19882
19922
|
function cursorOf(row, sort, displayedRunId, filterFingerprint2, planVersionId, evidenceFingerprint) {
|
|
19883
19923
|
const cursor = {
|
|
@@ -20071,6 +20111,7 @@ function resolveScope(plan, query) {
|
|
|
20071
20111
|
group: v2GroupOf(plan, groupKey)
|
|
20072
20112
|
};
|
|
20073
20113
|
}
|
|
20114
|
+
if (query.scope === "market") return resolveMarketScope(plan, query.marketKey);
|
|
20074
20115
|
if (query.scope === "property") {
|
|
20075
20116
|
if (query.targetKey === void 0) throw validationError('"targetKey" is required when scope is "property".');
|
|
20076
20117
|
const target = plan.targets.find((candidate) => candidate.stableKey === query.targetKey);
|
|
@@ -20079,6 +20120,24 @@ function resolveScope(plan, query) {
|
|
|
20079
20120
|
}
|
|
20080
20121
|
return { kind: "all", label: "All Properties", targetKeys: plan.targets.map((target) => target.stableKey), group: null };
|
|
20081
20122
|
}
|
|
20123
|
+
function resolveMarketScope(plan, marketKey) {
|
|
20124
|
+
if (marketKey === void 0) throw validationError('"marketKey" is required when scope is "market".');
|
|
20125
|
+
const notInRevision = () => validationError(`Measurement market "${marketKey}" is not in the active revision.`);
|
|
20126
|
+
if (plan.schemaVersion !== MEASUREMENT_PLAN_V2_SCHEMA_VERSION) throw notInRevision();
|
|
20127
|
+
const market = plan.reportingScopes?.find((candidate) => candidate.stableKey === marketKey);
|
|
20128
|
+
if (!market) throw notInRevision();
|
|
20129
|
+
const members = new Set(market.usageEdges.map(measurementV2UsageEdgeKey));
|
|
20130
|
+
const edges = plan.usageEdges.filter((edge) => members.has(measurementV2UsageEdgeKey(edge)));
|
|
20131
|
+
const targetKeys = new Set(edges.map((edge) => edge.targetKey));
|
|
20132
|
+
return {
|
|
20133
|
+
kind: "market",
|
|
20134
|
+
key: market.stableKey,
|
|
20135
|
+
label: market.label,
|
|
20136
|
+
targetKeys: plan.targets.map((target) => target.stableKey).filter((targetKey) => targetKeys.has(targetKey)),
|
|
20137
|
+
group: null,
|
|
20138
|
+
edgeIds: new Set(edges.map(v2UsageEdgeId))
|
|
20139
|
+
};
|
|
20140
|
+
}
|
|
20082
20141
|
function namedIdentitiesFor(plan, scope, queryClass) {
|
|
20083
20142
|
if (scope.kind !== "group" || queryClass !== "non-brand" || scope.group === null) return [];
|
|
20084
20143
|
if (scope.group.competitors.length === 0) return [];
|
|
@@ -20116,12 +20175,9 @@ function planExpectedSlots(plan) {
|
|
|
20116
20175
|
}
|
|
20117
20176
|
function matchesSearch(row, search) {
|
|
20118
20177
|
if (search === void 0) return true;
|
|
20119
|
-
const needle =
|
|
20178
|
+
const needle = normalizeIdentityText(search);
|
|
20120
20179
|
if (needle === "") return true;
|
|
20121
|
-
return
|
|
20122
|
-
}
|
|
20123
|
-
function compareGroupText(left, right) {
|
|
20124
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
20180
|
+
return normalizeIdentityText(row.label).includes(needle) || normalizeIdentityText(row.targetKey).includes(needle);
|
|
20125
20181
|
}
|
|
20126
20182
|
function propertyLocations(plan) {
|
|
20127
20183
|
const groupsByKey = new Map(plan.groups.map((group) => [group.stableKey, group]));
|
|
@@ -20138,7 +20194,7 @@ function propertyLocations(plan) {
|
|
|
20138
20194
|
}
|
|
20139
20195
|
return levels;
|
|
20140
20196
|
};
|
|
20141
|
-
const byLabel = (left, right) =>
|
|
20197
|
+
const byLabel = (left, right) => compareText(left.label, right.label) || compareText(left.stableKey, right.stableKey);
|
|
20142
20198
|
const metros = /* @__PURE__ */ new Map();
|
|
20143
20199
|
const submarkets = /* @__PURE__ */ new Map();
|
|
20144
20200
|
const push = (map, key, value) => {
|
|
@@ -20199,7 +20255,7 @@ function planV1Overview(db, projectId, active, query, scope) {
|
|
|
20199
20255
|
return {
|
|
20200
20256
|
mode: "active-v1",
|
|
20201
20257
|
scope: scopeDto(scope),
|
|
20202
|
-
queryClass: query
|
|
20258
|
+
queryClass: servedQueryClass(query),
|
|
20203
20259
|
measurement: {
|
|
20204
20260
|
state: displayed ? displayedState(displayed.status) : "not_measured",
|
|
20205
20261
|
...displayed ? { currentRunId: displayed.id, displayedRunId: displayed.id } : {},
|
|
@@ -20220,7 +20276,7 @@ function planV1Overview(db, projectId, active, query, scope) {
|
|
|
20220
20276
|
};
|
|
20221
20277
|
}
|
|
20222
20278
|
function planV2Overview(db, projectId, active, plan, query, scope, cache) {
|
|
20223
|
-
const queryClass = query
|
|
20279
|
+
const queryClass = servedQueryClass(query);
|
|
20224
20280
|
const displayed = selectDisplayedRun(db, projectId, active, query);
|
|
20225
20281
|
const current = latestMeasurementRun(db, projectId, active.version.id, CURRENT_RUN_STATUSES);
|
|
20226
20282
|
const currentDto = current ? { currentRunId: current.id } : {};
|
|
@@ -20275,8 +20331,8 @@ function planV2Overview(db, projectId, active, plan, query, scope, cache) {
|
|
|
20275
20331
|
}, () => {
|
|
20276
20332
|
const manifest = measurementRunExpectedSlots(displayed, plan);
|
|
20277
20333
|
const { input, edgeQueryClass } = buildMeasurementPlanV2ReportInput(active.version.revision, plan, manifest, snapshots);
|
|
20278
|
-
const expectedSlots = input.expectedSlots.filter((slot) => (query.provider === void 0 || slot.provider ===
|
|
20279
|
-
const usageEdges =
|
|
20334
|
+
const expectedSlots = input.expectedSlots.filter((slot) => (query.provider === void 0 || slot.provider === normalizeIdentityText(query.provider)) && (query.location === void 0 || normalizeIdentityText(slot.location ?? "") === normalizeIdentityText(query.location)));
|
|
20335
|
+
const usageEdges = input.usageEdges.filter((edge) => (queryClass === "all" || edgeQueryClass.get(edge.id) === queryClass) && (scope.edgeIds === void 0 || scope.edgeIds.has(edge.id)));
|
|
20280
20336
|
return buildMeasurementOverview({
|
|
20281
20337
|
...input,
|
|
20282
20338
|
expectedSlots,
|
|
@@ -20514,7 +20570,7 @@ function readCompetitorIdentityChangedAt(db, projectId) {
|
|
|
20514
20570
|
return renamed !== null && renamed > audited ? renamed : audited;
|
|
20515
20571
|
}
|
|
20516
20572
|
function pooledRunIds(pooled) {
|
|
20517
|
-
return [...pooled].sort((left, right) => left.createdAt === right.createdAt ?
|
|
20573
|
+
return [...pooled].sort((left, right) => left.createdAt === right.createdAt ? compareText(right.id, left.id) : compareText(right.createdAt, left.createdAt)).slice(0, POOLED_RUN_ID_LIMIT).map((run) => run.id);
|
|
20518
20574
|
}
|
|
20519
20575
|
function readCompetitorLandscape(app, projectName, query, selection) {
|
|
20520
20576
|
const project = resolveProject(app.db, projectName);
|
|
@@ -20666,7 +20722,7 @@ function readCompetitorLandscape(app, projectName, query, selection) {
|
|
|
20666
20722
|
else excluded.nonCompleted++;
|
|
20667
20723
|
excludedByModel.set(key, excluded);
|
|
20668
20724
|
}
|
|
20669
|
-
const sortedGroups = [...modelGroups.values()].sort((left, right) =>
|
|
20725
|
+
const sortedGroups = [...modelGroups.values()].sort((left, right) => compareText(left.provider, right.provider) || (left.model === null ? right.model === null ? 0 : -1 : right.model === null ? 1 : compareText(left.model, right.model)));
|
|
20670
20726
|
const groups = sortedGroups.slice(0, COMPETITOR_LANDSCAPE_MODEL_GROUP_LIMIT).map((group) => {
|
|
20671
20727
|
const groupHistory = buildHistory(group.snapshots);
|
|
20672
20728
|
const groupObserved = groupHistory.observed.slice(0, COMPETITOR_LANDSCAPE_RANKED_ROW_LIMIT);
|
|
@@ -20823,12 +20879,23 @@ function scopeForFrozenPlan(plan, kind, groupKey) {
|
|
|
20823
20879
|
ids.add(edge.targetKey);
|
|
20824
20880
|
targetIdsByExecution.set(edge.executionNodeKey, ids);
|
|
20825
20881
|
}
|
|
20882
|
+
const queryTextByExecution = new Map(plan.executionNodes.map((node) => [node.stableKey, node.queryText]));
|
|
20883
|
+
const brandedTargetIdsByExecution = /* @__PURE__ */ new Map();
|
|
20884
|
+
for (const assignment of plan.assignments) {
|
|
20885
|
+
if (assignment.queryClass !== "branded") continue;
|
|
20886
|
+
const ids = brandedTargetIdsByExecution.get(assignment.executionNodeKey) ?? [];
|
|
20887
|
+
ids.push(assignment.targetKey);
|
|
20888
|
+
brandedTargetIdsByExecution.set(assignment.executionNodeKey, ids);
|
|
20889
|
+
}
|
|
20826
20890
|
return {
|
|
20827
20891
|
executionNodeKeys,
|
|
20828
20892
|
queryClassesByExecution,
|
|
20829
20893
|
mentioned: (answerText, executionId, citedUrls) => {
|
|
20830
20894
|
const ids = executionId === null ? [] : [...targetIdsByExecution.get(executionId) ?? []];
|
|
20831
|
-
|
|
20895
|
+
const queryText = executionId === null ? void 0 : queryTextByExecution.get(executionId);
|
|
20896
|
+
const brandedTargetIds = executionId === null ? void 0 : brandedTargetIdsByExecution.get(executionId);
|
|
20897
|
+
const query = queryText === void 0 || brandedTargetIds === void 0 ? null : { text: queryText, brandedTargetIds };
|
|
20898
|
+
return readMention(answerText, ids, citedUrls, query);
|
|
20832
20899
|
},
|
|
20833
20900
|
competitors: mergePins(groups.flatMap((group) => group.competitors.map((competitor) => ({
|
|
20834
20901
|
domain: competitor.domain,
|
|
@@ -20878,9 +20945,6 @@ function snapshotMatchesFilters(input) {
|
|
|
20878
20945
|
function requestedModel(model) {
|
|
20879
20946
|
return model?.trim() || null;
|
|
20880
20947
|
}
|
|
20881
|
-
function compareStoredIds(left, right) {
|
|
20882
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
20883
|
-
}
|
|
20884
20948
|
function normalizedDomain(value) {
|
|
20885
20949
|
return registrableDomain(value) || hostOf(value);
|
|
20886
20950
|
}
|
|
@@ -21853,8 +21917,8 @@ function hash(value) {
|
|
|
21853
21917
|
function answerIdentityFrom(project, competitorRows2, marketNames = /* @__PURE__ */ new Map()) {
|
|
21854
21918
|
const projectDomains = effectiveDomains({ canonicalDomain: project.canonicalDomain, ownedDomains: project.ownedDomains });
|
|
21855
21919
|
const projectBrandNames = effectiveBrandNames({ displayName: project.displayName, aliases: project.aliases });
|
|
21856
|
-
const names = competitorRows2.map((row) => [row.domain, competitorIdentityAliases(row)]).sort(([left], [right]) => left
|
|
21857
|
-
const market = [...marketNames].map(([domain, learned]) => [domain, [...learned]]).sort(([left], [right]) => left
|
|
21920
|
+
const names = competitorRows2.map((row) => [row.domain, competitorIdentityAliases(row)]).sort(([left], [right]) => compareText(left, right));
|
|
21921
|
+
const market = [...marketNames].map(([domain, learned]) => [domain, [...learned]]).sort(([left], [right]) => compareText(left, right));
|
|
21858
21922
|
return {
|
|
21859
21923
|
projectDomains,
|
|
21860
21924
|
projectBrandNames,
|
|
@@ -22304,12 +22368,6 @@ var MeasurementDiscoveryConfigurationError = class extends Error {
|
|
|
22304
22368
|
this.name = "MeasurementDiscoveryConfigurationError";
|
|
22305
22369
|
}
|
|
22306
22370
|
};
|
|
22307
|
-
function compareText5(a, b) {
|
|
22308
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
22309
|
-
}
|
|
22310
|
-
function sortedUnique3(values) {
|
|
22311
|
-
return [...new Set(values)].sort(compareText5);
|
|
22312
|
-
}
|
|
22313
22371
|
function normalizeDeclaredHost(value, label) {
|
|
22314
22372
|
const normalized = value.trim().toLowerCase().replace(/\.$/, "");
|
|
22315
22373
|
if (!normalized || normalized.includes("://") || normalized.includes("/") || normalized.includes("@")) {
|
|
@@ -22374,7 +22432,7 @@ function normalizePatternValue(value, label) {
|
|
|
22374
22432
|
return normalized;
|
|
22375
22433
|
}
|
|
22376
22434
|
function compileRules(input) {
|
|
22377
|
-
const ownedHosts =
|
|
22435
|
+
const ownedHosts = sortedUnique(input.ownedHosts.map((host, index2) => normalizeDeclaredHost(host, `ownedHosts[${index2}]`)));
|
|
22378
22436
|
if (ownedHosts.length === 0) {
|
|
22379
22437
|
throw new MeasurementDiscoveryConfigurationError("ownedHosts must contain at least one hostname");
|
|
22380
22438
|
}
|
|
@@ -22385,7 +22443,7 @@ function compileRules(input) {
|
|
|
22385
22443
|
if (aliases.some((alias2) => alias2.host === primary.host)) {
|
|
22386
22444
|
throw new MeasurementDiscoveryConfigurationError("alias rules must use another owned host");
|
|
22387
22445
|
}
|
|
22388
|
-
const excludedSlugSuffixes =
|
|
22446
|
+
const excludedSlugSuffixes = sortedUnique(
|
|
22389
22447
|
(input.rules.excludedSlugSuffixes ?? []).map(
|
|
22390
22448
|
(value, index2) => normalizePatternValue(value, `rules.excludedSlugSuffixes[${index2}]`)
|
|
22391
22449
|
)
|
|
@@ -22393,7 +22451,7 @@ function compileRules(input) {
|
|
|
22393
22451
|
const excludedSlugPatterns = (input.rules.excludedSlugPatterns ?? []).map((pattern, index2) => ({
|
|
22394
22452
|
kind: pattern.kind,
|
|
22395
22453
|
value: normalizePatternValue(pattern.value, `rules.excludedSlugPatterns[${index2}].value`)
|
|
22396
|
-
})).sort((a, b) =>
|
|
22454
|
+
})).sort((a, b) => compareText(`${a.kind}:${a.value}`, `${b.kind}:${b.value}`));
|
|
22397
22455
|
return { ownedHosts, rules: { primary, aliases, excludedSlugSuffixes, excludedSlugPatterns } };
|
|
22398
22456
|
}
|
|
22399
22457
|
function parseSitemapUrl(value) {
|
|
@@ -22473,10 +22531,10 @@ function classifyMeasurementSitemapUrls(input) {
|
|
|
22473
22531
|
const shared = [];
|
|
22474
22532
|
const unmatched = [];
|
|
22475
22533
|
const excluded = [];
|
|
22476
|
-
const canonicalEntries = [...canonicalGroups.entries()].sort(([a], [b]) =>
|
|
22534
|
+
const canonicalEntries = [...canonicalGroups.entries()].sort(([a], [b]) => compareText(a, b));
|
|
22477
22535
|
let classifiedUrlCount = 0;
|
|
22478
22536
|
for (const [canonicalUrl, group] of canonicalEntries) {
|
|
22479
|
-
const orderedGroup = [...group].sort((a, b) =>
|
|
22537
|
+
const orderedGroup = [...group].sort((a, b) => compareText(a.sourceUrl, b.sourceUrl));
|
|
22480
22538
|
const representative = orderedGroup[0];
|
|
22481
22539
|
for (let index2 = 1; index2 < orderedGroup.length; index2 += 1) {
|
|
22482
22540
|
diagnostics.push({
|
|
@@ -22534,7 +22592,7 @@ function classifyMeasurementSitemapUrls(input) {
|
|
|
22534
22592
|
else unmatched.push(item(canonicalUrl, "unmatched", "unmatched-path"));
|
|
22535
22593
|
}
|
|
22536
22594
|
const proposed = [...candidatesBySlug.values()].map(({ slug, stableKey, url }) => {
|
|
22537
|
-
const aliasCoverageUrls =
|
|
22595
|
+
const aliasCoverageUrls = sortedUnique((aliasesBySlug.get(slug) ?? []).map((match) => match.url));
|
|
22538
22596
|
return {
|
|
22539
22597
|
classification: "proposed",
|
|
22540
22598
|
reason: "primary-match",
|
|
@@ -22544,7 +22602,7 @@ function classifyMeasurementSitemapUrls(input) {
|
|
|
22544
22602
|
primaryUrl: url,
|
|
22545
22603
|
aliasCoverageUrls
|
|
22546
22604
|
};
|
|
22547
|
-
}).sort((a, b) =>
|
|
22605
|
+
}).sort((a, b) => compareText(a.stableKey, b.stableKey));
|
|
22548
22606
|
const aliases = [];
|
|
22549
22607
|
for (const [slug, matches] of aliasesBySlug) {
|
|
22550
22608
|
const target = candidatesBySlug.get(slug);
|
|
@@ -22562,14 +22620,14 @@ function classifyMeasurementSitemapUrls(input) {
|
|
|
22562
22620
|
}
|
|
22563
22621
|
for (const match of matches) unmatched.push(item(match.url, "unmatched", "alias-without-primary"));
|
|
22564
22622
|
}
|
|
22565
|
-
const sortItems = (items) => items.sort((a, b) =>
|
|
22623
|
+
const sortItems = (items) => items.sort((a, b) => compareText(a.url, b.url));
|
|
22566
22624
|
return {
|
|
22567
22625
|
proposed,
|
|
22568
|
-
aliases: aliases.sort((a, b) =>
|
|
22626
|
+
aliases: aliases.sort((a, b) => compareText(a.url, b.url)),
|
|
22569
22627
|
shared: sortItems(shared),
|
|
22570
22628
|
unmatched: sortItems(unmatched),
|
|
22571
22629
|
excluded: sortItems(excluded),
|
|
22572
|
-
diagnostics: diagnostics.sort((a, b) =>
|
|
22630
|
+
diagnostics: diagnostics.sort((a, b) => compareText(`${a.kind}:${a.url}`, `${b.kind}:${b.url}`))
|
|
22573
22631
|
};
|
|
22574
22632
|
}
|
|
22575
22633
|
|
|
@@ -22580,7 +22638,117 @@ import { gunzip } from "zlib";
|
|
|
22580
22638
|
import { promisify as promisify2 } from "util";
|
|
22581
22639
|
import http from "http";
|
|
22582
22640
|
import https from "https";
|
|
22641
|
+
import net2 from "net";
|
|
22642
|
+
|
|
22643
|
+
// ../api-routes/src/egress-policy.ts
|
|
22583
22644
|
import net from "net";
|
|
22645
|
+
function blockedAddressReason(value, options = {}) {
|
|
22646
|
+
const address = stripZoneId(stripIpv6Brackets(value));
|
|
22647
|
+
if (options.allowLoopback && isLoopbackAddress(address)) return null;
|
|
22648
|
+
const family = net.isIP(address);
|
|
22649
|
+
if (family === 4) return blockedIpv4Reason(ipv4Octets(address));
|
|
22650
|
+
if (family === 6) {
|
|
22651
|
+
const bytes = ipv6Bytes(address);
|
|
22652
|
+
return bytes ? blockedIpv6Reason(bytes) : "which could not be read as an address";
|
|
22653
|
+
}
|
|
22654
|
+
return "which is not an IP address";
|
|
22655
|
+
}
|
|
22656
|
+
function isLoopbackAddress(value) {
|
|
22657
|
+
const address = stripZoneId(stripIpv6Brackets(value));
|
|
22658
|
+
const family = net.isIP(address);
|
|
22659
|
+
if (family === 4) return ipv4Octets(address)[0] === 127;
|
|
22660
|
+
if (family !== 6) return false;
|
|
22661
|
+
const bytes = ipv6Bytes(address);
|
|
22662
|
+
if (!bytes) return false;
|
|
22663
|
+
const mapped = mappedIpv4(bytes);
|
|
22664
|
+
if (mapped) return mapped[0] === 127;
|
|
22665
|
+
return bytes.slice(0, 15).every((byte) => byte === 0) && bytes[15] === 1;
|
|
22666
|
+
}
|
|
22667
|
+
function stripIpv6Brackets(value) {
|
|
22668
|
+
return value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
22669
|
+
}
|
|
22670
|
+
function stripZoneId(address) {
|
|
22671
|
+
return address.split("%")[0];
|
|
22672
|
+
}
|
|
22673
|
+
function ipv4Octets(address) {
|
|
22674
|
+
return address.split(".").map((part) => Number.parseInt(part, 10));
|
|
22675
|
+
}
|
|
22676
|
+
function blockedIpv4Reason(octets) {
|
|
22677
|
+
const [first, second] = octets;
|
|
22678
|
+
if (first === 0) return "which is unspecified space";
|
|
22679
|
+
if (first === 127) return "which is loopback";
|
|
22680
|
+
if (first === 10 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168) {
|
|
22681
|
+
return "which is private space";
|
|
22682
|
+
}
|
|
22683
|
+
if (first === 100 && second >= 64 && second <= 127) return "which is carrier-grade NAT space";
|
|
22684
|
+
if (first === 169 && second === 254) return "which is link-local";
|
|
22685
|
+
if (first >= 224 && first <= 239) return "which is multicast";
|
|
22686
|
+
if (first === 192 && second === 0 && octets[2] === 0 || first === 198 && (second === 18 || second === 19) || first >= 240) {
|
|
22687
|
+
return "which is reserved";
|
|
22688
|
+
}
|
|
22689
|
+
return null;
|
|
22690
|
+
}
|
|
22691
|
+
function blockedIpv6Reason(bytes) {
|
|
22692
|
+
const first = bytes[0];
|
|
22693
|
+
if (first === 0) {
|
|
22694
|
+
const nat64 = nat64Ipv4(bytes);
|
|
22695
|
+
if (nat64) return blockedIpv4Reason(nat64) === null ? null : `which is the NAT64 form of ${nat64.join(".")}`;
|
|
22696
|
+
const mapped = mappedIpv4(bytes);
|
|
22697
|
+
return mapped ? `which is the IPv6 form of ${mapped.join(".")}` : "which is reserved IPv6 space";
|
|
22698
|
+
}
|
|
22699
|
+
if (first === 1 && bytes.slice(1, 8).every((byte) => byte === 0)) return "which is reserved IPv6 space";
|
|
22700
|
+
if (first === 255) return "which is multicast";
|
|
22701
|
+
if ((first & 254) === 252) return "which is unique-local";
|
|
22702
|
+
if (first === 254 && (bytes[1] & 192) === 128) return "which is link-local";
|
|
22703
|
+
if (first === 254 && (bytes[1] & 192) === 192) return "which is reserved site-local space";
|
|
22704
|
+
if (first === 32 && bytes[1] === 1 && bytes[2] === 0 && bytes[3] === 0) return "which is a Teredo tunnel";
|
|
22705
|
+
if (first === 32 && bytes[1] === 2) return "which is a 6to4 tunnel";
|
|
22706
|
+
if (first === 32 && bytes[1] === 1 && bytes[2] === 0 && bytes[3] === 2 && bytes[4] === 0 && bytes[5] === 0) {
|
|
22707
|
+
return "which is reserved IPv6 space";
|
|
22708
|
+
}
|
|
22709
|
+
return null;
|
|
22710
|
+
}
|
|
22711
|
+
function mappedIpv4(bytes) {
|
|
22712
|
+
const isMapped = bytes.slice(0, 10).every((byte) => byte === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
22713
|
+
return isMapped ? [bytes[12], bytes[13], bytes[14], bytes[15]] : null;
|
|
22714
|
+
}
|
|
22715
|
+
var NAT64_WELL_KNOWN_PREFIX = [0, 100, 255, 155, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
22716
|
+
function nat64Ipv4(bytes) {
|
|
22717
|
+
const isNat64 = NAT64_WELL_KNOWN_PREFIX.every((byte, index2) => bytes[index2] === byte);
|
|
22718
|
+
return isNat64 ? [bytes[12], bytes[13], bytes[14], bytes[15]] : null;
|
|
22719
|
+
}
|
|
22720
|
+
function ipv6Bytes(address) {
|
|
22721
|
+
if (!net.isIPv6(address)) return null;
|
|
22722
|
+
const halves = address.split("::");
|
|
22723
|
+
if (halves.length > 2) return null;
|
|
22724
|
+
const expand = (text2) => {
|
|
22725
|
+
if (text2 === "") return [];
|
|
22726
|
+
const groups2 = [];
|
|
22727
|
+
const chunks = text2.split(":");
|
|
22728
|
+
for (const [index2, chunk] of chunks.entries()) {
|
|
22729
|
+
if (index2 === chunks.length - 1 && chunk.includes(".")) {
|
|
22730
|
+
const octets = ipv4Octets(chunk);
|
|
22731
|
+
groups2.push(octets[0] << 8 | octets[1], octets[2] << 8 | octets[3]);
|
|
22732
|
+
continue;
|
|
22733
|
+
}
|
|
22734
|
+
groups2.push(Number.parseInt(chunk, 16));
|
|
22735
|
+
}
|
|
22736
|
+
return groups2;
|
|
22737
|
+
};
|
|
22738
|
+
const head = expand(halves[0]);
|
|
22739
|
+
const tail = halves.length === 2 ? expand(halves[1]) : [];
|
|
22740
|
+
const missing = 8 - head.length - tail.length;
|
|
22741
|
+
if (halves.length === 1 ? missing !== 0 : missing < 0) return null;
|
|
22742
|
+
const groups = [...head, ...new Array(halves.length === 2 ? missing : 0).fill(0), ...tail];
|
|
22743
|
+
const bytes = new Uint8Array(16);
|
|
22744
|
+
for (const [index2, group] of groups.entries()) {
|
|
22745
|
+
bytes[index2 * 2] = group >> 8;
|
|
22746
|
+
bytes[index2 * 2 + 1] = group & 255;
|
|
22747
|
+
}
|
|
22748
|
+
return bytes;
|
|
22749
|
+
}
|
|
22750
|
+
|
|
22751
|
+
// ../api-routes/src/measurement-sitemap-fetch.ts
|
|
22584
22752
|
var gunzipAsync = promisify2(gunzip);
|
|
22585
22753
|
var DEFAULT_MEASUREMENT_SITEMAP_LIMITS = {
|
|
22586
22754
|
timeoutMs: 1e4,
|
|
@@ -22630,7 +22798,7 @@ async function fetchMeasurementSitemap(sitemapUrl, options = {}) {
|
|
|
22630
22798
|
}
|
|
22631
22799
|
}
|
|
22632
22800
|
await visit(sitemapUrl, 0);
|
|
22633
|
-
return { urls: [...urls].sort(
|
|
22801
|
+
return { urls: [...urls].sort(compareText), fetchedSitemaps: visited.size, bytesChecksum: documents.digest("hex") };
|
|
22634
22802
|
}
|
|
22635
22803
|
async function resolveMeasurementSitemapTarget(rawUrl, options = {}) {
|
|
22636
22804
|
let parsed;
|
|
@@ -22647,7 +22815,7 @@ async function resolveMeasurementSitemapTarget(rawUrl, options = {}) {
|
|
|
22647
22815
|
}
|
|
22648
22816
|
const hostname = stripIpv6Brackets(parsed.hostname);
|
|
22649
22817
|
if (!hostname) return { ok: false, message: "the URL has no hostname" };
|
|
22650
|
-
const literalFamily =
|
|
22818
|
+
const literalFamily = net2.isIPv6(hostname) ? 6 : net2.isIPv4(hostname) ? 4 : null;
|
|
22651
22819
|
const addresses = literalFamily ? [{ address: hostname, family: literalFamily }] : await (options.resolveAddresses ?? resolveSitemapAddresses)(hostname);
|
|
22652
22820
|
if (addresses.length === 0) return { ok: false, message: `${hostname} does not resolve` };
|
|
22653
22821
|
for (const entry of addresses) {
|
|
@@ -22663,85 +22831,6 @@ async function resolveSitemapAddresses(hostname) {
|
|
|
22663
22831
|
if (ipv6.status === "fulfilled") for (const address of ipv6.value) found.set(`6:${address}`, { address, family: 6 });
|
|
22664
22832
|
return [...found.values()];
|
|
22665
22833
|
}
|
|
22666
|
-
function blockedAddressReason(value) {
|
|
22667
|
-
const address = stripIpv6Brackets(value).split("%")[0];
|
|
22668
|
-
const family = net.isIP(address);
|
|
22669
|
-
if (family === 4) return blockedIpv4Reason(ipv4Octets(address));
|
|
22670
|
-
if (family === 6) {
|
|
22671
|
-
const bytes = ipv6Bytes(address);
|
|
22672
|
-
return bytes ? blockedIpv6Reason(bytes) : "which could not be read as an address";
|
|
22673
|
-
}
|
|
22674
|
-
return "which is not an IP address";
|
|
22675
|
-
}
|
|
22676
|
-
function ipv4Octets(address) {
|
|
22677
|
-
return address.split(".").map((part) => Number.parseInt(part, 10));
|
|
22678
|
-
}
|
|
22679
|
-
function blockedIpv4Reason(octets) {
|
|
22680
|
-
const [first, second] = octets;
|
|
22681
|
-
if (first === 0) return "which is unspecified space";
|
|
22682
|
-
if (first === 127) return "which is loopback";
|
|
22683
|
-
if (first === 10 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168) {
|
|
22684
|
-
return "which is private space";
|
|
22685
|
-
}
|
|
22686
|
-
if (first === 100 && second >= 64 && second <= 127) return "which is carrier-grade NAT space";
|
|
22687
|
-
if (first === 169 && second === 254) return "which is link-local";
|
|
22688
|
-
if (first >= 224 && first <= 239) return "which is multicast";
|
|
22689
|
-
if (first === 192 && second === 0 && octets[2] === 0 || first === 198 && (second === 18 || second === 19) || first >= 240) {
|
|
22690
|
-
return "which is reserved";
|
|
22691
|
-
}
|
|
22692
|
-
return null;
|
|
22693
|
-
}
|
|
22694
|
-
function blockedIpv6Reason(bytes) {
|
|
22695
|
-
const first = bytes[0];
|
|
22696
|
-
if (first === 0) {
|
|
22697
|
-
const mapped = mappedIpv4(bytes);
|
|
22698
|
-
return mapped ? `which is the IPv6 form of ${mapped.join(".")}` : "which is reserved IPv6 space";
|
|
22699
|
-
}
|
|
22700
|
-
if (first === 255) return "which is multicast";
|
|
22701
|
-
if ((first & 254) === 252) return "which is unique-local";
|
|
22702
|
-
if (first === 254 && (bytes[1] & 192) === 128) return "which is link-local";
|
|
22703
|
-
if (first === 254 && (bytes[1] & 192) === 192) return "which is reserved site-local space";
|
|
22704
|
-
if (first === 32 && bytes[1] === 1 && bytes[2] === 0 && bytes[3] === 0) return "which is a Teredo tunnel";
|
|
22705
|
-
if (first === 32 && bytes[1] === 2) return "which is a 6to4 tunnel";
|
|
22706
|
-
return null;
|
|
22707
|
-
}
|
|
22708
|
-
function mappedIpv4(bytes) {
|
|
22709
|
-
const isMapped = bytes.slice(0, 10).every((byte) => byte === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
22710
|
-
return isMapped ? [bytes[12], bytes[13], bytes[14], bytes[15]] : null;
|
|
22711
|
-
}
|
|
22712
|
-
function ipv6Bytes(address) {
|
|
22713
|
-
if (!net.isIPv6(address)) return null;
|
|
22714
|
-
const halves = address.split("::");
|
|
22715
|
-
if (halves.length > 2) return null;
|
|
22716
|
-
const expand = (text2) => {
|
|
22717
|
-
if (text2 === "") return [];
|
|
22718
|
-
const groups2 = [];
|
|
22719
|
-
const chunks = text2.split(":");
|
|
22720
|
-
for (const [index2, chunk] of chunks.entries()) {
|
|
22721
|
-
if (index2 === chunks.length - 1 && chunk.includes(".")) {
|
|
22722
|
-
const octets = ipv4Octets(chunk);
|
|
22723
|
-
groups2.push(octets[0] << 8 | octets[1], octets[2] << 8 | octets[3]);
|
|
22724
|
-
continue;
|
|
22725
|
-
}
|
|
22726
|
-
groups2.push(Number.parseInt(chunk, 16));
|
|
22727
|
-
}
|
|
22728
|
-
return groups2;
|
|
22729
|
-
};
|
|
22730
|
-
const head = expand(halves[0]);
|
|
22731
|
-
const tail = halves.length === 2 ? expand(halves[1]) : [];
|
|
22732
|
-
const missing = 8 - head.length - tail.length;
|
|
22733
|
-
if (halves.length === 1 ? missing !== 0 : missing < 0) return null;
|
|
22734
|
-
const groups = [...head, ...new Array(halves.length === 2 ? missing : 0).fill(0), ...tail];
|
|
22735
|
-
const bytes = new Uint8Array(16);
|
|
22736
|
-
for (const [index2, group] of groups.entries()) {
|
|
22737
|
-
bytes[index2 * 2] = group >> 8;
|
|
22738
|
-
bytes[index2 * 2 + 1] = group & 255;
|
|
22739
|
-
}
|
|
22740
|
-
return bytes;
|
|
22741
|
-
}
|
|
22742
|
-
function stripIpv6Brackets(value) {
|
|
22743
|
-
return value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
22744
|
-
}
|
|
22745
22834
|
async function requestFollowingValidatedRedirects(startUrl, resolveTarget, transport, limits, deadlineAt) {
|
|
22746
22835
|
let current = startUrl;
|
|
22747
22836
|
for (let redirects = 0; ; redirects += 1) {
|
|
@@ -22798,7 +22887,7 @@ async function requestPinnedSitemap(target, limits) {
|
|
|
22798
22887
|
path: `${target.url.pathname}${target.url.search}`,
|
|
22799
22888
|
headers: { Host: target.url.host, Accept: "application/xml,text/xml,*/*;q=0.1", "Accept-Encoding": "gzip" }
|
|
22800
22889
|
};
|
|
22801
|
-
if (secure) requestOptions.servername = target.url.hostname
|
|
22890
|
+
if (secure) requestOptions.servername = stripIpv6Brackets(target.url.hostname);
|
|
22802
22891
|
return await new Promise((resolve, reject) => {
|
|
22803
22892
|
let settled = false;
|
|
22804
22893
|
const succeed = (response) => {
|
|
@@ -22854,11 +22943,11 @@ async function decodeSitemapBody(body, headers, maxBodyBytes) {
|
|
|
22854
22943
|
}
|
|
22855
22944
|
function sitemapIndexLocations(xml) {
|
|
22856
22945
|
if (!hasXmlElement(xml, "sitemapindex")) return [];
|
|
22857
|
-
return
|
|
22946
|
+
return sortedUnique(xmlElementContents(xml, "sitemap").flatMap(extractLocation));
|
|
22858
22947
|
}
|
|
22859
22948
|
function urlSetLocations(xml) {
|
|
22860
22949
|
if (!hasXmlElement(xml, "urlset")) return [];
|
|
22861
|
-
return
|
|
22950
|
+
return sortedUnique(xmlElementContents(xml, "url").flatMap(extractLocation));
|
|
22862
22951
|
}
|
|
22863
22952
|
function extractLocation(xml) {
|
|
22864
22953
|
const value = xmlElementContents(xml, "loc")[0]?.trim();
|
|
@@ -22949,12 +23038,6 @@ function decodeXmlEntities(value) {
|
|
|
22949
23038
|
function firstHeader(value) {
|
|
22950
23039
|
return Array.isArray(value) ? value[0] : value;
|
|
22951
23040
|
}
|
|
22952
|
-
function compareText6(a, b) {
|
|
22953
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
22954
|
-
}
|
|
22955
|
-
function sortedUniqueUrls(values) {
|
|
22956
|
-
return [...new Set(values)].sort(compareText6);
|
|
22957
|
-
}
|
|
22958
23041
|
function assertLimits(limits) {
|
|
22959
23042
|
for (const [name, value] of Object.entries(limits)) {
|
|
22960
23043
|
if (!Number.isSafeInteger(value) || value < 1) throw new Error(`Sitemap ${name} must be a positive safe integer`);
|
|
@@ -23008,7 +23091,13 @@ async function measurementServiceRoutes(app, opts) {
|
|
|
23008
23091
|
if (runId !== void 0 && (typeof runId !== "string" || runId.trim() === "")) {
|
|
23009
23092
|
throw validationError('"runId" must be a non-empty string');
|
|
23010
23093
|
}
|
|
23011
|
-
|
|
23094
|
+
let queryClass;
|
|
23095
|
+
if (request.query.queryClass !== void 0) {
|
|
23096
|
+
const parsedClass = measurementQueryClassFilterSchema.safeParse(request.query.queryClass);
|
|
23097
|
+
if (!parsedClass.success) throw validationError('"queryClass" must be one of non-brand, branded or all');
|
|
23098
|
+
queryClass = parsedClass.data;
|
|
23099
|
+
}
|
|
23100
|
+
const result = buildStoredMeasurementReport(app.db, project.id, revision, runId, queryClass);
|
|
23012
23101
|
if (result.kind === "no-plan") throw notFound("Measurement plan revision", rawRevision);
|
|
23013
23102
|
if (runId !== void 0 && result.report.run?.id !== runId) throw notFound("Measurement run", runId);
|
|
23014
23103
|
return measurementReportResponseSchema.parse(result.report);
|
|
@@ -23025,12 +23114,6 @@ var MEASUREMENT_DRAFT_MAX_QUERIES = 1e3;
|
|
|
23025
23114
|
var MEASUREMENT_PLAN_V2_MAX_BYTES = 4 * 1024 * 1024;
|
|
23026
23115
|
var CHECKSUM_PLACEHOLDER = "0".repeat(64);
|
|
23027
23116
|
var MEASUREMENT_V2_PROVENANCE_EPOCH = "1970-01-01T00:00:00.000Z";
|
|
23028
|
-
function compareText7(left, right) {
|
|
23029
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
23030
|
-
}
|
|
23031
|
-
function canonicalStrings(values) {
|
|
23032
|
-
return [...new Set(values)].sort(compareText7);
|
|
23033
|
-
}
|
|
23034
23117
|
function proposeQueryClass(queryText, brandNames) {
|
|
23035
23118
|
const matcher = compileBrandAliases(brandNames.filter((name) => brandKeyFromText(name).length > 0));
|
|
23036
23119
|
return matcherMatchesText(matcher, queryText) ? "branded" : "non-brand";
|
|
@@ -23062,12 +23145,17 @@ function parseDraftMatcher(value) {
|
|
|
23062
23145
|
function matcherHost(matcher) {
|
|
23063
23146
|
return matcher.kind === "exact" ? new URL(matcher.url).hostname : matcher.host;
|
|
23064
23147
|
}
|
|
23148
|
+
function matcherPathIdentity(path8, pathCase) {
|
|
23149
|
+
return pathCase === "insensitive" ? path8.toLocaleLowerCase("en") : path8;
|
|
23150
|
+
}
|
|
23065
23151
|
function matcherIdentity(matcher) {
|
|
23066
23152
|
switch (matcher.kind) {
|
|
23067
|
-
case "exact":
|
|
23068
|
-
|
|
23153
|
+
case "exact": {
|
|
23154
|
+
const url = new URL(matcher.url);
|
|
23155
|
+
return `exact\0${url.hostname}\0${matcherPathIdentity(url.pathname, matcher.pathCase)}`;
|
|
23156
|
+
}
|
|
23069
23157
|
case "prefix":
|
|
23070
|
-
return `prefix\0${matcher.host}\0${matcher.pathPrefix}`;
|
|
23158
|
+
return `prefix\0${matcher.host}\0${matcherPathIdentity(matcher.pathPrefix, matcher.pathCase)}`;
|
|
23071
23159
|
case "host":
|
|
23072
23160
|
return `host\0${matcher.host}\0`;
|
|
23073
23161
|
}
|
|
@@ -23142,7 +23230,7 @@ function createChecks() {
|
|
|
23142
23230
|
};
|
|
23143
23231
|
}
|
|
23144
23232
|
function normalizeProviders2(values) {
|
|
23145
|
-
return
|
|
23233
|
+
return sortedUnique(values.map((value) => value.trim().toLowerCase()).filter(Boolean));
|
|
23146
23234
|
}
|
|
23147
23235
|
function compileMeasurementDraft(authoring, context) {
|
|
23148
23236
|
const { checks, sink, failed } = createChecks();
|
|
@@ -23152,7 +23240,7 @@ function compileMeasurementDraft(authoring, context) {
|
|
|
23152
23240
|
} catch {
|
|
23153
23241
|
sink.fail("invalid-project-identity", "The project canonical domain is not a valid hostname.", ["identities", "projectBrand", "canonicalHost"]);
|
|
23154
23242
|
}
|
|
23155
|
-
const ownedHosts =
|
|
23243
|
+
const ownedHosts = sortedUnique([context.canonicalDomain, ...context.ownedDomains].flatMap((value) => {
|
|
23156
23244
|
try {
|
|
23157
23245
|
return [normalizeMeasurementHost(value)];
|
|
23158
23246
|
} catch {
|
|
@@ -23216,7 +23304,7 @@ function compileMeasurementDraft(authoring, context) {
|
|
|
23216
23304
|
}
|
|
23217
23305
|
aliasClaims.set(identity, target.stableKey);
|
|
23218
23306
|
});
|
|
23219
|
-
const aliases =
|
|
23307
|
+
const aliases = sortedUnique(target.aliases);
|
|
23220
23308
|
const aliasKeys = aliases.map(measurementMentionAliasKey).filter(Boolean);
|
|
23221
23309
|
target.identityAliases?.forEach((identityAlias, index2) => {
|
|
23222
23310
|
const identityKey = measurementMentionAliasKey(identityAlias);
|
|
@@ -23233,7 +23321,7 @@ function compileMeasurementDraft(authoring, context) {
|
|
|
23233
23321
|
stableKey: target.stableKey,
|
|
23234
23322
|
label: target.label,
|
|
23235
23323
|
aliases,
|
|
23236
|
-
...target.identityAliases === void 0 ? {} : { identityAliases:
|
|
23324
|
+
...target.identityAliases === void 0 ? {} : { identityAliases: sortedUnique(target.identityAliases) },
|
|
23237
23325
|
urlMatchers: matchers,
|
|
23238
23326
|
mentionNotApplicable: aliases.length === 0,
|
|
23239
23327
|
discoveryIdentity: target.discoveryIdentity ?? null
|
|
@@ -23281,8 +23369,8 @@ function compileMeasurementDraft(authoring, context) {
|
|
|
23281
23369
|
stableKey: group.stableKey,
|
|
23282
23370
|
label: group.label,
|
|
23283
23371
|
...group.parentGroupKey === void 0 ? {} : { parentGroupKey: group.parentGroupKey },
|
|
23284
|
-
targetKeys:
|
|
23285
|
-
competitors: [...group.competitors].map((competitor) => ({ ...competitor, aliases:
|
|
23372
|
+
targetKeys: sortedUnique(group.targetKeys),
|
|
23373
|
+
competitors: [...group.competitors].map((competitor) => ({ ...competitor, aliases: sortedUnique(competitor.aliases) })).sort((left, right) => compareText(left.stableKey, right.stableKey))
|
|
23286
23374
|
});
|
|
23287
23375
|
});
|
|
23288
23376
|
const resolveContext = (override, path8) => {
|
|
@@ -23300,7 +23388,7 @@ function compileMeasurementDraft(authoring, context) {
|
|
|
23300
23388
|
}
|
|
23301
23389
|
models[normalized] = model;
|
|
23302
23390
|
}
|
|
23303
|
-
const labels =
|
|
23391
|
+
const labels = sortedUnique(override?.locations ?? authoring.defaultContext.locations);
|
|
23304
23392
|
const locations = [];
|
|
23305
23393
|
labels.forEach((label, labelIndex) => {
|
|
23306
23394
|
const location = locationsByLabel.get(label);
|
|
@@ -23461,7 +23549,7 @@ function compileMeasurementDraft(authoring, context) {
|
|
|
23461
23549
|
}
|
|
23462
23550
|
}
|
|
23463
23551
|
if (failed()) return { ok: false, checks };
|
|
23464
|
-
const querySnapshots3 = [...usedQueryIds].sort(
|
|
23552
|
+
const querySnapshots3 = [...usedQueryIds].sort(compareText).map((queryId) => ({
|
|
23465
23553
|
queryId,
|
|
23466
23554
|
queryText: queriesById.get(queryId),
|
|
23467
23555
|
// Provenance is frozen at publish time so a later reader can explain the
|
|
@@ -23478,7 +23566,7 @@ function compileMeasurementDraft(authoring, context) {
|
|
|
23478
23566
|
projectBrand: {
|
|
23479
23567
|
canonicalHost,
|
|
23480
23568
|
ownedHosts,
|
|
23481
|
-
names:
|
|
23569
|
+
names: sortedUnique(context.brandNames)
|
|
23482
23570
|
}
|
|
23483
23571
|
},
|
|
23484
23572
|
targets: compiledTargets.map(({ target }) => target),
|
|
@@ -23527,13 +23615,13 @@ function keyedDiff2(before, after) {
|
|
|
23527
23615
|
const added = [];
|
|
23528
23616
|
const changed = [];
|
|
23529
23617
|
const unchanged = [];
|
|
23530
|
-
for (const stableKey of [...afterByKey.keys()].sort(
|
|
23618
|
+
for (const stableKey of [...afterByKey.keys()].sort(compareText)) {
|
|
23531
23619
|
const beforeValue = beforeByKey.get(stableKey);
|
|
23532
23620
|
if (!beforeValue) added.push(stableKey);
|
|
23533
23621
|
else if (JSON.stringify(canonicalJsonValue(beforeValue)) === JSON.stringify(canonicalJsonValue(afterByKey.get(stableKey)))) unchanged.push(stableKey);
|
|
23534
23622
|
else changed.push(stableKey);
|
|
23535
23623
|
}
|
|
23536
|
-
const removed = [...beforeByKey.keys()].filter((key) => !afterByKey.has(key)).sort(
|
|
23624
|
+
const removed = [...beforeByKey.keys()].filter((key) => !afterByKey.has(key)).sort(compareText);
|
|
23537
23625
|
return { added, removed, changed, unchanged };
|
|
23538
23626
|
}
|
|
23539
23627
|
function assignmentClasses(plan) {
|
|
@@ -23562,8 +23650,8 @@ function diffCompiledPlans(active, candidate, activeRevision) {
|
|
|
23562
23650
|
groups: keyedDiff2(active?.groups ?? [], candidate.groups),
|
|
23563
23651
|
assignments: { added: addedAssignments, removed: removedAssignments, reclassified },
|
|
23564
23652
|
execution: {
|
|
23565
|
-
addedNodeKeys: [...afterNodes].filter((key) => !beforeNodes.has(key)).sort(
|
|
23566
|
-
removedNodeKeys: [...beforeNodes].filter((key) => !afterNodes.has(key)).sort(
|
|
23653
|
+
addedNodeKeys: [...afterNodes].filter((key) => !beforeNodes.has(key)).sort(compareText),
|
|
23654
|
+
removedNodeKeys: [...beforeNodes].filter((key) => !afterNodes.has(key)).sort(compareText)
|
|
23567
23655
|
}
|
|
23568
23656
|
};
|
|
23569
23657
|
}
|
|
@@ -23636,9 +23724,6 @@ function requireGroup(authoring, groupKey) {
|
|
|
23636
23724
|
function unique(values) {
|
|
23637
23725
|
return [...new Set(values)];
|
|
23638
23726
|
}
|
|
23639
|
-
function compareText8(left, right) {
|
|
23640
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
23641
|
-
}
|
|
23642
23727
|
function knownQueryIds(queryIds, context) {
|
|
23643
23728
|
const uniqueQueryIds = unique(queryIds);
|
|
23644
23729
|
const unknown = uniqueQueryIds.filter((queryId) => !context.queriesById.has(queryId));
|
|
@@ -23692,7 +23777,7 @@ function resolveDraftAudience(authoring, audience) {
|
|
|
23692
23777
|
groups.push({ groupKey: group.stableKey, label: group.label, memberCount: memberKeys.length });
|
|
23693
23778
|
selected.push(...memberKeys);
|
|
23694
23779
|
}
|
|
23695
|
-
const targetKeys = unique(selected).sort(
|
|
23780
|
+
const targetKeys = unique(selected).sort(compareText);
|
|
23696
23781
|
return {
|
|
23697
23782
|
targetKeys,
|
|
23698
23783
|
groups,
|
|
@@ -24122,9 +24207,6 @@ var MeasurementGroupMembershipImportError = class extends Error {
|
|
|
24122
24207
|
statusCode;
|
|
24123
24208
|
details;
|
|
24124
24209
|
};
|
|
24125
|
-
function compareText9(left, right) {
|
|
24126
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
24127
|
-
}
|
|
24128
24210
|
function sha256Hex2(value) {
|
|
24129
24211
|
return crypto20.createHash("sha256").update(value).digest("hex");
|
|
24130
24212
|
}
|
|
@@ -24132,7 +24214,7 @@ function canonicalJsonValue2(value) {
|
|
|
24132
24214
|
if (Array.isArray(value)) return value.map(canonicalJsonValue2);
|
|
24133
24215
|
if (value && typeof value === "object") {
|
|
24134
24216
|
return Object.fromEntries(
|
|
24135
|
-
Object.entries(value).filter(([, child]) => child !== void 0).sort(([left], [right]) =>
|
|
24217
|
+
Object.entries(value).filter(([, child]) => child !== void 0).sort(([left], [right]) => compareText(left, right)).map(([key, child]) => [key, canonicalJsonValue2(child)])
|
|
24136
24218
|
);
|
|
24137
24219
|
}
|
|
24138
24220
|
return value;
|
|
@@ -24429,8 +24511,8 @@ function groupIdentities(rows, authoring, segments) {
|
|
|
24429
24511
|
groupsByLabel.set(label, matches);
|
|
24430
24512
|
}
|
|
24431
24513
|
const identities = [];
|
|
24432
|
-
for (const normalizedGroupLabel of [...labels.keys()].sort(
|
|
24433
|
-
const existing = [...groupsByLabel.get(normalizedGroupLabel) ?? []].sort((left, right) =>
|
|
24514
|
+
for (const normalizedGroupLabel of [...labels.keys()].sort(compareText)) {
|
|
24515
|
+
const existing = [...groupsByLabel.get(normalizedGroupLabel) ?? []].sort((left, right) => compareText(left.stableKey, right.stableKey));
|
|
24434
24516
|
if (existing.length > 1) {
|
|
24435
24517
|
identities.push({
|
|
24436
24518
|
kind: "ambiguous",
|
|
@@ -24508,8 +24590,8 @@ function groupChangesFromRows(rows, identities) {
|
|
|
24508
24590
|
bucket.rows.push(row);
|
|
24509
24591
|
byGroupKey.set(row.groupKey, bucket);
|
|
24510
24592
|
}
|
|
24511
|
-
return [...byGroupKey.values()].sort((left, right) =>
|
|
24512
|
-
const targetKeys =
|
|
24593
|
+
return [...byGroupKey.values()].sort((left, right) => compareText(left.identity.normalizedGroupLabel, right.identity.normalizedGroupLabel)).map(({ identity, rows: matchedRows }) => {
|
|
24594
|
+
const targetKeys = sortedUnique(matchedRows.map((row) => row.targetKey));
|
|
24513
24595
|
const addedTargetKeys = targetKeys.filter((targetKey) => !identity.existingTargetKeys.has(targetKey));
|
|
24514
24596
|
const unchangedTargetKeys = targetKeys.filter((targetKey) => identity.existingTargetKeys.has(targetKey));
|
|
24515
24597
|
return {
|
|
@@ -24573,7 +24655,7 @@ function resolveRows(parsedRows, authoring, identities) {
|
|
|
24573
24655
|
resolved.push(rowFailure(row, "invalid", "missing-group"));
|
|
24574
24656
|
continue;
|
|
24575
24657
|
}
|
|
24576
|
-
const labelCandidates = [...targetsByLabel.get(row.normalizedProperty) ?? []].sort((left, right) =>
|
|
24658
|
+
const labelCandidates = [...targetsByLabel.get(row.normalizedProperty) ?? []].sort((left, right) => compareText(left.stableKey, right.stableKey));
|
|
24577
24659
|
if (!labelCandidates.length) {
|
|
24578
24660
|
resolved.push(rowFailure(row, "unmatched", "property-not-found"));
|
|
24579
24661
|
continue;
|
|
@@ -24654,7 +24736,7 @@ function resolveArtifacts(input) {
|
|
|
24654
24736
|
return { rows, groupChanges, counts: countsFromRows(rows, groupChanges), identities };
|
|
24655
24737
|
}
|
|
24656
24738
|
function checksumIdentities(identities) {
|
|
24657
|
-
return [...identities].sort((left, right) =>
|
|
24739
|
+
return [...identities].sort((left, right) => compareText(left.normalizedGroupLabel, right.normalizedGroupLabel)).map((identity) => {
|
|
24658
24740
|
if (identity.kind === "ready") {
|
|
24659
24741
|
return {
|
|
24660
24742
|
normalizedGroupLabel: identity.normalizedGroupLabel,
|
|
@@ -24668,7 +24750,7 @@ function checksumIdentities(identities) {
|
|
|
24668
24750
|
return {
|
|
24669
24751
|
normalizedGroupLabel: identity.normalizedGroupLabel,
|
|
24670
24752
|
outcome: identity.kind,
|
|
24671
|
-
groupKeys: [...identity.groupKeys].sort(
|
|
24753
|
+
groupKeys: [...identity.groupKeys].sort(compareText)
|
|
24672
24754
|
};
|
|
24673
24755
|
}
|
|
24674
24756
|
return {
|
|
@@ -24676,8 +24758,8 @@ function checksumIdentities(identities) {
|
|
|
24676
24758
|
outcome: identity.kind,
|
|
24677
24759
|
groupKey: identity.groupKey,
|
|
24678
24760
|
evidence: [...identity.evidence].sort((left, right) => {
|
|
24679
|
-
const bySource =
|
|
24680
|
-
return bySource ||
|
|
24761
|
+
const bySource = compareText(left.source, right.source);
|
|
24762
|
+
return bySource || compareText(left.normalizedGroupLabel ?? "", right.normalizedGroupLabel ?? "");
|
|
24681
24763
|
})
|
|
24682
24764
|
};
|
|
24683
24765
|
});
|
|
@@ -24697,7 +24779,7 @@ function resolveGroupMembershipPreview(input) {
|
|
|
24697
24779
|
})),
|
|
24698
24780
|
rows: artifacts.rows,
|
|
24699
24781
|
proposedGroups: checksumIdentities(artifacts.identities),
|
|
24700
|
-
persistedSegments: [...input.segments].sort((left, right) =>
|
|
24782
|
+
persistedSegments: [...input.segments].sort((left, right) => compareText(left.stableKey, right.stableKey)).map((segment) => ({ stableKey: segment.stableKey, kind: segment.kind, retiredAt: segment.retiredAt }))
|
|
24701
24783
|
});
|
|
24702
24784
|
return {
|
|
24703
24785
|
draftEtag: input.draftEtag,
|
|
@@ -24825,9 +24907,6 @@ function applyReviewedGroupMembership(authoring, preview, review) {
|
|
|
24825
24907
|
|
|
24826
24908
|
// ../api-routes/src/measurement-draft.ts
|
|
24827
24909
|
var MEASUREMENT_GROUP_MEMBERSHIP_BODY_LIMIT = MEASUREMENT_GROUP_MEMBERSHIP_CSV_MAX_BYTES * 7 + 4096;
|
|
24828
|
-
function compareText10(left, right) {
|
|
24829
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
24830
|
-
}
|
|
24831
24910
|
function authoringIdentity(authoring) {
|
|
24832
24911
|
return canonicalJson(authoring);
|
|
24833
24912
|
}
|
|
@@ -24896,7 +24975,7 @@ function emptyAuthoring(project, opts) {
|
|
|
24896
24975
|
defaultContext: {
|
|
24897
24976
|
providers,
|
|
24898
24977
|
...Object.keys(models).length ? { models } : {},
|
|
24899
|
-
locations: project.locations.map((location) => location.label).sort(
|
|
24978
|
+
locations: project.locations.map((location) => location.label).sort(compareText)
|
|
24900
24979
|
},
|
|
24901
24980
|
targets: [],
|
|
24902
24981
|
assignments: [],
|
|
@@ -25106,7 +25185,7 @@ function pageLimit(value) {
|
|
|
25106
25185
|
}
|
|
25107
25186
|
function paginate(rows, query, sortKey, searchable) {
|
|
25108
25187
|
const needle = query.search?.trim().toLowerCase();
|
|
25109
|
-
const filtered = (needle ? rows.filter((row) => searchable(row).toLowerCase().includes(needle)) : [...rows]).sort((left, right) =>
|
|
25188
|
+
const filtered = (needle ? rows.filter((row) => searchable(row).toLowerCase().includes(needle)) : [...rows]).sort((left, right) => compareText(sortKey(left), sortKey(right)));
|
|
25110
25189
|
const after = query.cursor ? Buffer.from(query.cursor, "base64url").toString("utf8") : null;
|
|
25111
25190
|
const remaining = after === null ? filtered : filtered.filter((row) => sortKey(row) > after);
|
|
25112
25191
|
const limit = pageLimit(query.limit);
|
|
@@ -26112,22 +26191,22 @@ function proposeMeasurementDiscoveryBindings(input) {
|
|
|
26112
26191
|
if (excluded.has(candidate.slug) || excluded.has(identity.toLowerCase())) continue;
|
|
26113
26192
|
const parts = parseMeasurementDiscoveryIdentity(identity);
|
|
26114
26193
|
if (!parts) continue;
|
|
26115
|
-
const candidates = missing.map((entry) => ({ targetKey: entry.target.stableKey, score: rebindScore(parts, entry.parts) })).filter((entry) => entry.score > MEASUREMENT_REBIND_THRESHOLD).sort((left, right) => right.score - left.score ||
|
|
26194
|
+
const candidates = missing.map((entry) => ({ targetKey: entry.target.stableKey, score: rebindScore(parts, entry.parts) })).filter((entry) => entry.score > MEASUREMENT_REBIND_THRESHOLD).sort((left, right) => right.score - left.score || compareText(left.targetKey, right.targetKey));
|
|
26116
26195
|
proposals.push({
|
|
26117
26196
|
discoveryIdentity: identity,
|
|
26118
26197
|
stableKey: candidate.stableKey,
|
|
26119
26198
|
slug: candidate.slug,
|
|
26120
26199
|
label: candidate.label,
|
|
26121
26200
|
discoveredUrl: candidate.primaryUrl,
|
|
26122
|
-
urlMatchers:
|
|
26201
|
+
urlMatchers: sortedUnique([candidate.primaryUrl, ...candidate.aliasCoverageUrls]),
|
|
26123
26202
|
kind: candidates.length === 1 ? "rebind" : candidates.length === 0 ? "new-target" : "ambiguous",
|
|
26124
26203
|
...candidates.length === 1 ? { rebindTargetKey: candidates[0].targetKey } : {},
|
|
26125
26204
|
candidates
|
|
26126
26205
|
});
|
|
26127
26206
|
}
|
|
26128
26207
|
return {
|
|
26129
|
-
proposals: proposals.sort((left, right) =>
|
|
26130
|
-
unchanged: input.targets.filter((target) => target.discoveryIdentity !== void 0 && present.has(target.discoveryIdentity)).map((target) => target.stableKey).sort(
|
|
26208
|
+
proposals: proposals.sort((left, right) => compareText(left.discoveryIdentity, right.discoveryIdentity)),
|
|
26209
|
+
unchanged: input.targets.filter((target) => target.discoveryIdentity !== void 0 && present.has(target.discoveryIdentity)).map((target) => target.stableKey).sort(compareText)
|
|
26131
26210
|
};
|
|
26132
26211
|
}
|
|
26133
26212
|
function rebindScore(left, right) {
|
|
@@ -26150,15 +26229,12 @@ function normalizePathTemplate(value) {
|
|
|
26150
26229
|
function hostOfUrl(value) {
|
|
26151
26230
|
return normalizeMeasurementHost(new URL(value).hostname);
|
|
26152
26231
|
}
|
|
26153
|
-
function compareText11(left, right) {
|
|
26154
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
26155
|
-
}
|
|
26156
26232
|
function canonicalJson2(value) {
|
|
26157
26233
|
const canonical = (input) => {
|
|
26158
26234
|
if (Array.isArray(input)) return input.map(canonical);
|
|
26159
26235
|
if (input && typeof input === "object") {
|
|
26160
26236
|
return Object.fromEntries(
|
|
26161
|
-
Object.entries(input).filter(([, child]) => child !== void 0).sort(([left], [right]) =>
|
|
26237
|
+
Object.entries(input).filter(([, child]) => child !== void 0).sort(([left], [right]) => compareText(left, right)).map(([key, child]) => [key, canonical(child)])
|
|
26162
26238
|
);
|
|
26163
26239
|
}
|
|
26164
26240
|
return input;
|
|
@@ -26173,20 +26249,10 @@ function measurementDiscoveryInputChecksum(input) {
|
|
|
26173
26249
|
compilerVersion: MEASUREMENT_DISCOVERY_COMPILER_VERSION,
|
|
26174
26250
|
sitemapUrl: input.sitemapUrl,
|
|
26175
26251
|
rule: input.rule,
|
|
26176
|
-
exclusions:
|
|
26252
|
+
exclusions: sortedUnique(input.exclusions.map((value) => value.trim().toLowerCase())),
|
|
26177
26253
|
bytesChecksum: input.bytesChecksum
|
|
26178
26254
|
}));
|
|
26179
26255
|
}
|
|
26180
|
-
function draftCounts2(authoring) {
|
|
26181
|
-
return {
|
|
26182
|
-
targets: authoring.targets.length,
|
|
26183
|
-
includedTargets: authoring.targets.filter((target) => target.status === "included").length,
|
|
26184
|
-
assignments: authoring.assignments.length,
|
|
26185
|
-
unclassifiedAssignments: authoring.assignments.filter((assignment) => assignment.queryClass === "unclassified").length,
|
|
26186
|
-
groups: authoring.groups.length,
|
|
26187
|
-
competitors: authoring.groups.reduce((total, group) => total + group.competitors.length, 0)
|
|
26188
|
-
};
|
|
26189
|
-
}
|
|
26190
26256
|
function loadDraft(app, projectId, projectName) {
|
|
26191
26257
|
const row = app.db.select().from(measurementPlanDrafts).where(eq32(measurementPlanDrafts.projectId, projectId)).get();
|
|
26192
26258
|
if (!row) throw notFound("Measurement plan draft", projectName);
|
|
@@ -26243,7 +26309,7 @@ function commitDraft(app, input) {
|
|
|
26243
26309
|
etag: measurementDraftEtag(etagVersion),
|
|
26244
26310
|
changed: input.changed,
|
|
26245
26311
|
warnings: input.warnings,
|
|
26246
|
-
counts:
|
|
26312
|
+
counts: draftCounts(authoring.data)
|
|
26247
26313
|
});
|
|
26248
26314
|
const actor = actorReference(input.request);
|
|
26249
26315
|
app.db.transaction((tx) => {
|
|
@@ -26346,7 +26412,7 @@ async function measurementDiscoveryV2Routes(app) {
|
|
|
26346
26412
|
if (!parsed.success) {
|
|
26347
26413
|
throw validationError("Invalid sitemap import request", { issues: parsed.error.issues });
|
|
26348
26414
|
}
|
|
26349
|
-
const exclusions =
|
|
26415
|
+
const exclusions = sortedUnique((parsed.data.exclusions ?? []).map((value) => value.trim().toLowerCase()));
|
|
26350
26416
|
const idempotencyKey = requireIdempotencyKey2(request, "import-sitemap");
|
|
26351
26417
|
const requestChecksum2 = sha2562(canonicalJson2({ ...parsed.data, exclusions }));
|
|
26352
26418
|
const replay = replayedResponse(app, project.id, "import-sitemap", idempotencyKey, requestChecksum2);
|
|
@@ -26491,7 +26557,7 @@ async function measurementDiscoveryV2Routes(app) {
|
|
|
26491
26557
|
const rebound = {
|
|
26492
26558
|
...existing,
|
|
26493
26559
|
...relabelled,
|
|
26494
|
-
urlMatchers:
|
|
26560
|
+
urlMatchers: sortedUnique([...existing.urlMatchers, ...proposal.urlMatchers]),
|
|
26495
26561
|
discoveredUrl: proposal.discoveredUrl,
|
|
26496
26562
|
discoveryIdentity: proposal.discoveryIdentity
|
|
26497
26563
|
};
|
|
@@ -26549,15 +26615,12 @@ function parseEvidenceQuery(raw) {
|
|
|
26549
26615
|
}
|
|
26550
26616
|
return parsed.data;
|
|
26551
26617
|
}
|
|
26552
|
-
function normalizedText2(value) {
|
|
26553
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
|
|
26554
|
-
}
|
|
26555
26618
|
function filterFingerprint(query) {
|
|
26556
26619
|
const filters = {
|
|
26557
26620
|
targetKey: query.targetKey,
|
|
26558
26621
|
queryClass: query.queryClass ?? "all",
|
|
26559
|
-
provider: query.provider === void 0 ? null :
|
|
26560
|
-
location: query.location === void 0 ? null :
|
|
26622
|
+
provider: query.provider === void 0 ? null : normalizeIdentityText(query.provider),
|
|
26623
|
+
location: query.location === void 0 ? null : normalizeIdentityText(query.location)
|
|
26561
26624
|
};
|
|
26562
26625
|
return createHash5("sha256").update(JSON.stringify(filters)).digest("base64url");
|
|
26563
26626
|
}
|
|
@@ -26619,7 +26682,7 @@ function propertyEvidenceRows(db, plan, active, run, query, queryClass) {
|
|
|
26619
26682
|
const manifest = measurementRunExpectedSlots(run, plan);
|
|
26620
26683
|
const { input, edgeQueryClass } = buildMeasurementPlanV2ReportInput(active.version.revision, plan, manifest, snapshots);
|
|
26621
26684
|
const ownEdgeIds = new Set(input.usageEdges.filter((edge) => edge.type === "target" && edge.targetId === query.targetKey).filter((edge) => queryClass === "all" || edgeQueryClass.get(edge.id) === queryClass).map((edge) => edge.id));
|
|
26622
|
-
const provider = query.provider === void 0 ? void 0 :
|
|
26685
|
+
const provider = query.provider === void 0 ? void 0 : normalizeIdentityText(query.provider);
|
|
26623
26686
|
const location = query.location === void 0 ? void 0 : normalizeMeasurementLocation(query.location);
|
|
26624
26687
|
const owned = (row) => ownEdgeIds.has(row.usageEdgeId) && (provider === void 0 || row.provider === provider) && (location === void 0 || normalizeMeasurementLocation(row.location) === location);
|
|
26625
26688
|
const built = buildMeasurementEvidence(input);
|
|
@@ -26734,12 +26797,6 @@ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
|
26734
26797
|
RunStatuses.partial
|
|
26735
26798
|
]);
|
|
26736
26799
|
var { rawResponse: omittedRawResponse, ...measurementQuestionSnapshotColumns } = getTableColumns(querySnapshots);
|
|
26737
|
-
function compareText12(left, right) {
|
|
26738
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
26739
|
-
}
|
|
26740
|
-
function normalizeText(value) {
|
|
26741
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
|
|
26742
|
-
}
|
|
26743
26800
|
function excerptOf(answer) {
|
|
26744
26801
|
const normalized = answer.normalize("NFKC").trim().replace(/\s+/g, " ");
|
|
26745
26802
|
return normalized.length <= 240 ? normalized : normalized.slice(0, 240);
|
|
@@ -26826,16 +26883,16 @@ function selectMeasurementQuestionRun(db, projectId, active, runId) {
|
|
|
26826
26883
|
return run;
|
|
26827
26884
|
}
|
|
26828
26885
|
function slotKey(executionId, provider) {
|
|
26829
|
-
return `${executionId}\0${
|
|
26886
|
+
return `${executionId}\0${normalizeIdentityText(provider)}`;
|
|
26830
26887
|
}
|
|
26831
26888
|
function scopedManifest(manifest, scope) {
|
|
26832
26889
|
if (scope === void 0) return manifest;
|
|
26833
26890
|
const executionIds = new Set(scope.executionIds);
|
|
26834
|
-
const provider = scope.provider === void 0 ? void 0 :
|
|
26891
|
+
const provider = scope.provider === void 0 ? void 0 : normalizeIdentityText(scope.provider);
|
|
26835
26892
|
const location = scope.location === void 0 ? void 0 : normalizeMeasurementLocation(scope.location);
|
|
26836
26893
|
return {
|
|
26837
26894
|
...manifest,
|
|
26838
|
-
expectedSlots: manifest.expectedSlots.filter((slot) => executionIds.has(slot.executionId) && (provider === void 0 ||
|
|
26895
|
+
expectedSlots: manifest.expectedSlots.filter((slot) => executionIds.has(slot.executionId) && (provider === void 0 || normalizeIdentityText(slot.provider) === provider) && (location === void 0 || normalizeMeasurementLocation(slot.context?.label ?? null) === location))
|
|
26839
26896
|
};
|
|
26840
26897
|
}
|
|
26841
26898
|
function selectedQuestionSnapshots(db, runId, manifest, scope) {
|
|
@@ -26846,7 +26903,7 @@ function selectedQuestionSnapshots(db, runId, manifest, scope) {
|
|
|
26846
26903
|
conditions.push(inArray15(querySnapshots.measurementExecutionId, executionIds));
|
|
26847
26904
|
}
|
|
26848
26905
|
if (scope?.provider !== void 0) {
|
|
26849
|
-
conditions.push(sql12`lower(trim(${querySnapshots.provider})) = ${
|
|
26906
|
+
conditions.push(sql12`lower(trim(${querySnapshots.provider})) = ${normalizeIdentityText(scope.provider)}`);
|
|
26850
26907
|
}
|
|
26851
26908
|
return db.select(measurementQuestionSnapshotColumns).from(querySnapshots).where(and26(...conditions)).all();
|
|
26852
26909
|
}
|
|
@@ -26921,7 +26978,7 @@ function questionRows(plan, target, materialized, filters = {}) {
|
|
|
26921
26978
|
for (const slot of materialized.input.expectedSlots) {
|
|
26922
26979
|
const assignment = assignmentsByExecution.get(slot.executionId);
|
|
26923
26980
|
if (!assignment) continue;
|
|
26924
|
-
if (filters.provider !== void 0 &&
|
|
26981
|
+
if (filters.provider !== void 0 && normalizeIdentityText(slot.provider) !== normalizeIdentityText(filters.provider)) continue;
|
|
26925
26982
|
if (filters.location !== void 0 && normalizeMeasurementLocation(slot.location) !== normalizeMeasurementLocation(filters.location)) continue;
|
|
26926
26983
|
const question = queryById.get(assignment.queryId);
|
|
26927
26984
|
const manifestSlot = materialized.manifestSlotsByIdentity.get(slotKey(slot.executionId, slot.provider));
|
|
@@ -26958,7 +27015,7 @@ function questionRows(plan, target, materialized, filters = {}) {
|
|
|
26958
27015
|
const key = `${row.queryId}\0${row.provider}\0${row.location ?? ""}`;
|
|
26959
27016
|
if (!deduped.has(key)) deduped.set(key, row);
|
|
26960
27017
|
}
|
|
26961
|
-
return [...deduped.values()].sort((left, right) =>
|
|
27018
|
+
return [...deduped.values()].sort((left, right) => compareText(left.queryId, right.queryId) || compareText(left.provider, right.provider) || compareText(left.location ?? "", right.location ?? "") || compareText(left.text, right.text));
|
|
26962
27019
|
}
|
|
26963
27020
|
function recommendedInstead(candidates, target) {
|
|
26964
27021
|
const ownNames = new Set([target.label, ...target.aliases].map(brandKeyFromText).filter((key) => key.length > 0));
|
|
@@ -27095,12 +27152,6 @@ async function measurementQuestionReadRoutes(app) {
|
|
|
27095
27152
|
|
|
27096
27153
|
// ../api-routes/src/measurement-portfolio-reads.ts
|
|
27097
27154
|
var DEFAULT_LIMIT3 = 10;
|
|
27098
|
-
function compareText13(left, right) {
|
|
27099
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
27100
|
-
}
|
|
27101
|
-
function normalizeText2(value) {
|
|
27102
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
|
|
27103
|
-
}
|
|
27104
27155
|
function parseLimitQuery(raw, schema, message) {
|
|
27105
27156
|
const candidate = { ...raw, ...raw.limit === void 0 ? {} : { limit: Number(raw.limit) } };
|
|
27106
27157
|
const parsed = schema.safeParse(candidate);
|
|
@@ -27173,9 +27224,9 @@ function materializeWithDb(db, active, plan, run) {
|
|
|
27173
27224
|
return materializeMeasurementQuestionRun(db, active, plan, run);
|
|
27174
27225
|
}
|
|
27175
27226
|
function filteredEvaluator(materialized, filters, targetKeys) {
|
|
27176
|
-
const provider = filters.provider === void 0 ? void 0 :
|
|
27227
|
+
const provider = filters.provider === void 0 ? void 0 : normalizeIdentityText(filters.provider);
|
|
27177
27228
|
const location = filters.location === void 0 ? void 0 : normalizeMeasurementLocation(filters.location);
|
|
27178
|
-
const expectedSlots = materialized.input.expectedSlots.filter((slot) => (provider === void 0 ||
|
|
27229
|
+
const expectedSlots = materialized.input.expectedSlots.filter((slot) => (provider === void 0 || normalizeIdentityText(slot.provider) === provider) && (location === void 0 || normalizeMeasurementLocation(slot.location) === location));
|
|
27179
27230
|
const usageEdges = materialized.input.usageEdges.filter((edge) => filters.queryClass === "all" || materialized.edgeQueryClass.get(edge.id) === filters.queryClass);
|
|
27180
27231
|
return createMeasurementOverviewEvaluator({
|
|
27181
27232
|
...materialized.input,
|
|
@@ -27230,12 +27281,12 @@ function createTargetAnswerReader(plan, materialized, filters) {
|
|
|
27230
27281
|
}
|
|
27231
27282
|
const incompleteObservationIds = new Set(materialized.evidence.diagnostics.evidenceIncompleteObservationIds);
|
|
27232
27283
|
const questionsById = new Map(plan.querySnapshots.map((question) => [question.queryId, question.queryText]));
|
|
27233
|
-
const provider = filters.provider === void 0 ? void 0 :
|
|
27284
|
+
const provider = filters.provider === void 0 ? void 0 : normalizeIdentityText(filters.provider);
|
|
27234
27285
|
const location = filters.location === void 0 ? void 0 : normalizeMeasurementLocation(filters.location);
|
|
27235
27286
|
const slotsByExecution = /* @__PURE__ */ new Map();
|
|
27236
27287
|
const slotOrder = /* @__PURE__ */ new Map();
|
|
27237
27288
|
materialized.input.expectedSlots.forEach((slot, index2) => {
|
|
27238
|
-
if (provider !== void 0 &&
|
|
27289
|
+
if (provider !== void 0 && normalizeIdentityText(slot.provider) !== provider) return;
|
|
27239
27290
|
if (location !== void 0 && normalizeMeasurementLocation(slot.location) !== location) return;
|
|
27240
27291
|
pushTo(slotsByExecution, slot.executionId, slot);
|
|
27241
27292
|
slotOrder.set(slot.id, index2);
|
|
@@ -27316,7 +27367,7 @@ function domainRows(answers, limit, readHost = createCitedHostReader()) {
|
|
|
27316
27367
|
const domains = new Set(answerDomains(answer, readHost).values());
|
|
27317
27368
|
for (const domain of domains) counts2.set(domain, (counts2.get(domain) ?? 0) + 1);
|
|
27318
27369
|
}
|
|
27319
|
-
const rows = [...counts2].map(([domain, count6]) => ({ domain, answers: count6 })).sort((left, right) => right.answers - left.answers ||
|
|
27370
|
+
const rows = [...counts2].map(([domain, count6]) => ({ domain, answers: count6 })).sort((left, right) => right.answers - left.answers || compareText(left.domain, right.domain));
|
|
27320
27371
|
return { rows: rows.slice(0, limit), total: rows.length };
|
|
27321
27372
|
}
|
|
27322
27373
|
function targetAliasKeys(target) {
|
|
@@ -27341,7 +27392,7 @@ function recommendationRows(target, population) {
|
|
|
27341
27392
|
forEachNameWrittenInstead(target, population.answers, (key, name, answer, firstInAnswer) => {
|
|
27342
27393
|
const existing = grouped.get(key);
|
|
27343
27394
|
if (existing) {
|
|
27344
|
-
if (
|
|
27395
|
+
if (compareText(name, existing.name) < 0) existing.name = name;
|
|
27345
27396
|
if (!firstInAnswer) return;
|
|
27346
27397
|
existing.occurrences++;
|
|
27347
27398
|
existing.providers.add(answer.provider);
|
|
@@ -27356,8 +27407,8 @@ function recommendationRows(target, population) {
|
|
|
27356
27407
|
}
|
|
27357
27408
|
});
|
|
27358
27409
|
return [...grouped.values()].map((row) => {
|
|
27359
|
-
const providers = [...row.providers].sort(
|
|
27360
|
-
const questions = [...row.questions].sort(
|
|
27410
|
+
const providers = [...row.providers].sort(compareText);
|
|
27411
|
+
const questions = [...row.questions].sort(compareText);
|
|
27361
27412
|
return {
|
|
27362
27413
|
name: row.name,
|
|
27363
27414
|
occurrences: row.occurrences,
|
|
@@ -27368,7 +27419,7 @@ function recommendationRows(target, population) {
|
|
|
27368
27419
|
questionTotal: questions.length,
|
|
27369
27420
|
questionsTruncated: questions.length > 5
|
|
27370
27421
|
};
|
|
27371
|
-
}).sort((left, right) => right.occurrences - left.occurrences ||
|
|
27422
|
+
}).sort((left, right) => right.occurrences - left.occurrences || compareText(left.name, right.name));
|
|
27372
27423
|
}
|
|
27373
27424
|
function namedInsteadFields(names, limit = MEASUREMENT_PORTFOLIO_ROW_EVIDENCE_LIMIT) {
|
|
27374
27425
|
const returned = names.slice(0, limit);
|
|
@@ -27388,7 +27439,7 @@ function compareWeakest(left, right) {
|
|
|
27388
27439
|
const rightUnavailable = right.mentionCoverage.state === "unavailable" || right.citationCoverage.state === "unavailable";
|
|
27389
27440
|
if (leftUnavailable !== rightUnavailable) return leftUnavailable ? 1 : -1;
|
|
27390
27441
|
if (leftUnavailable || rightUnavailable) {
|
|
27391
|
-
return
|
|
27442
|
+
return compareText(left.label, right.label) || compareText(left.targetKey, right.targetKey);
|
|
27392
27443
|
}
|
|
27393
27444
|
if (left.mentionCoverage.state === "available" && right.mentionCoverage.state === "available" && left.mentionCoverage.value !== right.mentionCoverage.value) {
|
|
27394
27445
|
return left.mentionCoverage.value - right.mentionCoverage.value;
|
|
@@ -27396,7 +27447,7 @@ function compareWeakest(left, right) {
|
|
|
27396
27447
|
if (left.citationCoverage.state === "available" && right.citationCoverage.state === "available" && left.citationCoverage.value !== right.citationCoverage.value) {
|
|
27397
27448
|
return left.citationCoverage.value - right.citationCoverage.value;
|
|
27398
27449
|
}
|
|
27399
|
-
return
|
|
27450
|
+
return compareText(left.label, right.label) || compareText(left.targetKey, right.targetKey);
|
|
27400
27451
|
}
|
|
27401
27452
|
function requireGroup2(plan, groupKey) {
|
|
27402
27453
|
const group = plan.groups.find((candidate) => candidate.stableKey === groupKey);
|
|
@@ -27498,14 +27549,14 @@ function compareWeakestMarket(a, b) {
|
|
|
27498
27549
|
const aUnavailable = a.mentionCoverage.state === "unavailable" || a.citationCoverage.state === "unavailable";
|
|
27499
27550
|
const bUnavailable = b.mentionCoverage.state === "unavailable" || b.citationCoverage.state === "unavailable";
|
|
27500
27551
|
if (aUnavailable !== bUnavailable) return aUnavailable ? 1 : -1;
|
|
27501
|
-
if (aUnavailable || bUnavailable) return
|
|
27552
|
+
if (aUnavailable || bUnavailable) return compareText(a.label, b.label) || compareText(a.groupKey, b.groupKey);
|
|
27502
27553
|
if (a.mentionCoverage.state === "available" && b.mentionCoverage.state === "available" && a.mentionCoverage.value !== b.mentionCoverage.value) {
|
|
27503
27554
|
return a.mentionCoverage.value - b.mentionCoverage.value;
|
|
27504
27555
|
}
|
|
27505
27556
|
if (a.citationCoverage.state === "available" && b.citationCoverage.state === "available" && a.citationCoverage.value !== b.citationCoverage.value) {
|
|
27506
27557
|
return a.citationCoverage.value - b.citationCoverage.value;
|
|
27507
27558
|
}
|
|
27508
|
-
return
|
|
27559
|
+
return compareText(a.label, b.label) || compareText(a.groupKey, b.groupKey);
|
|
27509
27560
|
}
|
|
27510
27561
|
function mentionRanking(rows, limit) {
|
|
27511
27562
|
const eligible = [];
|
|
@@ -27526,7 +27577,7 @@ function mentionRanking(rows, limit) {
|
|
|
27526
27577
|
});
|
|
27527
27578
|
}
|
|
27528
27579
|
}
|
|
27529
|
-
const byLabel = (a, b) =>
|
|
27580
|
+
const byLabel = (a, b) => compareText(a.label, b.label) || compareText(a.targetKey, b.targetKey);
|
|
27530
27581
|
const byRate = (direction) => (a, b) => direction * (a.mentionCoverage.value - b.mentionCoverage.value) || byLabel(a, b);
|
|
27531
27582
|
return {
|
|
27532
27583
|
eligiblePropertyCount: eligible.length,
|
|
@@ -27676,7 +27727,7 @@ function compactPortfolioPage(response, query, versionId, evidence, cursor) {
|
|
|
27676
27727
|
function portfolioAnswerEvidence(plan, materialized, filters, targetKeys) {
|
|
27677
27728
|
const targets = new Set(targetKeys);
|
|
27678
27729
|
const executions = new Set(plan.assignments.filter((assignment) => targets.has(assignment.targetKey) && (filters.queryClass === "all" || assignment.queryClass === filters.queryClass)).map((assignment) => assignment.executionNodeKey));
|
|
27679
|
-
const slots = materialized.input.expectedSlots.filter((slot) => executions.has(slot.executionId) && (filters.provider === void 0 ||
|
|
27730
|
+
const slots = materialized.input.expectedSlots.filter((slot) => executions.has(slot.executionId) && (filters.provider === void 0 || normalizeIdentityText(slot.provider) === normalizeIdentityText(filters.provider)) && (filters.location === void 0 || normalizeMeasurementLocation(slot.location) === normalizeMeasurementLocation(filters.location)));
|
|
27680
27731
|
const mentionsBySlot = /* @__PURE__ */ new Map();
|
|
27681
27732
|
for (const answer of materialized.evidence.answers) pushTo(mentionsBySlot, answer.expectedSlotId, answer.mentioned);
|
|
27682
27733
|
const selected = [];
|
|
@@ -27818,7 +27869,7 @@ function portfolioResponse(db, active, plan, query, ownDomains) {
|
|
|
27818
27869
|
},
|
|
27819
27870
|
measurement,
|
|
27820
27871
|
queryClass: query.queryClass,
|
|
27821
|
-
engines: [...engines].sort(
|
|
27872
|
+
engines: [...engines].sort(compareText),
|
|
27822
27873
|
metrics: {
|
|
27823
27874
|
propertiesMentioned: countMetric2(overview.propertiesMentioned),
|
|
27824
27875
|
propertiesNeverMentioned: neverMentionedMetric(overview.propertiesMentioned, targetKeys.length),
|
|
@@ -27885,7 +27936,7 @@ function tieByMetro(rows) {
|
|
|
27885
27936
|
add(row.metro);
|
|
27886
27937
|
for (const other of row.otherMetros ?? []) add(other);
|
|
27887
27938
|
}
|
|
27888
|
-
return [...counts2.values()].sort((left, right) => right.count - left.count || (left.metro === null ? 1 : 0) - (right.metro === null ? 1 : 0) ||
|
|
27939
|
+
return [...counts2.values()].sort((left, right) => right.count - left.count || (left.metro === null ? 1 : 0) - (right.metro === null ? 1 : 0) || compareText(left.metro?.label ?? "", right.metro?.label ?? "") || compareText(left.metro?.groupKey ?? "", right.metro?.groupKey ?? "")).map(({ metro, count: count6 }) => ({ metro: metro?.label ?? null, count: count6 }));
|
|
27889
27940
|
}
|
|
27890
27941
|
function tieNamedInstead(rows) {
|
|
27891
27942
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -27893,14 +27944,14 @@ function tieNamedInstead(rows) {
|
|
|
27893
27944
|
forEachNameWrittenInstead(target, population.answers, (key, name, answer) => {
|
|
27894
27945
|
const existing = grouped.get(key);
|
|
27895
27946
|
if (existing) {
|
|
27896
|
-
if (
|
|
27947
|
+
if (compareText(name, existing.name) < 0) existing.name = name;
|
|
27897
27948
|
existing.answers.add(answer.snapshot.id);
|
|
27898
27949
|
} else {
|
|
27899
27950
|
grouped.set(key, { name, answers: /* @__PURE__ */ new Set([answer.snapshot.id]) });
|
|
27900
27951
|
}
|
|
27901
27952
|
});
|
|
27902
27953
|
}
|
|
27903
|
-
const counted = [...grouped.values()].map((row) => ({ name: row.name, answers: row.answers.size })).sort((left, right) => right.answers - left.answers ||
|
|
27954
|
+
const counted = [...grouped.values()].map((row) => ({ name: row.name, answers: row.answers.size })).sort((left, right) => right.answers - left.answers || compareText(left.name, right.name));
|
|
27904
27955
|
return { rows: counted.slice(0, MEASUREMENT_PORTFOLIO_TIE_NAMED_INSTEAD_LIMIT), total: counted.length };
|
|
27905
27956
|
}
|
|
27906
27957
|
function weakestAnswerSources(rows, ownDomains) {
|
|
@@ -28007,10 +28058,10 @@ function scopeKey(run) {
|
|
|
28007
28058
|
if (run.measurementScope === null) return "full";
|
|
28008
28059
|
const scope = run.measurementScope;
|
|
28009
28060
|
return JSON.stringify({
|
|
28010
|
-
groups: [...scope.groups].sort(
|
|
28011
|
-
targets: [...scope.targets].sort(
|
|
28012
|
-
queries: [...scope.queries].sort(
|
|
28013
|
-
resolvedTargets: [...scope.resolvedTargets].sort(
|
|
28061
|
+
groups: [...scope.groups].sort(compareText),
|
|
28062
|
+
targets: [...scope.targets].sort(compareText),
|
|
28063
|
+
queries: [...scope.queries].sort(compareText),
|
|
28064
|
+
resolvedTargets: [...scope.resolvedTargets].sort(compareText)
|
|
28014
28065
|
});
|
|
28015
28066
|
}
|
|
28016
28067
|
function previousComparableRun(db, active, current) {
|
|
@@ -28142,7 +28193,7 @@ function classifyPropertyMove(mention, citation) {
|
|
|
28142
28193
|
};
|
|
28143
28194
|
}
|
|
28144
28195
|
function compareChangeLabel(left, right) {
|
|
28145
|
-
return
|
|
28196
|
+
return compareText(left.row.label, right.row.label) || compareText(left.row.targetKey, right.row.targetKey);
|
|
28146
28197
|
}
|
|
28147
28198
|
function compareChangeMagnitude(left, right) {
|
|
28148
28199
|
const larger = (move) => Math.max(move.mentionSize, move.citationSize);
|
|
@@ -28320,11 +28371,11 @@ function qualityResponse(db, active, plan, query) {
|
|
|
28320
28371
|
const materialized = materializeWithDb(db, active, plan, run);
|
|
28321
28372
|
const expected = materialized.manifest.expectedSlots.length;
|
|
28322
28373
|
const snapshots = materialized.snapshots;
|
|
28323
|
-
const expectedSlotKeys = new Set(materialized.manifest.expectedSlots.map((slot) => `${slot.executionId}\0${
|
|
28374
|
+
const expectedSlotKeys = new Set(materialized.manifest.expectedSlots.map((slot) => `${slot.executionId}\0${normalizeIdentityText(slot.provider)}`));
|
|
28324
28375
|
const observedSlotKeys = /* @__PURE__ */ new Set();
|
|
28325
28376
|
for (const snapshot of snapshots) {
|
|
28326
28377
|
if (snapshot.measurementExecutionId === null) throw new Error("stored result has no frozen execution identity");
|
|
28327
|
-
const key = `${snapshot.measurementExecutionId}\0${
|
|
28378
|
+
const key = `${snapshot.measurementExecutionId}\0${normalizeIdentityText(snapshot.provider)}`;
|
|
28328
28379
|
if (!expectedSlotKeys.has(key) || observedSlotKeys.has(key)) {
|
|
28329
28380
|
throw new Error("stored results do not map one-to-one to the frozen manifest");
|
|
28330
28381
|
}
|
|
@@ -28515,16 +28566,10 @@ var VisibilityReportScopeError = class extends Error {
|
|
|
28515
28566
|
}
|
|
28516
28567
|
};
|
|
28517
28568
|
var ALL_CLASSES = ["branded", "non-brand", "unknown"];
|
|
28518
|
-
function compareText14(left, right) {
|
|
28519
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
28520
|
-
}
|
|
28521
|
-
function normalizeText3(value) {
|
|
28522
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
|
|
28523
|
-
}
|
|
28524
28569
|
function locationMatches(location, selected) {
|
|
28525
28570
|
if (selected.kind === "all") return true;
|
|
28526
28571
|
if (selected.kind === "none") return location === null;
|
|
28527
|
-
return location !== null &&
|
|
28572
|
+
return location !== null && normalizeIdentityText(location) === normalizeIdentityText(selected.value);
|
|
28528
28573
|
}
|
|
28529
28574
|
function selectedClasses(value) {
|
|
28530
28575
|
return value === "all" ? ALL_CLASSES : [value];
|
|
@@ -28580,15 +28625,15 @@ function scopeResolution(definition, selection) {
|
|
|
28580
28625
|
}
|
|
28581
28626
|
function scopeTargetKeys(definition, selection) {
|
|
28582
28627
|
const resolution = scopeResolution(definition, selection);
|
|
28583
|
-
if (resolution.market !== void 0) return
|
|
28584
|
-
if (selection.scope === "project") return definition.targets.map((target) => target.id).sort(
|
|
28628
|
+
if (resolution.market !== void 0) return sortedUnique(definition.edges.filter((edge) => resolution.edgeIds.has(edge.id)).map((edge) => edge.targetKey));
|
|
28629
|
+
if (selection.scope === "project") return definition.targets.map((target) => target.id).sort(compareText);
|
|
28585
28630
|
if (selection.scope === "property") return [resolution.option.id];
|
|
28586
28631
|
if (selection.scope === "group") {
|
|
28587
28632
|
const group = definition.groups.find((candidate) => candidate.id === resolution.option.id);
|
|
28588
28633
|
if (!group) throw new VisibilityReportScopeError(`Group "${resolution.option.id}" is not in this frozen definition.`, { reason: VisibilityReportScopeErrorReasons["retired-scope"], kind: "group", key: resolution.option.id });
|
|
28589
|
-
return [...group.targetKeys].sort(
|
|
28634
|
+
return [...group.targetKeys].sort(compareText);
|
|
28590
28635
|
}
|
|
28591
|
-
return
|
|
28636
|
+
return sortedUnique(definition.edges.filter((edge) => resolution.edgeIds.has(edge.id)).map((edge) => edge.targetKey));
|
|
28592
28637
|
}
|
|
28593
28638
|
function observationIndex(run) {
|
|
28594
28639
|
const index2 = /* @__PURE__ */ new Map();
|
|
@@ -28624,16 +28669,16 @@ function candidatesFor(run, selection, queryClass) {
|
|
|
28624
28669
|
bySlot.set(slot.id, list);
|
|
28625
28670
|
}
|
|
28626
28671
|
}
|
|
28627
|
-
const provider = selection.provider === void 0 ? void 0 :
|
|
28628
|
-
const model = selection.model === void 0 ? void 0 :
|
|
28672
|
+
const provider = selection.provider === void 0 ? void 0 : normalizeIdentityText(selection.provider);
|
|
28673
|
+
const model = selection.model === void 0 ? void 0 : normalizeIdentityText(selection.model);
|
|
28629
28674
|
const candidates = [];
|
|
28630
28675
|
for (const [slotId, edges] of bySlot) {
|
|
28631
28676
|
const slot = slotsById.get(slotId);
|
|
28632
28677
|
if (!slot) throw new Error(`Frozen slot ${slotId} disappeared during reconstruction`);
|
|
28633
28678
|
const observation = observations.get(slot.id) ?? null;
|
|
28634
|
-
if (provider !== void 0 &&
|
|
28679
|
+
if (provider !== void 0 && normalizeIdentityText(slot.provider) !== provider) continue;
|
|
28635
28680
|
if (!locationMatches(slot.location, selection.location)) continue;
|
|
28636
|
-
if (model !== void 0 && (observation === null || observation.model === null ||
|
|
28681
|
+
if (model !== void 0 && (observation === null || observation.model === null || normalizeIdentityText(observation.model) !== model)) continue;
|
|
28637
28682
|
candidates.push({ run, slot, edges, observation });
|
|
28638
28683
|
}
|
|
28639
28684
|
return { resolution, candidates };
|
|
@@ -28702,7 +28747,7 @@ function candidateKey(candidate) {
|
|
|
28702
28747
|
return `${candidate.run.id}\0${candidate.slot.id}`;
|
|
28703
28748
|
}
|
|
28704
28749
|
function selectedTargetKeys(candidates) {
|
|
28705
|
-
return
|
|
28750
|
+
return sortedUnique(candidates.flatMap((candidate) => candidate.edges.map((edge) => edge.targetKey)));
|
|
28706
28751
|
}
|
|
28707
28752
|
function targetMetrics(candidates, targets, targetKey) {
|
|
28708
28753
|
const own = candidates.filter((candidate) => candidate.edges.some((edge) => edge.targetKey === targetKey));
|
|
@@ -28773,7 +28818,7 @@ function summary2(candidates, definition, populationTargetKeys = selectedTargetK
|
|
|
28773
28818
|
}
|
|
28774
28819
|
function queryRows(candidates, definition, selection) {
|
|
28775
28820
|
const targets = targetMap(definition);
|
|
28776
|
-
const narrowed = candidates.filter((candidate) => (selection.queryKey === void 0 || candidate.slot.queryKey === selection.queryKey) && (selection.queryId === void 0 || candidate.slot.queryId === selection.queryId) && (selection.search === void 0 ||
|
|
28821
|
+
const narrowed = candidates.filter((candidate) => (selection.queryKey === void 0 || candidate.slot.queryKey === selection.queryKey) && (selection.queryId === void 0 || candidate.slot.queryId === selection.queryId) && (selection.search === void 0 || normalizeIdentityText(candidate.slot.query).includes(normalizeIdentityText(selection.search))));
|
|
28777
28822
|
const groups = /* @__PURE__ */ new Map();
|
|
28778
28823
|
for (const candidate of narrowed) {
|
|
28779
28824
|
const model = candidate.observation?.model ?? null;
|
|
@@ -28786,7 +28831,7 @@ function queryRows(candidates, definition, selection) {
|
|
|
28786
28831
|
const first = rows[0];
|
|
28787
28832
|
const value = coverageSummary(rows, definition, targets);
|
|
28788
28833
|
return {
|
|
28789
|
-
sourceSnapshotIds:
|
|
28834
|
+
sourceSnapshotIds: sortedUnique(rows.flatMap((row) => row.observation ? [row.observation.answerId] : [])),
|
|
28790
28835
|
queryKey: first.slot.queryKey,
|
|
28791
28836
|
queryId: first.slot.queryId,
|
|
28792
28837
|
query: first.slot.query,
|
|
@@ -28795,14 +28840,14 @@ function queryRows(candidates, definition, selection) {
|
|
|
28795
28840
|
location: first.slot.location,
|
|
28796
28841
|
targetKeys: selectedTargetKeys(rows),
|
|
28797
28842
|
...(() => {
|
|
28798
|
-
const marketKeys =
|
|
28843
|
+
const marketKeys = sortedUnique(rows.flatMap((row) => row.edges.flatMap((edge) => edge.marketKeys)));
|
|
28799
28844
|
return marketKeys.length === 0 ? {} : { marketKeys };
|
|
28800
28845
|
})(),
|
|
28801
28846
|
answerCount: value.answerCount,
|
|
28802
28847
|
mentionCoverage: value.mentionCoverage,
|
|
28803
28848
|
citationCoverage: value.citationCoverage
|
|
28804
28849
|
};
|
|
28805
|
-
}).sort((left, right) => (selection.scope === "property" && selection.marketKey === void 0 ?
|
|
28850
|
+
}).sort((left, right) => (selection.scope === "property" && selection.marketKey === void 0 ? compareText((left.marketKeys ?? []).join("\0"), (right.marketKeys ?? []).join("\0")) : 0) || compareText(normalizeIdentityText(left.query), normalizeIdentityText(right.query)) || compareText(left.provider, right.provider) || compareText(left.model ?? "", right.model ?? "") || compareText(left.location ?? "", right.location ?? "") || compareText(left.queryKey, right.queryKey));
|
|
28806
28851
|
}
|
|
28807
28852
|
function evidenceRows(candidates, definition, selection) {
|
|
28808
28853
|
if (selection.queryKey === void 0 && selection.queryId === void 0) return [];
|
|
@@ -28830,10 +28875,10 @@ function evidenceRows(candidates, definition, selection) {
|
|
|
28830
28875
|
sources: [...observation.sources],
|
|
28831
28876
|
observedCompetitors: [...observation.observedCompetitorNames]
|
|
28832
28877
|
};
|
|
28833
|
-
}).sort((left, right) =>
|
|
28878
|
+
}).sort((left, right) => compareText(left.createdAt, right.createdAt) || compareText(left.answerId, right.answerId));
|
|
28834
28879
|
}
|
|
28835
28880
|
function competitorRows(candidates) {
|
|
28836
|
-
const domains =
|
|
28881
|
+
const domains = sortedUnique(candidates.flatMap((candidate) => candidate.edges.flatMap((edge) => edge.competitorDomains)));
|
|
28837
28882
|
return domains.map((domain) => {
|
|
28838
28883
|
const eligible = candidates.map((candidate) => {
|
|
28839
28884
|
const edges = candidate.edges.filter((edge) => edge.competitorDomains.includes(domain));
|
|
@@ -28864,7 +28909,7 @@ function observedCompetitorRows(candidates) {
|
|
|
28864
28909
|
answersByName.set(name, answers);
|
|
28865
28910
|
}
|
|
28866
28911
|
}
|
|
28867
|
-
return [...answersByName].map(([name, answers]) => ({ name, answerCount: answers.size })).sort((left, right) =>
|
|
28912
|
+
return [...answersByName].map(([name, answers]) => ({ name, answerCount: answers.size })).sort((left, right) => compareText(left.name, right.name));
|
|
28868
28913
|
}
|
|
28869
28914
|
function narrowedToTargetKeys(candidate, targetKeys) {
|
|
28870
28915
|
const edges = candidate.edges.filter((edge) => targetKeys.has(edge.targetKey));
|
|
@@ -28883,7 +28928,7 @@ function breakdown(candidates, definition, populationTargetKeys = selectedTarget
|
|
|
28883
28928
|
queryCount: new Set(own.map((candidate) => candidate.slot.queryKey)).size,
|
|
28884
28929
|
...metrics
|
|
28885
28930
|
};
|
|
28886
|
-
}).sort((left, right) =>
|
|
28931
|
+
}).sort((left, right) => compareText(left.label, right.label) || compareText(left.id, right.id));
|
|
28887
28932
|
const populationTargetSet = new Set(populationTargetKeys);
|
|
28888
28933
|
const groups = definition.groups.map((group) => ({ group, targetKeys: group.targetKeys.filter((targetKey) => populationTargetSet.has(targetKey)) })).filter(({ targetKeys }) => targetKeys.length > 0).map(({ group, targetKeys }) => {
|
|
28889
28934
|
const groupTargetKeys = new Set(targetKeys);
|
|
@@ -28897,7 +28942,7 @@ function breakdown(candidates, definition, populationTargetKeys = selectedTarget
|
|
|
28897
28942
|
mentionCoverage: metrics,
|
|
28898
28943
|
citationCoverage: citations
|
|
28899
28944
|
};
|
|
28900
|
-
}).sort((left, right) =>
|
|
28945
|
+
}).sort((left, right) => compareText(left.label, right.label) || compareText(left.id, right.id));
|
|
28901
28946
|
return { properties, groups };
|
|
28902
28947
|
}
|
|
28903
28948
|
function fingerprint(value) {
|
|
@@ -28974,16 +29019,16 @@ function filterOptions(definition, selection, run) {
|
|
|
28974
29019
|
const resolution = scopeResolution(definition, selection);
|
|
28975
29020
|
const slots = definition.slots.filter((slot) => definition.edges.some((edge) => edge.executionId === slot.executionId && resolution.edgeIds.has(edge.id)));
|
|
28976
29021
|
const observations = run ? observationIndex(run) : /* @__PURE__ */ new Map();
|
|
28977
|
-
const providers =
|
|
29022
|
+
const providers = sortedUnique(slots.map((slot) => slot.provider));
|
|
28978
29023
|
const models = [...new Map(slots.flatMap((slot) => {
|
|
28979
29024
|
const observation = observations.get(slot.id);
|
|
28980
29025
|
return observation?.model === null || observation === void 0 ? [] : [[`${slot.provider}\0${observation.model}`, { provider: slot.provider, model: observation.model }]];
|
|
28981
|
-
})).values()].sort((left, right) =>
|
|
29026
|
+
})).values()].sort((left, right) => compareText(left.provider, right.provider) || compareText(left.model, right.model));
|
|
28982
29027
|
const locations = [{ kind: "all" }];
|
|
28983
29028
|
const seen2 = /* @__PURE__ */ new Set(["all"]);
|
|
28984
29029
|
for (const slot of slots) {
|
|
28985
29030
|
const value = slot.location === null ? { kind: "none" } : { kind: "exact", value: slot.location };
|
|
28986
|
-
const key = value.kind === "exact" ? `exact:${
|
|
29031
|
+
const key = value.kind === "exact" ? `exact:${normalizeIdentityText(value.value)}` : value.kind;
|
|
28987
29032
|
if (!seen2.has(key)) {
|
|
28988
29033
|
seen2.add(key);
|
|
28989
29034
|
locations.push(value);
|
|
@@ -29074,7 +29119,7 @@ function emptyPopulation(queryClass, definition, input) {
|
|
|
29074
29119
|
};
|
|
29075
29120
|
}
|
|
29076
29121
|
function buildVisibilityReport(input) {
|
|
29077
|
-
const runs2 = input.runs.filter((run) => !run.probe && inTime(run, input.selection)).sort((left, right) =>
|
|
29122
|
+
const runs2 = input.runs.filter((run) => !run.probe && inTime(run, input.selection)).sort((left, right) => compareText(left.createdAt, right.createdAt) || compareText(left.id, right.id));
|
|
29078
29123
|
const selectedRun = input.selection.runId === void 0 ? input.preferredRunId === void 0 ? runs2.at(-1) : runs2.find((run) => run.id === input.preferredRunId) : runs2.find((run) => run.id === input.selection.runId);
|
|
29079
29124
|
const definition = selectedRun?.definition ?? input.activeDefinition;
|
|
29080
29125
|
const selectedResolution = scopeResolution(definition, input.selection);
|
|
@@ -29208,9 +29253,6 @@ function visibilityComparisonPopulation(run, selection) {
|
|
|
29208
29253
|
}
|
|
29209
29254
|
|
|
29210
29255
|
// ../api-routes/src/visibility-report.ts
|
|
29211
|
-
function normalizedText3(value) {
|
|
29212
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
|
|
29213
|
-
}
|
|
29214
29256
|
function observedCompetitorNames2(values) {
|
|
29215
29257
|
const names = [];
|
|
29216
29258
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -29294,7 +29336,7 @@ function competitorSignals(snapshot, matchers, preparedAnswerText = preparedAnsw
|
|
|
29294
29336
|
try {
|
|
29295
29337
|
return normalizeMeasurementHost(value);
|
|
29296
29338
|
} catch {
|
|
29297
|
-
return
|
|
29339
|
+
return normalizeIdentityText(value);
|
|
29298
29340
|
}
|
|
29299
29341
|
}));
|
|
29300
29342
|
const cited = [...matchers.keys()].filter((domain) => citedDomains.has(normalizeMeasurementHost(domain)));
|
|
@@ -29462,7 +29504,7 @@ function stableSimpleDefinitionId(definition) {
|
|
|
29462
29504
|
return `simple:${createHash8("sha256").update(JSON.stringify(source)).digest("hex")}`;
|
|
29463
29505
|
}
|
|
29464
29506
|
function simpleModelFingerprint(definition) {
|
|
29465
|
-
const engines = [...definition.engines].map((engine) => ({ provider:
|
|
29507
|
+
const engines = [...definition.engines].map((engine) => ({ provider: normalizeIdentityText(engine.provider), requestedModel: engine.requestedModel })).sort((left, right) => left.provider.localeCompare(right.provider, "en"));
|
|
29466
29508
|
return `simple-model:${createHash8("sha256").update(JSON.stringify(engines)).digest("hex")}`;
|
|
29467
29509
|
}
|
|
29468
29510
|
function frozenSimpleRun(run, definition, snapshots, includeEvidence = true) {
|
|
@@ -29479,21 +29521,21 @@ function frozenSimpleRun(run, definition, snapshots, includeEvidence = true) {
|
|
|
29479
29521
|
matches.push(query);
|
|
29480
29522
|
queriesByText.set(query.queryText, matches);
|
|
29481
29523
|
}
|
|
29482
|
-
const enginesByProvider = new Map(definition.engines.map((engine) => [
|
|
29524
|
+
const enginesByProvider = new Map(definition.engines.map((engine) => [normalizeIdentityText(engine.provider), engine]));
|
|
29483
29525
|
const competitors2 = definition.competitors === void 0 ? null : frozenCompetitorMatchers(definition.competitors);
|
|
29484
29526
|
const slots = definition.queries.flatMap((query) => definition.engines.map((engine) => ({
|
|
29485
|
-
id: `slot:simple:${query.queryId}:${
|
|
29527
|
+
id: `slot:simple:${query.queryId}:${normalizeIdentityText(engine.provider)}`,
|
|
29486
29528
|
executionId: `simple:${query.queryId}`,
|
|
29487
29529
|
queryKey: query.queryId,
|
|
29488
29530
|
queryId: query.queryId,
|
|
29489
29531
|
query: query.queryText,
|
|
29490
|
-
provider:
|
|
29532
|
+
provider: normalizeIdentityText(engine.provider),
|
|
29491
29533
|
location: definition.location?.label ?? null
|
|
29492
29534
|
})));
|
|
29493
29535
|
const slotKeys = new Set(slots.map((slot) => slot.id));
|
|
29494
29536
|
const observations = [];
|
|
29495
29537
|
for (const snapshot of snapshots) {
|
|
29496
|
-
const provider =
|
|
29538
|
+
const provider = normalizeIdentityText(snapshot.provider);
|
|
29497
29539
|
const engine = enginesByProvider.get(provider);
|
|
29498
29540
|
if (!engine) throw new Error(`Frozen simple definition has no provider ${snapshot.provider} for stored snapshot ${snapshot.id}`);
|
|
29499
29541
|
const query = snapshot.queryId === null ? (() => {
|
|
@@ -29568,7 +29610,7 @@ function legacySimpleRun(project, run, snapshots, includeEvidence = true) {
|
|
|
29568
29610
|
queryKey: snapshot.queryId ?? `legacy:${snapshot.queryText ?? snapshot.id}`,
|
|
29569
29611
|
queryId: snapshot.queryId,
|
|
29570
29612
|
query: snapshot.queryText ?? "",
|
|
29571
|
-
provider:
|
|
29613
|
+
provider: normalizeIdentityText(snapshot.provider),
|
|
29572
29614
|
location: snapshot.location
|
|
29573
29615
|
}));
|
|
29574
29616
|
return {
|
|
@@ -29937,17 +29979,11 @@ import { and as and29, desc as desc16, eq as eq37, inArray as inArray18, isNotNu
|
|
|
29937
29979
|
var SOURCE_LIMIT = 100;
|
|
29938
29980
|
var MAX_REVIEW_AGE_MS = 15 * 60 * 1e3;
|
|
29939
29981
|
var MAX_REVIEW_FUTURE_SKEW_MS = 60 * 1e3;
|
|
29940
|
-
function compareText15(left, right) {
|
|
29941
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
29942
|
-
}
|
|
29943
|
-
function normalizeText4(value) {
|
|
29944
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase("en");
|
|
29945
|
-
}
|
|
29946
29982
|
function unique2(values) {
|
|
29947
29983
|
return [...new Set(values)];
|
|
29948
29984
|
}
|
|
29949
|
-
function stableNewQueryId(projectId,
|
|
29950
|
-
return `qtc_${sha256Hex(`${projectId}\0${
|
|
29985
|
+
function stableNewQueryId(projectId, normalizedText) {
|
|
29986
|
+
return `qtc_${sha256Hex(`${projectId}\0${normalizedText}`).slice(0, 32)}`;
|
|
29951
29987
|
}
|
|
29952
29988
|
function providerNames(project, opts) {
|
|
29953
29989
|
return resolveRunProviderSelection({
|
|
@@ -29981,7 +30017,7 @@ function simpleDispatchEngines(project, opts) {
|
|
|
29981
30017
|
// override/configuration, just as it is in JobRunner, which also resolves
|
|
29982
30018
|
// a retired override to the id that answers now.
|
|
29983
30019
|
requestedModel: resolvedOverride(provider, overrides[provider]) ?? summaries.get(provider)?.model ?? null
|
|
29984
|
-
})).sort((left, right) =>
|
|
30020
|
+
})).sort((left, right) => compareText(left.provider, right.provider));
|
|
29985
30021
|
}
|
|
29986
30022
|
function resolvedOverride(provider, model) {
|
|
29987
30023
|
return model === void 0 ? void 0 : resolveProviderModel(provider, model);
|
|
@@ -29992,12 +30028,12 @@ function simpleDefaultLocation(project) {
|
|
|
29992
30028
|
}
|
|
29993
30029
|
function contextKey(context) {
|
|
29994
30030
|
const location = context.location ? [context.location.label, context.location.city, context.location.region, context.location.country, context.location.timezone ?? ""].join("\0") : "";
|
|
29995
|
-
return canonicalJson({ providers: [...context.providers].sort(
|
|
30031
|
+
return canonicalJson({ providers: [...context.providers].sort(compareText), models: context.models, location });
|
|
29996
30032
|
}
|
|
29997
30033
|
function uniqueContexts(contexts) {
|
|
29998
30034
|
const byKey = /* @__PURE__ */ new Map();
|
|
29999
30035
|
for (const context of contexts) byKey.set(contextKey(context), context);
|
|
30000
|
-
return [...byKey.values()].sort((left, right) =>
|
|
30036
|
+
return [...byKey.values()].sort((left, right) => compareText(contextKey(left), contextKey(right)));
|
|
30001
30037
|
}
|
|
30002
30038
|
function defaultContexts(project, opts, mode) {
|
|
30003
30039
|
const providers = providerNames(project, opts);
|
|
@@ -30028,7 +30064,7 @@ function workspaceFingerprint(project, active, queryRows2) {
|
|
|
30028
30064
|
defaultLocation: project.defaultLocation
|
|
30029
30065
|
},
|
|
30030
30066
|
active: active ? { id: active.version.id, revision: active.version.revision, compiledChecksum: active.version.compiledChecksum } : null,
|
|
30031
|
-
queries: queryRows2.map((row) => ({ id: row.id, query: row.query, provenance: row.provenance })).sort((left, right) =>
|
|
30067
|
+
queries: queryRows2.map((row) => ({ id: row.id, query: row.query, provenance: row.provenance })).sort((left, right) => compareText(left.id, right.id))
|
|
30032
30068
|
}))}`;
|
|
30033
30069
|
}
|
|
30034
30070
|
function readWorkspace(db, project) {
|
|
@@ -30065,9 +30101,9 @@ function simpleExecutionSignature(input) {
|
|
|
30065
30101
|
return canonicalJson({
|
|
30066
30102
|
identity: {
|
|
30067
30103
|
displayName: input.identity.displayName,
|
|
30068
|
-
aliases: [...input.identity.aliases].sort(
|
|
30104
|
+
aliases: [...input.identity.aliases].sort(compareText),
|
|
30069
30105
|
canonicalDomain: input.identity.canonicalDomain,
|
|
30070
|
-
ownedDomains: [...input.identity.ownedDomains].sort(
|
|
30106
|
+
ownedDomains: [...input.identity.ownedDomains].sort(compareText)
|
|
30071
30107
|
},
|
|
30072
30108
|
country: input.country,
|
|
30073
30109
|
language: input.language,
|
|
@@ -30075,7 +30111,7 @@ function simpleExecutionSignature(input) {
|
|
|
30075
30111
|
engines: input.engines.map((engine) => ({
|
|
30076
30112
|
provider: engine.provider.trim().toLocaleLowerCase("en"),
|
|
30077
30113
|
requestedModel: engine.requestedModel
|
|
30078
|
-
})).sort((left, right) =>
|
|
30114
|
+
})).sort((left, right) => compareText(left.provider, right.provider))
|
|
30079
30115
|
});
|
|
30080
30116
|
}
|
|
30081
30117
|
function currentSimpleExecutionSignature(project, opts) {
|
|
@@ -30338,7 +30374,7 @@ function measuredAtByQuery(db, state, opts) {
|
|
|
30338
30374
|
for (const [queryId, signatures] of activeSignatures) {
|
|
30339
30375
|
const measured = measurements.get(queryId);
|
|
30340
30376
|
if (!measured || [...signatures].some((signature) => !measured.has(signature))) continue;
|
|
30341
|
-
const latest = [...measured.values()].sort(
|
|
30377
|
+
const latest = [...measured.values()].sort(compareText).at(-1);
|
|
30342
30378
|
if (latest) result.set(queryId, latest);
|
|
30343
30379
|
}
|
|
30344
30380
|
return result;
|
|
@@ -30353,7 +30389,7 @@ function marketKeysByEdge(plan) {
|
|
|
30353
30389
|
else result.set(key, [scope.stableKey]);
|
|
30354
30390
|
}
|
|
30355
30391
|
}
|
|
30356
|
-
for (const keys of result.values()) keys.sort(
|
|
30392
|
+
for (const keys of result.values()) keys.sort(compareText);
|
|
30357
30393
|
return result;
|
|
30358
30394
|
}
|
|
30359
30395
|
function trackedRows(db, state, queryRows2, plan, opts) {
|
|
@@ -30362,12 +30398,12 @@ function trackedRows(db, state, queryRows2, plan, opts) {
|
|
|
30362
30398
|
return [...queryRows2.values()].map((row) => ({
|
|
30363
30399
|
queryId: row.id,
|
|
30364
30400
|
queryText: row.query,
|
|
30365
|
-
normalizedText:
|
|
30401
|
+
normalizedText: normalizeIdentityText(row.query),
|
|
30366
30402
|
provenance: parseQueryProvenance(row.provenance),
|
|
30367
30403
|
state: measured.has(row.id) ? "tracked" : "awaiting-sweep",
|
|
30368
30404
|
lastMeasuredAt: measured.get(row.id) ?? null,
|
|
30369
30405
|
assignments: []
|
|
30370
|
-
})).sort((left, right) =>
|
|
30406
|
+
})).sort((left, right) => compareText(left.normalizedText, right.normalizedText) || compareText(left.queryId, right.queryId));
|
|
30371
30407
|
}
|
|
30372
30408
|
const snapshots = new Map(plan.querySnapshots.map((snapshot) => [snapshot.queryId, snapshot]));
|
|
30373
30409
|
const allIds = unique2([...queryRows2.keys(), ...snapshots.keys()]);
|
|
@@ -30405,7 +30441,7 @@ function trackedRows(db, state, queryRows2, plan, opts) {
|
|
|
30405
30441
|
} else {
|
|
30406
30442
|
assignments.set(assignment.targetKey, {
|
|
30407
30443
|
targetKey: assignment.targetKey,
|
|
30408
|
-
groupKeys: [...groupsByTarget.get(assignment.targetKey) ?? []].sort(
|
|
30444
|
+
groupKeys: [...groupsByTarget.get(assignment.targetKey) ?? []].sort(compareText),
|
|
30409
30445
|
marketKeys: [...marketsByEdge.get(edgeKey2) ?? []],
|
|
30410
30446
|
queryClass: assignment.queryClass,
|
|
30411
30447
|
classificationSource: source,
|
|
@@ -30416,17 +30452,17 @@ function trackedRows(db, state, queryRows2, plan, opts) {
|
|
|
30416
30452
|
return {
|
|
30417
30453
|
queryId,
|
|
30418
30454
|
queryText,
|
|
30419
|
-
normalizedText:
|
|
30455
|
+
normalizedText: normalizeIdentityText(queryText),
|
|
30420
30456
|
provenance: snapshot ? provenanceFromPlan(snapshot.provenance) : parseQueryProvenance(row?.provenance ?? null),
|
|
30421
30457
|
state: measured.has(queryId) ? "tracked" : "awaiting-sweep",
|
|
30422
30458
|
lastMeasuredAt: measured.get(queryId) ?? null,
|
|
30423
30459
|
assignments: [...assignments.values()].map((assignment) => ({
|
|
30424
30460
|
...assignment,
|
|
30425
|
-
marketKeys: unique2(assignment.marketKeys).sort(
|
|
30461
|
+
marketKeys: unique2(assignment.marketKeys).sort(compareText),
|
|
30426
30462
|
contexts: uniqueContexts(assignment.contexts)
|
|
30427
|
-
})).sort((left, right) =>
|
|
30463
|
+
})).sort((left, right) => compareText(left.targetKey, right.targetKey))
|
|
30428
30464
|
};
|
|
30429
|
-
}).sort((left, right) =>
|
|
30465
|
+
}).sort((left, right) => compareText(left.normalizedText, right.normalizedText) || compareText(left.queryId, right.queryId));
|
|
30430
30466
|
}
|
|
30431
30467
|
function savedSources(db, projectId) {
|
|
30432
30468
|
const research = db.select({
|
|
@@ -30509,9 +30545,9 @@ function resolveAudience(plan, input) {
|
|
|
30509
30545
|
throw validationError("Select at least one Property, group, or market for a portfolio query.");
|
|
30510
30546
|
}
|
|
30511
30547
|
return {
|
|
30512
|
-
targetKeys: [...targetKeys].sort(
|
|
30513
|
-
groupKeys: groupKeys.sort(
|
|
30514
|
-
marketKeys: marketKeys.sort(
|
|
30548
|
+
targetKeys: [...targetKeys].sort(compareText),
|
|
30549
|
+
groupKeys: groupKeys.sort(compareText),
|
|
30550
|
+
marketKeys: marketKeys.sort(compareText)
|
|
30515
30551
|
};
|
|
30516
30552
|
}
|
|
30517
30553
|
function contextsFromInput(project, contexts) {
|
|
@@ -30522,7 +30558,7 @@ function contextsFromInput(project, contexts) {
|
|
|
30522
30558
|
if (context.location !== null && !location) {
|
|
30523
30559
|
throw validationError(`Execution context names a location the project does not configure: ${context.location}`);
|
|
30524
30560
|
}
|
|
30525
|
-
const providers = unique2(context.providers.map((provider) => provider.trim().toLocaleLowerCase("en")).filter(Boolean)).sort(
|
|
30561
|
+
const providers = unique2(context.providers.map((provider) => provider.trim().toLocaleLowerCase("en")).filter(Boolean)).sort(compareText);
|
|
30526
30562
|
for (const provider of Object.keys(context.models)) {
|
|
30527
30563
|
if (!providers.includes(provider.trim().toLocaleLowerCase("en"))) {
|
|
30528
30564
|
throw validationError(`Model "${provider}" is not selected by this execution context.`);
|
|
@@ -30747,23 +30783,23 @@ function edgeKey(edge) {
|
|
|
30747
30783
|
function queryIdsByNormalized(state) {
|
|
30748
30784
|
const result = /* @__PURE__ */ new Map();
|
|
30749
30785
|
for (const row of state.queryRows) {
|
|
30750
|
-
const key =
|
|
30786
|
+
const key = normalizeIdentityText(row.query);
|
|
30751
30787
|
const ids = result.get(key);
|
|
30752
30788
|
if (ids) ids.push(row.id);
|
|
30753
30789
|
else result.set(key, [row.id]);
|
|
30754
30790
|
}
|
|
30755
30791
|
for (const snapshot of state.active?.plan.querySnapshots ?? []) {
|
|
30756
|
-
const key =
|
|
30792
|
+
const key = normalizeIdentityText(snapshot.queryText);
|
|
30757
30793
|
const ids = result.get(key);
|
|
30758
30794
|
if (ids) ids.push(snapshot.queryId);
|
|
30759
30795
|
else result.set(key, [snapshot.queryId]);
|
|
30760
30796
|
}
|
|
30761
|
-
for (const ids of result.values()) ids.sort(
|
|
30797
|
+
for (const ids of result.values()) ids.sort(compareText);
|
|
30762
30798
|
return result;
|
|
30763
30799
|
}
|
|
30764
|
-
function preferredQueryId(state,
|
|
30765
|
-
const candidates = queryIdsByNormalized(state).get(
|
|
30766
|
-
const bound = new Set((state.active?.plan.querySnapshots ?? []).filter((snapshot) =>
|
|
30800
|
+
function preferredQueryId(state, normalizedText) {
|
|
30801
|
+
const candidates = queryIdsByNormalized(state).get(normalizedText) ?? [];
|
|
30802
|
+
const bound = new Set((state.active?.plan.querySnapshots ?? []).filter((snapshot) => normalizeIdentityText(snapshot.queryText) === normalizedText).map((snapshot) => snapshot.queryId));
|
|
30767
30803
|
return candidates.find((candidate) => bound.has(candidate)) ?? candidates.at(0) ?? null;
|
|
30768
30804
|
}
|
|
30769
30805
|
function rowsEqual(before, after) {
|
|
@@ -30785,7 +30821,7 @@ function changeRows(ids, rows, plan, fallbackPlan = null) {
|
|
|
30785
30821
|
queryText,
|
|
30786
30822
|
assignmentCount: (plan ?? fallbackPlan)?.assignments.filter((assignment) => assignment.queryId === queryId).length ?? 0
|
|
30787
30823
|
}];
|
|
30788
|
-
}).sort((left, right) =>
|
|
30824
|
+
}).sort((left, right) => compareText(left.queryText, right.queryText) || compareText(left.queryId, right.queryId));
|
|
30789
30825
|
}
|
|
30790
30826
|
function planSnapshot(plan, queryId) {
|
|
30791
30827
|
return plan?.querySnapshots.find((snapshot) => snapshot.queryId === queryId) ?? null;
|
|
@@ -30793,26 +30829,26 @@ function planSnapshot(plan, queryId) {
|
|
|
30793
30829
|
function displayIds(rows, plan) {
|
|
30794
30830
|
return /* @__PURE__ */ new Set([...rows.keys(), ...plan?.querySnapshots.map((snapshot) => snapshot.queryId) ?? []]);
|
|
30795
30831
|
}
|
|
30796
|
-
function candidateIdsForText(state, plan, rows,
|
|
30832
|
+
function candidateIdsForText(state, plan, rows, normalizedText) {
|
|
30797
30833
|
const ids = /* @__PURE__ */ new Set();
|
|
30798
30834
|
for (const row of rows.values()) {
|
|
30799
|
-
if (
|
|
30835
|
+
if (normalizeIdentityText(row.query) === normalizedText) ids.add(row.id);
|
|
30800
30836
|
}
|
|
30801
30837
|
for (const snapshot of plan?.querySnapshots ?? []) {
|
|
30802
|
-
if (
|
|
30838
|
+
if (normalizeIdentityText(snapshot.queryText) === normalizedText) ids.add(snapshot.queryId);
|
|
30803
30839
|
}
|
|
30804
|
-
const activeId = preferredQueryId(state,
|
|
30840
|
+
const activeId = preferredQueryId(state, normalizedText);
|
|
30805
30841
|
return [...ids].sort((left, right) => {
|
|
30806
30842
|
if (left === activeId) return -1;
|
|
30807
30843
|
if (right === activeId) return 1;
|
|
30808
|
-
return
|
|
30844
|
+
return compareText(left, right);
|
|
30809
30845
|
});
|
|
30810
30846
|
}
|
|
30811
30847
|
function resolveMutationQueryId(state, plan, rows, removal) {
|
|
30812
30848
|
if (removal.queryId !== void 0) {
|
|
30813
30849
|
return rows.has(removal.queryId) || planSnapshot(plan, removal.queryId) !== null ? removal.queryId : null;
|
|
30814
30850
|
}
|
|
30815
|
-
const ids = candidateIdsForText(state, plan, rows,
|
|
30851
|
+
const ids = candidateIdsForText(state, plan, rows, normalizeIdentityText(removal.queryText));
|
|
30816
30852
|
return ids[0] ?? null;
|
|
30817
30853
|
}
|
|
30818
30854
|
function queryTextForId(state, plan, rows, queryId, fallback) {
|
|
@@ -30909,7 +30945,7 @@ function simpleWorkload(project, rows, opts) {
|
|
|
30909
30945
|
const result = /* @__PURE__ */ new Map();
|
|
30910
30946
|
for (const context of defaultContexts(project, opts, "simple")) {
|
|
30911
30947
|
for (const row of rows.values()) {
|
|
30912
|
-
result.set(`${
|
|
30948
|
+
result.set(`${normalizeIdentityText(row.query)}\0${contextKey(context)}`, context.providers.length);
|
|
30913
30949
|
}
|
|
30914
30950
|
}
|
|
30915
30951
|
return result;
|
|
@@ -30979,7 +31015,7 @@ function mutationPreviewToken(candidate, mutation, reviewedAt) {
|
|
|
30979
31015
|
mutation: { additions: mutation.additions, removals: mutation.removals, ...mutation.edits ? { edits: mutation.edits } : {} },
|
|
30980
31016
|
reviewedAt,
|
|
30981
31017
|
plan: document,
|
|
30982
|
-
queries: [...candidate.queryRows.values()].map((row) => ({ id: row.id, query: row.query, provenance: row.provenance })).sort((left, right) =>
|
|
31018
|
+
queries: [...candidate.queryRows.values()].map((row) => ({ id: row.id, query: row.query, provenance: row.provenance })).sort((left, right) => compareText(left.id, right.id))
|
|
30983
31019
|
}))}`;
|
|
30984
31020
|
}
|
|
30985
31021
|
function assertReviewedAt(reviewedAt) {
|
|
@@ -30996,7 +31032,7 @@ function assertSimpleAddition(addition) {
|
|
|
30996
31032
|
}
|
|
30997
31033
|
}
|
|
30998
31034
|
function addSimpleSource(state, rows, source, reusedQueryIds) {
|
|
30999
|
-
const normalized =
|
|
31035
|
+
const normalized = normalizeIdentityText(source.queryText);
|
|
31000
31036
|
const existingId = candidateIdsForText(state, null, rows, normalized).at(0) ?? preferredQueryId(state, normalized) ?? stableNewQueryId(state.project.id, normalized);
|
|
31001
31037
|
const existing = rows.get(existingId);
|
|
31002
31038
|
if (existing) {
|
|
@@ -31026,7 +31062,7 @@ function buildSimpleCandidate(db, state, mutation, opts, manualCapturedAt) {
|
|
|
31026
31062
|
}
|
|
31027
31063
|
const existing = rows.get(edit.queryId);
|
|
31028
31064
|
if (!existing) throw notFound("Tracked query", edit.queryId);
|
|
31029
|
-
if (edit.text === void 0 ||
|
|
31065
|
+
if (edit.text === void 0 || normalizeIdentityText(edit.text) === normalizeIdentityText(existing.query)) continue;
|
|
31030
31066
|
rows.delete(edit.queryId);
|
|
31031
31067
|
mutatedQueryIds.add(edit.queryId);
|
|
31032
31068
|
addSimpleSource(state, rows, {
|
|
@@ -31070,7 +31106,7 @@ function buildSimpleCandidate(db, state, mutation, opts, manualCapturedAt) {
|
|
|
31070
31106
|
};
|
|
31071
31107
|
}
|
|
31072
31108
|
function addAdvancedSource(state, plan, rows, addition, source, reusedQueryIds, mutatedQueryIds) {
|
|
31073
|
-
const normalized =
|
|
31109
|
+
const normalized = normalizeIdentityText(source.queryText);
|
|
31074
31110
|
const queryId = candidateIdsForText(state, plan, rows, normalized).at(0) ?? preferredQueryId(state, normalized) ?? stableNewQueryId(state.project.id, normalized);
|
|
31075
31111
|
const knownBefore = state.queryRows.some((row) => row.id === queryId) || planSnapshot(state.active?.plan ?? null, queryId) !== null;
|
|
31076
31112
|
const queryText = queryTextForId(state, plan, rows, queryId, source.queryText.trim());
|
|
@@ -31146,7 +31182,7 @@ function applyAdvancedEdit(state, plan, rows, edit, capturedAt, reusedQueryIds,
|
|
|
31146
31182
|
if (!snapshot && !row) throw notFound("Tracked query", edit.queryId);
|
|
31147
31183
|
const oldText = snapshot?.queryText ?? row.query;
|
|
31148
31184
|
const text2 = edit.text ?? oldText;
|
|
31149
|
-
const textChanged =
|
|
31185
|
+
const textChanged = normalizeIdentityText(text2) !== normalizeIdentityText(oldText);
|
|
31150
31186
|
const audience = edit.audience === void 0 ? null : resolveAudience(requireAdvanced(state).plan, edit.audience);
|
|
31151
31187
|
const targetKeys = audience ? new Set(audience.targetKeys) : null;
|
|
31152
31188
|
const marketKeys = new Set(audience?.marketKeys ?? []);
|
|
@@ -31180,7 +31216,7 @@ function applyAdvancedEdit(state, plan, rows, edit, capturedAt, reusedQueryIds,
|
|
|
31180
31216
|
}
|
|
31181
31217
|
return plan;
|
|
31182
31218
|
}
|
|
31183
|
-
const newQueryId = candidateIdsForText(state, plan, rows,
|
|
31219
|
+
const newQueryId = candidateIdsForText(state, plan, rows, normalizeIdentityText(text2)).at(0) ?? preferredQueryId(state, normalizeIdentityText(text2)) ?? stableNewQueryId(state.project.id, normalizeIdentityText(text2));
|
|
31184
31220
|
const queryText = queryTextForId(state, plan, rows, newQueryId, text2.trim());
|
|
31185
31221
|
const provenance = provenanceForId(state, plan, rows, newQueryId, { source: "manual", sourceId: null, capturedAt });
|
|
31186
31222
|
if (!rows.has(newQueryId)) rows.set(newQueryId, { id: newQueryId, query: queryText, provenance: serializeQueryProvenance(provenance) });
|
|
@@ -31281,7 +31317,7 @@ function writeSimpleCandidate(tx, candidate, now) {
|
|
|
31281
31317
|
for (const queryId of candidate.createdQueryIds) {
|
|
31282
31318
|
const planned = candidate.queryRows.get(queryId);
|
|
31283
31319
|
if (!planned) continue;
|
|
31284
|
-
const row = persisted.find((candidateRow) =>
|
|
31320
|
+
const row = persisted.find((candidateRow) => normalizeIdentityText(candidateRow.query) === normalizeIdentityText(planned.query));
|
|
31285
31321
|
if (!row) continue;
|
|
31286
31322
|
if (row.id !== planned.id) tx.update(queries).set({ id: planned.id }).where(eq37(queries.id, row.id)).run();
|
|
31287
31323
|
if (planned.provenance) tx.update(queries).set({ provenance: planned.provenance }).where(eq37(queries.id, planned.id)).run();
|
|
@@ -31616,7 +31652,7 @@ import crypto24 from "crypto";
|
|
|
31616
31652
|
import dns2 from "dns/promises";
|
|
31617
31653
|
import http2 from "http";
|
|
31618
31654
|
import https2 from "https";
|
|
31619
|
-
import
|
|
31655
|
+
import net3 from "net";
|
|
31620
31656
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
31621
31657
|
async function resolveWebhookTarget(raw, options = {}) {
|
|
31622
31658
|
let parsed;
|
|
@@ -31631,13 +31667,13 @@ async function resolveWebhookTarget(raw, options = {}) {
|
|
|
31631
31667
|
if (parsed.username || parsed.password) {
|
|
31632
31668
|
return { ok: false, message: '"url" must not include credentials' };
|
|
31633
31669
|
}
|
|
31634
|
-
const lookupHost =
|
|
31670
|
+
const lookupHost = stripIpv6Brackets(parsed.hostname);
|
|
31635
31671
|
if (!lookupHost) {
|
|
31636
31672
|
return { ok: false, message: '"url" must include a hostname' };
|
|
31637
31673
|
}
|
|
31638
|
-
const addresses = await resolveHostAddresses(lookupHost);
|
|
31674
|
+
const addresses = await resolveHostAddresses(lookupHost, options.resolveAddresses);
|
|
31639
31675
|
if (addresses.length === 0) {
|
|
31640
|
-
return { ok: false, message: '"url" hostname could not be resolved' };
|
|
31676
|
+
return { ok: false, message: '"url" hostname could not be resolved', unresolved: true };
|
|
31641
31677
|
}
|
|
31642
31678
|
const blocked = addresses.find((entry) => isBlockedAddress(entry.address, options));
|
|
31643
31679
|
if (blocked) {
|
|
@@ -31648,7 +31684,8 @@ async function resolveWebhookTarget(raw, options = {}) {
|
|
|
31648
31684
|
target: {
|
|
31649
31685
|
url: parsed,
|
|
31650
31686
|
address: addresses[0].address,
|
|
31651
|
-
family: addresses[0].family
|
|
31687
|
+
family: addresses[0].family,
|
|
31688
|
+
addresses
|
|
31652
31689
|
}
|
|
31653
31690
|
};
|
|
31654
31691
|
}
|
|
@@ -31668,16 +31705,23 @@ async function deliverWebhook(target, payload, webhookSecret) {
|
|
|
31668
31705
|
}
|
|
31669
31706
|
return await new Promise((resolve) => {
|
|
31670
31707
|
const requestOptions = {
|
|
31671
|
-
|
|
31708
|
+
// A connection of its own: a shared agent pools by hostname, so it could
|
|
31709
|
+
// hand this request a socket some other caller dialed unchecked.
|
|
31710
|
+
agent: false,
|
|
31711
|
+
// Dial the checked addresses, falling back from one to the next, so a
|
|
31712
|
+
// receiver that listens on only one of them (`localhost` checks as
|
|
31713
|
+
// 127.0.0.1 and ::1) still gets the delivery.
|
|
31714
|
+
autoSelectFamily: true,
|
|
31715
|
+
lookup: pinnedLookup(checkedAddresses(target)),
|
|
31672
31716
|
headers,
|
|
31673
|
-
hostname: target.
|
|
31717
|
+
hostname: stripIpv6Brackets(target.url.hostname),
|
|
31674
31718
|
method: "POST",
|
|
31675
31719
|
path: path8,
|
|
31676
31720
|
port,
|
|
31677
31721
|
timeout: REQUEST_TIMEOUT_MS
|
|
31678
31722
|
};
|
|
31679
31723
|
if (isHttps) {
|
|
31680
|
-
requestOptions.servername =
|
|
31724
|
+
requestOptions.servername = stripIpv6Brackets(target.url.hostname);
|
|
31681
31725
|
}
|
|
31682
31726
|
const request = (isHttps ? https2.request : http2.request)(requestOptions, (response) => {
|
|
31683
31727
|
response.resume();
|
|
@@ -31694,11 +31738,27 @@ async function deliverWebhook(target, payload, webhookSecret) {
|
|
|
31694
31738
|
request.end(body);
|
|
31695
31739
|
});
|
|
31696
31740
|
}
|
|
31697
|
-
|
|
31698
|
-
|
|
31741
|
+
function checkedAddresses(target) {
|
|
31742
|
+
return target.addresses ?? [{ address: target.address, family: target.family }];
|
|
31743
|
+
}
|
|
31744
|
+
function pinnedLookup(addresses) {
|
|
31745
|
+
return (_hostname, options, callback) => {
|
|
31746
|
+
if (options.all) callback(null, addresses.map(({ address, family }) => ({ address, family })));
|
|
31747
|
+
else callback(null, addresses[0].address, addresses[0].family);
|
|
31748
|
+
};
|
|
31749
|
+
}
|
|
31750
|
+
var LOCALHOST_ADDRESSES = [{ address: "127.0.0.1", family: 4 }, { address: "::1", family: 6 }];
|
|
31751
|
+
function isLocalhostName(hostname) {
|
|
31752
|
+
const name = hostname.toLowerCase().replace(/\.$/, "");
|
|
31753
|
+
return name === "localhost" || name.endsWith(".localhost");
|
|
31754
|
+
}
|
|
31755
|
+
async function resolveHostAddresses(hostname, resolveAddresses) {
|
|
31756
|
+
const family = net3.isIP(hostname);
|
|
31699
31757
|
if (family === 4 || family === 6) {
|
|
31700
31758
|
return [{ address: hostname, family }];
|
|
31701
31759
|
}
|
|
31760
|
+
if (isLocalhostName(hostname)) return LOCALHOST_ADDRESSES;
|
|
31761
|
+
if (resolveAddresses) return await resolveAddresses(hostname);
|
|
31702
31762
|
try {
|
|
31703
31763
|
const [ipv4, ipv6] = await Promise.allSettled([dns2.resolve4(hostname), dns2.resolve6(hostname)]);
|
|
31704
31764
|
const unique3 = /* @__PURE__ */ new Map();
|
|
@@ -31718,65 +31778,7 @@ async function resolveHostAddresses(hostname) {
|
|
|
31718
31778
|
}
|
|
31719
31779
|
}
|
|
31720
31780
|
function isBlockedAddress(address, options) {
|
|
31721
|
-
|
|
31722
|
-
const family = net2.isIP(normalized);
|
|
31723
|
-
if (family === 4) {
|
|
31724
|
-
return isBlockedIpv4(normalized, options);
|
|
31725
|
-
}
|
|
31726
|
-
if (family === 6) {
|
|
31727
|
-
const mappedIpv42 = extractMappedIpv4(normalized);
|
|
31728
|
-
if (mappedIpv42) {
|
|
31729
|
-
return isBlockedIpv4(mappedIpv42, options);
|
|
31730
|
-
}
|
|
31731
|
-
return isBlockedIpv6(normalized, options);
|
|
31732
|
-
}
|
|
31733
|
-
return true;
|
|
31734
|
-
}
|
|
31735
|
-
function isBlockedIpv4(address, options) {
|
|
31736
|
-
const octets = address.split(".").map((part) => Number.parseInt(part, 10));
|
|
31737
|
-
if (octets.length !== 4 || octets.some(Number.isNaN)) {
|
|
31738
|
-
return true;
|
|
31739
|
-
}
|
|
31740
|
-
const [first, second] = octets;
|
|
31741
|
-
if (first === 127 && !options.allowLoopback) {
|
|
31742
|
-
return true;
|
|
31743
|
-
}
|
|
31744
|
-
return first === 0 || first === 10 || first === 100 && second >= 64 && second <= 127 || first === 169 && second === 254 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168 || first === 198 && (second === 18 || second === 19);
|
|
31745
|
-
}
|
|
31746
|
-
function isBlockedIpv6(address, options) {
|
|
31747
|
-
const normalized = address.split("%")[0].toLowerCase();
|
|
31748
|
-
if (normalized === "::") {
|
|
31749
|
-
return true;
|
|
31750
|
-
}
|
|
31751
|
-
if (normalized === "::1") {
|
|
31752
|
-
return !options.allowLoopback;
|
|
31753
|
-
}
|
|
31754
|
-
const firstHextetText = normalized.split(":")[0] ?? "";
|
|
31755
|
-
const firstHextet = firstHextetText === "" ? 0 : Number.parseInt(firstHextetText, 16);
|
|
31756
|
-
if (Number.isNaN(firstHextet)) {
|
|
31757
|
-
return true;
|
|
31758
|
-
}
|
|
31759
|
-
return firstHextet >= 64512 && firstHextet <= 65023 || firstHextet >= 65152 && firstHextet <= 65215;
|
|
31760
|
-
}
|
|
31761
|
-
function extractMappedIpv4(address) {
|
|
31762
|
-
const normalized = address.toLowerCase();
|
|
31763
|
-
if (!normalized.startsWith("::ffff:")) {
|
|
31764
|
-
return null;
|
|
31765
|
-
}
|
|
31766
|
-
const remainder = normalized.slice("::ffff:".length);
|
|
31767
|
-
if (net2.isIP(remainder) === 4) {
|
|
31768
|
-
return remainder;
|
|
31769
|
-
}
|
|
31770
|
-
const parts = remainder.split(":");
|
|
31771
|
-
if (parts.length !== 2 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) {
|
|
31772
|
-
return null;
|
|
31773
|
-
}
|
|
31774
|
-
const high = Number.parseInt(parts[0], 16);
|
|
31775
|
-
const low = Number.parseInt(parts[1], 16);
|
|
31776
|
-
return [high >> 8, high & 255, low >> 8, low & 255].join(".");
|
|
31777
|
-
}
|
|
31778
|
-
function stripIpv6Brackets2(value) {
|
|
31779
|
-
return value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
31781
|
+
return blockedAddressReason(address, { allowLoopback: options.allowLoopback }) !== null;
|
|
31780
31782
|
}
|
|
31781
31783
|
|
|
31782
31784
|
// ../api-routes/src/apply.ts
|
|
@@ -31895,7 +31897,7 @@ async function applyRoutes(app, opts) {
|
|
|
31895
31897
|
const nextAliases = normalizeProjectAliases(config.spec.displayName, config.spec.aliases ?? []);
|
|
31896
31898
|
if (existing) {
|
|
31897
31899
|
const prevAliases = existing.aliases;
|
|
31898
|
-
aliasesChanged = !
|
|
31900
|
+
aliasesChanged = !aliasArraysEqual(prevAliases, nextAliases);
|
|
31899
31901
|
}
|
|
31900
31902
|
if (existing) {
|
|
31901
31903
|
projectId = existing.id;
|
|
@@ -32126,13 +32128,6 @@ async function applyRoutes(app, opts) {
|
|
|
32126
32128
|
});
|
|
32127
32129
|
});
|
|
32128
32130
|
}
|
|
32129
|
-
function aliasArraysEqual2(a, b) {
|
|
32130
|
-
if (a.length !== b.length) return false;
|
|
32131
|
-
for (let i = 0; i < a.length; i++) {
|
|
32132
|
-
if (a[i].toLowerCase() !== b[i].toLowerCase()) return false;
|
|
32133
|
-
}
|
|
32134
|
-
return true;
|
|
32135
|
-
}
|
|
32136
32131
|
|
|
32137
32132
|
// ../api-routes/src/history.ts
|
|
32138
32133
|
import { and as and31, asc as asc6, count, desc as desc17, eq as eq39, gte as gte6, inArray as inArray19, like as like2, ne as ne8, or as or10 } from "drizzle-orm";
|
|
@@ -36134,8 +36129,8 @@ function readVisibilityCompare(db, projectName, query) {
|
|
|
36134
36129
|
if (scope === "project" && filters.scopeKey !== void 0) throw validationError("scopeKey is not valid for project scope.");
|
|
36135
36130
|
if (scope === "market" && filters.marketKey !== void 0) throw validationError("marketKey is not valid for market scope.");
|
|
36136
36131
|
const { fromRaw, toRaw, fromBounds, toBounds } = compareMonths(query.from, query.to);
|
|
36137
|
-
const providerKey2 = filters.provider === void 0 ? void 0 :
|
|
36138
|
-
const locationKey2 = filters.location === void 0 ? void 0 :
|
|
36132
|
+
const providerKey2 = filters.provider === void 0 ? void 0 : normalizeIdentityText(filters.provider);
|
|
36133
|
+
const locationKey2 = filters.location === void 0 ? void 0 : normalizeIdentityText(filters.location);
|
|
36139
36134
|
const scoped = scope !== "project" || filters.marketKey !== void 0;
|
|
36140
36135
|
let advancedComparison;
|
|
36141
36136
|
if (activeMeasurementPlan(db, project.id) !== null) {
|
|
@@ -36186,7 +36181,7 @@ function readVisibilityCompare(db, projectName, query) {
|
|
|
36186
36181
|
}).from(querySnapshots).where(inArray23(querySnapshots.runId, runIds)).all() : [];
|
|
36187
36182
|
return {
|
|
36188
36183
|
runCount: runIds.length,
|
|
36189
|
-
snapshots: snapshots.filter((snapshot) => (providerKey2 === void 0 ||
|
|
36184
|
+
snapshots: snapshots.filter((snapshot) => (providerKey2 === void 0 || normalizeIdentityText(snapshot.provider) === providerKey2) && (locationKey2 === void 0 || (locationKey2 === "none" ? snapshot.location === null : snapshot.location !== null && normalizeIdentityText(snapshot.location) === locationKey2)))
|
|
36190
36185
|
};
|
|
36191
36186
|
};
|
|
36192
36187
|
const fromMonth = loadMonth(fromBounds);
|
|
@@ -38513,6 +38508,7 @@ var SCHEMA_TABLE = {
|
|
|
38513
38508
|
IndexingRequestResponseDto: indexingRequestResponseDtoSchema,
|
|
38514
38509
|
IntegrationSettingsSummaryDto: integrationSettingsSummaryDtoSchema,
|
|
38515
38510
|
KeywordDto: keywordDtoSchema,
|
|
38511
|
+
KeywordGenerateResponse: keywordGenerateResponseSchema,
|
|
38516
38512
|
LatestProjectRunDto: latestProjectRunDtoSchema,
|
|
38517
38513
|
RunAdmissionDto: runAdmissionDtoSchema,
|
|
38518
38514
|
LocationContext: locationContextSchema,
|
|
@@ -38589,13 +38585,16 @@ var SCHEMA_TABLE = {
|
|
|
38589
38585
|
OrganicEvidenceDto: organicEvidenceDtoSchema,
|
|
38590
38586
|
ProjectCreateRequest: projectCreateRequestSchema,
|
|
38591
38587
|
ProjectDto: projectDtoSchema,
|
|
38588
|
+
ProjectDefaultLocationResponse: projectDefaultLocationResponseSchema,
|
|
38592
38589
|
ProjectUpsertRequest: projectUpsertRequestSchema,
|
|
38593
38590
|
ProjectConfig: projectConfigSchema,
|
|
38591
|
+
ProjectConfigExport: projectConfigExportSchema,
|
|
38594
38592
|
ProjectOverviewDto: projectOverviewDtoSchema,
|
|
38595
38593
|
TrafficAnalyticsResponse: trafficAnalyticsResponseSchema,
|
|
38596
38594
|
ProjectSearchResponseDto: projectSearchResponseSchema,
|
|
38597
38595
|
ProviderSummaryEntryDto: providerSummaryEntryDtoSchema,
|
|
38598
38596
|
QueryDto: queryDtoSchema,
|
|
38597
|
+
QueryGenerateResponse: queryGenerateResponseSchema,
|
|
38599
38598
|
ReferralAssessment: referralAssessmentSchema,
|
|
38600
38599
|
ResultsExportDto: resultsExportDtoSchema,
|
|
38601
38600
|
RunCompletenessDto: runCompletenessDtoSchema,
|
|
@@ -38658,7 +38657,7 @@ var SCHEMA_TABLE = {
|
|
|
38658
38657
|
WordpressManualAssistDto: wordpressManualAssistDtoSchema,
|
|
38659
38658
|
WordpressOnboardResultDto: wordpressOnboardResultDtoSchema,
|
|
38660
38659
|
WordpressPageDetailDto: wordpressPageDetailDtoSchema,
|
|
38661
|
-
|
|
38660
|
+
WordpressPageListDto: wordpressPageListDtoSchema,
|
|
38662
38661
|
WordpressSchemaBlockDto: wordpressSchemaBlockDtoSchema,
|
|
38663
38662
|
WordpressSchemaDeployResultDto: wordpressSchemaDeployResultDtoSchema,
|
|
38664
38663
|
WordpressSchemaStatusResultDto: wordpressSchemaStatusResultDtoSchema,
|
|
@@ -38678,10 +38677,27 @@ var SCHEMA_TABLE = {
|
|
|
38678
38677
|
var SHARED_ENUM_REFS = [
|
|
38679
38678
|
{ schema: "ScheduleDto", property: "kind", component: "SchedulableRunKind" }
|
|
38680
38679
|
];
|
|
38680
|
+
function addNullToNullableEnums(node) {
|
|
38681
|
+
if (Array.isArray(node)) {
|
|
38682
|
+
for (const item2 of node) addNullToNullableEnums(item2);
|
|
38683
|
+
return;
|
|
38684
|
+
}
|
|
38685
|
+
if (node === null || typeof node !== "object") return;
|
|
38686
|
+
const record = node;
|
|
38687
|
+
if (record.nullable === true && Array.isArray(record.enum)) {
|
|
38688
|
+
const values = record.enum;
|
|
38689
|
+
if (!values.includes(null)) record.enum = [...values, null];
|
|
38690
|
+
}
|
|
38691
|
+
for (const value of Object.values(record)) addNullToNullableEnums(value);
|
|
38692
|
+
}
|
|
38693
|
+
var INPUT_FORM_SCHEMAS = /* @__PURE__ */ new Set(["ProjectConfig"]);
|
|
38681
38694
|
function buildComponentSchemas() {
|
|
38682
38695
|
const out = {};
|
|
38683
38696
|
for (const [name, schema] of Object.entries(SCHEMA_TABLE)) {
|
|
38684
|
-
|
|
38697
|
+
const io = INPUT_FORM_SCHEMAS.has(name) ? "input" : "output";
|
|
38698
|
+
const json = z.toJSONSchema(schema, { target: "openapi-3.0", io });
|
|
38699
|
+
addNullToNullableEnums(json);
|
|
38700
|
+
out[name] = json;
|
|
38685
38701
|
}
|
|
38686
38702
|
for (const { schema, property, component } of SHARED_ENUM_REFS) {
|
|
38687
38703
|
const properties = out[schema]?.properties;
|
|
@@ -38844,6 +38860,13 @@ var measurementReportRunParameter = {
|
|
|
38844
38860
|
description: "Eligible full measurement run to reconstruct. Omit to use the latest run for the revision.",
|
|
38845
38861
|
schema: stringSchema
|
|
38846
38862
|
};
|
|
38863
|
+
var measurementReportQueryClassParameter = {
|
|
38864
|
+
name: "queryClass",
|
|
38865
|
+
in: "query",
|
|
38866
|
+
required: false,
|
|
38867
|
+
description: "Query class every group, Target and evidence row is taken over. Defaults to non-brand; branded is a separate read. all pools branded and non-brand into one rate and is served only when asked for. A schema v1 revision records no class: omit this (or pass all) to read every answer, and the response echoes queryClass null.",
|
|
38868
|
+
schema: { type: "string", enum: ["non-brand", "branded", "all"], default: "non-brand" }
|
|
38869
|
+
};
|
|
38847
38870
|
var runIdParameter = {
|
|
38848
38871
|
name: "id",
|
|
38849
38872
|
in: "path",
|
|
@@ -39557,12 +39580,12 @@ var routeCatalog = [
|
|
|
39557
39580
|
method: "get",
|
|
39558
39581
|
path: "/api/v1/projects/{name}/measurement-report",
|
|
39559
39582
|
summary: "Get a revision-pinned measurement report",
|
|
39560
|
-
description: "Builds the Target, group, and evidence report from the immutable plan revision and either its latest eligible stored run or the exact eligible runId supplied by the caller. Missing run population remains explicit and never triggers live provider execution.",
|
|
39583
|
+
description: "Builds the Target, group, and evidence report from the immutable plan revision and either its latest eligible stored run or the exact eligible runId supplied by the caller. On a schema v2 revision every rate is taken over one query class, non-brand unless queryClass names another; the response echoes the class it served. Missing run population remains explicit and never triggers live provider execution.",
|
|
39561
39584
|
tags: ["measurement-plans"],
|
|
39562
|
-
parameters: [nameParameter, measurementReportRevisionParameter, measurementReportRunParameter],
|
|
39585
|
+
parameters: [nameParameter, measurementReportRevisionParameter, measurementReportRunParameter, measurementReportQueryClassParameter],
|
|
39563
39586
|
responses: {
|
|
39564
39587
|
200: jsonResponse("Revision-pinned measurement report returned.", "MeasurementReportResponse"),
|
|
39565
|
-
400: errorResponse("The revision
|
|
39588
|
+
400: errorResponse("The revision or queryClass parameter is invalid, or a class was requested for a schema v1 revision."),
|
|
39566
39589
|
404: errorResponse("Project, measurement-plan revision, or requested run not found.")
|
|
39567
39590
|
}
|
|
39568
39591
|
},
|
|
@@ -39829,15 +39852,16 @@ var routeCatalog = [
|
|
|
39829
39852
|
method: "get",
|
|
39830
39853
|
path: "/api/v1/projects/{name}/measurement-overview",
|
|
39831
39854
|
summary: "Get the scoped measurement overview",
|
|
39832
|
-
description: "Aggregates one revision-pinned run snapshot for All Properties, a group, or a single Property. This is snapshot ranking only: it never infers a trend or compares evidence across revisions. Without runId the most recent completed run pinned to the active revision is used; once paging begins, the cursor pins that revision, displayed run, evidence snapshot, and result filters. A run pinned to another revision is refused rather than joined, and appended evidence on a mutable named run invalidates its cursor. On a schema v2 plan every Property row carries its metro (the top-level group holding it, or null) and, when it sits in several top-level groups, otherMetros. Metrics are computed before search is applied, and a metric with no evidence is unavailable rather than zero. For coverage sorts, unavailable rows form the first bucket in either direction before available numeric rates follow the requested direction.",
|
|
39855
|
+
description: "Aggregates one revision-pinned run snapshot for All Properties, a group, a market, or a single Property. A group reads every query its Properties are assigned; a market reads only its own frozen queries, the population the dashboard's market view reads, and the response scope names which kind was read. Rates are taken over one query class, non-brand unless queryClass names another; all pools branded with non-brand and is served only when asked for. This is snapshot ranking only: it never infers a trend or compares evidence across revisions. Without runId the most recent completed run pinned to the active revision is used; once paging begins, the cursor pins that revision, displayed run, evidence snapshot, and result filters. A run pinned to another revision is refused rather than joined, and appended evidence on a mutable named run invalidates its cursor. On a schema v2 plan every Property row carries its metro (the top-level group holding it, or null) and, when it sits in several top-level groups, otherMetros. Metrics are computed before search is applied, and a metric with no evidence is unavailable rather than zero. For coverage sorts, unavailable rows form the first bucket in either direction before available numeric rates follow the requested direction.",
|
|
39833
39856
|
tags: ["measurement-plans"],
|
|
39834
39857
|
parameters: [
|
|
39835
39858
|
nameParameter,
|
|
39836
39859
|
{ name: "compact", in: "query", description: "Byte-bounded whole-row page; omits optional heavy detail with explicit metadata. Default false for compatibility.", schema: { type: "boolean" } },
|
|
39837
|
-
{ name: "scope", in: "query", required: true, description: "Reporting scope.", schema: { type: "string", enum: ["all", "group", "property"] } },
|
|
39860
|
+
{ name: "scope", in: "query", required: true, description: "Reporting scope. group is every query of the group's Properties; market is only the market's own queries.", schema: { type: "string", enum: ["all", "group", "market", "property"] } },
|
|
39838
39861
|
{ name: "groupKey", in: "query", description: 'Group stable key, required when scope is "group".', schema: stringSchema },
|
|
39862
|
+
{ name: "marketKey", in: "query", description: 'Market stable key (a frozen reporting scope), required when scope is "market" and refused (400) with any other scope.', schema: stringSchema },
|
|
39839
39863
|
{ name: "targetKey", in: "query", description: 'Target stable key, required when scope is "property".', schema: stringSchema },
|
|
39840
|
-
{ name: "queryClass", in: "query", description: "
|
|
39864
|
+
{ name: "queryClass", in: "query", description: "Query class the rates are taken over. Defaults to non-brand; read branded as a separate request. all pools branded and non-brand into one rate.", schema: { type: "string", enum: ["non-brand", "branded", "all"], default: "non-brand" } },
|
|
39841
39865
|
{ name: "provider", in: "query", description: "Restrict to one answer provider.", schema: stringSchema },
|
|
39842
39866
|
{ name: "location", in: "query", description: "Restrict to one execution location label.", schema: stringSchema },
|
|
39843
39867
|
{ name: "from", in: "query", description: "Inclusive start of the window (YYYY-MM-DD).", schema: stringSchema },
|
|
@@ -40085,14 +40109,14 @@ var routeCatalog = [
|
|
|
40085
40109
|
method: "get",
|
|
40086
40110
|
path: "/api/v1/projects/{name}/measurement-changes",
|
|
40087
40111
|
summary: "Compare the latest two comparable measurements",
|
|
40088
|
-
description: "Compares stored runs only when plan revision, execution identity, and full-or-spot-check scope agree. Deltas are current minus previous; it never crosses a revision or silently joins an engine/model change. changedProperties is ordered by the size of each move unless sort=label, and each row carries signed answer-count deltas, denominatorChanged (a metric was taken over a different number of answers, so its delta is not like for like) and withinNoise (every move is at most 2 answers). A move is the rate change times the larger of the two answer counts, the answer-count delta whenever the denominator held, so a falling rate never reads as a gain and a collapse never reads as noise. distribution buckets every Property in scope, not just the returned rows. With queryClass all,
|
|
40112
|
+
description: "Compares stored runs only when plan revision, execution identity, and full-or-spot-check scope agree. Deltas are current minus previous; it never crosses a revision or silently joins an engine/model change. changedProperties is ordered by the size of each move unless sort=label, and each row carries signed answer-count deltas, denominatorChanged (a metric was taken over a different number of answers, so its delta is not like for like) and withinNoise (every move is at most 2 answers). A move is the rate change times the larger of the two answer counts, the answer-count delta whenever the denominator held, so a falling rate never reads as a gain and a collapse never reads as noise. distribution buckets every Property in scope, not just the returned rows. Rates are taken over non-brand queries unless queryClass names another class. With queryClass all, the metrics, rows and distribution pool branded with non-brand, and metricsByClass reports each class beside them.",
|
|
40089
40113
|
tags: ["measurement-plans"],
|
|
40090
40114
|
parameters: [
|
|
40091
40115
|
nameParameter,
|
|
40092
40116
|
{ name: "scope", in: "query", description: "Reporting scope. Defaults to all.", schema: { type: "string", enum: ["all", "group", "property"], default: "all" } },
|
|
40093
40117
|
{ name: "groupKey", in: "query", description: "Required for group scope.", schema: stringSchema },
|
|
40094
40118
|
{ name: "targetKey", in: "query", description: "Required for Property scope.", schema: stringSchema },
|
|
40095
|
-
{ name: "queryClass", in: "query", description: "
|
|
40119
|
+
{ name: "queryClass", in: "query", description: "Query class. Defaults to non-brand; read branded as a separate request. all pools both classes.", schema: { type: "string", enum: ["non-brand", "branded", "all"], default: "non-brand" } },
|
|
40096
40120
|
{ name: "provider", in: "query", description: "Restrict both runs to one answer provider.", schema: stringSchema },
|
|
40097
40121
|
{ name: "location", in: "query", description: "Restrict both runs to one execution location label.", schema: stringSchema },
|
|
40098
40122
|
{ name: "runId", in: "query", description: "Use this completed or partial run as the current side.", schema: stringSchema },
|
|
@@ -40428,7 +40452,7 @@ var routeCatalog = [
|
|
|
40428
40452
|
}
|
|
40429
40453
|
},
|
|
40430
40454
|
responses: {
|
|
40431
|
-
200: jsonResponse("Default location updated.", "
|
|
40455
|
+
200: jsonResponse("Default location updated.", "ProjectDefaultLocationResponse"),
|
|
40432
40456
|
400: errorResponse("Invalid location."),
|
|
40433
40457
|
404: errorResponse("Project not found.")
|
|
40434
40458
|
}
|
|
@@ -40440,7 +40464,7 @@ var routeCatalog = [
|
|
|
40440
40464
|
tags: ["projects"],
|
|
40441
40465
|
parameters: [nameParameter],
|
|
40442
40466
|
responses: {
|
|
40443
|
-
200: jsonResponse("Project configuration returned.", "
|
|
40467
|
+
200: jsonResponse("Project configuration returned.", "ProjectConfigExport"),
|
|
40444
40468
|
404: errorResponse("Project not found.")
|
|
40445
40469
|
}
|
|
40446
40470
|
},
|
|
@@ -40643,7 +40667,7 @@ var routeCatalog = [
|
|
|
40643
40667
|
}
|
|
40644
40668
|
},
|
|
40645
40669
|
responses: {
|
|
40646
|
-
200:
|
|
40670
|
+
200: jsonResponse("Query suggestions returned.", "QueryGenerateResponse"),
|
|
40647
40671
|
501: errorResponse("Query generation is not available.")
|
|
40648
40672
|
}
|
|
40649
40673
|
},
|
|
@@ -40752,8 +40776,7 @@ var routeCatalog = [
|
|
|
40752
40776
|
}
|
|
40753
40777
|
},
|
|
40754
40778
|
responses: {
|
|
40755
|
-
|
|
40756
|
-
200: rawJsonResponse("Legacy keyword suggestions returned.", looseObjectSchema),
|
|
40779
|
+
200: jsonResponse("Legacy keyword suggestions returned.", "KeywordGenerateResponse"),
|
|
40757
40780
|
501: errorResponse("Legacy keyword generation is not available.")
|
|
40758
40781
|
}
|
|
40759
40782
|
},
|
|
@@ -44089,7 +44112,7 @@ var routeCatalog = [
|
|
|
44089
44112
|
tags: ["wordpress"],
|
|
44090
44113
|
parameters: [nameParameter, wordpressEnvQueryParameter],
|
|
44091
44114
|
responses: {
|
|
44092
|
-
200:
|
|
44115
|
+
200: jsonResponse("WordPress pages returned.", "WordpressPageListDto"),
|
|
44093
44116
|
400: errorResponse("Invalid environment or missing connection."),
|
|
44094
44117
|
404: errorResponse("Project not found.")
|
|
44095
44118
|
}
|
|
@@ -47812,18 +47835,18 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
|
|
|
47812
47835
|
const parsed = JSON.parse(body);
|
|
47813
47836
|
if (parsed.error) detail = parsed.error;
|
|
47814
47837
|
if (parsed.error_description) {
|
|
47815
|
-
const sanitized = parsed.error_description.replace(new RegExp(
|
|
47838
|
+
const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp2(clientId), "g"), "***").replace(new RegExp(escapeRegExp2(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp2(code), "g"), "***");
|
|
47816
47839
|
detail += detail ? `: ${sanitized}` : sanitized;
|
|
47817
47840
|
}
|
|
47818
47841
|
} catch {
|
|
47819
47842
|
const truncated = body.length <= 120 ? body : `${body.slice(0, 120)}...`;
|
|
47820
|
-
detail = truncated.replace(new RegExp(
|
|
47843
|
+
detail = truncated.replace(new RegExp(escapeRegExp2(clientId), "g"), "***").replace(new RegExp(escapeRegExp2(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp2(code), "g"), "***");
|
|
47821
47844
|
}
|
|
47822
47845
|
throw new GoogleAuthError(`Token exchange failed (${res.status}): ${detail}`);
|
|
47823
47846
|
}
|
|
47824
47847
|
return await res.json();
|
|
47825
47848
|
}
|
|
47826
|
-
function
|
|
47849
|
+
function escapeRegExp2(str) {
|
|
47827
47850
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47828
47851
|
}
|
|
47829
47852
|
async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
|
|
@@ -47851,12 +47874,12 @@ async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
|
|
|
47851
47874
|
const parsed = JSON.parse(body);
|
|
47852
47875
|
if (parsed.error) detail = parsed.error;
|
|
47853
47876
|
if (parsed.error_description) {
|
|
47854
|
-
const sanitized = parsed.error_description.replace(new RegExp(
|
|
47877
|
+
const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp2(clientId), "g"), "***").replace(new RegExp(escapeRegExp2(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp2(currentRefreshToken), "g"), "***");
|
|
47855
47878
|
detail += detail ? `: ${sanitized}` : sanitized;
|
|
47856
47879
|
}
|
|
47857
47880
|
} catch {
|
|
47858
47881
|
const truncated = body.length <= 120 ? body : `${body.slice(0, 120)}...`;
|
|
47859
|
-
detail = truncated.replace(new RegExp(
|
|
47882
|
+
detail = truncated.replace(new RegExp(escapeRegExp2(clientId), "g"), "***").replace(new RegExp(escapeRegExp2(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp2(currentRefreshToken), "g"), "***");
|
|
47860
47883
|
}
|
|
47861
47884
|
throw new GoogleAuthError(`Token refresh failed (${res.status}): ${detail}`);
|
|
47862
47885
|
}
|
|
@@ -47946,7 +47969,7 @@ function gscClientLog(level, action, ctx) {
|
|
|
47946
47969
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
47947
47970
|
stream.write(JSON.stringify(entry) + "\n");
|
|
47948
47971
|
}
|
|
47949
|
-
function
|
|
47972
|
+
function escapeRegExp3(str) {
|
|
47950
47973
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47951
47974
|
}
|
|
47952
47975
|
async function gscFetchResponse(accessToken, url, opts) {
|
|
@@ -47975,7 +47998,7 @@ async function gscFetchResponse(accessToken, url, opts) {
|
|
|
47975
47998
|
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
47976
47999
|
let sanitizedDetail = detail;
|
|
47977
48000
|
if (accessToken) {
|
|
47978
|
-
sanitizedDetail = sanitizedDetail.replace(new RegExp(
|
|
48001
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp3(accessToken), "g"), "***");
|
|
47979
48002
|
}
|
|
47980
48003
|
throw new GoogleApiError(`GSC API error (${res.status}): ${sanitizedDetail}`, res.status);
|
|
47981
48004
|
}
|
|
@@ -48263,15 +48286,12 @@ async function getAccessToken(clientEmail, privateKey) {
|
|
|
48263
48286
|
const body = await res.text().catch(() => "");
|
|
48264
48287
|
ga4Log("error", "token.failed", { httpStatus: res.status });
|
|
48265
48288
|
const detail = body.length <= 200 ? body : `${body.slice(0, 200)}... [truncated]`;
|
|
48266
|
-
const sanitizedDetail = detail.replace(new RegExp(
|
|
48289
|
+
const sanitizedDetail = detail.replace(new RegExp(escapeRegExp(clientEmail), "g"), "***").replace(new RegExp(escapeRegExp(privateKey.slice(0, 32)), "g"), "***");
|
|
48267
48290
|
throw new GA4ApiError(`Failed to get access token: ${sanitizedDetail}`, res.status);
|
|
48268
48291
|
}
|
|
48269
48292
|
const data = await res.json();
|
|
48270
48293
|
return data.access_token;
|
|
48271
48294
|
}
|
|
48272
|
-
function escapeRegExp3(str) {
|
|
48273
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48274
|
-
}
|
|
48275
48295
|
var gaInFlight = 0;
|
|
48276
48296
|
var gaWaitQueue = [];
|
|
48277
48297
|
async function acquireGa4Slot() {
|
|
@@ -48377,7 +48397,7 @@ async function runReport(accessToken, propertyId, request) {
|
|
|
48377
48397
|
}
|
|
48378
48398
|
let sanitizedDetail = detail;
|
|
48379
48399
|
if (accessToken) {
|
|
48380
|
-
sanitizedDetail = sanitizedDetail.replace(new RegExp(
|
|
48400
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
|
|
48381
48401
|
}
|
|
48382
48402
|
ga4Log("error", "report.auth-failed", { propertyId, httpStatus: res.status });
|
|
48383
48403
|
throw new GA4ApiError(
|
|
@@ -48397,7 +48417,7 @@ async function runReport(accessToken, propertyId, request) {
|
|
|
48397
48417
|
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
48398
48418
|
let sanitizedDetail = detail;
|
|
48399
48419
|
if (accessToken) {
|
|
48400
|
-
sanitizedDetail = sanitizedDetail.replace(new RegExp(
|
|
48420
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
|
|
48401
48421
|
}
|
|
48402
48422
|
throw new GA4ApiError(`GA4 API error (${res.status}): ${sanitizedDetail}`, res.status, retryAfterSeconds);
|
|
48403
48423
|
}
|
|
@@ -48421,7 +48441,7 @@ async function batchRunReports(accessToken, propertyId, requests) {
|
|
|
48421
48441
|
ga4Log("error", "batch-report.auth-failed", { propertyId, httpStatus: res.status });
|
|
48422
48442
|
let sanitizedDetail = body;
|
|
48423
48443
|
if (accessToken) {
|
|
48424
|
-
sanitizedDetail = sanitizedDetail.replace(new RegExp(
|
|
48444
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
|
|
48425
48445
|
}
|
|
48426
48446
|
throw new GA4ApiError(
|
|
48427
48447
|
`GA4 API authentication failed \u2014 check service account permissions. ${sanitizedDetail}`,
|
|
@@ -48440,7 +48460,7 @@ async function batchRunReports(accessToken, propertyId, requests) {
|
|
|
48440
48460
|
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
48441
48461
|
let sanitizedDetail = detail;
|
|
48442
48462
|
if (accessToken) {
|
|
48443
|
-
sanitizedDetail = sanitizedDetail.replace(new RegExp(
|
|
48463
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
|
|
48444
48464
|
}
|
|
48445
48465
|
throw new GA4ApiError(`GA4 API error (${res.status}): ${sanitizedDetail}`, res.status, retryAfterSeconds);
|
|
48446
48466
|
}
|
|
@@ -48742,7 +48762,7 @@ async function listProperties(accessToken) {
|
|
|
48742
48762
|
} catch {
|
|
48743
48763
|
if (raw.length < 200) detail = ` ${raw}`;
|
|
48744
48764
|
}
|
|
48745
|
-
const sanitized = detail.replace(new RegExp(
|
|
48765
|
+
const sanitized = detail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
|
|
48746
48766
|
const retryAfterSeconds = res.status === 429 || res.status >= 500 || res.status === 403 && quotaReason ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
|
|
48747
48767
|
ga4Log("error", "admin.account-summaries-failed", {
|
|
48748
48768
|
httpStatus: res.status,
|
|
@@ -58074,9 +58094,6 @@ function bingClientLog(level, action, ctx) {
|
|
|
58074
58094
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
58075
58095
|
stream.write(JSON.stringify(entry) + "\n");
|
|
58076
58096
|
}
|
|
58077
|
-
function escapeRegExp4(str) {
|
|
58078
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
58079
|
-
}
|
|
58080
58097
|
function parseBingErrorCode(body) {
|
|
58081
58098
|
try {
|
|
58082
58099
|
const parsed = JSON.parse(body);
|
|
@@ -58113,7 +58130,7 @@ async function bingFetchOnce(apiKey, endpoint, opts) {
|
|
|
58113
58130
|
if (!res.ok) {
|
|
58114
58131
|
const body = await res.text();
|
|
58115
58132
|
let detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
58116
|
-
detail = detail.replace(new RegExp(
|
|
58133
|
+
detail = detail.replace(new RegExp(escapeRegExp(apiKey), "g"), "***");
|
|
58117
58134
|
const bingErrorCode = parseBingErrorCode(body);
|
|
58118
58135
|
const err = new BingApiError(`Bing API error (${res.status}): ${detail}`, res.status, bingErrorCode);
|
|
58119
58136
|
bingClientLog("error", err.isThrottle ? "http.throttled" : "http.error", {
|
|
@@ -58956,7 +58973,7 @@ function findBiggestMover(current, prior) {
|
|
|
58956
58973
|
for (const row of current) entry(row.source).current += row.sessions ?? 0;
|
|
58957
58974
|
for (const row of prior) entry(row.source).prior += row.sessions ?? 0;
|
|
58958
58975
|
let mover = null;
|
|
58959
|
-
const sources = [...bySource.keys()].sort(
|
|
58976
|
+
const sources = [...bySource.keys()].sort(compareText);
|
|
58960
58977
|
for (const source of sources) {
|
|
58961
58978
|
const sessions = bySource.get(source);
|
|
58962
58979
|
const size = Math.abs(sessions.current - sessions.prior);
|
|
@@ -60572,7 +60589,7 @@ function buildAuthErrorMessage(res, responseText) {
|
|
|
60572
60589
|
async function fetchJson(connection, siteUrl, path8, init) {
|
|
60573
60590
|
if (siteUrl.startsWith("http:")) {
|
|
60574
60591
|
}
|
|
60575
|
-
const res = await fetch(`${normalizeSiteUrl(siteUrl)}${path8}`, {
|
|
60592
|
+
const res = await (connection.fetchImpl ?? fetch)(`${normalizeSiteUrl(siteUrl)}${path8}`, {
|
|
60576
60593
|
...init,
|
|
60577
60594
|
headers: {
|
|
60578
60595
|
"Authorization": `Basic ${encodeBasicAuth(connection.username, connection.appPassword)}`,
|
|
@@ -60596,10 +60613,10 @@ async function fetchJson(connection, siteUrl, path8, init) {
|
|
|
60596
60613
|
const text2 = await res.text().catch(() => "");
|
|
60597
60614
|
let detail = text2 || res.statusText;
|
|
60598
60615
|
if (connection.appPassword) {
|
|
60599
|
-
detail = detail.replace(new RegExp(
|
|
60616
|
+
detail = detail.replace(new RegExp(escapeRegExp(connection.appPassword), "g"), "***");
|
|
60600
60617
|
}
|
|
60601
60618
|
if (connection.username) {
|
|
60602
|
-
detail = detail.replace(new RegExp(
|
|
60619
|
+
detail = detail.replace(new RegExp(escapeRegExp(connection.username), "g"), "***");
|
|
60603
60620
|
}
|
|
60604
60621
|
throw new WordpressApiError("UPSTREAM_ERROR", `WordPress API error (${res.status}): ${detail}`, res.status);
|
|
60605
60622
|
}
|
|
@@ -60631,9 +60648,9 @@ async function fetchPageCollectionSummary(connection, siteUrl, options) {
|
|
|
60631
60648
|
);
|
|
60632
60649
|
return response;
|
|
60633
60650
|
}
|
|
60634
|
-
async function fetchText(url) {
|
|
60651
|
+
async function fetchText(connection, url) {
|
|
60635
60652
|
try {
|
|
60636
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(WP_FETCH_TEXT_TIMEOUT_MS) });
|
|
60653
|
+
const res = await (connection.fetchImpl ?? fetch)(url, { signal: AbortSignal.timeout(WP_FETCH_TEXT_TIMEOUT_MS) });
|
|
60637
60654
|
if (!res.ok) return null;
|
|
60638
60655
|
return await res.text();
|
|
60639
60656
|
} catch {
|
|
@@ -60775,7 +60792,7 @@ async function verifyWordpressConnection(connection) {
|
|
|
60775
60792
|
validateConnection(connection, site.siteUrl);
|
|
60776
60793
|
const userInfo = await verifyAuthenticatedRestAccess(connection, site.siteUrl);
|
|
60777
60794
|
const response = await fetchPageCollectionSummary(connection, site.siteUrl, { context: "view" });
|
|
60778
|
-
const homeHtml = await fetchText(site.siteUrl);
|
|
60795
|
+
const homeHtml = await fetchText(connection, site.siteUrl);
|
|
60779
60796
|
return {
|
|
60780
60797
|
url: site.siteUrl,
|
|
60781
60798
|
reachable: true,
|
|
@@ -60791,7 +60808,7 @@ async function getSiteStatus(connection, env) {
|
|
|
60791
60808
|
try {
|
|
60792
60809
|
const userInfo = await verifyAuthenticatedRestAccess(connection, site.siteUrl);
|
|
60793
60810
|
const response = await fetchPageCollectionSummary(connection, site.siteUrl, { context: "view" });
|
|
60794
|
-
const homeHtml = await fetchText(site.siteUrl);
|
|
60811
|
+
const homeHtml = await fetchText(connection, site.siteUrl);
|
|
60795
60812
|
const plugins = await listActivePlugins(connection, env);
|
|
60796
60813
|
return {
|
|
60797
60814
|
url: site.siteUrl,
|
|
@@ -60881,15 +60898,15 @@ async function getPageBySlug(connection, slug, env) {
|
|
|
60881
60898
|
}
|
|
60882
60899
|
return body[0];
|
|
60883
60900
|
}
|
|
60884
|
-
async function fetchRenderedPage(link) {
|
|
60901
|
+
async function fetchRenderedPage(connection, link) {
|
|
60885
60902
|
if (!link) return null;
|
|
60886
|
-
return fetchText(link);
|
|
60903
|
+
return fetchText(connection, link);
|
|
60887
60904
|
}
|
|
60888
60905
|
async function getPageDetail(connection, slug, env, plugins) {
|
|
60889
60906
|
const site = resolveEnvironment(connection, env);
|
|
60890
60907
|
const resolvedPlugins = plugins === void 0 ? await listActivePlugins(connection, site.env) : plugins;
|
|
60891
60908
|
const page3 = await getPageBySlug(connection, slug, site.env);
|
|
60892
|
-
const html = await fetchRenderedPage(page3.link);
|
|
60909
|
+
const html = await fetchRenderedPage(connection, page3.link);
|
|
60893
60910
|
const schemaBlocks = html ? extractSchemaBlocks(html) : [];
|
|
60894
60911
|
const seo = buildSeoState(page3, html, resolvedPlugins);
|
|
60895
60912
|
return {
|
|
@@ -61094,14 +61111,11 @@ var CANONRY_SCHEMA_START = "<!-- canonry:schema:start -->";
|
|
|
61094
61111
|
var CANONRY_SCHEMA_END = "<!-- canonry:schema:end -->";
|
|
61095
61112
|
function stripCanonrySchema(content) {
|
|
61096
61113
|
const regex = new RegExp(
|
|
61097
|
-
`${
|
|
61114
|
+
`${escapeRegExp(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp(CANONRY_SCHEMA_END)}`,
|
|
61098
61115
|
"g"
|
|
61099
61116
|
);
|
|
61100
61117
|
return content.replace(regex, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
61101
61118
|
}
|
|
61102
|
-
function escapeRegExp5(str) {
|
|
61103
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
61104
|
-
}
|
|
61105
61119
|
function injectCanonrySchema(content, schemas) {
|
|
61106
61120
|
if (schemas.length === 0) return content;
|
|
61107
61121
|
const blocks = schemas.map((schema) => `<script type="application/ld+json">${JSON.stringify(schema).replace(/<\//g, "<\\/")}</script>`).join("\n");
|
|
@@ -61206,7 +61220,7 @@ async function getSchemaStatus(connection, env) {
|
|
|
61206
61220
|
const thirdPartySchemas = [];
|
|
61207
61221
|
if (hasCanonryMarker) {
|
|
61208
61222
|
const markerRegex = new RegExp(
|
|
61209
|
-
`${
|
|
61223
|
+
`${escapeRegExp(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp(CANONRY_SCHEMA_END)}`
|
|
61210
61224
|
);
|
|
61211
61225
|
const match = markerRegex.exec(rawContent);
|
|
61212
61226
|
if (match?.[1]) {
|
|
@@ -61249,7 +61263,7 @@ async function getLlmsTxt(connection, env) {
|
|
|
61249
61263
|
return {
|
|
61250
61264
|
env: site.env,
|
|
61251
61265
|
url,
|
|
61252
|
-
content: await fetchText(url)
|
|
61266
|
+
content: await fetchText(connection, url)
|
|
61253
61267
|
};
|
|
61254
61268
|
}
|
|
61255
61269
|
async function buildManualLlmsTxtUpdate(connection, content, env) {
|
|
@@ -61431,6 +61445,132 @@ function parseEnv(value) {
|
|
|
61431
61445
|
return parsed.success ? parsed.data : void 0;
|
|
61432
61446
|
}
|
|
61433
61447
|
|
|
61448
|
+
// ../api-routes/src/guarded-fetch.ts
|
|
61449
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
61450
|
+
var GUARDED_FETCH_MAX_REDIRECTS = 5;
|
|
61451
|
+
var MAX_POOLED_AGENTS = 16;
|
|
61452
|
+
var EgressRefusedError = class extends Error {
|
|
61453
|
+
/**
|
|
61454
|
+
* True when the refused target is a redirect the site chose rather than the
|
|
61455
|
+
* URL the caller asked for. Refusing the caller's URL is a verdict on the
|
|
61456
|
+
* caller's input; refusing a redirect is the site's failure.
|
|
61457
|
+
*/
|
|
61458
|
+
redirected;
|
|
61459
|
+
constructor(message, redirected) {
|
|
61460
|
+
super(message);
|
|
61461
|
+
this.name = "EgressRefusedError";
|
|
61462
|
+
this.redirected = redirected;
|
|
61463
|
+
}
|
|
61464
|
+
};
|
|
61465
|
+
var EgressFailedError = class extends Error {
|
|
61466
|
+
constructor(message) {
|
|
61467
|
+
super(message);
|
|
61468
|
+
this.name = "EgressFailedError";
|
|
61469
|
+
}
|
|
61470
|
+
};
|
|
61471
|
+
var CROSS_ORIGIN_DROPPED_HEADERS = ["authorization", "cookie", "proxy-authorization"];
|
|
61472
|
+
var BODY_HEADERS = ["content-encoding", "content-language", "content-length", "content-location", "content-type"];
|
|
61473
|
+
function createGuardedFetch(options = {}) {
|
|
61474
|
+
const resolveTarget = options.resolveTarget ?? ((url) => resolveWebhookTarget(url, { allowLoopback: options.allowLoopback }));
|
|
61475
|
+
const agents = /* @__PURE__ */ new Map();
|
|
61476
|
+
function agentFor(target) {
|
|
61477
|
+
const addresses = checkedAddresses(target);
|
|
61478
|
+
const key = `${target.url.origin} ${addresses.map(({ address }) => address).sort().join(" ")}`;
|
|
61479
|
+
const agent = agents.get(key) ?? pinnedAgent(addresses);
|
|
61480
|
+
agents.delete(key);
|
|
61481
|
+
agents.set(key, agent);
|
|
61482
|
+
if (agents.size > MAX_POOLED_AGENTS) {
|
|
61483
|
+
const [oldestKey, oldest] = agents.entries().next().value;
|
|
61484
|
+
agents.delete(oldestKey);
|
|
61485
|
+
oldest.close().catch(() => {
|
|
61486
|
+
});
|
|
61487
|
+
}
|
|
61488
|
+
return agent;
|
|
61489
|
+
}
|
|
61490
|
+
return async (input, init = {}) => {
|
|
61491
|
+
let href = String(input);
|
|
61492
|
+
let method = (init.method ?? "GET").toUpperCase();
|
|
61493
|
+
let body = init.body ?? null;
|
|
61494
|
+
const headers = new Headers(init.headers);
|
|
61495
|
+
const signal = init.signal ?? void 0;
|
|
61496
|
+
let mayRedirectToLoopback = true;
|
|
61497
|
+
for (let redirects = 0; ; redirects += 1) {
|
|
61498
|
+
signal?.throwIfAborted();
|
|
61499
|
+
const check2 = await untilAborted(resolveTarget(href), signal);
|
|
61500
|
+
if (!check2.ok) {
|
|
61501
|
+
const parsed = URL.canParse(href) ? new URL(href) : null;
|
|
61502
|
+
if (check2.unresolved) throw new EgressFailedError(`Could not resolve ${parsed?.hostname || href}`);
|
|
61503
|
+
throw new EgressRefusedError(`Refused to connect to ${parsed?.host || href}: ${check2.message.replace(/^"url" /, "")}`, redirects > 0);
|
|
61504
|
+
}
|
|
61505
|
+
const url = check2.target.url;
|
|
61506
|
+
const onLoopback = checkedAddresses(check2.target).map(({ address }) => isLoopbackAddress(address));
|
|
61507
|
+
if (!mayRedirectToLoopback && onLoopback.includes(true)) {
|
|
61508
|
+
throw new EgressRefusedError(`Refused to connect to ${url.host}: a redirect from a site that is not on loopback must not lead to loopback`, true);
|
|
61509
|
+
}
|
|
61510
|
+
mayRedirectToLoopback = !onLoopback.includes(false);
|
|
61511
|
+
const response = await requestPinned(check2.target, agentFor(check2.target), { method, headers, body, signal });
|
|
61512
|
+
const location = isLocationRedirectStatus(response.status) ? response.headers.get("location") : null;
|
|
61513
|
+
if (location === null) return response;
|
|
61514
|
+
await response.body?.cancel();
|
|
61515
|
+
if (redirects >= GUARDED_FETCH_MAX_REDIRECTS) {
|
|
61516
|
+
throw new EgressFailedError(`${url.host} redirected more than ${GUARDED_FETCH_MAX_REDIRECTS} times`);
|
|
61517
|
+
}
|
|
61518
|
+
let next;
|
|
61519
|
+
try {
|
|
61520
|
+
next = new URL(location, url);
|
|
61521
|
+
} catch {
|
|
61522
|
+
throw new EgressFailedError(`${url.host} redirected to an invalid Location`);
|
|
61523
|
+
}
|
|
61524
|
+
const status = response.status;
|
|
61525
|
+
if (status === 303 && method !== "GET" && method !== "HEAD" || (status === 301 || status === 302) && method === "POST") {
|
|
61526
|
+
method = "GET";
|
|
61527
|
+
body = null;
|
|
61528
|
+
for (const name of BODY_HEADERS) headers.delete(name);
|
|
61529
|
+
}
|
|
61530
|
+
if (next.origin !== url.origin) {
|
|
61531
|
+
for (const name of CROSS_ORIGIN_DROPPED_HEADERS) headers.delete(name);
|
|
61532
|
+
}
|
|
61533
|
+
href = next.href;
|
|
61534
|
+
}
|
|
61535
|
+
};
|
|
61536
|
+
}
|
|
61537
|
+
async function untilAborted(operation, signal) {
|
|
61538
|
+
if (!signal) return await operation;
|
|
61539
|
+
let onAbort;
|
|
61540
|
+
try {
|
|
61541
|
+
return await Promise.race([
|
|
61542
|
+
operation,
|
|
61543
|
+
new Promise((_resolve, reject) => {
|
|
61544
|
+
onAbort = () => reject(signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError"));
|
|
61545
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
61546
|
+
})
|
|
61547
|
+
]);
|
|
61548
|
+
} finally {
|
|
61549
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
61550
|
+
}
|
|
61551
|
+
}
|
|
61552
|
+
async function requestPinned(target, dispatcher, init) {
|
|
61553
|
+
const response = await undiciFetch(target.url, {
|
|
61554
|
+
method: init.method,
|
|
61555
|
+
headers: [...init.headers],
|
|
61556
|
+
body: init.body,
|
|
61557
|
+
signal: init.signal,
|
|
61558
|
+
redirect: "manual",
|
|
61559
|
+
dispatcher
|
|
61560
|
+
});
|
|
61561
|
+
return response;
|
|
61562
|
+
}
|
|
61563
|
+
function pinnedAgent(addresses) {
|
|
61564
|
+
return new Agent({
|
|
61565
|
+
// Try the next checked address when one does not answer, so a dual-stack
|
|
61566
|
+
// site whose first address is unreachable (an IPv6-only network, a dead A
|
|
61567
|
+
// record) still loads, as it does through global `fetch`. Every address
|
|
61568
|
+
// passed the policy, so falling back reaches nothing it refused.
|
|
61569
|
+
autoSelectFamily: true,
|
|
61570
|
+
connect: { lookup: pinnedLookup(addresses) }
|
|
61571
|
+
});
|
|
61572
|
+
}
|
|
61573
|
+
|
|
61434
61574
|
// ../api-routes/src/wordpress.ts
|
|
61435
61575
|
function parseEnvInput(value, fieldName = "env") {
|
|
61436
61576
|
const env = parseEnv(value);
|
|
@@ -61458,6 +61598,8 @@ async function withWordpressErrorHandling(handler) {
|
|
|
61458
61598
|
return await handler();
|
|
61459
61599
|
} catch (error) {
|
|
61460
61600
|
if (error instanceof WordpressApiError) throw toAppError(error);
|
|
61601
|
+
if (error instanceof EgressRefusedError && !error.redirected) throw validationError(error.message);
|
|
61602
|
+
if (error instanceof EgressRefusedError || error instanceof EgressFailedError) throw providerError(error.message);
|
|
61461
61603
|
throw error;
|
|
61462
61604
|
}
|
|
61463
61605
|
}
|
|
@@ -61467,6 +61609,10 @@ async function wordpressRoutes(app, opts) {
|
|
|
61467
61609
|
if (opts.wordpressConnectionStore) return opts.wordpressConnectionStore;
|
|
61468
61610
|
throw validationError("WordPress connection storage is not configured for this deployment");
|
|
61469
61611
|
}
|
|
61612
|
+
const egressFetch = createGuardedFetch({ allowLoopback });
|
|
61613
|
+
function guarded(connection) {
|
|
61614
|
+
return { ...connection, fetchImpl: egressFetch };
|
|
61615
|
+
}
|
|
61470
61616
|
async function assertWordpressUrlAllowed(rawUrl, field) {
|
|
61471
61617
|
const check2 = await resolveWebhookTarget(rawUrl, { allowLoopback });
|
|
61472
61618
|
if (!check2.ok) {
|
|
@@ -61478,7 +61624,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
61478
61624
|
if (!connection) {
|
|
61479
61625
|
throw validationError(`No WordPress connection found for project "${projectName}". Run "canonry wordpress connect ${projectName}" first.`);
|
|
61480
61626
|
}
|
|
61481
|
-
return connection;
|
|
61627
|
+
return guarded(connection);
|
|
61482
61628
|
}
|
|
61483
61629
|
app.post("/projects/:name/wordpress/connect", async (request) => {
|
|
61484
61630
|
return withWordpressErrorHandling(async () => {
|
|
@@ -61506,8 +61652,8 @@ async function wordpressRoutes(app, opts) {
|
|
|
61506
61652
|
createdAt: existing?.createdAt ?? now,
|
|
61507
61653
|
updatedAt: now
|
|
61508
61654
|
};
|
|
61509
|
-
await verifyWordpressConnection(nextConnection);
|
|
61510
|
-
const connection = store.upsertConnection(nextConnection);
|
|
61655
|
+
await verifyWordpressConnection(guarded(nextConnection));
|
|
61656
|
+
const connection = guarded(store.upsertConnection(nextConnection));
|
|
61511
61657
|
const live = await getSiteStatus(connection, "live");
|
|
61512
61658
|
const staging = connection.stagingUrl ? await getSiteStatus(connection, "staging") : null;
|
|
61513
61659
|
writeAuditLog(app.db, {
|
|
@@ -61545,8 +61691,8 @@ async function wordpressRoutes(app, opts) {
|
|
|
61545
61691
|
});
|
|
61546
61692
|
app.get("/projects/:name/wordpress/status", async (request) => {
|
|
61547
61693
|
const project = resolveProject(app.db, request.params.name);
|
|
61548
|
-
const
|
|
61549
|
-
if (!
|
|
61694
|
+
const stored = opts.wordpressConnectionStore?.getConnection(project.name);
|
|
61695
|
+
if (!stored) {
|
|
61550
61696
|
return {
|
|
61551
61697
|
connected: false,
|
|
61552
61698
|
projectName: project.name,
|
|
@@ -61556,6 +61702,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
61556
61702
|
adminUrl: null
|
|
61557
61703
|
};
|
|
61558
61704
|
}
|
|
61705
|
+
const connection = guarded(stored);
|
|
61559
61706
|
const live = await getSiteStatus(connection, "live");
|
|
61560
61707
|
const staging = connection.stagingUrl ? await getSiteStatus(connection, "staging") : null;
|
|
61561
61708
|
return {
|
|
@@ -61840,8 +61987,8 @@ async function wordpressRoutes(app, opts) {
|
|
|
61840
61987
|
createdAt: existing?.createdAt ?? now,
|
|
61841
61988
|
updatedAt: now
|
|
61842
61989
|
};
|
|
61843
|
-
await verifyWordpressConnection(nextConnection);
|
|
61844
|
-
connection = store.upsertConnection(nextConnection);
|
|
61990
|
+
await verifyWordpressConnection(guarded(nextConnection));
|
|
61991
|
+
connection = guarded(store.upsertConnection(nextConnection));
|
|
61845
61992
|
writeAuditLog(app.db, {
|
|
61846
61993
|
projectId: project.id,
|
|
61847
61994
|
actor: "api",
|
|
@@ -62793,7 +62940,6 @@ async function backlinksRoutes(app, opts) {
|
|
|
62793
62940
|
import crypto48 from "crypto";
|
|
62794
62941
|
import { isIP } from "net";
|
|
62795
62942
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
62796
|
-
import { Agent as UndiciAgent } from "undici";
|
|
62797
62943
|
import { and as and60, desc as desc35, eq as eq69, gte as gte16, inArray as inArray28, lte as lte17, sql as sql28 } from "drizzle-orm";
|
|
62798
62944
|
|
|
62799
62945
|
// ../api-routes/src/cloudflare-worker-version.ts
|
|
@@ -78093,7 +78239,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
78093
78239
|
let skippedEntryCount = 0;
|
|
78094
78240
|
let hasMore = false;
|
|
78095
78241
|
const events = [];
|
|
78096
|
-
const
|
|
78242
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
78097
78243
|
for (let page3 = 0; page3 < maxPages; page3 += 1) {
|
|
78098
78244
|
const url = new URL(endpoint);
|
|
78099
78245
|
url.searchParams.set("limit", String(pageSize));
|
|
@@ -78107,7 +78253,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
78107
78253
|
url.searchParams.set("until", options.until);
|
|
78108
78254
|
}
|
|
78109
78255
|
url.searchParams.set("_cb", randomUUID4());
|
|
78110
|
-
const
|
|
78256
|
+
const response = await fetchImpl(url, {
|
|
78111
78257
|
method: "GET",
|
|
78112
78258
|
headers: {
|
|
78113
78259
|
Authorization: authHeader,
|
|
@@ -78115,11 +78261,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
78115
78261
|
"Cache-Control": "no-cache"
|
|
78116
78262
|
},
|
|
78117
78263
|
signal: AbortSignal.timeout(timeoutMs)
|
|
78118
|
-
};
|
|
78119
|
-
if (dispatcher !== void 0) {
|
|
78120
|
-
fetchInit.dispatcher = dispatcher;
|
|
78121
|
-
}
|
|
78122
|
-
const response = await fetch(url, fetchInit);
|
|
78264
|
+
});
|
|
78123
78265
|
if (!response.ok) {
|
|
78124
78266
|
const body2 = await readErrorBody2(response);
|
|
78125
78267
|
throw new WordpressTrafficApiError(
|
|
@@ -80570,28 +80712,8 @@ async function trafficRoutes(app, opts) {
|
|
|
80570
80712
|
if (!check2.ok) {
|
|
80571
80713
|
throw validationError(`WordPress baseUrl rejected: ${check2.message}`);
|
|
80572
80714
|
}
|
|
80573
|
-
const { address, family } = check2.target;
|
|
80574
|
-
return new UndiciAgent({
|
|
80575
|
-
connect: {
|
|
80576
|
-
lookup: (_hostname, options, cb) => {
|
|
80577
|
-
if (options?.all) {
|
|
80578
|
-
cb(null, [{ address, family: family === 6 ? 6 : 4 }]);
|
|
80579
|
-
} else {
|
|
80580
|
-
cb(null, address, family === 6 ? 6 : 4);
|
|
80581
|
-
}
|
|
80582
|
-
}
|
|
80583
|
-
}
|
|
80584
|
-
});
|
|
80585
|
-
}
|
|
80586
|
-
async function withPinnedWordpressDispatcher(baseUrl, fn) {
|
|
80587
|
-
const dispatcher = await assertWordpressTargetAllowed(baseUrl);
|
|
80588
|
-
try {
|
|
80589
|
-
return await fn(dispatcher);
|
|
80590
|
-
} finally {
|
|
80591
|
-
await dispatcher.close().catch(() => {
|
|
80592
|
-
});
|
|
80593
|
-
}
|
|
80594
80715
|
}
|
|
80716
|
+
const wordpressTrafficFetch = createGuardedFetch({ allowLoopback });
|
|
80595
80717
|
const vercelMaxPages = opts.defaultVercelMaxPages ?? DEFAULT_VERCEL_MAX_PAGES;
|
|
80596
80718
|
const vercelSyncDeadlineMs = opts.vercelSyncDeadlineMs ?? DEFAULT_VERCEL_SYNC_DEADLINE_MS;
|
|
80597
80719
|
const syncWindowMinutes = opts.defaultSyncWindowMinutes ?? DEFAULT_SYNC_WINDOW_MINUTES;
|
|
@@ -80743,26 +80865,26 @@ async function trafficRoutes(app, opts) {
|
|
|
80743
80865
|
throw validationError(parsed.error.issues.map((i) => i.message).join("; "));
|
|
80744
80866
|
}
|
|
80745
80867
|
const { baseUrl, username, applicationPassword, displayName } = parsed.data;
|
|
80746
|
-
await
|
|
80747
|
-
|
|
80748
|
-
|
|
80749
|
-
|
|
80750
|
-
|
|
80751
|
-
|
|
80752
|
-
|
|
80753
|
-
|
|
80754
|
-
|
|
80755
|
-
|
|
80756
|
-
|
|
80757
|
-
|
|
80758
|
-
|
|
80759
|
-
|
|
80760
|
-
|
|
80761
|
-
}
|
|
80762
|
-
const msg = describeError(e);
|
|
80763
|
-
throw providerError(`WordPress traffic probe failed: ${msg}`);
|
|
80868
|
+
await assertWordpressTargetAllowed(baseUrl);
|
|
80869
|
+
try {
|
|
80870
|
+
await pullWordpressEvents({
|
|
80871
|
+
baseUrl,
|
|
80872
|
+
username,
|
|
80873
|
+
applicationPassword,
|
|
80874
|
+
pageSize: 1,
|
|
80875
|
+
maxPages: 1,
|
|
80876
|
+
fetchImpl: wordpressTrafficFetch
|
|
80877
|
+
});
|
|
80878
|
+
} catch (e) {
|
|
80879
|
+
if (e instanceof WordpressTrafficApiError) {
|
|
80880
|
+
throw providerError(
|
|
80881
|
+
`WordPress traffic probe failed (HTTP ${e.status}): ${e.message}${e.body ? ` \u2014 ${e.body}` : ""}`
|
|
80882
|
+
);
|
|
80764
80883
|
}
|
|
80765
|
-
|
|
80884
|
+
if (e instanceof EgressRefusedError && !e.redirected) throw validationError(`WordPress baseUrl rejected: ${e.message}`);
|
|
80885
|
+
const msg = describeError(e);
|
|
80886
|
+
throw providerError(`WordPress traffic probe failed: ${msg}`);
|
|
80887
|
+
}
|
|
80766
80888
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
80767
80889
|
const existing = credentialStore.getConnection(project.name);
|
|
80768
80890
|
credentialStore.upsertConnection({
|
|
@@ -81835,10 +81957,9 @@ async function trafficRoutes(app, opts) {
|
|
|
81835
81957
|
const windowEndIso = windowEnd.toISOString();
|
|
81836
81958
|
const wpPageSize = opts.defaultWordpressPageSize ?? DEFAULT_WP_PAGE_SIZE;
|
|
81837
81959
|
const wpMaxPages = opts.defaultWordpressMaxPages ?? DEFAULT_WP_MAX_PAGES;
|
|
81838
|
-
let pinnedDispatcher;
|
|
81839
81960
|
try {
|
|
81840
81961
|
renewWordpressLease();
|
|
81841
|
-
|
|
81962
|
+
await assertWordpressTargetAllowed(credential.baseUrl);
|
|
81842
81963
|
} catch (e) {
|
|
81843
81964
|
const msg = describeError(e);
|
|
81844
81965
|
markFailed(msg, "PROVIDER_PULL");
|
|
@@ -81858,7 +81979,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81858
81979
|
maxPages: 1,
|
|
81859
81980
|
since: windowStartIso,
|
|
81860
81981
|
until: windowEndIso,
|
|
81861
|
-
|
|
81982
|
+
fetchImpl: wordpressTrafficFetch
|
|
81862
81983
|
});
|
|
81863
81984
|
collected.push(...pageResult.events);
|
|
81864
81985
|
const previousCursor = cursor;
|
|
@@ -81876,10 +81997,8 @@ async function trafficRoutes(app, opts) {
|
|
|
81876
81997
|
} catch (e) {
|
|
81877
81998
|
const msg = describeError(e);
|
|
81878
81999
|
markFailed(msg, "PROVIDER_PULL");
|
|
82000
|
+
if (e instanceof EgressRefusedError && !e.redirected) throw validationError(`WordPress baseUrl rejected: ${msg}`);
|
|
81879
82001
|
throw providerError(`WordPress pull failed: ${msg}`);
|
|
81880
|
-
} finally {
|
|
81881
|
-
await pinnedDispatcher.close().catch(() => {
|
|
81882
|
-
});
|
|
81883
82002
|
}
|
|
81884
82003
|
} else {
|
|
81885
82004
|
auditAction = "traffic.vercel.synced";
|
|
@@ -84383,7 +84502,7 @@ var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
|
|
|
84383
84502
|
import dns3 from "dns/promises";
|
|
84384
84503
|
import http3 from "http";
|
|
84385
84504
|
import https3 from "https";
|
|
84386
|
-
import
|
|
84505
|
+
import net4 from "net";
|
|
84387
84506
|
var SITE_REACHABILITY_USER_AGENT = "Canonry site-liveness (+https://canonry.ai)";
|
|
84388
84507
|
var DEFAULT_SITE_MAX_REDIRECTS = 10;
|
|
84389
84508
|
var DEFAULT_TIMEOUT_MS5 = 1e4;
|
|
@@ -84408,7 +84527,7 @@ function errorCode(err) {
|
|
|
84408
84527
|
return typeof code === "string" ? code : "";
|
|
84409
84528
|
}
|
|
84410
84529
|
async function lookupSiteAddresses(hostname, timeoutMs = DNS_TIMEOUT_MS) {
|
|
84411
|
-
const literalFamily =
|
|
84530
|
+
const literalFamily = net4.isIPv6(hostname) ? 6 : net4.isIPv4(hostname) ? 4 : null;
|
|
84412
84531
|
if (literalFamily) return { kind: "addresses", addresses: [{ address: hostname, family: literalFamily }] };
|
|
84413
84532
|
let timer;
|
|
84414
84533
|
const deadline = new Promise((resolve) => {
|
|
@@ -86374,7 +86493,10 @@ var WORDPRESS_PUBLISH_CHECKS = [
|
|
|
86374
86493
|
};
|
|
86375
86494
|
}
|
|
86376
86495
|
try {
|
|
86377
|
-
const status = await verifyWordpressConnection(
|
|
86496
|
+
const status = await verifyWordpressConnection({
|
|
86497
|
+
...connection,
|
|
86498
|
+
fetchImpl: createGuardedFetch({ allowLoopback: ctx.allowLoopbackWebhooks === true })
|
|
86499
|
+
});
|
|
86378
86500
|
return {
|
|
86379
86501
|
status: CheckStatuses.ok,
|
|
86380
86502
|
code: "wordpress.publish.connected",
|
|
@@ -87023,6 +87145,7 @@ async function doctorRoutes(app, opts) {
|
|
|
87023
87145
|
googleConnectionStore: opts.googleConnectionStore,
|
|
87024
87146
|
bingConnectionStore: opts.bingConnectionStore,
|
|
87025
87147
|
wordpressConnectionStore: opts.wordpressConnectionStore,
|
|
87148
|
+
allowLoopbackWebhooks: opts.allowLoopbackWebhooks,
|
|
87026
87149
|
ga4CredentialStore: opts.ga4CredentialStore,
|
|
87027
87150
|
adsCredentialStore: opts.adsCredentialStore,
|
|
87028
87151
|
getGoogleAuthConfig: opts.getGoogleAuthConfig,
|
|
@@ -87056,6 +87179,7 @@ async function doctorRoutes(app, opts) {
|
|
|
87056
87179
|
googleConnectionStore: opts.googleConnectionStore,
|
|
87057
87180
|
bingConnectionStore: opts.bingConnectionStore,
|
|
87058
87181
|
wordpressConnectionStore: opts.wordpressConnectionStore,
|
|
87182
|
+
allowLoopbackWebhooks: opts.allowLoopbackWebhooks,
|
|
87059
87183
|
ga4CredentialStore: opts.ga4CredentialStore,
|
|
87060
87184
|
adsCredentialStore: opts.adsCredentialStore,
|
|
87061
87185
|
getGoogleAuthConfig: opts.getGoogleAuthConfig,
|
|
@@ -90964,6 +91088,7 @@ async function apiRoutes(app, opts) {
|
|
|
90964
91088
|
googleConnectionStore: opts.googleConnectionStore,
|
|
90965
91089
|
bingConnectionStore: opts.bingConnectionStore,
|
|
90966
91090
|
wordpressConnectionStore: opts.wordpressConnectionStore,
|
|
91091
|
+
allowLoopbackWebhooks: opts.allowLoopbackWebhooks,
|
|
90967
91092
|
ga4CredentialStore: opts.ga4CredentialStore,
|
|
90968
91093
|
adsCredentialStore: opts.adsCredentialStore,
|
|
90969
91094
|
getGoogleAuthConfig: opts.getGoogleAuthConfig,
|
|
@@ -91028,6 +91153,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
91028
91153
|
if (opts.wordpressTrafficCredentialStore) {
|
|
91029
91154
|
const store = opts.wordpressTrafficCredentialStore;
|
|
91030
91155
|
const pullEvents = opts.pullWordpressTrafficEvents ?? listWordpressTrafficEvents;
|
|
91156
|
+
const wordpressTrafficFetch = createGuardedFetch({ allowLoopback: opts.allowLoopbackWebhooks === true });
|
|
91031
91157
|
validators[TrafficSourceTypes.wordpress] = {
|
|
91032
91158
|
validateCredentials: async (source) => {
|
|
91033
91159
|
const record = store.getConnection(source.projectName);
|
|
@@ -91045,7 +91171,10 @@ function buildTrafficSourceValidators(opts) {
|
|
|
91045
91171
|
username: record.username,
|
|
91046
91172
|
applicationPassword: record.applicationPassword,
|
|
91047
91173
|
pageSize: 1,
|
|
91048
|
-
maxPages: 1
|
|
91174
|
+
maxPages: 1,
|
|
91175
|
+
// The probe carries Basic-auth credentials: check the stored URL
|
|
91176
|
+
// and every redirect hop as the sync route does.
|
|
91177
|
+
fetchImpl: wordpressTrafficFetch
|
|
91049
91178
|
});
|
|
91050
91179
|
return {
|
|
91051
91180
|
status: CheckStatuses.ok,
|
|
@@ -92319,6 +92448,7 @@ export {
|
|
|
92319
92448
|
determineCitationState,
|
|
92320
92449
|
computeCitedCompetitorDomains,
|
|
92321
92450
|
resolveTrustProxy,
|
|
92451
|
+
hasProxyHeaders,
|
|
92322
92452
|
USER_SESSION_COOKIE_NAME,
|
|
92323
92453
|
cookieIsSecure,
|
|
92324
92454
|
anyUsersExist,
|
|
@@ -92331,6 +92461,8 @@ export {
|
|
|
92331
92461
|
requireInstanceAdministrator,
|
|
92332
92462
|
hashApiKey,
|
|
92333
92463
|
notProbeRun,
|
|
92464
|
+
writeAuditLog,
|
|
92465
|
+
auditFromRequest,
|
|
92334
92466
|
registerAgentConversationRoutes,
|
|
92335
92467
|
measurementSlotKey,
|
|
92336
92468
|
measurementRunSlotState,
|
|
@@ -92392,6 +92524,9 @@ export {
|
|
|
92392
92524
|
classifyReviewsError,
|
|
92393
92525
|
getUrlInfo,
|
|
92394
92526
|
getCrawlIssues,
|
|
92527
|
+
EgressRefusedError,
|
|
92528
|
+
EgressFailedError,
|
|
92529
|
+
createGuardedFetch,
|
|
92395
92530
|
PLUGIN_DIR,
|
|
92396
92531
|
DUCKDB_SPEC,
|
|
92397
92532
|
CC_CACHE_DIR,
|