@canonry/canonry 5.32.0 → 5.34.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/canonry/references/aeo-analysis.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +12 -3
- package/assets/assets/{AuditHistoryPanel-DW2aeqJP.js → AuditHistoryPanel-Ddg6JIi3.js} +1 -1
- package/assets/assets/{BacklinksPage-DroPpjd3.js → BacklinksPage-DI9BmaYt.js} +1 -1
- package/assets/assets/{HistoryPage-2o6p-ZDF.js → HistoryPage-CACIL005.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-D_i3t0DT.js +1 -0
- package/assets/assets/{ProjectPage-CXV64SBB.js → ProjectPage-d1djqfh3.js} +7 -7
- package/assets/assets/{RunRow-CIhSIAoR.js → RunRow-CjhvvA6g.js} +1 -1
- package/assets/assets/{RunsPage-lO9xMTSl.js → RunsPage-DuBhw2uc.js} +1 -1
- package/assets/assets/{SettingsPage-CBwns7cb.js → SettingsPage-MUpfB0HH.js} +1 -1
- package/assets/assets/{SiteHealthSection-C372wrfT.js → SiteHealthSection-6qdXlFcO.js} +3 -3
- package/assets/assets/{TrafficPage-DGyS-IjJ.js → TrafficPage--nGSOgqn.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BBaz_ivE.js → TrafficSourceDetailPage-BPqNmuws.js} +1 -1
- package/assets/assets/{extract-error-message-QcHrDOjc.js → extract-error-message-B7OeWj0x.js} +1 -1
- package/assets/assets/{index-CbnQjcJj.js → index-C4lreGwR.js} +24 -24
- package/assets/assets/index-DX5M4MHr.css +1 -0
- package/assets/assets/{react-sigma_core.esm.min-D9tjGma4.js → react-sigma_core.esm.min-jiMNkILi.js} +1 -1
- package/assets/assets/v2-overview-adapter-Caj3bHQE.js +1 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-DFIK6IPP.js → chunk-25H4K7KL.js} +4 -4
- package/dist/{chunk-6UO23AKO.js → chunk-AIAVZY6L.js} +12 -10
- package/dist/{chunk-7YS62CJ4.js → chunk-G2K5KCUY.js} +172 -34
- package/dist/{chunk-RCX6I3DX.js → chunk-NOIK3FUO.js} +75 -4
- package/dist/{chunk-YVWYQBXW.js → chunk-NZCKJMLC.js} +2 -2
- package/dist/{chunk-WZL73LY3.js → chunk-QTETJEHI.js} +2 -2
- package/dist/{chunk-NEP2PJ2T.js → chunk-VHPGBX4P.js} +3 -3
- package/dist/cli.js +53 -13
- package/dist/{demo-server-ERFY4Q2P.js → demo-server-UI24OAHX.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-PULUYOPR.js → intelligence-service-ZKFLGJ23.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +8 -8
- package/assets/assets/MeasurementPropertyPage-VTGLyVuD.js +0 -1
- package/assets/assets/index-D0bY1wpI.css +0 -1
- package/assets/assets/v2-overview-adapter-CLlJkExr.js +0 -1
|
@@ -291,6 +291,7 @@ import {
|
|
|
291
291
|
compareSemver,
|
|
292
292
|
competitorBatchRequestSchema,
|
|
293
293
|
competitorDtoSchema,
|
|
294
|
+
competitorLabelFromDomain,
|
|
294
295
|
competitorLandscapeQuerySchema,
|
|
295
296
|
competitorLandscapeResponseSchema,
|
|
296
297
|
compileBrandAliases,
|
|
@@ -720,6 +721,7 @@ import {
|
|
|
720
721
|
resolveMeasurementRunQueryScope,
|
|
721
722
|
resolveMeasurementRunScope,
|
|
722
723
|
resolveNegativeReviewMaxStars,
|
|
724
|
+
resolveProjectQualifiedAliases,
|
|
723
725
|
resolveProviderModel,
|
|
724
726
|
resolveRunDispatchModes,
|
|
725
727
|
resolveSnapshotRequestQueries,
|
|
@@ -774,6 +776,7 @@ import {
|
|
|
774
776
|
shareOfVoiceReason,
|
|
775
777
|
shareOfVoiceSummary,
|
|
776
778
|
shiftIsoCalendarDate,
|
|
779
|
+
simpleMeasurementCompetitorNames,
|
|
777
780
|
simpleMeasurementDefinitionSchema,
|
|
778
781
|
siteAuditLivePageHealthSchema,
|
|
779
782
|
siteAuditPageFactorSchema,
|
|
@@ -866,7 +869,7 @@ import {
|
|
|
866
869
|
wordpressSchemaDeployResultDtoSchema,
|
|
867
870
|
wordpressSchemaStatusResultDtoSchema,
|
|
868
871
|
wordpressStatusDtoSchema
|
|
869
|
-
} from "./chunk-
|
|
872
|
+
} from "./chunk-NOIK3FUO.js";
|
|
870
873
|
|
|
871
874
|
// src/intelligence-service.ts
|
|
872
875
|
import { eq as eq82, desc as desc36, asc as asc15, and as and69, ne as ne14, or as or20, inArray as inArray32, gte as gte22, lte as lte20, isNull as isNull16, sql as sql31, exists } from "drizzle-orm";
|
|
@@ -1017,6 +1020,12 @@ var projects = sqliteTable("projects", {
|
|
|
1017
1020
|
canonicalDomain: text("canonical_domain").notNull(),
|
|
1018
1021
|
ownedDomains: text("owned_domains", { mode: "json" }).$type().notNull().default([]),
|
|
1019
1022
|
aliases: text("aliases", { mode: "json" }).$type().notNull().default([]),
|
|
1023
|
+
/**
|
|
1024
|
+
* Operator-chosen subset of `aliases` that Simple sentiment tells the
|
|
1025
|
+
* evaluator are this brand's own names. Frozen into each Simple run's
|
|
1026
|
+
* sidecar at dispatch; never read by mention detection or query classes.
|
|
1027
|
+
*/
|
|
1028
|
+
qualifiedAliases: text("qualified_aliases", { mode: "json" }).$type().notNull().default([]),
|
|
1020
1029
|
country: text("country").notNull(),
|
|
1021
1030
|
language: text("language").notNull(),
|
|
1022
1031
|
tags: text("tags", { mode: "json" }).$type().notNull().default([]),
|
|
@@ -8172,6 +8181,16 @@ var MIGRATION_VERSIONS = [
|
|
|
8172
8181
|
name: "aero-ledger-conversation-ids",
|
|
8173
8182
|
statements: [],
|
|
8174
8183
|
run: dropAeroLedgerConversationKeys
|
|
8184
|
+
},
|
|
8185
|
+
{
|
|
8186
|
+
// Sentiment-only project setting: the aliases the evaluator is told are
|
|
8187
|
+
// the brand's own names. Defaulted, so an older writer that omits it and
|
|
8188
|
+
// every existing project read as not opted in.
|
|
8189
|
+
version: 166,
|
|
8190
|
+
name: "projects-qualified-aliases",
|
|
8191
|
+
statements: [
|
|
8192
|
+
`ALTER TABLE projects ADD COLUMN qualified_aliases TEXT NOT NULL DEFAULT '[]'`
|
|
8193
|
+
]
|
|
8175
8194
|
}
|
|
8176
8195
|
];
|
|
8177
8196
|
var AERO_LEDGER_REBUILDS = [
|
|
@@ -13504,7 +13523,10 @@ function selectSentimentSources(db, projectId, filters) {
|
|
|
13504
13523
|
const selectedRuns = db.select(sourceRunColumns).from(runs).where(and3(...conditions)).orderBy(desc2(runs.createdAt)).limit(101).all();
|
|
13505
13524
|
if (selectedRuns.length > 100) throw validationError("Selection exceeds 100 runs; narrow the date range.");
|
|
13506
13525
|
if (requestedIds && new Set(selectedRuns.map((row) => row.id)).size !== new Set(requestedIds).size) throw validationError("One or more selected runs are unavailable in this project.");
|
|
13507
|
-
const out = { assessments: [], runIds: selectedRuns.map((row) => row.id), sourceCoverage: { expected: 0, completed: 0 }, skipped: {}, skippedByRun: {} };
|
|
13526
|
+
const out = { assessments: [], runIds: selectedRuns.map((row) => row.id), sourceCoverage: { expected: 0, completed: 0 }, sourceSlotIds: { expected: [], completed: [] }, skipped: {}, skippedByRun: {} };
|
|
13527
|
+
const recordSlots = (runId, slots) => {
|
|
13528
|
+
for (const key of ["expected", "completed"]) out.sourceSlotIds[key].push(...slots[key].map((slot) => pairKey(runId, slot)));
|
|
13529
|
+
};
|
|
13508
13530
|
let currentRunId = "";
|
|
13509
13531
|
const skip = (reason, count4 = 1) => {
|
|
13510
13532
|
out.skipped[reason] = (out.skipped[reason] ?? 0) + count4;
|
|
@@ -13531,6 +13553,7 @@ function selectSentimentSources(db, projectId, filters) {
|
|
|
13531
13553
|
const completedSuperseded = run.status === "superseded" && Boolean(db.select({ sequence: sentimentCompletionReceipts.sequence }).from(sentimentCompletionReceipts).where(and3(eq8(sentimentCompletionReceipts.projectId, projectId), eq8(sentimentCompletionReceipts.runId, run.id))).get());
|
|
13532
13554
|
if (run.status !== "completed" && !completedSuperseded) {
|
|
13533
13555
|
const coverage = selectedSlotCoverage(db, projectId, run, filters, plans);
|
|
13556
|
+
recordSlots(run.id, coverage.slotIds);
|
|
13534
13557
|
out.sourceCoverage.expected += coverage.expected;
|
|
13535
13558
|
out.sourceCoverage.completed += coverage.completed;
|
|
13536
13559
|
skip(run.status === "failed" ? "failed-run" : "incomplete-run");
|
|
@@ -13555,6 +13578,7 @@ function selectSentimentSources(db, projectId, filters) {
|
|
|
13555
13578
|
out.sourceCoverage.completed += slots.executed;
|
|
13556
13579
|
if (!slots.planned || !slots.readable || slots.missing.length > 0 || slots.hasUnboundSnapshot) {
|
|
13557
13580
|
const coverage = selectedSlotCoverage(db, projectId, run, filters, plans);
|
|
13581
|
+
recordSlots(run.id, coverage.slotIds);
|
|
13558
13582
|
out.sourceCoverage.expected = beforeCoverage.expected + coverage.expected;
|
|
13559
13583
|
out.sourceCoverage.completed = beforeCoverage.completed + coverage.completed;
|
|
13560
13584
|
skip("incomplete-run");
|
|
@@ -13626,12 +13650,14 @@ function selectSentimentSources(db, projectId, filters) {
|
|
|
13626
13650
|
out.sourceCoverage.completed += expected.filter((key) => recorded.has(key)).length;
|
|
13627
13651
|
if (!expected.length || expected.some((key) => !recorded.has(key)) || snapshots.length !== expected.length || snapshots.some(({ snapshot, query }) => !expectedSlots.has(simpleSlotKey(query?.queryId, snapshot.provider)))) {
|
|
13628
13652
|
const coverage = selectedSlotCoverage(db, projectId, run, filters, plans);
|
|
13653
|
+
recordSlots(run.id, coverage.slotIds);
|
|
13629
13654
|
out.sourceCoverage.expected = beforeCoverage.expected + coverage.expected;
|
|
13630
13655
|
out.sourceCoverage.completed = beforeCoverage.completed + coverage.completed;
|
|
13631
13656
|
skip("incomplete-run");
|
|
13632
13657
|
continue;
|
|
13633
13658
|
}
|
|
13634
13659
|
const identity = definition.identity;
|
|
13660
|
+
const qualifiedAliases = [...identity.qualifiedAliases ?? []].sort();
|
|
13635
13661
|
const names = effectiveBrandNames(identity);
|
|
13636
13662
|
const urls = [identity.canonicalDomain, ...identity.ownedDomains];
|
|
13637
13663
|
const mentionNotApplicable = !names.length && !urls.some((url) => url.trim());
|
|
@@ -13653,14 +13679,15 @@ function selectSentimentSources(db, projectId, filters) {
|
|
|
13653
13679
|
language: definition.language,
|
|
13654
13680
|
queryText: query.queryText,
|
|
13655
13681
|
revision: null,
|
|
13656
|
-
subject: { key: projectId, name: identity.displayName, aliases: [...names], identityAliases: [], urls: [...urls], mentionNotApplicable },
|
|
13682
|
+
subject: { key: projectId, name: identity.displayName, aliases: [...names], identityAliases: [...qualifiedAliases], urls: [...urls], mentionNotApplicable },
|
|
13657
13683
|
edges: [edge]
|
|
13658
13684
|
});
|
|
13659
13685
|
}
|
|
13660
13686
|
}
|
|
13661
|
-
const selectedSlots = new Set(out.assessments.slice(beforeAssessments).map((item2) => item2.snapshotId))
|
|
13662
|
-
|
|
13663
|
-
out.sourceCoverage.
|
|
13687
|
+
const selectedSlots = [...new Set(out.assessments.slice(beforeAssessments).map((item2) => item2.snapshotId))];
|
|
13688
|
+
recordSlots(run.id, { expected: selectedSlots, completed: selectedSlots });
|
|
13689
|
+
out.sourceCoverage.expected = beforeCoverage.expected + selectedSlots.length;
|
|
13690
|
+
out.sourceCoverage.completed = beforeCoverage.completed + selectedSlots.length;
|
|
13664
13691
|
}
|
|
13665
13692
|
return out;
|
|
13666
13693
|
}
|
|
@@ -13707,7 +13734,8 @@ function indexedPlan(db, projectId, versionId, cache) {
|
|
|
13707
13734
|
}
|
|
13708
13735
|
function selectedSlotCoverage(db, projectId, run, filters, plans) {
|
|
13709
13736
|
const expected = /* @__PURE__ */ new Set(), completed = /* @__PURE__ */ new Set();
|
|
13710
|
-
|
|
13737
|
+
const coverage = () => ({ expected: expected.size, completed: completed.size, slotIds: { expected: [...expected], completed: [...completed] } });
|
|
13738
|
+
if (run.status === "failed") return coverage();
|
|
13711
13739
|
const snapshots = db.select({ queryId: querySnapshots.queryId, queryText: querySnapshots.queryText, provider: querySnapshots.provider, servedModel: querySnapshots.servedModel, measurementExecutionId: querySnapshots.measurementExecutionId }).from(querySnapshots).where(eq8(querySnapshots.runId, run.id)).all();
|
|
13712
13740
|
const count4 = (key, edge, recorded) => {
|
|
13713
13741
|
if (!matchesSentimentEdge(edge, filters)) return;
|
|
@@ -13716,7 +13744,7 @@ function selectedSlotCoverage(db, projectId, run, filters, plans) {
|
|
|
13716
13744
|
};
|
|
13717
13745
|
if (run.measurementPlanVersionId) {
|
|
13718
13746
|
const indexed = indexedPlan(db, projectId, run.measurementPlanVersionId, plans);
|
|
13719
|
-
if (!indexed || filters.revision !== void 0 && indexed.revision !== filters.revision) return
|
|
13747
|
+
if (!indexed || filters.revision !== void 0 && indexed.revision !== filters.revision) return coverage();
|
|
13720
13748
|
const bySlot = /* @__PURE__ */ new Map();
|
|
13721
13749
|
for (const snapshot of snapshots) if (snapshot.measurementExecutionId !== null) keep(bySlot, pairKey(snapshot.measurementExecutionId, snapshot.provider), snapshot);
|
|
13722
13750
|
const scopedTargets = run.measurementScope?.resolvedTargets.length ? new Set(run.measurementScope.resolvedTargets) : null;
|
|
@@ -13733,10 +13761,10 @@ function selectedSlotCoverage(db, projectId, run, filters, plans) {
|
|
|
13733
13761
|
}
|
|
13734
13762
|
}
|
|
13735
13763
|
} else {
|
|
13736
|
-
if (filters.revision !== void 0 || filters.scope && filters.scope !== "project" || filters.marketKey) return
|
|
13764
|
+
if (filters.revision !== void 0 || filters.scope && filters.scope !== "project" || filters.marketKey) return coverage();
|
|
13737
13765
|
const row = db.select({ definition: simpleMeasurementDefinitions.definition }).from(simpleMeasurementDefinitions).where(eq8(simpleMeasurementDefinitions.runId, run.id)).get();
|
|
13738
13766
|
const stored = simpleMeasurementDefinitionSchema.safeParse(row?.definition);
|
|
13739
|
-
if (!stored.success) return
|
|
13767
|
+
if (!stored.success) return coverage();
|
|
13740
13768
|
const sourceQuery = simpleSourceQueries(stored.data);
|
|
13741
13769
|
const bySlot = /* @__PURE__ */ new Map();
|
|
13742
13770
|
for (const snapshot of snapshots) {
|
|
@@ -13749,7 +13777,7 @@ function selectedSlotCoverage(db, projectId, run, filters, plans) {
|
|
|
13749
13777
|
count4(`${query.queryId}:${engine.provider}`, { queryKey: query.queryId, queryText: query.queryText, executionNodeKey: null, queryClass: query.queryClass, propertyKey: run.projectId, groupKeys: [], marketKeys: [], provider: engine.provider, sourceModel: engine.requestedModel, servedModel: snapshot?.servedModel ?? null, context: stored.data.location }, Boolean(snapshot));
|
|
13750
13778
|
}
|
|
13751
13779
|
}
|
|
13752
|
-
return
|
|
13780
|
+
return coverage();
|
|
13753
13781
|
}
|
|
13754
13782
|
function matchesSentimentEdge(edge, filters) {
|
|
13755
13783
|
if (edge.queryClass !== (filters.queryClass ?? "branded")) return false;
|
|
@@ -13836,7 +13864,7 @@ function sentimentClassifierInput(source, definition) {
|
|
|
13836
13864
|
}
|
|
13837
13865
|
|
|
13838
13866
|
// ../api-routes/src/sentiment-service.ts
|
|
13839
|
-
var DISCLOSURE = "Experimental English sentiment classification. Enabling sends each stored answer's text to TypeSafe with the frozen subject identity (name, aliases and URLs), the tracked query text and query class, the answer engine with its requested and served models, the location, and internal query, subject, Property, group and market identifiers.";
|
|
13867
|
+
var DISCLOSURE = "Experimental English sentiment classification. Enabling sends each stored answer's text to TypeSafe with the frozen subject identity (name, aliases, qualified aliases and URLs), the tracked query text and query class, the answer engine with its requested and served models, the location, and internal query, subject, Property, group and market identifiers.";
|
|
13840
13868
|
var FULL_ROWS = { include: /* @__PURE__ */ new Set(["assessments", "locations"]), limit: null, after: null };
|
|
13841
13869
|
var COMPACT_ROWS = { include: /* @__PURE__ */ new Set(), limit: null, after: null };
|
|
13842
13870
|
var OPERATIONAL_OUTCOMES = /* @__PURE__ */ new Set([SentimentOutcomes.pending, SentimentOutcomes.running, SentimentOutcomes["waiting-to-retry"], SentimentOutcomes.failed, SentimentOutcomes.canceled]);
|
|
@@ -14154,26 +14182,12 @@ var SentimentService = class {
|
|
|
14154
14182
|
const selections = resolved.map((entry) => entry.selection), sources = resolved.map((entry) => entry.source);
|
|
14155
14183
|
const runIds = selections.flatMap((selection2) => selection2.runId ? [selection2.runId] : []).sort();
|
|
14156
14184
|
const definitions = new Set(selections.flatMap((selection2) => selection2.evaluationDefinitionId ? [selection2.evaluationDefinitionId] : []));
|
|
14157
|
-
const
|
|
14158
|
-
const revisions = new Set(selections.map((selection2) => selection2.revision));
|
|
14159
|
-
const source = { assessments: sources.flatMap((source2) => source2.assessments), runIds, sourceCoverage: { expected: sources.reduce((sum, source2) => sum + source2.sourceCoverage.expected, 0), completed: sources.reduce((sum, source2) => sum + source2.sourceCoverage.completed, 0) }, skipped: sources.reduce((counts2, source2) => {
|
|
14185
|
+
const source = { assessments: sources.flatMap((source2) => source2.assessments), runIds, sourceCoverage: { expected: sources.reduce((sum, source2) => sum + source2.sourceCoverage.expected, 0), completed: sources.reduce((sum, source2) => sum + source2.sourceCoverage.completed, 0) }, sourceSlotIds: { expected: sources.flatMap((source2) => source2.sourceSlotIds.expected), completed: sources.flatMap((source2) => source2.sourceSlotIds.completed) }, skipped: sources.reduce((counts2, source2) => {
|
|
14160
14186
|
for (const [reason2, count4] of Object.entries(source2.skipped)) counts2[reason2] = (counts2[reason2] ?? 0) + count4;
|
|
14161
14187
|
return counts2;
|
|
14162
14188
|
}, {}) };
|
|
14163
14189
|
const selection = { ...selections[0], runId: runIds.length === 1 ? runIds[0] : null, runIds: runIds.length > 1 ? runIds : void 0, evaluationDefinitionId: definitions.size === 1 ? [...definitions][0] : null };
|
|
14164
|
-
|
|
14165
|
-
const subjects = /* @__PURE__ */ new Map(), queries2 = /* @__PURE__ */ new Map();
|
|
14166
|
-
const filter = sourceFilter(query);
|
|
14167
|
-
for (const assessment of source.assessments) {
|
|
14168
|
-
const identity = sentimentHash(assessment.subject), prior = subjects.get(assessment.subject.key);
|
|
14169
|
-
if (prior && prior !== identity) reason ??= "subject-identity-changed";
|
|
14170
|
-
subjects.set(assessment.subject.key, identity);
|
|
14171
|
-
for (const edge of assessment.edges.filter((edge2) => matchesSentimentEdge(edge2, filter))) {
|
|
14172
|
-
const previous = queries2.get(edge.queryKey);
|
|
14173
|
-
if (previous && previous !== edge.queryText) reason ??= "query-identity-changed";
|
|
14174
|
-
queries2.set(edge.queryKey, edge.queryText);
|
|
14175
|
-
}
|
|
14176
|
-
}
|
|
14190
|
+
const reason = incompatibleSelectionReason(selections, source.assessments, sourceFilter(query));
|
|
14177
14191
|
return { selection, items, source, reason, fingerprint: sentimentHash(selections) };
|
|
14178
14192
|
}
|
|
14179
14193
|
/** Incompatible pooled identities replace every score in the response with an unavailable one. */
|
|
@@ -14205,6 +14219,7 @@ var SentimentService = class {
|
|
|
14205
14219
|
overview(projectId, runIds, location) {
|
|
14206
14220
|
const settings = this.settings(projectId);
|
|
14207
14221
|
const configured = settings.enabled && settings.installEnabled;
|
|
14222
|
+
const reads = [];
|
|
14208
14223
|
const headline = (queryClass) => {
|
|
14209
14224
|
if (!configured || !runIds.length) {
|
|
14210
14225
|
const empty = { mode: "simple", scope: "project", queryClass, runId: null, revision: null, evaluationDefinitionId: null };
|
|
@@ -14212,10 +14227,12 @@ var SentimentService = class {
|
|
|
14212
14227
|
return { state: result2.state, reason: result2.reason, provisional: result2.provisional, coverage: result2.coverage, score: result2.score, selection: empty, runIds: [] };
|
|
14213
14228
|
}
|
|
14214
14229
|
const read = this.readSelection(projectId, { mode: "auto", scope: "project", queryClass, runIds, ...location ? { location } : {} });
|
|
14230
|
+
reads.push(read);
|
|
14215
14231
|
const { summary: result } = this.present(read, settings, null);
|
|
14216
14232
|
return { state: result.state, reason: result.reason, provisional: result.provisional, coverage: result.coverage, score: result.score, selection: result.selection, runIds: result.selection.runIds ?? (result.selection.runId ? [result.selection.runId] : []) };
|
|
14217
14233
|
};
|
|
14218
|
-
|
|
14234
|
+
const branded = headline("branded"), nonBrand = headline("non-brand");
|
|
14235
|
+
return { configured, branded, nonBrand, overall: overallSentiment(reads, !configured) };
|
|
14219
14236
|
}
|
|
14220
14237
|
evidence(projectId, query, limit, cursor) {
|
|
14221
14238
|
const { assessmentId, ...sourceSelection } = query;
|
|
@@ -14276,7 +14293,44 @@ var SentimentService = class {
|
|
|
14276
14293
|
};
|
|
14277
14294
|
var jobAttemptJoin = and4(eq9(sentimentJobItems.workItemId, sentimentAttempts.workItemId), eq9(sentimentJobItems.projectId, sentimentAttempts.projectId));
|
|
14278
14295
|
function emptySource() {
|
|
14279
|
-
return { assessments: [], runIds: [], sourceCoverage: { expected: 0, completed: 0 }, skipped: {} };
|
|
14296
|
+
return { assessments: [], runIds: [], sourceCoverage: { expected: 0, completed: 0 }, sourceSlotIds: { expected: [], completed: [] }, skipped: {} };
|
|
14297
|
+
}
|
|
14298
|
+
function incompatibleSelectionReason(selections, assessments, filter) {
|
|
14299
|
+
const definitions = new Set(selections.flatMap((selection) => selection.evaluationDefinitionId ? [selection.evaluationDefinitionId] : []));
|
|
14300
|
+
const modes = new Set(selections.map((selection) => selection.mode));
|
|
14301
|
+
const revisions = new Set(selections.map((selection) => selection.revision));
|
|
14302
|
+
let reason = definitions.size > 1 ? "evaluation-definition-changed" : modes.size > 1 ? "source-mode-changed" : revisions.size > 1 ? "measurement-revision-changed" : null;
|
|
14303
|
+
const subjects = /* @__PURE__ */ new Map(), queries2 = /* @__PURE__ */ new Map();
|
|
14304
|
+
for (const assessment of assessments) {
|
|
14305
|
+
const identity = sentimentHash(assessment.subject), prior = subjects.get(assessment.subject.key);
|
|
14306
|
+
if (prior && prior !== identity) reason ??= "subject-identity-changed";
|
|
14307
|
+
subjects.set(assessment.subject.key, identity);
|
|
14308
|
+
for (const edge of assessment.edges) {
|
|
14309
|
+
if (filter && !matchesSentimentEdge(edge, filter)) continue;
|
|
14310
|
+
const previous = queries2.get(edge.queryKey);
|
|
14311
|
+
if (previous && previous !== edge.queryText) reason ??= "query-identity-changed";
|
|
14312
|
+
queries2.set(edge.queryKey, edge.queryText);
|
|
14313
|
+
}
|
|
14314
|
+
}
|
|
14315
|
+
return reason;
|
|
14316
|
+
}
|
|
14317
|
+
function overallSentiment(reads, disabled) {
|
|
14318
|
+
const assessmentKey = (item2) => JSON.stringify([item2.snapshotId, item2.subject.key]);
|
|
14319
|
+
const assessments = reads.flatMap((read) => read.source.assessments);
|
|
14320
|
+
const eligible = [...new Map(assessments.map((item2) => [assessmentKey(item2), item2])).values()];
|
|
14321
|
+
const selected = [...new Map(reads.flatMap((read) => read.items).map((item2) => [assessmentKey(item2.member), item2])).values()];
|
|
14322
|
+
const slots = {
|
|
14323
|
+
expected: new Set(reads.flatMap((read) => read.source.sourceSlotIds.expected)).size,
|
|
14324
|
+
completed: new Set(reads.flatMap((read) => read.source.sourceSlotIds.completed)).size
|
|
14325
|
+
};
|
|
14326
|
+
const result = scoreAssessments(selected, eligible, { disabled, slots, incomplete: reads.some((read) => Boolean(read.source.skipped["incomplete-run"])) });
|
|
14327
|
+
const reason = reads.find((read) => read.reason)?.reason ?? incompatibleSelectionReason(reads.map((read) => read.selection), assessments);
|
|
14328
|
+
return {
|
|
14329
|
+
...result,
|
|
14330
|
+
...!disabled && reason ? { state: "unsupported", reason, provisional: true, score: aggregateSentiment([]).score } : {},
|
|
14331
|
+
queryClass: "all",
|
|
14332
|
+
runIds: [...new Set(reads.flatMap((read) => read.source.runIds))].sort()
|
|
14333
|
+
};
|
|
14280
14334
|
}
|
|
14281
14335
|
function append2(map, key, value) {
|
|
14282
14336
|
const list = map.get(key);
|
|
@@ -16832,6 +16886,7 @@ async function projectRoutes(app, opts) {
|
|
|
16832
16886
|
const id = crypto9.randomUUID();
|
|
16833
16887
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16834
16888
|
const nextAliases = normalizeProjectAliases(body.displayName, body.aliases ?? []);
|
|
16889
|
+
const qualifiedAliases = body.qualifiedAliases === void 0 ? [] : requireQualifiedAliases({ displayName: body.displayName, aliases: nextAliases }, body.qualifiedAliases, []);
|
|
16835
16890
|
const inserted = app.db.transaction((tx) => {
|
|
16836
16891
|
const result = tx.insert(projects).values({
|
|
16837
16892
|
id,
|
|
@@ -16840,6 +16895,7 @@ async function projectRoutes(app, opts) {
|
|
|
16840
16895
|
canonicalDomain,
|
|
16841
16896
|
ownedDomains: body.ownedDomains ?? [],
|
|
16842
16897
|
aliases: nextAliases,
|
|
16898
|
+
qualifiedAliases,
|
|
16843
16899
|
country: body.country,
|
|
16844
16900
|
language: body.language,
|
|
16845
16901
|
tags: body.tags ?? [],
|
|
@@ -16926,6 +16982,9 @@ async function projectRoutes(app, opts) {
|
|
|
16926
16982
|
const nextNegativeReviewMaxStars = body.negativeReviewMaxStars !== void 0 ? body.negativeReviewMaxStars : existing ? readNegativeReviewMaxStars(app.db, existing.id) : null;
|
|
16927
16983
|
const nextMeasurement = body.measurement ?? existing?.measurement ?? DEFAULT_MEASUREMENT_CONFIG;
|
|
16928
16984
|
const nextAliases = normalizeProjectAliases(body.displayName, body.aliases ?? []);
|
|
16985
|
+
const nextIdentity = { displayName: body.displayName, aliases: nextAliases };
|
|
16986
|
+
const liveCompetitors = existing ? liveCompetitorNames(app.db, existing.id) : [];
|
|
16987
|
+
const nextQualifiedAliases = body.qualifiedAliases !== void 0 ? requireQualifiedAliases(nextIdentity, body.qualifiedAliases, liveCompetitors) : resolveProjectQualifiedAliases(nextIdentity, existing?.qualifiedAliases ?? [], liveCompetitors).value;
|
|
16929
16988
|
if (existing) {
|
|
16930
16989
|
const prevAliases = existing.aliases;
|
|
16931
16990
|
const aliasesChanged = !aliasArraysEqual(prevAliases, nextAliases);
|
|
@@ -16935,6 +16994,7 @@ async function projectRoutes(app, opts) {
|
|
|
16935
16994
|
canonicalDomain: body.canonicalDomain,
|
|
16936
16995
|
ownedDomains: body.ownedDomains ?? [],
|
|
16937
16996
|
aliases: nextAliases,
|
|
16997
|
+
qualifiedAliases: nextQualifiedAliases,
|
|
16938
16998
|
country: body.country,
|
|
16939
16999
|
language: body.language,
|
|
16940
17000
|
tags: body.tags ?? [],
|
|
@@ -16973,6 +17033,7 @@ async function projectRoutes(app, opts) {
|
|
|
16973
17033
|
canonicalDomain: body.canonicalDomain,
|
|
16974
17034
|
ownedDomains: body.ownedDomains ?? [],
|
|
16975
17035
|
aliases: nextAliases,
|
|
17036
|
+
qualifiedAliases: nextQualifiedAliases,
|
|
16976
17037
|
country: body.country,
|
|
16977
17038
|
language: body.language,
|
|
16978
17039
|
tags: body.tags ?? [],
|
|
@@ -17161,6 +17222,11 @@ async function projectRoutes(app, opts) {
|
|
|
17161
17222
|
eq15(schedules.kind, SchedulableRunKinds["answer-visibility"])
|
|
17162
17223
|
)).get();
|
|
17163
17224
|
const notificationRows = app.db.select().from(notifications).where(eq15(notifications.projectId, project.id)).all();
|
|
17225
|
+
const exportedQualifiedAliases = resolveProjectQualifiedAliases(
|
|
17226
|
+
project,
|
|
17227
|
+
project.qualifiedAliases,
|
|
17228
|
+
comps.map((c) => competitorLabelFromDomain(c.domain))
|
|
17229
|
+
).value;
|
|
17164
17230
|
const config = {
|
|
17165
17231
|
apiVersion: "canonry/v1",
|
|
17166
17232
|
kind: "Project",
|
|
@@ -17173,6 +17239,7 @@ async function projectRoutes(app, opts) {
|
|
|
17173
17239
|
canonicalDomain: project.canonicalDomain,
|
|
17174
17240
|
ownedDomains: project.ownedDomains,
|
|
17175
17241
|
aliases: project.aliases,
|
|
17242
|
+
...exportedQualifiedAliases.length ? { qualifiedAliases: exportedQualifiedAliases } : {},
|
|
17176
17243
|
country: project.country,
|
|
17177
17244
|
language: project.language,
|
|
17178
17245
|
queries: qs.map((q) => q.query),
|
|
@@ -17212,6 +17279,32 @@ function assertProviderModelScope(request, current, next, nextProviders) {
|
|
|
17212
17279
|
if (providerModelsEqual(effectiveCurrent, effectiveNext)) return;
|
|
17213
17280
|
requireScope(request, SETTINGS_WRITE_SCOPE);
|
|
17214
17281
|
}
|
|
17282
|
+
function requireQualifiedAliases(identity, requested, competitorNames) {
|
|
17283
|
+
const resolved = resolveProjectQualifiedAliases(identity, requested, competitorNames);
|
|
17284
|
+
if (resolved.rejected.length > 0) {
|
|
17285
|
+
const reasons = resolved.rejected.map((entry) => `${entry.name} (${entry.reason})`).join(", ");
|
|
17286
|
+
throw validationError(`Rejected qualifiedAliases: ${reasons}`, {
|
|
17287
|
+
rejectedQualifiedAliases: resolved.rejected
|
|
17288
|
+
});
|
|
17289
|
+
}
|
|
17290
|
+
return resolved.value;
|
|
17291
|
+
}
|
|
17292
|
+
function liveCompetitorNames(db, projectId) {
|
|
17293
|
+
return db.select({ domain: competitors.domain }).from(competitors).where(eq15(competitors.projectId, projectId)).all().map((row) => competitorLabelFromDomain(row.domain));
|
|
17294
|
+
}
|
|
17295
|
+
function pruneQualifiedAliasesForCompetitors(tx, projectId, now) {
|
|
17296
|
+
const project = tx.select({
|
|
17297
|
+
displayName: projects.displayName,
|
|
17298
|
+
aliases: projects.aliases,
|
|
17299
|
+
qualifiedAliases: projects.qualifiedAliases
|
|
17300
|
+
}).from(projects).where(eq15(projects.id, projectId)).get();
|
|
17301
|
+
if (!project?.qualifiedAliases.length) return [];
|
|
17302
|
+
const kept = resolveProjectQualifiedAliases(project, project.qualifiedAliases, liveCompetitorNames(tx, projectId)).value;
|
|
17303
|
+
const dropped = project.qualifiedAliases.filter((name) => !kept.includes(name));
|
|
17304
|
+
if (dropped.length === 0) return [];
|
|
17305
|
+
tx.update(projects).set({ qualifiedAliases: kept, updatedAt: now }).where(eq15(projects.id, projectId)).run();
|
|
17306
|
+
return dropped;
|
|
17307
|
+
}
|
|
17215
17308
|
function providerModelsEqual(a, b) {
|
|
17216
17309
|
const aKeys = Object.keys(a);
|
|
17217
17310
|
if (aKeys.length !== Object.keys(b).length) return false;
|
|
@@ -17225,6 +17318,7 @@ function formatProject(row, negativeReviewMaxStars) {
|
|
|
17225
17318
|
canonicalDomain: row.canonicalDomain,
|
|
17226
17319
|
ownedDomains: row.ownedDomains,
|
|
17227
17320
|
aliases: row.aliases,
|
|
17321
|
+
qualifiedAliases: row.qualifiedAliases,
|
|
17228
17322
|
country: row.country,
|
|
17229
17323
|
language: row.language,
|
|
17230
17324
|
tags: row.tags,
|
|
@@ -17943,12 +18037,16 @@ async function competitorRoutes(app) {
|
|
|
17943
18037
|
createdAt: now
|
|
17944
18038
|
}).run();
|
|
17945
18039
|
}
|
|
18040
|
+
const droppedQualifiedAliases = pruneQualifiedAliasesForCompetitors(tx, project.id, now);
|
|
17946
18041
|
writeAuditLog(tx, {
|
|
17947
18042
|
projectId: project.id,
|
|
17948
18043
|
actor: "api",
|
|
17949
18044
|
action: "competitors.replaced",
|
|
17950
18045
|
entityType: "competitor",
|
|
17951
|
-
diff: {
|
|
18046
|
+
diff: {
|
|
18047
|
+
competitors: normalizedCompetitors,
|
|
18048
|
+
...droppedQualifiedAliases.length ? { droppedQualifiedAliases } : {}
|
|
18049
|
+
}
|
|
17952
18050
|
});
|
|
17953
18051
|
});
|
|
17954
18052
|
const rows = app.db.select().from(competitors).where(eq19(competitors.projectId, project.id)).all();
|
|
@@ -17975,12 +18073,13 @@ async function competitorRoutes(app) {
|
|
|
17975
18073
|
target: [competitors.projectId, competitors.domain]
|
|
17976
18074
|
}).run();
|
|
17977
18075
|
}
|
|
18076
|
+
const droppedQualifiedAliases = pruneQualifiedAliasesForCompetitors(tx, project.id, now);
|
|
17978
18077
|
writeAuditLog(tx, {
|
|
17979
18078
|
projectId: project.id,
|
|
17980
18079
|
actor: "api",
|
|
17981
18080
|
action: "competitors.appended",
|
|
17982
18081
|
entityType: "competitor",
|
|
17983
|
-
diff: { added }
|
|
18082
|
+
diff: { added, ...droppedQualifiedAliases.length ? { droppedQualifiedAliases } : {} }
|
|
17984
18083
|
});
|
|
17985
18084
|
});
|
|
17986
18085
|
const rows = app.db.select().from(competitors).where(eq19(competitors.projectId, project.id)).all();
|
|
@@ -29674,6 +29773,15 @@ async function applyRoutes(app, opts) {
|
|
|
29674
29773
|
specProviders
|
|
29675
29774
|
);
|
|
29676
29775
|
const specDispatchModes = config.spec.providerDispatchModes === void 0 ? void 0 : validateProviderDispatchModes(config.spec.providerDispatchModes, opts?.providerAdapters);
|
|
29776
|
+
const specCompetitorNames = normalizeCompetitorList2(config.spec.competitors).map(competitorLabelFromDomain);
|
|
29777
|
+
const specQualifiedAliases = config.spec.qualifiedAliases === void 0 ? void 0 : requireQualifiedAliases(
|
|
29778
|
+
{
|
|
29779
|
+
displayName: config.spec.displayName,
|
|
29780
|
+
aliases: normalizeProjectAliases(config.spec.displayName, config.spec.aliases)
|
|
29781
|
+
},
|
|
29782
|
+
config.spec.qualifiedAliases,
|
|
29783
|
+
specCompetitorNames
|
|
29784
|
+
);
|
|
29677
29785
|
let resolvedSchedule = null;
|
|
29678
29786
|
let deleteSchedule = false;
|
|
29679
29787
|
if (config.spec.schedule) {
|
|
@@ -29732,6 +29840,11 @@ async function applyRoutes(app, opts) {
|
|
|
29732
29840
|
}
|
|
29733
29841
|
if (existing) {
|
|
29734
29842
|
projectId = existing.id;
|
|
29843
|
+
const qualifiedAliases = specQualifiedAliases ?? resolveProjectQualifiedAliases(
|
|
29844
|
+
{ displayName: config.spec.displayName, aliases: nextAliases },
|
|
29845
|
+
existing.qualifiedAliases,
|
|
29846
|
+
specCompetitorNames
|
|
29847
|
+
).value;
|
|
29735
29848
|
const providerDispatchModes = pruneProviderDispatchModes(
|
|
29736
29849
|
specDispatchModes ?? existing.providerDispatchModes,
|
|
29737
29850
|
specProviders,
|
|
@@ -29742,6 +29855,7 @@ async function applyRoutes(app, opts) {
|
|
|
29742
29855
|
canonicalDomain: config.spec.canonicalDomain,
|
|
29743
29856
|
ownedDomains: config.spec.ownedDomains ?? [],
|
|
29744
29857
|
aliases: nextAliases,
|
|
29858
|
+
qualifiedAliases,
|
|
29745
29859
|
country: config.spec.country,
|
|
29746
29860
|
language: config.spec.language,
|
|
29747
29861
|
labels: config.metadata.labels,
|
|
@@ -29773,6 +29887,7 @@ async function applyRoutes(app, opts) {
|
|
|
29773
29887
|
canonicalDomain: config.spec.canonicalDomain,
|
|
29774
29888
|
ownedDomains: config.spec.ownedDomains ?? [],
|
|
29775
29889
|
aliases: nextAliases,
|
|
29890
|
+
qualifiedAliases: specQualifiedAliases ?? [],
|
|
29776
29891
|
country: config.spec.country,
|
|
29777
29892
|
language: config.spec.language,
|
|
29778
29893
|
tags: [],
|
|
@@ -29917,6 +30032,7 @@ async function applyRoutes(app, opts) {
|
|
|
29917
30032
|
canonicalDomain: project.canonicalDomain,
|
|
29918
30033
|
ownedDomains: project.ownedDomains,
|
|
29919
30034
|
aliases: project.aliases,
|
|
30035
|
+
qualifiedAliases: project.qualifiedAliases,
|
|
29920
30036
|
country: project.country,
|
|
29921
30037
|
language: project.language,
|
|
29922
30038
|
tags: project.tags,
|
|
@@ -39392,6 +39508,7 @@ function formatProject2(row, negativeReviewMaxStars) {
|
|
|
39392
39508
|
canonicalDomain: row.canonicalDomain,
|
|
39393
39509
|
ownedDomains: row.ownedDomains,
|
|
39394
39510
|
aliases: row.aliases,
|
|
39511
|
+
qualifiedAliases: row.qualifiedAliases,
|
|
39395
39512
|
country: row.country,
|
|
39396
39513
|
language: row.language,
|
|
39397
39514
|
tags: row.tags,
|
|
@@ -87632,13 +87749,18 @@ async function discoveryRoutes(app, opts) {
|
|
|
87632
87749
|
createdAt: now
|
|
87633
87750
|
}).run();
|
|
87634
87751
|
}
|
|
87752
|
+
const droppedQualifiedAliases = promotedCompetitors.length ? pruneQualifiedAliasesForCompetitors(tx, project.id, now) : [];
|
|
87635
87753
|
writeAuditLog(tx, {
|
|
87636
87754
|
projectId: project.id,
|
|
87637
87755
|
actor: "api",
|
|
87638
87756
|
action: "discovery.promoted",
|
|
87639
87757
|
entityType: "discovery_session",
|
|
87640
87758
|
entityId: session.id,
|
|
87641
|
-
diff: {
|
|
87759
|
+
diff: {
|
|
87760
|
+
queries: promotedQueries,
|
|
87761
|
+
competitors: promotedCompetitors,
|
|
87762
|
+
...droppedQualifiedAliases.length ? { droppedQualifiedAliases } : {}
|
|
87763
|
+
}
|
|
87642
87764
|
});
|
|
87643
87765
|
});
|
|
87644
87766
|
}
|
|
@@ -90339,12 +90461,28 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
90339
90461
|
if (definition.queries.some((query) => query.queryClass !== (classifier?.classify(query.queryText) ?? null))) {
|
|
90340
90462
|
throw validationError("Query classes must match the captured identity and text.");
|
|
90341
90463
|
}
|
|
90464
|
+
if (definition.identity.qualifiedAliases !== void 0) {
|
|
90465
|
+
const expected = resolveProjectQualifiedAliases(
|
|
90466
|
+
definition.identity,
|
|
90467
|
+
definition.identity.qualifiedAliases,
|
|
90468
|
+
simpleMeasurementCompetitorNames(definition.competitors)
|
|
90469
|
+
).value;
|
|
90470
|
+
const captured = definition.identity.qualifiedAliases;
|
|
90471
|
+
if (captured.length !== expected.length || captured.some((name, index2) => name !== expected[index2])) {
|
|
90472
|
+
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
90473
|
+
}
|
|
90474
|
+
}
|
|
90342
90475
|
const existing = tx.select().from(simpleMeasurementDefinitions).where(and68(eq81(simpleMeasurementDefinitions.projectId, input.projectId), eq81(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
90343
90476
|
if (existing) {
|
|
90344
90477
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
90478
|
+
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition.identity;
|
|
90345
90479
|
const replayDefinition = {
|
|
90346
90480
|
...definition,
|
|
90347
|
-
capturedAt: frozen.capturedAt
|
|
90481
|
+
capturedAt: frozen.capturedAt,
|
|
90482
|
+
identity: {
|
|
90483
|
+
...liveIdentity,
|
|
90484
|
+
...frozen.identity.qualifiedAliases?.length ? { qualifiedAliases: frozen.identity.qualifiedAliases } : {}
|
|
90485
|
+
}
|
|
90348
90486
|
};
|
|
90349
90487
|
const replayChecksum = crypto51.createHash("sha256").update(canonicalSimpleMeasurementDefinitionJson(replayDefinition)).digest("hex");
|
|
90350
90488
|
if (existing.checksum !== replayChecksum) {
|