From 661fe9778888ac8669a09d683df5b5d8f721b835 Mon Sep 17 00:00:00 2001 From: Arber X <14798762+arberx@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:09:13 +0000 Subject: [PATCH] feat(measurement): Advanced Measurement v2 contracts, migrations and route surface The foundation for Advanced Measurement v2. Nothing here changes behaviour: every new route answers 501 until the services behind them land. It exists so the rest of the work can proceed against fixed contracts rather than invented ones. Adds schema v2 as a published plan shape with Branded/Non-brand assignments, frozen per-Group competitor identities, revision-frozen project identity, and query snapshots that keep a later rename from rewriting history. The v1 decoder is untouched and stored v1 plans stay byte-compatible; the version switch gains a v2 case, and a new any-version reader returns the union. The new compiled_checksum is a separate, nullable, separately indexed column rather than a redefinition of the existing checksum. They are not interchangeable: checksum hashes the whole document including revision, so reusing it as the publish guard would mean no two revisions could ever compare equal, and republishing identical content or reverting to an earlier configuration could never be recognised. Its index is deliberately non-unique, because a unique one makes reverting a plan impossible. Adds migrations v122 and v123 for the draft, query-set, template, discovery-config and idempotency-receipt tables, with the drizzle schema mirrored column by column and a test that fails when the two disagree, since that class of drift corrupts reads silently and nothing else catches it. Read surfaces now decode any schema version. Previously three call sites went through the v1-only reader, so the first published v2 revision would have turned plan reads, revision detail and the report adapter into 500s. The segment retirement guard now sees v2 plans too, which matters: it is what stops a segment being retired out from under an active revision. Execution and reporting refuse a v2 revision with a domain error and are marked as the seams the runner and reporting slices replace. Every behaviour is pinned by a test proven by reverting the production change. --- .../generated/@tanstack/react-query.gen.ts | 862 +- .../src/generated/sdk.gen.ts | 696 +- .../src/generated/types.gen.ts | 9147 +++++++++++------ packages/api-routes/src/index.ts | 8 + .../src/measurement-discovery-v2.ts | 23 + packages/api-routes/src/measurement-draft.ts | 74 + .../api-routes/src/measurement-overview.ts | 15 + packages/api-routes/src/measurement-plan.ts | 37 +- .../src/measurement-report-adapter.ts | 9 +- packages/api-routes/src/openapi-schemas.ts | 76 + packages/api-routes/src/openapi.ts | 467 +- packages/api-routes/src/run-queue.ts | 11 +- .../api-routes/test/db-dto-coverage.test.ts | 28 + .../test/measurement-v2-openapi.test.ts | 125 + .../test/measurement-v2-stubs.test.ts | 104 + .../canonry/src/mcp/openapi-classification.ts | 39 + packages/contracts/src/errors.ts | 78 + packages/contracts/src/index.ts | 2 + packages/contracts/src/measurement-draft.ts | 510 + packages/contracts/src/measurement-plan-v2.ts | 412 + packages/contracts/src/measurement-plan.ts | 30 +- .../contracts/test/measurement-draft.test.ts | 205 + .../test/measurement-plan-v2.test.ts | 232 + .../measurement-plan-version-dispatch.test.ts | 105 + .../contracts/test/measurement-plan.test.ts | 7 +- packages/db/src/migrate.ts | 122 + packages/db/src/schema.ts | 126 + .../test/measurement-plan-persistence.test.ts | 2 +- .../measurement-v2-tables-migration.test.ts | 271 + 29 files changed, 10514 insertions(+), 3309 deletions(-) create mode 100644 packages/api-routes/src/measurement-discovery-v2.ts create mode 100644 packages/api-routes/src/measurement-draft.ts create mode 100644 packages/api-routes/src/measurement-overview.ts create mode 100644 packages/api-routes/test/measurement-v2-openapi.test.ts create mode 100644 packages/api-routes/test/measurement-v2-stubs.test.ts create mode 100644 packages/contracts/src/measurement-draft.ts create mode 100644 packages/contracts/src/measurement-plan-v2.ts create mode 100644 packages/contracts/test/measurement-draft.test.ts create mode 100644 packages/contracts/test/measurement-plan-v2.test.ts create mode 100644 packages/contracts/test/measurement-plan-version-dispatch.test.ts create mode 100644 packages/db/test/measurement-v2-tables-migration.test.ts diff --git a/packages/api-client-generated/src/generated/@tanstack/react-query.gen.ts b/packages/api-client-generated/src/generated/@tanstack/react-query.gen.ts index 18b6693f8..53a2c436b 100644 --- a/packages/api-client-generated/src/generated/@tanstack/react-query.gen.ts +++ b/packages/api-client-generated/src/generated/@tanstack/react-query.gen.ts @@ -3,8 +3,8 @@ import { type DefaultError, type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; import { client } from '../client.gen.js'; -import { deleteApiV1AuthSessions, deleteApiV1BacklinksCacheByRelease, deleteApiV1ProjectsByName, deleteApiV1ProjectsByNameAdsConnection, deleteApiV1ProjectsByNameAgentMemory, deleteApiV1ProjectsByNameAgentTranscript, deleteApiV1ProjectsByNameBingDisconnect, deleteApiV1ProjectsByNameCompetitors, deleteApiV1ProjectsByNameCompetitorsById, deleteApiV1ProjectsByNameContentDismissalsByTargetRef, deleteApiV1ProjectsByNameGaDisconnect, deleteApiV1ProjectsByNameGbpConnection, deleteApiV1ProjectsByNameGoogleConnectionsByType, deleteApiV1ProjectsByNameKeywords, deleteApiV1ProjectsByNameLocationsByLabel, deleteApiV1ProjectsByNameNotificationsById, deleteApiV1ProjectsByNameQueries, deleteApiV1ProjectsByNameQueriesById, deleteApiV1ProjectsByNameSchedule, deleteApiV1ProjectsByNameWordpressDisconnect, deleteApiV1UsersByName, getApiV1AuthSession, getApiV1AuthSessions, getApiV1BacklinksLatestRelease, getApiV1BacklinksReleases, getApiV1BacklinksStatus, getApiV1BacklinksSyncs, getApiV1BacklinksSyncsLatest, getApiV1CdpStatus, getApiV1Doctor, getApiV1GoogleCallback, getApiV1History, getApiV1Keys, getApiV1KeysSelf, getApiV1NotificationsEvents, getApiV1OpenapiJson, getApiV1Projects, getApiV1ProjectsByName, getApiV1ProjectsByNameAdsAccount, getApiV1ProjectsByNameAdsCampaigns, getApiV1ProjectsByNameAdsConversionsEventSettings, getApiV1ProjectsByNameAdsConversionsPixels, getApiV1ProjectsByNameAdsDeliveryDiagnostics, getApiV1ProjectsByNameAdsGeoSearch, getApiV1ProjectsByNameAdsInsights, getApiV1ProjectsByNameAdsLiveDelivery, getApiV1ProjectsByNameAdsOperations, getApiV1ProjectsByNameAdsOperationsByOperationKey, getApiV1ProjectsByNameAdsStatus, getApiV1ProjectsByNameAdsSummary, getApiV1ProjectsByNameAgentMemory, getApiV1ProjectsByNameAgentProviders, getApiV1ProjectsByNameAgentTranscript, getApiV1ProjectsByNameAnalyticsGaps, getApiV1ProjectsByNameAnalyticsMetrics, getApiV1ProjectsByNameAnalyticsSources, getApiV1ProjectsByNameBacklinksDomains, getApiV1ProjectsByNameBacklinksHistory, getApiV1ProjectsByNameBacklinksSources, getApiV1ProjectsByNameBacklinksSummary, getApiV1ProjectsByNameBingCoverage, getApiV1ProjectsByNameBingCoverageHistory, getApiV1ProjectsByNameBingInspections, getApiV1ProjectsByNameBingPerformance, getApiV1ProjectsByNameBingSites, getApiV1ProjectsByNameBingStatus, getApiV1ProjectsByNameCitationsVisibility, getApiV1ProjectsByNameCompetitors, getApiV1ProjectsByNameContentDismissals, getApiV1ProjectsByNameContentDomainClassifications, getApiV1ProjectsByNameContentGaps, getApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysis, getApiV1ProjectsByNameContentRecommendationsByTargetRefBrief, getApiV1ProjectsByNameContentSources, getApiV1ProjectsByNameContentTargets, getApiV1ProjectsByNameDeletePreview, getApiV1ProjectsByNameDiscoverSessions, getApiV1ProjectsByNameDiscoverSessionsById, getApiV1ProjectsByNameDiscoverSessionsByIdHarvest, getApiV1ProjectsByNameDiscoverSessionsByIdPromote, getApiV1ProjectsByNameDoctor, getApiV1ProjectsByNameExport, getApiV1ProjectsByNameGaAiReferralDaily, getApiV1ProjectsByNameGaAiReferralHistory, getApiV1ProjectsByNameGaAttributionTrend, getApiV1ProjectsByNameGaCoverage, getApiV1ProjectsByNameGaMeasurementAnalysis, getApiV1ProjectsByNameGaSessionHistory, getApiV1ProjectsByNameGaSocialReferralHistory, getApiV1ProjectsByNameGaSocialReferralTrend, getApiV1ProjectsByNameGaStatus, getApiV1ProjectsByNameGaTraffic, getApiV1ProjectsByNameGbpAccounts, getApiV1ProjectsByNameGbpAttributes, getApiV1ProjectsByNameGbpKeywords, getApiV1ProjectsByNameGbpLocations, getApiV1ProjectsByNameGbpLodging, getApiV1ProjectsByNameGbpMetrics, getApiV1ProjectsByNameGbpPlaceActions, getApiV1ProjectsByNameGbpPlaces, getApiV1ProjectsByNameGbpSummary, getApiV1ProjectsByNameGoogleCallback, getApiV1ProjectsByNameGoogleConnections, getApiV1ProjectsByNameGoogleGscCoverage, getApiV1ProjectsByNameGoogleGscCoverageHistory, getApiV1ProjectsByNameGoogleGscDeindexed, getApiV1ProjectsByNameGoogleGscInspections, getApiV1ProjectsByNameGoogleGscPerformance, getApiV1ProjectsByNameGoogleGscPerformanceDaily, getApiV1ProjectsByNameGoogleGscSitemaps, getApiV1ProjectsByNameGoogleGscTopPages, getApiV1ProjectsByNameGoogleProperties, getApiV1ProjectsByNameHealthHistory, getApiV1ProjectsByNameHealthLatest, getApiV1ProjectsByNameHistory, getApiV1ProjectsByNameInsights, getApiV1ProjectsByNameInsightsById, getApiV1ProjectsByNameKeywords, getApiV1ProjectsByNameLocations, getApiV1ProjectsByNameMeasurementPlan, getApiV1ProjectsByNameMeasurementPlanVersions, getApiV1ProjectsByNameMeasurementPlanVersionsByRevision, getApiV1ProjectsByNameMeasurementReport, getApiV1ProjectsByNameNotifications, getApiV1ProjectsByNameOrganicEvidence, getApiV1ProjectsByNameOverview, getApiV1ProjectsByNameQueries, getApiV1ProjectsByNameReport, getApiV1ProjectsByNameReportHtml, getApiV1ProjectsByNameResearchRuns, getApiV1ProjectsByNameResearchRunsByRunId, getApiV1ProjectsByNameResultsExport, getApiV1ProjectsByNameRuns, getApiV1ProjectsByNameRunsByRunIdBrowserDiff, getApiV1ProjectsByNameRunsLatest, getApiV1ProjectsByNameSchedule, getApiV1ProjectsByNameSearch, getApiV1ProjectsByNameSnapshots, getApiV1ProjectsByNameSnapshotsDiff, getApiV1ProjectsByNameTechnicalAeo, getApiV1ProjectsByNameTechnicalAeoPages, getApiV1ProjectsByNameTechnicalAeoTrend, getApiV1ProjectsByNameTimeline, getApiV1ProjectsByNameTrafficEvents, getApiV1ProjectsByNameTrafficSources, getApiV1ProjectsByNameTrafficSourcesById, getApiV1ProjectsByNameTrafficStatus, getApiV1ProjectsByNameVisibilityCompare, getApiV1ProjectsByNameVisibilityStats, getApiV1ProjectsByNameWordpressAudit, getApiV1ProjectsByNameWordpressDiff, getApiV1ProjectsByNameWordpressLlmsTxt, getApiV1ProjectsByNameWordpressPage, getApiV1ProjectsByNameWordpressPages, getApiV1ProjectsByNameWordpressSchema, getApiV1ProjectsByNameWordpressSchemaStatus, getApiV1ProjectsByNameWordpressStagingStatus, getApiV1ProjectsByNameWordpressStatus, getApiV1Runs, getApiV1RunsById, getApiV1ScreenshotsBySnapshotId, getApiV1Settings, getApiV1Telemetry, getApiV1Users, type Options, postApiV1Apply, postApiV1AuthLogin, postApiV1AuthLogout, postApiV1BacklinksInstall, postApiV1BacklinksSyncs, postApiV1CdpScreenshot, postApiV1Keys, postApiV1KeysByIdRevoke, postApiV1ProjectsByNameAdsActivationGrants, postApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevoke, postApiV1ProjectsByNameAdsAdGroups, postApiV1ProjectsByNameAdsAdGroupsById, postApiV1ProjectsByNameAdsAdGroupsByIdPause, postApiV1ProjectsByNameAdsAds, postApiV1ProjectsByNameAdsAdsById, postApiV1ProjectsByNameAdsAdsByIdPause, postApiV1ProjectsByNameAdsCampaigns, postApiV1ProjectsByNameAdsCampaignsById, postApiV1ProjectsByNameAdsCampaignsByIdActivateTree, postApiV1ProjectsByNameAdsCampaignsByIdPause, postApiV1ProjectsByNameAdsConnect, postApiV1ProjectsByNameAdsFiles, postApiV1ProjectsByNameAdsOperationsByOperationKeyReconcile, postApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivation, postApiV1ProjectsByNameAdsSync, postApiV1ProjectsByNameBacklinksExtract, postApiV1ProjectsByNameBingConnect, postApiV1ProjectsByNameBingInspectSitemap, postApiV1ProjectsByNameBingInspectUrl, postApiV1ProjectsByNameBingRequestIndexing, postApiV1ProjectsByNameBingSetSite, postApiV1ProjectsByNameCompetitors, postApiV1ProjectsByNameContentDismissals, postApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyze, postApiV1ProjectsByNameContentRecommendationsByTargetRefBrief, postApiV1ProjectsByNameDiscoverRun, postApiV1ProjectsByNameDiscoverSessionsByIdPromote, postApiV1ProjectsByNameGaConnect, postApiV1ProjectsByNameGaSync, postApiV1ProjectsByNameGbpLocationsDiscover, postApiV1ProjectsByNameGbpSync, postApiV1ProjectsByNameGoogleConnect, postApiV1ProjectsByNameGoogleGscDiscoverSitemaps, postApiV1ProjectsByNameGoogleGscInspect, postApiV1ProjectsByNameGoogleGscInspectSitemap, postApiV1ProjectsByNameGoogleGscSitemapsSubmit, postApiV1ProjectsByNameGoogleGscSync, postApiV1ProjectsByNameGoogleIndexingRequest, postApiV1ProjectsByNameInsightsByIdDismiss, postApiV1ProjectsByNameKeywords, postApiV1ProjectsByNameKeywordsGenerate, postApiV1ProjectsByNameLocations, postApiV1ProjectsByNameMeasurementDiscovery, postApiV1ProjectsByNameMeasurementPlanCompilePreview, postApiV1ProjectsByNameMeasurementPlanDiffPreview, postApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetire, postApiV1ProjectsByNameNotifications, postApiV1ProjectsByNameNotificationsByIdTest, postApiV1ProjectsByNameQueries, postApiV1ProjectsByNameQueriesGenerate, postApiV1ProjectsByNameQueriesReplacePreview, postApiV1ProjectsByNameResearchRuns, postApiV1ProjectsByNameRuns, postApiV1ProjectsByNameTechnicalAeoRuns, postApiV1ProjectsByNameTrafficConnectCloudRun, postApiV1ProjectsByNameTrafficConnectVercel, postApiV1ProjectsByNameTrafficConnectWordpress, postApiV1ProjectsByNameTrafficSourcesByIdBackfill, postApiV1ProjectsByNameTrafficSourcesByIdReset, postApiV1ProjectsByNameTrafficSourcesByIdSync, postApiV1ProjectsByNameWordpressConnect, postApiV1ProjectsByNameWordpressLlmsTxtManual, postApiV1ProjectsByNameWordpressOnboard, postApiV1ProjectsByNameWordpressPageMeta, postApiV1ProjectsByNameWordpressPages, postApiV1ProjectsByNameWordpressPagesMetaBulk, postApiV1ProjectsByNameWordpressSchemaDeploy, postApiV1ProjectsByNameWordpressSchemaManual, postApiV1ProjectsByNameWordpressStagingPush, postApiV1Runs, postApiV1RunsByIdCancel, postApiV1Snapshot, postApiV1TelemetryOnboarding, postApiV1Users, putApiV1ProjectsByName, putApiV1ProjectsByNameAgentMemory, putApiV1ProjectsByNameCompetitors, putApiV1ProjectsByNameGbpLocationsByLocationNameSelection, putApiV1ProjectsByNameGoogleConnectionsByTypeProperty, putApiV1ProjectsByNameGoogleConnectionsByTypeSitemap, putApiV1ProjectsByNameKeywords, putApiV1ProjectsByNameLocationsDefault, putApiV1ProjectsByNameMeasurementPlan, putApiV1ProjectsByNameQueries, putApiV1ProjectsByNameSchedule, putApiV1ProjectsByNameWordpressPage, putApiV1SettingsBing, putApiV1SettingsCdp, putApiV1SettingsGoogle, putApiV1SettingsProvidersByName, putApiV1Telemetry } from '../sdk.gen.js'; -import type { DeleteApiV1AuthSessionsData, DeleteApiV1AuthSessionsError, DeleteApiV1AuthSessionsResponse, DeleteApiV1BacklinksCacheByReleaseData, DeleteApiV1BacklinksCacheByReleaseError, DeleteApiV1BacklinksCacheByReleaseResponse, DeleteApiV1ProjectsByNameAdsConnectionData, DeleteApiV1ProjectsByNameAdsConnectionError, DeleteApiV1ProjectsByNameAdsConnectionResponse, DeleteApiV1ProjectsByNameAgentMemoryData, DeleteApiV1ProjectsByNameAgentMemoryError, DeleteApiV1ProjectsByNameAgentMemoryResponse, DeleteApiV1ProjectsByNameAgentTranscriptData, DeleteApiV1ProjectsByNameAgentTranscriptError, DeleteApiV1ProjectsByNameAgentTranscriptResponse, DeleteApiV1ProjectsByNameBingDisconnectData, DeleteApiV1ProjectsByNameBingDisconnectError, DeleteApiV1ProjectsByNameBingDisconnectResponse, DeleteApiV1ProjectsByNameCompetitorsByIdData, DeleteApiV1ProjectsByNameCompetitorsByIdError, DeleteApiV1ProjectsByNameCompetitorsByIdResponse, DeleteApiV1ProjectsByNameCompetitorsData, DeleteApiV1ProjectsByNameCompetitorsError, DeleteApiV1ProjectsByNameCompetitorsResponse, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefData, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefError, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefResponse, DeleteApiV1ProjectsByNameData, DeleteApiV1ProjectsByNameError, DeleteApiV1ProjectsByNameGaDisconnectData, DeleteApiV1ProjectsByNameGaDisconnectError, DeleteApiV1ProjectsByNameGaDisconnectResponse, DeleteApiV1ProjectsByNameGbpConnectionData, DeleteApiV1ProjectsByNameGbpConnectionError, DeleteApiV1ProjectsByNameGbpConnectionResponse, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeData, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeError, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeResponse, DeleteApiV1ProjectsByNameKeywordsData, DeleteApiV1ProjectsByNameKeywordsError, DeleteApiV1ProjectsByNameKeywordsResponse, DeleteApiV1ProjectsByNameLocationsByLabelData, DeleteApiV1ProjectsByNameLocationsByLabelError, DeleteApiV1ProjectsByNameLocationsByLabelResponse, DeleteApiV1ProjectsByNameNotificationsByIdData, DeleteApiV1ProjectsByNameNotificationsByIdError, DeleteApiV1ProjectsByNameNotificationsByIdResponse, DeleteApiV1ProjectsByNameQueriesByIdData, DeleteApiV1ProjectsByNameQueriesByIdError, DeleteApiV1ProjectsByNameQueriesByIdResponse, DeleteApiV1ProjectsByNameQueriesData, DeleteApiV1ProjectsByNameQueriesError, DeleteApiV1ProjectsByNameQueriesResponse, DeleteApiV1ProjectsByNameResponse, DeleteApiV1ProjectsByNameScheduleData, DeleteApiV1ProjectsByNameScheduleError, DeleteApiV1ProjectsByNameScheduleResponse, DeleteApiV1ProjectsByNameWordpressDisconnectData, DeleteApiV1ProjectsByNameWordpressDisconnectError, DeleteApiV1ProjectsByNameWordpressDisconnectResponse, DeleteApiV1UsersByNameData, DeleteApiV1UsersByNameError, DeleteApiV1UsersByNameResponse, GetApiV1AuthSessionData, GetApiV1AuthSessionsData, GetApiV1BacklinksLatestReleaseData, GetApiV1BacklinksReleasesData, GetApiV1BacklinksStatusData, GetApiV1BacklinksSyncsData, GetApiV1BacklinksSyncsLatestData, GetApiV1CdpStatusData, GetApiV1DoctorData, GetApiV1GoogleCallbackData, GetApiV1HistoryData, GetApiV1HistoryResponse, GetApiV1KeysData, GetApiV1KeysSelfData, GetApiV1NotificationsEventsData, GetApiV1OpenapiJsonData, GetApiV1ProjectsByNameAdsAccountData, GetApiV1ProjectsByNameAdsCampaignsData, GetApiV1ProjectsByNameAdsConversionsEventSettingsData, GetApiV1ProjectsByNameAdsConversionsPixelsData, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsData, GetApiV1ProjectsByNameAdsGeoSearchData, GetApiV1ProjectsByNameAdsInsightsData, GetApiV1ProjectsByNameAdsLiveDeliveryData, GetApiV1ProjectsByNameAdsOperationsByOperationKeyData, GetApiV1ProjectsByNameAdsOperationsData, GetApiV1ProjectsByNameAdsOperationsError, GetApiV1ProjectsByNameAdsOperationsResponse, GetApiV1ProjectsByNameAdsStatusData, GetApiV1ProjectsByNameAdsSummaryData, GetApiV1ProjectsByNameAgentMemoryData, GetApiV1ProjectsByNameAgentProvidersData, GetApiV1ProjectsByNameAgentTranscriptData, GetApiV1ProjectsByNameAnalyticsGapsData, GetApiV1ProjectsByNameAnalyticsMetricsData, GetApiV1ProjectsByNameAnalyticsSourcesData, GetApiV1ProjectsByNameBacklinksDomainsData, GetApiV1ProjectsByNameBacklinksDomainsError, GetApiV1ProjectsByNameBacklinksDomainsResponse, GetApiV1ProjectsByNameBacklinksHistoryData, GetApiV1ProjectsByNameBacklinksSourcesData, GetApiV1ProjectsByNameBacklinksSummaryData, GetApiV1ProjectsByNameBingCoverageData, GetApiV1ProjectsByNameBingCoverageHistoryData, GetApiV1ProjectsByNameBingInspectionsData, GetApiV1ProjectsByNameBingPerformanceData, GetApiV1ProjectsByNameBingSitesData, GetApiV1ProjectsByNameBingStatusData, GetApiV1ProjectsByNameCitationsVisibilityData, GetApiV1ProjectsByNameCompetitorsData, GetApiV1ProjectsByNameContentDismissalsData, GetApiV1ProjectsByNameContentDomainClassificationsData, GetApiV1ProjectsByNameContentGapsData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, GetApiV1ProjectsByNameContentSourcesData, GetApiV1ProjectsByNameContentTargetsData, GetApiV1ProjectsByNameData, GetApiV1ProjectsByNameDeletePreviewData, GetApiV1ProjectsByNameDiscoverSessionsByIdData, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestData, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, GetApiV1ProjectsByNameDiscoverSessionsData, GetApiV1ProjectsByNameDoctorData, GetApiV1ProjectsByNameExportData, GetApiV1ProjectsByNameGaAiReferralDailyData, GetApiV1ProjectsByNameGaAiReferralHistoryData, GetApiV1ProjectsByNameGaAttributionTrendData, GetApiV1ProjectsByNameGaCoverageData, GetApiV1ProjectsByNameGaMeasurementAnalysisData, GetApiV1ProjectsByNameGaSessionHistoryData, GetApiV1ProjectsByNameGaSocialReferralHistoryData, GetApiV1ProjectsByNameGaSocialReferralTrendData, GetApiV1ProjectsByNameGaStatusData, GetApiV1ProjectsByNameGaTrafficData, GetApiV1ProjectsByNameGbpAccountsData, GetApiV1ProjectsByNameGbpAttributesData, GetApiV1ProjectsByNameGbpKeywordsData, GetApiV1ProjectsByNameGbpLocationsData, GetApiV1ProjectsByNameGbpLodgingData, GetApiV1ProjectsByNameGbpMetricsData, GetApiV1ProjectsByNameGbpPlaceActionsData, GetApiV1ProjectsByNameGbpPlacesData, GetApiV1ProjectsByNameGbpSummaryData, GetApiV1ProjectsByNameGoogleCallbackData, GetApiV1ProjectsByNameGoogleConnectionsData, GetApiV1ProjectsByNameGoogleGscCoverageData, GetApiV1ProjectsByNameGoogleGscCoverageHistoryData, GetApiV1ProjectsByNameGoogleGscDeindexedData, GetApiV1ProjectsByNameGoogleGscInspectionsData, GetApiV1ProjectsByNameGoogleGscPerformanceDailyData, GetApiV1ProjectsByNameGoogleGscPerformanceData, GetApiV1ProjectsByNameGoogleGscPerformanceError, GetApiV1ProjectsByNameGoogleGscPerformanceResponse, GetApiV1ProjectsByNameGoogleGscSitemapsData, GetApiV1ProjectsByNameGoogleGscTopPagesData, GetApiV1ProjectsByNameGooglePropertiesData, GetApiV1ProjectsByNameHealthHistoryData, GetApiV1ProjectsByNameHealthLatestData, GetApiV1ProjectsByNameHistoryData, GetApiV1ProjectsByNameHistoryResponse, GetApiV1ProjectsByNameInsightsByIdData, GetApiV1ProjectsByNameInsightsData, GetApiV1ProjectsByNameKeywordsData, GetApiV1ProjectsByNameLocationsData, GetApiV1ProjectsByNameMeasurementPlanData, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionData, GetApiV1ProjectsByNameMeasurementPlanVersionsData, GetApiV1ProjectsByNameMeasurementReportData, GetApiV1ProjectsByNameNotificationsData, GetApiV1ProjectsByNameOrganicEvidenceData, GetApiV1ProjectsByNameOverviewData, GetApiV1ProjectsByNameQueriesData, GetApiV1ProjectsByNameReportData, GetApiV1ProjectsByNameReportHtmlData, GetApiV1ProjectsByNameResearchRunsByRunIdData, GetApiV1ProjectsByNameResearchRunsData, GetApiV1ProjectsByNameResultsExportData, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffData, GetApiV1ProjectsByNameRunsData, GetApiV1ProjectsByNameRunsLatestData, GetApiV1ProjectsByNameScheduleData, GetApiV1ProjectsByNameSearchData, GetApiV1ProjectsByNameSnapshotsData, GetApiV1ProjectsByNameSnapshotsDiffData, GetApiV1ProjectsByNameSnapshotsResponse, GetApiV1ProjectsByNameTechnicalAeoData, GetApiV1ProjectsByNameTechnicalAeoPagesData, GetApiV1ProjectsByNameTechnicalAeoPagesError, GetApiV1ProjectsByNameTechnicalAeoPagesResponse, GetApiV1ProjectsByNameTechnicalAeoTrendData, GetApiV1ProjectsByNameTimelineData, GetApiV1ProjectsByNameTrafficEventsData, GetApiV1ProjectsByNameTrafficSourcesByIdData, GetApiV1ProjectsByNameTrafficSourcesData, GetApiV1ProjectsByNameTrafficStatusData, GetApiV1ProjectsByNameVisibilityCompareData, GetApiV1ProjectsByNameVisibilityStatsData, GetApiV1ProjectsByNameWordpressAuditData, GetApiV1ProjectsByNameWordpressDiffData, GetApiV1ProjectsByNameWordpressLlmsTxtData, GetApiV1ProjectsByNameWordpressPageData, GetApiV1ProjectsByNameWordpressPagesData, GetApiV1ProjectsByNameWordpressSchemaData, GetApiV1ProjectsByNameWordpressSchemaStatusData, GetApiV1ProjectsByNameWordpressStagingStatusData, GetApiV1ProjectsByNameWordpressStatusData, GetApiV1ProjectsData, GetApiV1RunsByIdData, GetApiV1RunsData, GetApiV1ScreenshotsBySnapshotIdData, GetApiV1SettingsData, GetApiV1TelemetryData, GetApiV1UsersData, PostApiV1ApplyData, PostApiV1ApplyError, PostApiV1ApplyResponse, PostApiV1AuthLoginData, PostApiV1AuthLoginError, PostApiV1AuthLoginResponse, PostApiV1AuthLogoutData, PostApiV1AuthLogoutResponse, PostApiV1BacklinksInstallData, PostApiV1BacklinksInstallError, PostApiV1BacklinksInstallResponse, PostApiV1BacklinksSyncsData, PostApiV1BacklinksSyncsError, PostApiV1BacklinksSyncsResponse, PostApiV1CdpScreenshotData, PostApiV1CdpScreenshotError, PostApiV1CdpScreenshotResponse, PostApiV1KeysByIdRevokeData, PostApiV1KeysByIdRevokeError, PostApiV1KeysByIdRevokeResponse, PostApiV1KeysData, PostApiV1KeysError, PostApiV1KeysResponse, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeData, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeError, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeResponse, PostApiV1ProjectsByNameAdsActivationGrantsData, PostApiV1ProjectsByNameAdsActivationGrantsError, PostApiV1ProjectsByNameAdsActivationGrantsResponse, PostApiV1ProjectsByNameAdsAdGroupsByIdData, PostApiV1ProjectsByNameAdsAdGroupsByIdError, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseData, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseError, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseResponse, PostApiV1ProjectsByNameAdsAdGroupsByIdResponse, PostApiV1ProjectsByNameAdsAdGroupsData, PostApiV1ProjectsByNameAdsAdGroupsError, PostApiV1ProjectsByNameAdsAdGroupsResponse, PostApiV1ProjectsByNameAdsAdsByIdData, PostApiV1ProjectsByNameAdsAdsByIdError, PostApiV1ProjectsByNameAdsAdsByIdPauseData, PostApiV1ProjectsByNameAdsAdsByIdPauseError, PostApiV1ProjectsByNameAdsAdsByIdPauseResponse, PostApiV1ProjectsByNameAdsAdsByIdResponse, PostApiV1ProjectsByNameAdsAdsData, PostApiV1ProjectsByNameAdsAdsError, PostApiV1ProjectsByNameAdsAdsResponse, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeData, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeError, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeResponse, PostApiV1ProjectsByNameAdsCampaignsByIdData, PostApiV1ProjectsByNameAdsCampaignsByIdError, PostApiV1ProjectsByNameAdsCampaignsByIdPauseData, PostApiV1ProjectsByNameAdsCampaignsByIdPauseError, PostApiV1ProjectsByNameAdsCampaignsByIdPauseResponse, PostApiV1ProjectsByNameAdsCampaignsByIdResponse, PostApiV1ProjectsByNameAdsCampaignsData, PostApiV1ProjectsByNameAdsCampaignsError, PostApiV1ProjectsByNameAdsCampaignsResponse, PostApiV1ProjectsByNameAdsConnectData, PostApiV1ProjectsByNameAdsConnectError, PostApiV1ProjectsByNameAdsConnectResponse, PostApiV1ProjectsByNameAdsFilesData, PostApiV1ProjectsByNameAdsFilesError, PostApiV1ProjectsByNameAdsFilesResponse, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileError, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileResponse, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationError, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationResponse, PostApiV1ProjectsByNameAdsSyncData, PostApiV1ProjectsByNameAdsSyncError, PostApiV1ProjectsByNameAdsSyncResponse, PostApiV1ProjectsByNameBacklinksExtractData, PostApiV1ProjectsByNameBacklinksExtractError, PostApiV1ProjectsByNameBacklinksExtractResponse, PostApiV1ProjectsByNameBingConnectData, PostApiV1ProjectsByNameBingConnectError, PostApiV1ProjectsByNameBingConnectResponse, PostApiV1ProjectsByNameBingInspectSitemapData, PostApiV1ProjectsByNameBingInspectSitemapError, PostApiV1ProjectsByNameBingInspectSitemapResponse, PostApiV1ProjectsByNameBingInspectUrlData, PostApiV1ProjectsByNameBingInspectUrlError, PostApiV1ProjectsByNameBingInspectUrlResponse, PostApiV1ProjectsByNameBingRequestIndexingData, PostApiV1ProjectsByNameBingRequestIndexingError, PostApiV1ProjectsByNameBingRequestIndexingResponse, PostApiV1ProjectsByNameBingSetSiteData, PostApiV1ProjectsByNameBingSetSiteError, PostApiV1ProjectsByNameBingSetSiteResponse, PostApiV1ProjectsByNameCompetitorsData, PostApiV1ProjectsByNameCompetitorsError, PostApiV1ProjectsByNameCompetitorsResponse, PostApiV1ProjectsByNameContentDismissalsData, PostApiV1ProjectsByNameContentDismissalsError, PostApiV1ProjectsByNameContentDismissalsResponse, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeError, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeResponse, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefError, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefResponse, PostApiV1ProjectsByNameDiscoverRunData, PostApiV1ProjectsByNameDiscoverRunError, PostApiV1ProjectsByNameDiscoverRunResponse, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteError, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteResponse, PostApiV1ProjectsByNameGaConnectData, PostApiV1ProjectsByNameGaConnectError, PostApiV1ProjectsByNameGaConnectResponse, PostApiV1ProjectsByNameGaSyncData, PostApiV1ProjectsByNameGaSyncError, PostApiV1ProjectsByNameGaSyncResponse, PostApiV1ProjectsByNameGbpLocationsDiscoverData, PostApiV1ProjectsByNameGbpLocationsDiscoverError, PostApiV1ProjectsByNameGbpLocationsDiscoverResponse, PostApiV1ProjectsByNameGbpSyncData, PostApiV1ProjectsByNameGbpSyncError, PostApiV1ProjectsByNameGbpSyncResponse, PostApiV1ProjectsByNameGoogleConnectData, PostApiV1ProjectsByNameGoogleConnectError, PostApiV1ProjectsByNameGoogleConnectResponse, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsData, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsError, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsResponse, PostApiV1ProjectsByNameGoogleGscInspectData, PostApiV1ProjectsByNameGoogleGscInspectError, PostApiV1ProjectsByNameGoogleGscInspectResponse, PostApiV1ProjectsByNameGoogleGscInspectSitemapData, PostApiV1ProjectsByNameGoogleGscInspectSitemapError, PostApiV1ProjectsByNameGoogleGscInspectSitemapResponse, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitData, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitError, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponse, PostApiV1ProjectsByNameGoogleGscSyncData, PostApiV1ProjectsByNameGoogleGscSyncError, PostApiV1ProjectsByNameGoogleGscSyncResponse, PostApiV1ProjectsByNameGoogleIndexingRequestData, PostApiV1ProjectsByNameGoogleIndexingRequestError, PostApiV1ProjectsByNameGoogleIndexingRequestResponse, PostApiV1ProjectsByNameInsightsByIdDismissData, PostApiV1ProjectsByNameInsightsByIdDismissError, PostApiV1ProjectsByNameInsightsByIdDismissResponse, PostApiV1ProjectsByNameKeywordsData, PostApiV1ProjectsByNameKeywordsGenerateData, PostApiV1ProjectsByNameKeywordsGenerateError, PostApiV1ProjectsByNameKeywordsGenerateResponse, PostApiV1ProjectsByNameKeywordsResponse, PostApiV1ProjectsByNameLocationsData, PostApiV1ProjectsByNameLocationsError, PostApiV1ProjectsByNameLocationsResponse, PostApiV1ProjectsByNameMeasurementDiscoveryData, PostApiV1ProjectsByNameMeasurementDiscoveryError, PostApiV1ProjectsByNameMeasurementDiscoveryResponse, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewData, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewError, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponse, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewData, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewError, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponse, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireData, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireError, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponse, PostApiV1ProjectsByNameNotificationsByIdTestData, PostApiV1ProjectsByNameNotificationsByIdTestError, PostApiV1ProjectsByNameNotificationsByIdTestResponse, PostApiV1ProjectsByNameNotificationsData, PostApiV1ProjectsByNameNotificationsResponse, PostApiV1ProjectsByNameQueriesData, PostApiV1ProjectsByNameQueriesGenerateData, PostApiV1ProjectsByNameQueriesGenerateError, PostApiV1ProjectsByNameQueriesGenerateResponse, PostApiV1ProjectsByNameQueriesReplacePreviewData, PostApiV1ProjectsByNameQueriesReplacePreviewError, PostApiV1ProjectsByNameQueriesReplacePreviewResponse, PostApiV1ProjectsByNameQueriesResponse, PostApiV1ProjectsByNameResearchRunsData, PostApiV1ProjectsByNameResearchRunsError, PostApiV1ProjectsByNameResearchRunsResponse, PostApiV1ProjectsByNameRunsData, PostApiV1ProjectsByNameRunsError, PostApiV1ProjectsByNameRunsResponse, PostApiV1ProjectsByNameTechnicalAeoRunsData, PostApiV1ProjectsByNameTechnicalAeoRunsError, PostApiV1ProjectsByNameTechnicalAeoRunsResponse, PostApiV1ProjectsByNameTrafficConnectCloudRunData, PostApiV1ProjectsByNameTrafficConnectCloudRunError, PostApiV1ProjectsByNameTrafficConnectCloudRunResponse, PostApiV1ProjectsByNameTrafficConnectVercelData, PostApiV1ProjectsByNameTrafficConnectVercelError, PostApiV1ProjectsByNameTrafficConnectVercelResponse, PostApiV1ProjectsByNameTrafficConnectWordpressData, PostApiV1ProjectsByNameTrafficConnectWordpressError, PostApiV1ProjectsByNameTrafficConnectWordpressResponse, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillData, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillError, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillResponse, PostApiV1ProjectsByNameTrafficSourcesByIdResetData, PostApiV1ProjectsByNameTrafficSourcesByIdResetError, PostApiV1ProjectsByNameTrafficSourcesByIdResetResponse, PostApiV1ProjectsByNameTrafficSourcesByIdSyncData, PostApiV1ProjectsByNameTrafficSourcesByIdSyncError, PostApiV1ProjectsByNameTrafficSourcesByIdSyncResponse, PostApiV1ProjectsByNameWordpressConnectData, PostApiV1ProjectsByNameWordpressConnectError, PostApiV1ProjectsByNameWordpressConnectResponse, PostApiV1ProjectsByNameWordpressLlmsTxtManualData, PostApiV1ProjectsByNameWordpressLlmsTxtManualError, PostApiV1ProjectsByNameWordpressLlmsTxtManualResponse, PostApiV1ProjectsByNameWordpressOnboardData, PostApiV1ProjectsByNameWordpressOnboardError, PostApiV1ProjectsByNameWordpressOnboardResponse, PostApiV1ProjectsByNameWordpressPageMetaData, PostApiV1ProjectsByNameWordpressPageMetaError, PostApiV1ProjectsByNameWordpressPageMetaResponse, PostApiV1ProjectsByNameWordpressPagesData, PostApiV1ProjectsByNameWordpressPagesError, PostApiV1ProjectsByNameWordpressPagesMetaBulkData, PostApiV1ProjectsByNameWordpressPagesMetaBulkError, PostApiV1ProjectsByNameWordpressPagesMetaBulkResponse, PostApiV1ProjectsByNameWordpressPagesResponse, PostApiV1ProjectsByNameWordpressSchemaDeployData, PostApiV1ProjectsByNameWordpressSchemaDeployError, PostApiV1ProjectsByNameWordpressSchemaDeployResponse, PostApiV1ProjectsByNameWordpressSchemaManualData, PostApiV1ProjectsByNameWordpressSchemaManualError, PostApiV1ProjectsByNameWordpressSchemaManualResponse, PostApiV1ProjectsByNameWordpressStagingPushData, PostApiV1ProjectsByNameWordpressStagingPushError, PostApiV1ProjectsByNameWordpressStagingPushResponse, PostApiV1RunsByIdCancelData, PostApiV1RunsByIdCancelError, PostApiV1RunsByIdCancelResponse, PostApiV1RunsData, PostApiV1RunsError, PostApiV1RunsResponse, PostApiV1SnapshotData, PostApiV1SnapshotError, PostApiV1SnapshotResponse, PostApiV1TelemetryOnboardingData, PostApiV1TelemetryOnboardingError, PostApiV1TelemetryOnboardingResponse, PostApiV1UsersData, PostApiV1UsersError, PostApiV1UsersResponse, PutApiV1ProjectsByNameAgentMemoryData, PutApiV1ProjectsByNameAgentMemoryError, PutApiV1ProjectsByNameAgentMemoryResponse, PutApiV1ProjectsByNameCompetitorsData, PutApiV1ProjectsByNameCompetitorsResponse, PutApiV1ProjectsByNameData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionError, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionResponse, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyData, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyError, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyResponse, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapData, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapError, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapResponse, PutApiV1ProjectsByNameKeywordsData, PutApiV1ProjectsByNameKeywordsResponse, PutApiV1ProjectsByNameLocationsDefaultData, PutApiV1ProjectsByNameLocationsDefaultError, PutApiV1ProjectsByNameLocationsDefaultResponse, PutApiV1ProjectsByNameMeasurementPlanData, PutApiV1ProjectsByNameMeasurementPlanError, PutApiV1ProjectsByNameMeasurementPlanResponse, PutApiV1ProjectsByNameQueriesData, PutApiV1ProjectsByNameQueriesResponse, PutApiV1ProjectsByNameResponse, PutApiV1ProjectsByNameScheduleData, PutApiV1ProjectsByNameScheduleError, PutApiV1ProjectsByNameScheduleResponse, PutApiV1ProjectsByNameWordpressPageData, PutApiV1ProjectsByNameWordpressPageError, PutApiV1ProjectsByNameWordpressPageResponse, PutApiV1SettingsBingData, PutApiV1SettingsBingError, PutApiV1SettingsBingResponse, PutApiV1SettingsCdpData, PutApiV1SettingsCdpError, PutApiV1SettingsCdpResponse, PutApiV1SettingsGoogleData, PutApiV1SettingsGoogleError, PutApiV1SettingsGoogleResponse, PutApiV1SettingsProvidersByNameData, PutApiV1SettingsProvidersByNameError, PutApiV1SettingsProvidersByNameResponse, PutApiV1TelemetryData, PutApiV1TelemetryError, PutApiV1TelemetryResponse } from '../types.gen.js'; +import { deleteApiV1AuthSessions, deleteApiV1BacklinksCacheByRelease, deleteApiV1ProjectsByName, deleteApiV1ProjectsByNameAdsConnection, deleteApiV1ProjectsByNameAgentMemory, deleteApiV1ProjectsByNameAgentTranscript, deleteApiV1ProjectsByNameBingDisconnect, deleteApiV1ProjectsByNameCompetitors, deleteApiV1ProjectsByNameCompetitorsById, deleteApiV1ProjectsByNameContentDismissalsByTargetRef, deleteApiV1ProjectsByNameGaDisconnect, deleteApiV1ProjectsByNameGbpConnection, deleteApiV1ProjectsByNameGoogleConnectionsByType, deleteApiV1ProjectsByNameKeywords, deleteApiV1ProjectsByNameLocationsByLabel, deleteApiV1ProjectsByNameMeasurementQuerySetsBySetId, deleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateId, deleteApiV1ProjectsByNameNotificationsById, deleteApiV1ProjectsByNameQueries, deleteApiV1ProjectsByNameQueriesById, deleteApiV1ProjectsByNameSchedule, deleteApiV1ProjectsByNameWordpressDisconnect, deleteApiV1UsersByName, getApiV1AuthSession, getApiV1AuthSessions, getApiV1BacklinksLatestRelease, getApiV1BacklinksReleases, getApiV1BacklinksStatus, getApiV1BacklinksSyncs, getApiV1BacklinksSyncsLatest, getApiV1CdpStatus, getApiV1Doctor, getApiV1GoogleCallback, getApiV1History, getApiV1Keys, getApiV1KeysSelf, getApiV1NotificationsEvents, getApiV1OpenapiJson, getApiV1Projects, getApiV1ProjectsByName, getApiV1ProjectsByNameAdsAccount, getApiV1ProjectsByNameAdsCampaigns, getApiV1ProjectsByNameAdsConversionsEventSettings, getApiV1ProjectsByNameAdsConversionsPixels, getApiV1ProjectsByNameAdsDeliveryDiagnostics, getApiV1ProjectsByNameAdsGeoSearch, getApiV1ProjectsByNameAdsInsights, getApiV1ProjectsByNameAdsLiveDelivery, getApiV1ProjectsByNameAdsOperations, getApiV1ProjectsByNameAdsOperationsByOperationKey, getApiV1ProjectsByNameAdsStatus, getApiV1ProjectsByNameAdsSummary, getApiV1ProjectsByNameAgentMemory, getApiV1ProjectsByNameAgentProviders, getApiV1ProjectsByNameAgentTranscript, getApiV1ProjectsByNameAnalyticsGaps, getApiV1ProjectsByNameAnalyticsMetrics, getApiV1ProjectsByNameAnalyticsSources, getApiV1ProjectsByNameBacklinksDomains, getApiV1ProjectsByNameBacklinksHistory, getApiV1ProjectsByNameBacklinksSources, getApiV1ProjectsByNameBacklinksSummary, getApiV1ProjectsByNameBingCoverage, getApiV1ProjectsByNameBingCoverageHistory, getApiV1ProjectsByNameBingInspections, getApiV1ProjectsByNameBingPerformance, getApiV1ProjectsByNameBingSites, getApiV1ProjectsByNameBingStatus, getApiV1ProjectsByNameCitationsVisibility, getApiV1ProjectsByNameCompetitors, getApiV1ProjectsByNameContentDismissals, getApiV1ProjectsByNameContentDomainClassifications, getApiV1ProjectsByNameContentGaps, getApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysis, getApiV1ProjectsByNameContentRecommendationsByTargetRefBrief, getApiV1ProjectsByNameContentSources, getApiV1ProjectsByNameContentTargets, getApiV1ProjectsByNameDeletePreview, getApiV1ProjectsByNameDiscoverSessions, getApiV1ProjectsByNameDiscoverSessionsById, getApiV1ProjectsByNameDiscoverSessionsByIdHarvest, getApiV1ProjectsByNameDiscoverSessionsByIdPromote, getApiV1ProjectsByNameDoctor, getApiV1ProjectsByNameExport, getApiV1ProjectsByNameGaAiReferralDaily, getApiV1ProjectsByNameGaAiReferralHistory, getApiV1ProjectsByNameGaAttributionTrend, getApiV1ProjectsByNameGaCoverage, getApiV1ProjectsByNameGaMeasurementAnalysis, getApiV1ProjectsByNameGaSessionHistory, getApiV1ProjectsByNameGaSocialReferralHistory, getApiV1ProjectsByNameGaSocialReferralTrend, getApiV1ProjectsByNameGaStatus, getApiV1ProjectsByNameGaTraffic, getApiV1ProjectsByNameGbpAccounts, getApiV1ProjectsByNameGbpAttributes, getApiV1ProjectsByNameGbpKeywords, getApiV1ProjectsByNameGbpLocations, getApiV1ProjectsByNameGbpLodging, getApiV1ProjectsByNameGbpMetrics, getApiV1ProjectsByNameGbpPlaceActions, getApiV1ProjectsByNameGbpPlaces, getApiV1ProjectsByNameGbpSummary, getApiV1ProjectsByNameGoogleCallback, getApiV1ProjectsByNameGoogleConnections, getApiV1ProjectsByNameGoogleGscCoverage, getApiV1ProjectsByNameGoogleGscCoverageHistory, getApiV1ProjectsByNameGoogleGscDeindexed, getApiV1ProjectsByNameGoogleGscInspections, getApiV1ProjectsByNameGoogleGscPerformance, getApiV1ProjectsByNameGoogleGscPerformanceDaily, getApiV1ProjectsByNameGoogleGscSitemaps, getApiV1ProjectsByNameGoogleGscTopPages, getApiV1ProjectsByNameGoogleProperties, getApiV1ProjectsByNameHealthHistory, getApiV1ProjectsByNameHealthLatest, getApiV1ProjectsByNameHistory, getApiV1ProjectsByNameInsights, getApiV1ProjectsByNameInsightsById, getApiV1ProjectsByNameKeywords, getApiV1ProjectsByNameLocations, getApiV1ProjectsByNameMeasurementOverview, getApiV1ProjectsByNameMeasurementPlan, getApiV1ProjectsByNameMeasurementPlanDraft, getApiV1ProjectsByNameMeasurementPlanDraftAssignments, getApiV1ProjectsByNameMeasurementPlanDraftGroups, getApiV1ProjectsByNameMeasurementPlanDraftTargets, getApiV1ProjectsByNameMeasurementPlanVersions, getApiV1ProjectsByNameMeasurementPlanVersionsByRevision, getApiV1ProjectsByNameMeasurementQuerySets, getApiV1ProjectsByNameMeasurementQuerySetsBySetId, getApiV1ProjectsByNameMeasurementQueryTemplates, getApiV1ProjectsByNameMeasurementReport, getApiV1ProjectsByNameMeasurementSetup, getApiV1ProjectsByNameNotifications, getApiV1ProjectsByNameOrganicEvidence, getApiV1ProjectsByNameOverview, getApiV1ProjectsByNameQueries, getApiV1ProjectsByNameReport, getApiV1ProjectsByNameReportHtml, getApiV1ProjectsByNameResearchRuns, getApiV1ProjectsByNameResearchRunsByRunId, getApiV1ProjectsByNameResultsExport, getApiV1ProjectsByNameRuns, getApiV1ProjectsByNameRunsByRunIdBrowserDiff, getApiV1ProjectsByNameRunsLatest, getApiV1ProjectsByNameSchedule, getApiV1ProjectsByNameSearch, getApiV1ProjectsByNameSnapshots, getApiV1ProjectsByNameSnapshotsDiff, getApiV1ProjectsByNameTechnicalAeo, getApiV1ProjectsByNameTechnicalAeoPages, getApiV1ProjectsByNameTechnicalAeoTrend, getApiV1ProjectsByNameTimeline, getApiV1ProjectsByNameTrafficEvents, getApiV1ProjectsByNameTrafficSources, getApiV1ProjectsByNameTrafficSourcesById, getApiV1ProjectsByNameTrafficStatus, getApiV1ProjectsByNameVisibilityCompare, getApiV1ProjectsByNameVisibilityStats, getApiV1ProjectsByNameWordpressAudit, getApiV1ProjectsByNameWordpressDiff, getApiV1ProjectsByNameWordpressLlmsTxt, getApiV1ProjectsByNameWordpressPage, getApiV1ProjectsByNameWordpressPages, getApiV1ProjectsByNameWordpressSchema, getApiV1ProjectsByNameWordpressSchemaStatus, getApiV1ProjectsByNameWordpressStagingStatus, getApiV1ProjectsByNameWordpressStatus, getApiV1Runs, getApiV1RunsById, getApiV1ScreenshotsBySnapshotId, getApiV1Settings, getApiV1Telemetry, getApiV1Users, type Options, postApiV1Apply, postApiV1AuthLogin, postApiV1AuthLogout, postApiV1BacklinksInstall, postApiV1BacklinksSyncs, postApiV1CdpScreenshot, postApiV1Keys, postApiV1KeysByIdRevoke, postApiV1ProjectsByNameAdsActivationGrants, postApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevoke, postApiV1ProjectsByNameAdsAdGroups, postApiV1ProjectsByNameAdsAdGroupsById, postApiV1ProjectsByNameAdsAdGroupsByIdPause, postApiV1ProjectsByNameAdsAds, postApiV1ProjectsByNameAdsAdsById, postApiV1ProjectsByNameAdsAdsByIdPause, postApiV1ProjectsByNameAdsCampaigns, postApiV1ProjectsByNameAdsCampaignsById, postApiV1ProjectsByNameAdsCampaignsByIdActivateTree, postApiV1ProjectsByNameAdsCampaignsByIdPause, postApiV1ProjectsByNameAdsConnect, postApiV1ProjectsByNameAdsFiles, postApiV1ProjectsByNameAdsOperationsByOperationKeyReconcile, postApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivation, postApiV1ProjectsByNameAdsSync, postApiV1ProjectsByNameBacklinksExtract, postApiV1ProjectsByNameBingConnect, postApiV1ProjectsByNameBingInspectSitemap, postApiV1ProjectsByNameBingInspectUrl, postApiV1ProjectsByNameBingRequestIndexing, postApiV1ProjectsByNameBingSetSite, postApiV1ProjectsByNameCompetitors, postApiV1ProjectsByNameContentDismissals, postApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyze, postApiV1ProjectsByNameContentRecommendationsByTargetRefBrief, postApiV1ProjectsByNameDiscoverRun, postApiV1ProjectsByNameDiscoverSessionsByIdPromote, postApiV1ProjectsByNameGaConnect, postApiV1ProjectsByNameGaSync, postApiV1ProjectsByNameGbpLocationsDiscover, postApiV1ProjectsByNameGbpSync, postApiV1ProjectsByNameGoogleConnect, postApiV1ProjectsByNameGoogleGscDiscoverSitemaps, postApiV1ProjectsByNameGoogleGscInspect, postApiV1ProjectsByNameGoogleGscInspectSitemap, postApiV1ProjectsByNameGoogleGscSitemapsSubmit, postApiV1ProjectsByNameGoogleGscSync, postApiV1ProjectsByNameGoogleIndexingRequest, postApiV1ProjectsByNameInsightsByIdDismiss, postApiV1ProjectsByNameKeywords, postApiV1ProjectsByNameKeywordsGenerate, postApiV1ProjectsByNameLocations, postApiV1ProjectsByNameMeasurementDiscovery, postApiV1ProjectsByNameMeasurementPlanActionsDeactivate, postApiV1ProjectsByNameMeasurementPlanCompilePreview, postApiV1ProjectsByNameMeasurementPlanDiffPreview, postApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignments, postApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelection, postApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignments, postApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignments, postApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreview, postApiV1ProjectsByNameMeasurementPlanDraftActionsCreate, postApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreview, postApiV1ProjectsByNameMeasurementPlanDraftActionsDiscard, postApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTarget, postApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemap, postApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargets, postApiV1ProjectsByNameMeasurementPlanDraftActionsPublish, postApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTarget, postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignment, postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitor, postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroup, postApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTarget, postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitor, postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroup, postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTarget, postApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetire, postApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApply, postApiV1ProjectsByNameNotifications, postApiV1ProjectsByNameNotificationsByIdTest, postApiV1ProjectsByNameQueries, postApiV1ProjectsByNameQueriesGenerate, postApiV1ProjectsByNameQueriesReplacePreview, postApiV1ProjectsByNameResearchRuns, postApiV1ProjectsByNameRuns, postApiV1ProjectsByNameTechnicalAeoRuns, postApiV1ProjectsByNameTrafficConnectCloudRun, postApiV1ProjectsByNameTrafficConnectVercel, postApiV1ProjectsByNameTrafficConnectWordpress, postApiV1ProjectsByNameTrafficSourcesByIdBackfill, postApiV1ProjectsByNameTrafficSourcesByIdReset, postApiV1ProjectsByNameTrafficSourcesByIdSync, postApiV1ProjectsByNameWordpressConnect, postApiV1ProjectsByNameWordpressLlmsTxtManual, postApiV1ProjectsByNameWordpressOnboard, postApiV1ProjectsByNameWordpressPageMeta, postApiV1ProjectsByNameWordpressPages, postApiV1ProjectsByNameWordpressPagesMetaBulk, postApiV1ProjectsByNameWordpressSchemaDeploy, postApiV1ProjectsByNameWordpressSchemaManual, postApiV1ProjectsByNameWordpressStagingPush, postApiV1Runs, postApiV1RunsByIdCancel, postApiV1Snapshot, postApiV1TelemetryOnboarding, postApiV1Users, putApiV1ProjectsByName, putApiV1ProjectsByNameAgentMemory, putApiV1ProjectsByNameCompetitors, putApiV1ProjectsByNameGbpLocationsByLocationNameSelection, putApiV1ProjectsByNameGoogleConnectionsByTypeProperty, putApiV1ProjectsByNameGoogleConnectionsByTypeSitemap, putApiV1ProjectsByNameKeywords, putApiV1ProjectsByNameLocationsDefault, putApiV1ProjectsByNameMeasurementPlan, putApiV1ProjectsByNameMeasurementQuerySetsBySetId, putApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateId, putApiV1ProjectsByNameQueries, putApiV1ProjectsByNameSchedule, putApiV1ProjectsByNameWordpressPage, putApiV1SettingsBing, putApiV1SettingsCdp, putApiV1SettingsGoogle, putApiV1SettingsProvidersByName, putApiV1Telemetry } from '../sdk.gen.js'; +import type { DeleteApiV1AuthSessionsData, DeleteApiV1AuthSessionsError, DeleteApiV1AuthSessionsResponse, DeleteApiV1BacklinksCacheByReleaseData, DeleteApiV1BacklinksCacheByReleaseError, DeleteApiV1BacklinksCacheByReleaseResponse, DeleteApiV1ProjectsByNameAdsConnectionData, DeleteApiV1ProjectsByNameAdsConnectionError, DeleteApiV1ProjectsByNameAdsConnectionResponse, DeleteApiV1ProjectsByNameAgentMemoryData, DeleteApiV1ProjectsByNameAgentMemoryError, DeleteApiV1ProjectsByNameAgentMemoryResponse, DeleteApiV1ProjectsByNameAgentTranscriptData, DeleteApiV1ProjectsByNameAgentTranscriptError, DeleteApiV1ProjectsByNameAgentTranscriptResponse, DeleteApiV1ProjectsByNameBingDisconnectData, DeleteApiV1ProjectsByNameBingDisconnectError, DeleteApiV1ProjectsByNameBingDisconnectResponse, DeleteApiV1ProjectsByNameCompetitorsByIdData, DeleteApiV1ProjectsByNameCompetitorsByIdError, DeleteApiV1ProjectsByNameCompetitorsByIdResponse, DeleteApiV1ProjectsByNameCompetitorsData, DeleteApiV1ProjectsByNameCompetitorsError, DeleteApiV1ProjectsByNameCompetitorsResponse, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefData, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefError, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefResponse, DeleteApiV1ProjectsByNameData, DeleteApiV1ProjectsByNameError, DeleteApiV1ProjectsByNameGaDisconnectData, DeleteApiV1ProjectsByNameGaDisconnectError, DeleteApiV1ProjectsByNameGaDisconnectResponse, DeleteApiV1ProjectsByNameGbpConnectionData, DeleteApiV1ProjectsByNameGbpConnectionError, DeleteApiV1ProjectsByNameGbpConnectionResponse, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeData, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeError, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeResponse, DeleteApiV1ProjectsByNameKeywordsData, DeleteApiV1ProjectsByNameKeywordsError, DeleteApiV1ProjectsByNameKeywordsResponse, DeleteApiV1ProjectsByNameLocationsByLabelData, DeleteApiV1ProjectsByNameLocationsByLabelError, DeleteApiV1ProjectsByNameLocationsByLabelResponse, DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdData, DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdError, DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponse, DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdData, DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdError, DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponse, DeleteApiV1ProjectsByNameNotificationsByIdData, DeleteApiV1ProjectsByNameNotificationsByIdError, DeleteApiV1ProjectsByNameNotificationsByIdResponse, DeleteApiV1ProjectsByNameQueriesByIdData, DeleteApiV1ProjectsByNameQueriesByIdError, DeleteApiV1ProjectsByNameQueriesByIdResponse, DeleteApiV1ProjectsByNameQueriesData, DeleteApiV1ProjectsByNameQueriesError, DeleteApiV1ProjectsByNameQueriesResponse, DeleteApiV1ProjectsByNameResponse, DeleteApiV1ProjectsByNameScheduleData, DeleteApiV1ProjectsByNameScheduleError, DeleteApiV1ProjectsByNameScheduleResponse, DeleteApiV1ProjectsByNameWordpressDisconnectData, DeleteApiV1ProjectsByNameWordpressDisconnectError, DeleteApiV1ProjectsByNameWordpressDisconnectResponse, DeleteApiV1UsersByNameData, DeleteApiV1UsersByNameError, DeleteApiV1UsersByNameResponse, GetApiV1AuthSessionData, GetApiV1AuthSessionsData, GetApiV1BacklinksLatestReleaseData, GetApiV1BacklinksReleasesData, GetApiV1BacklinksStatusData, GetApiV1BacklinksSyncsData, GetApiV1BacklinksSyncsLatestData, GetApiV1CdpStatusData, GetApiV1DoctorData, GetApiV1GoogleCallbackData, GetApiV1HistoryData, GetApiV1HistoryResponse, GetApiV1KeysData, GetApiV1KeysSelfData, GetApiV1NotificationsEventsData, GetApiV1OpenapiJsonData, GetApiV1ProjectsByNameAdsAccountData, GetApiV1ProjectsByNameAdsCampaignsData, GetApiV1ProjectsByNameAdsConversionsEventSettingsData, GetApiV1ProjectsByNameAdsConversionsPixelsData, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsData, GetApiV1ProjectsByNameAdsGeoSearchData, GetApiV1ProjectsByNameAdsInsightsData, GetApiV1ProjectsByNameAdsLiveDeliveryData, GetApiV1ProjectsByNameAdsOperationsByOperationKeyData, GetApiV1ProjectsByNameAdsOperationsData, GetApiV1ProjectsByNameAdsOperationsError, GetApiV1ProjectsByNameAdsOperationsResponse, GetApiV1ProjectsByNameAdsStatusData, GetApiV1ProjectsByNameAdsSummaryData, GetApiV1ProjectsByNameAgentMemoryData, GetApiV1ProjectsByNameAgentProvidersData, GetApiV1ProjectsByNameAgentTranscriptData, GetApiV1ProjectsByNameAnalyticsGapsData, GetApiV1ProjectsByNameAnalyticsMetricsData, GetApiV1ProjectsByNameAnalyticsSourcesData, GetApiV1ProjectsByNameBacklinksDomainsData, GetApiV1ProjectsByNameBacklinksDomainsError, GetApiV1ProjectsByNameBacklinksDomainsResponse, GetApiV1ProjectsByNameBacklinksHistoryData, GetApiV1ProjectsByNameBacklinksSourcesData, GetApiV1ProjectsByNameBacklinksSummaryData, GetApiV1ProjectsByNameBingCoverageData, GetApiV1ProjectsByNameBingCoverageHistoryData, GetApiV1ProjectsByNameBingInspectionsData, GetApiV1ProjectsByNameBingPerformanceData, GetApiV1ProjectsByNameBingSitesData, GetApiV1ProjectsByNameBingStatusData, GetApiV1ProjectsByNameCitationsVisibilityData, GetApiV1ProjectsByNameCompetitorsData, GetApiV1ProjectsByNameContentDismissalsData, GetApiV1ProjectsByNameContentDomainClassificationsData, GetApiV1ProjectsByNameContentGapsData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, GetApiV1ProjectsByNameContentSourcesData, GetApiV1ProjectsByNameContentTargetsData, GetApiV1ProjectsByNameData, GetApiV1ProjectsByNameDeletePreviewData, GetApiV1ProjectsByNameDiscoverSessionsByIdData, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestData, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, GetApiV1ProjectsByNameDiscoverSessionsData, GetApiV1ProjectsByNameDoctorData, GetApiV1ProjectsByNameExportData, GetApiV1ProjectsByNameGaAiReferralDailyData, GetApiV1ProjectsByNameGaAiReferralHistoryData, GetApiV1ProjectsByNameGaAttributionTrendData, GetApiV1ProjectsByNameGaCoverageData, GetApiV1ProjectsByNameGaMeasurementAnalysisData, GetApiV1ProjectsByNameGaSessionHistoryData, GetApiV1ProjectsByNameGaSocialReferralHistoryData, GetApiV1ProjectsByNameGaSocialReferralTrendData, GetApiV1ProjectsByNameGaStatusData, GetApiV1ProjectsByNameGaTrafficData, GetApiV1ProjectsByNameGbpAccountsData, GetApiV1ProjectsByNameGbpAttributesData, GetApiV1ProjectsByNameGbpKeywordsData, GetApiV1ProjectsByNameGbpLocationsData, GetApiV1ProjectsByNameGbpLodgingData, GetApiV1ProjectsByNameGbpMetricsData, GetApiV1ProjectsByNameGbpPlaceActionsData, GetApiV1ProjectsByNameGbpPlacesData, GetApiV1ProjectsByNameGbpSummaryData, GetApiV1ProjectsByNameGoogleCallbackData, GetApiV1ProjectsByNameGoogleConnectionsData, GetApiV1ProjectsByNameGoogleGscCoverageData, GetApiV1ProjectsByNameGoogleGscCoverageHistoryData, GetApiV1ProjectsByNameGoogleGscDeindexedData, GetApiV1ProjectsByNameGoogleGscInspectionsData, GetApiV1ProjectsByNameGoogleGscPerformanceDailyData, GetApiV1ProjectsByNameGoogleGscPerformanceData, GetApiV1ProjectsByNameGoogleGscPerformanceError, GetApiV1ProjectsByNameGoogleGscPerformanceResponse, GetApiV1ProjectsByNameGoogleGscSitemapsData, GetApiV1ProjectsByNameGoogleGscTopPagesData, GetApiV1ProjectsByNameGooglePropertiesData, GetApiV1ProjectsByNameHealthHistoryData, GetApiV1ProjectsByNameHealthLatestData, GetApiV1ProjectsByNameHistoryData, GetApiV1ProjectsByNameHistoryResponse, GetApiV1ProjectsByNameInsightsByIdData, GetApiV1ProjectsByNameInsightsData, GetApiV1ProjectsByNameKeywordsData, GetApiV1ProjectsByNameLocationsData, GetApiV1ProjectsByNameMeasurementOverviewData, GetApiV1ProjectsByNameMeasurementOverviewError, GetApiV1ProjectsByNameMeasurementOverviewResponse, GetApiV1ProjectsByNameMeasurementPlanData, GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsData, GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsError, GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsResponse, GetApiV1ProjectsByNameMeasurementPlanDraftData, GetApiV1ProjectsByNameMeasurementPlanDraftGroupsData, GetApiV1ProjectsByNameMeasurementPlanDraftGroupsError, GetApiV1ProjectsByNameMeasurementPlanDraftGroupsResponse, GetApiV1ProjectsByNameMeasurementPlanDraftTargetsData, GetApiV1ProjectsByNameMeasurementPlanDraftTargetsError, GetApiV1ProjectsByNameMeasurementPlanDraftTargetsResponse, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionData, GetApiV1ProjectsByNameMeasurementPlanVersionsData, GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdData, GetApiV1ProjectsByNameMeasurementQuerySetsData, GetApiV1ProjectsByNameMeasurementQueryTemplatesData, GetApiV1ProjectsByNameMeasurementReportData, GetApiV1ProjectsByNameMeasurementSetupData, GetApiV1ProjectsByNameNotificationsData, GetApiV1ProjectsByNameOrganicEvidenceData, GetApiV1ProjectsByNameOverviewData, GetApiV1ProjectsByNameQueriesData, GetApiV1ProjectsByNameReportData, GetApiV1ProjectsByNameReportHtmlData, GetApiV1ProjectsByNameResearchRunsByRunIdData, GetApiV1ProjectsByNameResearchRunsData, GetApiV1ProjectsByNameResultsExportData, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffData, GetApiV1ProjectsByNameRunsData, GetApiV1ProjectsByNameRunsLatestData, GetApiV1ProjectsByNameScheduleData, GetApiV1ProjectsByNameSearchData, GetApiV1ProjectsByNameSnapshotsData, GetApiV1ProjectsByNameSnapshotsDiffData, GetApiV1ProjectsByNameSnapshotsResponse, GetApiV1ProjectsByNameTechnicalAeoData, GetApiV1ProjectsByNameTechnicalAeoPagesData, GetApiV1ProjectsByNameTechnicalAeoPagesError, GetApiV1ProjectsByNameTechnicalAeoPagesResponse, GetApiV1ProjectsByNameTechnicalAeoTrendData, GetApiV1ProjectsByNameTimelineData, GetApiV1ProjectsByNameTrafficEventsData, GetApiV1ProjectsByNameTrafficSourcesByIdData, GetApiV1ProjectsByNameTrafficSourcesData, GetApiV1ProjectsByNameTrafficStatusData, GetApiV1ProjectsByNameVisibilityCompareData, GetApiV1ProjectsByNameVisibilityStatsData, GetApiV1ProjectsByNameWordpressAuditData, GetApiV1ProjectsByNameWordpressDiffData, GetApiV1ProjectsByNameWordpressLlmsTxtData, GetApiV1ProjectsByNameWordpressPageData, GetApiV1ProjectsByNameWordpressPagesData, GetApiV1ProjectsByNameWordpressSchemaData, GetApiV1ProjectsByNameWordpressSchemaStatusData, GetApiV1ProjectsByNameWordpressStagingStatusData, GetApiV1ProjectsByNameWordpressStatusData, GetApiV1ProjectsData, GetApiV1RunsByIdData, GetApiV1RunsData, GetApiV1ScreenshotsBySnapshotIdData, GetApiV1SettingsData, GetApiV1TelemetryData, GetApiV1UsersData, PostApiV1ApplyData, PostApiV1ApplyError, PostApiV1ApplyResponse, PostApiV1AuthLoginData, PostApiV1AuthLoginError, PostApiV1AuthLoginResponse, PostApiV1AuthLogoutData, PostApiV1AuthLogoutResponse, PostApiV1BacklinksInstallData, PostApiV1BacklinksInstallError, PostApiV1BacklinksInstallResponse, PostApiV1BacklinksSyncsData, PostApiV1BacklinksSyncsError, PostApiV1BacklinksSyncsResponse, PostApiV1CdpScreenshotData, PostApiV1CdpScreenshotError, PostApiV1CdpScreenshotResponse, PostApiV1KeysByIdRevokeData, PostApiV1KeysByIdRevokeError, PostApiV1KeysByIdRevokeResponse, PostApiV1KeysData, PostApiV1KeysError, PostApiV1KeysResponse, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeData, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeError, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeResponse, PostApiV1ProjectsByNameAdsActivationGrantsData, PostApiV1ProjectsByNameAdsActivationGrantsError, PostApiV1ProjectsByNameAdsActivationGrantsResponse, PostApiV1ProjectsByNameAdsAdGroupsByIdData, PostApiV1ProjectsByNameAdsAdGroupsByIdError, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseData, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseError, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseResponse, PostApiV1ProjectsByNameAdsAdGroupsByIdResponse, PostApiV1ProjectsByNameAdsAdGroupsData, PostApiV1ProjectsByNameAdsAdGroupsError, PostApiV1ProjectsByNameAdsAdGroupsResponse, PostApiV1ProjectsByNameAdsAdsByIdData, PostApiV1ProjectsByNameAdsAdsByIdError, PostApiV1ProjectsByNameAdsAdsByIdPauseData, PostApiV1ProjectsByNameAdsAdsByIdPauseError, PostApiV1ProjectsByNameAdsAdsByIdPauseResponse, PostApiV1ProjectsByNameAdsAdsByIdResponse, PostApiV1ProjectsByNameAdsAdsData, PostApiV1ProjectsByNameAdsAdsError, PostApiV1ProjectsByNameAdsAdsResponse, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeData, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeError, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeResponse, PostApiV1ProjectsByNameAdsCampaignsByIdData, PostApiV1ProjectsByNameAdsCampaignsByIdError, PostApiV1ProjectsByNameAdsCampaignsByIdPauseData, PostApiV1ProjectsByNameAdsCampaignsByIdPauseError, PostApiV1ProjectsByNameAdsCampaignsByIdPauseResponse, PostApiV1ProjectsByNameAdsCampaignsByIdResponse, PostApiV1ProjectsByNameAdsCampaignsData, PostApiV1ProjectsByNameAdsCampaignsError, PostApiV1ProjectsByNameAdsCampaignsResponse, PostApiV1ProjectsByNameAdsConnectData, PostApiV1ProjectsByNameAdsConnectError, PostApiV1ProjectsByNameAdsConnectResponse, PostApiV1ProjectsByNameAdsFilesData, PostApiV1ProjectsByNameAdsFilesError, PostApiV1ProjectsByNameAdsFilesResponse, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileError, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileResponse, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationError, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationResponse, PostApiV1ProjectsByNameAdsSyncData, PostApiV1ProjectsByNameAdsSyncError, PostApiV1ProjectsByNameAdsSyncResponse, PostApiV1ProjectsByNameBacklinksExtractData, PostApiV1ProjectsByNameBacklinksExtractError, PostApiV1ProjectsByNameBacklinksExtractResponse, PostApiV1ProjectsByNameBingConnectData, PostApiV1ProjectsByNameBingConnectError, PostApiV1ProjectsByNameBingConnectResponse, PostApiV1ProjectsByNameBingInspectSitemapData, PostApiV1ProjectsByNameBingInspectSitemapError, PostApiV1ProjectsByNameBingInspectSitemapResponse, PostApiV1ProjectsByNameBingInspectUrlData, PostApiV1ProjectsByNameBingInspectUrlError, PostApiV1ProjectsByNameBingInspectUrlResponse, PostApiV1ProjectsByNameBingRequestIndexingData, PostApiV1ProjectsByNameBingRequestIndexingError, PostApiV1ProjectsByNameBingRequestIndexingResponse, PostApiV1ProjectsByNameBingSetSiteData, PostApiV1ProjectsByNameBingSetSiteError, PostApiV1ProjectsByNameBingSetSiteResponse, PostApiV1ProjectsByNameCompetitorsData, PostApiV1ProjectsByNameCompetitorsError, PostApiV1ProjectsByNameCompetitorsResponse, PostApiV1ProjectsByNameContentDismissalsData, PostApiV1ProjectsByNameContentDismissalsError, PostApiV1ProjectsByNameContentDismissalsResponse, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeError, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeResponse, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefError, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefResponse, PostApiV1ProjectsByNameDiscoverRunData, PostApiV1ProjectsByNameDiscoverRunError, PostApiV1ProjectsByNameDiscoverRunResponse, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteError, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteResponse, PostApiV1ProjectsByNameGaConnectData, PostApiV1ProjectsByNameGaConnectError, PostApiV1ProjectsByNameGaConnectResponse, PostApiV1ProjectsByNameGaSyncData, PostApiV1ProjectsByNameGaSyncError, PostApiV1ProjectsByNameGaSyncResponse, PostApiV1ProjectsByNameGbpLocationsDiscoverData, PostApiV1ProjectsByNameGbpLocationsDiscoverError, PostApiV1ProjectsByNameGbpLocationsDiscoverResponse, PostApiV1ProjectsByNameGbpSyncData, PostApiV1ProjectsByNameGbpSyncError, PostApiV1ProjectsByNameGbpSyncResponse, PostApiV1ProjectsByNameGoogleConnectData, PostApiV1ProjectsByNameGoogleConnectError, PostApiV1ProjectsByNameGoogleConnectResponse, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsData, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsError, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsResponse, PostApiV1ProjectsByNameGoogleGscInspectData, PostApiV1ProjectsByNameGoogleGscInspectError, PostApiV1ProjectsByNameGoogleGscInspectResponse, PostApiV1ProjectsByNameGoogleGscInspectSitemapData, PostApiV1ProjectsByNameGoogleGscInspectSitemapError, PostApiV1ProjectsByNameGoogleGscInspectSitemapResponse, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitData, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitError, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponse, PostApiV1ProjectsByNameGoogleGscSyncData, PostApiV1ProjectsByNameGoogleGscSyncError, PostApiV1ProjectsByNameGoogleGscSyncResponse, PostApiV1ProjectsByNameGoogleIndexingRequestData, PostApiV1ProjectsByNameGoogleIndexingRequestError, PostApiV1ProjectsByNameGoogleIndexingRequestResponse, PostApiV1ProjectsByNameInsightsByIdDismissData, PostApiV1ProjectsByNameInsightsByIdDismissError, PostApiV1ProjectsByNameInsightsByIdDismissResponse, PostApiV1ProjectsByNameKeywordsData, PostApiV1ProjectsByNameKeywordsGenerateData, PostApiV1ProjectsByNameKeywordsGenerateError, PostApiV1ProjectsByNameKeywordsGenerateResponse, PostApiV1ProjectsByNameKeywordsResponse, PostApiV1ProjectsByNameLocationsData, PostApiV1ProjectsByNameLocationsError, PostApiV1ProjectsByNameLocationsResponse, PostApiV1ProjectsByNameMeasurementDiscoveryData, PostApiV1ProjectsByNameMeasurementDiscoveryError, PostApiV1ProjectsByNameMeasurementDiscoveryResponse, PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateData, PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateError, PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateResponse, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewData, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewError, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponse, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewData, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewError, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupResponse, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetError, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetResponse, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireData, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireError, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponse, PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyData, PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyError, PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyResponse, PostApiV1ProjectsByNameNotificationsByIdTestData, PostApiV1ProjectsByNameNotificationsByIdTestError, PostApiV1ProjectsByNameNotificationsByIdTestResponse, PostApiV1ProjectsByNameNotificationsData, PostApiV1ProjectsByNameNotificationsResponse, PostApiV1ProjectsByNameQueriesData, PostApiV1ProjectsByNameQueriesGenerateData, PostApiV1ProjectsByNameQueriesGenerateError, PostApiV1ProjectsByNameQueriesGenerateResponse, PostApiV1ProjectsByNameQueriesReplacePreviewData, PostApiV1ProjectsByNameQueriesReplacePreviewError, PostApiV1ProjectsByNameQueriesReplacePreviewResponse, PostApiV1ProjectsByNameQueriesResponse, PostApiV1ProjectsByNameResearchRunsData, PostApiV1ProjectsByNameResearchRunsError, PostApiV1ProjectsByNameResearchRunsResponse, PostApiV1ProjectsByNameRunsData, PostApiV1ProjectsByNameRunsError, PostApiV1ProjectsByNameRunsResponse, PostApiV1ProjectsByNameTechnicalAeoRunsData, PostApiV1ProjectsByNameTechnicalAeoRunsError, PostApiV1ProjectsByNameTechnicalAeoRunsResponse, PostApiV1ProjectsByNameTrafficConnectCloudRunData, PostApiV1ProjectsByNameTrafficConnectCloudRunError, PostApiV1ProjectsByNameTrafficConnectCloudRunResponse, PostApiV1ProjectsByNameTrafficConnectVercelData, PostApiV1ProjectsByNameTrafficConnectVercelError, PostApiV1ProjectsByNameTrafficConnectVercelResponse, PostApiV1ProjectsByNameTrafficConnectWordpressData, PostApiV1ProjectsByNameTrafficConnectWordpressError, PostApiV1ProjectsByNameTrafficConnectWordpressResponse, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillData, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillError, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillResponse, PostApiV1ProjectsByNameTrafficSourcesByIdResetData, PostApiV1ProjectsByNameTrafficSourcesByIdResetError, PostApiV1ProjectsByNameTrafficSourcesByIdResetResponse, PostApiV1ProjectsByNameTrafficSourcesByIdSyncData, PostApiV1ProjectsByNameTrafficSourcesByIdSyncError, PostApiV1ProjectsByNameTrafficSourcesByIdSyncResponse, PostApiV1ProjectsByNameWordpressConnectData, PostApiV1ProjectsByNameWordpressConnectError, PostApiV1ProjectsByNameWordpressConnectResponse, PostApiV1ProjectsByNameWordpressLlmsTxtManualData, PostApiV1ProjectsByNameWordpressLlmsTxtManualError, PostApiV1ProjectsByNameWordpressLlmsTxtManualResponse, PostApiV1ProjectsByNameWordpressOnboardData, PostApiV1ProjectsByNameWordpressOnboardError, PostApiV1ProjectsByNameWordpressOnboardResponse, PostApiV1ProjectsByNameWordpressPageMetaData, PostApiV1ProjectsByNameWordpressPageMetaError, PostApiV1ProjectsByNameWordpressPageMetaResponse, PostApiV1ProjectsByNameWordpressPagesData, PostApiV1ProjectsByNameWordpressPagesError, PostApiV1ProjectsByNameWordpressPagesMetaBulkData, PostApiV1ProjectsByNameWordpressPagesMetaBulkError, PostApiV1ProjectsByNameWordpressPagesMetaBulkResponse, PostApiV1ProjectsByNameWordpressPagesResponse, PostApiV1ProjectsByNameWordpressSchemaDeployData, PostApiV1ProjectsByNameWordpressSchemaDeployError, PostApiV1ProjectsByNameWordpressSchemaDeployResponse, PostApiV1ProjectsByNameWordpressSchemaManualData, PostApiV1ProjectsByNameWordpressSchemaManualError, PostApiV1ProjectsByNameWordpressSchemaManualResponse, PostApiV1ProjectsByNameWordpressStagingPushData, PostApiV1ProjectsByNameWordpressStagingPushError, PostApiV1ProjectsByNameWordpressStagingPushResponse, PostApiV1RunsByIdCancelData, PostApiV1RunsByIdCancelError, PostApiV1RunsByIdCancelResponse, PostApiV1RunsData, PostApiV1RunsError, PostApiV1RunsResponse, PostApiV1SnapshotData, PostApiV1SnapshotError, PostApiV1SnapshotResponse, PostApiV1TelemetryOnboardingData, PostApiV1TelemetryOnboardingError, PostApiV1TelemetryOnboardingResponse, PostApiV1UsersData, PostApiV1UsersError, PostApiV1UsersResponse, PutApiV1ProjectsByNameAgentMemoryData, PutApiV1ProjectsByNameAgentMemoryError, PutApiV1ProjectsByNameAgentMemoryResponse, PutApiV1ProjectsByNameCompetitorsData, PutApiV1ProjectsByNameCompetitorsResponse, PutApiV1ProjectsByNameData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionError, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionResponse, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyData, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyError, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyResponse, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapData, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapError, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapResponse, PutApiV1ProjectsByNameKeywordsData, PutApiV1ProjectsByNameKeywordsResponse, PutApiV1ProjectsByNameLocationsDefaultData, PutApiV1ProjectsByNameLocationsDefaultError, PutApiV1ProjectsByNameLocationsDefaultResponse, PutApiV1ProjectsByNameMeasurementPlanData, PutApiV1ProjectsByNameMeasurementPlanError, PutApiV1ProjectsByNameMeasurementPlanResponse, PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdData, PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdError, PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponse, PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdData, PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdError, PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponse, PutApiV1ProjectsByNameQueriesData, PutApiV1ProjectsByNameQueriesResponse, PutApiV1ProjectsByNameResponse, PutApiV1ProjectsByNameScheduleData, PutApiV1ProjectsByNameScheduleError, PutApiV1ProjectsByNameScheduleResponse, PutApiV1ProjectsByNameWordpressPageData, PutApiV1ProjectsByNameWordpressPageError, PutApiV1ProjectsByNameWordpressPageResponse, PutApiV1SettingsBingData, PutApiV1SettingsBingError, PutApiV1SettingsBingResponse, PutApiV1SettingsCdpData, PutApiV1SettingsCdpError, PutApiV1SettingsCdpResponse, PutApiV1SettingsGoogleData, PutApiV1SettingsGoogleError, PutApiV1SettingsGoogleResponse, PutApiV1SettingsProvidersByNameData, PutApiV1SettingsProvidersByNameError, PutApiV1SettingsProvidersByNameResponse, PutApiV1TelemetryData, PutApiV1TelemetryError, PutApiV1TelemetryResponse } from '../types.gen.js'; export type QueryKey = [ Pick & { @@ -315,6 +315,833 @@ export const getApiV1ProjectsByNameMeasurementReportOptions = (options: Options< }); }; +export const getApiV1ProjectsByNameMeasurementSetupQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementSetup', options); + +/** + * Get the measurement setup state + * + * Returns exactly one state, evaluated in a fixed precedence: republish_required, setup_in_progress, awaiting_first_run, operational, simple. A draft over an active v1 plan is republish_required, because republishing is the blocking action. + */ +export const getApiV1ProjectsByNameMeasurementSetupOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementSetup({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementSetupQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementPlanDraftQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementPlanDraft', options); + +/** + * Get the server-side setup draft + * + * Returns the single draft for the project, or an explicit null for a project with none. The response ETag is required on every subsequent mutation. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementPlanDraft({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementPlanDraftQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementPlanDraftTargetsQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementPlanDraftTargets', options); + +/** + * Page the draft Targets + * + * Cursor-paginated and deterministically ordered by normalized label then stable key. Serves a thousand Targets without client-side truncation. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftTargetsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementPlanDraftTargets({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementPlanDraftTargetsQueryKey(options) + }); +}; + +const createInfiniteParams = [0], 'body' | 'headers' | 'path' | 'query'>>(queryKey: QueryKey, page: K) => { + const params = { + ...queryKey[0] + }; + if (page.body) { + params.body = { + ...queryKey[0].body as any, + ...page.body as any + }; + } + if (page.headers) { + params.headers = { + ...queryKey[0].headers, + ...page.headers + }; + } + if (page.path) { + params.path = { + ...queryKey[0].path as any, + ...page.path as any + }; + } + if (page.query) { + params.query = { + ...queryKey[0].query as any, + ...page.query as any + }; + } + return params as unknown as typeof page; +}; + +export const getApiV1ProjectsByNameMeasurementPlanDraftTargetsInfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('getApiV1ProjectsByNameMeasurementPlanDraftTargets', options, true); + +/** + * Page the draft Targets + * + * Cursor-paginated and deterministically ordered by normalized label then stable key. Serves a thousand Targets without client-side truncation. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftTargetsInfiniteOptions = (options: Options) => { + return infiniteQueryOptions, QueryKey>, string | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>( + // @ts-ignore + { + queryFn: async ({ pageParam, queryKey, signal }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = typeof pageParam === 'object' ? pageParam : { + query: { + cursor: pageParam + } + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await getApiV1ProjectsByNameMeasurementPlanDraftTargets({ + ...options, + ...params, + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementPlanDraftTargetsInfiniteQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementPlanDraftAssignmentsQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementPlanDraftAssignments', options); + +/** + * Page the draft assignments + * + * Cursor-paginated Target-owned query assignments, each carrying its class and where that class came from. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftAssignmentsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementPlanDraftAssignments({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementPlanDraftAssignmentsQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementPlanDraftAssignmentsInfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('getApiV1ProjectsByNameMeasurementPlanDraftAssignments', options, true); + +/** + * Page the draft assignments + * + * Cursor-paginated Target-owned query assignments, each carrying its class and where that class came from. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftAssignmentsInfiniteOptions = (options: Options) => { + return infiniteQueryOptions, QueryKey>, string | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>( + // @ts-ignore + { + queryFn: async ({ pageParam, queryKey, signal }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = typeof pageParam === 'object' ? pageParam : { + query: { + cursor: pageParam + } + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await getApiV1ProjectsByNameMeasurementPlanDraftAssignments({ + ...options, + ...params, + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementPlanDraftAssignmentsInfiniteQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementPlanDraftGroupsQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementPlanDraftGroups', options); + +/** + * Page the draft groups + * + * Cursor-paginated reporting groups and their confirmed competitors. Groups never hold queries or execution context. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftGroupsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementPlanDraftGroups({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementPlanDraftGroupsQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementPlanDraftGroupsInfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('getApiV1ProjectsByNameMeasurementPlanDraftGroups', options, true); + +/** + * Page the draft groups + * + * Cursor-paginated reporting groups and their confirmed competitors. Groups never hold queries or execution context. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftGroupsInfiniteOptions = (options: Options) => { + return infiniteQueryOptions, QueryKey>, string | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>( + // @ts-ignore + { + queryFn: async ({ pageParam, queryKey, signal }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = typeof pageParam === 'object' ? pageParam : { + query: { + cursor: pageParam + } + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await getApiV1ProjectsByNameMeasurementPlanDraftGroups({ + ...options, + ...params, + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementPlanDraftGroupsInfiniteQueryKey(options) + }); +}; + +/** + * Start a setup draft + * + * Creates the single draft for the project, recording the active revision it was created from. A draft never changes the active mode. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsCreateMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsCreate({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Import a sitemap into the draft + * + * Fetches an operator-supplied sitemap under strict egress policy and records the deterministic discovery inputs on the draft. It proposes Targets for review; it never publishes a plan or starts a run. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemap({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Apply the operator selection from discovery + * + * Turns reviewed discovery proposals into new or rebound Targets. Ambiguity is never resolved automatically. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelection({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Add or update a draft Target + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTarget({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Rename a draft Target + * + * Changes the label only. The stable key, its assignments and its group membership are untouched. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTarget({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Merge draft Targets into one + * + * The survivor keeps its stable key, and the merged Targets contribute their assignments and group membership. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargets({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Exclude a draft Target + * + * Excluded Targets stay in the draft for review but never compile. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTarget({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Rebind a draft Target to a new discovered URL + * + * Preserves the stable key, the assignments and the group membership, so history follows the Target across a site restructure. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTarget({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Assign project queries to a draft Target + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignments({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Remove one assignment from a draft Target + * + * Removes the assignment only. The project query behind it is never deleted. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignment({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Clear every assignment on a draft Target + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignments({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Classify assignments as Branded or Non-brand + * + * Always records the class as operator-sourced, so a later rule proposal cannot overwrite it. The class belongs to the assignment, so one query can be Branded for one Target and Non-brand for another. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignments({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Add or update a draft group + * + * Reporting membership only. A payload carrying queries, providers, locations or models is rejected. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroup({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Remove a draft group + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroup({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Add or update a group competitor + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitor({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Remove a group competitor + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitor({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Compile the draft without publishing + * + * Compiles the stored draft and returns the compiled checksum the publish guard expects. Writes nothing, so a view-only account can call it. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreview({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Compare the draft with the active revision + * + * Compiles the stored draft and reports the keys that changed against the active revision. Writes nothing. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreview({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Publish the draft as a new revision + * + * Recompiles server-side and refuses content that changed after review. Content identical to the active revision is a no-op returning it; content identical to an older revision publishes as a new revision, so a revert is a first-class operation. Publishing never starts a run. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsPublishMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsPublish({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Discard the draft + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanDraftActionsDiscard({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Deactivate the active measurement plan + * + * Deletes the active-plan pointer and nothing else. Schedules, runs, queries, versions and evidence are untouched, and the revisions stay readable. + */ +export const postApiV1ProjectsByNameMeasurementPlanActionsDeactivateMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementPlanActionsDeactivate({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const getApiV1ProjectsByNameMeasurementOverviewQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementOverview', options); + +/** + * Get the scoped measurement overview + * + * Aggregates one run of evidence for All Properties, a group, or a single Property. Without runId the most recent completed run pinned to the active revision is used; a run pinned to another revision is refused rather than joined. Metrics are computed before search is applied, and a metric with no evidence is unavailable rather than zero. + */ +export const getApiV1ProjectsByNameMeasurementOverviewOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementOverview({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementOverviewQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementOverviewInfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('getApiV1ProjectsByNameMeasurementOverview', options, true); + +/** + * Get the scoped measurement overview + * + * Aggregates one run of evidence for All Properties, a group, or a single Property. Without runId the most recent completed run pinned to the active revision is used; a run pinned to another revision is refused rather than joined. Metrics are computed before search is applied, and a metric with no evidence is unavailable rather than zero. + */ +export const getApiV1ProjectsByNameMeasurementOverviewInfiniteOptions = (options: Options) => { + return infiniteQueryOptions, QueryKey>, string | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>( + // @ts-ignore + { + queryFn: async ({ pageParam, queryKey, signal }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = typeof pageParam === 'object' ? pageParam : { + query: { + cursor: pageParam + } + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await getApiV1ProjectsByNameMeasurementOverview({ + ...options, + ...params, + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementOverviewInfiniteQueryKey(options) + }); +}; + +export const getApiV1ProjectsByNameMeasurementQuerySetsQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementQuerySets', options); + +/** + * List measurement query sets + */ +export const getApiV1ProjectsByNameMeasurementQuerySetsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementQuerySets({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementQuerySetsQueryKey(options) + }); +}; + +/** + * Delete a measurement query set + * + * Deletes the set and its membership rows. It never deletes a query or a published snapshot. + */ +export const deleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await deleteApiV1ProjectsByNameMeasurementQuerySetsBySetId({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const getApiV1ProjectsByNameMeasurementQuerySetsBySetIdQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementQuerySetsBySetId', options); + +/** + * Get a measurement query set + */ +export const getApiV1ProjectsByNameMeasurementQuerySetsBySetIdOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementQuerySetsBySetId({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementQuerySetsBySetIdQueryKey(options) + }); +}; + +/** + * Create or replace a measurement query set + * + * A set holds ordered references to project query IDs. Membership changes never create or delete a query. + */ +export const putApiV1ProjectsByNameMeasurementQuerySetsBySetIdMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await putApiV1ProjectsByNameMeasurementQuerySetsBySetId({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const getApiV1ProjectsByNameMeasurementQueryTemplatesQueryKey = (options: Options) => createQueryKey('getApiV1ProjectsByNameMeasurementQueryTemplates', options); + +/** + * List measurement query templates + */ +export const getApiV1ProjectsByNameMeasurementQueryTemplatesOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getApiV1ProjectsByNameMeasurementQueryTemplates({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getApiV1ProjectsByNameMeasurementQueryTemplatesQueryKey(options) + }); +}; + +/** + * Delete a measurement query template + * + * Deletes the authoring template. Queries it already expanded, and every published snapshot of them, are untouched. + */ +export const deleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await deleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateId({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Create or replace a measurement query template + * + * Templates are authoring assets. A published plan contains only the immutable snapshots an expansion produced. + */ +export const putApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await putApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateId({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Expand a query template into project queries + * + * Expands one binding per concrete query. Expansion is additive: a query that already exists is reported rather than duplicated. + */ +export const postApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApply({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getApiV1ProjectsQueryKey = (options?: Options) => createQueryKey('getApiV1Projects', options); /** @@ -997,37 +1824,6 @@ export const getApiV1ProjectsByNameHistoryOptions = (options: Options[0], 'body' | 'headers' | 'path' | 'query'>>(queryKey: QueryKey, page: K) => { - const params = { - ...queryKey[0] - }; - if (page.body) { - params.body = { - ...queryKey[0].body as any, - ...page.body as any - }; - } - if (page.headers) { - params.headers = { - ...queryKey[0].headers, - ...page.headers - }; - } - if (page.path) { - params.path = { - ...queryKey[0].path as any, - ...page.path as any - }; - } - if (page.query) { - params.query = { - ...queryKey[0].query as any, - ...page.query as any - }; - } - return params as unknown as typeof page; -}; - export const getApiV1ProjectsByNameHistoryInfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('getApiV1ProjectsByNameHistory', options, true); /** diff --git a/packages/api-client-generated/src/generated/sdk.gen.ts b/packages/api-client-generated/src/generated/sdk.gen.ts index 40b44559f..2035ea8fb 100644 --- a/packages/api-client-generated/src/generated/sdk.gen.ts +++ b/packages/api-client-generated/src/generated/sdk.gen.ts @@ -2,7 +2,7 @@ import { client } from './client.gen.js'; import type { Client, Options as Options2, TDataShape } from './client/index.js'; -import type { DeleteApiV1AuthSessionsData, DeleteApiV1AuthSessionsErrors, DeleteApiV1AuthSessionsResponses, DeleteApiV1BacklinksCacheByReleaseData, DeleteApiV1BacklinksCacheByReleaseErrors, DeleteApiV1BacklinksCacheByReleaseResponses, DeleteApiV1ProjectsByNameAdsConnectionData, DeleteApiV1ProjectsByNameAdsConnectionErrors, DeleteApiV1ProjectsByNameAdsConnectionResponses, DeleteApiV1ProjectsByNameAgentMemoryData, DeleteApiV1ProjectsByNameAgentMemoryErrors, DeleteApiV1ProjectsByNameAgentMemoryResponses, DeleteApiV1ProjectsByNameAgentTranscriptData, DeleteApiV1ProjectsByNameAgentTranscriptErrors, DeleteApiV1ProjectsByNameAgentTranscriptResponses, DeleteApiV1ProjectsByNameBingDisconnectData, DeleteApiV1ProjectsByNameBingDisconnectErrors, DeleteApiV1ProjectsByNameBingDisconnectResponses, DeleteApiV1ProjectsByNameCompetitorsByIdData, DeleteApiV1ProjectsByNameCompetitorsByIdErrors, DeleteApiV1ProjectsByNameCompetitorsByIdResponses, DeleteApiV1ProjectsByNameCompetitorsData, DeleteApiV1ProjectsByNameCompetitorsErrors, DeleteApiV1ProjectsByNameCompetitorsResponses, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefData, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefErrors, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefResponses, DeleteApiV1ProjectsByNameData, DeleteApiV1ProjectsByNameErrors, DeleteApiV1ProjectsByNameGaDisconnectData, DeleteApiV1ProjectsByNameGaDisconnectErrors, DeleteApiV1ProjectsByNameGaDisconnectResponses, DeleteApiV1ProjectsByNameGbpConnectionData, DeleteApiV1ProjectsByNameGbpConnectionErrors, DeleteApiV1ProjectsByNameGbpConnectionResponses, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeData, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeErrors, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeResponses, DeleteApiV1ProjectsByNameKeywordsData, DeleteApiV1ProjectsByNameKeywordsErrors, DeleteApiV1ProjectsByNameKeywordsResponses, DeleteApiV1ProjectsByNameLocationsByLabelData, DeleteApiV1ProjectsByNameLocationsByLabelErrors, DeleteApiV1ProjectsByNameLocationsByLabelResponses, DeleteApiV1ProjectsByNameNotificationsByIdData, DeleteApiV1ProjectsByNameNotificationsByIdErrors, DeleteApiV1ProjectsByNameNotificationsByIdResponses, DeleteApiV1ProjectsByNameQueriesByIdData, DeleteApiV1ProjectsByNameQueriesByIdErrors, DeleteApiV1ProjectsByNameQueriesByIdResponses, DeleteApiV1ProjectsByNameQueriesData, DeleteApiV1ProjectsByNameQueriesErrors, DeleteApiV1ProjectsByNameQueriesResponses, DeleteApiV1ProjectsByNameResponses, DeleteApiV1ProjectsByNameScheduleData, DeleteApiV1ProjectsByNameScheduleErrors, DeleteApiV1ProjectsByNameScheduleResponses, DeleteApiV1ProjectsByNameWordpressDisconnectData, DeleteApiV1ProjectsByNameWordpressDisconnectErrors, DeleteApiV1ProjectsByNameWordpressDisconnectResponses, DeleteApiV1UsersByNameData, DeleteApiV1UsersByNameErrors, DeleteApiV1UsersByNameResponses, GetApiV1AuthSessionData, GetApiV1AuthSessionResponses, GetApiV1AuthSessionsData, GetApiV1AuthSessionsErrors, GetApiV1AuthSessionsResponses, GetApiV1BacklinksLatestReleaseData, GetApiV1BacklinksLatestReleaseErrors, GetApiV1BacklinksLatestReleaseResponses, GetApiV1BacklinksReleasesData, GetApiV1BacklinksReleasesResponses, GetApiV1BacklinksStatusData, GetApiV1BacklinksStatusErrors, GetApiV1BacklinksStatusResponses, GetApiV1BacklinksSyncsData, GetApiV1BacklinksSyncsLatestData, GetApiV1BacklinksSyncsLatestResponses, GetApiV1BacklinksSyncsResponses, GetApiV1CdpStatusData, GetApiV1CdpStatusErrors, GetApiV1CdpStatusResponses, GetApiV1DoctorData, GetApiV1DoctorResponses, GetApiV1GoogleCallbackData, GetApiV1GoogleCallbackErrors, GetApiV1GoogleCallbackResponses, GetApiV1HistoryData, GetApiV1HistoryResponses, GetApiV1KeysData, GetApiV1KeysResponses, GetApiV1KeysSelfData, GetApiV1KeysSelfErrors, GetApiV1KeysSelfResponses, GetApiV1NotificationsEventsData, GetApiV1NotificationsEventsResponses, GetApiV1OpenapiJsonData, GetApiV1OpenapiJsonResponses, GetApiV1ProjectsByNameAdsAccountData, GetApiV1ProjectsByNameAdsAccountErrors, GetApiV1ProjectsByNameAdsAccountResponses, GetApiV1ProjectsByNameAdsCampaignsData, GetApiV1ProjectsByNameAdsCampaignsErrors, GetApiV1ProjectsByNameAdsCampaignsResponses, GetApiV1ProjectsByNameAdsConversionsEventSettingsData, GetApiV1ProjectsByNameAdsConversionsEventSettingsErrors, GetApiV1ProjectsByNameAdsConversionsEventSettingsResponses, GetApiV1ProjectsByNameAdsConversionsPixelsData, GetApiV1ProjectsByNameAdsConversionsPixelsErrors, GetApiV1ProjectsByNameAdsConversionsPixelsResponses, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsData, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsErrors, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsResponses, GetApiV1ProjectsByNameAdsGeoSearchData, GetApiV1ProjectsByNameAdsGeoSearchErrors, GetApiV1ProjectsByNameAdsGeoSearchResponses, GetApiV1ProjectsByNameAdsInsightsData, GetApiV1ProjectsByNameAdsInsightsErrors, GetApiV1ProjectsByNameAdsInsightsResponses, GetApiV1ProjectsByNameAdsLiveDeliveryData, GetApiV1ProjectsByNameAdsLiveDeliveryErrors, GetApiV1ProjectsByNameAdsLiveDeliveryResponses, GetApiV1ProjectsByNameAdsOperationsByOperationKeyData, GetApiV1ProjectsByNameAdsOperationsByOperationKeyErrors, GetApiV1ProjectsByNameAdsOperationsByOperationKeyResponses, GetApiV1ProjectsByNameAdsOperationsData, GetApiV1ProjectsByNameAdsOperationsErrors, GetApiV1ProjectsByNameAdsOperationsResponses, GetApiV1ProjectsByNameAdsStatusData, GetApiV1ProjectsByNameAdsStatusErrors, GetApiV1ProjectsByNameAdsStatusResponses, GetApiV1ProjectsByNameAdsSummaryData, GetApiV1ProjectsByNameAdsSummaryErrors, GetApiV1ProjectsByNameAdsSummaryResponses, GetApiV1ProjectsByNameAgentMemoryData, GetApiV1ProjectsByNameAgentMemoryErrors, GetApiV1ProjectsByNameAgentMemoryResponses, GetApiV1ProjectsByNameAgentProvidersData, GetApiV1ProjectsByNameAgentProvidersErrors, GetApiV1ProjectsByNameAgentProvidersResponses, GetApiV1ProjectsByNameAgentTranscriptData, GetApiV1ProjectsByNameAgentTranscriptErrors, GetApiV1ProjectsByNameAgentTranscriptResponses, GetApiV1ProjectsByNameAnalyticsGapsData, GetApiV1ProjectsByNameAnalyticsGapsErrors, GetApiV1ProjectsByNameAnalyticsGapsResponses, GetApiV1ProjectsByNameAnalyticsMetricsData, GetApiV1ProjectsByNameAnalyticsMetricsErrors, GetApiV1ProjectsByNameAnalyticsMetricsResponses, GetApiV1ProjectsByNameAnalyticsSourcesData, GetApiV1ProjectsByNameAnalyticsSourcesErrors, GetApiV1ProjectsByNameAnalyticsSourcesResponses, GetApiV1ProjectsByNameBacklinksDomainsData, GetApiV1ProjectsByNameBacklinksDomainsErrors, GetApiV1ProjectsByNameBacklinksDomainsResponses, GetApiV1ProjectsByNameBacklinksHistoryData, GetApiV1ProjectsByNameBacklinksHistoryErrors, GetApiV1ProjectsByNameBacklinksHistoryResponses, GetApiV1ProjectsByNameBacklinksSourcesData, GetApiV1ProjectsByNameBacklinksSourcesErrors, GetApiV1ProjectsByNameBacklinksSourcesResponses, GetApiV1ProjectsByNameBacklinksSummaryData, GetApiV1ProjectsByNameBacklinksSummaryErrors, GetApiV1ProjectsByNameBacklinksSummaryResponses, GetApiV1ProjectsByNameBingCoverageData, GetApiV1ProjectsByNameBingCoverageErrors, GetApiV1ProjectsByNameBingCoverageHistoryData, GetApiV1ProjectsByNameBingCoverageHistoryErrors, GetApiV1ProjectsByNameBingCoverageHistoryResponses, GetApiV1ProjectsByNameBingCoverageResponses, GetApiV1ProjectsByNameBingInspectionsData, GetApiV1ProjectsByNameBingInspectionsErrors, GetApiV1ProjectsByNameBingInspectionsResponses, GetApiV1ProjectsByNameBingPerformanceData, GetApiV1ProjectsByNameBingPerformanceErrors, GetApiV1ProjectsByNameBingPerformanceResponses, GetApiV1ProjectsByNameBingSitesData, GetApiV1ProjectsByNameBingSitesErrors, GetApiV1ProjectsByNameBingSitesResponses, GetApiV1ProjectsByNameBingStatusData, GetApiV1ProjectsByNameBingStatusErrors, GetApiV1ProjectsByNameBingStatusResponses, GetApiV1ProjectsByNameCitationsVisibilityData, GetApiV1ProjectsByNameCitationsVisibilityErrors, GetApiV1ProjectsByNameCitationsVisibilityResponses, GetApiV1ProjectsByNameCompetitorsData, GetApiV1ProjectsByNameCompetitorsResponses, GetApiV1ProjectsByNameContentDismissalsData, GetApiV1ProjectsByNameContentDismissalsErrors, GetApiV1ProjectsByNameContentDismissalsResponses, GetApiV1ProjectsByNameContentDomainClassificationsData, GetApiV1ProjectsByNameContentDomainClassificationsErrors, GetApiV1ProjectsByNameContentDomainClassificationsResponses, GetApiV1ProjectsByNameContentGapsData, GetApiV1ProjectsByNameContentGapsErrors, GetApiV1ProjectsByNameContentGapsResponses, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisErrors, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisResponses, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefErrors, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefResponses, GetApiV1ProjectsByNameContentSourcesData, GetApiV1ProjectsByNameContentSourcesErrors, GetApiV1ProjectsByNameContentSourcesResponses, GetApiV1ProjectsByNameContentTargetsData, GetApiV1ProjectsByNameContentTargetsErrors, GetApiV1ProjectsByNameContentTargetsResponses, GetApiV1ProjectsByNameData, GetApiV1ProjectsByNameDeletePreviewData, GetApiV1ProjectsByNameDeletePreviewErrors, GetApiV1ProjectsByNameDeletePreviewResponses, GetApiV1ProjectsByNameDiscoverSessionsByIdData, GetApiV1ProjectsByNameDiscoverSessionsByIdErrors, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestData, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestErrors, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestResponses, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteErrors, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteResponses, GetApiV1ProjectsByNameDiscoverSessionsByIdResponses, GetApiV1ProjectsByNameDiscoverSessionsData, GetApiV1ProjectsByNameDiscoverSessionsErrors, GetApiV1ProjectsByNameDiscoverSessionsResponses, GetApiV1ProjectsByNameDoctorData, GetApiV1ProjectsByNameDoctorErrors, GetApiV1ProjectsByNameDoctorResponses, GetApiV1ProjectsByNameErrors, GetApiV1ProjectsByNameExportData, GetApiV1ProjectsByNameExportErrors, GetApiV1ProjectsByNameExportResponses, GetApiV1ProjectsByNameGaAiReferralDailyData, GetApiV1ProjectsByNameGaAiReferralDailyErrors, GetApiV1ProjectsByNameGaAiReferralDailyResponses, GetApiV1ProjectsByNameGaAiReferralHistoryData, GetApiV1ProjectsByNameGaAiReferralHistoryErrors, GetApiV1ProjectsByNameGaAiReferralHistoryResponses, GetApiV1ProjectsByNameGaAttributionTrendData, GetApiV1ProjectsByNameGaAttributionTrendErrors, GetApiV1ProjectsByNameGaAttributionTrendResponses, GetApiV1ProjectsByNameGaCoverageData, GetApiV1ProjectsByNameGaCoverageErrors, GetApiV1ProjectsByNameGaCoverageResponses, GetApiV1ProjectsByNameGaMeasurementAnalysisData, GetApiV1ProjectsByNameGaMeasurementAnalysisErrors, GetApiV1ProjectsByNameGaMeasurementAnalysisResponses, GetApiV1ProjectsByNameGaSessionHistoryData, GetApiV1ProjectsByNameGaSessionHistoryErrors, GetApiV1ProjectsByNameGaSessionHistoryResponses, GetApiV1ProjectsByNameGaSocialReferralHistoryData, GetApiV1ProjectsByNameGaSocialReferralHistoryErrors, GetApiV1ProjectsByNameGaSocialReferralHistoryResponses, GetApiV1ProjectsByNameGaSocialReferralTrendData, GetApiV1ProjectsByNameGaSocialReferralTrendErrors, GetApiV1ProjectsByNameGaSocialReferralTrendResponses, GetApiV1ProjectsByNameGaStatusData, GetApiV1ProjectsByNameGaStatusErrors, GetApiV1ProjectsByNameGaStatusResponses, GetApiV1ProjectsByNameGaTrafficData, GetApiV1ProjectsByNameGaTrafficErrors, GetApiV1ProjectsByNameGaTrafficResponses, GetApiV1ProjectsByNameGbpAccountsData, GetApiV1ProjectsByNameGbpAccountsErrors, GetApiV1ProjectsByNameGbpAccountsResponses, GetApiV1ProjectsByNameGbpAttributesData, GetApiV1ProjectsByNameGbpAttributesErrors, GetApiV1ProjectsByNameGbpAttributesResponses, GetApiV1ProjectsByNameGbpKeywordsData, GetApiV1ProjectsByNameGbpKeywordsErrors, GetApiV1ProjectsByNameGbpKeywordsResponses, GetApiV1ProjectsByNameGbpLocationsData, GetApiV1ProjectsByNameGbpLocationsErrors, GetApiV1ProjectsByNameGbpLocationsResponses, GetApiV1ProjectsByNameGbpLodgingData, GetApiV1ProjectsByNameGbpLodgingErrors, GetApiV1ProjectsByNameGbpLodgingResponses, GetApiV1ProjectsByNameGbpMetricsData, GetApiV1ProjectsByNameGbpMetricsErrors, GetApiV1ProjectsByNameGbpMetricsResponses, GetApiV1ProjectsByNameGbpPlaceActionsData, GetApiV1ProjectsByNameGbpPlaceActionsErrors, GetApiV1ProjectsByNameGbpPlaceActionsResponses, GetApiV1ProjectsByNameGbpPlacesData, GetApiV1ProjectsByNameGbpPlacesErrors, GetApiV1ProjectsByNameGbpPlacesResponses, GetApiV1ProjectsByNameGbpSummaryData, GetApiV1ProjectsByNameGbpSummaryErrors, GetApiV1ProjectsByNameGbpSummaryResponses, GetApiV1ProjectsByNameGoogleCallbackData, GetApiV1ProjectsByNameGoogleCallbackErrors, GetApiV1ProjectsByNameGoogleCallbackResponses, GetApiV1ProjectsByNameGoogleConnectionsData, GetApiV1ProjectsByNameGoogleConnectionsErrors, GetApiV1ProjectsByNameGoogleConnectionsResponses, GetApiV1ProjectsByNameGoogleGscCoverageData, GetApiV1ProjectsByNameGoogleGscCoverageErrors, GetApiV1ProjectsByNameGoogleGscCoverageHistoryData, GetApiV1ProjectsByNameGoogleGscCoverageHistoryErrors, GetApiV1ProjectsByNameGoogleGscCoverageHistoryResponses, GetApiV1ProjectsByNameGoogleGscCoverageResponses, GetApiV1ProjectsByNameGoogleGscDeindexedData, GetApiV1ProjectsByNameGoogleGscDeindexedErrors, GetApiV1ProjectsByNameGoogleGscDeindexedResponses, GetApiV1ProjectsByNameGoogleGscInspectionsData, GetApiV1ProjectsByNameGoogleGscInspectionsErrors, GetApiV1ProjectsByNameGoogleGscInspectionsResponses, GetApiV1ProjectsByNameGoogleGscPerformanceDailyData, GetApiV1ProjectsByNameGoogleGscPerformanceDailyErrors, GetApiV1ProjectsByNameGoogleGscPerformanceDailyResponses, GetApiV1ProjectsByNameGoogleGscPerformanceData, GetApiV1ProjectsByNameGoogleGscPerformanceErrors, GetApiV1ProjectsByNameGoogleGscPerformanceResponses, GetApiV1ProjectsByNameGoogleGscSitemapsData, GetApiV1ProjectsByNameGoogleGscSitemapsErrors, GetApiV1ProjectsByNameGoogleGscSitemapsResponses, GetApiV1ProjectsByNameGoogleGscTopPagesData, GetApiV1ProjectsByNameGoogleGscTopPagesErrors, GetApiV1ProjectsByNameGoogleGscTopPagesResponses, GetApiV1ProjectsByNameGooglePropertiesData, GetApiV1ProjectsByNameGooglePropertiesErrors, GetApiV1ProjectsByNameGooglePropertiesResponses, GetApiV1ProjectsByNameHealthHistoryData, GetApiV1ProjectsByNameHealthHistoryErrors, GetApiV1ProjectsByNameHealthHistoryResponses, GetApiV1ProjectsByNameHealthLatestData, GetApiV1ProjectsByNameHealthLatestErrors, GetApiV1ProjectsByNameHealthLatestResponses, GetApiV1ProjectsByNameHistoryData, GetApiV1ProjectsByNameHistoryResponses, GetApiV1ProjectsByNameInsightsByIdData, GetApiV1ProjectsByNameInsightsByIdErrors, GetApiV1ProjectsByNameInsightsByIdResponses, GetApiV1ProjectsByNameInsightsData, GetApiV1ProjectsByNameInsightsErrors, GetApiV1ProjectsByNameInsightsResponses, GetApiV1ProjectsByNameKeywordsData, GetApiV1ProjectsByNameKeywordsResponses, GetApiV1ProjectsByNameLocationsData, GetApiV1ProjectsByNameLocationsErrors, GetApiV1ProjectsByNameLocationsResponses, GetApiV1ProjectsByNameMeasurementPlanData, GetApiV1ProjectsByNameMeasurementPlanErrors, GetApiV1ProjectsByNameMeasurementPlanResponses, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionData, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses, GetApiV1ProjectsByNameMeasurementPlanVersionsData, GetApiV1ProjectsByNameMeasurementPlanVersionsErrors, GetApiV1ProjectsByNameMeasurementPlanVersionsResponses, GetApiV1ProjectsByNameMeasurementReportData, GetApiV1ProjectsByNameMeasurementReportErrors, GetApiV1ProjectsByNameMeasurementReportResponses, GetApiV1ProjectsByNameNotificationsData, GetApiV1ProjectsByNameNotificationsResponses, GetApiV1ProjectsByNameOrganicEvidenceData, GetApiV1ProjectsByNameOrganicEvidenceErrors, GetApiV1ProjectsByNameOrganicEvidenceResponses, GetApiV1ProjectsByNameOverviewData, GetApiV1ProjectsByNameOverviewErrors, GetApiV1ProjectsByNameOverviewResponses, GetApiV1ProjectsByNameQueriesData, GetApiV1ProjectsByNameQueriesResponses, GetApiV1ProjectsByNameReportData, GetApiV1ProjectsByNameReportErrors, GetApiV1ProjectsByNameReportHtmlData, GetApiV1ProjectsByNameReportHtmlErrors, GetApiV1ProjectsByNameReportHtmlResponses, GetApiV1ProjectsByNameReportResponses, GetApiV1ProjectsByNameResearchRunsByRunIdData, GetApiV1ProjectsByNameResearchRunsByRunIdErrors, GetApiV1ProjectsByNameResearchRunsByRunIdResponses, GetApiV1ProjectsByNameResearchRunsData, GetApiV1ProjectsByNameResearchRunsErrors, GetApiV1ProjectsByNameResearchRunsResponses, GetApiV1ProjectsByNameResponses, GetApiV1ProjectsByNameResultsExportData, GetApiV1ProjectsByNameResultsExportErrors, GetApiV1ProjectsByNameResultsExportResponses, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffData, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffErrors, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffResponses, GetApiV1ProjectsByNameRunsData, GetApiV1ProjectsByNameRunsLatestData, GetApiV1ProjectsByNameRunsLatestResponses, GetApiV1ProjectsByNameRunsResponses, GetApiV1ProjectsByNameScheduleData, GetApiV1ProjectsByNameScheduleErrors, GetApiV1ProjectsByNameScheduleResponses, GetApiV1ProjectsByNameSearchData, GetApiV1ProjectsByNameSearchErrors, GetApiV1ProjectsByNameSearchResponses, GetApiV1ProjectsByNameSnapshotsData, GetApiV1ProjectsByNameSnapshotsDiffData, GetApiV1ProjectsByNameSnapshotsDiffErrors, GetApiV1ProjectsByNameSnapshotsDiffResponses, GetApiV1ProjectsByNameSnapshotsResponses, GetApiV1ProjectsByNameTechnicalAeoData, GetApiV1ProjectsByNameTechnicalAeoErrors, GetApiV1ProjectsByNameTechnicalAeoPagesData, GetApiV1ProjectsByNameTechnicalAeoPagesErrors, GetApiV1ProjectsByNameTechnicalAeoPagesResponses, GetApiV1ProjectsByNameTechnicalAeoResponses, GetApiV1ProjectsByNameTechnicalAeoTrendData, GetApiV1ProjectsByNameTechnicalAeoTrendErrors, GetApiV1ProjectsByNameTechnicalAeoTrendResponses, GetApiV1ProjectsByNameTimelineData, GetApiV1ProjectsByNameTimelineResponses, GetApiV1ProjectsByNameTrafficEventsData, GetApiV1ProjectsByNameTrafficEventsErrors, GetApiV1ProjectsByNameTrafficEventsResponses, GetApiV1ProjectsByNameTrafficSourcesByIdData, GetApiV1ProjectsByNameTrafficSourcesByIdErrors, GetApiV1ProjectsByNameTrafficSourcesByIdResponses, GetApiV1ProjectsByNameTrafficSourcesData, GetApiV1ProjectsByNameTrafficSourcesErrors, GetApiV1ProjectsByNameTrafficSourcesResponses, GetApiV1ProjectsByNameTrafficStatusData, GetApiV1ProjectsByNameTrafficStatusErrors, GetApiV1ProjectsByNameTrafficStatusResponses, GetApiV1ProjectsByNameVisibilityCompareData, GetApiV1ProjectsByNameVisibilityCompareErrors, GetApiV1ProjectsByNameVisibilityCompareResponses, GetApiV1ProjectsByNameVisibilityStatsData, GetApiV1ProjectsByNameVisibilityStatsErrors, GetApiV1ProjectsByNameVisibilityStatsResponses, GetApiV1ProjectsByNameWordpressAuditData, GetApiV1ProjectsByNameWordpressAuditErrors, GetApiV1ProjectsByNameWordpressAuditResponses, GetApiV1ProjectsByNameWordpressDiffData, GetApiV1ProjectsByNameWordpressDiffErrors, GetApiV1ProjectsByNameWordpressDiffResponses, GetApiV1ProjectsByNameWordpressLlmsTxtData, GetApiV1ProjectsByNameWordpressLlmsTxtErrors, GetApiV1ProjectsByNameWordpressLlmsTxtResponses, GetApiV1ProjectsByNameWordpressPageData, GetApiV1ProjectsByNameWordpressPageErrors, GetApiV1ProjectsByNameWordpressPageResponses, GetApiV1ProjectsByNameWordpressPagesData, GetApiV1ProjectsByNameWordpressPagesErrors, GetApiV1ProjectsByNameWordpressPagesResponses, GetApiV1ProjectsByNameWordpressSchemaData, GetApiV1ProjectsByNameWordpressSchemaErrors, GetApiV1ProjectsByNameWordpressSchemaResponses, GetApiV1ProjectsByNameWordpressSchemaStatusData, GetApiV1ProjectsByNameWordpressSchemaStatusErrors, GetApiV1ProjectsByNameWordpressSchemaStatusResponses, GetApiV1ProjectsByNameWordpressStagingStatusData, GetApiV1ProjectsByNameWordpressStagingStatusErrors, GetApiV1ProjectsByNameWordpressStagingStatusResponses, GetApiV1ProjectsByNameWordpressStatusData, GetApiV1ProjectsByNameWordpressStatusErrors, GetApiV1ProjectsByNameWordpressStatusResponses, GetApiV1ProjectsData, GetApiV1ProjectsResponses, GetApiV1RunsByIdData, GetApiV1RunsByIdErrors, GetApiV1RunsByIdResponses, GetApiV1RunsData, GetApiV1RunsResponses, GetApiV1ScreenshotsBySnapshotIdData, GetApiV1ScreenshotsBySnapshotIdErrors, GetApiV1ScreenshotsBySnapshotIdResponses, GetApiV1SettingsData, GetApiV1SettingsResponses, GetApiV1TelemetryData, GetApiV1TelemetryErrors, GetApiV1TelemetryResponses, GetApiV1UsersData, GetApiV1UsersErrors, GetApiV1UsersResponses, PostApiV1ApplyData, PostApiV1ApplyErrors, PostApiV1ApplyResponses, PostApiV1AuthLoginData, PostApiV1AuthLoginErrors, PostApiV1AuthLoginResponses, PostApiV1AuthLogoutData, PostApiV1AuthLogoutResponses, PostApiV1BacklinksInstallData, PostApiV1BacklinksInstallErrors, PostApiV1BacklinksInstallResponses, PostApiV1BacklinksSyncsData, PostApiV1BacklinksSyncsErrors, PostApiV1BacklinksSyncsResponses, PostApiV1CdpScreenshotData, PostApiV1CdpScreenshotErrors, PostApiV1CdpScreenshotResponses, PostApiV1KeysByIdRevokeData, PostApiV1KeysByIdRevokeErrors, PostApiV1KeysByIdRevokeResponses, PostApiV1KeysData, PostApiV1KeysErrors, PostApiV1KeysResponses, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeData, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeErrors, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeResponses, PostApiV1ProjectsByNameAdsActivationGrantsData, PostApiV1ProjectsByNameAdsActivationGrantsErrors, PostApiV1ProjectsByNameAdsActivationGrantsResponses, PostApiV1ProjectsByNameAdsAdGroupsByIdData, PostApiV1ProjectsByNameAdsAdGroupsByIdErrors, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseData, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseErrors, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseResponses, PostApiV1ProjectsByNameAdsAdGroupsByIdResponses, PostApiV1ProjectsByNameAdsAdGroupsData, PostApiV1ProjectsByNameAdsAdGroupsErrors, PostApiV1ProjectsByNameAdsAdGroupsResponses, PostApiV1ProjectsByNameAdsAdsByIdData, PostApiV1ProjectsByNameAdsAdsByIdErrors, PostApiV1ProjectsByNameAdsAdsByIdPauseData, PostApiV1ProjectsByNameAdsAdsByIdPauseErrors, PostApiV1ProjectsByNameAdsAdsByIdPauseResponses, PostApiV1ProjectsByNameAdsAdsByIdResponses, PostApiV1ProjectsByNameAdsAdsData, PostApiV1ProjectsByNameAdsAdsErrors, PostApiV1ProjectsByNameAdsAdsResponses, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeData, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeErrors, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeResponses, PostApiV1ProjectsByNameAdsCampaignsByIdData, PostApiV1ProjectsByNameAdsCampaignsByIdErrors, PostApiV1ProjectsByNameAdsCampaignsByIdPauseData, PostApiV1ProjectsByNameAdsCampaignsByIdPauseErrors, PostApiV1ProjectsByNameAdsCampaignsByIdPauseResponses, PostApiV1ProjectsByNameAdsCampaignsByIdResponses, PostApiV1ProjectsByNameAdsCampaignsData, PostApiV1ProjectsByNameAdsCampaignsErrors, PostApiV1ProjectsByNameAdsCampaignsResponses, PostApiV1ProjectsByNameAdsConnectData, PostApiV1ProjectsByNameAdsConnectErrors, PostApiV1ProjectsByNameAdsConnectResponses, PostApiV1ProjectsByNameAdsFilesData, PostApiV1ProjectsByNameAdsFilesErrors, PostApiV1ProjectsByNameAdsFilesResponses, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileErrors, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileResponses, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationErrors, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationResponses, PostApiV1ProjectsByNameAdsSyncData, PostApiV1ProjectsByNameAdsSyncErrors, PostApiV1ProjectsByNameAdsSyncResponses, PostApiV1ProjectsByNameBacklinksExtractData, PostApiV1ProjectsByNameBacklinksExtractErrors, PostApiV1ProjectsByNameBacklinksExtractResponses, PostApiV1ProjectsByNameBingConnectData, PostApiV1ProjectsByNameBingConnectErrors, PostApiV1ProjectsByNameBingConnectResponses, PostApiV1ProjectsByNameBingInspectSitemapData, PostApiV1ProjectsByNameBingInspectSitemapErrors, PostApiV1ProjectsByNameBingInspectSitemapResponses, PostApiV1ProjectsByNameBingInspectUrlData, PostApiV1ProjectsByNameBingInspectUrlErrors, PostApiV1ProjectsByNameBingInspectUrlResponses, PostApiV1ProjectsByNameBingRequestIndexingData, PostApiV1ProjectsByNameBingRequestIndexingErrors, PostApiV1ProjectsByNameBingRequestIndexingResponses, PostApiV1ProjectsByNameBingSetSiteData, PostApiV1ProjectsByNameBingSetSiteErrors, PostApiV1ProjectsByNameBingSetSiteResponses, PostApiV1ProjectsByNameCompetitorsData, PostApiV1ProjectsByNameCompetitorsErrors, PostApiV1ProjectsByNameCompetitorsResponses, PostApiV1ProjectsByNameContentDismissalsData, PostApiV1ProjectsByNameContentDismissalsErrors, PostApiV1ProjectsByNameContentDismissalsResponses, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeErrors, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeResponses, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefErrors, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefResponses, PostApiV1ProjectsByNameDiscoverRunData, PostApiV1ProjectsByNameDiscoverRunErrors, PostApiV1ProjectsByNameDiscoverRunResponses, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteErrors, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteResponses, PostApiV1ProjectsByNameGaConnectData, PostApiV1ProjectsByNameGaConnectErrors, PostApiV1ProjectsByNameGaConnectResponses, PostApiV1ProjectsByNameGaSyncData, PostApiV1ProjectsByNameGaSyncErrors, PostApiV1ProjectsByNameGaSyncResponses, PostApiV1ProjectsByNameGbpLocationsDiscoverData, PostApiV1ProjectsByNameGbpLocationsDiscoverErrors, PostApiV1ProjectsByNameGbpLocationsDiscoverResponses, PostApiV1ProjectsByNameGbpSyncData, PostApiV1ProjectsByNameGbpSyncErrors, PostApiV1ProjectsByNameGbpSyncResponses, PostApiV1ProjectsByNameGoogleConnectData, PostApiV1ProjectsByNameGoogleConnectErrors, PostApiV1ProjectsByNameGoogleConnectResponses, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsData, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsErrors, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsResponses, PostApiV1ProjectsByNameGoogleGscInspectData, PostApiV1ProjectsByNameGoogleGscInspectErrors, PostApiV1ProjectsByNameGoogleGscInspectResponses, PostApiV1ProjectsByNameGoogleGscInspectSitemapData, PostApiV1ProjectsByNameGoogleGscInspectSitemapErrors, PostApiV1ProjectsByNameGoogleGscInspectSitemapResponses, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitData, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses, PostApiV1ProjectsByNameGoogleGscSyncData, PostApiV1ProjectsByNameGoogleGscSyncErrors, PostApiV1ProjectsByNameGoogleGscSyncResponses, PostApiV1ProjectsByNameGoogleIndexingRequestData, PostApiV1ProjectsByNameGoogleIndexingRequestErrors, PostApiV1ProjectsByNameGoogleIndexingRequestResponses, PostApiV1ProjectsByNameInsightsByIdDismissData, PostApiV1ProjectsByNameInsightsByIdDismissErrors, PostApiV1ProjectsByNameInsightsByIdDismissResponses, PostApiV1ProjectsByNameKeywordsData, PostApiV1ProjectsByNameKeywordsGenerateData, PostApiV1ProjectsByNameKeywordsGenerateErrors, PostApiV1ProjectsByNameKeywordsGenerateResponses, PostApiV1ProjectsByNameKeywordsResponses, PostApiV1ProjectsByNameLocationsData, PostApiV1ProjectsByNameLocationsErrors, PostApiV1ProjectsByNameLocationsResponses, PostApiV1ProjectsByNameMeasurementDiscoveryData, PostApiV1ProjectsByNameMeasurementDiscoveryErrors, PostApiV1ProjectsByNameMeasurementDiscoveryResponses, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewData, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewData, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireData, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses, PostApiV1ProjectsByNameNotificationsByIdTestData, PostApiV1ProjectsByNameNotificationsByIdTestErrors, PostApiV1ProjectsByNameNotificationsByIdTestResponses, PostApiV1ProjectsByNameNotificationsData, PostApiV1ProjectsByNameNotificationsResponses, PostApiV1ProjectsByNameQueriesData, PostApiV1ProjectsByNameQueriesGenerateData, PostApiV1ProjectsByNameQueriesGenerateErrors, PostApiV1ProjectsByNameQueriesGenerateResponses, PostApiV1ProjectsByNameQueriesReplacePreviewData, PostApiV1ProjectsByNameQueriesReplacePreviewErrors, PostApiV1ProjectsByNameQueriesReplacePreviewResponses, PostApiV1ProjectsByNameQueriesResponses, PostApiV1ProjectsByNameResearchRunsData, PostApiV1ProjectsByNameResearchRunsErrors, PostApiV1ProjectsByNameResearchRunsResponses, PostApiV1ProjectsByNameRunsData, PostApiV1ProjectsByNameRunsErrors, PostApiV1ProjectsByNameRunsResponses, PostApiV1ProjectsByNameTechnicalAeoRunsData, PostApiV1ProjectsByNameTechnicalAeoRunsErrors, PostApiV1ProjectsByNameTechnicalAeoRunsResponses, PostApiV1ProjectsByNameTrafficConnectCloudRunData, PostApiV1ProjectsByNameTrafficConnectCloudRunErrors, PostApiV1ProjectsByNameTrafficConnectCloudRunResponses, PostApiV1ProjectsByNameTrafficConnectVercelData, PostApiV1ProjectsByNameTrafficConnectVercelErrors, PostApiV1ProjectsByNameTrafficConnectVercelResponses, PostApiV1ProjectsByNameTrafficConnectWordpressData, PostApiV1ProjectsByNameTrafficConnectWordpressErrors, PostApiV1ProjectsByNameTrafficConnectWordpressResponses, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillData, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillErrors, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillResponses, PostApiV1ProjectsByNameTrafficSourcesByIdResetData, PostApiV1ProjectsByNameTrafficSourcesByIdResetErrors, PostApiV1ProjectsByNameTrafficSourcesByIdResetResponses, PostApiV1ProjectsByNameTrafficSourcesByIdSyncData, PostApiV1ProjectsByNameTrafficSourcesByIdSyncErrors, PostApiV1ProjectsByNameTrafficSourcesByIdSyncResponses, PostApiV1ProjectsByNameWordpressConnectData, PostApiV1ProjectsByNameWordpressConnectErrors, PostApiV1ProjectsByNameWordpressConnectResponses, PostApiV1ProjectsByNameWordpressLlmsTxtManualData, PostApiV1ProjectsByNameWordpressLlmsTxtManualErrors, PostApiV1ProjectsByNameWordpressLlmsTxtManualResponses, PostApiV1ProjectsByNameWordpressOnboardData, PostApiV1ProjectsByNameWordpressOnboardErrors, PostApiV1ProjectsByNameWordpressOnboardResponses, PostApiV1ProjectsByNameWordpressPageMetaData, PostApiV1ProjectsByNameWordpressPageMetaErrors, PostApiV1ProjectsByNameWordpressPageMetaResponses, PostApiV1ProjectsByNameWordpressPagesData, PostApiV1ProjectsByNameWordpressPagesErrors, PostApiV1ProjectsByNameWordpressPagesMetaBulkData, PostApiV1ProjectsByNameWordpressPagesMetaBulkErrors, PostApiV1ProjectsByNameWordpressPagesMetaBulkResponses, PostApiV1ProjectsByNameWordpressPagesResponses, PostApiV1ProjectsByNameWordpressSchemaDeployData, PostApiV1ProjectsByNameWordpressSchemaDeployErrors, PostApiV1ProjectsByNameWordpressSchemaDeployResponses, PostApiV1ProjectsByNameWordpressSchemaManualData, PostApiV1ProjectsByNameWordpressSchemaManualErrors, PostApiV1ProjectsByNameWordpressSchemaManualResponses, PostApiV1ProjectsByNameWordpressStagingPushData, PostApiV1ProjectsByNameWordpressStagingPushErrors, PostApiV1ProjectsByNameWordpressStagingPushResponses, PostApiV1RunsByIdCancelData, PostApiV1RunsByIdCancelErrors, PostApiV1RunsByIdCancelResponses, PostApiV1RunsData, PostApiV1RunsErrors, PostApiV1RunsResponses, PostApiV1SnapshotData, PostApiV1SnapshotErrors, PostApiV1SnapshotResponses, PostApiV1TelemetryOnboardingData, PostApiV1TelemetryOnboardingErrors, PostApiV1TelemetryOnboardingResponses, PostApiV1UsersData, PostApiV1UsersErrors, PostApiV1UsersResponses, PutApiV1ProjectsByNameAgentMemoryData, PutApiV1ProjectsByNameAgentMemoryErrors, PutApiV1ProjectsByNameAgentMemoryResponses, PutApiV1ProjectsByNameCompetitorsData, PutApiV1ProjectsByNameCompetitorsResponses, PutApiV1ProjectsByNameData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionErrors, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionResponses, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyData, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyErrors, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyResponses, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapData, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapErrors, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapResponses, PutApiV1ProjectsByNameKeywordsData, PutApiV1ProjectsByNameKeywordsResponses, PutApiV1ProjectsByNameLocationsDefaultData, PutApiV1ProjectsByNameLocationsDefaultErrors, PutApiV1ProjectsByNameLocationsDefaultResponses, PutApiV1ProjectsByNameMeasurementPlanData, PutApiV1ProjectsByNameMeasurementPlanErrors, PutApiV1ProjectsByNameMeasurementPlanResponses, PutApiV1ProjectsByNameQueriesData, PutApiV1ProjectsByNameQueriesResponses, PutApiV1ProjectsByNameResponses, PutApiV1ProjectsByNameScheduleData, PutApiV1ProjectsByNameScheduleErrors, PutApiV1ProjectsByNameScheduleResponses, PutApiV1ProjectsByNameWordpressPageData, PutApiV1ProjectsByNameWordpressPageErrors, PutApiV1ProjectsByNameWordpressPageResponses, PutApiV1SettingsBingData, PutApiV1SettingsBingErrors, PutApiV1SettingsBingResponses, PutApiV1SettingsCdpData, PutApiV1SettingsCdpErrors, PutApiV1SettingsCdpResponses, PutApiV1SettingsGoogleData, PutApiV1SettingsGoogleErrors, PutApiV1SettingsGoogleResponses, PutApiV1SettingsProvidersByNameData, PutApiV1SettingsProvidersByNameErrors, PutApiV1SettingsProvidersByNameResponses, PutApiV1TelemetryData, PutApiV1TelemetryErrors, PutApiV1TelemetryResponses } from './types.gen.js'; +import type { DeleteApiV1AuthSessionsData, DeleteApiV1AuthSessionsErrors, DeleteApiV1AuthSessionsResponses, DeleteApiV1BacklinksCacheByReleaseData, DeleteApiV1BacklinksCacheByReleaseErrors, DeleteApiV1BacklinksCacheByReleaseResponses, DeleteApiV1ProjectsByNameAdsConnectionData, DeleteApiV1ProjectsByNameAdsConnectionErrors, DeleteApiV1ProjectsByNameAdsConnectionResponses, DeleteApiV1ProjectsByNameAgentMemoryData, DeleteApiV1ProjectsByNameAgentMemoryErrors, DeleteApiV1ProjectsByNameAgentMemoryResponses, DeleteApiV1ProjectsByNameAgentTranscriptData, DeleteApiV1ProjectsByNameAgentTranscriptErrors, DeleteApiV1ProjectsByNameAgentTranscriptResponses, DeleteApiV1ProjectsByNameBingDisconnectData, DeleteApiV1ProjectsByNameBingDisconnectErrors, DeleteApiV1ProjectsByNameBingDisconnectResponses, DeleteApiV1ProjectsByNameCompetitorsByIdData, DeleteApiV1ProjectsByNameCompetitorsByIdErrors, DeleteApiV1ProjectsByNameCompetitorsByIdResponses, DeleteApiV1ProjectsByNameCompetitorsData, DeleteApiV1ProjectsByNameCompetitorsErrors, DeleteApiV1ProjectsByNameCompetitorsResponses, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefData, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefErrors, DeleteApiV1ProjectsByNameContentDismissalsByTargetRefResponses, DeleteApiV1ProjectsByNameData, DeleteApiV1ProjectsByNameErrors, DeleteApiV1ProjectsByNameGaDisconnectData, DeleteApiV1ProjectsByNameGaDisconnectErrors, DeleteApiV1ProjectsByNameGaDisconnectResponses, DeleteApiV1ProjectsByNameGbpConnectionData, DeleteApiV1ProjectsByNameGbpConnectionErrors, DeleteApiV1ProjectsByNameGbpConnectionResponses, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeData, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeErrors, DeleteApiV1ProjectsByNameGoogleConnectionsByTypeResponses, DeleteApiV1ProjectsByNameKeywordsData, DeleteApiV1ProjectsByNameKeywordsErrors, DeleteApiV1ProjectsByNameKeywordsResponses, DeleteApiV1ProjectsByNameLocationsByLabelData, DeleteApiV1ProjectsByNameLocationsByLabelErrors, DeleteApiV1ProjectsByNameLocationsByLabelResponses, DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdData, DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors, DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses, DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdData, DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors, DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses, DeleteApiV1ProjectsByNameNotificationsByIdData, DeleteApiV1ProjectsByNameNotificationsByIdErrors, DeleteApiV1ProjectsByNameNotificationsByIdResponses, DeleteApiV1ProjectsByNameQueriesByIdData, DeleteApiV1ProjectsByNameQueriesByIdErrors, DeleteApiV1ProjectsByNameQueriesByIdResponses, DeleteApiV1ProjectsByNameQueriesData, DeleteApiV1ProjectsByNameQueriesErrors, DeleteApiV1ProjectsByNameQueriesResponses, DeleteApiV1ProjectsByNameResponses, DeleteApiV1ProjectsByNameScheduleData, DeleteApiV1ProjectsByNameScheduleErrors, DeleteApiV1ProjectsByNameScheduleResponses, DeleteApiV1ProjectsByNameWordpressDisconnectData, DeleteApiV1ProjectsByNameWordpressDisconnectErrors, DeleteApiV1ProjectsByNameWordpressDisconnectResponses, DeleteApiV1UsersByNameData, DeleteApiV1UsersByNameErrors, DeleteApiV1UsersByNameResponses, GetApiV1AuthSessionData, GetApiV1AuthSessionResponses, GetApiV1AuthSessionsData, GetApiV1AuthSessionsErrors, GetApiV1AuthSessionsResponses, GetApiV1BacklinksLatestReleaseData, GetApiV1BacklinksLatestReleaseErrors, GetApiV1BacklinksLatestReleaseResponses, GetApiV1BacklinksReleasesData, GetApiV1BacklinksReleasesResponses, GetApiV1BacklinksStatusData, GetApiV1BacklinksStatusErrors, GetApiV1BacklinksStatusResponses, GetApiV1BacklinksSyncsData, GetApiV1BacklinksSyncsLatestData, GetApiV1BacklinksSyncsLatestResponses, GetApiV1BacklinksSyncsResponses, GetApiV1CdpStatusData, GetApiV1CdpStatusErrors, GetApiV1CdpStatusResponses, GetApiV1DoctorData, GetApiV1DoctorResponses, GetApiV1GoogleCallbackData, GetApiV1GoogleCallbackErrors, GetApiV1GoogleCallbackResponses, GetApiV1HistoryData, GetApiV1HistoryResponses, GetApiV1KeysData, GetApiV1KeysResponses, GetApiV1KeysSelfData, GetApiV1KeysSelfErrors, GetApiV1KeysSelfResponses, GetApiV1NotificationsEventsData, GetApiV1NotificationsEventsResponses, GetApiV1OpenapiJsonData, GetApiV1OpenapiJsonResponses, GetApiV1ProjectsByNameAdsAccountData, GetApiV1ProjectsByNameAdsAccountErrors, GetApiV1ProjectsByNameAdsAccountResponses, GetApiV1ProjectsByNameAdsCampaignsData, GetApiV1ProjectsByNameAdsCampaignsErrors, GetApiV1ProjectsByNameAdsCampaignsResponses, GetApiV1ProjectsByNameAdsConversionsEventSettingsData, GetApiV1ProjectsByNameAdsConversionsEventSettingsErrors, GetApiV1ProjectsByNameAdsConversionsEventSettingsResponses, GetApiV1ProjectsByNameAdsConversionsPixelsData, GetApiV1ProjectsByNameAdsConversionsPixelsErrors, GetApiV1ProjectsByNameAdsConversionsPixelsResponses, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsData, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsErrors, GetApiV1ProjectsByNameAdsDeliveryDiagnosticsResponses, GetApiV1ProjectsByNameAdsGeoSearchData, GetApiV1ProjectsByNameAdsGeoSearchErrors, GetApiV1ProjectsByNameAdsGeoSearchResponses, GetApiV1ProjectsByNameAdsInsightsData, GetApiV1ProjectsByNameAdsInsightsErrors, GetApiV1ProjectsByNameAdsInsightsResponses, GetApiV1ProjectsByNameAdsLiveDeliveryData, GetApiV1ProjectsByNameAdsLiveDeliveryErrors, GetApiV1ProjectsByNameAdsLiveDeliveryResponses, GetApiV1ProjectsByNameAdsOperationsByOperationKeyData, GetApiV1ProjectsByNameAdsOperationsByOperationKeyErrors, GetApiV1ProjectsByNameAdsOperationsByOperationKeyResponses, GetApiV1ProjectsByNameAdsOperationsData, GetApiV1ProjectsByNameAdsOperationsErrors, GetApiV1ProjectsByNameAdsOperationsResponses, GetApiV1ProjectsByNameAdsStatusData, GetApiV1ProjectsByNameAdsStatusErrors, GetApiV1ProjectsByNameAdsStatusResponses, GetApiV1ProjectsByNameAdsSummaryData, GetApiV1ProjectsByNameAdsSummaryErrors, GetApiV1ProjectsByNameAdsSummaryResponses, GetApiV1ProjectsByNameAgentMemoryData, GetApiV1ProjectsByNameAgentMemoryErrors, GetApiV1ProjectsByNameAgentMemoryResponses, GetApiV1ProjectsByNameAgentProvidersData, GetApiV1ProjectsByNameAgentProvidersErrors, GetApiV1ProjectsByNameAgentProvidersResponses, GetApiV1ProjectsByNameAgentTranscriptData, GetApiV1ProjectsByNameAgentTranscriptErrors, GetApiV1ProjectsByNameAgentTranscriptResponses, GetApiV1ProjectsByNameAnalyticsGapsData, GetApiV1ProjectsByNameAnalyticsGapsErrors, GetApiV1ProjectsByNameAnalyticsGapsResponses, GetApiV1ProjectsByNameAnalyticsMetricsData, GetApiV1ProjectsByNameAnalyticsMetricsErrors, GetApiV1ProjectsByNameAnalyticsMetricsResponses, GetApiV1ProjectsByNameAnalyticsSourcesData, GetApiV1ProjectsByNameAnalyticsSourcesErrors, GetApiV1ProjectsByNameAnalyticsSourcesResponses, GetApiV1ProjectsByNameBacklinksDomainsData, GetApiV1ProjectsByNameBacklinksDomainsErrors, GetApiV1ProjectsByNameBacklinksDomainsResponses, GetApiV1ProjectsByNameBacklinksHistoryData, GetApiV1ProjectsByNameBacklinksHistoryErrors, GetApiV1ProjectsByNameBacklinksHistoryResponses, GetApiV1ProjectsByNameBacklinksSourcesData, GetApiV1ProjectsByNameBacklinksSourcesErrors, GetApiV1ProjectsByNameBacklinksSourcesResponses, GetApiV1ProjectsByNameBacklinksSummaryData, GetApiV1ProjectsByNameBacklinksSummaryErrors, GetApiV1ProjectsByNameBacklinksSummaryResponses, GetApiV1ProjectsByNameBingCoverageData, GetApiV1ProjectsByNameBingCoverageErrors, GetApiV1ProjectsByNameBingCoverageHistoryData, GetApiV1ProjectsByNameBingCoverageHistoryErrors, GetApiV1ProjectsByNameBingCoverageHistoryResponses, GetApiV1ProjectsByNameBingCoverageResponses, GetApiV1ProjectsByNameBingInspectionsData, GetApiV1ProjectsByNameBingInspectionsErrors, GetApiV1ProjectsByNameBingInspectionsResponses, GetApiV1ProjectsByNameBingPerformanceData, GetApiV1ProjectsByNameBingPerformanceErrors, GetApiV1ProjectsByNameBingPerformanceResponses, GetApiV1ProjectsByNameBingSitesData, GetApiV1ProjectsByNameBingSitesErrors, GetApiV1ProjectsByNameBingSitesResponses, GetApiV1ProjectsByNameBingStatusData, GetApiV1ProjectsByNameBingStatusErrors, GetApiV1ProjectsByNameBingStatusResponses, GetApiV1ProjectsByNameCitationsVisibilityData, GetApiV1ProjectsByNameCitationsVisibilityErrors, GetApiV1ProjectsByNameCitationsVisibilityResponses, GetApiV1ProjectsByNameCompetitorsData, GetApiV1ProjectsByNameCompetitorsResponses, GetApiV1ProjectsByNameContentDismissalsData, GetApiV1ProjectsByNameContentDismissalsErrors, GetApiV1ProjectsByNameContentDismissalsResponses, GetApiV1ProjectsByNameContentDomainClassificationsData, GetApiV1ProjectsByNameContentDomainClassificationsErrors, GetApiV1ProjectsByNameContentDomainClassificationsResponses, GetApiV1ProjectsByNameContentGapsData, GetApiV1ProjectsByNameContentGapsErrors, GetApiV1ProjectsByNameContentGapsResponses, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisErrors, GetApiV1ProjectsByNameContentRecommendationsByTargetRefAnalysisResponses, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefErrors, GetApiV1ProjectsByNameContentRecommendationsByTargetRefBriefResponses, GetApiV1ProjectsByNameContentSourcesData, GetApiV1ProjectsByNameContentSourcesErrors, GetApiV1ProjectsByNameContentSourcesResponses, GetApiV1ProjectsByNameContentTargetsData, GetApiV1ProjectsByNameContentTargetsErrors, GetApiV1ProjectsByNameContentTargetsResponses, GetApiV1ProjectsByNameData, GetApiV1ProjectsByNameDeletePreviewData, GetApiV1ProjectsByNameDeletePreviewErrors, GetApiV1ProjectsByNameDeletePreviewResponses, GetApiV1ProjectsByNameDiscoverSessionsByIdData, GetApiV1ProjectsByNameDiscoverSessionsByIdErrors, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestData, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestErrors, GetApiV1ProjectsByNameDiscoverSessionsByIdHarvestResponses, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteErrors, GetApiV1ProjectsByNameDiscoverSessionsByIdPromoteResponses, GetApiV1ProjectsByNameDiscoverSessionsByIdResponses, GetApiV1ProjectsByNameDiscoverSessionsData, GetApiV1ProjectsByNameDiscoverSessionsErrors, GetApiV1ProjectsByNameDiscoverSessionsResponses, GetApiV1ProjectsByNameDoctorData, GetApiV1ProjectsByNameDoctorErrors, GetApiV1ProjectsByNameDoctorResponses, GetApiV1ProjectsByNameErrors, GetApiV1ProjectsByNameExportData, GetApiV1ProjectsByNameExportErrors, GetApiV1ProjectsByNameExportResponses, GetApiV1ProjectsByNameGaAiReferralDailyData, GetApiV1ProjectsByNameGaAiReferralDailyErrors, GetApiV1ProjectsByNameGaAiReferralDailyResponses, GetApiV1ProjectsByNameGaAiReferralHistoryData, GetApiV1ProjectsByNameGaAiReferralHistoryErrors, GetApiV1ProjectsByNameGaAiReferralHistoryResponses, GetApiV1ProjectsByNameGaAttributionTrendData, GetApiV1ProjectsByNameGaAttributionTrendErrors, GetApiV1ProjectsByNameGaAttributionTrendResponses, GetApiV1ProjectsByNameGaCoverageData, GetApiV1ProjectsByNameGaCoverageErrors, GetApiV1ProjectsByNameGaCoverageResponses, GetApiV1ProjectsByNameGaMeasurementAnalysisData, GetApiV1ProjectsByNameGaMeasurementAnalysisErrors, GetApiV1ProjectsByNameGaMeasurementAnalysisResponses, GetApiV1ProjectsByNameGaSessionHistoryData, GetApiV1ProjectsByNameGaSessionHistoryErrors, GetApiV1ProjectsByNameGaSessionHistoryResponses, GetApiV1ProjectsByNameGaSocialReferralHistoryData, GetApiV1ProjectsByNameGaSocialReferralHistoryErrors, GetApiV1ProjectsByNameGaSocialReferralHistoryResponses, GetApiV1ProjectsByNameGaSocialReferralTrendData, GetApiV1ProjectsByNameGaSocialReferralTrendErrors, GetApiV1ProjectsByNameGaSocialReferralTrendResponses, GetApiV1ProjectsByNameGaStatusData, GetApiV1ProjectsByNameGaStatusErrors, GetApiV1ProjectsByNameGaStatusResponses, GetApiV1ProjectsByNameGaTrafficData, GetApiV1ProjectsByNameGaTrafficErrors, GetApiV1ProjectsByNameGaTrafficResponses, GetApiV1ProjectsByNameGbpAccountsData, GetApiV1ProjectsByNameGbpAccountsErrors, GetApiV1ProjectsByNameGbpAccountsResponses, GetApiV1ProjectsByNameGbpAttributesData, GetApiV1ProjectsByNameGbpAttributesErrors, GetApiV1ProjectsByNameGbpAttributesResponses, GetApiV1ProjectsByNameGbpKeywordsData, GetApiV1ProjectsByNameGbpKeywordsErrors, GetApiV1ProjectsByNameGbpKeywordsResponses, GetApiV1ProjectsByNameGbpLocationsData, GetApiV1ProjectsByNameGbpLocationsErrors, GetApiV1ProjectsByNameGbpLocationsResponses, GetApiV1ProjectsByNameGbpLodgingData, GetApiV1ProjectsByNameGbpLodgingErrors, GetApiV1ProjectsByNameGbpLodgingResponses, GetApiV1ProjectsByNameGbpMetricsData, GetApiV1ProjectsByNameGbpMetricsErrors, GetApiV1ProjectsByNameGbpMetricsResponses, GetApiV1ProjectsByNameGbpPlaceActionsData, GetApiV1ProjectsByNameGbpPlaceActionsErrors, GetApiV1ProjectsByNameGbpPlaceActionsResponses, GetApiV1ProjectsByNameGbpPlacesData, GetApiV1ProjectsByNameGbpPlacesErrors, GetApiV1ProjectsByNameGbpPlacesResponses, GetApiV1ProjectsByNameGbpSummaryData, GetApiV1ProjectsByNameGbpSummaryErrors, GetApiV1ProjectsByNameGbpSummaryResponses, GetApiV1ProjectsByNameGoogleCallbackData, GetApiV1ProjectsByNameGoogleCallbackErrors, GetApiV1ProjectsByNameGoogleCallbackResponses, GetApiV1ProjectsByNameGoogleConnectionsData, GetApiV1ProjectsByNameGoogleConnectionsErrors, GetApiV1ProjectsByNameGoogleConnectionsResponses, GetApiV1ProjectsByNameGoogleGscCoverageData, GetApiV1ProjectsByNameGoogleGscCoverageErrors, GetApiV1ProjectsByNameGoogleGscCoverageHistoryData, GetApiV1ProjectsByNameGoogleGscCoverageHistoryErrors, GetApiV1ProjectsByNameGoogleGscCoverageHistoryResponses, GetApiV1ProjectsByNameGoogleGscCoverageResponses, GetApiV1ProjectsByNameGoogleGscDeindexedData, GetApiV1ProjectsByNameGoogleGscDeindexedErrors, GetApiV1ProjectsByNameGoogleGscDeindexedResponses, GetApiV1ProjectsByNameGoogleGscInspectionsData, GetApiV1ProjectsByNameGoogleGscInspectionsErrors, GetApiV1ProjectsByNameGoogleGscInspectionsResponses, GetApiV1ProjectsByNameGoogleGscPerformanceDailyData, GetApiV1ProjectsByNameGoogleGscPerformanceDailyErrors, GetApiV1ProjectsByNameGoogleGscPerformanceDailyResponses, GetApiV1ProjectsByNameGoogleGscPerformanceData, GetApiV1ProjectsByNameGoogleGscPerformanceErrors, GetApiV1ProjectsByNameGoogleGscPerformanceResponses, GetApiV1ProjectsByNameGoogleGscSitemapsData, GetApiV1ProjectsByNameGoogleGscSitemapsErrors, GetApiV1ProjectsByNameGoogleGscSitemapsResponses, GetApiV1ProjectsByNameGoogleGscTopPagesData, GetApiV1ProjectsByNameGoogleGscTopPagesErrors, GetApiV1ProjectsByNameGoogleGscTopPagesResponses, GetApiV1ProjectsByNameGooglePropertiesData, GetApiV1ProjectsByNameGooglePropertiesErrors, GetApiV1ProjectsByNameGooglePropertiesResponses, GetApiV1ProjectsByNameHealthHistoryData, GetApiV1ProjectsByNameHealthHistoryErrors, GetApiV1ProjectsByNameHealthHistoryResponses, GetApiV1ProjectsByNameHealthLatestData, GetApiV1ProjectsByNameHealthLatestErrors, GetApiV1ProjectsByNameHealthLatestResponses, GetApiV1ProjectsByNameHistoryData, GetApiV1ProjectsByNameHistoryResponses, GetApiV1ProjectsByNameInsightsByIdData, GetApiV1ProjectsByNameInsightsByIdErrors, GetApiV1ProjectsByNameInsightsByIdResponses, GetApiV1ProjectsByNameInsightsData, GetApiV1ProjectsByNameInsightsErrors, GetApiV1ProjectsByNameInsightsResponses, GetApiV1ProjectsByNameKeywordsData, GetApiV1ProjectsByNameKeywordsResponses, GetApiV1ProjectsByNameLocationsData, GetApiV1ProjectsByNameLocationsErrors, GetApiV1ProjectsByNameLocationsResponses, GetApiV1ProjectsByNameMeasurementOverviewData, GetApiV1ProjectsByNameMeasurementOverviewErrors, GetApiV1ProjectsByNameMeasurementOverviewResponses, GetApiV1ProjectsByNameMeasurementPlanData, GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsData, GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsErrors, GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsResponses, GetApiV1ProjectsByNameMeasurementPlanDraftData, GetApiV1ProjectsByNameMeasurementPlanDraftErrors, GetApiV1ProjectsByNameMeasurementPlanDraftGroupsData, GetApiV1ProjectsByNameMeasurementPlanDraftGroupsErrors, GetApiV1ProjectsByNameMeasurementPlanDraftGroupsResponses, GetApiV1ProjectsByNameMeasurementPlanDraftResponses, GetApiV1ProjectsByNameMeasurementPlanDraftTargetsData, GetApiV1ProjectsByNameMeasurementPlanDraftTargetsErrors, GetApiV1ProjectsByNameMeasurementPlanDraftTargetsResponses, GetApiV1ProjectsByNameMeasurementPlanErrors, GetApiV1ProjectsByNameMeasurementPlanResponses, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionData, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors, GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses, GetApiV1ProjectsByNameMeasurementPlanVersionsData, GetApiV1ProjectsByNameMeasurementPlanVersionsErrors, GetApiV1ProjectsByNameMeasurementPlanVersionsResponses, GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdData, GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors, GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses, GetApiV1ProjectsByNameMeasurementQuerySetsData, GetApiV1ProjectsByNameMeasurementQuerySetsErrors, GetApiV1ProjectsByNameMeasurementQuerySetsResponses, GetApiV1ProjectsByNameMeasurementQueryTemplatesData, GetApiV1ProjectsByNameMeasurementQueryTemplatesErrors, GetApiV1ProjectsByNameMeasurementQueryTemplatesResponses, GetApiV1ProjectsByNameMeasurementReportData, GetApiV1ProjectsByNameMeasurementReportErrors, GetApiV1ProjectsByNameMeasurementReportResponses, GetApiV1ProjectsByNameMeasurementSetupData, GetApiV1ProjectsByNameMeasurementSetupErrors, GetApiV1ProjectsByNameMeasurementSetupResponses, GetApiV1ProjectsByNameNotificationsData, GetApiV1ProjectsByNameNotificationsResponses, GetApiV1ProjectsByNameOrganicEvidenceData, GetApiV1ProjectsByNameOrganicEvidenceErrors, GetApiV1ProjectsByNameOrganicEvidenceResponses, GetApiV1ProjectsByNameOverviewData, GetApiV1ProjectsByNameOverviewErrors, GetApiV1ProjectsByNameOverviewResponses, GetApiV1ProjectsByNameQueriesData, GetApiV1ProjectsByNameQueriesResponses, GetApiV1ProjectsByNameReportData, GetApiV1ProjectsByNameReportErrors, GetApiV1ProjectsByNameReportHtmlData, GetApiV1ProjectsByNameReportHtmlErrors, GetApiV1ProjectsByNameReportHtmlResponses, GetApiV1ProjectsByNameReportResponses, GetApiV1ProjectsByNameResearchRunsByRunIdData, GetApiV1ProjectsByNameResearchRunsByRunIdErrors, GetApiV1ProjectsByNameResearchRunsByRunIdResponses, GetApiV1ProjectsByNameResearchRunsData, GetApiV1ProjectsByNameResearchRunsErrors, GetApiV1ProjectsByNameResearchRunsResponses, GetApiV1ProjectsByNameResponses, GetApiV1ProjectsByNameResultsExportData, GetApiV1ProjectsByNameResultsExportErrors, GetApiV1ProjectsByNameResultsExportResponses, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffData, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffErrors, GetApiV1ProjectsByNameRunsByRunIdBrowserDiffResponses, GetApiV1ProjectsByNameRunsData, GetApiV1ProjectsByNameRunsLatestData, GetApiV1ProjectsByNameRunsLatestResponses, GetApiV1ProjectsByNameRunsResponses, GetApiV1ProjectsByNameScheduleData, GetApiV1ProjectsByNameScheduleErrors, GetApiV1ProjectsByNameScheduleResponses, GetApiV1ProjectsByNameSearchData, GetApiV1ProjectsByNameSearchErrors, GetApiV1ProjectsByNameSearchResponses, GetApiV1ProjectsByNameSnapshotsData, GetApiV1ProjectsByNameSnapshotsDiffData, GetApiV1ProjectsByNameSnapshotsDiffErrors, GetApiV1ProjectsByNameSnapshotsDiffResponses, GetApiV1ProjectsByNameSnapshotsResponses, GetApiV1ProjectsByNameTechnicalAeoData, GetApiV1ProjectsByNameTechnicalAeoErrors, GetApiV1ProjectsByNameTechnicalAeoPagesData, GetApiV1ProjectsByNameTechnicalAeoPagesErrors, GetApiV1ProjectsByNameTechnicalAeoPagesResponses, GetApiV1ProjectsByNameTechnicalAeoResponses, GetApiV1ProjectsByNameTechnicalAeoTrendData, GetApiV1ProjectsByNameTechnicalAeoTrendErrors, GetApiV1ProjectsByNameTechnicalAeoTrendResponses, GetApiV1ProjectsByNameTimelineData, GetApiV1ProjectsByNameTimelineResponses, GetApiV1ProjectsByNameTrafficEventsData, GetApiV1ProjectsByNameTrafficEventsErrors, GetApiV1ProjectsByNameTrafficEventsResponses, GetApiV1ProjectsByNameTrafficSourcesByIdData, GetApiV1ProjectsByNameTrafficSourcesByIdErrors, GetApiV1ProjectsByNameTrafficSourcesByIdResponses, GetApiV1ProjectsByNameTrafficSourcesData, GetApiV1ProjectsByNameTrafficSourcesErrors, GetApiV1ProjectsByNameTrafficSourcesResponses, GetApiV1ProjectsByNameTrafficStatusData, GetApiV1ProjectsByNameTrafficStatusErrors, GetApiV1ProjectsByNameTrafficStatusResponses, GetApiV1ProjectsByNameVisibilityCompareData, GetApiV1ProjectsByNameVisibilityCompareErrors, GetApiV1ProjectsByNameVisibilityCompareResponses, GetApiV1ProjectsByNameVisibilityStatsData, GetApiV1ProjectsByNameVisibilityStatsErrors, GetApiV1ProjectsByNameVisibilityStatsResponses, GetApiV1ProjectsByNameWordpressAuditData, GetApiV1ProjectsByNameWordpressAuditErrors, GetApiV1ProjectsByNameWordpressAuditResponses, GetApiV1ProjectsByNameWordpressDiffData, GetApiV1ProjectsByNameWordpressDiffErrors, GetApiV1ProjectsByNameWordpressDiffResponses, GetApiV1ProjectsByNameWordpressLlmsTxtData, GetApiV1ProjectsByNameWordpressLlmsTxtErrors, GetApiV1ProjectsByNameWordpressLlmsTxtResponses, GetApiV1ProjectsByNameWordpressPageData, GetApiV1ProjectsByNameWordpressPageErrors, GetApiV1ProjectsByNameWordpressPageResponses, GetApiV1ProjectsByNameWordpressPagesData, GetApiV1ProjectsByNameWordpressPagesErrors, GetApiV1ProjectsByNameWordpressPagesResponses, GetApiV1ProjectsByNameWordpressSchemaData, GetApiV1ProjectsByNameWordpressSchemaErrors, GetApiV1ProjectsByNameWordpressSchemaResponses, GetApiV1ProjectsByNameWordpressSchemaStatusData, GetApiV1ProjectsByNameWordpressSchemaStatusErrors, GetApiV1ProjectsByNameWordpressSchemaStatusResponses, GetApiV1ProjectsByNameWordpressStagingStatusData, GetApiV1ProjectsByNameWordpressStagingStatusErrors, GetApiV1ProjectsByNameWordpressStagingStatusResponses, GetApiV1ProjectsByNameWordpressStatusData, GetApiV1ProjectsByNameWordpressStatusErrors, GetApiV1ProjectsByNameWordpressStatusResponses, GetApiV1ProjectsData, GetApiV1ProjectsResponses, GetApiV1RunsByIdData, GetApiV1RunsByIdErrors, GetApiV1RunsByIdResponses, GetApiV1RunsData, GetApiV1RunsResponses, GetApiV1ScreenshotsBySnapshotIdData, GetApiV1ScreenshotsBySnapshotIdErrors, GetApiV1ScreenshotsBySnapshotIdResponses, GetApiV1SettingsData, GetApiV1SettingsResponses, GetApiV1TelemetryData, GetApiV1TelemetryErrors, GetApiV1TelemetryResponses, GetApiV1UsersData, GetApiV1UsersErrors, GetApiV1UsersResponses, PostApiV1ApplyData, PostApiV1ApplyErrors, PostApiV1ApplyResponses, PostApiV1AuthLoginData, PostApiV1AuthLoginErrors, PostApiV1AuthLoginResponses, PostApiV1AuthLogoutData, PostApiV1AuthLogoutResponses, PostApiV1BacklinksInstallData, PostApiV1BacklinksInstallErrors, PostApiV1BacklinksInstallResponses, PostApiV1BacklinksSyncsData, PostApiV1BacklinksSyncsErrors, PostApiV1BacklinksSyncsResponses, PostApiV1CdpScreenshotData, PostApiV1CdpScreenshotErrors, PostApiV1CdpScreenshotResponses, PostApiV1KeysByIdRevokeData, PostApiV1KeysByIdRevokeErrors, PostApiV1KeysByIdRevokeResponses, PostApiV1KeysData, PostApiV1KeysErrors, PostApiV1KeysResponses, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeData, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeErrors, PostApiV1ProjectsByNameAdsActivationGrantsByGrantIdRevokeResponses, PostApiV1ProjectsByNameAdsActivationGrantsData, PostApiV1ProjectsByNameAdsActivationGrantsErrors, PostApiV1ProjectsByNameAdsActivationGrantsResponses, PostApiV1ProjectsByNameAdsAdGroupsByIdData, PostApiV1ProjectsByNameAdsAdGroupsByIdErrors, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseData, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseErrors, PostApiV1ProjectsByNameAdsAdGroupsByIdPauseResponses, PostApiV1ProjectsByNameAdsAdGroupsByIdResponses, PostApiV1ProjectsByNameAdsAdGroupsData, PostApiV1ProjectsByNameAdsAdGroupsErrors, PostApiV1ProjectsByNameAdsAdGroupsResponses, PostApiV1ProjectsByNameAdsAdsByIdData, PostApiV1ProjectsByNameAdsAdsByIdErrors, PostApiV1ProjectsByNameAdsAdsByIdPauseData, PostApiV1ProjectsByNameAdsAdsByIdPauseErrors, PostApiV1ProjectsByNameAdsAdsByIdPauseResponses, PostApiV1ProjectsByNameAdsAdsByIdResponses, PostApiV1ProjectsByNameAdsAdsData, PostApiV1ProjectsByNameAdsAdsErrors, PostApiV1ProjectsByNameAdsAdsResponses, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeData, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeErrors, PostApiV1ProjectsByNameAdsCampaignsByIdActivateTreeResponses, PostApiV1ProjectsByNameAdsCampaignsByIdData, PostApiV1ProjectsByNameAdsCampaignsByIdErrors, PostApiV1ProjectsByNameAdsCampaignsByIdPauseData, PostApiV1ProjectsByNameAdsCampaignsByIdPauseErrors, PostApiV1ProjectsByNameAdsCampaignsByIdPauseResponses, PostApiV1ProjectsByNameAdsCampaignsByIdResponses, PostApiV1ProjectsByNameAdsCampaignsData, PostApiV1ProjectsByNameAdsCampaignsErrors, PostApiV1ProjectsByNameAdsCampaignsResponses, PostApiV1ProjectsByNameAdsConnectData, PostApiV1ProjectsByNameAdsConnectErrors, PostApiV1ProjectsByNameAdsConnectResponses, PostApiV1ProjectsByNameAdsFilesData, PostApiV1ProjectsByNameAdsFilesErrors, PostApiV1ProjectsByNameAdsFilesResponses, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileErrors, PostApiV1ProjectsByNameAdsOperationsByOperationKeyReconcileResponses, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationData, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationErrors, PostApiV1ProjectsByNameAdsOperationsByOperationKeyResumeActivationResponses, PostApiV1ProjectsByNameAdsSyncData, PostApiV1ProjectsByNameAdsSyncErrors, PostApiV1ProjectsByNameAdsSyncResponses, PostApiV1ProjectsByNameBacklinksExtractData, PostApiV1ProjectsByNameBacklinksExtractErrors, PostApiV1ProjectsByNameBacklinksExtractResponses, PostApiV1ProjectsByNameBingConnectData, PostApiV1ProjectsByNameBingConnectErrors, PostApiV1ProjectsByNameBingConnectResponses, PostApiV1ProjectsByNameBingInspectSitemapData, PostApiV1ProjectsByNameBingInspectSitemapErrors, PostApiV1ProjectsByNameBingInspectSitemapResponses, PostApiV1ProjectsByNameBingInspectUrlData, PostApiV1ProjectsByNameBingInspectUrlErrors, PostApiV1ProjectsByNameBingInspectUrlResponses, PostApiV1ProjectsByNameBingRequestIndexingData, PostApiV1ProjectsByNameBingRequestIndexingErrors, PostApiV1ProjectsByNameBingRequestIndexingResponses, PostApiV1ProjectsByNameBingSetSiteData, PostApiV1ProjectsByNameBingSetSiteErrors, PostApiV1ProjectsByNameBingSetSiteResponses, PostApiV1ProjectsByNameCompetitorsData, PostApiV1ProjectsByNameCompetitorsErrors, PostApiV1ProjectsByNameCompetitorsResponses, PostApiV1ProjectsByNameContentDismissalsData, PostApiV1ProjectsByNameContentDismissalsErrors, PostApiV1ProjectsByNameContentDismissalsResponses, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeErrors, PostApiV1ProjectsByNameContentRecommendationsByTargetRefAnalyzeResponses, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefData, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefErrors, PostApiV1ProjectsByNameContentRecommendationsByTargetRefBriefResponses, PostApiV1ProjectsByNameDiscoverRunData, PostApiV1ProjectsByNameDiscoverRunErrors, PostApiV1ProjectsByNameDiscoverRunResponses, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteData, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteErrors, PostApiV1ProjectsByNameDiscoverSessionsByIdPromoteResponses, PostApiV1ProjectsByNameGaConnectData, PostApiV1ProjectsByNameGaConnectErrors, PostApiV1ProjectsByNameGaConnectResponses, PostApiV1ProjectsByNameGaSyncData, PostApiV1ProjectsByNameGaSyncErrors, PostApiV1ProjectsByNameGaSyncResponses, PostApiV1ProjectsByNameGbpLocationsDiscoverData, PostApiV1ProjectsByNameGbpLocationsDiscoverErrors, PostApiV1ProjectsByNameGbpLocationsDiscoverResponses, PostApiV1ProjectsByNameGbpSyncData, PostApiV1ProjectsByNameGbpSyncErrors, PostApiV1ProjectsByNameGbpSyncResponses, PostApiV1ProjectsByNameGoogleConnectData, PostApiV1ProjectsByNameGoogleConnectErrors, PostApiV1ProjectsByNameGoogleConnectResponses, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsData, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsErrors, PostApiV1ProjectsByNameGoogleGscDiscoverSitemapsResponses, PostApiV1ProjectsByNameGoogleGscInspectData, PostApiV1ProjectsByNameGoogleGscInspectErrors, PostApiV1ProjectsByNameGoogleGscInspectResponses, PostApiV1ProjectsByNameGoogleGscInspectSitemapData, PostApiV1ProjectsByNameGoogleGscInspectSitemapErrors, PostApiV1ProjectsByNameGoogleGscInspectSitemapResponses, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitData, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors, PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses, PostApiV1ProjectsByNameGoogleGscSyncData, PostApiV1ProjectsByNameGoogleGscSyncErrors, PostApiV1ProjectsByNameGoogleGscSyncResponses, PostApiV1ProjectsByNameGoogleIndexingRequestData, PostApiV1ProjectsByNameGoogleIndexingRequestErrors, PostApiV1ProjectsByNameGoogleIndexingRequestResponses, PostApiV1ProjectsByNameInsightsByIdDismissData, PostApiV1ProjectsByNameInsightsByIdDismissErrors, PostApiV1ProjectsByNameInsightsByIdDismissResponses, PostApiV1ProjectsByNameKeywordsData, PostApiV1ProjectsByNameKeywordsGenerateData, PostApiV1ProjectsByNameKeywordsGenerateErrors, PostApiV1ProjectsByNameKeywordsGenerateResponses, PostApiV1ProjectsByNameKeywordsResponses, PostApiV1ProjectsByNameLocationsData, PostApiV1ProjectsByNameLocationsErrors, PostApiV1ProjectsByNameLocationsResponses, PostApiV1ProjectsByNameMeasurementDiscoveryData, PostApiV1ProjectsByNameMeasurementDiscoveryErrors, PostApiV1ProjectsByNameMeasurementDiscoveryResponses, PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateData, PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateErrors, PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateResponses, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewData, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors, PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewData, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors, PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupResponses, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetData, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetErrors, PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetResponses, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireData, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors, PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses, PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyData, PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyErrors, PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyResponses, PostApiV1ProjectsByNameNotificationsByIdTestData, PostApiV1ProjectsByNameNotificationsByIdTestErrors, PostApiV1ProjectsByNameNotificationsByIdTestResponses, PostApiV1ProjectsByNameNotificationsData, PostApiV1ProjectsByNameNotificationsResponses, PostApiV1ProjectsByNameQueriesData, PostApiV1ProjectsByNameQueriesGenerateData, PostApiV1ProjectsByNameQueriesGenerateErrors, PostApiV1ProjectsByNameQueriesGenerateResponses, PostApiV1ProjectsByNameQueriesReplacePreviewData, PostApiV1ProjectsByNameQueriesReplacePreviewErrors, PostApiV1ProjectsByNameQueriesReplacePreviewResponses, PostApiV1ProjectsByNameQueriesResponses, PostApiV1ProjectsByNameResearchRunsData, PostApiV1ProjectsByNameResearchRunsErrors, PostApiV1ProjectsByNameResearchRunsResponses, PostApiV1ProjectsByNameRunsData, PostApiV1ProjectsByNameRunsErrors, PostApiV1ProjectsByNameRunsResponses, PostApiV1ProjectsByNameTechnicalAeoRunsData, PostApiV1ProjectsByNameTechnicalAeoRunsErrors, PostApiV1ProjectsByNameTechnicalAeoRunsResponses, PostApiV1ProjectsByNameTrafficConnectCloudRunData, PostApiV1ProjectsByNameTrafficConnectCloudRunErrors, PostApiV1ProjectsByNameTrafficConnectCloudRunResponses, PostApiV1ProjectsByNameTrafficConnectVercelData, PostApiV1ProjectsByNameTrafficConnectVercelErrors, PostApiV1ProjectsByNameTrafficConnectVercelResponses, PostApiV1ProjectsByNameTrafficConnectWordpressData, PostApiV1ProjectsByNameTrafficConnectWordpressErrors, PostApiV1ProjectsByNameTrafficConnectWordpressResponses, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillData, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillErrors, PostApiV1ProjectsByNameTrafficSourcesByIdBackfillResponses, PostApiV1ProjectsByNameTrafficSourcesByIdResetData, PostApiV1ProjectsByNameTrafficSourcesByIdResetErrors, PostApiV1ProjectsByNameTrafficSourcesByIdResetResponses, PostApiV1ProjectsByNameTrafficSourcesByIdSyncData, PostApiV1ProjectsByNameTrafficSourcesByIdSyncErrors, PostApiV1ProjectsByNameTrafficSourcesByIdSyncResponses, PostApiV1ProjectsByNameWordpressConnectData, PostApiV1ProjectsByNameWordpressConnectErrors, PostApiV1ProjectsByNameWordpressConnectResponses, PostApiV1ProjectsByNameWordpressLlmsTxtManualData, PostApiV1ProjectsByNameWordpressLlmsTxtManualErrors, PostApiV1ProjectsByNameWordpressLlmsTxtManualResponses, PostApiV1ProjectsByNameWordpressOnboardData, PostApiV1ProjectsByNameWordpressOnboardErrors, PostApiV1ProjectsByNameWordpressOnboardResponses, PostApiV1ProjectsByNameWordpressPageMetaData, PostApiV1ProjectsByNameWordpressPageMetaErrors, PostApiV1ProjectsByNameWordpressPageMetaResponses, PostApiV1ProjectsByNameWordpressPagesData, PostApiV1ProjectsByNameWordpressPagesErrors, PostApiV1ProjectsByNameWordpressPagesMetaBulkData, PostApiV1ProjectsByNameWordpressPagesMetaBulkErrors, PostApiV1ProjectsByNameWordpressPagesMetaBulkResponses, PostApiV1ProjectsByNameWordpressPagesResponses, PostApiV1ProjectsByNameWordpressSchemaDeployData, PostApiV1ProjectsByNameWordpressSchemaDeployErrors, PostApiV1ProjectsByNameWordpressSchemaDeployResponses, PostApiV1ProjectsByNameWordpressSchemaManualData, PostApiV1ProjectsByNameWordpressSchemaManualErrors, PostApiV1ProjectsByNameWordpressSchemaManualResponses, PostApiV1ProjectsByNameWordpressStagingPushData, PostApiV1ProjectsByNameWordpressStagingPushErrors, PostApiV1ProjectsByNameWordpressStagingPushResponses, PostApiV1RunsByIdCancelData, PostApiV1RunsByIdCancelErrors, PostApiV1RunsByIdCancelResponses, PostApiV1RunsData, PostApiV1RunsErrors, PostApiV1RunsResponses, PostApiV1SnapshotData, PostApiV1SnapshotErrors, PostApiV1SnapshotResponses, PostApiV1TelemetryOnboardingData, PostApiV1TelemetryOnboardingErrors, PostApiV1TelemetryOnboardingResponses, PostApiV1UsersData, PostApiV1UsersErrors, PostApiV1UsersResponses, PutApiV1ProjectsByNameAgentMemoryData, PutApiV1ProjectsByNameAgentMemoryErrors, PutApiV1ProjectsByNameAgentMemoryResponses, PutApiV1ProjectsByNameCompetitorsData, PutApiV1ProjectsByNameCompetitorsResponses, PutApiV1ProjectsByNameData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionData, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionErrors, PutApiV1ProjectsByNameGbpLocationsByLocationNameSelectionResponses, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyData, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyErrors, PutApiV1ProjectsByNameGoogleConnectionsByTypePropertyResponses, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapData, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapErrors, PutApiV1ProjectsByNameGoogleConnectionsByTypeSitemapResponses, PutApiV1ProjectsByNameKeywordsData, PutApiV1ProjectsByNameKeywordsResponses, PutApiV1ProjectsByNameLocationsDefaultData, PutApiV1ProjectsByNameLocationsDefaultErrors, PutApiV1ProjectsByNameLocationsDefaultResponses, PutApiV1ProjectsByNameMeasurementPlanData, PutApiV1ProjectsByNameMeasurementPlanErrors, PutApiV1ProjectsByNameMeasurementPlanResponses, PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdData, PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors, PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses, PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdData, PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors, PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses, PutApiV1ProjectsByNameQueriesData, PutApiV1ProjectsByNameQueriesResponses, PutApiV1ProjectsByNameResponses, PutApiV1ProjectsByNameScheduleData, PutApiV1ProjectsByNameScheduleErrors, PutApiV1ProjectsByNameScheduleResponses, PutApiV1ProjectsByNameWordpressPageData, PutApiV1ProjectsByNameWordpressPageErrors, PutApiV1ProjectsByNameWordpressPageResponses, PutApiV1SettingsBingData, PutApiV1SettingsBingErrors, PutApiV1SettingsBingResponses, PutApiV1SettingsCdpData, PutApiV1SettingsCdpErrors, PutApiV1SettingsCdpResponses, PutApiV1SettingsGoogleData, PutApiV1SettingsGoogleErrors, PutApiV1SettingsGoogleResponses, PutApiV1SettingsProvidersByNameData, PutApiV1SettingsProvidersByNameErrors, PutApiV1SettingsProvidersByNameResponses, PutApiV1TelemetryData, PutApiV1TelemetryErrors, PutApiV1TelemetryResponses } from './types.gen.js'; export type Options = Options2 & { /** @@ -278,6 +278,700 @@ export const getApiV1ProjectsByNameMeasurementReport = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-setup', + ...options + }); +}; + +/** + * Get the server-side setup draft + * + * Returns the single draft for the project, or an explicit null for a project with none. The response ETag is required on every subsequent mutation. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraft = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft', + ...options + }); +}; + +/** + * Page the draft Targets + * + * Cursor-paginated and deterministically ordered by normalized label then stable key. Serves a thousand Targets without client-side truncation. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftTargets = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/targets', + ...options + }); +}; + +/** + * Page the draft assignments + * + * Cursor-paginated Target-owned query assignments, each carrying its class and where that class came from. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftAssignments = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/assignments', + ...options + }); +}; + +/** + * Page the draft groups + * + * Cursor-paginated reporting groups and their confirmed competitors. Groups never hold queries or execution context. + */ +export const getApiV1ProjectsByNameMeasurementPlanDraftGroups = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/groups', + ...options + }); +}; + +/** + * Start a setup draft + * + * Creates the single draft for the project, recording the active revision it was created from. A draft never changes the active mode. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsCreate = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/create', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Import a sitemap into the draft + * + * Fetches an operator-supplied sitemap under strict egress policy and records the deterministic discovery inputs on the draft. It proposes Targets for review; it never publishes a plan or starts a run. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemap = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/import-sitemap', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Apply the operator selection from discovery + * + * Turns reviewed discovery proposals into new or rebound Targets. Ambiguity is never resolved automatically. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelection = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/apply-sitemap-selection', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Add or update a draft Target + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTarget = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/upsert-target', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Rename a draft Target + * + * Changes the label only. The stable key, its assignments and its group membership are untouched. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTarget = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/rename-target', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Merge draft Targets into one + * + * The survivor keeps its stable key, and the merged Targets contribute their assignments and group membership. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargets = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/merge-targets', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Exclude a draft Target + * + * Excluded Targets stay in the draft for review but never compile. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTarget = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/exclude-target', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Rebind a draft Target to a new discovered URL + * + * Preserves the stable key, the assignments and the group membership, so history follows the Target across a site restructure. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTarget = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/rebind-target', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Assign project queries to a draft Target + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignments = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/apply-assignments', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Remove one assignment from a draft Target + * + * Removes the assignment only. The project query behind it is never deleted. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignment = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/remove-assignment', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Clear every assignment on a draft Target + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignments = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/clear-assignments', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Classify assignments as Branded or Non-brand + * + * Always records the class as operator-sourced, so a later rule proposal cannot overwrite it. The class belongs to the assignment, so one query can be Branded for one Target and Non-brand for another. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignments = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/classify-assignments', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Add or update a draft group + * + * Reporting membership only. A payload carrying queries, providers, locations or models is rejected. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroup = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/upsert-group', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Remove a draft group + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroup = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/remove-group', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Add or update a group competitor + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitor = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/upsert-competitor', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Remove a group competitor + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitor = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/remove-competitor', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Compile the draft without publishing + * + * Compiles the stored draft and returns the compiled checksum the publish guard expects. Writes nothing, so a view-only account can call it. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreview = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/compile-preview', + ...options + }); +}; + +/** + * Compare the draft with the active revision + * + * Compiles the stored draft and reports the keys that changed against the active revision. Writes nothing. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreview = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/diff-preview', + ...options + }); +}; + +/** + * Publish the draft as a new revision + * + * Recompiles server-side and refuses content that changed after review. Content identical to the active revision is a no-op returning it; content identical to an older revision publishes as a new revision, so a revert is a first-class operation. Publishing never starts a run. + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsPublish = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/publish', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Discard the draft + */ +export const postApiV1ProjectsByNameMeasurementPlanDraftActionsDiscard = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/discard', + ...options + }); +}; + +/** + * Deactivate the active measurement plan + * + * Deletes the active-plan pointer and nothing else. Schedules, runs, queries, versions and evidence are untouched, and the revisions stay readable. + */ +export const postApiV1ProjectsByNameMeasurementPlanActionsDeactivate = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-plan/actions/deactivate', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Get the scoped measurement overview + * + * Aggregates one run of evidence for All Properties, a group, or a single Property. Without runId the most recent completed run pinned to the active revision is used; a run pinned to another revision is refused rather than joined. Metrics are computed before search is applied, and a metric with no evidence is unavailable rather than zero. + */ +export const getApiV1ProjectsByNameMeasurementOverview = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-overview', + ...options + }); +}; + +/** + * List measurement query sets + */ +export const getApiV1ProjectsByNameMeasurementQuerySets = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-sets', + ...options + }); +}; + +/** + * Delete a measurement query set + * + * Deletes the set and its membership rows. It never deletes a query or a published snapshot. + */ +export const deleteApiV1ProjectsByNameMeasurementQuerySetsBySetId = (options: Options) => { + return (options.client ?? client).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-sets/{setId}', + ...options + }); +}; + +/** + * Get a measurement query set + */ +export const getApiV1ProjectsByNameMeasurementQuerySetsBySetId = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-sets/{setId}', + ...options + }); +}; + +/** + * Create or replace a measurement query set + * + * A set holds ordered references to project query IDs. Membership changes never create or delete a query. + */ +export const putApiV1ProjectsByNameMeasurementQuerySetsBySetId = (options: Options) => { + return (options.client ?? client).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-sets/{setId}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * List measurement query templates + */ +export const getApiV1ProjectsByNameMeasurementQueryTemplates = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-templates', + ...options + }); +}; + +/** + * Delete a measurement query template + * + * Deletes the authoring template. Queries it already expanded, and every published snapshot of them, are untouched. + */ +export const deleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateId = (options: Options) => { + return (options.client ?? client).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-templates/{templateId}', + ...options + }); +}; + +/** + * Create or replace a measurement query template + * + * Templates are authoring assets. A published plan contains only the immutable snapshots an expansion produced. + */ +export const putApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateId = (options: Options) => { + return (options.client ?? client).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-templates/{templateId}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Expand a query template into project queries + * + * Expands one binding per concrete query. Expansion is additive: a query that already exists is reported rather than duplicated. + */ +export const postApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApply = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/projects/{name}/measurement-query-templates/{templateId}/apply', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + /** * List projects */ diff --git a/packages/api-client-generated/src/generated/types.gen.ts b/packages/api-client-generated/src/generated/types.gen.ts index 590a5b279..280ef6402 100644 --- a/packages/api-client-generated/src/generated/types.gen.ts +++ b/packages/api-client-generated/src/generated/types.gen.ts @@ -2856,48 +2856,88 @@ export type MeasurementDiscoveryResponse = { }>; }; -export type MeasurementPlanCompilePreviewResponse = { +export type MeasurementDraftApplyAssignmentsRequest = { + targetKey: string; + queryIds: Array; + contextOverride?: { + providers?: Array; + models?: { + [key: string]: string; + }; + locations?: Array; + }; +}; + +export type MeasurementDraftApplySitemapSelectionRequest = { + selections: Array<{ + discoveryIdentity: string; + action: 'create' | 'rebind' | 'ignore'; + targetKey?: string; + label?: string; + }>; +}; + +export type MeasurementDraftAssignmentPage = { + items: Array<{ + targetKey: string; + queryId: string; + contextOverride?: { + providers?: Array; + models?: { + [key: string]: string; + }; + locations?: Array; + }; + queryClass: 'branded' | 'non-brand' | 'unclassified'; + classificationSource: 'rule' | 'operator'; + }>; + nextCursor: string | null; + totalEstimate?: number; +}; + +export type MeasurementDraftClassifyAssignmentsRequest = { + queryClass: 'branded' | 'non-brand'; + assignments: Array<{ + targetKey: string; + queryId: string; + }>; +}; + +export type MeasurementDraftClearAssignmentsRequest = { + targetKey: string; +}; + +export type MeasurementDraftCompilePreviewResponse = { ok: true; + compiledChecksum: string; checks: Array<{ - id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + ruleId: string; severity: 'fail' | 'warn'; message: string; path: Array; }>; - executionNodes: Array<{ - stableKey: string; - queryText: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - expectedSnapshots: number; - }>; - dedupSaved: number; - usageEdges: { - baseline: number; - target: number; + counts: { + targets: number; + includedTargets: number; + assignments: number; + unclassifiedAssignments: number; + groups: number; + competitors: number; }; - estCostUsd: null; plan: { - schemaVersion: 1; - defaultContext: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - effectiveOwnedHosts: Array; - projectCanonicalHost: string; - projectBrandNames: Array; + schemaVersion: 2; + identities: { + projectBrand: { + canonicalHost: string; + ownedHosts: Array; + names: Array; + }; + }; targets: Array<{ stableKey: string; label: string; - urls: Array<{ + aliases: Array; + urlMatchers: Array<{ kind: 'exact'; url: string; pathCase: 'sensitive' | 'insensitive'; @@ -2910,148 +2950,107 @@ export type MeasurementPlanCompilePreviewResponse = { kind: 'host'; host: string; }>; - aliases: Array; - metadata?: { - [key: string]: string; - }; mentionNotApplicable: boolean; + discoveryIdentity: string | null; }>; groups: Array<{ stableKey: string; label: string; targetKeys: Array; - competitors?: Array; - }>; - targetQuerySelections: Array<{ - targetKey: string; - queryIds: Array; - context?: { + competitors: Array<{ + stableKey: string; label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; + domain: string; + aliases: Array; + }>; }>; querySnapshots: Array<{ queryId: string; queryText: string; + provenance: { + source: 'manual' | 'query-set' | 'template' | 'discovery'; + sourceId: string | null; + capturedAt: string; + }; + }>; + assignments: Array<{ + targetKey: string; + queryId: string; + queryClass: 'branded' | 'non-brand'; + executionNodeKey: string; }>; executionNodes: Array<{ stableKey: string; + queryId: string; queryText: string; context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; + providers: Array; + models: { + [key: string]: string; + }; + location: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + }; expectedSnapshots: number; }>; usageEdges: Array<{ - kind: 'baseline'; - executionNodeKey: string; - queryId: string; - } | { - kind: 'target'; executionNodeKey: string; - queryId: string; targetKey: string; + queryId: string; }>; - warnings: Array<{ - code: 'target-alias-prefix-overlap'; - message: string; - targetKeys: Array; - aliases: Array; - }>; - }; - warnings: Array<{ - code: 'target-alias-prefix-overlap'; - message: string; - targetKeys: Array; - aliases: Array; - }>; - counts: { - targets: number; - groups: number; - queries: number; - executionNodes: number; - usageEdges: number; - baselineEdges: number; - targetEdges: number; - dedupSavings: number; + compiledChecksum: string; }; } | { ok: false; + compiledChecksum: null; checks: Array<{ - id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + ruleId: string; severity: 'fail' | 'warn'; message: string; path: Array; }>; - executionNodes: Array<{ - stableKey: string; - queryText: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - expectedSnapshots: number; - }>; - dedupSaved: 0; - usageEdges: { - baseline: 0; - target: 0; - }; - estCostUsd: null; }; -export type MeasurementPlanDiffPreviewResponse = { +export type MeasurementDraftCreateRequest = { + expectedActiveRevision: number | null; +}; + +export type MeasurementDraftDiffPreviewResponse = { ok: true; + compiledChecksum: string; checks: Array<{ - id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + ruleId: string; severity: 'fail' | 'warn'; message: string; path: Array; }>; - executionNodes: Array<{ - stableKey: string; - queryText: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - expectedSnapshots: number; - }>; - dedupSaved: number; - usageEdges: { - baseline: number; - target: number; + counts: { + targets: number; + includedTargets: number; + assignments: number; + unclassifiedAssignments: number; + groups: number; + competitors: number; }; - estCostUsd: null; plan: { - schemaVersion: 1; - defaultContext: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - effectiveOwnedHosts: Array; - projectCanonicalHost: string; - projectBrandNames: Array; + schemaVersion: 2; + identities: { + projectBrand: { + canonicalHost: string; + ownedHosts: Array; + names: Array; + }; + }; targets: Array<{ stableKey: string; label: string; - urls: Array<{ + aliases: Array; + urlMatchers: Array<{ kind: 'exact'; url: string; pathCase: 'sensitive' | 'insensitive'; @@ -3064,395 +3063,480 @@ export type MeasurementPlanDiffPreviewResponse = { kind: 'host'; host: string; }>; - aliases: Array; - metadata?: { - [key: string]: string; - }; mentionNotApplicable: boolean; + discoveryIdentity: string | null; }>; groups: Array<{ stableKey: string; label: string; targetKeys: Array; - competitors?: Array; - }>; - targetQuerySelections: Array<{ - targetKey: string; - queryIds: Array; - context?: { + competitors: Array<{ + stableKey: string; label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; + domain: string; + aliases: Array; + }>; }>; querySnapshots: Array<{ queryId: string; queryText: string; + provenance: { + source: 'manual' | 'query-set' | 'template' | 'discovery'; + sourceId: string | null; + capturedAt: string; + }; + }>; + assignments: Array<{ + targetKey: string; + queryId: string; + queryClass: 'branded' | 'non-brand'; + executionNodeKey: string; }>; executionNodes: Array<{ stableKey: string; + queryId: string; queryText: string; context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; + providers: Array; + models: { + [key: string]: string; + }; + location: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + }; expectedSnapshots: number; }>; usageEdges: Array<{ - kind: 'baseline'; - executionNodeKey: string; - queryId: string; - } | { - kind: 'target'; executionNodeKey: string; - queryId: string; targetKey: string; + queryId: string; }>; - warnings: Array<{ - code: 'target-alias-prefix-overlap'; - message: string; - targetKeys: Array; - aliases: Array; - }>; - }; - warnings: Array<{ - code: 'target-alias-prefix-overlap'; - message: string; - targetKeys: Array; - aliases: Array; - }>; - counts: { - targets: number; - groups: number; - queries: number; - executionNodes: number; - usageEdges: number; - baselineEdges: number; - targetEdges: number; - dedupSavings: number; + compiledChecksum: string; }; diff: { activeRevision: number | null; targets: { - added: Array<{ - stableKey: string; - label: string; - urls: Array<{ - kind: 'exact'; - url: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'prefix'; - host: string; - pathPrefix: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'host'; - host: string; - }>; - aliases: Array; - metadata?: { - [key: string]: string; - }; - mentionNotApplicable: boolean; - }>; - removed: Array<{ - stableKey: string; - label: string; - urls: Array<{ - kind: 'exact'; - url: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'prefix'; - host: string; - pathPrefix: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'host'; - host: string; - }>; - aliases: Array; - metadata?: { - [key: string]: string; - }; - mentionNotApplicable: boolean; - }>; - changed: Array<{ - stableKey: string; - before: { - stableKey: string; - label: string; - urls: Array<{ - kind: 'exact'; - url: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'prefix'; - host: string; - pathPrefix: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'host'; - host: string; - }>; - aliases: Array; - metadata?: { - [key: string]: string; - }; - mentionNotApplicable: boolean; - }; - after: { - stableKey: string; - label: string; - urls: Array<{ - kind: 'exact'; - url: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'prefix'; - host: string; - pathPrefix: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'host'; - host: string; - }>; - aliases: Array; - metadata?: { - [key: string]: string; - }; - mentionNotApplicable: boolean; - }; - }>; + added: Array; + removed: Array; + changed: Array; unchanged: Array; }; groups: { - added: Array<{ - stableKey: string; - label: string; - targetKeys: Array; - competitors?: Array; - }>; - removed: Array<{ - stableKey: string; - label: string; - targetKeys: Array; - competitors?: Array; - }>; - changed: Array<{ - stableKey: string; - before: { - stableKey: string; - label: string; - targetKeys: Array; - competitors?: Array; - }; - after: { - stableKey: string; - label: string; - targetKeys: Array; - competitors?: Array; - }; - }>; + added: Array; + removed: Array; + changed: Array; unchanged: Array; }; - querySelections: { - added: Array<{ - targetKey: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - queryIds: Array; - }>; - removed: Array<{ - targetKey: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - queryIds: Array; - }>; - changed: Array<{ - targetKey: string; - before: { - targetKey: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - queryIds: Array; - }; - after: { - targetKey: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - queryIds: Array; - }; - }>; - unchanged: Array<{ - targetKey: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - }>; + assignments: { + added: number; + removed: number; + reclassified: number; }; execution: { addedNodeKeys: Array; removedNodeKeys: Array; - addedUsageEdges: Array<{ - kind: 'baseline'; - executionNodeKey: string; - queryId: string; - } | { - kind: 'target'; - executionNodeKey: string; - queryId: string; - targetKey: string; - }>; - removedUsageEdges: Array<{ - kind: 'baseline'; - executionNodeKey: string; - queryId: string; - } | { - kind: 'target'; - executionNodeKey: string; - queryId: string; - targetKey: string; - }>; - counts: { - before: { - targets: number; - groups: number; - queries: number; - executionNodes: number; - usageEdges: number; - baselineEdges: number; - targetEdges: number; - dedupSavings: number; - } | null; - after: { - targets: number; - groups: number; - queries: number; - executionNodes: number; - usageEdges: number; - baselineEdges: number; - targetEdges: number; - dedupSavings: number; - }; - delta: { - targets: number; - groups: number; - queries: number; - executionNodes: number; - usageEdges: number; - baselineEdges: number; - targetEdges: number; - dedupSavings: number; - } | null; - }; }; }; } | { ok: false; + compiledChecksum: null; checks: Array<{ - id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + ruleId: string; severity: 'fail' | 'warn'; message: string; path: Array; }>; - executionNodes: Array<{ - stableKey: string; - queryText: string; - context: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - expectedSnapshots: number; - }>; - dedupSaved: 0; - usageEdges: { - baseline: 0; - target: 0; - }; - estCostUsd: null; diff: null; }; -export type MeasurementPlanInput = { - schemaVersion: 1; - targets: Array<{ +export type MeasurementDraftDiscardResponse = { + discarded: boolean; +}; + +export type MeasurementDraftExcludeTargetRequest = { + targetKey: string; +}; + +export type MeasurementDraftGroupPage = { + items: Array<{ stableKey: string; label: string; - urls: Array<{ - kind: 'exact'; - url: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'prefix'; + targetKeys: Array; + competitors: Array<{ + stableKey: string; + label: string; + domain: string; + aliases: Array; + }>; + }>; + nextCursor: string | null; + totalEstimate?: number; +}; + +export type MeasurementDraftImportSitemapRequest = { + sitemapUrl: string; + rule: { + primary: { host: string; - pathPrefix: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'host'; + pathTemplate: string; + }; + aliases?: Array<{ host: string; + pathTemplate: string; }>; - aliases: Array; - metadata?: { - [key: string]: string; + excludedSlugSuffixes?: Array; + excludedSlugPatterns?: Array<{ + kind: 'exact' | 'prefix' | 'suffix' | 'contains'; + value: string; + }>; + }; + exclusions?: Array; +}; + +export type MeasurementDraftMergeTargetsRequest = { + targetKey: string; + mergedKeys: Array; +}; + +export type MeasurementDraftMutationResponse = { + etag: string; + changed: boolean; + warnings: Array<{ + code: string; + message: string; + path: Array; + }>; + counts: { + targets: number; + includedTargets: number; + assignments: number; + unclassifiedAssignments: number; + groups: number; + competitors: number; + }; +}; + +export type MeasurementDraftPublishRequest = { + expectedActiveRevision: number | null; + expectedCompiledChecksum: string; +}; + +export type MeasurementDraftRebindTargetRequest = { + targetKey: string; + discoveryIdentity: string; + discoveredUrl: string; +}; + +export type MeasurementDraftRemoveAssignmentRequest = { + targetKey: string; + queryId: string; +}; + +export type MeasurementDraftRemoveCompetitorRequest = { + groupKey: string; + competitorKey: string; +}; + +export type MeasurementDraftRemoveGroupRequest = { + groupKey: string; +}; + +export type MeasurementDraftRenameTargetRequest = { + targetKey: string; + label: string; +}; + +export type MeasurementDraftResponse = { + draft: { + id: string; + projectId: string; + schemaVersion: 2; + baseActiveVersionId: string | null; + baseActiveRevision: number | null; + authoring: { + defaultContext: { + providers: Array; + models?: { + [key: string]: string; + }; + locations: Array; + }; + targets: Array<{ + stableKey: string; + label: string; + status: 'proposed' | 'included' | 'excluded'; + aliases: Array; + urlMatchers: Array; + source: 'manual' | 'sitemap'; + discoveredUrl?: string; + discoveryIdentity?: string; + }>; + assignments: Array<{ + targetKey: string; + queryId: string; + contextOverride?: { + providers?: Array; + models?: { + [key: string]: string; + }; + locations?: Array; + }; + queryClass: 'branded' | 'non-brand' | 'unclassified'; + classificationSource: 'rule' | 'operator'; + }>; + groups: Array<{ + stableKey: string; + label: string; + targetKeys: Array; + competitors: Array<{ + stableKey: string; + label: string; + domain: string; + aliases: Array; + }>; + }>; + discovery?: { + sitemapUrl: string; + rule: { + primary: { + host: string; + pathTemplate: string; + }; + aliases?: Array<{ + host: string; + pathTemplate: string; + }>; + excludedSlugSuffixes?: Array; + excludedSlugPatterns?: Array<{ + kind: 'exact' | 'prefix' | 'suffix' | 'contains'; + value: string; + }>; + }; + exclusions: Array; + inputChecksum: string; + reviewedAt?: string; + }; + }; + createdBy: { + kind: 'user' | 'api-key' | 'system'; + id: string; + label: string; + }; + updatedBy: { + kind: 'user' | 'api-key' | 'system'; + id: string; + label: string; }; + createdAt: string; + updatedAt: string; + } | null; + etag: string | null; +}; + +export type MeasurementDraftTargetPage = { + items: Array<{ + stableKey: string; + label: string; + status: 'proposed' | 'included' | 'excluded'; + aliases: Array; + urlMatchers: Array; + source: 'manual' | 'sitemap'; + discoveredUrl?: string; + discoveryIdentity?: string; }>; - groups?: Array<{ + nextCursor: string | null; + totalEstimate?: number; +}; + +export type MeasurementDraftUpsertCompetitorRequest = { + groupKey: string; + competitor: { + stableKey: string; + label: string; + domain: string; + aliases: Array; + }; +}; + +export type MeasurementDraftUpsertGroupRequest = { + group: { stableKey: string; label: string; targetKeys: Array; - competitors?: Array; + }; +}; + +export type MeasurementDraftUpsertTargetRequest = { + target: { + stableKey: string; + label: string; + status: 'proposed' | 'included' | 'excluded'; + aliases: Array; + urlMatchers: Array; + source: 'manual' | 'sitemap'; + discoveredUrl?: string; + discoveryIdentity?: string; + }; +}; + +export type MeasurementOverviewResponse = { + mode: 'active-v1' | 'active-v2'; + scope: { + kind: 'all' | 'group' | 'property'; + key?: string; + label: string; + }; + queryClass: 'all' | 'branded' | 'non-brand'; + measurement: { + state: 'not_measured' | 'queued' | 'running' | 'complete' | 'partial' | 'failed'; + currentRunId?: string; + displayedRunId?: string; + completed: number; + expected: number; + completedAt?: string; + }; + nextAction: { + kind: 'run_measurement' | 'review_flags' | 'complete_setup' | 'republish_setup' | 'none'; + count?: number; + }; + metrics: { + propertiesMentioned: { + state: 'available'; + value: number; + numerator?: number; + denominator?: number; + } | { + state: 'unavailable'; + reason: 'no_completed_run' | 'plan_v1' | 'no_population' | 'evidence_incomplete' | 'not_applicable'; + }; + mentionCoverage: { + state: 'available'; + value: number; + numerator?: number; + denominator?: number; + } | { + state: 'unavailable'; + reason: 'no_completed_run' | 'plan_v1' | 'no_population' | 'evidence_incomplete' | 'not_applicable'; + }; + citationCoverage: { + state: 'available'; + value: number; + numerator?: number; + denominator?: number; + } | { + state: 'unavailable'; + reason: 'no_completed_run' | 'plan_v1' | 'no_population' | 'evidence_incomplete' | 'not_applicable'; + }; + brandPresence: { + state: 'available'; + value: number; + numerator?: number; + denominator?: number; + } | { + state: 'unavailable'; + reason: 'no_completed_run' | 'plan_v1' | 'no_population' | 'evidence_incomplete' | 'not_applicable'; + }; + /** + * Deprecated alias of brandPresence carrying the identical value. Remove once the browser migrates. + */ + sov: { + state: 'available'; + value: number; + numerator?: number; + denominator?: number; + } | { + state: 'unavailable'; + reason: 'no_completed_run' | 'plan_v1' | 'no_population' | 'evidence_incomplete' | 'not_applicable'; + }; + }; + properties: { + items: Array<{ + targetKey: string; + label: string; + mentionCoverage: { + state: 'available'; + value: number; + numerator?: number; + denominator?: number; + } | { + state: 'unavailable'; + reason: 'no_completed_run' | 'plan_v1' | 'no_population' | 'evidence_incomplete' | 'not_applicable'; + }; + citationCoverage: { + state: 'available'; + value: number; + numerator?: number; + denominator?: number; + } | { + state: 'unavailable'; + reason: 'no_completed_run' | 'plan_v1' | 'no_population' | 'evidence_incomplete' | 'not_applicable'; + }; + flags: number; + }>; + nextCursor: string | null; + totalEstimate?: number; + }; + flags: { + total: number; + }; + namedShareOfVoice?: { + groupKey: string; + queryClass: 'non-brand'; + denominator: number; + entries: Array<{ + kind: 'project' | 'competitor'; + stableKey: string; + label: string; + domain: string; + credits: number; + share: number; + }>; + }; +}; + +export type MeasurementPlanCompilePreviewResponse = { + ok: true; + checks: Array<{ + id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + severity: 'fail' | 'warn'; + message: string; + path: Array; }>; - targetQuerySelections?: Array<{ - targetKey: string; - queryIds: Array; - context?: { + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { label: string; city: string; region: string; country: string; timezone?: string; } | null; + expectedSnapshots: number; }>; -}; - -export type MeasurementPlanPublishRequest = { - expectedActiveRevision: number | null; + dedupSaved: number; + usageEdges: { + baseline: number; + target: number; + }; + estCostUsd: null; plan: { schemaVersion: 1; + defaultContext: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + effectiveOwnedHosts: Array; + projectCanonicalHost: string; + projectBrandNames: Array; targets: Array<{ stableKey: string; label: string; @@ -3473,14 +3557,15 @@ export type MeasurementPlanPublishRequest = { metadata?: { [key: string]: string; }; + mentionNotApplicable: boolean; }>; - groups?: Array<{ + groups: Array<{ stableKey: string; label: string; targetKeys: Array; competitors?: Array; }>; - targetQuerySelections?: Array<{ + targetQuerySelections: Array<{ targetKey: string; queryIds: Array; context?: { @@ -3491,35 +3576,230 @@ export type MeasurementPlanPublishRequest = { timezone?: string; } | null; }>; - }; -}; - -export type MeasurementPlanResponse = { - active: { - revision: number; - checksum: string; - createdAt: string; - plan: { - schemaVersion: 1; - defaultContext: { + querySnapshots: Array<{ + queryId: string; + queryText: string; + }>; + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { label: string; city: string; region: string; country: string; timezone?: string; } | null; - effectiveOwnedHosts: Array; - projectCanonicalHost: string; - projectBrandNames: Array; - targets: Array<{ - stableKey: string; - label: string; - urls: Array<{ - kind: 'exact'; - url: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'prefix'; + expectedSnapshots: number; + }>; + usageEdges: Array<{ + kind: 'baseline'; + executionNodeKey: string; + queryId: string; + } | { + kind: 'target'; + executionNodeKey: string; + queryId: string; + targetKey: string; + }>; + warnings: Array<{ + code: 'target-alias-prefix-overlap'; + message: string; + targetKeys: Array; + aliases: Array; + }>; + }; + warnings: Array<{ + code: 'target-alias-prefix-overlap'; + message: string; + targetKeys: Array; + aliases: Array; + }>; + counts: { + targets: number; + groups: number; + queries: number; + executionNodes: number; + usageEdges: number; + baselineEdges: number; + targetEdges: number; + dedupSavings: number; + }; +} | { + ok: false; + checks: Array<{ + id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + severity: 'fail' | 'warn'; + message: string; + path: Array; + }>; + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + expectedSnapshots: number; + }>; + dedupSaved: 0; + usageEdges: { + baseline: 0; + target: 0; + }; + estCostUsd: null; +}; + +export type MeasurementPlanDeactivateRequest = { + expectedActiveRevision: number; +}; + +export type MeasurementPlanDeactivateResponse = { + deactivated: boolean; + previousRevision: number | null; +}; + +export type MeasurementPlanDiffPreviewResponse = { + ok: true; + checks: Array<{ + id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + severity: 'fail' | 'warn'; + message: string; + path: Array; + }>; + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + expectedSnapshots: number; + }>; + dedupSaved: number; + usageEdges: { + baseline: number; + target: number; + }; + estCostUsd: null; + plan: { + schemaVersion: 1; + defaultContext: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + effectiveOwnedHosts: Array; + projectCanonicalHost: string; + projectBrandNames: Array; + targets: Array<{ + stableKey: string; + label: string; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'host'; + host: string; + }>; + aliases: Array; + metadata?: { + [key: string]: string; + }; + mentionNotApplicable: boolean; + }>; + groups: Array<{ + stableKey: string; + label: string; + targetKeys: Array; + competitors?: Array; + }>; + targetQuerySelections: Array<{ + targetKey: string; + queryIds: Array; + context?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + }>; + querySnapshots: Array<{ + queryId: string; + queryText: string; + }>; + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + expectedSnapshots: number; + }>; + usageEdges: Array<{ + kind: 'baseline'; + executionNodeKey: string; + queryId: string; + } | { + kind: 'target'; + executionNodeKey: string; + queryId: string; + targetKey: string; + }>; + warnings: Array<{ + code: 'target-alias-prefix-overlap'; + message: string; + targetKeys: Array; + aliases: Array; + }>; + }; + warnings: Array<{ + code: 'target-alias-prefix-overlap'; + message: string; + targetKeys: Array; + aliases: Array; + }>; + counts: { + targets: number; + groups: number; + queries: number; + executionNodes: number; + usageEdges: number; + baselineEdges: number; + targetEdges: number; + dedupSavings: number; + }; + diff: { + activeRevision: number | null; + targets: { + added: Array<{ + stableKey: string; + label: string; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; host: string; pathPrefix: string; pathCase: 'sensitive' | 'insensitive'; @@ -3533,123 +3813,157 @@ export type MeasurementPlanResponse = { }; mentionNotApplicable: boolean; }>; - groups: Array<{ + removed: Array<{ stableKey: string; label: string; - targetKeys: Array; - competitors?: Array; - }>; - targetQuerySelections: Array<{ - targetKey: string; - queryIds: Array; - context?: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - }>; - querySnapshots: Array<{ - queryId: string; - queryText: string; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'host'; + host: string; + }>; + aliases: Array; + metadata?: { + [key: string]: string; + }; + mentionNotApplicable: boolean; }>; - executionNodes: Array<{ + changed: Array<{ stableKey: string; - queryText: string; - context: { + before: { + stableKey: string; label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - expectedSnapshots: number; - }>; - usageEdges: Array<{ - kind: 'baseline'; - executionNodeKey: string; - queryId: string; - } | { - kind: 'target'; - executionNodeKey: string; - queryId: string; - targetKey: string; - }>; - warnings: Array<{ - code: 'target-alias-prefix-overlap'; - message: string; - targetKeys: Array; - aliases: Array; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'host'; + host: string; + }>; + aliases: Array; + metadata?: { + [key: string]: string; + }; + mentionNotApplicable: boolean; + }; + after: { + stableKey: string; + label: string; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'host'; + host: string; + }>; + aliases: Array; + metadata?: { + [key: string]: string; + }; + mentionNotApplicable: boolean; + }; }>; + unchanged: Array; }; - } | null; -}; - -export type MeasurementPlanVersionResponse = { - version: { - revision: number; - checksum: string; - createdAt: string; - active: boolean; - plan: { - schemaVersion: 1; - defaultContext: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - effectiveOwnedHosts: Array; - projectCanonicalHost: string; - projectBrandNames: Array; - targets: Array<{ + groups: { + added: Array<{ stableKey: string; label: string; - urls: Array<{ - kind: 'exact'; - url: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'prefix'; - host: string; - pathPrefix: string; - pathCase: 'sensitive' | 'insensitive'; - } | { - kind: 'host'; - host: string; - }>; - aliases: Array; - metadata?: { - [key: string]: string; - }; - mentionNotApplicable: boolean; + targetKeys: Array; + competitors?: Array; }>; - groups: Array<{ + removed: Array<{ stableKey: string; label: string; targetKeys: Array; competitors?: Array; }>; - targetQuerySelections: Array<{ + changed: Array<{ + stableKey: string; + before: { + stableKey: string; + label: string; + targetKeys: Array; + competitors?: Array; + }; + after: { + stableKey: string; + label: string; + targetKeys: Array; + competitors?: Array; + }; + }>; + unchanged: Array; + }; + querySelections: { + added: Array<{ targetKey: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; queryIds: Array; - context?: { + }>; + removed: Array<{ + targetKey: string; + context: { label: string; city: string; region: string; country: string; timezone?: string; } | null; + queryIds: Array; }>; - querySnapshots: Array<{ - queryId: string; - queryText: string; + changed: Array<{ + targetKey: string; + before: { + targetKey: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + queryIds: Array; + }; + after: { + targetKey: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + queryIds: Array; + }; }>; - executionNodes: Array<{ - stableKey: string; - queryText: string; + unchanged: Array<{ + targetKey: string; context: { label: string; city: string; @@ -3657,9 +3971,12 @@ export type MeasurementPlanVersionResponse = { country: string; timezone?: string; } | null; - expectedSnapshots: number; }>; - usageEdges: Array<{ + }; + execution: { + addedNodeKeys: Array; + removedNodeKeys: Array; + addedUsageEdges: Array<{ kind: 'baseline'; executionNodeKey: string; queryId: string; @@ -3669,2759 +3986,4916 @@ export type MeasurementPlanVersionResponse = { queryId: string; targetKey: string; }>; - warnings: Array<{ - code: 'target-alias-prefix-overlap'; - message: string; - targetKeys: Array; - aliases: Array; + removedUsageEdges: Array<{ + kind: 'baseline'; + executionNodeKey: string; + queryId: string; + } | { + kind: 'target'; + executionNodeKey: string; + queryId: string; + targetKey: string; }>; + counts: { + before: { + targets: number; + groups: number; + queries: number; + executionNodes: number; + usageEdges: number; + baselineEdges: number; + targetEdges: number; + dedupSavings: number; + } | null; + after: { + targets: number; + groups: number; + queries: number; + executionNodes: number; + usageEdges: number; + baselineEdges: number; + targetEdges: number; + dedupSavings: number; + }; + delta: { + targets: number; + groups: number; + queries: number; + executionNodes: number; + usageEdges: number; + baselineEdges: number; + targetEdges: number; + dedupSavings: number; + } | null; + }; }; }; -}; - -export type MeasurementPlanVersionsResponse = { - versions: Array<{ - revision: number; - checksum: string; - createdAt: string; - active: boolean; +} | { + ok: false; + checks: Array<{ + id: 'invalid-authoring' | 'duplicate-identity' | 'unknown-target' | 'unknown-query' | 'invalid-project-context' | 'unowned-target-url' | 'owned-competitor' | 'target-query-context-conflict' | 'target-url-ownership-tie' | 'target-alias-cross-target-collision' | 'target-alias-project-brand-collision' | 'target-alias-prefix-overlap'; + severity: 'fail' | 'warn'; + message: string; + path: Array; + }>; + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + expectedSnapshots: number; }>; + dedupSaved: 0; + usageEdges: { + baseline: 0; + target: 0; + }; + estCostUsd: null; + diff: null; }; -export type MeasurementReportResponse = { - revision: number; - run: { - id: string; - status: 'completed' | 'partial'; - createdAt: string; - startedAt: string | null; - finishedAt: string | null; - } | null; - groups: Array<{ - id: string; +export type MeasurementPlanInput = { + schemaVersion: 1; + targets: Array<{ + stableKey: string; label: string; - targetIds: Array; - completeness: { - executed: number; - expected: number; - complete: boolean; - sourceComplete: boolean; - sourceCompleteObservations: number; - answerComplete: boolean; - }; - answerCoverage: { - numerator: number; - denominator: number; - rate: number; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; } | { - numerator: null; - denominator: null; - rate: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }; - targetCoverage: { - numerator: number; - denominator: number; - rate: number; + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; } | { - numerator: null; - denominator: null; - rate: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }; - sov: { - domains: Array<{ - domain: string; - own: boolean; - presentIn: number; - of: number; - } | { - domain: string; - own: boolean; - presentIn: null; - of: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }>; - providers: Array<{ - provider: string; - domains: Array<{ - domain: string; - own: boolean; - presentIn: number; - of: number; - } | { - domain: string; - own: boolean; - presentIn: null; - of: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }>; - }>; - }; - providers: Array<{ - provider: string; - completeness: { - executed: number; - expected: number; - complete: boolean; - sourceComplete: boolean; - sourceCompleteObservations: number; - answerComplete: boolean; - }; - answerCoverage: { - numerator: number; - denominator: number; - rate: number; - } | { - numerator: null; - denominator: null; - rate: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }; + kind: 'host'; + host: string; }>; + aliases: Array; + metadata?: { + [key: string]: string; + }; }>; - targets: Array<{ - id: string; + groups?: Array<{ + stableKey: string; label: string; - completeness: { - executed: number; - expected: number; - complete: boolean; - sourceComplete: boolean; - sourceCompleteObservations: number; - answerComplete: boolean; - }; - citationCoverage: { - numerator: number; - denominator: number; - rate: number; - } | { - numerator: null; - denominator: null; - rate: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }; - mentionCoverage: { - numerator: number; - denominator: number; - rate: number; - } | { - numerator: null; - denominator: null; - rate: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }; - providers: Array<{ - provider: string; - completeness: { - executed: number; - expected: number; - complete: boolean; - sourceComplete: boolean; - sourceCompleteObservations: number; - answerComplete: boolean; - }; - citationCoverage: { - numerator: number; - denominator: number; - rate: number; + targetKeys: Array; + competitors?: Array; + }>; + targetQuerySelections?: Array<{ + targetKey: string; + queryIds: Array; + context?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + }>; +}; + +export type MeasurementPlanPublishRequest = { + expectedActiveRevision: number | null; + plan: { + schemaVersion: 1; + targets: Array<{ + stableKey: string; + label: string; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; } | { - numerator: null; - denominator: null; - rate: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; - }; - mentionCoverage: { - numerator: number; - denominator: number; - rate: number; + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; } | { - numerator: null; - denominator: null; - rate: null; - reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; + kind: 'host'; + host: string; + }>; + aliases: Array; + metadata?: { + [key: string]: string; }; }>; - }>; - evidence: Array<{ - observationId: string; - expectedSlotId: string; - executionId: string; - usageEdgeId: string; - usageEdgeType: 'baseline' | 'target'; - provider: string; - queryText: string; - location: string | null; - sourceUrl: string; - bridged: boolean; - historical: boolean; - evidenceComplete: boolean; - classification: 'assigned' | 'sibling' | 'ownedUnmapped' | 'external' | 'ambiguous' | 'invalid'; - normalizedUrl: string | null; - matchedTargetIds: Array; - matchedUrlIds: Array; - }>; - diagnostics: { - bridgedObservationIds: Array; - historicalObservationIds: Array; - evidenceIncompleteObservationIds: Array; - ambiguousObservationIds: Array; - unmatchedObservationIds: Array; + groups?: Array<{ + stableKey: string; + label: string; + targetKeys: Array; + competitors?: Array; + }>; + targetQuerySelections?: Array<{ + targetKey: string; + queryIds: Array; + context?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + }>; }; }; -export type MeasurementSegmentRetirementResponse = { - stableKey: string; - retiredAt: string; -}; - -export type NotificationDto = { - id: string; - projectId: string; - channel: 'webhook'; - url: string; - urlDisplay: string; - urlHost: string; - events: Array<'citation.lost' | 'citation.gained' | 'run.completed' | 'run.failed' | 'insight.critical' | 'insight.high' | 'health.degraded' | 'health.recovered'>; - enabled: boolean; - source?: string; - webhookSecret?: string; - createdAt: string; - updatedAt: string; -}; - -export type OnboardingTelemetryEvent = { - eventId: string; - flowVersion: 1; - onboardingSessionId: string; - event: 'onboarding.started'; - step: 'system' | 'project' | 'queries' | 'competitors' | 'run'; - resumed: boolean; -} | { - eventId: string; - flowVersion: 1; - onboardingSessionId: string; - event: 'onboarding.step_completed'; - step: 'system' | 'project' | 'queries' | 'competitors' | 'run'; - method: 'existing' | 'inline' | 'manual' | 'generated' | 'skipped' | 'automatic'; - countBucket?: '0' | '1' | '2-3' | '4-5' | '6-10' | '11+'; -} | { - eventId: string; - flowVersion: 1; - onboardingSessionId: string; - event: 'onboarding.blocked'; - step: 'system' | 'project' | 'queries' | 'competitors' | 'run'; - action: 'continue' | 'configure_provider' | 'generate_queries' | 'save' | 'launch_run' | 'retry_run'; - reasonCode: 'api_unavailable' | 'database_unavailable' | 'worker_unavailable' | 'no_provider' | 'no_queries' | 'provider_save_failed' | 'project_create_failed' | 'query_save_failed' | 'run_rejected' | 'run_failed' | 'run_cancelled' | 'unknown'; -} | { - eventId: string; - flowVersion: 1; - onboardingSessionId: string; - event: 'run.requested'; - origin: 'dashboard_setup'; - result: 'queued' | 'rejected'; - providerCountBucket: '0' | '1' | '2-3' | '4-5' | '6-10' | '11+'; - queryCountBucket: '0' | '1' | '2-3' | '4-5' | '6-10' | '11+'; - reasonCode?: 'api_unavailable' | 'database_unavailable' | 'worker_unavailable' | 'no_provider' | 'no_queries' | 'provider_save_failed' | 'project_create_failed' | 'query_save_failed' | 'run_rejected' | 'run_failed' | 'run_cancelled' | 'unknown'; -}; - -export type OrganicEvidenceDto = { - contractVersion: 'organic-evidence/v1'; - periodDays: 60 | 90; - asOfDate: string | null; - coverage: { - gsc: boolean; - ga4: boolean; - server: boolean; - visibility: boolean; - }; - sourceCoverage: { - gsc: { - startDate: string; - endDate: string; - observedDays: number; - } | null; - ga4: { - startDate: string; - endDate: string; - observedDays: number; - } | null; - server: { - startDate: string; - endDate: string; - observedDays: number; - } | null; - visibility: { - completedAt: string; - ageDays: number; - } | null; - }; - gsc: { - propertyTotals: { - clicks: number; - impressions: number; - }; - namedBrand: { - clicks: number; - impressions: number; - }; - namedNonBrand: { - clicks: number; - impressions: number; - }; - suppressedOrUnreportedResidual: { - clicks: number; - impressions: number; - }; - cohorts: Array<{ - name: 'earliest' | 'middle' | 'prior' | 'latest'; - startDate: string; - endDate: string; - totals: { - clicks: number; - impressions: number; - }; - }>; - } | null; - ga4: { - organicSessions: number; - cohorts: Array<{ - name: 'earliest' | 'middle' | 'prior' | 'latest'; - startDate: string; - endDate: string; - organicSessions: number; - }>; - } | null; - gaAiReferrals: { - paidSessions: number; - organicSessions: number; - } | null; - server: { - crawlerHits: { - verified: number; - claimedUnverified: number; - unknownAiLike: number; - }; - userFetchHits: { - verified: number; - claimedUnverified: number; - unknownAiLike: number; - }; - referralSessions: { - total: number; - paid: number; - organic: number; - unknown: number; - }; - } | null; - visibility: { - runId: string; - completedAt: string; - ageDays: number; - answerPairs: number; - mentionedPairs: number; - citedPairs: number; - } | null; - measurement: { - window: '30d' | '60d' | '90d'; - bucketDays: 30; - filters: { - hostScope: 'marketing' | 'all'; - marketingHosts: Array; - pathPrefix: string | null; - brandTerms: Array; - queryMixScope: 'property'; - }; - acquisition: { - status: 'never-synced' | 'ready' | 'error'; - error: string | null; - syncedAt: string | null; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - sessions: number; - }>; - channels: Array<{ - channelGroup: string; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - sessions: number; +export type MeasurementPlanResponse = { + active: { + revision: number; + checksum: string; + createdAt: string; + plan: { + schemaVersion: 1; + defaultContext: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + effectiveOwnedHosts: Array; + projectCanonicalHost: string; + projectBrandNames: Array; + targets: Array<{ + stableKey: string; + label: string; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'host'; + host: string; }>; + aliases: Array; + metadata?: { + [key: string]: string; + }; + mentionNotApplicable: boolean; }>; - pages: Array<{ - hostName: string; - landingPage: string; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - sessions: number; - }>; + groups: Array<{ + stableKey: string; + label: string; + targetKeys: Array; + competitors?: Array; }>; - }; - leads: { - status: 'never-synced' | 'ready' | 'error'; - error: string | null; - syncedAt: string | null; - attributionScope: 'landing-page' | 'channel'; - hostAndPathFiltersApplied: boolean; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - eventCount: number; + targetQuerySelections: Array<{ + targetKey: string; + queryIds: Array; + context?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; }>; - channels: Array<{ - channelGroup: string; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - eventCount: number; - }>; + querySnapshots: Array<{ + queryId: string; + queryText: string; }>; - }; - engagement: { - status: 'ready' | 'unavailable'; - availableFromDate: string | null; - latestDate: string | null; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - sessions: number; - engagementRate: number | null; - dailyTotalUsers: number | null; - dailyNewUsers: number | null; - metricsAvailable: boolean; - daysInPeriod: number; - daysWithEngagementRate: number; - daysWithUserSplit: number; + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + expectedSnapshots: number; }>; - }; - searchDemand: { - status: 'ready' | 'unavailable'; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - propertyClicks: number; - propertyImpressions: number; - reportedQueryClicks: number; - reportedQueryImpressions: number; - brandedClicks: number; - brandedImpressions: number; - nonBrandedClicks: number; - nonBrandedImpressions: number; - unreportedClicks: number; - unreportedImpressions: number; + usageEdges: Array<{ + kind: 'baseline'; + executionNodeKey: string; + queryId: string; + } | { + kind: 'target'; + executionNodeKey: string; + queryId: string; + targetKey: string; }>; - queries: Array<{ - query: string; - classification: 'branded' | 'non-branded'; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - clicks: number; - impressions: number; + warnings: Array<{ + code: 'target-alias-prefix-overlap'; + message: string; + targetKeys: Array; + aliases: Array; + }>; + }; + } | null; +}; + +export type MeasurementPlanV2PublishResponse = { + published: boolean; + active: { + revision: number; + checksum: string; + compiledChecksum: string; + createdAt: string; + plan: { + schemaVersion: 2; + identities: { + projectBrand: { + canonicalHost: string; + ownedHosts: Array; + names: Array; + }; + }; + targets: Array<{ + stableKey: string; + label: string; + aliases: Array; + urlMatchers: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'host'; + host: string; }>; + mentionNotApplicable: boolean; + discoveryIdentity: string | null; }>; - pages: Array<{ - hostName: string; - landingPage: string; - periods: Array<{ - label: 'earliest' | 'middle' | 'previous' | 'latest'; - startDate: string; - endDate: string; - clicks: number; - impressions: number; + groups: Array<{ + stableKey: string; + label: string; + targetKeys: Array; + competitors: Array<{ + stableKey: string; + label: string; + domain: string; + aliases: Array; }>; }>; - latestDate: string | null; - }; - }; - pages: Array<{ - path: string; - gsc: { - clicks: number; - impressions: number; - }; - ga4OrganicSessions: number; - server: { - crawlerHits: { - verified: number; - claimedUnverified: number; - unknownAiLike: number; - }; - userFetchHits: { - verified: number; - claimedUnverified: number; - unknownAiLike: number; - }; - referralSessions: { - total: number; - paid: number; - organic: number; - unknown: number; - }; + querySnapshots: Array<{ + queryId: string; + queryText: string; + provenance: { + source: 'manual' | 'query-set' | 'template' | 'discovery'; + sourceId: string | null; + capturedAt: string; + }; + }>; + assignments: Array<{ + targetKey: string; + queryId: string; + queryClass: 'branded' | 'non-brand'; + executionNodeKey: string; + }>; + executionNodes: Array<{ + stableKey: string; + queryId: string; + queryText: string; + context: { + providers: Array; + models: { + [key: string]: string; + }; + location: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + }; + expectedSnapshots: number; + }>; + usageEdges: Array<{ + executionNodeKey: string; + targetKey: string; + queryId: string; + }>; + compiledChecksum: string; }; - }>; - findings: Array<{ - tone: 'positive' | 'caution' | 'neutral'; - title: string; - detail: string; - }>; - limitations: Array<{ - code: string; - detail: string; - }>; -}; - -export type ProjectDto = { - id: string; - name: string; - displayName?: string; - canonicalDomain: string; - ownedDomains: Array; - aliases: Array; - country: string; - language: string; - tags: Array; - labels: { - [key: string]: string; - }; - providers: Array; - providerModels: { - [key: string]: string; - }; - locations: Array<{ - label: string; - city: string; - region: string; - country: string; - timezone?: string; - }>; - defaultLocation?: string | null; - measurement: { - marketingHosts: Array; - brandTerms: Array; - leadEventNames: Array; - }; - autoExtractBacklinks: boolean; - configSource: 'cli' | 'api' | 'config-file'; - configRevision: number; - createdAt?: string; - updatedAt?: string; -}; - -export type ProjectUpsertRequest = { - displayName: string; - canonicalDomain: string; - ownedDomains?: Array; - aliases?: Array; - country: string; - language: string; - tags?: Array; - labels?: { - [key: string]: string; - }; - providers?: Array; - providerModels?: { - [key: string]: string; - }; - locations?: Array<{ - label: string; - city: string; - region: string; - country: string; - timezone?: string; - }>; - defaultLocation?: string | null; - measurement?: { - marketingHosts: Array; - brandTerms: Array; - leadEventNames: Array; }; - autoExtractBacklinks?: boolean; - configSource?: 'cli' | 'api' | 'config-file'; }; -export type ProjectConfig = { - apiVersion: 'canonry/v1'; - kind: 'Project'; - metadata: { - name: string; - labels: { - [key: string]: string; - }; - }; - spec: { - displayName: string; - canonicalDomain: string; - ownedDomains: Array; - aliases: Array; - country: string; - language: string; - queries?: Array; - keywords?: Array; - competitors: Array; - providers: Array; - providerModels: { - [key: string]: string; - }; - locations: Array<{ - label: string; - city: string; - region: string; - country: string; - timezone?: string; - }>; - defaultLocation?: string; - measurement: { - marketingHosts: Array; - brandTerms: Array; - leadEventNames: Array; - }; - schedule?: { - preset?: string; - cron?: string; - timezone: string; - providers: Array; - enabled?: boolean; - }; - notifications: Array<{ - channel: 'webhook'; - url: string; - events: Array<'citation.lost' | 'citation.gained' | 'run.completed' | 'run.failed' | 'insight.critical' | 'insight.high' | 'health.degraded' | 'health.recovered'>; - }>; - google?: { - gsc?: { - propertyUrl: string; - }; - syncSchedule?: { - preset?: string; - cron?: string; - }; - }; - autoExtractBacklinks: boolean; - }; -}; - -export type ProjectOverviewDto = { - project: { - id: string; - name: string; - displayName?: string; - canonicalDomain: string; - ownedDomains: Array; - aliases: Array; - country: string; - language: string; - tags: Array; - labels: { - [key: string]: string; - }; - providers: Array; - providerModels: { - [key: string]: string; - }; - locations: Array<{ - label: string; - city: string; - region: string; - country: string; - timezone?: string; - }>; - defaultLocation?: string | null; - measurement: { - marketingHosts: Array; - brandTerms: Array; - leadEventNames: Array; - }; - autoExtractBacklinks: boolean; - configSource: 'cli' | 'api' | 'config-file'; - configRevision: number; - createdAt?: string; - updatedAt?: string; - }; - latestRun: { - totalRuns: number; - run: { - id: string; - projectId: string; - kind: 'answer-visibility' | 'site-audit' | 'gsc-sync' | 'inspect-sitemap' | 'ga-sync' | 'bing-inspect' | 'bing-inspect-sitemap' | 'backlink-extract' | 'traffic-sync' | 'aeo-discover-seed' | 'aeo-discover-probe' | 'gbp-sync' | 'ads-sync'; - status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - trigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; - measurementPlanVersionId?: string | null; - measurementManifest?: { - [key: string]: unknown; - } | null; - measurementScope?: { - groups: Array; - targets: Array; - queries: Array; - resolvedTargets: Array; +export type MeasurementPlanVersionResponse = { + version: { + revision: number; + checksum: string; + createdAt: string; + active: boolean; + plan: { + schemaVersion: 1; + defaultContext: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; } | null; - measurementExecutionIdentity?: { - schemaVersion: 1; - providers: Array; - models: { + effectiveOwnedHosts: Array; + projectCanonicalHost: string; + projectBrandNames: Array; + targets: Array<{ + stableKey: string; + label: string; + urls: Array<{ + kind: 'exact'; + url: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'prefix'; + host: string; + pathPrefix: string; + pathCase: 'sensitive' | 'insensitive'; + } | { + kind: 'host'; + host: string; + }>; + aliases: Array; + metadata?: { [key: string]: string; }; - checksum: string; - } | null; - location?: string | null; - queries?: Array | null; - startedAt?: string | null; - finishedAt?: string | null; - error?: { - message?: string; - providers?: { - [key: string]: { - message: string; - raw?: unknown; - }; - }; - } | null; - queryBasketRevision?: number | null; - createdAt: string; - snapshots?: Array<{ - id: string; - runId: string; - queryId: string; - query?: string; - provider: string; - citationState: 'cited' | 'not-cited'; - answerMentioned?: boolean; - visibilityState?: 'visible' | 'not-visible'; - mentionState?: 'mentioned' | 'not-mentioned'; - transition?: 'new' | 'cited' | 'lost' | 'emerging' | 'not-cited'; - answerText?: string | null; - citedDomains: Array; - citedUrls: Array | null; - captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; - sourceCount: number | null; - resolvedCount: number | null; - captureVersion: number | null; - retrievalStatus: 'used' | 'not-used' | 'unknown' | 'not-applicable' | null; - retrievalContract: 'native-auto-v1' | 'search-required-v1' | null; - competitorOverlap: Array; - recommendedCompetitors: Array; - matchedTerms: Array; - groundingSources: Array<{ - uri: string; - title: string; - }>; - searchQueries: Array; - model?: string | null; - servedModel?: string | null; - location?: string | null; - requestedContext?: { + mentionNotApplicable: boolean; + }>; + groups: Array<{ + stableKey: string; + label: string; + targetKeys: Array; + competitors?: Array; + }>; + targetQuerySelections: Array<{ + targetKey: string; + queryIds: Array; + context?: { label: string; city: string; region: string; country: string; timezone?: string; } | null; - supportedContext?: { - status: 'applied' | 'ignored' | 'browser-implicit' | 'unknown'; - resolved?: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; + }>; + querySnapshots: Array<{ + queryId: string; + queryText: string; + }>; + executionNodes: Array<{ + stableKey: string; + queryText: string; + context: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; } | null; - createdAt: string; + expectedSnapshots: number; }>; - } | null; + usageEdges: Array<{ + kind: 'baseline'; + executionNodeKey: string; + queryId: string; + } | { + kind: 'target'; + executionNodeKey: string; + queryId: string; + targetKey: string; + }>; + warnings: Array<{ + code: 'target-alias-prefix-overlap'; + message: string; + targetKeys: Array; + aliases: Array; + }>; + }; }; - health: { - id: string; - projectId: string; - runId: string | null; - overallCitedRate: number; - overallMentionRate: number; - totalPairs: number; - citedPairs: number; - mentionedPairs: number; - providerBreakdown: { - [key: string]: { - citedRate: number; - mentionRate: number; - cited: number; - mentioned: number; - total: number; - }; - }; +}; + +export type MeasurementPlanVersionsResponse = { + versions: Array<{ + revision: number; + checksum: string; createdAt: string; - status: 'ready' | 'no-data'; - reason?: 'no-runs-yet'; - } | null; - topInsights: Array<{ + active: boolean; + }>; +}; + +export type MeasurementQuerySetDetail = { + id: string; + projectId: string; + name: string; + description: string | null; + itemCount: number; + createdAt: string; + updatedAt: string; + items: Array<{ + queryId: string; + queryText: string; + position: number; + }>; +}; + +export type MeasurementQuerySetListResponse = { + querySets: Array<{ id: string; projectId: string; - runId: string | null; - type: 'regression' | 'gain' | 'opportunity' | 'first-citation' | 'provider-pickup' | 'persistent-gap' | 'competitor-gained' | 'competitor-lost' | 'gbp-lodging-gap' | 'gbp-listing-discrepancy' | 'gbp-cta-gap' | 'gbp-description-missing' | 'gbp-metric-drop' | 'gbp-keyword-drop'; - severity: 'critical' | 'high' | 'medium' | 'low'; - title: string; - query: string; - provider: string; - recommendation?: { - action: string; - target?: string; - reason: string; - }; - cause?: { - cause: string; - competitorDomain?: string; - details?: string; - }; - dismissed: boolean; + name: string; + description: string | null; + itemCount: number; createdAt: string; + updatedAt: string; }>; - queryCounts: { - totalQueries: number; - citedQueries: number; - notCitedQueries: number; - citedRate: number; - mentionedQueries: number; - notMentionedQueries: number; - mentionRate: number; - }; - providers: Array<{ - provider: string; - citedRate: number; - cited: number; - total: number; +}; + +export type MeasurementQuerySetUpsertRequest = { + name: string; + description?: string | null; + queryIds: Array; +}; + +export type MeasurementQueryTemplateApplyRequest = { + bindings: Array<{ + [key: string]: string; }>; - transitions: { - since: string | null; - gained: number; - lost: number; - emerging: number; - }; - scores: { - mention: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; + querySetId?: string; +}; + +export type MeasurementQueryTemplateApplyResponse = { + created: Array<{ + queryId: string; + queryText: string; + }>; + existing: Array<{ + queryId: string; + queryText: string; + }>; +}; + +export type MeasurementQueryTemplateDto = { + id: string; + projectId: string; + name: string; + description: string | null; + pattern: string; + variables: Array; + createdAt: string; + updatedAt: string; +}; + +export type MeasurementQueryTemplateListResponse = { + templates: Array<{ + id: string; + projectId: string; + name: string; + description: string | null; + pattern: string; + variables: Array; + createdAt: string; + updatedAt: string; + }>; +}; + +export type MeasurementQueryTemplateUpsertRequest = { + name: string; + description?: string | null; + pattern: string; + variables: Array; +}; + +export type MeasurementReportResponse = { + revision: number; + run: { + id: string; + status: 'completed' | 'partial'; + createdAt: string; + startedAt: string | null; + finishedAt: string | null; + } | null; + groups: Array<{ + id: string; + label: string; + targetIds: Array; + completeness: { + executed: number; + expected: number; + complete: boolean; + sourceComplete: boolean; + sourceCompleteObservations: number; + answerComplete: boolean; }; - visibility: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; + answerCoverage: { + numerator: number; + denominator: number; + rate: number; + } | { + numerator: null; + denominator: null; + rate: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; }; - mentionShare: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; - breakdown: { - projectMentionSnapshots: number; - competitorMentionSnapshots: number; - perCompetitor: Array<{ + targetCoverage: { + numerator: number; + denominator: number; + rate: number; + } | { + numerator: null; + denominator: null; + rate: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; + }; + sov: { + domains: Array<{ + domain: string; + own: boolean; + presentIn: number; + of: number; + } | { + domain: string; + own: boolean; + presentIn: null; + of: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; + }>; + providers: Array<{ + provider: string; + domains: Array<{ domain: string; - mentionSnapshots: number; - shareOfCompetitiveTotal: number; + own: boolean; + presentIn: number; + of: number; + } | { + domain: string; + own: boolean; + presentIn: null; + of: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; }>; - snapshotsWithAnswerText: number; - snapshotsTotal: number; - }; + }>; }; - gapQueries: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; + providers: Array<{ + provider: string; + completeness: { + executed: number; + expected: number; + complete: boolean; + sourceComplete: boolean; + sourceCompleteObservations: number; + answerComplete: boolean; + }; + answerCoverage: { + numerator: number; + denominator: number; + rate: number; + } | { + numerator: null; + denominator: null; + rate: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; + }; + }>; + }>; + targets: Array<{ + id: string; + label: string; + completeness: { + executed: number; + expected: number; + complete: boolean; + sourceComplete: boolean; + sourceCompleteObservations: number; + answerComplete: boolean; }; - mentionGaps: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; - }; - indexCoverage: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; - }; - competitorPressure: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; + citationCoverage: { + numerator: number; + denominator: number; + rate: number; + } | { + numerator: null; + denominator: null; + rate: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; }; - runStatus: { - label: string; - value: string; - delta: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - description: string; - tooltip?: string; - trend: Array; - progress?: number; - providerCoverage?: string; + mentionCoverage: { + numerator: number; + denominator: number; + rate: number; + } | { + numerator: null; + denominator: null; + rate: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; }; - }; - movementSummary: { - gained: number; - lost: number; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - hasPreviousRun: boolean; - gainedQueries?: Array; - lostQueries?: Array; - }; - citationMovement: { - gained: number; - lost: number; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - hasPreviousRun: boolean; - gainedQueries?: Array; - lostQueries?: Array; - }; - mentionMovement: { - gained: number; - lost: number; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - hasPreviousRun: boolean; - gainedQueries?: Array; - lostQueries?: Array; - }; - movementComparison: { - hasPreviousRun: boolean; - comparable: boolean; - querySetChanged: boolean; - previousRunAt: string | null; - currentQueryCount: number; - previousQueryCount: number; - comparableQueryCount: number; - addedQueryCount: number; - removedQueryCount: number; - addedQueries: Array; - removedQueries: Array; - }; - competitors: Array<{ - id: string; - domain: string; - citationCount: number; - totalQueries: number; - pressureLabel: 'None' | 'Low' | 'Moderate' | 'High'; - citedQueries: Array; + providers: Array<{ + provider: string; + completeness: { + executed: number; + expected: number; + complete: boolean; + sourceComplete: boolean; + sourceCompleteObservations: number; + answerComplete: boolean; + }; + citationCoverage: { + numerator: number; + denominator: number; + rate: number; + } | { + numerator: null; + denominator: null; + rate: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; + }; + mentionCoverage: { + numerator: number; + denominator: number; + rate: number; + } | { + numerator: null; + denominator: null; + rate: null; + reason: 'incomplete' | 'evidence-incomplete' | 'no-population' | 'aliasless' | 'no-competitors' | 'no-project-aliases'; + }; + }>; }>; - providerScores: Array<{ + evidence: Array<{ + observationId: string; + expectedSlotId: string; + executionId: string; + usageEdgeId: string; + usageEdgeType: 'baseline' | 'target'; provider: string; - model: string | null; - score: number; - cited: number; - total: number; - trend?: Array; - }>; - attentionItems: Array<{ - id: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - title: string; - detail: string; - actionLabel: string; - href: string; - }>; - runHistory: Array<{ - runId: string; - createdAt: string; - citedCount: number; - totalCount: number; - citationRate: number; - mentionedCount: number; - mentionRate: number; - status: string; + queryText: string; + location: string | null; + sourceUrl: string; + bridged: boolean; + historical: boolean; + evidenceComplete: boolean; + classification: 'assigned' | 'sibling' | 'ownedUnmapped' | 'external' | 'ambiguous' | 'invalid'; + normalizedUrl: string | null; + matchedTargetIds: Array; + matchedUrlIds: Array; }>; - suggestedQueries: { - rows: Array<{ - query: string; - impressions: number; - clicks: number; - avgPosition: number; - reason: string; - }>; - totalCandidates: number; - skippedAlreadyTracked: number; + diagnostics: { + bridgedObservationIds: Array; + historicalObservationIds: Array; + evidenceIncompleteObservationIds: Array; + ambiguousObservationIds: Array; + unmatchedObservationIds: Array; }; - dateRangeLabel: string; - contextLabel: string; }; -export type ProjectReportDto = { - meta: { - generatedAt: string; - project: { - id: string; - name: string; - displayName: string; - canonicalDomain: string; - country: string; - language: string; - }; - location: { - label: string; - city: string; - region: string; - country: string; - otherConfiguredLabels: Array; - } | null; - providerLocationHandling: Array<{ - provider: string; - treatment: 'prompt' | 'request-param' | 'browser-geo' | 'ignored'; - description: string; - }>; - periodStart: string | null; - periodEnd: string | null; - periodDays: number; - }; - executiveSummary: { - citationRate: number; - citedQueryCount: number; - totalQueryCount: number; - mentionRate: number; - mentionedQueryCount: number; - trend: 'up' | 'down' | 'flat' | 'unknown'; - queryCount: number; - competitorCount: number; - providerCount: number; - gsc: { - clicks: number; - impressions: number; - ctr: number; - avgPosition: number; - periodStart: string; - periodEnd: string; - } | null; - ga: { - sessions: number; - users: number; - periodStart: string; - periodEnd: string; - } | null; - findings: Array<{ - title: string; - detail: string; - tone: 'positive' | 'caution' | 'negative' | 'neutral'; - }>; - }; - citationScorecard: { - queries: Array; - providers: Array; - matrix: Array>; - providerRates: Array<{ - provider: string; - citedCount: number; - mentionedCount: number; - totalCount: number; - citationRate: number; - mentionRate: number; - }>; - }; - competitorLandscape: { - projectCitationCount: number; - competitors: Array<{ - domain: string; - citationCount: number; - totalCount: number; - pressureLabel: 'High' | 'Moderate' | 'Low' | 'None'; - citedQueries: Array; - sharePct: number; - theirCitedPages: Array<{ - url: string; - citedFor: Array; - }>; - }>; - }; - mentionLandscape: { - projectMentionCount: number; - totalAnswerSnapshots: number; - competitors: Array<{ - domain: string; - mentionCount: number; - totalCount: number; - pressureLabel: 'High' | 'Moderate' | 'Low' | 'None'; - mentionedQueries: Array; - sharePct: number; - }>; +export type MeasurementSegmentRetirementResponse = { + stableKey: string; + retiredAt: string; +}; + +export type MeasurementSetupResponse = { + state: 'republish_required' | 'setup_in_progress' | 'awaiting_first_run' | 'operational' | 'simple'; + nextAction: 'republish_setup' | 'continue_setup' | 'run_measurement' | 'view_measurement' | 'start_setup'; + mode: 'simple' | 'draft-only' | 'active-v1' | 'active-v2'; + activeRevision: number | null; + activeSchemaVersion: 1 | 2 | null; + draft: { + etag: string; + updatedAt: string; + } | null; +}; + +export type NotificationDto = { + id: string; + projectId: string; + channel: 'webhook'; + url: string; + urlDisplay: string; + urlHost: string; + events: Array<'citation.lost' | 'citation.gained' | 'run.completed' | 'run.failed' | 'insight.critical' | 'insight.high' | 'health.degraded' | 'health.recovered'>; + enabled: boolean; + source?: string; + webhookSecret?: string; + createdAt: string; + updatedAt: string; +}; + +export type OnboardingTelemetryEvent = { + eventId: string; + flowVersion: 1; + onboardingSessionId: string; + event: 'onboarding.started'; + step: 'system' | 'project' | 'queries' | 'competitors' | 'run'; + resumed: boolean; +} | { + eventId: string; + flowVersion: 1; + onboardingSessionId: string; + event: 'onboarding.step_completed'; + step: 'system' | 'project' | 'queries' | 'competitors' | 'run'; + method: 'existing' | 'inline' | 'manual' | 'generated' | 'skipped' | 'automatic'; + countBucket?: '0' | '1' | '2-3' | '4-5' | '6-10' | '11+'; +} | { + eventId: string; + flowVersion: 1; + onboardingSessionId: string; + event: 'onboarding.blocked'; + step: 'system' | 'project' | 'queries' | 'competitors' | 'run'; + action: 'continue' | 'configure_provider' | 'generate_queries' | 'save' | 'launch_run' | 'retry_run'; + reasonCode: 'api_unavailable' | 'database_unavailable' | 'worker_unavailable' | 'no_provider' | 'no_queries' | 'provider_save_failed' | 'project_create_failed' | 'query_save_failed' | 'run_rejected' | 'run_failed' | 'run_cancelled' | 'unknown'; +} | { + eventId: string; + flowVersion: 1; + onboardingSessionId: string; + event: 'run.requested'; + origin: 'dashboard_setup'; + result: 'queued' | 'rejected'; + providerCountBucket: '0' | '1' | '2-3' | '4-5' | '6-10' | '11+'; + queryCountBucket: '0' | '1' | '2-3' | '4-5' | '6-10' | '11+'; + reasonCode?: 'api_unavailable' | 'database_unavailable' | 'worker_unavailable' | 'no_provider' | 'no_queries' | 'provider_save_failed' | 'project_create_failed' | 'query_save_failed' | 'run_rejected' | 'run_failed' | 'run_cancelled' | 'unknown'; +}; + +export type OrganicEvidenceDto = { + contractVersion: 'organic-evidence/v1'; + periodDays: 60 | 90; + asOfDate: string | null; + coverage: { + gsc: boolean; + ga4: boolean; + server: boolean; + visibility: boolean; }; - aiSourceOrigin: { - categories: Array<{ - category: string; - label: string; - count: number; - sharePct: number; - }>; - topDomains: Array<{ - domain: string; - count: number; - isCompetitor: boolean; - }>; + sourceCoverage: { + gsc: { + startDate: string; + endDate: string; + observedDays: number; + } | null; + ga4: { + startDate: string; + endDate: string; + observedDays: number; + } | null; + server: { + startDate: string; + endDate: string; + observedDays: number; + } | null; + visibility: { + completedAt: string; + ageDays: number; + } | null; }; gsc: { - periodStart: string; - periodEnd: string; - totalClicks: number; - totalImpressions: number; - ctr: number; - avgPosition: number; - topQueries: Array<{ - query: string; + propertyTotals: { clicks: number; impressions: number; - ctr: number; - avgPosition: number; - category: 'brand' | 'lead-gen' | 'industry' | 'other'; - }>; - categoryBreakdown: Array<{ - category: 'brand' | 'lead-gen' | 'industry' | 'other'; + }; + namedBrand: { clicks: number; impressions: number; - sharePct: number; - }>; - trend: Array<{ - date: string; + }; + namedNonBrand: { clicks: number; impressions: number; - }>; - trackedButNoGsc: Array; - gscButNotTracked: Array; - } | null; - ga: { - totalSessions: number; - totalUsers: number; - totalOrganicSessions: number; - periodStart: string; - periodEnd: string; - topLandingPages: Array<{ - page: string; - sessions: number; - users: number; - organicSessions: number; - }>; - channelBreakdown: Array<{ - channel: string; - sessions: number; - sharePct: number; + }; + suppressedOrUnreportedResidual: { + clicks: number; + impressions: number; + }; + cohorts: Array<{ + name: 'earliest' | 'middle' | 'prior' | 'latest'; + startDate: string; + endDate: string; + totals: { + clicks: number; + impressions: number; + }; }>; } | null; - socialReferrals: { - totalSessions: number; + ga4: { organicSessions: number; - paidSessions: number; - channels: Array<{ - channelGroup: string; - sessions: number; - sharePct: number; - }>; - topCampaigns: Array<{ - source: string; - medium: string; - sessions: number; + cohorts: Array<{ + name: 'earliest' | 'middle' | 'prior' | 'latest'; + startDate: string; + endDate: string; + organicSessions: number; }>; } | null; - aiReferrals: { - totalSessions: number; + gaAiReferrals: { paidSessions: number; organicSessions: number; - totalUsers?: number; - paidUsers?: number; - organicUsers?: number; - bySource: Array<{ - source: string; - sessions: number; - users?: number; - paidSessions: number; - organicSessions: number; - sharePct: number; - }>; - trend: Array<{ - date: string; - sessions: number; - }>; - topLandingPages: Array<{ - page: string; - sessions: number; - users?: number; - }>; } | null; - serverActivity: { - windowStart: string; - windowEnd: string; - hasData: boolean; - verifiedCrawlerHits: { - current: number; - prior: number; - deltaPct: number | null; + server: { + crawlerHits: { + verified: number; + claimedUnverified: number; + unknownAiLike: number; }; - unverifiedCrawlerHits: { - current: number; - prior: number; - deltaPct: number | null; + userFetchHits: { + verified: number; + claimedUnverified: number; + unknownAiLike: number; }; - aiUserFetchHits: { - current: number; - prior: number; - deltaPct: number | null; + referralSessions: { + total: number; + paid: number; + organic: number; + unknown: number; }; - referralArrivals: { - current: number; - prior: number; - deltaPct: number | null; + } | null; + visibility: { + runId: string; + completedAt: string; + ageDays: number; + answerPairs: number; + mentionedPairs: number; + citedPairs: number; + } | null; + measurement: { + window: '30d' | '60d' | '90d'; + bucketDays: 30; + filters: { + hostScope: 'marketing' | 'all'; + marketingHosts: Array; + pathPrefix: string | null; + brandTerms: Array; + queryMixScope: 'property'; }; - referralArrivalsByClass: { - paid: { - current: number; - prior: number; - deltaPct: number | null; + acquisition: { + status: 'never-synced' | 'ready' | 'error'; + error: string | null; + syncedAt: string | null; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + sessions: number; + }>; + channels: Array<{ + channelGroup: string; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + sessions: number; + }>; + }>; + pages: Array<{ + hostName: string; + landingPage: string; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + sessions: number; + }>; + }>; + }; + leads: { + status: 'never-synced' | 'ready' | 'error'; + error: string | null; + syncedAt: string | null; + attributionScope: 'landing-page' | 'channel'; + hostAndPathFiltersApplied: boolean; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + eventCount: number; + }>; + channels: Array<{ + channelGroup: string; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + eventCount: number; + }>; + }>; + }; + engagement: { + status: 'ready' | 'unavailable'; + availableFromDate: string | null; + latestDate: string | null; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + sessions: number; + engagementRate: number | null; + dailyTotalUsers: number | null; + dailyNewUsers: number | null; + metricsAvailable: boolean; + daysInPeriod: number; + daysWithEngagementRate: number; + daysWithUserSplit: number; + }>; + }; + searchDemand: { + status: 'ready' | 'unavailable'; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + propertyClicks: number; + propertyImpressions: number; + reportedQueryClicks: number; + reportedQueryImpressions: number; + brandedClicks: number; + brandedImpressions: number; + nonBrandedClicks: number; + nonBrandedImpressions: number; + unreportedClicks: number; + unreportedImpressions: number; + }>; + queries: Array<{ + query: string; + classification: 'branded' | 'non-branded'; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + clicks: number; + impressions: number; + }>; + }>; + pages: Array<{ + hostName: string; + landingPage: string; + periods: Array<{ + label: 'earliest' | 'middle' | 'previous' | 'latest'; + startDate: string; + endDate: string; + clicks: number; + impressions: number; + }>; + }>; + latestDate: string | null; + }; + }; + pages: Array<{ + path: string; + gsc: { + clicks: number; + impressions: number; + }; + ga4OrganicSessions: number; + server: { + crawlerHits: { + verified: number; + claimedUnverified: number; + unknownAiLike: number; }; - organic: { - current: number; - prior: number; - deltaPct: number | null; + userFetchHits: { + verified: number; + claimedUnverified: number; + unknownAiLike: number; }; - unclassified: { - current: number; - prior: number; - deltaPct: number | null; + referralSessions: { + total: number; + paid: number; + organic: number; + unknown: number; }; }; - referralArrivalsClassSummary: string; - byOperator: Array<{ - operator: string; - verifiedHits: number; - unverifiedHits: number; - userFetchHits: number; - referralArrivals: number; - deltaPct: number | null; - }>; - topCrawledPaths: Array<{ - path: string; - verifiedHits: number; - distinctOperators: number; - }>; - referralProducts: Array<{ - product: string; - arrivals: number; - distinctLandingPaths: number; - }>; - dailyTrend: Array<{ - date: string; - verifiedCrawlerHits: number; - userFetchHits: number; - referralArrivals: number; - }>; - topReferralLandingPaths: Array<{ - path: string; - arrivals: number; - distinctProducts: number; - }>; - } | null; - indexingHealth: { - provider: 'google' | 'bing'; - total: number; - indexed: number; - notIndexed: number; - deindexed: number; - unknown: number; - indexedPct: number; - } | null; - citationsTrend: Array<{ - runId: string; - date: string; - citationRate: number; - citedQueryCount: number; - totalQueryCount: number; - mentionRate: number; - mentionedQueryCount: number; - providerRates: Array<{ - provider: string; - citationRate: number; - mentionRate: number; - }>; }>; - whatsChanged: { - enoughHistory: boolean; - headline: string; - citationRate: { - current: number; - prior: number; - deltaAbs: number; - deltaPct: number | null; - direction: 'up' | 'down' | 'flat'; - window?: number; - } | null; - mentionRate: { - current: number; - prior: number; - deltaAbs: number; - deltaPct: number | null; - direction: 'up' | 'down' | 'flat'; - window?: number; - } | null; - citedQueryCount: { - current: number; - prior: number; - deltaAbs: number; - deltaPct: number | null; - direction: 'up' | 'down' | 'flat'; - window?: number; - } | null; - mentionedQueryCount: { - current: number; - prior: number; - deltaAbs: number; - deltaPct: number | null; - direction: 'up' | 'down' | 'flat'; - window?: number; - } | null; - gscClicksDelta: { - current: number; - prior: number; - deltaAbs: number; - deltaPct: number | null; - direction: 'up' | 'down' | 'flat'; - window?: number; - } | null; - aiReferralsDelta: { - current: number; - prior: number; - deltaAbs: number; - deltaPct: number | null; - direction: 'up' | 'down' | 'flat'; - window?: number; - } | null; - comparisonWindowDays: number; - providerMovements: Array<{ - provider: string; - current: number; - prior: number; - deltaAbs: number; - direction: 'up' | 'down' | 'flat'; - }>; - wins: Array<{ - id: string; - type: 'regression' | 'gain' | 'opportunity'; - severity: 'critical' | 'high' | 'medium' | 'low'; - title: string; - query: string; - provider: string; - recommendation: string | null; - createdAt: string; - instanceCount: number; - }>; - regressions: Array<{ - id: string; - type: 'regression' | 'gain' | 'opportunity'; - severity: 'critical' | 'high' | 'medium' | 'low'; - title: string; - query: string; - provider: string; - recommendation: string | null; - createdAt: string; - instanceCount: number; - }>; - }; - insights: Array<{ - id: string; - type: 'regression' | 'gain' | 'opportunity'; - severity: 'critical' | 'high' | 'medium' | 'low'; + findings: Array<{ + tone: 'positive' | 'caution' | 'neutral'; title: string; - query: string; - provider: string; - recommendation: string | null; - createdAt: string; - instanceCount: number; + detail: string; }>; - recommendedNextSteps: Array<{ - horizon: 'immediate' | 'short-term' | 'medium-term'; - title: string; - rationale: string; + limitations: Array<{ + code: string; + detail: string; }>; - actionPlan: Array<{ - audience: 'agency' | 'client' | 'both'; - priority: number; - horizon: 'immediate' | 'short-term' | 'medium-term'; - category: 'content' | 'competitors' | 'provider' | 'search-demand' | 'indexing' | 'location' | 'monitoring'; - title: string; - action: string; - why: Array; - evidence: Array; - successMetric: string; - confidence: 'high' | 'medium' | 'low'; - targetRef?: string; +}; + +export type ProjectDto = { + id: string; + name: string; + displayName?: string; + canonicalDomain: string; + ownedDomains: Array; + aliases: Array; + country: string; + language: string; + tags: Array; + labels: { + [key: string]: string; + }; + providers: Array; + providerModels: { + [key: string]: string; + }; + locations: Array<{ + label: string; + city: string; + region: string; + country: string; + timezone?: string; }>; - clientSummary: { - headline: string; - overview: string; - actionItems: Array<{ - audience: 'agency' | 'client' | 'both'; - priority: number; - horizon: 'immediate' | 'short-term' | 'medium-term'; - category: 'content' | 'competitors' | 'provider' | 'search-demand' | 'indexing' | 'location' | 'monitoring'; - title: string; - action: string; - why: Array; - evidence: Array; - successMetric: string; - confidence: 'high' | 'medium' | 'low'; - targetRef?: string; - }>; - confidenceNotes: Array; + defaultLocation?: string | null; + measurement: { + marketingHosts: Array; + brandTerms: Array; + leadEventNames: Array; }; - agencyDiagnostics: { - priorities: Array<{ - audience: 'agency' | 'client' | 'both'; - priority: number; - horizon: 'immediate' | 'short-term' | 'medium-term'; - category: 'content' | 'competitors' | 'provider' | 'search-demand' | 'indexing' | 'location' | 'monitoring'; - title: string; - action: string; - why: Array; - evidence: Array; - successMetric: string; - confidence: 'high' | 'medium' | 'low'; - targetRef?: string; + autoExtractBacklinks: boolean; + configSource: 'cli' | 'api' | 'config-file'; + configRevision: number; + createdAt?: string; + updatedAt?: string; +}; + +export type ProjectUpsertRequest = { + displayName: string; + canonicalDomain: string; + ownedDomains?: Array; + aliases?: Array; + country: string; + language: string; + tags?: Array; + labels?: { + [key: string]: string; + }; + providers?: Array; + providerModels?: { + [key: string]: string; + }; + locations?: Array<{ + label: string; + city: string; + region: string; + country: string; + timezone?: string; + }>; + defaultLocation?: string | null; + measurement?: { + marketingHosts: Array; + brandTerms: Array; + leadEventNames: Array; + }; + autoExtractBacklinks?: boolean; + configSource?: 'cli' | 'api' | 'config-file'; +}; + +export type ProjectConfig = { + apiVersion: 'canonry/v1'; + kind: 'Project'; + metadata: { + name: string; + labels: { + [key: string]: string; + }; + }; + spec: { + displayName: string; + canonicalDomain: string; + ownedDomains: Array; + aliases: Array; + country: string; + language: string; + queries?: Array; + keywords?: Array; + competitors: Array; + providers: Array; + providerModels: { + [key: string]: string; + }; + locations: Array<{ + label: string; + city: string; + region: string; + country: string; + timezone?: string; }>; - diagnostics: Array<{ - title: string; - detail: string; - severity: 'positive' | 'caution' | 'negative' | 'neutral'; - evidence: Array; + defaultLocation?: string; + measurement: { + marketingHosts: Array; + brandTerms: Array; + leadEventNames: Array; + }; + schedule?: { + preset?: string; + cron?: string; + timezone: string; + providers: Array; + enabled?: boolean; + }; + notifications: Array<{ + channel: 'webhook'; + url: string; + events: Array<'citation.lost' | 'citation.gained' | 'run.completed' | 'run.failed' | 'insight.critical' | 'insight.high' | 'health.degraded' | 'health.recovered'>; }>; + google?: { + gsc?: { + propertyUrl: string; + }; + syncSchedule?: { + preset?: string; + cron?: string; + }; + }; + autoExtractBacklinks: boolean; }; - contentOpportunities: Array<{ - targetRef: string; - query: string; - action: 'create' | 'expand' | 'refresh' | 'add-schema'; - ourBestPage: { - url: string; - gscImpressions: number; - gscClicks: number; - gscAvgPosition: number | null; - organicSessions: number; - } | null; - winningCompetitor: { - domain: string; - url: string; - title: string; - citationCount: number; - } | null; - score: number; - scoreBreakdown: { - demand: number; - competitor: number; - absence: number; - gapSeverity: number; +}; + +export type ProjectOverviewDto = { + project: { + id: string; + name: string; + displayName?: string; + canonicalDomain: string; + ownedDomains: Array; + aliases: Array; + country: string; + language: string; + tags: Array; + labels: { + [key: string]: string; }; - drivers: Array; - demandSource: 'gsc' | 'competitor-evidence' | 'both'; - actionConfidence: 'high' | 'medium' | 'low'; - existingAction: { - actionId: string; - state: 'proposed' | 'briefed' | 'payload-generated' | 'draft-created' | 'published' | 'validated' | 'dismissed'; - lastUpdated: string; + providers: Array; + providerModels: { + [key: string]: string; + }; + locations: Array<{ + label: string; + city: string; + region: string; + country: string; + timezone?: string; + }>; + defaultLocation?: string | null; + measurement: { + marketingHosts: Array; + brandTerms: Array; + leadEventNames: Array; + }; + autoExtractBacklinks: boolean; + configSource: 'cli' | 'api' | 'config-file'; + configRevision: number; + createdAt?: string; + updatedAt?: string; + }; + latestRun: { + totalRuns: number; + run: { + id: string; + projectId: string; + kind: 'answer-visibility' | 'site-audit' | 'gsc-sync' | 'inspect-sitemap' | 'ga-sync' | 'bing-inspect' | 'bing-inspect-sitemap' | 'backlink-extract' | 'traffic-sync' | 'aeo-discover-seed' | 'aeo-discover-probe' | 'gbp-sync' | 'ads-sync'; + status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + trigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; + measurementPlanVersionId?: string | null; + measurementManifest?: { + [key: string]: unknown; + } | null; + measurementScope?: { + groups: Array; + targets: Array; + queries: Array; + resolvedTargets: Array; + } | null; + measurementExecutionIdentity?: { + schemaVersion: 1; + providers: Array; + models: { + [key: string]: string; + }; + checksum: string; + } | null; + location?: string | null; + queries?: Array | null; + startedAt?: string | null; + finishedAt?: string | null; + error?: { + message?: string; + providers?: { + [key: string]: { + message: string; + raw?: unknown; + }; + }; + } | null; + queryBasketRevision?: number | null; + createdAt: string; + snapshots?: Array<{ + id: string; + runId: string; + queryId: string; + query?: string; + provider: string; + citationState: 'cited' | 'not-cited'; + answerMentioned?: boolean; + visibilityState?: 'visible' | 'not-visible'; + mentionState?: 'mentioned' | 'not-mentioned'; + transition?: 'new' | 'cited' | 'lost' | 'emerging' | 'not-cited'; + answerText?: string | null; + citedDomains: Array; + citedUrls: Array | null; + captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; + sourceCount: number | null; + resolvedCount: number | null; + captureVersion: number | null; + retrievalStatus: 'used' | 'not-used' | 'unknown' | 'not-applicable' | null; + retrievalContract: 'native-auto-v1' | 'search-required-v1' | null; + competitorOverlap: Array; + recommendedCompetitors: Array; + matchedTerms: Array; + groundingSources: Array<{ + uri: string; + title: string; + }>; + searchQueries: Array; + model?: string | null; + servedModel?: string | null; + location?: string | null; + requestedContext?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + supportedContext?: { + status: 'applied' | 'ignored' | 'browser-implicit' | 'unknown'; + resolved?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + } | null; + createdAt: string; + }>; } | null; - winnabilityClass: 'ownable' | 'ceded'; - winnability: number | null; - }>; - contentGaps: Array<{ + }; + health: { + id: string; + projectId: string; + runId: string | null; + overallCitedRate: number; + overallMentionRate: number; + totalPairs: number; + citedPairs: number; + mentionedPairs: number; + providerBreakdown: { + [key: string]: { + citedRate: number; + mentionRate: number; + cited: number; + mentioned: number; + total: number; + }; + }; + createdAt: string; + status: 'ready' | 'no-data'; + reason?: 'no-runs-yet'; + } | null; + topInsights: Array<{ + id: string; + projectId: string; + runId: string | null; + type: 'regression' | 'gain' | 'opportunity' | 'first-citation' | 'provider-pickup' | 'persistent-gap' | 'competitor-gained' | 'competitor-lost' | 'gbp-lodging-gap' | 'gbp-listing-discrepancy' | 'gbp-cta-gap' | 'gbp-description-missing' | 'gbp-metric-drop' | 'gbp-keyword-drop'; + severity: 'critical' | 'high' | 'medium' | 'low'; + title: string; query: string; - competitorDomains: Array; - competitorCount: number; - missRate: number; - lastSeenInRunId: string; + provider: string; + recommendation?: { + action: string; + target?: string; + reason: string; + }; + cause?: { + cause: string; + competitorDomain?: string; + details?: string; + }; + dismissed: boolean; + createdAt: string; }>; - groundingSources: Array<{ - query: string; - groundingSources: Array<{ - uri: string; - title: string; - domain: string; - isOurDomain: boolean; - isCompetitor: boolean; - citationCount: number; - providers: Array; - }>; + queryCounts: { + totalQueries: number; + citedQueries: number; + notCitedQueries: number; + citedRate: number; + mentionedQueries: number; + notMentionedQueries: number; + mentionRate: number; + }; + providers: Array<{ + provider: string; + citedRate: number; + cited: number; + total: number; }>; + transitions: { + since: string | null; + gained: number; + lost: number; + emerging: number; + }; + scores: { + mention: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + }; + visibility: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + }; + mentionShare: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + breakdown: { + projectMentionSnapshots: number; + competitorMentionSnapshots: number; + perCompetitor: Array<{ + domain: string; + mentionSnapshots: number; + shareOfCompetitiveTotal: number; + }>; + snapshotsWithAnswerText: number; + snapshotsTotal: number; + }; + }; + gapQueries: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + }; + mentionGaps: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + }; + indexCoverage: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + }; + competitorPressure: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + }; + runStatus: { + label: string; + value: string; + delta: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + description: string; + tooltip?: string; + trend: Array; + progress?: number; + providerCoverage?: string; + }; + }; + movementSummary: { + gained: number; + lost: number; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + hasPreviousRun: boolean; + gainedQueries?: Array; + lostQueries?: Array; + }; + citationMovement: { + gained: number; + lost: number; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + hasPreviousRun: boolean; + gainedQueries?: Array; + lostQueries?: Array; + }; + mentionMovement: { + gained: number; + lost: number; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + hasPreviousRun: boolean; + gainedQueries?: Array; + lostQueries?: Array; + }; + movementComparison: { + hasPreviousRun: boolean; + comparable: boolean; + querySetChanged: boolean; + previousRunAt: string | null; + currentQueryCount: number; + previousQueryCount: number; + comparableQueryCount: number; + addedQueryCount: number; + removedQueryCount: number; + addedQueries: Array; + removedQueries: Array; + }; + competitors: Array<{ + id: string; + domain: string; + citationCount: number; + totalQueries: number; + pressureLabel: 'None' | 'Low' | 'Moderate' | 'High'; + citedQueries: Array; + }>; + providerScores: Array<{ + provider: string; + model: string | null; + score: number; + cited: number; + total: number; + trend?: Array; + }>; + attentionItems: Array<{ + id: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + title: string; + detail: string; + actionLabel: string; + href: string; + }>; + runHistory: Array<{ + runId: string; + createdAt: string; + citedCount: number; + totalCount: number; + citationRate: number; + mentionedCount: number; + mentionRate: number; + status: string; + }>; + suggestedQueries: { + rows: Array<{ + query: string; + impressions: number; + clicks: number; + avgPosition: number; + reason: string; + }>; + totalCandidates: number; + skippedAlreadyTracked: number; + }; + dateRangeLabel: string; + contextLabel: string; +}; + +export type ProjectReportDto = { + meta: { + generatedAt: string; + project: { + id: string; + name: string; + displayName: string; + canonicalDomain: string; + country: string; + language: string; + }; + location: { + label: string; + city: string; + region: string; + country: string; + otherConfiguredLabels: Array; + } | null; + providerLocationHandling: Array<{ + provider: string; + treatment: 'prompt' | 'request-param' | 'browser-geo' | 'ignored'; + description: string; + }>; + periodStart: string | null; + periodEnd: string | null; + periodDays: number; + }; + executiveSummary: { + citationRate: number; + citedQueryCount: number; + totalQueryCount: number; + mentionRate: number; + mentionedQueryCount: number; + trend: 'up' | 'down' | 'flat' | 'unknown'; + queryCount: number; + competitorCount: number; + providerCount: number; + gsc: { + clicks: number; + impressions: number; + ctr: number; + avgPosition: number; + periodStart: string; + periodEnd: string; + } | null; + ga: { + sessions: number; + users: number; + periodStart: string; + periodEnd: string; + } | null; + findings: Array<{ + title: string; + detail: string; + tone: 'positive' | 'caution' | 'negative' | 'neutral'; + }>; + }; + citationScorecard: { + queries: Array; + providers: Array; + matrix: Array>; + providerRates: Array<{ + provider: string; + citedCount: number; + mentionedCount: number; + totalCount: number; + citationRate: number; + mentionRate: number; + }>; + }; + competitorLandscape: { + projectCitationCount: number; + competitors: Array<{ + domain: string; + citationCount: number; + totalCount: number; + pressureLabel: 'High' | 'Moderate' | 'Low' | 'None'; + citedQueries: Array; + sharePct: number; + theirCitedPages: Array<{ + url: string; + citedFor: Array; + }>; + }>; + }; + mentionLandscape: { + projectMentionCount: number; + totalAnswerSnapshots: number; + competitors: Array<{ + domain: string; + mentionCount: number; + totalCount: number; + pressureLabel: 'High' | 'Moderate' | 'Low' | 'None'; + mentionedQueries: Array; + sharePct: number; + }>; + }; + aiSourceOrigin: { + categories: Array<{ + category: string; + label: string; + count: number; + sharePct: number; + }>; + topDomains: Array<{ + domain: string; + count: number; + isCompetitor: boolean; + }>; + }; + gsc: { + periodStart: string; + periodEnd: string; + totalClicks: number; + totalImpressions: number; + ctr: number; + avgPosition: number; + topQueries: Array<{ + query: string; + clicks: number; + impressions: number; + ctr: number; + avgPosition: number; + category: 'brand' | 'lead-gen' | 'industry' | 'other'; + }>; + categoryBreakdown: Array<{ + category: 'brand' | 'lead-gen' | 'industry' | 'other'; + clicks: number; + impressions: number; + sharePct: number; + }>; + trend: Array<{ + date: string; + clicks: number; + impressions: number; + }>; + trackedButNoGsc: Array; + gscButNotTracked: Array; + } | null; + ga: { + totalSessions: number; + totalUsers: number; + totalOrganicSessions: number; + periodStart: string; + periodEnd: string; + topLandingPages: Array<{ + page: string; + sessions: number; + users: number; + organicSessions: number; + }>; + channelBreakdown: Array<{ + channel: string; + sessions: number; + sharePct: number; + }>; + } | null; + socialReferrals: { + totalSessions: number; + organicSessions: number; + paidSessions: number; + channels: Array<{ + channelGroup: string; + sessions: number; + sharePct: number; + }>; + topCampaigns: Array<{ + source: string; + medium: string; + sessions: number; + }>; + } | null; + aiReferrals: { + totalSessions: number; + paidSessions: number; + organicSessions: number; + totalUsers?: number; + paidUsers?: number; + organicUsers?: number; + bySource: Array<{ + source: string; + sessions: number; + users?: number; + paidSessions: number; + organicSessions: number; + sharePct: number; + }>; + trend: Array<{ + date: string; + sessions: number; + }>; + topLandingPages: Array<{ + page: string; + sessions: number; + users?: number; + }>; + } | null; + serverActivity: { + windowStart: string; + windowEnd: string; + hasData: boolean; + verifiedCrawlerHits: { + current: number; + prior: number; + deltaPct: number | null; + }; + unverifiedCrawlerHits: { + current: number; + prior: number; + deltaPct: number | null; + }; + aiUserFetchHits: { + current: number; + prior: number; + deltaPct: number | null; + }; + referralArrivals: { + current: number; + prior: number; + deltaPct: number | null; + }; + referralArrivalsByClass: { + paid: { + current: number; + prior: number; + deltaPct: number | null; + }; + organic: { + current: number; + prior: number; + deltaPct: number | null; + }; + unclassified: { + current: number; + prior: number; + deltaPct: number | null; + }; + }; + referralArrivalsClassSummary: string; + byOperator: Array<{ + operator: string; + verifiedHits: number; + unverifiedHits: number; + userFetchHits: number; + referralArrivals: number; + deltaPct: number | null; + }>; + topCrawledPaths: Array<{ + path: string; + verifiedHits: number; + distinctOperators: number; + }>; + referralProducts: Array<{ + product: string; + arrivals: number; + distinctLandingPaths: number; + }>; + dailyTrend: Array<{ + date: string; + verifiedCrawlerHits: number; + userFetchHits: number; + referralArrivals: number; + }>; + topReferralLandingPaths: Array<{ + path: string; + arrivals: number; + distinctProducts: number; + }>; + } | null; + indexingHealth: { + provider: 'google' | 'bing'; + total: number; + indexed: number; + notIndexed: number; + deindexed: number; + unknown: number; + indexedPct: number; + } | null; + citationsTrend: Array<{ + runId: string; + date: string; + citationRate: number; + citedQueryCount: number; + totalQueryCount: number; + mentionRate: number; + mentionedQueryCount: number; + providerRates: Array<{ + provider: string; + citationRate: number; + mentionRate: number; + }>; + }>; + whatsChanged: { + enoughHistory: boolean; + headline: string; + citationRate: { + current: number; + prior: number; + deltaAbs: number; + deltaPct: number | null; + direction: 'up' | 'down' | 'flat'; + window?: number; + } | null; + mentionRate: { + current: number; + prior: number; + deltaAbs: number; + deltaPct: number | null; + direction: 'up' | 'down' | 'flat'; + window?: number; + } | null; + citedQueryCount: { + current: number; + prior: number; + deltaAbs: number; + deltaPct: number | null; + direction: 'up' | 'down' | 'flat'; + window?: number; + } | null; + mentionedQueryCount: { + current: number; + prior: number; + deltaAbs: number; + deltaPct: number | null; + direction: 'up' | 'down' | 'flat'; + window?: number; + } | null; + gscClicksDelta: { + current: number; + prior: number; + deltaAbs: number; + deltaPct: number | null; + direction: 'up' | 'down' | 'flat'; + window?: number; + } | null; + aiReferralsDelta: { + current: number; + prior: number; + deltaAbs: number; + deltaPct: number | null; + direction: 'up' | 'down' | 'flat'; + window?: number; + } | null; + comparisonWindowDays: number; + providerMovements: Array<{ + provider: string; + current: number; + prior: number; + deltaAbs: number; + direction: 'up' | 'down' | 'flat'; + }>; + wins: Array<{ + id: string; + type: 'regression' | 'gain' | 'opportunity'; + severity: 'critical' | 'high' | 'medium' | 'low'; + title: string; + query: string; + provider: string; + recommendation: string | null; + createdAt: string; + instanceCount: number; + }>; + regressions: Array<{ + id: string; + type: 'regression' | 'gain' | 'opportunity'; + severity: 'critical' | 'high' | 'medium' | 'low'; + title: string; + query: string; + provider: string; + recommendation: string | null; + createdAt: string; + instanceCount: number; + }>; + }; + insights: Array<{ + id: string; + type: 'regression' | 'gain' | 'opportunity'; + severity: 'critical' | 'high' | 'medium' | 'low'; + title: string; + query: string; + provider: string; + recommendation: string | null; + createdAt: string; + instanceCount: number; + }>; + recommendedNextSteps: Array<{ + horizon: 'immediate' | 'short-term' | 'medium-term'; + title: string; + rationale: string; + }>; + actionPlan: Array<{ + audience: 'agency' | 'client' | 'both'; + priority: number; + horizon: 'immediate' | 'short-term' | 'medium-term'; + category: 'content' | 'competitors' | 'provider' | 'search-demand' | 'indexing' | 'location' | 'monitoring'; + title: string; + action: string; + why: Array; + evidence: Array; + successMetric: string; + confidence: 'high' | 'medium' | 'low'; + targetRef?: string; + }>; + clientSummary: { + headline: string; + overview: string; + actionItems: Array<{ + audience: 'agency' | 'client' | 'both'; + priority: number; + horizon: 'immediate' | 'short-term' | 'medium-term'; + category: 'content' | 'competitors' | 'provider' | 'search-demand' | 'indexing' | 'location' | 'monitoring'; + title: string; + action: string; + why: Array; + evidence: Array; + successMetric: string; + confidence: 'high' | 'medium' | 'low'; + targetRef?: string; + }>; + confidenceNotes: Array; + }; + agencyDiagnostics: { + priorities: Array<{ + audience: 'agency' | 'client' | 'both'; + priority: number; + horizon: 'immediate' | 'short-term' | 'medium-term'; + category: 'content' | 'competitors' | 'provider' | 'search-demand' | 'indexing' | 'location' | 'monitoring'; + title: string; + action: string; + why: Array; + evidence: Array; + successMetric: string; + confidence: 'high' | 'medium' | 'low'; + targetRef?: string; + }>; + diagnostics: Array<{ + title: string; + detail: string; + severity: 'positive' | 'caution' | 'negative' | 'neutral'; + evidence: Array; + }>; + }; + contentOpportunities: Array<{ + targetRef: string; + query: string; + action: 'create' | 'expand' | 'refresh' | 'add-schema'; + ourBestPage: { + url: string; + gscImpressions: number; + gscClicks: number; + gscAvgPosition: number | null; + organicSessions: number; + } | null; + winningCompetitor: { + domain: string; + url: string; + title: string; + citationCount: number; + } | null; + score: number; + scoreBreakdown: { + demand: number; + competitor: number; + absence: number; + gapSeverity: number; + }; + drivers: Array; + demandSource: 'gsc' | 'competitor-evidence' | 'both'; + actionConfidence: 'high' | 'medium' | 'low'; + existingAction: { + actionId: string; + state: 'proposed' | 'briefed' | 'payload-generated' | 'draft-created' | 'published' | 'validated' | 'dismissed'; + lastUpdated: string; + } | null; + winnabilityClass: 'ownable' | 'ceded'; + winnability: number | null; + }>; + contentGaps: Array<{ + query: string; + competitorDomains: Array; + competitorCount: number; + missRate: number; + lastSeenInRunId: string; + }>; + groundingSources: Array<{ + query: string; + groundingSources: Array<{ + uri: string; + title: string; + domain: string; + isOurDomain: boolean; + isCompetitor: boolean; + citationCount: number; + providers: Array; + }>; + }>; +}; + +export type ProjectSearchResponseDto = { + query: string; + totalHits: number; + truncated: boolean; + hits: Array<{ + kind: 'snapshot'; + id: string; + runId: string; + query: string; + provider: string; + model: string | null; + citationState: 'cited' | 'not-cited'; + matchedField: 'answerText' | 'citedDomains' | 'citedUrls' | 'searchQueries' | 'query'; + snippet: string; + createdAt: string; + } | { + kind: 'insight'; + id: string; + runId: string | null; + type: 'regression' | 'gain' | 'opportunity' | 'first-citation' | 'provider-pickup' | 'persistent-gap' | 'competitor-gained' | 'competitor-lost' | 'gbp-lodging-gap' | 'gbp-listing-discrepancy' | 'gbp-cta-gap' | 'gbp-description-missing' | 'gbp-metric-drop' | 'gbp-keyword-drop'; + severity: 'critical' | 'high' | 'medium' | 'low'; + title: string; + query: string; + provider: string; + matchedField: 'title' | 'query' | 'recommendation' | 'cause'; + snippet: string; + dismissed: boolean; + createdAt: string; + }>; +}; + +export type QueryDto = { + id: string; + query: string; + createdAt: string; +}; + +export type ResultsExportDto = { + schemaVersion: 'canonry.results-export/v1'; + generatedAt: string; + project: { + id: string; + name: string; + displayName: string; + canonicalDomain: string; + country: string; + language: string; + }; + filters: { + since: string | null; + until: string | null; + includeProbes: boolean; + }; + recordCount: number; + records: Array<{ + runId: string; + runKind: 'answer-visibility'; + runStatus: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + runTrigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; + runCreatedAt: string; + runStartedAt: string | null; + runFinishedAt: string | null; + snapshotId: string; + snapshotCreatedAt: string; + queryId: string | null; + query: string | null; + provider: string; + model: string | null; + location: string | null; + citationState: 'cited' | 'not-cited'; + cited: boolean; + answerMentioned: boolean | null; + mentionState: 'mentioned' | 'not-mentioned' | null; + citedDomains: Array; + citedUrls: Array | null; + captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; + sourceCount: number | null; + resolvedCount: number | null; + captureVersion: number | null; + competitorOverlap: Array; + recommendedCompetitors: Array; + answerText: string | null; + groundingSources: Array<{ + uri: string; + title: string; + }>; + searchQueries: Array; + }>; +}; + +export type RunDetailDto = { + id: string; + projectId: string; + kind: 'answer-visibility' | 'site-audit' | 'gsc-sync' | 'inspect-sitemap' | 'ga-sync' | 'bing-inspect' | 'bing-inspect-sitemap' | 'backlink-extract' | 'traffic-sync' | 'aeo-discover-seed' | 'aeo-discover-probe' | 'gbp-sync' | 'ads-sync'; + status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + trigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; + measurementPlanVersionId?: string | null; + measurementManifest?: { + [key: string]: unknown; + } | null; + measurementScope?: { + groups: Array; + targets: Array; + queries: Array; + resolvedTargets: Array; + } | null; + measurementExecutionIdentity?: { + schemaVersion: 1; + providers: Array; + models: { + [key: string]: string; + }; + checksum: string; + } | null; + location?: string | null; + queries?: Array | null; + startedAt?: string | null; + finishedAt?: string | null; + error?: { + message?: string; + providers?: { + [key: string]: { + message: string; + raw?: unknown; + }; + }; + } | null; + queryBasketRevision?: number | null; + createdAt: string; + snapshots?: Array<{ + id: string; + runId: string; + queryId: string; + query?: string; + provider: string; + citationState: 'cited' | 'not-cited'; + answerMentioned?: boolean; + visibilityState?: 'visible' | 'not-visible'; + mentionState?: 'mentioned' | 'not-mentioned'; + transition?: 'new' | 'cited' | 'lost' | 'emerging' | 'not-cited'; + answerText?: string | null; + citedDomains: Array; + citedUrls: Array | null; + captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; + sourceCount: number | null; + resolvedCount: number | null; + captureVersion: number | null; + retrievalStatus: 'used' | 'not-used' | 'unknown' | 'not-applicable' | null; + retrievalContract: 'native-auto-v1' | 'search-required-v1' | null; + competitorOverlap: Array; + recommendedCompetitors: Array; + matchedTerms: Array; + groundingSources: Array<{ + uri: string; + title: string; + }>; + searchQueries: Array; + model?: string | null; + servedModel?: string | null; + location?: string | null; + requestedContext?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + supportedContext?: { + status: 'applied' | 'ignored' | 'browser-implicit' | 'unknown'; + resolved?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + } | null; + createdAt: string; + }>; +}; + +export type RunDto = { + id: string; + projectId: string; + kind: 'answer-visibility' | 'site-audit' | 'gsc-sync' | 'inspect-sitemap' | 'ga-sync' | 'bing-inspect' | 'bing-inspect-sitemap' | 'backlink-extract' | 'traffic-sync' | 'aeo-discover-seed' | 'aeo-discover-probe' | 'gbp-sync' | 'ads-sync'; + status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + trigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; + measurementPlanVersionId?: string | null; + measurementManifest?: { + [key: string]: unknown; + } | null; + measurementScope?: { + groups: Array; + targets: Array; + queries: Array; + resolvedTargets: Array; + } | null; + measurementExecutionIdentity?: { + schemaVersion: 1; + providers: Array; + models: { + [key: string]: string; + }; + checksum: string; + } | null; + location?: string | null; + queries?: Array | null; + startedAt?: string | null; + finishedAt?: string | null; + error?: { + message?: string; + providers?: { + [key: string]: { + message: string; + raw?: unknown; + }; + }; + } | null; + queryBasketRevision?: number | null; + createdAt: string; +}; + +export type SchedulableRunKind = 'answer-visibility' | 'traffic-sync' | 'gbp-sync' | 'data-refresh' | 'backlinks-sync' | 'site-audit' | 'ads-sync' | 'doctor'; + +export type ScheduleDto = { + id: string; + projectId: string; + kind: SchedulableRunKind; + cronExpr: string; + preset?: string | null; + timezone: string; + enabled: boolean; + providers: Array; + sourceId?: string | null; + lastRunAt?: string | null; + nextRunAt?: string | null; + createdAt: string; + updatedAt: string; +}; + +export type SettingsDto = { + providers: Array<{ + name: string; + displayName?: string; + keyUrl?: string; + modelHint?: string; + model?: string; + defaultModel?: string; + configured: boolean; + quota?: { + maxConcurrency: number; + maxRequestsPerMinute: number; + maxRequestsPerDay: number; + }; + vertexConfigured?: boolean; + }>; + providerCatalog: Array<{ + name: string; + displayName: string; + mode: 'api' | 'browser'; + modelConfigurable: boolean; + defaultModel: string; + knownModels: Array<{ + id: string; + displayName: string; + tier: 'flagship' | 'standard' | 'fast' | 'economy'; + }>; + modelValidationPattern: { + source: string; + flags: string; + }; + modelValidationHint: string; + }>; + google: { + configured: boolean; + }; + bing: { + configured: boolean; + }; +}; + +export type SiteAuditPagesResponseDto = { + project: string; + runId: string | null; + auditedAt: string | null; + total: number; + pages: Array<{ + url: string; + overallScore: number; + status: 'success' | 'error'; + error?: string | null; + factors: Array<{ + id: string; + name: string; + weight: number; + score: number; + }>; + }>; +}; + +export type SiteAuditRunResponseDto = { + runId: string; + status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; +}; + +export type SiteAuditScoreDto = { + project: string; + hasData: boolean; + runId: string | null; + runStatus: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + sitemapUrl: string | null; + auditedAt: string | null; + aggregateScore: number; + pagesDiscovered: number; + pagesAudited: number; + pagesSkipped: number; + pagesErrored: number; + deltaScore: number | null; + trend: 'up' | 'down' | 'flat'; + previousScore: number | null; + previousAuditedAt: string | null; + factors: Array<{ + id: string; + name: string; + weight: number; + avgScore: number; + status: 'pass' | 'partial' | 'fail'; + pagesPassing: number; + pagesPartial: number; + pagesFailing: number; + }>; + crossCuttingIssues: Array<{ + factorId: string; + factorName: string; + avgScore: number; + affectedPages: number; + totalPages: number; + affectedPct: number; + topRecommendations: Array; + }>; + prioritizedFixes: Array; +}; + +export type SiteAuditTrendResponseDto = { + project: string; + points: Array<{ + runId: string; + auditedAt: string; + aggregateScore: number; + pagesAudited: number; + }>; +}; + +export type SnapshotDiffResponse = { + run1: string; + run2: string; + diff: Array<{ + queryId: string | null; + query: string | null; + run1State: 'cited' | 'not-cited'; + run2State: 'cited' | 'not-cited'; + run1AnswerMentioned: boolean | null; + run2AnswerMentioned: boolean | null; + run1VisibilityState: 'visible' | 'not-visible'; + run2VisibilityState: 'visible' | 'not-visible'; + run1MentionState?: 'mentioned' | 'not-mentioned'; + run2MentionState?: 'mentioned' | 'not-mentioned'; + changed: boolean; + visibilityChanged: boolean; + }>; +}; + +export type SnapshotListResponse = { + snapshots: Array<{ + id: string; + runId: string; + queryId: string; + query?: string; + provider: string; + citationState: 'cited' | 'not-cited'; + answerMentioned?: boolean; + visibilityState?: 'visible' | 'not-visible'; + mentionState?: 'mentioned' | 'not-mentioned'; + transition?: 'new' | 'cited' | 'lost' | 'emerging' | 'not-cited'; + answerText?: string | null; + citedDomains: Array; + citedUrls: Array | null; + captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; + sourceCount: number | null; + resolvedCount: number | null; + captureVersion: number | null; + retrievalStatus: 'used' | 'not-used' | 'unknown' | 'not-applicable' | null; + retrievalContract: 'native-auto-v1' | 'search-required-v1' | null; + competitorOverlap: Array; + recommendedCompetitors: Array; + matchedTerms: Array; + groundingSources: Array<{ + uri: string; + title: string; + }>; + searchQueries: Array; + model?: string | null; + servedModel?: string | null; + location?: string | null; + requestedContext?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + supportedContext?: { + status: 'applied' | 'ignored' | 'browser-implicit' | 'unknown'; + resolved?: { + label: string; + city: string; + region: string; + country: string; + timezone?: string; + } | null; + } | null; + createdAt: string; + }>; + total: number; +}; + +export type SnapshotReportDto = { + companyName: string; + domain: string; + homepageUrl: string; + generatedAt: string; + queries: Array; + competitors: Array; + profile: { + industry: string; + summary: string; + services: Array; + categoryTerms: Array; + }; + audit: { + url: string; + finalUrl: string; + auditedAt: string; + overallScore: number; + summary: string; + factors: Array<{ + id: string; + name: string; + weight: number; + score: number; + findings: Array<{ + type: string; + message: string; + }>; + recommendations: Array; + }>; + }; + queryResults: Array<{ + query: string; + providerResults: Array<{ + provider: string; + displayName: string; + model?: string | null; + mentioned: boolean; + cited: boolean; + describedAccurately: 'yes' | 'no' | 'unknown' | 'not-mentioned'; + accuracyNotes?: string | null; + incorrectClaims: Array; + recommendedCompetitors: Array; + citedDomains: Array; + groundingSources: Array<{ + uri: string; + title: string; + }>; + searchQueries: Array; + answerText: string; + error?: string | null; + }>; + }>; + summary: { + totalQueries: number; + totalProviders: number; + totalComparisons: number; + mentionCount: number; + citationCount: number; + topCompetitors: Array<{ + name: string; + count: number; + }>; + visibilityGap: string; + whatThisMeans: Array; + recommendedActions: Array; + }; +}; + +export type SourceBreakdownDto = { + overall: Array<{ + category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; + label: string; + count: number; + percentage: number; + topDomains: Array<{ + domain: string; + count: number; + }>; + }>; + byQuery: { + [key: string]: Array<{ + category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; + label: string; + count: number; + percentage: number; + topDomains: Array<{ + domain: string; + count: number; + }>; + }>; + }; + ranked: { + totalCitedSlots: number; + domainTotal: number; + entries: Array<{ + domain: string; + count: number; + percentage: number; + category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; + label: string; + surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; + }>; + truncatedDomainCount: number; + truncatedCitedSlots: number; + bySurfaceClass: Array<{ + surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; + label: string; + count: number; + percentage: number; + domainCount: number; + }>; + }; + byProvider: { + [key: string]: { + totalCitedSlots: number; + domainTotal: number; + entries: Array<{ + domain: string; + count: number; + percentage: number; + category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; + label: string; + surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; + }>; + truncatedDomainCount: number; + truncatedCitedSlots: number; + bySurfaceClass: Array<{ + surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; + label: string; + count: number; + percentage: number; + domainCount: number; + }>; + }; + }; + runId: string; + window: '7d' | '30d' | '90d' | 'all'; + limit: number | null; +}; + +export type TelemetryEventAcceptedDto = { + accepted: boolean; +}; + +export type TrafficBackfillResponse = { + sourceId: string; + runId: string; + status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + windowStart: string; + windowEnd: string; + daysRequested: number; + daysApplied: number; +}; + +export type TrafficEventsResponse = { + windowStart: string; + windowEnd: string; + series: { + granularity: 'hour' | 'day'; + points: Array<{ + bucket: string; + crawlerHits: number; + aiUserFetchHits: number; + aiReferralHits: number; + }>; + }; + totals: { + crawlerHits: number; + crawlerContentHits: number; + crawlerInfraHits: number; + crawlerSegments: { + content: number; + sitemap: number; + robots: number; + asset: number; + other: number; + }; + aiUserFetchHits: number; + aiReferralHits: number; + aiReferralPaidHits: number; + aiReferralOrganicHits: number; + aiReferralUnknownHits: number; + }; + eventRows: { + total: number; + returned: number; + truncated: boolean; + }; + events: Array<{ + kind: 'crawler'; + sourceId: string; + tsHour: string; + botId: string; + operator: string; + verificationStatus: string; + pathNormalized: string; + pathClass: 'content' | 'sitemap' | 'robots' | 'asset' | 'other'; + status: number; + hits: number; + } | { + kind: 'ai-user-fetch'; + sourceId: string; + tsHour: string; + botId: string; + operator: string; + verificationStatus: string; + pathNormalized: string; + status: number; + hits: number; + } | { + kind: 'ai-referral'; + sourceId: string; + tsHour: string; + product: string; + operator: string; + sourceDomain: string; + evidenceType: string; + landingPathNormalized: string; + status: number; + hits: number; + paidHits: number; + organicHits: number; + unknownHits: number; + }>; +}; + +export type TrafficSourceDetailDto = { + id: string; + projectId: string; + sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; + displayName: string; + status: 'connected' | 'paused' | 'error' | 'archived'; + lastSyncedAt: string | null; + lastCursor: string | null; + lastError: string | null; + skippedThroughAt: string | null; + archivedAt: string | null; + config: { + [key: string]: unknown; + }; + createdAt: string; + updatedAt: string; + totals24h: { + crawlerHits: number; + crawlerContentHits: number; + crawlerInfraHits: number; + crawlerSegments: { + content: number; + sitemap: number; + robots: number; + asset: number; + other: number; + }; + aiUserFetchHits: number; + aiReferralHits: number; + sampleCount: number; + }; + latestRun: { + runId: string; + status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + startedAt: string | null; + finishedAt: string | null; + error: string | null; + } | null; +}; + +export type TrafficSourceDto = { + id: string; + projectId: string; + sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; + displayName: string; + status: 'connected' | 'paused' | 'error' | 'archived'; + lastSyncedAt: string | null; + lastCursor: string | null; + lastError: string | null; + skippedThroughAt: string | null; + archivedAt: string | null; + config: { + [key: string]: unknown; + }; + createdAt: string; + updatedAt: string; +}; + +export type TrafficSourceListResponse = { + sources: Array<{ + id: string; + projectId: string; + sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; + displayName: string; + status: 'connected' | 'paused' | 'error' | 'archived'; + lastSyncedAt: string | null; + lastCursor: string | null; + lastError: string | null; + skippedThroughAt: string | null; + archivedAt: string | null; + config: { + [key: string]: unknown; + }; + createdAt: string; + updatedAt: string; + }>; +}; + +export type TrafficStatusResponse = { + sources: Array<{ + id: string; + projectId: string; + sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; + displayName: string; + status: 'connected' | 'paused' | 'error' | 'archived'; + lastSyncedAt: string | null; + lastCursor: string | null; + lastError: string | null; + skippedThroughAt: string | null; + archivedAt: string | null; + config: { + [key: string]: unknown; + }; + createdAt: string; + updatedAt: string; + totals24h: { + crawlerHits: number; + crawlerContentHits: number; + crawlerInfraHits: number; + crawlerSegments: { + content: number; + sitemap: number; + robots: number; + asset: number; + other: number; + }; + aiUserFetchHits: number; + aiReferralHits: number; + sampleCount: number; + }; + latestRun: { + runId: string; + status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; + startedAt: string | null; + finishedAt: string | null; + error: string | null; + } | null; + }>; +}; + +export type TrafficSyncResponse = { + sourceId: string; + runId: string; + syncedAt: string; + pulledEvents: number; + selfTrafficExcluded: number; + crawlerHits: number; + aiUserFetchHits: number; + aiReferralHits: number; + unknownHits: number; + crawlerBucketRows: number; + aiUserFetchBucketRows: number; + aiReferralBucketRows: number; + sampleRows: number; + windowStart: string; + windowEnd: string; +}; + +export type UserDto = { + id: string; + name: string; + role: 'admin' | 'viewer'; + createdAt: string; + lastLoginAt: string | null; +}; + +export type UserListDto = { + users: Array<{ + id: string; + name: string; + role: 'admin' | 'viewer'; + createdAt: string; + lastLoginAt: string | null; + }>; +}; + +export type CreateUserRequest = { + name: string; + password: string; + role: 'admin' | 'viewer'; +}; + +export type LoginRequest = { + name: string; + password: string; +}; + +export type VisibilityCompareDto = { + project: string; + from: { + month: string; + since: string; + until: string; + runCount: number; + lowRunCount: boolean; + }; + to: { + month: string; + since: string; + until: string; + runCount: number; + lowRunCount: boolean; + }; + basket: { + queryCount: number; + excludedFromOnly: number; + excludedToOnly: number; + providers: Array; + excludedProviders: Array; + }; + metrics: Array<{ + key: 'mention-share-of-voice' | 'cited-share-of-voice' | 'mention-rate' | 'cited-rate'; + label: string; + driftRobust: boolean; + from: { + point: number | null; + ciLow: number | null; + ciHigh: number | null; + numerator: number; + denominator: number; + }; + to: { + point: number | null; + ciLow: number | null; + ciHigh: number | null; + numerator: number; + denominator: number; + }; + rateRatio: number | null; + direction: 'up' | 'down' | 'flat' | null; + verdict: 'within-noise' | 'moved' | 'insufficient-data' | 'model-discontinuous' | 'model-unknown'; + }>; + queriesMentioned: { + from: { + count: number; + of: number; + }; + to: { + count: number; + of: number; + }; + }; + byProvider: Array<{ + provider: string; + from: { + checked: number; + mentioned: number; + cited: number; + }; + to: { + checked: number; + mentioned: number; + cited: number; + }; + }>; + modelChanges: Array<{ + provider: string; + fromModels: Array; + toModels: Array; + }>; + continuity: { + status: 'comparable' | 'model-discontinuous' | 'model-unknown' | 'insufficient-data'; + comparedProviders: Array; + providers: Array<{ + provider: string; + status: 'included' | 'model-discontinuous' | 'model-unknown'; + fromModels: Array; + toModels: Array; + }>; + }; + competitors: { + from: Array<{ + domain: string; + mentions: number; + }>; + to: Array<{ + domain: string; + mentions: number; + }>; + }; +}; + +export type VisibilityStatsDto = { + project: string; + groupBy?: 'provider'; + window: { + since: string | null; + until: string | null; + lastRuns: number | null; + runCount: number; + }; + totals: { + total: number; + checked: number; + mentioned: number; + cited: number; + mentionRate: number | null; + citedRate: number | null; + }; + byProvider?: Array<{ + total: number; + checked: number; + mentioned: number; + cited: number; + mentionRate: number | null; + citedRate: number | null; + provider: string; + firstObserved: string; + lastObserved: string; + }>; + queries: Array<{ + total: number; + checked: number; + mentioned: number; + cited: number; + mentionRate: number | null; + citedRate: number | null; + queryId: string | null; + query: string; + firstObserved: string; + lastObserved: string; + providers?: Array<{ + total: number; + checked: number; + mentioned: number; + cited: number; + mentionRate: number | null; + citedRate: number | null; + provider: string; + firstObserved: string; + lastObserved: string; + }>; + }>; + shareOfVoice?: { + percent: number | null; + projectMentions: number; + competitorMentions: number; + snapshotsWithAnswerText: number; + perCompetitor: Array<{ + domain: string; + mentions: number; + }>; + }; +}; + +export type WordpressAuditPageDto = { + slug: string; + title: string; + status: string; + wordCount: number; + seo: { + title: string | null; + description: string | null; + noindex: boolean | null; + writable: boolean; + writeTargets: Array; + }; + schemaPresent: boolean; + issues: Array<{ + slug: string; + severity: 'high' | 'medium' | 'low'; + code: 'noindex' | 'missing-seo-title' | 'missing-meta-description' | 'missing-schema' | 'thin-content'; + message: string; + }>; +}; + +export type WordpressBulkMetaResultDto = { + env: 'live' | 'staging'; + strategy: 'plugin' | 'manual'; + results: Array<{ + slug: string; + status: 'applied' | 'skipped' | 'manual'; + error?: string; + manualAssist?: { + manualRequired: true; + targetUrl: string; + adminUrl?: string | null; + content: string; + nextSteps: Array; + }; + }>; +}; + +export type WordpressDiffDto = { + slug: string; + live: { + id: number; + slug: string; + title: string; + status: string; + modifiedAt?: string | null; + link?: string | null; + env: 'live' | 'staging'; + content: string; + seo: { + title: string | null; + description: string | null; + noindex: boolean | null; + writable: boolean; + writeTargets: Array; + }; + schemaBlocks: Array<{ + type: string; + json: { + [key: string]: unknown; + }; + }>; + contentHash: string; + contentSnippet: string; + }; + staging: { + id: number; + slug: string; + title: string; + status: string; + modifiedAt?: string | null; + link?: string | null; + env: 'live' | 'staging'; + content: string; + seo: { + title: string | null; + description: string | null; + noindex: boolean | null; + writable: boolean; + writeTargets: Array; + }; + schemaBlocks: Array<{ + type: string; + json: { + [key: string]: unknown; + }; + }>; + contentHash: string; + contentSnippet: string; + }; + hasDifferences: boolean; + differences: { + title: boolean; + slug: boolean; + content: boolean; + seoTitle: boolean; + seoDescription: boolean; + noindex: boolean; + schema: boolean; + }; +}; + +export type WordpressManualAssistDto = { + manualRequired: true; + targetUrl: string; + adminUrl?: string | null; + content: string; + nextSteps: Array; +}; + +export type WordpressOnboardResultDto = { + projectName: string; + steps: Array<{ + name: string; + status: 'completed' | 'skipped' | 'failed'; + summary?: string; + error?: string; + }>; +}; + +export type WordpressPageDetailDto = { + id: number; + slug: string; + title: string; + status: string; + modifiedAt?: string | null; + link?: string | null; + env: 'live' | 'staging'; + content: string; + seo: { + title: string | null; + description: string | null; + noindex: boolean | null; + writable: boolean; + writeTargets: Array; + }; + schemaBlocks: Array<{ + type: string; + json: { + [key: string]: unknown; + }; + }>; +}; + +export type WordpressPageSummaryDto = { + id: number; + slug: string; + title: string; + status: string; + modifiedAt?: string | null; + link?: string | null; +}; + +export type WordpressSchemaBlockDto = { + type: string; + json: { + [key: string]: unknown; + }; +}; + +export type WordpressSchemaDeployResultDto = { + env: 'live' | 'staging'; + results: Array<{ + slug: string; + status: 'deployed' | 'stripped' | 'skipped' | 'failed'; + schemasInjected?: Array; + manualAssist?: { + manualRequired: true; + targetUrl: string; + adminUrl?: string | null; + content: string; + nextSteps: Array; + }; + error?: string; + }>; +}; + +export type WordpressSchemaStatusResultDto = { + env: 'live' | 'staging'; + pages: Array<{ + slug: string; + title: string; + canonrySchemas: Array; + thirdPartySchemas: Array; + hasCanonrySchema: boolean; + }>; +}; + +export type WordpressStatusDto = { + connected: boolean; + projectName: string; + defaultEnv: 'live' | 'staging'; + live: { + url: string; + reachable: boolean; + pageCount?: number | null; + version?: string | null; + error?: string | null; + plugins?: Array; + authenticatedUser?: { + id: number; + slug: string; + } | null; + } | null; + staging: { + url: string; + reachable: boolean; + pageCount?: number | null; + version?: string | null; + error?: string | null; + plugins?: Array; + authenticatedUser?: { + id: number; + slug: string; + } | null; + } | null; + adminUrl?: string | null; +}; + +export type ErrorEnvelope = { + error: { + code: string; + message: string; + details?: unknown; + }; +}; + +export type GetApiV1OpenapiJsonData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/openapi.json'; +}; + +export type GetApiV1OpenapiJsonResponses = { + /** + * OpenAPI document. + */ + 200: { + [key: string]: unknown; + }; +}; + +export type GetApiV1OpenapiJsonResponse = GetApiV1OpenapiJsonResponses[keyof GetApiV1OpenapiJsonResponses]; + +export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitData = { + body: { + sitemapUrls: Array; + }; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/google/gsc/sitemaps/submit'; +}; + +export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors = { + /** + * Invalid sitemap request, property ownership, or OAuth scope. + */ + 400: ErrorEnvelope; + /** + * Project or connection not found. + */ + 404: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitError = PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors[keyof PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors]; + +export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses = { + /** + * Per-sitemap submission results returned. + */ + 200: GscSubmitSitemapsResponseDto; +}; + +export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponse = PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses[keyof PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses]; + +export type DeleteApiV1ProjectsByNameData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}'; +}; + +export type DeleteApiV1ProjectsByNameErrors = { + /** + * Project not found. + */ + 404: ErrorEnvelope; +}; + +export type DeleteApiV1ProjectsByNameError = DeleteApiV1ProjectsByNameErrors[keyof DeleteApiV1ProjectsByNameErrors]; + +export type DeleteApiV1ProjectsByNameResponses = { + /** + * Project deleted. + */ + 204: void; +}; + +export type DeleteApiV1ProjectsByNameResponse = DeleteApiV1ProjectsByNameResponses[keyof DeleteApiV1ProjectsByNameResponses]; + +export type GetApiV1ProjectsByNameData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}'; +}; + +export type GetApiV1ProjectsByNameErrors = { + /** + * Project not found. + */ + 404: ErrorEnvelope; +}; + +export type GetApiV1ProjectsByNameError = GetApiV1ProjectsByNameErrors[keyof GetApiV1ProjectsByNameErrors]; + +export type GetApiV1ProjectsByNameResponses = { + /** + * Project returned. + */ + 200: ProjectDto; +}; + +export type GetApiV1ProjectsByNameResponse = GetApiV1ProjectsByNameResponses[keyof GetApiV1ProjectsByNameResponses]; + +export type PutApiV1ProjectsByNameData = { + body: ProjectUpsertRequest; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}'; +}; + +export type PutApiV1ProjectsByNameResponses = { + /** + * Project updated. + */ + 200: ProjectDto; + /** + * Project created. + */ + 201: ProjectDto; +}; + +export type PutApiV1ProjectsByNameResponse = PutApiV1ProjectsByNameResponses[keyof PutApiV1ProjectsByNameResponses]; + +export type PostApiV1ProjectsByNameMeasurementDiscoveryData = { + body: MeasurementDiscoveryRequest; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-discovery'; +}; + +export type PostApiV1ProjectsByNameMeasurementDiscoveryErrors = { + /** + * The sitemap URL or discovery rule is invalid. + */ + 400: ErrorEnvelope; + /** + * The API key lacks measurement-plan.write. + */ + 403: ErrorEnvelope; + /** + * Project not found. + */ + 404: ErrorEnvelope; + /** + * The sitemap could not be fetched safely. + */ + 502: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementDiscoveryError = PostApiV1ProjectsByNameMeasurementDiscoveryErrors[keyof PostApiV1ProjectsByNameMeasurementDiscoveryErrors]; + +export type PostApiV1ProjectsByNameMeasurementDiscoveryResponses = { + /** + * Deterministic sitemap classification returned. + */ + 200: MeasurementDiscoveryResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementDiscoveryResponse = PostApiV1ProjectsByNameMeasurementDiscoveryResponses[keyof PostApiV1ProjectsByNameMeasurementDiscoveryResponses]; + +export type GetApiV1ProjectsByNameMeasurementPlanData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan'; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanErrors = { + /** + * Project not found. + */ + 404: ErrorEnvelope; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanError = GetApiV1ProjectsByNameMeasurementPlanErrors[keyof GetApiV1ProjectsByNameMeasurementPlanErrors]; + +export type GetApiV1ProjectsByNameMeasurementPlanResponses = { + /** + * Active measurement plan returned. + */ + 200: MeasurementPlanResponse; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanResponse = GetApiV1ProjectsByNameMeasurementPlanResponses[keyof GetApiV1ProjectsByNameMeasurementPlanResponses]; + +export type PutApiV1ProjectsByNameMeasurementPlanData = { + body: MeasurementPlanPublishRequest; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan'; +}; + +export type PutApiV1ProjectsByNameMeasurementPlanErrors = { + /** + * The measurement plan or publish request is invalid. + */ + 400: ErrorEnvelope; + /** + * The API key lacks measurement-plan.write. + */ + 403: ErrorEnvelope; + /** + * Project not found. + */ + 404: ErrorEnvelope; + /** + * The active measurement-plan revision changed after the caller loaded it. + */ + 409: ErrorEnvelope; +}; + +export type PutApiV1ProjectsByNameMeasurementPlanError = PutApiV1ProjectsByNameMeasurementPlanErrors[keyof PutApiV1ProjectsByNameMeasurementPlanErrors]; + +export type PutApiV1ProjectsByNameMeasurementPlanResponses = { + /** + * The identical active revision was returned. + */ + 200: MeasurementPlanResponse; + /** + * A new immutable revision was published. + */ + 201: MeasurementPlanResponse; +}; + +export type PutApiV1ProjectsByNameMeasurementPlanResponse = PutApiV1ProjectsByNameMeasurementPlanResponses[keyof PutApiV1ProjectsByNameMeasurementPlanResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewData = { + body: MeasurementPlanInput; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/compile-preview'; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors = { + /** + * The API key lacks measurement-plan.write. + */ + 403: ErrorEnvelope; + /** + * Project not found. + */ + 404: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewError = PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors[keyof PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses = { + /** + * Compiled measurement-plan preview returned. + */ + 200: MeasurementPlanCompilePreviewResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponse = PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses[keyof PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewData = { + body: MeasurementPlanInput; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/diff-preview'; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors = { + /** + * The API key lacks measurement-plan.write. + */ + 403: ErrorEnvelope; + /** + * Project not found. + */ + 404: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewError = PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses = { + /** + * Semantic measurement-plan diff returned. + */ + 200: MeasurementPlanDiffPreviewResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponse = PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses]; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/versions'; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsErrors = { + /** + * Project not found. + */ + 404: ErrorEnvelope; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsError = GetApiV1ProjectsByNameMeasurementPlanVersionsErrors[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsErrors]; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsResponses = { + /** + * Measurement-plan revision metadata returned. + */ + 200: MeasurementPlanVersionsResponse; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsResponse = GetApiV1ProjectsByNameMeasurementPlanVersionsResponses[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + /** + * Stable Target or group key to retire. + */ + stableKey: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/segments/{stableKey}/retire'; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors = { + /** + * The segment remains in the active measurement-plan revision. + */ + 400: ErrorEnvelope; + /** + * The API key lacks measurement-plan.write. + */ + 403: ErrorEnvelope; + /** + * Project or measurement segment not found. + */ + 404: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireError = PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors[keyof PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses = { + /** + * Segment retirement state returned. + */ + 200: MeasurementSegmentRetirementResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponse = PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses[keyof PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses]; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + /** + * Immutable project-local measurement-plan revision. + */ + revision: number; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/versions/{revision}'; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors = { + /** + * Project or revision not found. + */ + 404: ErrorEnvelope; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionError = GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors]; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses = { + /** + * Immutable measurement-plan revision returned. + */ + 200: MeasurementPlanVersionResponse; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponse = GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses]; + +export type GetApiV1ProjectsByNameMeasurementReportData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + }; + query: { + /** + * Immutable project-local measurement-plan revision to report. + */ + revision: number; + }; + url: '/api/v1/projects/{name}/measurement-report'; +}; + +export type GetApiV1ProjectsByNameMeasurementReportErrors = { + /** + * The revision query parameter is invalid. + */ + 400: ErrorEnvelope; + /** + * Project or measurement-plan revision not found. + */ + 404: ErrorEnvelope; +}; + +export type GetApiV1ProjectsByNameMeasurementReportError = GetApiV1ProjectsByNameMeasurementReportErrors[keyof GetApiV1ProjectsByNameMeasurementReportErrors]; + +export type GetApiV1ProjectsByNameMeasurementReportResponses = { + /** + * Revision-pinned measurement report returned. + */ + 200: MeasurementReportResponse; }; -export type ProjectSearchResponseDto = { - query: string; - totalHits: number; - truncated: boolean; - hits: Array<{ - kind: 'snapshot'; - id: string; - runId: string; - query: string; - provider: string; - model: string | null; - citationState: 'cited' | 'not-cited'; - matchedField: 'answerText' | 'citedDomains' | 'citedUrls' | 'searchQueries' | 'query'; - snippet: string; - createdAt: string; - } | { - kind: 'insight'; - id: string; - runId: string | null; - type: 'regression' | 'gain' | 'opportunity' | 'first-citation' | 'provider-pickup' | 'persistent-gap' | 'competitor-gained' | 'competitor-lost' | 'gbp-lodging-gap' | 'gbp-listing-discrepancy' | 'gbp-cta-gap' | 'gbp-description-missing' | 'gbp-metric-drop' | 'gbp-keyword-drop'; - severity: 'critical' | 'high' | 'medium' | 'low'; - title: string; - query: string; - provider: string; - matchedField: 'title' | 'query' | 'recommendation' | 'cause'; - snippet: string; - dismissed: boolean; - createdAt: string; - }>; +export type GetApiV1ProjectsByNameMeasurementReportResponse = GetApiV1ProjectsByNameMeasurementReportResponses[keyof GetApiV1ProjectsByNameMeasurementReportResponses]; + +export type GetApiV1ProjectsByNameMeasurementSetupData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-setup'; }; -export type QueryDto = { - id: string; - query: string; - createdAt: string; +export type GetApiV1ProjectsByNameMeasurementSetupErrors = { + /** + * Project not found. + */ + 404: ErrorEnvelope; }; -export type ResultsExportDto = { - schemaVersion: 'canonry.results-export/v1'; - generatedAt: string; - project: { - id: string; +export type GetApiV1ProjectsByNameMeasurementSetupError = GetApiV1ProjectsByNameMeasurementSetupErrors[keyof GetApiV1ProjectsByNameMeasurementSetupErrors]; + +export type GetApiV1ProjectsByNameMeasurementSetupResponses = { + /** + * Setup state and its next action returned. + */ + 200: MeasurementSetupResponse; +}; + +export type GetApiV1ProjectsByNameMeasurementSetupResponse = GetApiV1ProjectsByNameMeasurementSetupResponses[keyof GetApiV1ProjectsByNameMeasurementSetupResponses]; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftData = { + body?: never; + path: { + /** + * Project name. + */ name: string; - displayName: string; - canonicalDomain: string; - country: string; - language: string; }; - filters: { - since: string | null; - until: string | null; - includeProbes: boolean; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft'; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftErrors = { + /** + * Project not found. + */ + 404: ErrorEnvelope; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftError = GetApiV1ProjectsByNameMeasurementPlanDraftErrors[keyof GetApiV1ProjectsByNameMeasurementPlanDraftErrors]; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftResponses = { + /** + * Draft and its ETag returned. + */ + 200: MeasurementDraftResponse; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftResponse = GetApiV1ProjectsByNameMeasurementPlanDraftResponses[keyof GetApiV1ProjectsByNameMeasurementPlanDraftResponses]; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftTargetsData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; }; - recordCount: number; - records: Array<{ - runId: string; - runKind: 'answer-visibility'; - runStatus: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - runTrigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; - runCreatedAt: string; - runStartedAt: string | null; - runFinishedAt: string | null; - snapshotId: string; - snapshotCreatedAt: string; - queryId: string | null; - query: string | null; - provider: string; - model: string | null; - location: string | null; - citationState: 'cited' | 'not-cited'; - cited: boolean; - answerMentioned: boolean | null; - mentionState: 'mentioned' | 'not-mentioned' | null; - citedDomains: Array; - citedUrls: Array | null; - captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; - sourceCount: number | null; - resolvedCount: number | null; - captureVersion: number | null; - competitorOverlap: Array; - recommendedCompetitors: Array; - answerText: string | null; - groundingSources: Array<{ - uri: string; - title: string; - }>; - searchQueries: Array; - }>; + query?: { + /** + * Case-insensitive filter over the returned rows. It never changes a metric denominator. + */ + search?: string; + /** + * Opaque cursor from the previous page. + */ + cursor?: string; + /** + * Page size. Defaults to 50, maximum 100. + */ + limit?: number; + }; + url: '/api/v1/projects/{name}/measurement-plan/draft/targets'; }; -export type RunDetailDto = { - id: string; - projectId: string; - kind: 'answer-visibility' | 'site-audit' | 'gsc-sync' | 'inspect-sitemap' | 'ga-sync' | 'bing-inspect' | 'bing-inspect-sitemap' | 'backlink-extract' | 'traffic-sync' | 'aeo-discover-seed' | 'aeo-discover-probe' | 'gbp-sync' | 'ads-sync'; - status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - trigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; - measurementPlanVersionId?: string | null; - measurementManifest?: { - [key: string]: unknown; - } | null; - measurementScope?: { - groups: Array; - targets: Array; - queries: Array; - resolvedTargets: Array; - } | null; - measurementExecutionIdentity?: { - schemaVersion: 1; - providers: Array; - models: { - [key: string]: string; - }; - checksum: string; - } | null; - location?: string | null; - queries?: Array | null; - startedAt?: string | null; - finishedAt?: string | null; - error?: { - message?: string; - providers?: { - [key: string]: { - message: string; - raw?: unknown; - }; - }; - } | null; - queryBasketRevision?: number | null; - createdAt: string; - snapshots?: Array<{ - id: string; - runId: string; - queryId: string; - query?: string; - provider: string; - citationState: 'cited' | 'not-cited'; - answerMentioned?: boolean; - visibilityState?: 'visible' | 'not-visible'; - mentionState?: 'mentioned' | 'not-mentioned'; - transition?: 'new' | 'cited' | 'lost' | 'emerging' | 'not-cited'; - answerText?: string | null; - citedDomains: Array; - citedUrls: Array | null; - captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; - sourceCount: number | null; - resolvedCount: number | null; - captureVersion: number | null; - retrievalStatus: 'used' | 'not-used' | 'unknown' | 'not-applicable' | null; - retrievalContract: 'native-auto-v1' | 'search-required-v1' | null; - competitorOverlap: Array; - recommendedCompetitors: Array; - matchedTerms: Array; - groundingSources: Array<{ - uri: string; - title: string; - }>; - searchQueries: Array; - model?: string | null; - servedModel?: string | null; - location?: string | null; - requestedContext?: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - supportedContext?: { - status: 'applied' | 'ignored' | 'browser-implicit' | 'unknown'; - resolved?: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - } | null; - createdAt: string; - }>; +export type GetApiV1ProjectsByNameMeasurementPlanDraftTargetsErrors = { + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftTargetsError = GetApiV1ProjectsByNameMeasurementPlanDraftTargetsErrors[keyof GetApiV1ProjectsByNameMeasurementPlanDraftTargetsErrors]; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftTargetsResponses = { + /** + * Draft Target page returned. + */ + 200: MeasurementDraftTargetPage; +}; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftTargetsResponse = GetApiV1ProjectsByNameMeasurementPlanDraftTargetsResponses[keyof GetApiV1ProjectsByNameMeasurementPlanDraftTargetsResponses]; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsData = { + body?: never; + path: { + /** + * Project name. + */ + name: string; + }; + query?: { + /** + * Case-insensitive filter over the returned rows. It never changes a metric denominator. + */ + search?: string; + /** + * Opaque cursor from the previous page. + */ + cursor?: string; + /** + * Page size. Defaults to 50, maximum 100. + */ + limit?: number; + }; + url: '/api/v1/projects/{name}/measurement-plan/draft/assignments'; }; -export type RunDto = { - id: string; - projectId: string; - kind: 'answer-visibility' | 'site-audit' | 'gsc-sync' | 'inspect-sitemap' | 'ga-sync' | 'bing-inspect' | 'bing-inspect-sitemap' | 'backlink-extract' | 'traffic-sync' | 'aeo-discover-seed' | 'aeo-discover-probe' | 'gbp-sync' | 'ads-sync'; - status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - trigger: 'manual' | 'scheduled' | 'config-apply' | 'backfill' | 'probe'; - measurementPlanVersionId?: string | null; - measurementManifest?: { - [key: string]: unknown; - } | null; - measurementScope?: { - groups: Array; - targets: Array; - queries: Array; - resolvedTargets: Array; - } | null; - measurementExecutionIdentity?: { - schemaVersion: 1; - providers: Array; - models: { - [key: string]: string; - }; - checksum: string; - } | null; - location?: string | null; - queries?: Array | null; - startedAt?: string | null; - finishedAt?: string | null; - error?: { - message?: string; - providers?: { - [key: string]: { - message: string; - raw?: unknown; - }; - }; - } | null; - queryBasketRevision?: number | null; - createdAt: string; +export type GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsErrors = { + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; }; -export type SchedulableRunKind = 'answer-visibility' | 'traffic-sync' | 'gbp-sync' | 'data-refresh' | 'backlinks-sync' | 'site-audit' | 'ads-sync' | 'doctor'; +export type GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsError = GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsErrors[keyof GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsErrors]; -export type ScheduleDto = { - id: string; - projectId: string; - kind: SchedulableRunKind; - cronExpr: string; - preset?: string | null; - timezone: string; - enabled: boolean; - providers: Array; - sourceId?: string | null; - lastRunAt?: string | null; - nextRunAt?: string | null; - createdAt: string; - updatedAt: string; +export type GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsResponses = { + /** + * Draft assignment page returned. + */ + 200: MeasurementDraftAssignmentPage; }; -export type SettingsDto = { - providers: Array<{ - name: string; - displayName?: string; - keyUrl?: string; - modelHint?: string; - model?: string; - defaultModel?: string; - configured: boolean; - quota?: { - maxConcurrency: number; - maxRequestsPerMinute: number; - maxRequestsPerDay: number; - }; - vertexConfigured?: boolean; - }>; - providerCatalog: Array<{ +export type GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsResponse = GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsResponses[keyof GetApiV1ProjectsByNameMeasurementPlanDraftAssignmentsResponses]; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftGroupsData = { + body?: never; + path: { + /** + * Project name. + */ name: string; - displayName: string; - mode: 'api' | 'browser'; - modelConfigurable: boolean; - defaultModel: string; - knownModels: Array<{ - id: string; - displayName: string; - tier: 'flagship' | 'standard' | 'fast' | 'economy'; - }>; - modelValidationPattern: { - source: string; - flags: string; - }; - modelValidationHint: string; - }>; - google: { - configured: boolean; }; - bing: { - configured: boolean; + query?: { + /** + * Case-insensitive filter over the returned rows. It never changes a metric denominator. + */ + search?: string; + /** + * Opaque cursor from the previous page. + */ + cursor?: string; + /** + * Page size. Defaults to 50, maximum 100. + */ + limit?: number; }; + url: '/api/v1/projects/{name}/measurement-plan/draft/groups'; }; -export type SiteAuditPagesResponseDto = { - project: string; - runId: string | null; - auditedAt: string | null; - total: number; - pages: Array<{ - url: string; - overallScore: number; - status: 'success' | 'error'; - error?: string | null; - factors: Array<{ - id: string; - name: string; - weight: number; - score: number; - }>; - }>; +export type GetApiV1ProjectsByNameMeasurementPlanDraftGroupsErrors = { + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; }; -export type SiteAuditRunResponseDto = { - runId: string; - status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; +export type GetApiV1ProjectsByNameMeasurementPlanDraftGroupsError = GetApiV1ProjectsByNameMeasurementPlanDraftGroupsErrors[keyof GetApiV1ProjectsByNameMeasurementPlanDraftGroupsErrors]; + +export type GetApiV1ProjectsByNameMeasurementPlanDraftGroupsResponses = { + /** + * Draft group page returned. + */ + 200: MeasurementDraftGroupPage; }; -export type SiteAuditScoreDto = { - project: string; - hasData: boolean; - runId: string | null; - runStatus: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - sitemapUrl: string | null; - auditedAt: string | null; - aggregateScore: number; - pagesDiscovered: number; - pagesAudited: number; - pagesSkipped: number; - pagesErrored: number; - deltaScore: number | null; - trend: 'up' | 'down' | 'flat'; - previousScore: number | null; - previousAuditedAt: string | null; - factors: Array<{ - id: string; +export type GetApiV1ProjectsByNameMeasurementPlanDraftGroupsResponse = GetApiV1ProjectsByNameMeasurementPlanDraftGroupsResponses[keyof GetApiV1ProjectsByNameMeasurementPlanDraftGroupsResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateData = { + body: MeasurementDraftCreateRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; + path: { + /** + * Project name. + */ name: string; - weight: number; - avgScore: number; - status: 'pass' | 'partial' | 'fail'; - pagesPassing: number; - pagesPartial: number; - pagesFailing: number; - }>; - crossCuttingIssues: Array<{ - factorId: string; - factorName: string; - avgScore: number; - affectedPages: number; - totalPages: number; - affectedPct: number; - topRecommendations: Array; - }>; - prioritizedFixes: Array; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/create'; }; -export type SiteAuditTrendResponseDto = { - project: string; - points: Array<{ - runId: string; - auditedAt: string; - aggregateScore: number; - pagesAudited: number; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type SnapshotDiffResponse = { - run1: string; - run2: string; - diff: Array<{ - queryId: string | null; - query: string | null; - run1State: 'cited' | 'not-cited'; - run2State: 'cited' | 'not-cited'; - run1AnswerMentioned: boolean | null; - run2AnswerMentioned: boolean | null; - run1VisibilityState: 'visible' | 'not-visible'; - run2VisibilityState: 'visible' | 'not-visible'; - run1MentionState?: 'mentioned' | 'not-mentioned'; - run2MentionState?: 'mentioned' | 'not-mentioned'; - changed: boolean; - visibilityChanged: boolean; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsCreateResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapData = { + body: MeasurementDraftImportSitemapRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/import-sitemap'; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type SnapshotListResponse = { - snapshots: Array<{ - id: string; - runId: string; - queryId: string; - query?: string; - provider: string; - citationState: 'cited' | 'not-cited'; - answerMentioned?: boolean; - visibilityState?: 'visible' | 'not-visible'; - mentionState?: 'mentioned' | 'not-mentioned'; - transition?: 'new' | 'cited' | 'lost' | 'emerging' | 'not-cited'; - answerText?: string | null; - citedDomains: Array; - citedUrls: Array | null; - captureStatus: 'complete' | 'partial' | 'failed' | 'unsupported' | null; - sourceCount: number | null; - resolvedCount: number | null; - captureVersion: number | null; - retrievalStatus: 'used' | 'not-used' | 'unknown' | 'not-applicable' | null; - retrievalContract: 'native-auto-v1' | 'search-required-v1' | null; - competitorOverlap: Array; - recommendedCompetitors: Array; - matchedTerms: Array; - groundingSources: Array<{ - uri: string; - title: string; - }>; - searchQueries: Array; - model?: string | null; - servedModel?: string | null; - location?: string | null; - requestedContext?: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - supportedContext?: { - status: 'applied' | 'ignored' | 'browser-implicit' | 'unknown'; - resolved?: { - label: string; - city: string; - region: string; - country: string; - timezone?: string; - } | null; - } | null; - createdAt: string; - }>; - total: number; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type SnapshotReportDto = { - companyName: string; - domain: string; - homepageUrl: string; - generatedAt: string; - queries: Array; - competitors: Array; - profile: { - industry: string; - summary: string; - services: Array; - categoryTerms: Array; - }; - audit: { - url: string; - finalUrl: string; - auditedAt: string; - overallScore: number; - summary: string; - factors: Array<{ - id: string; - name: string; - weight: number; - score: number; - findings: Array<{ - type: string; - message: string; - }>; - recommendations: Array; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsImportSitemapResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionData = { + body: MeasurementDraftApplySitemapSelectionRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - queryResults: Array<{ - query: string; - providerResults: Array<{ - provider: string; - displayName: string; - model?: string | null; - mentioned: boolean; - cited: boolean; - describedAccurately: 'yes' | 'no' | 'unknown' | 'not-mentioned'; - accuracyNotes?: string | null; - incorrectClaims: Array; - recommendedCompetitors: Array; - citedDomains: Array; - groundingSources: Array<{ - uri: string; - title: string; - }>; - searchQueries: Array; - answerText: string; - error?: string | null; - }>; - }>; - summary: { - totalQueries: number; - totalProviders: number; - totalComparisons: number; - mentionCount: number; - citationCount: number; - topCompetitors: Array<{ - name: string; - count: number; - }>; - visibilityGap: string; - whatThisMeans: Array; - recommendedActions: Array; + path: { + /** + * Project name. + */ + name: string; }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/apply-sitemap-selection'; }; -export type SourceBreakdownDto = { - overall: Array<{ - category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; - label: string; - count: number; - percentage: number; - topDomains: Array<{ - domain: string; - count: number; - }>; - }>; - byQuery: { - [key: string]: Array<{ - category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; - label: string; - count: number; - percentage: number; - topDomains: Array<{ - domain: string; - count: number; - }>; - }>; - }; - ranked: { - totalCitedSlots: number; - domainTotal: number; - entries: Array<{ - domain: string; - count: number; - percentage: number; - category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; - label: string; - surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; - }>; - truncatedDomainCount: number; - truncatedCitedSlots: number; - bySurfaceClass: Array<{ - surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; - label: string; - count: number; - percentage: number; - domainCount: number; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplySitemapSelectionResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetData = { + body: MeasurementDraftUpsertTargetRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - byProvider: { - [key: string]: { - totalCitedSlots: number; - domainTotal: number; - entries: Array<{ - domain: string; - count: number; - percentage: number; - category: 'competitor' | 'directory' | 'social' | 'forum' | 'news' | 'reference' | 'blog' | 'ecommerce' | 'video' | 'academic' | 'other'; - label: string; - surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; - }>; - truncatedDomainCount: number; - truncatedCitedSlots: number; - bySurfaceClass: Array<{ - surfaceClass: 'own' | 'direct-competitor' | 'ota-aggregator' | 'editorial-media' | 'other'; - label: string; - count: number; - percentage: number; - domainCount: number; - }>; - }; + path: { + /** + * Project name. + */ + name: string; }; - runId: string; - window: '7d' | '30d' | '90d' | 'all'; - limit: number | null; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/upsert-target'; }; -export type TelemetryEventAcceptedDto = { - accepted: boolean; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type TrafficBackfillResponse = { - sourceId: string; - runId: string; - status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - windowStart: string; - windowEnd: string; - daysRequested: number; - daysApplied: number; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type TrafficEventsResponse = { - windowStart: string; - windowEnd: string; - series: { - granularity: 'hour' | 'day'; - points: Array<{ - bucket: string; - crawlerHits: number; - aiUserFetchHits: number; - aiReferralHits: number; - }>; - }; - totals: { - crawlerHits: number; - crawlerContentHits: number; - crawlerInfraHits: number; - crawlerSegments: { - content: number; - sitemap: number; - robots: number; - asset: number; - other: number; - }; - aiUserFetchHits: number; - aiReferralHits: number; - aiReferralPaidHits: number; - aiReferralOrganicHits: number; - aiReferralUnknownHits: number; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertTargetResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetData = { + body: MeasurementDraftRenameTargetRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - eventRows: { - total: number; - returned: number; - truncated: boolean; + path: { + /** + * Project name. + */ + name: string; }; - events: Array<{ - kind: 'crawler'; - sourceId: string; - tsHour: string; - botId: string; - operator: string; - verificationStatus: string; - pathNormalized: string; - pathClass: 'content' | 'sitemap' | 'robots' | 'asset' | 'other'; - status: number; - hits: number; - } | { - kind: 'ai-user-fetch'; - sourceId: string; - tsHour: string; - botId: string; - operator: string; - verificationStatus: string; - pathNormalized: string; - status: number; - hits: number; - } | { - kind: 'ai-referral'; - sourceId: string; - tsHour: string; - product: string; - operator: string; - sourceDomain: string; - evidenceType: string; - landingPathNormalized: string; - status: number; - hits: number; - paidHits: number; - organicHits: number; - unknownHits: number; - }>; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/rename-target'; }; -export type TrafficSourceDetailDto = { - id: string; - projectId: string; - sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; - displayName: string; - status: 'connected' | 'paused' | 'error' | 'archived'; - lastSyncedAt: string | null; - lastCursor: string | null; - lastError: string | null; - skippedThroughAt: string | null; - archivedAt: string | null; - config: { - [key: string]: unknown; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRenameTargetResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsData = { + body: MeasurementDraftMergeTargetsRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - createdAt: string; - updatedAt: string; - totals24h: { - crawlerHits: number; - crawlerContentHits: number; - crawlerInfraHits: number; - crawlerSegments: { - content: number; - sitemap: number; - robots: number; - asset: number; - other: number; - }; - aiUserFetchHits: number; - aiReferralHits: number; - sampleCount: number; + path: { + /** + * Project name. + */ + name: string; }; - latestRun: { - runId: string; - status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - startedAt: string | null; - finishedAt: string | null; - error: string | null; - } | null; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/merge-targets'; }; -export type TrafficSourceDto = { - id: string; - projectId: string; - sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; - displayName: string; - status: 'connected' | 'paused' | 'error' | 'archived'; - lastSyncedAt: string | null; - lastCursor: string | null; - lastError: string | null; - skippedThroughAt: string | null; - archivedAt: string | null; - config: { - [key: string]: unknown; - }; - createdAt: string; - updatedAt: string; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type TrafficSourceListResponse = { - sources: Array<{ - id: string; - projectId: string; - sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; - displayName: string; - status: 'connected' | 'paused' | 'error' | 'archived'; - lastSyncedAt: string | null; - lastCursor: string | null; - lastError: string | null; - skippedThroughAt: string | null; - archivedAt: string | null; - config: { - [key: string]: unknown; - }; - createdAt: string; - updatedAt: string; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type TrafficStatusResponse = { - sources: Array<{ - id: string; - projectId: string; - sourceType: 'cloud-run' | 'wordpress' | 'cloudflare' | 'vercel' | 'generic-log'; - displayName: string; - status: 'connected' | 'paused' | 'error' | 'archived'; - lastSyncedAt: string | null; - lastCursor: string | null; - lastError: string | null; - skippedThroughAt: string | null; - archivedAt: string | null; - config: { - [key: string]: unknown; - }; - createdAt: string; - updatedAt: string; - totals24h: { - crawlerHits: number; - crawlerContentHits: number; - crawlerInfraHits: number; - crawlerSegments: { - content: number; - sitemap: number; - robots: number; - asset: number; - other: number; - }; - aiUserFetchHits: number; - aiReferralHits: number; - sampleCount: number; - }; - latestRun: { - runId: string; - status: 'queued' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'; - startedAt: string | null; - finishedAt: string | null; - error: string | null; - } | null; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsMergeTargetsResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetData = { + body: MeasurementDraftExcludeTargetRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/exclude-target'; }; -export type TrafficSyncResponse = { - sourceId: string; - runId: string; - syncedAt: string; - pulledEvents: number; - selfTrafficExcluded: number; - crawlerHits: number; - aiUserFetchHits: number; - aiReferralHits: number; - unknownHits: number; - crawlerBucketRows: number; - aiUserFetchBucketRows: number; - aiReferralBucketRows: number; - sampleRows: number; - windowStart: string; - windowEnd: string; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type UserDto = { - id: string; - name: string; - role: 'admin' | 'viewer'; - createdAt: string; - lastLoginAt: string | null; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type UserListDto = { - users: Array<{ - id: string; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsExcludeTargetResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetData = { + body: MeasurementDraftRebindTargetRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; + path: { + /** + * Project name. + */ name: string; - role: 'admin' | 'viewer'; - createdAt: string; - lastLoginAt: string | null; - }>; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/rebind-target'; }; -export type CreateUserRequest = { - name: string; - password: string; - role: 'admin' | 'viewer'; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type LoginRequest = { - name: string; - password: string; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type VisibilityCompareDto = { - project: string; - from: { - month: string; - since: string; - until: string; - runCount: number; - lowRunCount: boolean; - }; - to: { - month: string; - since: string; - until: string; - runCount: number; - lowRunCount: boolean; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRebindTargetResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsData = { + body: MeasurementDraftApplyAssignmentsRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - basket: { - queryCount: number; - excludedFromOnly: number; - excludedToOnly: number; - providers: Array; - excludedProviders: Array; + path: { + /** + * Project name. + */ + name: string; }; - metrics: Array<{ - key: 'mention-share-of-voice' | 'cited-share-of-voice' | 'mention-rate' | 'cited-rate'; - label: string; - driftRobust: boolean; - from: { - point: number | null; - ciLow: number | null; - ciHigh: number | null; - numerator: number; - denominator: number; - }; - to: { - point: number | null; - ciLow: number | null; - ciHigh: number | null; - numerator: number; - denominator: number; - }; - rateRatio: number | null; - direction: 'up' | 'down' | 'flat' | null; - verdict: 'within-noise' | 'moved' | 'insufficient-data' | 'model-discontinuous' | 'model-unknown'; - }>; - queriesMentioned: { - from: { - count: number; - of: number; - }; - to: { - count: number; - of: number; - }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/apply-assignments'; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsApplyAssignmentsResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentData = { + body: MeasurementDraftRemoveAssignmentRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - byProvider: Array<{ - provider: string; - from: { - checked: number; - mentioned: number; - cited: number; - }; - to: { - checked: number; - mentioned: number; - cited: number; - }; - }>; - modelChanges: Array<{ - provider: string; - fromModels: Array; - toModels: Array; - }>; - continuity: { - status: 'comparable' | 'model-discontinuous' | 'model-unknown' | 'insufficient-data'; - comparedProviders: Array; - providers: Array<{ - provider: string; - status: 'included' | 'model-discontinuous' | 'model-unknown'; - fromModels: Array; - toModels: Array; - }>; + path: { + /** + * Project name. + */ + name: string; }; - competitors: { - from: Array<{ - domain: string; - mentions: number; - }>; - to: Array<{ - domain: string; - mentions: number; - }>; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/remove-assignment'; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveAssignmentResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsData = { + body: MeasurementDraftClearAssignmentsRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; + path: { + /** + * Project name. + */ + name: string; }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/clear-assignments'; }; -export type VisibilityStatsDto = { - project: string; - groupBy?: 'provider'; - window: { - since: string | null; - until: string | null; - lastRuns: number | null; - runCount: number; - }; - totals: { - total: number; - checked: number; - mentioned: number; - cited: number; - mentionRate: number | null; - citedRate: number | null; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsClearAssignmentsResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsData = { + body: MeasurementDraftClassifyAssignmentsRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - byProvider?: Array<{ - total: number; - checked: number; - mentioned: number; - cited: number; - mentionRate: number | null; - citedRate: number | null; - provider: string; - firstObserved: string; - lastObserved: string; - }>; - queries: Array<{ - total: number; - checked: number; - mentioned: number; - cited: number; - mentionRate: number | null; - citedRate: number | null; - queryId: string | null; - query: string; - firstObserved: string; - lastObserved: string; - providers?: Array<{ - total: number; - checked: number; - mentioned: number; - cited: number; - mentionRate: number | null; - citedRate: number | null; - provider: string; - firstObserved: string; - lastObserved: string; - }>; - }>; - shareOfVoice?: { - percent: number | null; - projectMentions: number; - competitorMentions: number; - snapshotsWithAnswerText: number; - perCompetitor: Array<{ - domain: string; - mentions: number; - }>; + path: { + /** + * Project name. + */ + name: string; }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/classify-assignments'; }; -export type WordpressAuditPageDto = { - slug: string; - title: string; - status: string; - wordCount: number; - seo: { - title: string | null; - description: string | null; - noindex: boolean | null; - writable: boolean; - writeTargets: Array; - }; - schemaPresent: boolean; - issues: Array<{ - slug: string; - severity: 'high' | 'medium' | 'low'; - code: 'noindex' | 'missing-seo-title' | 'missing-meta-description' | 'missing-schema' | 'thin-content'; - message: string; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type WordpressBulkMetaResultDto = { - env: 'live' | 'staging'; - strategy: 'plugin' | 'manual'; - results: Array<{ - slug: string; - status: 'applied' | 'skipped' | 'manual'; - error?: string; - manualAssist?: { - manualRequired: true; - targetUrl: string; - adminUrl?: string | null; - content: string; - nextSteps: Array; - }; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type WordpressDiffDto = { - slug: string; - live: { - id: number; - slug: string; - title: string; - status: string; - modifiedAt?: string | null; - link?: string | null; - env: 'live' | 'staging'; - content: string; - seo: { - title: string | null; - description: string | null; - noindex: boolean | null; - writable: boolean; - writeTargets: Array; - }; - schemaBlocks: Array<{ - type: string; - json: { - [key: string]: unknown; - }; - }>; - contentHash: string; - contentSnippet: string; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsClassifyAssignmentsResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupData = { + body: MeasurementDraftUpsertGroupRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - staging: { - id: number; - slug: string; - title: string; - status: string; - modifiedAt?: string | null; - link?: string | null; - env: 'live' | 'staging'; - content: string; - seo: { - title: string | null; - description: string | null; - noindex: boolean | null; - writable: boolean; - writeTargets: Array; - }; - schemaBlocks: Array<{ - type: string; - json: { - [key: string]: unknown; - }; - }>; - contentHash: string; - contentSnippet: string; + path: { + /** + * Project name. + */ + name: string; }; - hasDifferences: boolean; - differences: { - title: boolean; - slug: boolean; - content: boolean; - seoTitle: boolean; - seoDescription: boolean; - noindex: boolean; - schema: boolean; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/upsert-group'; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertGroupResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupData = { + body: MeasurementDraftRemoveGroupRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; + path: { + /** + * Project name. + */ + name: string; }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/remove-group'; }; -export type WordpressManualAssistDto = { - manualRequired: true; - targetUrl: string; - adminUrl?: string | null; - content: string; - nextSteps: Array; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type WordpressOnboardResultDto = { - projectName: string; - steps: Array<{ - name: string; - status: 'completed' | 'skipped' | 'failed'; - summary?: string; - error?: string; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type WordpressPageDetailDto = { - id: number; - slug: string; - title: string; - status: string; - modifiedAt?: string | null; - link?: string | null; - env: 'live' | 'staging'; - content: string; - seo: { - title: string | null; - description: string | null; - noindex: boolean | null; - writable: boolean; - writeTargets: Array; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveGroupResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorData = { + body: MeasurementDraftUpsertCompetitorRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; }; - schemaBlocks: Array<{ - type: string; - json: { - [key: string]: unknown; - }; - }>; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/upsert-competitor'; }; -export type WordpressPageSummaryDto = { - id: number; - slug: string; - title: string; - status: string; - modifiedAt?: string | null; - link?: string | null; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type WordpressSchemaBlockDto = { - type: string; - json: { - [key: string]: unknown; - }; -}; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorErrors]; -export type WordpressSchemaDeployResultDto = { - env: 'live' | 'staging'; - results: Array<{ - slug: string; - status: 'deployed' | 'stripped' | 'skipped' | 'failed'; - schemasInjected?: Array; - manualAssist?: { - manualRequired: true; - targetUrl: string; - adminUrl?: string | null; - content: string; - nextSteps: Array; - }; - error?: string; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type WordpressSchemaStatusResultDto = { - env: 'live' | 'staging'; - pages: Array<{ - slug: string; - title: string; - canonrySchemas: Array; - thirdPartySchemas: Array; - hasCanonrySchema: boolean; - }>; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsUpsertCompetitorResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorData = { + body: MeasurementDraftRemoveCompetitorRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; + path: { + /** + * Project name. + */ + name: string; + }; + query?: never; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/remove-competitor'; }; -export type WordpressStatusDto = { - connected: boolean; - projectName: string; - defaultEnv: 'live' | 'staging'; - live: { - url: string; - reachable: boolean; - pageCount?: number | null; - version?: string | null; - error?: string | null; - plugins?: Array; - authenticatedUser?: { - id: number; - slug: string; - } | null; - } | null; - staging: { - url: string; - reachable: boolean; - pageCount?: number | null; - version?: string | null; - error?: string | null; - plugins?: Array; - authenticatedUser?: { - id: number; - slug: string; - } | null; - } | null; - adminUrl?: string | null; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type ErrorEnvelope = { - error: { - code: string; - message: string; - details?: unknown; - }; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorErrors]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorResponses = { + /** + * Draft mutated; the new ETag and counts are returned. + */ + 200: MeasurementDraftMutationResponse; }; -export type GetApiV1OpenapiJsonData = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsRemoveCompetitorResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewData = { body?: never; - path?: never; + path: { + /** + * Project name. + */ + name: string; + }; query?: never; - url: '/api/v1/openapi.json'; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/compile-preview'; }; -export type GetApiV1OpenapiJsonResponses = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewErrors = { /** - * OpenAPI document. + * The action payload is invalid. */ - 200: { - [key: string]: unknown; - }; + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. + */ + 404: ErrorEnvelope; }; -export type GetApiV1OpenapiJsonResponse = GetApiV1OpenapiJsonResponses[keyof GetApiV1OpenapiJsonResponses]; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewErrors]; -export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitData = { - body: { - sitemapUrls: Array; - }; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewResponses = { + /** + * Compiled draft preview returned. Invalid authoring returns ok=false with typed checks. + */ + 200: MeasurementDraftCompilePreviewResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsCompilePreviewResponses]; + +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewData = { + body?: never; path: { /** * Project name. @@ -6429,33 +8903,47 @@ export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitData = { name: string; }; query?: never; - url: '/api/v1/projects/{name}/google/gsc/sitemaps/submit'; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/diff-preview'; }; -export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewErrors = { /** - * Invalid sitemap request, property ownership, or OAuth scope. + * The action payload is invalid. */ 400: ErrorEnvelope; /** - * Project or connection not found. + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. */ 404: ErrorEnvelope; }; -export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitError = PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors[keyof PostApiV1ProjectsByNameGoogleGscSitemapsSubmitErrors]; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewErrors]; -export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewResponses = { /** - * Per-sitemap submission results returned. + * Draft diff returned. Invalid authoring returns ok=false with a null diff. */ - 200: GscSubmitSitemapsResponseDto; + 200: MeasurementDraftDiffPreviewResponse; }; -export type PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponse = PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses[keyof PostApiV1ProjectsByNameGoogleGscSitemapsSubmitResponses]; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiffPreviewResponses]; -export type DeleteApiV1ProjectsByNameData = { - body?: never; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishData = { + body: MeasurementDraftPublishRequest; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; path: { /** * Project name. @@ -6463,29 +8951,59 @@ export type DeleteApiV1ProjectsByNameData = { name: string; }; query?: never; - url: '/api/v1/projects/{name}'; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/publish'; }; -export type DeleteApiV1ProjectsByNameErrors = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishErrors = { + /** + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; /** - * Project not found. + * Project or draft not found. */ 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type DeleteApiV1ProjectsByNameError = DeleteApiV1ProjectsByNameErrors[keyof DeleteApiV1ProjectsByNameErrors]; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishErrors]; -export type DeleteApiV1ProjectsByNameResponses = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishResponses = { /** - * Project deleted. + * The published revision, or the unchanged active revision when the content was identical to it. */ - 204: void; + 200: MeasurementPlanV2PublishResponse; }; -export type DeleteApiV1ProjectsByNameResponse = DeleteApiV1ProjectsByNameResponses[keyof DeleteApiV1ProjectsByNameResponses]; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsPublishResponses]; -export type GetApiV1ProjectsByNameData = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardData = { body?: never; + headers: { + /** + * Current draft ETag. Missing returns 428; stale returns 412. + */ + 'If-Match': string; + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; path: { /** * Project name. @@ -6493,29 +9011,55 @@ export type GetApiV1ProjectsByNameData = { name: string; }; query?: never; - url: '/api/v1/projects/{name}'; + url: '/api/v1/projects/{name}/measurement-plan/draft/actions/discard'; }; -export type GetApiV1ProjectsByNameErrors = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardErrors = { /** - * Project not found. + * The action payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read the draft but not mutate it. + */ + 403: ErrorEnvelope; + /** + * Project or draft not found. */ 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; + /** + * The draft changed since it was loaded. + */ + 412: ErrorEnvelope; + /** + * The draft ETag was not supplied in `If-Match`. + */ + 428: ErrorEnvelope; }; -export type GetApiV1ProjectsByNameError = GetApiV1ProjectsByNameErrors[keyof GetApiV1ProjectsByNameErrors]; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardError = PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardErrors]; -export type GetApiV1ProjectsByNameResponses = { +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardResponses = { /** - * Project returned. + * Draft discarded. */ - 200: ProjectDto; + 200: MeasurementDraftDiscardResponse; }; -export type GetApiV1ProjectsByNameResponse = GetApiV1ProjectsByNameResponses[keyof GetApiV1ProjectsByNameResponses]; +export type PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardResponse = PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDraftActionsDiscardResponses]; -export type PutApiV1ProjectsByNameData = { - body: ProjectUpsertRequest; +export type PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateData = { + body: MeasurementPlanDeactivateRequest; + headers: { + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; path: { /** * Project name. @@ -6523,65 +9067,123 @@ export type PutApiV1ProjectsByNameData = { name: string; }; query?: never; - url: '/api/v1/projects/{name}'; + url: '/api/v1/projects/{name}/measurement-plan/actions/deactivate'; }; -export type PutApiV1ProjectsByNameResponses = { +export type PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateErrors = { /** - * Project updated. + * The caller may read the plan but not deactivate it. */ - 200: ProjectDto; + 403: ErrorEnvelope; /** - * Project created. + * Project or active plan not found. */ - 201: ProjectDto; + 404: ErrorEnvelope; + /** + * The active revision changed after the caller loaded it. + */ + 409: ErrorEnvelope; }; -export type PutApiV1ProjectsByNameResponse = PutApiV1ProjectsByNameResponses[keyof PutApiV1ProjectsByNameResponses]; +export type PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateError = PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateErrors[keyof PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateErrors]; -export type PostApiV1ProjectsByNameMeasurementDiscoveryData = { - body: MeasurementDiscoveryRequest; +export type PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateResponses = { + /** + * The plan pointer was removed. + */ + 200: MeasurementPlanDeactivateResponse; +}; + +export type PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateResponse = PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateResponses[keyof PostApiV1ProjectsByNameMeasurementPlanActionsDeactivateResponses]; + +export type GetApiV1ProjectsByNameMeasurementOverviewData = { + body?: never; path: { /** * Project name. */ name: string; }; - query?: never; - url: '/api/v1/projects/{name}/measurement-discovery'; + query: { + /** + * Reporting scope. + */ + scope: 'all' | 'group' | 'property'; + /** + * Group stable key, required when scope is "group". + */ + groupKey?: string; + /** + * Target stable key, required when scope is "property". + */ + targetKey?: string; + /** + * Restrict to one question class. Never pooled across classes. + */ + queryClass?: 'all' | 'branded' | 'non-brand'; + /** + * Restrict to one answer provider. + */ + provider?: string; + /** + * Restrict to one execution location label. + */ + location?: string; + /** + * Inclusive start of the window (YYYY-MM-DD). + */ + from?: string; + /** + * Inclusive end of the window (YYYY-MM-DD). + */ + to?: string; + /** + * Display this run. It must be pinned to the active revision. This is also the only way to display a scoped spot check. + */ + runId?: string; + /** + * Case-insensitive filter over the returned rows. It never changes a metric denominator. + */ + search?: string; + /** + * Opaque cursor from the previous page. + */ + cursor?: string; + /** + * Page size. Defaults to 50, maximum 100. + */ + limit?: number; + }; + url: '/api/v1/projects/{name}/measurement-overview'; }; -export type PostApiV1ProjectsByNameMeasurementDiscoveryErrors = { +export type GetApiV1ProjectsByNameMeasurementOverviewErrors = { /** - * The sitemap URL or discovery rule is invalid. + * The scope or its required key is invalid. */ 400: ErrorEnvelope; - /** - * The API key lacks measurement-plan.write. - */ - 403: ErrorEnvelope; /** * Project not found. */ 404: ErrorEnvelope; /** - * The sitemap could not be fetched safely. + * The named run is pinned to a different plan revision. */ - 502: ErrorEnvelope; + 422: ErrorEnvelope; }; -export type PostApiV1ProjectsByNameMeasurementDiscoveryError = PostApiV1ProjectsByNameMeasurementDiscoveryErrors[keyof PostApiV1ProjectsByNameMeasurementDiscoveryErrors]; +export type GetApiV1ProjectsByNameMeasurementOverviewError = GetApiV1ProjectsByNameMeasurementOverviewErrors[keyof GetApiV1ProjectsByNameMeasurementOverviewErrors]; -export type PostApiV1ProjectsByNameMeasurementDiscoveryResponses = { +export type GetApiV1ProjectsByNameMeasurementOverviewResponses = { /** - * Deterministic sitemap classification returned. + * Scoped measurement overview returned. */ - 200: MeasurementDiscoveryResponse; + 200: MeasurementOverviewResponse; }; -export type PostApiV1ProjectsByNameMeasurementDiscoveryResponse = PostApiV1ProjectsByNameMeasurementDiscoveryResponses[keyof PostApiV1ProjectsByNameMeasurementDiscoveryResponses]; +export type GetApiV1ProjectsByNameMeasurementOverviewResponse = GetApiV1ProjectsByNameMeasurementOverviewResponses[keyof GetApiV1ProjectsByNameMeasurementOverviewResponses]; -export type GetApiV1ProjectsByNameMeasurementPlanData = { +export type GetApiV1ProjectsByNameMeasurementQuerySetsData = { body?: never; path: { /** @@ -6590,142 +9192,146 @@ export type GetApiV1ProjectsByNameMeasurementPlanData = { name: string; }; query?: never; - url: '/api/v1/projects/{name}/measurement-plan'; + url: '/api/v1/projects/{name}/measurement-query-sets'; }; -export type GetApiV1ProjectsByNameMeasurementPlanErrors = { +export type GetApiV1ProjectsByNameMeasurementQuerySetsErrors = { /** * Project not found. */ 404: ErrorEnvelope; }; -export type GetApiV1ProjectsByNameMeasurementPlanError = GetApiV1ProjectsByNameMeasurementPlanErrors[keyof GetApiV1ProjectsByNameMeasurementPlanErrors]; +export type GetApiV1ProjectsByNameMeasurementQuerySetsError = GetApiV1ProjectsByNameMeasurementQuerySetsErrors[keyof GetApiV1ProjectsByNameMeasurementQuerySetsErrors]; -export type GetApiV1ProjectsByNameMeasurementPlanResponses = { +export type GetApiV1ProjectsByNameMeasurementQuerySetsResponses = { /** - * Active measurement plan returned. + * Query sets returned. */ - 200: MeasurementPlanResponse; + 200: MeasurementQuerySetListResponse; }; -export type GetApiV1ProjectsByNameMeasurementPlanResponse = GetApiV1ProjectsByNameMeasurementPlanResponses[keyof GetApiV1ProjectsByNameMeasurementPlanResponses]; +export type GetApiV1ProjectsByNameMeasurementQuerySetsResponse = GetApiV1ProjectsByNameMeasurementQuerySetsResponses[keyof GetApiV1ProjectsByNameMeasurementQuerySetsResponses]; -export type PutApiV1ProjectsByNameMeasurementPlanData = { - body: MeasurementPlanPublishRequest; +export type DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdData = { + body?: never; path: { /** * Project name. */ name: string; + /** + * Measurement query-set ID. + */ + setId: string; }; query?: never; - url: '/api/v1/projects/{name}/measurement-plan'; + url: '/api/v1/projects/{name}/measurement-query-sets/{setId}'; }; -export type PutApiV1ProjectsByNameMeasurementPlanErrors = { - /** - * The measurement plan or publish request is invalid. - */ - 400: ErrorEnvelope; +export type DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors = { /** - * The API key lacks measurement-plan.write. + * The caller may read query assets but not manage them. */ 403: ErrorEnvelope; /** - * Project not found. + * Project or query set not found. */ 404: ErrorEnvelope; - /** - * The active measurement-plan revision changed after the caller loaded it. - */ - 409: ErrorEnvelope; }; -export type PutApiV1ProjectsByNameMeasurementPlanError = PutApiV1ProjectsByNameMeasurementPlanErrors[keyof PutApiV1ProjectsByNameMeasurementPlanErrors]; +export type DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdError = DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors[keyof DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors]; -export type PutApiV1ProjectsByNameMeasurementPlanResponses = { - /** - * The identical active revision was returned. - */ - 200: MeasurementPlanResponse; +export type DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses = { /** - * A new immutable revision was published. + * Query set deleted. */ - 201: MeasurementPlanResponse; + 204: void; }; -export type PutApiV1ProjectsByNameMeasurementPlanResponse = PutApiV1ProjectsByNameMeasurementPlanResponses[keyof PutApiV1ProjectsByNameMeasurementPlanResponses]; +export type DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponse = DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses[keyof DeleteApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses]; -export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewData = { - body: MeasurementPlanInput; +export type GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdData = { + body?: never; path: { /** * Project name. */ name: string; + /** + * Measurement query-set ID. + */ + setId: string; }; query?: never; - url: '/api/v1/projects/{name}/measurement-plan/compile-preview'; + url: '/api/v1/projects/{name}/measurement-query-sets/{setId}'; }; -export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors = { - /** - * The API key lacks measurement-plan.write. - */ - 403: ErrorEnvelope; +export type GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors = { /** - * Project not found. + * Project or query set not found. */ 404: ErrorEnvelope; }; -export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewError = PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors[keyof PostApiV1ProjectsByNameMeasurementPlanCompilePreviewErrors]; +export type GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdError = GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors[keyof GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors]; -export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses = { +export type GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses = { /** - * Compiled measurement-plan preview returned. + * Query set and its ordered members returned. */ - 200: MeasurementPlanCompilePreviewResponse; + 200: MeasurementQuerySetDetail; }; -export type PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponse = PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses[keyof PostApiV1ProjectsByNameMeasurementPlanCompilePreviewResponses]; +export type GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponse = GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses[keyof GetApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses]; -export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewData = { - body: MeasurementPlanInput; +export type PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdData = { + body: MeasurementQuerySetUpsertRequest; path: { /** * Project name. */ name: string; + /** + * Measurement query-set ID. + */ + setId: string; }; query?: never; - url: '/api/v1/projects/{name}/measurement-plan/diff-preview'; + url: '/api/v1/projects/{name}/measurement-query-sets/{setId}'; }; -export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors = { +export type PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors = { /** - * The API key lacks measurement-plan.write. + * The query set payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read query assets but not manage them. */ 403: ErrorEnvelope; /** - * Project not found. + * Project or referenced query not found. */ 404: ErrorEnvelope; }; -export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewError = PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors[keyof PostApiV1ProjectsByNameMeasurementPlanDiffPreviewErrors]; +export type PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdError = PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors[keyof PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdErrors]; -export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses = { +export type PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses = { /** - * Semantic measurement-plan diff returned. + * Query set replaced. */ - 200: MeasurementPlanDiffPreviewResponse; + 200: MeasurementQuerySetDetail; + /** + * Query set created. + */ + 201: MeasurementQuerySetDetail; }; -export type PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponse = PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses[keyof PostApiV1ProjectsByNameMeasurementPlanDiffPreviewResponses]; +export type PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponse = PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses[keyof PutApiV1ProjectsByNameMeasurementQuerySetsBySetIdResponses]; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsData = { +export type GetApiV1ProjectsByNameMeasurementQueryTemplatesData = { body?: never; path: { /** @@ -6734,28 +9340,28 @@ export type GetApiV1ProjectsByNameMeasurementPlanVersionsData = { name: string; }; query?: never; - url: '/api/v1/projects/{name}/measurement-plan/versions'; + url: '/api/v1/projects/{name}/measurement-query-templates'; }; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsErrors = { +export type GetApiV1ProjectsByNameMeasurementQueryTemplatesErrors = { /** * Project not found. */ 404: ErrorEnvelope; }; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsError = GetApiV1ProjectsByNameMeasurementPlanVersionsErrors[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsErrors]; +export type GetApiV1ProjectsByNameMeasurementQueryTemplatesError = GetApiV1ProjectsByNameMeasurementQueryTemplatesErrors[keyof GetApiV1ProjectsByNameMeasurementQueryTemplatesErrors]; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsResponses = { +export type GetApiV1ProjectsByNameMeasurementQueryTemplatesResponses = { /** - * Measurement-plan revision metadata returned. + * Query templates returned. */ - 200: MeasurementPlanVersionsResponse; + 200: MeasurementQueryTemplateListResponse; }; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsResponse = GetApiV1ProjectsByNameMeasurementPlanVersionsResponses[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsResponses]; +export type GetApiV1ProjectsByNameMeasurementQueryTemplatesResponse = GetApiV1ProjectsByNameMeasurementQueryTemplatesResponses[keyof GetApiV1ProjectsByNameMeasurementQueryTemplatesResponses]; -export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireData = { +export type DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdData = { body?: never; path: { /** @@ -6763,112 +9369,133 @@ export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireData */ name: string; /** - * Stable Target or group key to retire. + * Measurement query-template ID. */ - stableKey: string; + templateId: string; }; query?: never; - url: '/api/v1/projects/{name}/measurement-plan/segments/{stableKey}/retire'; + url: '/api/v1/projects/{name}/measurement-query-templates/{templateId}'; }; -export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors = { - /** - * The segment remains in the active measurement-plan revision. - */ - 400: ErrorEnvelope; +export type DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors = { /** - * The API key lacks measurement-plan.write. + * The caller may read query assets but not manage them. */ 403: ErrorEnvelope; /** - * Project or measurement segment not found. + * Project or query template not found. */ 404: ErrorEnvelope; }; -export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireError = PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors[keyof PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireErrors]; +export type DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdError = DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors[keyof DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors]; -export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses = { +export type DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses = { /** - * Segment retirement state returned. + * Query template deleted. */ - 200: MeasurementSegmentRetirementResponse; + 204: void; }; -export type PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponse = PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses[keyof PostApiV1ProjectsByNameMeasurementPlanSegmentsByStableKeyRetireResponses]; +export type DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponse = DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses[keyof DeleteApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses]; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionData = { - body?: never; +export type PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdData = { + body: MeasurementQueryTemplateUpsertRequest; path: { /** * Project name. */ name: string; /** - * Immutable project-local measurement-plan revision. + * Measurement query-template ID. */ - revision: number; + templateId: string; }; query?: never; - url: '/api/v1/projects/{name}/measurement-plan/versions/{revision}'; + url: '/api/v1/projects/{name}/measurement-query-templates/{templateId}'; }; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors = { +export type PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors = { /** - * Project or revision not found. + * The template payload is invalid. + */ + 400: ErrorEnvelope; + /** + * The caller may read query assets but not manage them. + */ + 403: ErrorEnvelope; + /** + * Project not found. */ 404: ErrorEnvelope; }; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionError = GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionErrors]; +export type PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdError = PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors[keyof PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdErrors]; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses = { +export type PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses = { /** - * Immutable measurement-plan revision returned. + * Query template replaced. */ - 200: MeasurementPlanVersionResponse; + 200: MeasurementQueryTemplateDto; + /** + * Query template created. + */ + 201: MeasurementQueryTemplateDto; }; -export type GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponse = GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses[keyof GetApiV1ProjectsByNameMeasurementPlanVersionsByRevisionResponses]; +export type PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponse = PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses[keyof PutApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdResponses]; -export type GetApiV1ProjectsByNameMeasurementReportData = { - body?: never; +export type PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyData = { + body: MeasurementQueryTemplateApplyRequest; + headers: { + /** + * Replay key. The same key with a different request body returns 409. + */ + 'Idempotency-Key': string; + }; path: { /** * Project name. */ name: string; - }; - query: { /** - * Immutable project-local measurement-plan revision to report. + * Measurement query-template ID. */ - revision: number; + templateId: string; }; - url: '/api/v1/projects/{name}/measurement-report'; + query?: never; + url: '/api/v1/projects/{name}/measurement-query-templates/{templateId}/apply'; }; -export type GetApiV1ProjectsByNameMeasurementReportErrors = { +export type PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyErrors = { /** - * The revision query parameter is invalid. + * A binding does not satisfy the template variables. */ 400: ErrorEnvelope; /** - * Project or measurement-plan revision not found. + * The caller may read query assets but not manage them. + */ + 403: ErrorEnvelope; + /** + * Project or query template not found. */ 404: ErrorEnvelope; + /** + * The idempotency key was already used with a different request body. + */ + 409: ErrorEnvelope; }; -export type GetApiV1ProjectsByNameMeasurementReportError = GetApiV1ProjectsByNameMeasurementReportErrors[keyof GetApiV1ProjectsByNameMeasurementReportErrors]; +export type PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyError = PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyErrors[keyof PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyErrors]; -export type GetApiV1ProjectsByNameMeasurementReportResponses = { +export type PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyResponses = { /** - * Revision-pinned measurement report returned. + * Expansion result returned. */ - 200: MeasurementReportResponse; + 200: MeasurementQueryTemplateApplyResponse; }; -export type GetApiV1ProjectsByNameMeasurementReportResponse = GetApiV1ProjectsByNameMeasurementReportResponses[keyof GetApiV1ProjectsByNameMeasurementReportResponses]; +export type PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyResponse = PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyResponses[keyof PostApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApplyResponses]; export type GetApiV1ProjectsData = { body?: never; diff --git a/packages/api-routes/src/index.ts b/packages/api-routes/src/index.ts index 1efb80213..138bef289 100644 --- a/packages/api-routes/src/index.ts +++ b/packages/api-routes/src/index.ts @@ -14,6 +14,9 @@ import { measurementPlanRoutes } from './measurement-plan.js' import type { MeasurementPlanRoutesOptions } from './measurement-plan.js' import { measurementServiceRoutes } from './measurement-service.js' import type { MeasurementServiceRoutesOptions } from './measurement-service.js' +import { measurementDraftRoutes } from './measurement-draft.js' +import { measurementDiscoveryV2Routes } from './measurement-discovery-v2.js' +import { measurementOverviewRoutes } from './measurement-overview.js' import { applyRoutes } from './apply.js' import type { ApplyRoutesOptions } from './apply.js' import { historyRoutes } from './history.js' @@ -412,6 +415,11 @@ export async function apiRoutes(app: FastifyInstance, opts: ApiRoutesOptions) { await api.register(measurementServiceRoutes, { fetchSitemap: opts.fetchMeasurementSitemap, } satisfies MeasurementServiceRoutesOptions) + // Advanced Measurement v2. Registered here so the slices that fill in the + // handlers never have to touch this file or `openapi.ts`. + await api.register(measurementDraftRoutes) + await api.register(measurementDiscoveryV2Routes) + await api.register(measurementOverviewRoutes) await api.register(applyRoutes, { onScheduleUpdated: opts.onScheduleUpdated, onProjectUpserted: opts.onProjectUpserted, diff --git a/packages/api-routes/src/measurement-discovery-v2.ts b/packages/api-routes/src/measurement-discovery-v2.ts new file mode 100644 index 000000000..55a36c2ae --- /dev/null +++ b/packages/api-routes/src/measurement-discovery-v2.ts @@ -0,0 +1,23 @@ +import type { FastifyInstance } from 'fastify' +import { notImplemented } from '@ainyc/canonry-contracts' + +/** + * Sitemap import and identity rebinding against a setup draft. + * + * These are draft actions, but they belong here rather than in + * `measurement-draft.ts` because the fetch they perform is the hardened one: + * the server dereferences an operator-supplied URL while sitting on a tailnet + * beside internal services. Both produce review proposals only — neither + * publishes a plan nor starts a run. + * + * Registered now, with the bodies landing alongside the hardened fetch. + */ +export async function measurementDiscoveryV2Routes(app: FastifyInstance) { + const pending = (action: string): never => { + throw notImplemented(`Measurement ${action} is not available yet.`) + } + + app.post<{ Params: { name: string } }>('/projects/:name/measurement-plan/draft/actions/import-sitemap', async () => pending('sitemap import')) + + app.post<{ Params: { name: string } }>('/projects/:name/measurement-plan/draft/actions/apply-sitemap-selection', async () => pending('sitemap selection')) +} diff --git a/packages/api-routes/src/measurement-draft.ts b/packages/api-routes/src/measurement-draft.ts new file mode 100644 index 000000000..caecaf025 --- /dev/null +++ b/packages/api-routes/src/measurement-draft.ts @@ -0,0 +1,74 @@ +import type { FastifyInstance } from 'fastify' +import { notImplemented } from '@ainyc/canonry-contracts' + +/** + * Server-side setup draft and the query assets it authors from. + * + * The routes are registered and fully described in `openapi.ts` so the + * generated client and every parity gate already know the surface; the handler + * bodies, their scope checks and their ETag/idempotency enforcement land with + * the draft service. Until then each one answers 501 rather than 404, so a + * caller can tell "not built yet" from "wrong URL". + */ +export async function measurementDraftRoutes(app: FastifyInstance) { + const pending = (action: string): never => { + throw notImplemented(`Measurement ${action} is not available yet.`) + } + + app.get<{ Params: { name: string } }>('/projects/:name/measurement-setup', async () => pending('setup state')) + + app.get<{ Params: { name: string } }>('/projects/:name/measurement-plan/draft', async () => pending('draft reads')) + + app.get<{ Params: { name: string } }>('/projects/:name/measurement-plan/draft/targets', async () => pending('draft Target paging')) + + app.get<{ Params: { name: string } }>('/projects/:name/measurement-plan/draft/assignments', async () => pending('draft assignment paging')) + + app.get<{ Params: { name: string } }>('/projects/:name/measurement-plan/draft/groups', async () => pending('draft group paging')) + + for (const action of [ + 'create', + 'upsert-target', + 'rename-target', + 'merge-targets', + 'exclude-target', + 'rebind-target', + 'apply-assignments', + 'remove-assignment', + 'clear-assignments', + 'classify-assignments', + 'upsert-group', + 'remove-group', + 'upsert-competitor', + 'remove-competitor', + 'publish', + 'discard', + ]) { + app.post<{ Params: { name: string } }>(`/projects/:name/measurement-plan/draft/actions/${action}`, async () => pending(`draft action "${action}"`)) + } + + // Both previews are POSTs only because the draft they compile is far too + // large for a URL, and neither writes a row — see `readSemantic` in `auth.ts`. + for (const action of ['compile-preview', 'diff-preview']) { + app.post<{ Params: { name: string } }>(`/projects/:name/measurement-plan/draft/actions/${action}`, { + config: { readSemantic: true }, + }, async () => pending(`draft action "${action}"`)) + } + + app.post<{ Params: { name: string } }>('/projects/:name/measurement-plan/actions/deactivate', async () => pending('plan deactivation')) + + app.get<{ Params: { name: string } }>('/projects/:name/measurement-query-sets', async () => pending('query sets')) + + app.get<{ Params: { name: string; setId: string } }>('/projects/:name/measurement-query-sets/:setId', async () => pending('query sets')) + + app.put<{ Params: { name: string; setId: string } }>('/projects/:name/measurement-query-sets/:setId', async () => pending('query sets')) + + app.delete<{ Params: { name: string; setId: string } }>('/projects/:name/measurement-query-sets/:setId', async () => pending('query sets')) + + app.get<{ Params: { name: string } }>('/projects/:name/measurement-query-templates', async () => pending('query templates')) + + app.put<{ Params: { name: string; templateId: string } }>('/projects/:name/measurement-query-templates/:templateId', async () => pending('query templates')) + + app.delete<{ Params: { name: string; templateId: string } }>('/projects/:name/measurement-query-templates/:templateId', async () => pending('query templates')) + + app.post<{ Params: { name: string; templateId: string } }>('/projects/:name/measurement-query-templates/:templateId/apply', async () => pending('query template expansion')) +} diff --git a/packages/api-routes/src/measurement-overview.ts b/packages/api-routes/src/measurement-overview.ts new file mode 100644 index 000000000..45d4b5159 --- /dev/null +++ b/packages/api-routes/src/measurement-overview.ts @@ -0,0 +1,15 @@ +import type { FastifyInstance } from 'fastify' +import { notImplemented } from '@ainyc/canonry-contracts' + +/** + * Scoped Advanced aggregates over one run's evidence. + * + * Run selection, the `brandPresence`/`sov` pair and group-only Named Share of + * Voice all land with the reporting slice. The route is registered now so the + * generated client carries the shape from the start. + */ +export async function measurementOverviewRoutes(app: FastifyInstance) { + app.get<{ Params: { name: string } }>('/projects/:name/measurement-overview', async () => { + throw notImplemented('Measurement overview is not available yet.') + }) +} diff --git a/packages/api-routes/src/measurement-plan.ts b/packages/api-routes/src/measurement-plan.ts index 899f1df32..dcfc80c26 100644 --- a/packages/api-routes/src/measurement-plan.ts +++ b/packages/api-routes/src/measurement-plan.ts @@ -11,9 +11,10 @@ import { measurementPlanPublishRequestSchema, measurementPlanRevisionConflict, notFound, - parseStoredMeasurementPlan, + parseStoredMeasurementPlanAnyVersion, validationError, type MeasurementPlan, + type StoredMeasurementPlan, type MeasurementPlanContext, type MeasurementPlanCounts, type MeasurementPlanInput, @@ -43,8 +44,26 @@ type SemanticSelection = { queryIds: string[] } -function parseStoredPlan(canonicalJson: unknown): MeasurementPlan { - return parseStoredMeasurementPlan(canonicalJson) +/** + * Reads a revision of any schema version. Every read surface here must use this + * rather than the v1-only reader: the moment a v2 revision exists, decoding it + * through `parseStoredPlan` throws, and a route that lets that escape answers + * 500 on a perfectly valid stored plan. + */ +function parseStoredPlanAnyVersion(canonicalJson: unknown): StoredMeasurementPlan { + return parseStoredMeasurementPlanAnyVersion(canonicalJson) +} + +/** + * A v1 candidate cannot be diffed against a v2 active revision: they do not + * share an assignment model, so any diff would be invented rather than + * computed. Refusing is the honest answer until the v2 diff lands. + */ +function requireV1Plan(plan: StoredMeasurementPlan, what: string): MeasurementPlan { + if (plan.schemaVersion !== 1) { + throw validationError(`${what} is not available for a schema v${plan.schemaVersion} revision.`) + } + return plan } function versionDto(row: typeof measurementPlanVersions.$inferSelect, active: boolean) { @@ -53,7 +72,9 @@ function versionDto(row: typeof measurementPlanVersions.$inferSelect, active: bo checksum: row.checksum, createdAt: row.createdAt, active, - plan: parseStoredPlan(row.canonicalJson), + // Any schema version: a stored revision must remain readable through the + // revision-detail surfaces regardless of which compiler wrote it. + plan: parseStoredPlanAnyVersion(row.canonicalJson), } } @@ -292,7 +313,7 @@ function assertNoRetiredSegmentReuse( } } -function activePlanContainsKey(plan: MeasurementPlan, stableKey: string): boolean { +function activePlanContainsKey(plan: StoredMeasurementPlan, stableKey: string): boolean { return plan.targets.some(target => target.stableKey === stableKey) || plan.groups.some(group => group.stableKey === stableKey) } @@ -324,7 +345,9 @@ export async function measurementPlanRoutes(app: FastifyInstance, opts: Measurem const preview = compileMeasurementPlanPreview(request.body, compileContextForProject(app, project, opts)) if (!preview.ok) return { ...preview, diff: null } const active = activePlanVersion(app, project.id) - const activePlan = active ? parseStoredPlan(active.version.canonicalJson) : null + const activePlan = active + ? requireV1Plan(parseStoredPlanAnyVersion(active.version.canonicalJson), 'Plan diff') + : null return { ...preview, diff: planDiff(activePlan, preview.plan, active?.version.revision ?? null), @@ -430,7 +453,7 @@ export async function measurementPlanRoutes(app: FastifyInstance, opts: Measurem if (segment.retiredAt !== null) return reply.send({ stableKey: segment.stableKey, retiredAt: segment.retiredAt }) const active = activePlanVersion(app, project.id) - if (active && activePlanContainsKey(parseStoredPlan(active.version.canonicalJson), segment.stableKey)) { + if (active && activePlanContainsKey(parseStoredPlanAnyVersion(active.version.canonicalJson), segment.stableKey)) { throw validationError(`Measurement segment "${segment.stableKey}" is in the active plan. Publish a revision without it before retiring it.`) } diff --git a/packages/api-routes/src/measurement-report-adapter.ts b/packages/api-routes/src/measurement-report-adapter.ts index c3b53a389..92c018447 100644 --- a/packages/api-routes/src/measurement-report-adapter.ts +++ b/packages/api-routes/src/measurement-report-adapter.ts @@ -15,7 +15,7 @@ import { filterCapturedCitedUrls, isVertexGroundingRedirect, parseMeasurementRunManifestV1, - parseStoredMeasurementPlan, + parseStoredMeasurementPlanAnyVersion, RunKinds, RunStatuses, RunTriggers, @@ -297,7 +297,12 @@ export function buildStoredMeasurementReport(db: DatabaseClient, projectId: stri inArray(runs.status, [RunStatuses.completed, RunStatuses.partial]), ne(runs.trigger, RunTriggers.probe), )).orderBy(desc(runs.createdAt), desc(runs.id)).get() - const plan = parseStoredMeasurementPlan(version.canonicalJson) + // Slice 5 replaces this branch with the v2 report path. Until then a v2 + // revision is reported as not-yet-reportable rather than decoded through the + // v1 reader, which would throw and surface as a 500. + const stored = parseStoredMeasurementPlanAnyVersion(version.canonicalJson) + if (stored.schemaVersion !== 1) return { kind: 'no-plan', revision } + const plan = stored let selectedRun = run let manifest: MeasurementRunManifestV1 let legacy = false diff --git a/packages/api-routes/src/openapi-schemas.ts b/packages/api-routes/src/openapi-schemas.ts index 46a1ff749..f0a32a01e 100644 --- a/packages/api-routes/src/openapi-schemas.ts +++ b/packages/api-routes/src/openapi-schemas.ts @@ -106,15 +106,53 @@ import { locationContextSchema, measurementDiscoveryRequestSchema, measurementDiscoveryResponseSchema, + measurementDraftApplyAssignmentsRequestSchema, + measurementDraftApplySitemapSelectionRequestSchema, + measurementDraftAssignmentPageSchema, + measurementDraftClassifyAssignmentsRequestSchema, + measurementDraftClearAssignmentsRequestSchema, + measurementDraftCompilePreviewResponseSchema, + measurementDraftCreateRequestSchema, + measurementDraftDiffPreviewResponseSchema, + measurementDraftDiscardResponseSchema, + measurementDraftExcludeTargetRequestSchema, + measurementDraftGroupPageSchema, + measurementDraftImportSitemapRequestSchema, + measurementDraftMergeTargetsRequestSchema, + measurementDraftMutationResponseSchema, + measurementDraftPublishRequestSchema, + measurementDraftRebindTargetRequestSchema, + measurementDraftRemoveAssignmentRequestSchema, + measurementDraftRemoveCompetitorRequestSchema, + measurementDraftRemoveGroupRequestSchema, + measurementDraftRenameTargetRequestSchema, + measurementDraftResponseSchema, + measurementDraftTargetPageSchema, + measurementDraftUpsertCompetitorRequestSchema, + measurementDraftUpsertGroupRequestSchema, + measurementDraftUpsertTargetRequestSchema, + measurementOverviewResponseSchema, measurementPlanAuthoringSchema, measurementPlanCompilePreviewResponseSchema, + measurementPlanDeactivateRequestSchema, + measurementPlanDeactivateResponseSchema, measurementPlanDiffPreviewResponseSchema, measurementPlanPublishRequestSchema, measurementPlanResponseSchema, + measurementPlanV2PublishResponseSchema, measurementPlanVersionResponseSchema, measurementPlanVersionsResponseSchema, + measurementQuerySetDetailSchema, + measurementQuerySetListResponseSchema, + measurementQuerySetUpsertRequestSchema, + measurementQueryTemplateApplyRequestSchema, + measurementQueryTemplateApplyResponseSchema, + measurementQueryTemplateListResponseSchema, + measurementQueryTemplateSchema, + measurementQueryTemplateUpsertRequestSchema, measurementReportResponseSchema, measurementSegmentRetirementResponseSchema, + measurementSetupResponseSchema, notificationDtoSchema, onboardingTelemetryEventSchema, organicEvidenceDtoSchema, @@ -290,15 +328,53 @@ const SCHEMA_TABLE = { LocationContext: locationContextSchema, MeasurementDiscoveryRequest: measurementDiscoveryRequestSchema, MeasurementDiscoveryResponse: measurementDiscoveryResponseSchema, + MeasurementDraftApplyAssignmentsRequest: measurementDraftApplyAssignmentsRequestSchema, + MeasurementDraftApplySitemapSelectionRequest: measurementDraftApplySitemapSelectionRequestSchema, + MeasurementDraftAssignmentPage: measurementDraftAssignmentPageSchema, + MeasurementDraftClassifyAssignmentsRequest: measurementDraftClassifyAssignmentsRequestSchema, + MeasurementDraftClearAssignmentsRequest: measurementDraftClearAssignmentsRequestSchema, + MeasurementDraftCompilePreviewResponse: measurementDraftCompilePreviewResponseSchema, + MeasurementDraftCreateRequest: measurementDraftCreateRequestSchema, + MeasurementDraftDiffPreviewResponse: measurementDraftDiffPreviewResponseSchema, + MeasurementDraftDiscardResponse: measurementDraftDiscardResponseSchema, + MeasurementDraftExcludeTargetRequest: measurementDraftExcludeTargetRequestSchema, + MeasurementDraftGroupPage: measurementDraftGroupPageSchema, + MeasurementDraftImportSitemapRequest: measurementDraftImportSitemapRequestSchema, + MeasurementDraftMergeTargetsRequest: measurementDraftMergeTargetsRequestSchema, + MeasurementDraftMutationResponse: measurementDraftMutationResponseSchema, + MeasurementDraftPublishRequest: measurementDraftPublishRequestSchema, + MeasurementDraftRebindTargetRequest: measurementDraftRebindTargetRequestSchema, + MeasurementDraftRemoveAssignmentRequest: measurementDraftRemoveAssignmentRequestSchema, + MeasurementDraftRemoveCompetitorRequest: measurementDraftRemoveCompetitorRequestSchema, + MeasurementDraftRemoveGroupRequest: measurementDraftRemoveGroupRequestSchema, + MeasurementDraftRenameTargetRequest: measurementDraftRenameTargetRequestSchema, + MeasurementDraftResponse: measurementDraftResponseSchema, + MeasurementDraftTargetPage: measurementDraftTargetPageSchema, + MeasurementDraftUpsertCompetitorRequest: measurementDraftUpsertCompetitorRequestSchema, + MeasurementDraftUpsertGroupRequest: measurementDraftUpsertGroupRequestSchema, + MeasurementDraftUpsertTargetRequest: measurementDraftUpsertTargetRequestSchema, + MeasurementOverviewResponse: measurementOverviewResponseSchema, MeasurementPlanCompilePreviewResponse: measurementPlanCompilePreviewResponseSchema, + MeasurementPlanDeactivateRequest: measurementPlanDeactivateRequestSchema, + MeasurementPlanDeactivateResponse: measurementPlanDeactivateResponseSchema, MeasurementPlanDiffPreviewResponse: measurementPlanDiffPreviewResponseSchema, MeasurementPlanInput: measurementPlanAuthoringSchema, MeasurementPlanPublishRequest: measurementPlanPublishRequestSchema, MeasurementPlanResponse: measurementPlanResponseSchema, + MeasurementPlanV2PublishResponse: measurementPlanV2PublishResponseSchema, MeasurementPlanVersionResponse: measurementPlanVersionResponseSchema, MeasurementPlanVersionsResponse: measurementPlanVersionsResponseSchema, + MeasurementQuerySetDetail: measurementQuerySetDetailSchema, + MeasurementQuerySetListResponse: measurementQuerySetListResponseSchema, + MeasurementQuerySetUpsertRequest: measurementQuerySetUpsertRequestSchema, + MeasurementQueryTemplateApplyRequest: measurementQueryTemplateApplyRequestSchema, + MeasurementQueryTemplateApplyResponse: measurementQueryTemplateApplyResponseSchema, + MeasurementQueryTemplateDto: measurementQueryTemplateSchema, + MeasurementQueryTemplateListResponse: measurementQueryTemplateListResponseSchema, + MeasurementQueryTemplateUpsertRequest: measurementQueryTemplateUpsertRequestSchema, MeasurementReportResponse: measurementReportResponseSchema, MeasurementSegmentRetirementResponse: measurementSegmentRetirementResponseSchema, + MeasurementSetupResponse: measurementSetupResponseSchema, NotificationDto: notificationDtoSchema, OnboardingTelemetryEvent: onboardingTelemetryEventSchema, OrganicEvidenceDto: organicEvidenceDtoSchema, diff --git a/packages/api-routes/src/openapi.ts b/packages/api-routes/src/openapi.ts index be268e7f4..07134a831 100644 --- a/packages/api-routes/src/openapi.ts +++ b/packages/api-routes/src/openapi.ts @@ -12,6 +12,7 @@ import { jsonResponse, looseObjectSchema, rawJsonResponse, + type RegisteredSchemaName, } from './openapi-schemas.js' export interface OpenApiInfo { @@ -32,7 +33,8 @@ type HttpMethod = 'get' | 'post' | 'put' | 'delete' interface OpenApiParameter { name: string - in: 'path' | 'query' + /** `header` covers the concurrency and idempotency guards on draft mutations. */ + in: 'path' | 'query' | 'header' required?: boolean description: string schema: Record @@ -437,6 +439,119 @@ const wordpressSlugQueryParameter: OpenApiParameter = { schema: stringSchema, } +const measurementIfMatchParameter: OpenApiParameter = { + name: 'If-Match', + in: 'header', + required: true, + description: 'Current draft ETag. Missing returns 428; stale returns 412.', + schema: stringSchema, +} + +const measurementIdempotencyKeyParameter: OpenApiParameter = { + name: 'Idempotency-Key', + in: 'header', + required: true, + description: 'Replay key. The same key with a different request body returns 409.', + schema: stringSchema, +} + +const measurementSearchParameter: OpenApiParameter = { + name: 'search', + in: 'query', + description: 'Case-insensitive filter over the returned rows. It never changes a metric denominator.', + schema: stringSchema, +} + +const measurementCursorParameter: OpenApiParameter = { + name: 'cursor', + in: 'query', + description: 'Opaque cursor from the previous page.', + schema: stringSchema, +} + +const measurementLimitParameter: OpenApiParameter = { + name: 'limit', + in: 'query', + description: 'Page size. Defaults to 50, maximum 100.', + schema: { type: 'integer', minimum: 1, maximum: 100 }, +} + +const measurementQuerySetIdParameter: OpenApiParameter = { + name: 'setId', + in: 'path', + required: true, + description: 'Measurement query-set ID.', + schema: stringSchema, +} + +const measurementQueryTemplateIdParameter: OpenApiParameter = { + name: 'templateId', + in: 'path', + required: true, + description: 'Measurement query-template ID.', + schema: stringSchema, +} + +const measurementDraftCollectionParameters: OpenApiParameter[] = [ + nameParameter, + measurementSearchParameter, + measurementCursorParameter, + measurementLimitParameter, +] + +/** + * One typed draft action. Every one is a POST under the same prefix, carries + * the draft ETag and an idempotency key, and answers with the new ETag plus + * counts — so the shared half is built once here rather than repeated per + * action, where a divergence would be invisible. + */ +function measurementDraftAction(input: { + action: string + summary: string + description?: string + request?: RegisteredSchemaName + response?: RegisteredSchemaName + responseDescription?: string + /** Set for the previews, which compile the stored draft and write nothing. */ + readOnly?: boolean +}): OpenApiOperation { + const parameters = input.readOnly + ? [nameParameter] + : [nameParameter, measurementIfMatchParameter, measurementIdempotencyKeyParameter] + return { + method: 'post', + path: `/api/v1/projects/{name}/measurement-plan/draft/actions/${input.action}`, + summary: input.summary, + ...(input.description ? { description: input.description } : {}), + tags: ['measurement-plans'], + parameters, + ...(input.request + ? { + requestBody: { + required: true, + content: { 'application/json': { schema: { $ref: `#/components/schemas/${input.request}` } } }, + }, + } + : {}), + responses: { + 200: jsonResponse( + input.responseDescription ?? 'Draft mutated; the new ETag and counts are returned.', + input.response ?? 'MeasurementDraftMutationResponse', + ), + 400: errorResponse('The action payload is invalid.'), + 403: errorResponse('The caller may read the draft but not mutate it.'), + 404: errorResponse('Project or draft not found.'), + ...(input.readOnly + ? {} + : { + 409: errorResponse('The idempotency key was already used with a different request body.'), + 412: errorResponse('The draft changed since it was loaded.'), + 428: errorResponse('The draft ETag was not supplied in `If-Match`.'), + }), + }, + } +} + const routeCatalog: OpenApiOperation[] = [ { method: 'get', @@ -646,6 +761,356 @@ const routeCatalog: OpenApiOperation[] = [ 404: errorResponse('Project or measurement-plan revision not found.'), }, }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-setup', + summary: 'Get the measurement setup state', + description: 'Returns exactly one state, evaluated in a fixed precedence: republish_required, setup_in_progress, awaiting_first_run, operational, simple. A draft over an active v1 plan is republish_required, because republishing is the blocking action.', + tags: ['measurement-plans'], + parameters: [nameParameter], + responses: { + 200: jsonResponse('Setup state and its next action returned.', 'MeasurementSetupResponse'), + 404: errorResponse('Project not found.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-plan/draft', + summary: 'Get the server-side setup draft', + description: 'Returns the single draft for the project, or an explicit null for a project with none. The response ETag is required on every subsequent mutation.', + tags: ['measurement-plans'], + parameters: [nameParameter], + responses: { + 200: jsonResponse('Draft and its ETag returned.', 'MeasurementDraftResponse'), + 404: errorResponse('Project not found.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-plan/draft/targets', + summary: 'Page the draft Targets', + description: 'Cursor-paginated and deterministically ordered by normalized label then stable key. Serves a thousand Targets without client-side truncation.', + tags: ['measurement-plans'], + parameters: measurementDraftCollectionParameters, + responses: { + 200: jsonResponse('Draft Target page returned.', 'MeasurementDraftTargetPage'), + 404: errorResponse('Project or draft not found.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-plan/draft/assignments', + summary: 'Page the draft assignments', + description: 'Cursor-paginated Target-owned query assignments, each carrying its class and where that class came from.', + tags: ['measurement-plans'], + parameters: measurementDraftCollectionParameters, + responses: { + 200: jsonResponse('Draft assignment page returned.', 'MeasurementDraftAssignmentPage'), + 404: errorResponse('Project or draft not found.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-plan/draft/groups', + summary: 'Page the draft groups', + description: 'Cursor-paginated reporting groups and their confirmed competitors. Groups never hold queries or execution context.', + tags: ['measurement-plans'], + parameters: measurementDraftCollectionParameters, + responses: { + 200: jsonResponse('Draft group page returned.', 'MeasurementDraftGroupPage'), + 404: errorResponse('Project or draft not found.'), + }, + }, + measurementDraftAction({ + action: 'create', + summary: 'Start a setup draft', + description: 'Creates the single draft for the project, recording the active revision it was created from. A draft never changes the active mode.', + request: 'MeasurementDraftCreateRequest', + }), + measurementDraftAction({ + action: 'import-sitemap', + summary: 'Import a sitemap into the draft', + description: 'Fetches an operator-supplied sitemap under strict egress policy and records the deterministic discovery inputs on the draft. It proposes Targets for review; it never publishes a plan or starts a run.', + request: 'MeasurementDraftImportSitemapRequest', + }), + measurementDraftAction({ + action: 'apply-sitemap-selection', + summary: 'Apply the operator selection from discovery', + description: 'Turns reviewed discovery proposals into new or rebound Targets. Ambiguity is never resolved automatically.', + request: 'MeasurementDraftApplySitemapSelectionRequest', + }), + measurementDraftAction({ + action: 'upsert-target', + summary: 'Add or update a draft Target', + request: 'MeasurementDraftUpsertTargetRequest', + }), + measurementDraftAction({ + action: 'rename-target', + summary: 'Rename a draft Target', + description: 'Changes the label only. The stable key, its assignments and its group membership are untouched.', + request: 'MeasurementDraftRenameTargetRequest', + }), + measurementDraftAction({ + action: 'merge-targets', + summary: 'Merge draft Targets into one', + description: 'The survivor keeps its stable key, and the merged Targets contribute their assignments and group membership.', + request: 'MeasurementDraftMergeTargetsRequest', + }), + measurementDraftAction({ + action: 'exclude-target', + summary: 'Exclude a draft Target', + description: 'Excluded Targets stay in the draft for review but never compile.', + request: 'MeasurementDraftExcludeTargetRequest', + }), + measurementDraftAction({ + action: 'rebind-target', + summary: 'Rebind a draft Target to a new discovered URL', + description: 'Preserves the stable key, the assignments and the group membership, so history follows the Target across a site restructure.', + request: 'MeasurementDraftRebindTargetRequest', + }), + measurementDraftAction({ + action: 'apply-assignments', + summary: 'Assign project queries to a draft Target', + request: 'MeasurementDraftApplyAssignmentsRequest', + }), + measurementDraftAction({ + action: 'remove-assignment', + summary: 'Remove one assignment from a draft Target', + description: 'Removes the assignment only. The project query behind it is never deleted.', + request: 'MeasurementDraftRemoveAssignmentRequest', + }), + measurementDraftAction({ + action: 'clear-assignments', + summary: 'Clear every assignment on a draft Target', + request: 'MeasurementDraftClearAssignmentsRequest', + }), + measurementDraftAction({ + action: 'classify-assignments', + summary: 'Classify assignments as Branded or Non-brand', + description: 'Always records the class as operator-sourced, so a later rule proposal cannot overwrite it. The class belongs to the assignment, so one query can be Branded for one Target and Non-brand for another.', + request: 'MeasurementDraftClassifyAssignmentsRequest', + }), + measurementDraftAction({ + action: 'upsert-group', + summary: 'Add or update a draft group', + description: 'Reporting membership only. A payload carrying queries, providers, locations or models is rejected.', + request: 'MeasurementDraftUpsertGroupRequest', + }), + measurementDraftAction({ + action: 'remove-group', + summary: 'Remove a draft group', + request: 'MeasurementDraftRemoveGroupRequest', + }), + measurementDraftAction({ + action: 'upsert-competitor', + summary: 'Add or update a group competitor', + request: 'MeasurementDraftUpsertCompetitorRequest', + }), + measurementDraftAction({ + action: 'remove-competitor', + summary: 'Remove a group competitor', + request: 'MeasurementDraftRemoveCompetitorRequest', + }), + measurementDraftAction({ + action: 'compile-preview', + summary: 'Compile the draft without publishing', + description: 'Compiles the stored draft and returns the compiled checksum the publish guard expects. Writes nothing, so a view-only account can call it.', + response: 'MeasurementDraftCompilePreviewResponse', + responseDescription: 'Compiled draft preview returned. Invalid authoring returns ok=false with typed checks.', + readOnly: true, + }), + measurementDraftAction({ + action: 'diff-preview', + summary: 'Compare the draft with the active revision', + description: 'Compiles the stored draft and reports the keys that changed against the active revision. Writes nothing.', + response: 'MeasurementDraftDiffPreviewResponse', + responseDescription: 'Draft diff returned. Invalid authoring returns ok=false with a null diff.', + readOnly: true, + }), + measurementDraftAction({ + action: 'publish', + summary: 'Publish the draft as a new revision', + description: 'Recompiles server-side and refuses content that changed after review. Content identical to the active revision is a no-op returning it; content identical to an older revision publishes as a new revision, so a revert is a first-class operation. Publishing never starts a run.', + request: 'MeasurementDraftPublishRequest', + response: 'MeasurementPlanV2PublishResponse', + responseDescription: 'The published revision, or the unchanged active revision when the content was identical to it.', + }), + measurementDraftAction({ + action: 'discard', + summary: 'Discard the draft', + response: 'MeasurementDraftDiscardResponse', + responseDescription: 'Draft discarded.', + }), + { + method: 'post', + path: '/api/v1/projects/{name}/measurement-plan/actions/deactivate', + summary: 'Deactivate the active measurement plan', + description: 'Deletes the active-plan pointer and nothing else. Schedules, runs, queries, versions and evidence are untouched, and the revisions stay readable.', + tags: ['measurement-plans'], + parameters: [nameParameter, measurementIdempotencyKeyParameter], + requestBody: { + required: true, + content: { + 'application/json': { schema: { $ref: '#/components/schemas/MeasurementPlanDeactivateRequest' } }, + }, + }, + responses: { + 200: jsonResponse('The plan pointer was removed.', 'MeasurementPlanDeactivateResponse'), + 403: errorResponse('The caller may read the plan but not deactivate it.'), + 404: errorResponse('Project or active plan not found.'), + 409: errorResponse('The active revision changed after the caller loaded it.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-overview', + summary: 'Get the scoped measurement overview', + description: 'Aggregates one run of evidence for All Properties, a group, or a single Property. Without runId the most recent completed run pinned to the active revision is used; a run pinned to another revision is refused rather than joined. Metrics are computed before search is applied, and a metric with no evidence is unavailable rather than zero.', + tags: ['measurement-plans'], + parameters: [ + nameParameter, + { name: 'scope', in: 'query', required: true, description: 'Reporting scope.', schema: { type: 'string', enum: ['all', 'group', 'property'] } }, + { name: 'groupKey', in: 'query', description: 'Group stable key, required when scope is "group".', schema: stringSchema }, + { name: 'targetKey', in: 'query', description: 'Target stable key, required when scope is "property".', schema: stringSchema }, + { name: 'queryClass', in: 'query', description: 'Restrict to one question class. Never pooled across classes.', schema: { type: 'string', enum: ['all', 'branded', 'non-brand'] } }, + { name: 'provider', in: 'query', description: 'Restrict to one answer provider.', schema: stringSchema }, + { name: 'location', in: 'query', description: 'Restrict to one execution location label.', schema: stringSchema }, + { name: 'from', in: 'query', description: 'Inclusive start of the window (YYYY-MM-DD).', schema: stringSchema }, + { name: 'to', in: 'query', description: 'Inclusive end of the window (YYYY-MM-DD).', schema: stringSchema }, + { name: 'runId', in: 'query', description: 'Display this run. It must be pinned to the active revision. This is also the only way to display a scoped spot check.', schema: stringSchema }, + measurementSearchParameter, + measurementCursorParameter, + measurementLimitParameter, + ], + responses: { + 200: jsonResponse('Scoped measurement overview returned.', 'MeasurementOverviewResponse'), + 400: errorResponse('The scope or its required key is invalid.'), + 404: errorResponse('Project not found.'), + 422: errorResponse('The named run is pinned to a different plan revision.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-query-sets', + summary: 'List measurement query sets', + tags: ['measurement-plans'], + parameters: [nameParameter], + responses: { + 200: jsonResponse('Query sets returned.', 'MeasurementQuerySetListResponse'), + 404: errorResponse('Project not found.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-query-sets/{setId}', + summary: 'Get a measurement query set', + tags: ['measurement-plans'], + parameters: [nameParameter, measurementQuerySetIdParameter], + responses: { + 200: jsonResponse('Query set and its ordered members returned.', 'MeasurementQuerySetDetail'), + 404: errorResponse('Project or query set not found.'), + }, + }, + { + method: 'put', + path: '/api/v1/projects/{name}/measurement-query-sets/{setId}', + summary: 'Create or replace a measurement query set', + description: 'A set holds ordered references to project query IDs. Membership changes never create or delete a query.', + tags: ['measurement-plans'], + parameters: [nameParameter, measurementQuerySetIdParameter], + requestBody: { + required: true, + content: { + 'application/json': { schema: { $ref: '#/components/schemas/MeasurementQuerySetUpsertRequest' } }, + }, + }, + responses: { + 200: jsonResponse('Query set replaced.', 'MeasurementQuerySetDetail'), + 201: jsonResponse('Query set created.', 'MeasurementQuerySetDetail'), + 400: errorResponse('The query set payload is invalid.'), + 403: errorResponse('The caller may read query assets but not manage them.'), + 404: errorResponse('Project or referenced query not found.'), + }, + }, + { + method: 'delete', + path: '/api/v1/projects/{name}/measurement-query-sets/{setId}', + summary: 'Delete a measurement query set', + description: 'Deletes the set and its membership rows. It never deletes a query or a published snapshot.', + tags: ['measurement-plans'], + parameters: [nameParameter, measurementQuerySetIdParameter], + responses: { + 204: { description: 'Query set deleted.' }, + 403: errorResponse('The caller may read query assets but not manage them.'), + 404: errorResponse('Project or query set not found.'), + }, + }, + { + method: 'get', + path: '/api/v1/projects/{name}/measurement-query-templates', + summary: 'List measurement query templates', + tags: ['measurement-plans'], + parameters: [nameParameter], + responses: { + 200: jsonResponse('Query templates returned.', 'MeasurementQueryTemplateListResponse'), + 404: errorResponse('Project not found.'), + }, + }, + { + method: 'put', + path: '/api/v1/projects/{name}/measurement-query-templates/{templateId}', + summary: 'Create or replace a measurement query template', + description: 'Templates are authoring assets. A published plan contains only the immutable snapshots an expansion produced.', + tags: ['measurement-plans'], + parameters: [nameParameter, measurementQueryTemplateIdParameter], + requestBody: { + required: true, + content: { + 'application/json': { schema: { $ref: '#/components/schemas/MeasurementQueryTemplateUpsertRequest' } }, + }, + }, + responses: { + 200: jsonResponse('Query template replaced.', 'MeasurementQueryTemplateDto'), + 201: jsonResponse('Query template created.', 'MeasurementQueryTemplateDto'), + 400: errorResponse('The template payload is invalid.'), + 403: errorResponse('The caller may read query assets but not manage them.'), + 404: errorResponse('Project not found.'), + }, + }, + { + method: 'delete', + path: '/api/v1/projects/{name}/measurement-query-templates/{templateId}', + summary: 'Delete a measurement query template', + description: 'Deletes the authoring template. Queries it already expanded, and every published snapshot of them, are untouched.', + tags: ['measurement-plans'], + parameters: [nameParameter, measurementQueryTemplateIdParameter], + responses: { + 204: { description: 'Query template deleted.' }, + 403: errorResponse('The caller may read query assets but not manage them.'), + 404: errorResponse('Project or query template not found.'), + }, + }, + { + method: 'post', + path: '/api/v1/projects/{name}/measurement-query-templates/{templateId}/apply', + summary: 'Expand a query template into project queries', + description: 'Expands one binding per concrete query. Expansion is additive: a query that already exists is reported rather than duplicated.', + tags: ['measurement-plans'], + parameters: [nameParameter, measurementQueryTemplateIdParameter, measurementIdempotencyKeyParameter], + requestBody: { + required: true, + content: { + 'application/json': { schema: { $ref: '#/components/schemas/MeasurementQueryTemplateApplyRequest' } }, + }, + }, + responses: { + 200: jsonResponse('Expansion result returned.', 'MeasurementQueryTemplateApplyResponse'), + 400: errorResponse('A binding does not satisfy the template variables.'), + 403: errorResponse('The caller may read query assets but not manage them.'), + 404: errorResponse('Project or query template not found.'), + 409: errorResponse('The idempotency key was already used with a different request body.'), + }, + }, { method: 'get', path: '/api/v1/projects', diff --git a/packages/api-routes/src/run-queue.ts b/packages/api-routes/src/run-queue.ts index bee8fbf62..be2a7981f 100644 --- a/packages/api-routes/src/run-queue.ts +++ b/packages/api-routes/src/run-queue.ts @@ -6,7 +6,7 @@ import { canonicalMeasurementExecutionIdentityJson, MeasurementRunScopeError, measurementRunScopeIsEmpty, - parseStoredMeasurementPlan, + parseStoredMeasurementPlanAnyVersion, RunTriggers, resolveMeasurementRunQueryScope, resolveMeasurementRunScope, @@ -162,7 +162,14 @@ function measurementStamp(tx: DatabaseClient, params: QueueRunParams): Measureme )).get() if (!version) throw new Error(`Measurement plan ${params.projectId} points to missing version ${active.activeVersionId}`) - const plan: MeasurementPlan = parseStoredMeasurementPlan(version.canonicalJson) + // Slice 4 replaces this branch with real v2 materialization. Until then a v2 + // revision is refused with a domain error rather than reaching the v1 decoder, + // which would throw and surface as a 500 on an otherwise valid stored plan. + const stored = parseStoredMeasurementPlanAnyVersion(version.canonicalJson) + if (stored.schemaVersion !== 1) { + throw validationError(`Measurement plan revision ${version.revision} is schema v${stored.schemaVersion}, which this runner cannot execute yet.`) + } + const plan: MeasurementPlan = stored const providers = providerRoster(tx, params) const models = effectiveModels(tx, params, providers) if (providers.length === 0) { diff --git a/packages/api-routes/test/db-dto-coverage.test.ts b/packages/api-routes/test/db-dto-coverage.test.ts index f41624d08..45210101b 100644 --- a/packages/api-routes/test/db-dto-coverage.test.ts +++ b/packages/api-routes/test/db-dto-coverage.test.ts @@ -53,6 +53,8 @@ import { siteAuditPageSchema, trafficSourceDtoSchema, userDtoSchema, + measurementQuerySetSchema, + measurementQueryTemplateSchema, } from '@ainyc/canonry-contracts' /** @@ -614,6 +616,32 @@ const COVERAGE: Record = { kind: 'internal-only', reason: 'Stable target/group identity anchor; labels and attribution rules remain in immutable plan JSON.', }, + measurementPlanDrafts: { + kind: 'internal-only', + reason: 'Authoring draft storage; the draft API returns the parsed authoring document and an ETag, not this row.', + }, + measurementQuerySets: { + kind: 'dto', + dto: measurementQuerySetSchema, + internal: {}, + }, + measurementQuerySetItems: { + kind: 'internal-only', + reason: 'Ordered membership rows; returned only inside a query-set detail, never as a row of their own.', + }, + measurementQueryTemplates: { + kind: 'dto', + dto: measurementQueryTemplateSchema, + internal: {}, + }, + measurementDiscoveryConfigs: { + kind: 'internal-only', + reason: 'Recorded discovery inputs for determinism; the draft carries the operator-facing copy of them.', + }, + measurementOperationReceipts: { + kind: 'internal-only', + reason: 'Idempotency receipts. A replay returns the stored response body, never the receipt row.', + }, usageCounters: { kind: 'internal-only', reason: 'Internal rate-limit / quota counters; never exposed.', diff --git a/packages/api-routes/test/measurement-v2-openapi.test.ts b/packages/api-routes/test/measurement-v2-openapi.test.ts new file mode 100644 index 000000000..317f45a7c --- /dev/null +++ b/packages/api-routes/test/measurement-v2-openapi.test.ts @@ -0,0 +1,125 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import Fastify from 'fastify' +import { afterEach, describe, expect, it } from 'vitest' +import { createClient, migrate } from '@ainyc/canonry-db' +import { apiRoutes } from '../src/index.js' + +interface SpecOperation { + parameters?: Array<{ name?: string; in?: string; required?: boolean }> + requestBody?: { content?: Record } + responses?: Record }> +} + +interface Spec { + components?: { schemas?: Record; required?: string[] }> } + paths: Record> +} + +const cleanups: Array<() => void> = [] + +afterEach(async () => { + for (const fn of cleanups.splice(0)) fn() +}) + +async function spec(): Promise { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'canonry-measurement-v2-spec-')) + cleanups.push(() => fs.rmSync(tmpDir, { recursive: true, force: true })) + const db = createClient(path.join(tmpDir, 'test.db')) + migrate(db) + + const app = Fastify() + app.register(apiRoutes, { db, skipAuth: true }) + await app.ready() + cleanups.push(() => void app.close()) + + const response = await app.inject({ method: 'GET', url: '/api/v1/openapi.json' }) + expect(response.statusCode).toBe(200) + return response.json() as Spec +} + +const DRAFT = '/api/v1/projects/{name}/measurement-plan/draft' + +describe('advanced measurement v2 openapi surface', () => { + it('types every draft action against a contract, not a loose object', async () => { + const document = await spec() + const cases: Array<[string, string]> = [ + ['create', 'MeasurementDraftCreateRequest'], + ['import-sitemap', 'MeasurementDraftImportSitemapRequest'], + ['apply-sitemap-selection', 'MeasurementDraftApplySitemapSelectionRequest'], + ['upsert-target', 'MeasurementDraftUpsertTargetRequest'], + ['rename-target', 'MeasurementDraftRenameTargetRequest'], + ['merge-targets', 'MeasurementDraftMergeTargetsRequest'], + ['exclude-target', 'MeasurementDraftExcludeTargetRequest'], + ['rebind-target', 'MeasurementDraftRebindTargetRequest'], + ['apply-assignments', 'MeasurementDraftApplyAssignmentsRequest'], + ['remove-assignment', 'MeasurementDraftRemoveAssignmentRequest'], + ['clear-assignments', 'MeasurementDraftClearAssignmentsRequest'], + ['classify-assignments', 'MeasurementDraftClassifyAssignmentsRequest'], + ['upsert-group', 'MeasurementDraftUpsertGroupRequest'], + ['remove-group', 'MeasurementDraftRemoveGroupRequest'], + ['upsert-competitor', 'MeasurementDraftUpsertCompetitorRequest'], + ['remove-competitor', 'MeasurementDraftRemoveCompetitorRequest'], + ['publish', 'MeasurementDraftPublishRequest'], + ] + for (const [action, schemaName] of cases) { + const operation = document.paths[`${DRAFT}/actions/${action}`]?.post + expect(operation, `${action} is not documented`).toBeDefined() + expect( + operation!.requestBody?.content?.['application/json']?.schema?.$ref, + `${action} request body`, + ).toBe(`#/components/schemas/${schemaName}`) + } + }) + + it('demands the ETag and idempotency guards on a mutation but not on a preview', async () => { + const document = await spec() + const headers = (action: string) => (document.paths[`${DRAFT}/actions/${action}`]?.post?.parameters ?? []) + .filter(parameter => parameter.in === 'header') + .map(parameter => `${parameter.name}:${parameter.required === true}`) + .sort() + + expect(headers('publish')).toEqual(['Idempotency-Key:true', 'If-Match:true']) + expect(headers('upsert-target')).toEqual(['Idempotency-Key:true', 'If-Match:true']) + // Compiling the stored draft writes nothing, so it carries neither guard. + expect(headers('compile-preview')).toEqual([]) + expect(headers('diff-preview')).toEqual([]) + }) + + it('publishes the compiled checksum on every contract that reviews or guards content', async () => { + const document = await spec() + const compile = document.components?.schemas?.MeasurementDraftCompilePreviewResponse + const publishRequest = document.components?.schemas?.MeasurementDraftPublishRequest + const publishResponse = document.components?.schemas?.MeasurementPlanV2PublishResponse + + expect(JSON.stringify(compile)).toContain('compiledChecksum') + expect(publishRequest?.required).toEqual( + expect.arrayContaining(['expectedActiveRevision', 'expectedCompiledChecksum']), + ) + expect(JSON.stringify(publishResponse)).toContain('compiledChecksum') + }) + + it('emits brandPresence and documents sov as its deprecated alias', async () => { + const document = await spec() + const overview = document.components?.schemas?.MeasurementOverviewResponse + const metrics = overview?.properties?.metrics as { + required?: string[] + properties?: Record + } | undefined + + expect(metrics?.required).toEqual(expect.arrayContaining(['brandPresence', 'sov'])) + expect(metrics?.properties?.sov?.description).toMatch(/deprecated/i) + }) + + it('documents the cross-revision run rejection on the overview', async () => { + const document = await spec() + const overview = document.paths['/api/v1/projects/{name}/measurement-overview']?.get + const runId = overview?.parameters?.find(parameter => parameter.name === 'runId') + + expect(runId).toMatchObject({ in: 'query' }) + expect(overview?.responses?.['422']).toBeDefined() + expect(overview?.responses?.['200']?.content?.['application/json']?.schema?.$ref) + .toBe('#/components/schemas/MeasurementOverviewResponse') + }) +}) diff --git a/packages/api-routes/test/measurement-v2-stubs.test.ts b/packages/api-routes/test/measurement-v2-stubs.test.ts new file mode 100644 index 000000000..68a35092b --- /dev/null +++ b/packages/api-routes/test/measurement-v2-stubs.test.ts @@ -0,0 +1,104 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import Fastify from 'fastify' +import { afterEach, expect, it } from 'vitest' +import { createClient, migrate, projects } from '@ainyc/canonry-db' +import { apiRoutes } from '../src/index.js' +import type { ApiRoutesOptions } from '../src/index.js' + +const NOW = '2026-08-01T00:00:00.000Z' + +/** + * Slice 1 registers these routes and leaves the bodies to the slices that own + * each module. Registration is the contract those slices build against, so it + * is pinned here: a route that quietly fails to mount would surface as a 404 + * only once the handler landed. + */ +const DRAFT_ROUTES = [ + ['GET', '/measurement-setup'], + ['GET', '/measurement-plan/draft'], + ['GET', '/measurement-plan/draft/targets'], + ['GET', '/measurement-plan/draft/assignments'], + ['GET', '/measurement-plan/draft/groups'], + ['POST', '/measurement-plan/draft/actions/create'], + ['POST', '/measurement-plan/draft/actions/upsert-target'], + ['POST', '/measurement-plan/draft/actions/rename-target'], + ['POST', '/measurement-plan/draft/actions/merge-targets'], + ['POST', '/measurement-plan/draft/actions/exclude-target'], + ['POST', '/measurement-plan/draft/actions/rebind-target'], + ['POST', '/measurement-plan/draft/actions/apply-assignments'], + ['POST', '/measurement-plan/draft/actions/remove-assignment'], + ['POST', '/measurement-plan/draft/actions/clear-assignments'], + ['POST', '/measurement-plan/draft/actions/classify-assignments'], + ['POST', '/measurement-plan/draft/actions/upsert-group'], + ['POST', '/measurement-plan/draft/actions/remove-group'], + ['POST', '/measurement-plan/draft/actions/upsert-competitor'], + ['POST', '/measurement-plan/draft/actions/remove-competitor'], + ['POST', '/measurement-plan/draft/actions/compile-preview'], + ['POST', '/measurement-plan/draft/actions/diff-preview'], + ['POST', '/measurement-plan/draft/actions/publish'], + ['POST', '/measurement-plan/draft/actions/discard'], + ['POST', '/measurement-plan/actions/deactivate'], + ['GET', '/measurement-query-sets'], + ['GET', '/measurement-query-sets/qs-1'], + ['PUT', '/measurement-query-sets/qs-1'], + ['DELETE', '/measurement-query-sets/qs-1'], + ['GET', '/measurement-query-templates'], + ['PUT', '/measurement-query-templates/tpl-1'], + ['DELETE', '/measurement-query-templates/tpl-1'], + ['POST', '/measurement-query-templates/tpl-1/apply'], +] as const + +const DISCOVERY_ROUTES = [ + ['POST', '/measurement-plan/draft/actions/import-sitemap'], + ['POST', '/measurement-plan/draft/actions/apply-sitemap-selection'], +] as const + +const OVERVIEW_ROUTES = [ + ['GET', '/measurement-overview?scope=all'], +] as const + +function buildApp() { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'canonry-measurement-v2-stubs-')) + const db = createClient(path.join(tmpDir, 'test.db')) + migrate(db) + db.insert(projects).values({ + id: 'prj_1', + name: 'northstar', + displayName: 'Northstar', + canonicalDomain: 'northstar.example', + country: 'US', + language: 'en', + createdAt: NOW, + updatedAt: NOW, + }).run() + + const app = Fastify() + app.register(apiRoutes, { db, skipAuth: true } satisfies ApiRoutesOptions) + return { app, tmpDir } +} + +const cleanups: Array<() => void> = [] + +afterEach(async () => { + for (const fn of cleanups.splice(0)) fn() +}) + +it.each([...DRAFT_ROUTES, ...DISCOVERY_ROUTES, ...OVERVIEW_ROUTES])( + '%s %s is registered and answers 501 until its slice lands', + async (method, route) => { + const { app, tmpDir } = buildApp() + cleanups.push(() => fs.rmSync(tmpDir, { recursive: true, force: true })) + + const response = await app.inject({ + method: method as 'GET' | 'POST' | 'PUT' | 'DELETE', + url: `/api/v1/projects/northstar${route}`, + ...(method === 'GET' || method === 'DELETE' ? {} : { payload: {} }), + }) + + expect(response.statusCode, `${method} ${route} returned ${response.statusCode}`).toBe(501) + expect(response.json()).toMatchObject({ error: { code: 'NOT_IMPLEMENTED' } }) + await app.close() + }, +) diff --git a/packages/canonry/src/mcp/openapi-classification.ts b/packages/canonry/src/mcp/openapi-classification.ts index 3805aa830..7b3693c51 100644 --- a/packages/canonry/src/mcp/openapi-classification.ts +++ b/packages/canonry/src/mcp/openapi-classification.ts @@ -49,6 +49,45 @@ export const MCP_OPENAPI_OPERATION_CLASSIFICATIONS = { 'GET /api/v1/projects/{name}/measurement-plan/versions': 'included', 'GET /api/v1/projects/{name}/measurement-plan/versions/{revision}': 'included', 'GET /api/v1/projects/{name}/measurement-report': 'included', + // Advanced Measurement v2. The routes are registered and typed, but their + // handlers answer 501 until the slice that owns each module lands, so an MCP + // tool over them would only teach an agent to call something that cannot + // work. Promote each to 'included' with its handler. + 'GET /api/v1/projects/{name}/measurement-setup': 'deferred', + 'GET /api/v1/projects/{name}/measurement-plan/draft': 'deferred', + 'GET /api/v1/projects/{name}/measurement-plan/draft/targets': 'deferred', + 'GET /api/v1/projects/{name}/measurement-plan/draft/assignments': 'deferred', + 'GET /api/v1/projects/{name}/measurement-plan/draft/groups': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/create': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/import-sitemap': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-sitemap-selection': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-target': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/rename-target': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/merge-targets': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/exclude-target': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/rebind-target': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-assignments': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-assignment': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/clear-assignments': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/classify-assignments': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-group': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-group': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-competitor': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-competitor': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/compile-preview': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/diff-preview': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/publish': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/draft/actions/discard': 'deferred', + 'POST /api/v1/projects/{name}/measurement-plan/actions/deactivate': 'deferred', + 'GET /api/v1/projects/{name}/measurement-overview': 'deferred', + 'GET /api/v1/projects/{name}/measurement-query-sets': 'deferred', + 'GET /api/v1/projects/{name}/measurement-query-sets/{setId}': 'deferred', + 'PUT /api/v1/projects/{name}/measurement-query-sets/{setId}': 'deferred', + 'DELETE /api/v1/projects/{name}/measurement-query-sets/{setId}': 'deferred', + 'GET /api/v1/projects/{name}/measurement-query-templates': 'deferred', + 'PUT /api/v1/projects/{name}/measurement-query-templates/{templateId}': 'deferred', + 'DELETE /api/v1/projects/{name}/measurement-query-templates/{templateId}': 'deferred', + 'POST /api/v1/projects/{name}/measurement-query-templates/{templateId}/apply': 'deferred', 'POST /api/v1/projects/{name}/research/runs': 'included', 'GET /api/v1/projects/{name}/research/runs': 'included', 'GET /api/v1/projects/{name}/research/runs/{runId}': 'included', diff --git a/packages/contracts/src/errors.ts b/packages/contracts/src/errors.ts index 5cb417cf0..047917479 100644 --- a/packages/contracts/src/errors.ts +++ b/packages/contracts/src/errors.ts @@ -20,6 +20,12 @@ export type ErrorCode = | 'MISSING_DEPENDENCY' | 'RUNTIME_STATE_MISSING' | 'MEASUREMENT_PLAN_REVISION_CONFLICT' + | 'MEASUREMENT_COMPILED_CHECKSUM_CONFLICT' + | 'MEASUREMENT_RUN_REVISION_MISMATCH' + | 'MEASUREMENT_DRAFT_ETAG_REQUIRED' + | 'MEASUREMENT_DRAFT_ETAG_STALE' + | 'MEASUREMENT_IDEMPOTENCY_KEY_REQUIRED' + | 'MEASUREMENT_IDEMPOTENCY_KEY_CONFLICT' export class AppError extends Error { readonly code: ErrorCode @@ -123,6 +129,78 @@ export function measurementPlanRevisionConflict( ) } +/** + * The recompile at publish time produced different content than the operator + * reviewed. Nothing is written: republishing a document nobody looked at is the + * failure this prevents. + */ +export function measurementCompiledChecksumConflict( + expectedCompiledChecksum: string, + actualCompiledChecksum: string, +): AppError { + return new AppError( + 'MEASUREMENT_COMPILED_CHECKSUM_CONFLICT', + 'The compiled measurement plan changed after it was reviewed. Reload the review and publish again.', + 409, + { expectedCompiledChecksum, actualCompiledChecksum }, + ) +} + +/** + * A report was asked to display a run pinned to a different plan revision. + * Joining across revisions would compare two different sets of questions, so + * the request is refused rather than answered with a mixed basis. + */ +export function measurementRunRevisionMismatch( + runId: string, + runRevision: number, + activeRevision: number | null, +): AppError { + return new AppError( + 'MEASUREMENT_RUN_REVISION_MISMATCH', + `Run '${runId}' measured plan revision ${runRevision}, not the active revision. Select a run pinned to the active revision.`, + 422, + { runId, runRevision, activeRevision }, + ) +} + +/** A draft mutation arrived without `If-Match`, so it cannot be shown to be acting on what the caller saw. */ +export function measurementDraftEtagRequired(): AppError { + return new AppError( + 'MEASUREMENT_DRAFT_ETAG_REQUIRED', + 'This action requires the current draft ETag in `If-Match`. Reload the draft and retry.', + 428, + ) +} + +export function measurementDraftEtagStale(expectedEtag: string, actualEtag: string): AppError { + return new AppError( + 'MEASUREMENT_DRAFT_ETAG_STALE', + 'The measurement draft changed since it was loaded. Reload it and retry.', + 412, + { expectedEtag, actualEtag }, + ) +} + +export function measurementIdempotencyKeyRequired(operation: string): AppError { + return new AppError( + 'MEASUREMENT_IDEMPOTENCY_KEY_REQUIRED', + `The '${operation}' action requires an \`Idempotency-Key\` header.`, + 400, + { operation }, + ) +} + +/** Same key, different content: replaying a receipt over a changed request would silently apply the wrong one. */ +export function measurementIdempotencyKeyConflict(operation: string): AppError { + return new AppError( + 'MEASUREMENT_IDEMPOTENCY_KEY_CONFLICT', + `The \`Idempotency-Key\` for '${operation}' was already used with a different request body.`, + 409, + { operation }, + ) +} + export function runNotCancellable(runId: string, status: string): AppError { return new AppError('RUN_NOT_CANCELLABLE', `Run '${runId}' is already in terminal state '${status}' and cannot be cancelled`, 409) } diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index d3d2e4a85..9eac0512c 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -3,6 +3,8 @@ export * from './config-schema.js' export * from './models.js' export * from './measurement.js' export * from './measurement-plan.js' +export * from './measurement-plan-v2.js' +export * from './measurement-draft.js' export * from './measurement-service.js' export * from './model-pointers.js' export * from './errors.js' diff --git a/packages/contracts/src/measurement-draft.ts b/packages/contracts/src/measurement-draft.ts new file mode 100644 index 000000000..f93155835 --- /dev/null +++ b/packages/contracts/src/measurement-draft.ts @@ -0,0 +1,510 @@ +import { z } from 'zod' +import { + MEASUREMENT_PAGE_MAX_LIMIT, + measurementCursorPageSchema, + measurementPlanV2Schema, + measurementQueryClassSchema, + measurementV2CompetitorSchema, + measurementV2StableKeySchema, +} from './measurement-plan-v2.js' +import { measurementDiscoveryRuleSchema } from './measurement-service.js' +import { providerNameSchema } from './provider.js' + +const sha256HexSchema = z.string().regex(/^[a-f0-9]{64}$/) +const measurementDraftQueryIdSchema = z.string().trim().min(1).max(256) +const measurementDraftRevisionSchema = z.number().int().positive() + +/** Who acted, recorded on the draft and repeated in the audit trail. */ +export const measurementActorReferenceSchema = z.object({ + kind: z.enum(['user', 'api-key', 'system']), + id: z.string().trim().min(1), + label: z.string().trim().min(1), +}).strict() +export type ActorReference = z.output + +/** + * `proposed` is what discovery produces, `included` is what the operator + * accepted, `excluded` is what they rejected. Only `included` Targets compile. + */ +export const measurementDraftTargetStatusSchema = z.enum(['proposed', 'included', 'excluded']) +export type MeasurementDraftTargetStatus = z.output + +export const measurementDraftTargetSourceSchema = z.enum(['manual', 'sitemap']) +export type MeasurementDraftTargetSource = z.output + +export const measurementDraftTargetSchema = z.object({ + stableKey: measurementV2StableKeySchema, + label: z.string().trim().min(1), + status: measurementDraftTargetStatusSchema, + aliases: z.array(z.string().trim().min(1)), + urlMatchers: z.array(z.string().trim().min(1)), + source: measurementDraftTargetSourceSchema, + discoveredUrl: z.string().trim().min(1).optional(), + /** Structural identity from discovery. Rebinding keeps it, so history follows the Target. */ + discoveryIdentity: z.string().trim().min(1).optional(), +}).strict() +export type MeasurementDraftTarget = z.output + +/** Per-assignment escape hatch from the draft's default execution context. */ +export const measurementDraftContextOverrideSchema = z.object({ + providers: z.array(providerNameSchema).optional(), + models: z.record(providerNameSchema, z.string().trim().min(1)).optional(), + locations: z.array(z.string().trim().min(1)).optional(), +}).strict() +export type MeasurementDraftContextOverride = z.output + +/** + * A draft class may be `unclassified`; publish validation rejects it. The class + * belongs to the Target-owned assignment, so one question can be Branded for + * one Target and Non-brand for another when the operator chooses that. + */ +export const measurementDraftQueryClassSchema = z.enum(['branded', 'non-brand', 'unclassified']) +export type MeasurementDraftQueryClass = z.output + +/** A proposal never overwrites an operator decision, so the source travels with the class. */ +export const measurementClassificationSourceSchema = z.enum(['rule', 'operator']) +export type MeasurementClassificationSource = z.output + +export const measurementDraftAssignmentSchema = z.object({ + targetKey: measurementV2StableKeySchema, + queryId: measurementDraftQueryIdSchema, + contextOverride: measurementDraftContextOverrideSchema.optional(), + queryClass: measurementDraftQueryClassSchema, + classificationSource: measurementClassificationSourceSchema, +}).strict() +export type MeasurementDraftAssignment = z.output + +export const measurementDraftCompetitorSchema = measurementV2CompetitorSchema + +/** Reporting membership and competitors only. Strictness is what rejects a query or execution field here. */ +export const measurementDraftGroupSchema = z.object({ + stableKey: measurementV2StableKeySchema, + label: z.string().trim().min(1), + targetKeys: z.array(measurementV2StableKeySchema), + competitors: z.array(measurementDraftCompetitorSchema), +}).strict() +export type MeasurementDraftGroup = z.output + +/** The discovery inputs a rerun must reproduce byte for byte to be called deterministic. */ +export const measurementDraftDiscoverySchema = z.object({ + sitemapUrl: z.string().url(), + rule: measurementDiscoveryRuleSchema, + exclusions: z.array(z.string().trim().min(1)), + inputChecksum: sha256HexSchema, + reviewedAt: z.string().datetime().optional(), +}).strict() +export type MeasurementDraftDiscovery = z.output + +export const measurementDraftDefaultContextSchema = z.object({ + providers: z.array(providerNameSchema), + models: z.record(providerNameSchema, z.string().trim().min(1)).optional(), + locations: z.array(z.string().trim().min(1)), +}).strict() +export type MeasurementDraftDefaultContext = z.output + +/** + * Authoring intent only. Compiled nodes, usage edges, query snapshots and + * derived counts are compiler output and never round-trip through a draft; + * strictness is what keeps them out. + */ +export const measurementDraftAuthoringSchema = z.object({ + defaultContext: measurementDraftDefaultContextSchema, + targets: z.array(measurementDraftTargetSchema), + assignments: z.array(measurementDraftAssignmentSchema), + groups: z.array(measurementDraftGroupSchema), + discovery: measurementDraftDiscoverySchema.optional(), +}).strict() +export type MeasurementDraftAuthoring = z.output + +export const measurementPlanDraftSchema = z.object({ + id: z.string().trim().min(1), + projectId: z.string().trim().min(1), + schemaVersion: z.literal(2), + /** Null on a draft started from a planless project. */ + baseActiveVersionId: z.string().trim().min(1).nullable(), + baseActiveRevision: measurementDraftRevisionSchema.nullable(), + authoring: measurementDraftAuthoringSchema, + createdBy: measurementActorReferenceSchema, + updatedBy: measurementActorReferenceSchema, + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), +}).strict() +export type MeasurementPlanDraft = z.output + +export const MEASUREMENT_DRAFT_ETAG_PREFIX = 'mpd_' + +/** + * Backed by the monotonic `etag_version` counter, never a content hash: the tag + * must change after every mutation and must not repeat when content returns to + * a previous value. + */ +export function measurementDraftEtag(etagVersion: number): string { + return `"${MEASUREMENT_DRAFT_ETAG_PREFIX}${etagVersion}"` +} + +/** Reads a counter back from `If-Match`, which arrives quoted. Weak tags are not accepted. */ +export function parseMeasurementDraftEtagVersion(value: string): number | null { + const match = /^"?mpd_(\d+)"?$/.exec(value.trim()) + if (!match) return null + const parsed = Number(match[1]) + return Number.isSafeInteger(parsed) ? parsed : null +} + +export const measurementDraftCountsSchema = z.object({ + targets: z.number().int().nonnegative(), + includedTargets: z.number().int().nonnegative(), + assignments: z.number().int().nonnegative(), + /** What still blocks publish, surfaced on every mutation so nobody discovers it at publish time. */ + unclassifiedAssignments: z.number().int().nonnegative(), + groups: z.number().int().nonnegative(), + competitors: z.number().int().nonnegative(), +}).strict() +export type MeasurementDraftCounts = z.output + +export const measurementDraftWarningSchema = z.object({ + code: z.string().trim().min(1), + message: z.string().trim().min(1), + path: z.array(z.union([z.string(), z.number().int()])), +}).strict() +export type MeasurementDraftWarning = z.output + +/** Every typed draft action answers with this. `changed` is false when the action was a no-op. */ +export const measurementDraftMutationResponseSchema = z.object({ + etag: z.string().trim().min(1), + changed: z.boolean(), + warnings: z.array(measurementDraftWarningSchema), + counts: measurementDraftCountsSchema, +}).strict() +export type DraftMutationResponse = z.output + +export const measurementDraftResponseSchema = z.object({ + draft: measurementPlanDraftSchema.nullable(), + etag: z.string().trim().min(1).nullable(), +}).strict() +export type MeasurementDraftResponse = z.output + +export const measurementDraftTargetPageSchema = measurementCursorPageSchema(measurementDraftTargetSchema) +export const measurementDraftAssignmentPageSchema = measurementCursorPageSchema(measurementDraftAssignmentSchema) +export const measurementDraftGroupPageSchema = measurementCursorPageSchema(measurementDraftGroupSchema) + +export const measurementDraftCollectionQuerySchema = z.object({ + search: z.string().optional(), + cursor: z.string().trim().min(1).optional(), + limit: z.number().int().positive().max(MEASUREMENT_PAGE_MAX_LIMIT).optional(), +}).strict() +export type MeasurementDraftCollectionQuery = z.output + +/** The caller states the active revision it observed, so a draft is never started against a moved pointer. */ +export const measurementDraftCreateRequestSchema = z.object({ + expectedActiveRevision: measurementDraftRevisionSchema.nullable(), +}).strict() + +export const measurementDraftImportSitemapRequestSchema = z.object({ + sitemapUrl: z.string().url(), + rule: measurementDiscoveryRuleSchema, + exclusions: z.array(z.string().trim().min(1)).optional(), +}).strict() + +/** Discovery proposes; the operator selects. Ambiguity is never resolved automatically. */ +export const measurementDraftSitemapSelectionSchema = z.object({ + discoveryIdentity: z.string().trim().min(1), + action: z.enum(['create', 'rebind', 'ignore']), + targetKey: measurementV2StableKeySchema.optional(), + label: z.string().trim().min(1).optional(), +}).strict() + +export const measurementDraftApplySitemapSelectionRequestSchema = z.object({ + selections: z.array(measurementDraftSitemapSelectionSchema).min(1), +}).strict() + +export const measurementDraftUpsertTargetRequestSchema = z.object({ + target: measurementDraftTargetSchema, +}).strict() + +export const measurementDraftRenameTargetRequestSchema = z.object({ + targetKey: measurementV2StableKeySchema, + label: z.string().trim().min(1), +}).strict() + +/** The survivor keeps its key, its assignments and its group membership. */ +export const measurementDraftMergeTargetsRequestSchema = z.object({ + targetKey: measurementV2StableKeySchema, + mergedKeys: z.array(measurementV2StableKeySchema).min(1), +}).strict() + +export const measurementDraftExcludeTargetRequestSchema = z.object({ + targetKey: measurementV2StableKeySchema, +}).strict() + +/** Rebinding preserves `targetKey`, assignments and group membership by construction. */ +export const measurementDraftRebindTargetRequestSchema = z.object({ + targetKey: measurementV2StableKeySchema, + discoveryIdentity: z.string().trim().min(1), + discoveredUrl: z.string().url(), +}).strict() + +export const measurementDraftApplyAssignmentsRequestSchema = z.object({ + targetKey: measurementV2StableKeySchema, + queryIds: z.array(measurementDraftQueryIdSchema).min(1), + contextOverride: measurementDraftContextOverrideSchema.optional(), +}).strict() + +/** Removing an assignment never deletes the project query behind it. */ +export const measurementDraftRemoveAssignmentRequestSchema = z.object({ + targetKey: measurementV2StableKeySchema, + queryId: measurementDraftQueryIdSchema, +}).strict() + +export const measurementDraftClearAssignmentsRequestSchema = z.object({ + targetKey: measurementV2StableKeySchema, +}).strict() + +/** An explicit classification is always operator-sourced; the server records that, not the caller. */ +export const measurementDraftClassifyAssignmentsRequestSchema = z.object({ + queryClass: measurementQueryClassSchema, + assignments: z.array(z.object({ + targetKey: measurementV2StableKeySchema, + queryId: measurementDraftQueryIdSchema, + }).strict()).min(1), +}).strict() + +export const measurementDraftUpsertGroupRequestSchema = z.object({ + group: z.object({ + stableKey: measurementV2StableKeySchema, + label: z.string().trim().min(1), + targetKeys: z.array(measurementV2StableKeySchema), + }).strict(), +}).strict() + +export const measurementDraftRemoveGroupRequestSchema = z.object({ + groupKey: measurementV2StableKeySchema, +}).strict() + +export const measurementDraftUpsertCompetitorRequestSchema = z.object({ + groupKey: measurementV2StableKeySchema, + competitor: measurementDraftCompetitorSchema, +}).strict() + +export const measurementDraftRemoveCompetitorRequestSchema = z.object({ + groupKey: measurementV2StableKeySchema, + competitorKey: measurementV2StableKeySchema, +}).strict() + +/** Stable field paths plus a machine-readable rule id, so a browser can point at the field that failed. */ +export const measurementDraftCompileCheckSchema = z.object({ + ruleId: z.string().trim().min(1), + severity: z.enum(['fail', 'warn']), + message: z.string().trim().min(1), + path: z.array(z.union([z.string(), z.number().int()])), +}).strict() +export type MeasurementDraftCompileCheck = z.output + +export const measurementDraftCompilePreviewResponseSchema = z.discriminatedUnion('ok', [ + z.object({ + ok: z.literal(true), + compiledChecksum: sha256HexSchema, + checks: z.array(measurementDraftCompileCheckSchema), + counts: measurementDraftCountsSchema, + plan: measurementPlanV2Schema, + }).strict(), + z.object({ + ok: z.literal(false), + compiledChecksum: z.null(), + checks: z.array(measurementDraftCompileCheckSchema).min(1), + }).strict(), +]) +export type MeasurementDraftCompilePreviewResponse = z.output + +/** + * Keys, not bodies. A thousand-Target diff has to stay bounded, and the caller + * already has the rows through the paginated collections. + */ +const keyedDraftDiffSchema = z.object({ + added: z.array(z.string().trim().min(1)), + removed: z.array(z.string().trim().min(1)), + changed: z.array(z.string().trim().min(1)), + unchanged: z.array(z.string().trim().min(1)), +}).strict() + +export const measurementDraftDiffSchema = z.object({ + activeRevision: measurementDraftRevisionSchema.nullable(), + targets: keyedDraftDiffSchema, + groups: keyedDraftDiffSchema, + assignments: z.object({ + added: z.number().int().nonnegative(), + removed: z.number().int().nonnegative(), + reclassified: z.number().int().nonnegative(), + }).strict(), + execution: z.object({ + addedNodeKeys: z.array(z.string().trim().min(1)), + removedNodeKeys: z.array(z.string().trim().min(1)), + }).strict(), +}).strict() +export type MeasurementDraftDiff = z.output + +export const measurementDraftDiffPreviewResponseSchema = z.discriminatedUnion('ok', [ + z.object({ + ok: z.literal(true), + compiledChecksum: sha256HexSchema, + checks: z.array(measurementDraftCompileCheckSchema), + counts: measurementDraftCountsSchema, + plan: measurementPlanV2Schema, + diff: measurementDraftDiffSchema, + }).strict(), + z.object({ + ok: z.literal(false), + compiledChecksum: z.null(), + checks: z.array(measurementDraftCompileCheckSchema).min(1), + diff: z.null(), + }).strict(), +]) +export type MeasurementDraftDiffPreviewResponse = z.output + +/** + * Both guards are required. The revision proves the pointer has not moved; the + * compiled checksum proves the content is the one that was reviewed. + */ +export const measurementDraftPublishRequestSchema = z.object({ + expectedActiveRevision: measurementDraftRevisionSchema.nullable(), + expectedCompiledChecksum: sha256HexSchema, +}).strict() + +/** `published` is false on the no-op path: identical content to the active revision returns it unchanged. */ +export const measurementPlanV2PublishResponseSchema = z.object({ + published: z.boolean(), + active: z.object({ + revision: measurementDraftRevisionSchema, + checksum: sha256HexSchema, + compiledChecksum: sha256HexSchema, + createdAt: z.string().datetime(), + plan: measurementPlanV2Schema, + }).strict(), +}).strict() +export type MeasurementPlanV2PublishResponse = z.output + +export const measurementDraftDiscardResponseSchema = z.object({ + discarded: z.boolean(), +}).strict() + +/** Deactivation deletes the active-plan pointer row and nothing else. */ +export const measurementPlanDeactivateRequestSchema = z.object({ + expectedActiveRevision: measurementDraftRevisionSchema, +}).strict() + +export const measurementPlanDeactivateResponseSchema = z.object({ + deactivated: z.boolean(), + previousRevision: measurementDraftRevisionSchema.nullable(), +}).strict() + +export const measurementSetupStateSchema = z.enum([ + 'republish_required', + 'setup_in_progress', + 'awaiting_first_run', + 'operational', + 'simple', +]) +export type MeasurementSetupState = z.output + +export const measurementSetupNextActionSchema = z.enum([ + 'republish_setup', + 'continue_setup', + 'run_measurement', + 'view_measurement', + 'start_setup', +]) +export type MeasurementSetupNextAction = z.output + +export const measurementSetupModeSchema = z.enum(['simple', 'draft-only', 'active-v1', 'active-v2']) +export type MeasurementSetupMode = z.output + +/** + * Exactly one state, evaluated in a fixed precedence: a draft over an active v1 + * is `republish_required`, because republishing is the blocking action. + */ +export const measurementSetupResponseSchema = z.object({ + state: measurementSetupStateSchema, + nextAction: measurementSetupNextActionSchema, + mode: measurementSetupModeSchema, + activeRevision: measurementDraftRevisionSchema.nullable(), + activeSchemaVersion: z.union([z.literal(1), z.literal(2)]).nullable(), + draft: z.object({ + etag: z.string().trim().min(1), + updatedAt: z.string().datetime(), + }).strict().nullable(), +}).strict() +export type MeasurementSetupResponse = z.output + +/** Query sets hold ordered references. Deleting a set never deletes its queries or a published snapshot. */ +export const measurementQuerySetSchema = z.object({ + id: z.string().trim().min(1), + projectId: z.string().trim().min(1), + name: z.string().trim().min(1), + description: z.string().nullable(), + itemCount: z.number().int().nonnegative(), + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), +}).strict() +export type MeasurementQuerySet = z.output + +export const measurementQuerySetItemSchema = z.object({ + queryId: measurementDraftQueryIdSchema, + queryText: z.string().min(1), + position: z.number().int().nonnegative(), +}).strict() + +export const measurementQuerySetDetailSchema = measurementQuerySetSchema.extend({ + items: z.array(measurementQuerySetItemSchema), +}).strict() +export type MeasurementQuerySetDetail = z.output + +export const measurementQuerySetListResponseSchema = z.object({ + querySets: z.array(measurementQuerySetSchema), +}).strict() + +export const measurementQuerySetUpsertRequestSchema = z.object({ + name: z.string().trim().min(1), + description: z.string().nullable().optional(), + queryIds: z.array(measurementDraftQueryIdSchema), +}).strict() + +/** Authoring asset. Applying one expands concrete project queries; published plans hold only snapshots. */ +export const measurementQueryTemplateSchema = z.object({ + id: z.string().trim().min(1), + projectId: z.string().trim().min(1), + name: z.string().trim().min(1), + description: z.string().nullable(), + pattern: z.string().trim().min(1), + variables: z.array(z.string().trim().min(1)), + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), +}).strict() +export type MeasurementQueryTemplate = z.output + +export const measurementQueryTemplateListResponseSchema = z.object({ + templates: z.array(measurementQueryTemplateSchema), +}).strict() + +export const measurementQueryTemplateUpsertRequestSchema = z.object({ + name: z.string().trim().min(1), + description: z.string().nullable().optional(), + pattern: z.string().trim().min(1), + variables: z.array(z.string().trim().min(1)), +}).strict() + +export const measurementQueryTemplateApplyRequestSchema = z.object({ + bindings: z.array(z.record(z.string().trim().min(1), z.string().trim().min(1))).min(1), + querySetId: z.string().trim().min(1).optional(), +}).strict() + +/** Expansion is additive: an expansion that already exists is reported, never duplicated. */ +export const measurementQueryTemplateApplyResponseSchema = z.object({ + created: z.array(z.object({ + queryId: measurementDraftQueryIdSchema, + queryText: z.string().min(1), + }).strict()), + existing: z.array(z.object({ + queryId: measurementDraftQueryIdSchema, + queryText: z.string().min(1), + }).strict()), +}).strict() +export type MeasurementQueryTemplateApplyResponse = z.output diff --git a/packages/contracts/src/measurement-plan-v2.ts b/packages/contracts/src/measurement-plan-v2.ts new file mode 100644 index 000000000..07026065f --- /dev/null +++ b/packages/contracts/src/measurement-plan-v2.ts @@ -0,0 +1,412 @@ +import { z } from 'zod' +import { locationContextSchema, providerNameSchema } from './provider.js' +import { hostOf } from './url-normalize.js' + +/** + * Schema v2 adds Branded/Non-brand assignments, frozen group competitors and a + * review guard over the compiled document. v1 stays frozen and displayable; + * `parseStoredMeasurementPlan` dispatches on this literal. + */ +export const MEASUREMENT_PLAN_V2_SCHEMA_VERSION = 2 as const + +const sha256HexSchema = z.string().regex(/^[a-f0-9]{64}$/) +const measurementV2QueryIdSchema = z.string().trim().min(1).max(256) +const pathCaseSchema = z.enum(['sensitive', 'insensitive']) + +/** + * Structurally identical to `measurementStableKeySchema`, declared here rather + * than imported: `measurement-plan.ts` imports this module for its stored-plan + * dispatch, and importing back would close a module cycle that evaluates zod + * schemas before they exist. The v2 contract test pins the two to the same + * accept/reject set so they cannot drift apart. + */ +export const measurementV2StableKeySchema = z.string() + .trim() + .min(1) + .max(128) + .regex(/^[a-z0-9][\w.~-]*$/i, 'Must be a URL-safe stable key') + +const competitorDomainSchema = z.string().trim().min(1) + .refine(value => hostOf(value) !== null, 'A competitor domain must be a valid hostname') + +/** + * A stored matcher is already canonical — the compiler normalized it before the + * revision was frozen — so the decoder checks shape rather than re-running + * normalization over bytes that must never change. The field set matches + * `MeasurementTargetUrlMatcher` so v1 and v2 Targets resolve through the same + * attribution precedence. + */ +export const measurementV2UrlMatcherSchema = z.discriminatedUnion('kind', [ + z.object({ + kind: z.literal('exact'), + url: z.string().min(1), + pathCase: pathCaseSchema, + }).strict(), + z.object({ + kind: z.literal('prefix'), + host: z.string().min(1), + pathPrefix: z.string().min(1), + pathCase: pathCaseSchema, + }).strict(), + z.object({ + kind: z.literal('host'), + host: z.string().min(1), + }).strict(), +]) +export type MeasurementV2UrlMatcher = z.output + +/** Revision-frozen project identity. Live project config never rewrites a published revision. */ +export const measurementV2ProjectBrandSchema = z.object({ + canonicalHost: z.string().min(1), + ownedHosts: z.array(z.string().min(1)), + names: z.array(z.string().min(1)), +}).strict() + +export const measurementV2IdentitiesSchema = z.object({ + projectBrand: measurementV2ProjectBrandSchema, +}).strict() +export type MeasurementV2Identities = z.output + +export const measurementV2TargetSchema = z.object({ + stableKey: measurementV2StableKeySchema, + label: z.string().trim().min(1), + aliases: z.array(z.string().min(1)), + urlMatchers: z.array(measurementV2UrlMatcherSchema), + /** An aliasless Target can be cited but never mentioned; the flag keeps that out of a 0% reading. */ + mentionNotApplicable: z.boolean(), + /** Set only on a discovered Target. Rebinding preserves it, so history follows the identity. */ + discoveryIdentity: z.string().min(1).nullable(), +}).strict() +export type MeasurementV2Target = z.output + +export const measurementV2CompetitorSchema = z.object({ + stableKey: measurementV2StableKeySchema, + label: z.string().trim().min(1), + domain: competitorDomainSchema, + aliases: z.array(z.string().min(1)), +}).strict() +export type MeasurementV2Competitor = z.output + +/** Reporting membership and competitors only: a group never holds queries or execution context. */ +export const measurementV2GroupSchema = z.object({ + stableKey: measurementV2StableKeySchema, + label: z.string().trim().min(1), + targetKeys: z.array(measurementV2StableKeySchema), + competitors: z.array(measurementV2CompetitorSchema), +}).strict() +export type MeasurementV2Group = z.output + +export const measurementV2QueryProvenanceSourceSchema = z.enum(['manual', 'query-set', 'template', 'discovery']) +export type MeasurementV2QueryProvenanceSource = z.output + +/** Where the frozen question came from, so a later reader can explain the basket without the live assets. */ +export const measurementV2QueryProvenanceSchema = z.object({ + source: measurementV2QueryProvenanceSourceSchema, + sourceId: z.string().min(1).nullable(), + capturedAt: z.string().datetime(), +}).strict() + +export const measurementV2QuerySnapshotSchema = z.object({ + queryId: measurementV2QueryIdSchema, + queryText: z.string().min(1), + provenance: measurementV2QueryProvenanceSchema, +}).strict() +export type MeasurementV2QuerySnapshot = z.output + +/** The dedup identity: one provider request per unique query + context + provider slot. */ +export const measurementV2ExecutionContextSchema = z.object({ + providers: z.array(providerNameSchema).min(1), + models: z.record(providerNameSchema, z.string().min(1)), + location: locationContextSchema.nullable(), +}).strict() +export type MeasurementV2ExecutionContext = z.output + +export const measurementV2ExecutionNodeSchema = z.object({ + stableKey: z.string().min(1), + queryId: measurementV2QueryIdSchema, + queryText: z.string().min(1), + context: measurementV2ExecutionContextSchema, + expectedSnapshots: z.number().int().nonnegative(), +}).strict() +export type MeasurementV2ExecutionNode = z.output + +/** Published classes are exhaustive: an unclassified assignment never survives publish validation. */ +export const measurementQueryClassSchema = z.enum(['branded', 'non-brand']) +export type MeasurementQueryClass = z.output + +export const measurementV2AssignmentSchema = z.object({ + targetKey: measurementV2StableKeySchema, + queryId: measurementV2QueryIdSchema, + queryClass: measurementQueryClassSchema, + executionNodeKey: z.string().min(1), +}).strict() +export type MeasurementV2Assignment = z.output + +/** Target reuse of one execution node adds an edge, never a second provider call. */ +export const measurementV2UsageEdgeSchema = z.object({ + executionNodeKey: z.string().min(1), + targetKey: measurementV2StableKeySchema, + queryId: measurementV2QueryIdSchema, +}).strict() +export type MeasurementV2UsageEdge = z.output + +/** Frozen persisted v2 decoder. */ +export const measurementPlanV2Schema = z.object({ + schemaVersion: z.literal(MEASUREMENT_PLAN_V2_SCHEMA_VERSION), + identities: measurementV2IdentitiesSchema, + targets: z.array(measurementV2TargetSchema), + groups: z.array(measurementV2GroupSchema), + querySnapshots: z.array(measurementV2QuerySnapshotSchema), + assignments: z.array(measurementV2AssignmentSchema), + executionNodes: z.array(measurementV2ExecutionNodeSchema), + usageEdges: z.array(measurementV2UsageEdgeSchema), + /** + * The review guard, distinct from `measurement_plan_versions.checksum`. It + * excludes storage ids, timestamps, revision and itself, so the same content + * at two revisions compares equal and a revert is expressible. + */ + compiledChecksum: sha256HexSchema, +}).strict() +export type MeasurementPlanV2 = z.output + +function compareText(left: string, right: string): number { + return left < right ? -1 : left > right ? 1 : 0 +} + +/** Private copy of the v1 canonicalizer: this module must not import back into `measurement-plan.ts`. */ +function canonicalJsonValue(value: unknown): unknown { + if (Array.isArray(value)) return value.map(canonicalJsonValue) + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value) + .filter(([, child]) => child !== undefined) + .sort(([left], [right]) => compareText(left, right)) + .map(([key, child]) => [key, canonicalJsonValue(child)]), + ) + } + return value +} + +function matcherOrderKey(matcher: MeasurementV2UrlMatcher): string { + switch (matcher.kind) { + case 'exact': return ['exact', matcher.url, '', matcher.pathCase].join('') + case 'prefix': return ['prefix', matcher.host, matcher.pathPrefix, matcher.pathCase].join('') + case 'host': return ['host', matcher.host, '', ''].join('') + } +} + +/** + * Total order over every component of a revision, not only the assignment list. + * The dedup identity includes the provider set and the model map, so an + * ordering that left provider configuration alone would let one plan compile to + * two different checksums on two machines. + */ +export function canonicalMeasurementPlanV2(plan: MeasurementPlanV2): MeasurementPlanV2 { + return { + schemaVersion: plan.schemaVersion, + identities: { + projectBrand: { + canonicalHost: plan.identities.projectBrand.canonicalHost, + ownedHosts: [...plan.identities.projectBrand.ownedHosts].sort(compareText), + names: [...plan.identities.projectBrand.names].sort(compareText), + }, + }, + targets: [...plan.targets] + .map(target => ({ + ...target, + aliases: [...target.aliases].sort(compareText), + urlMatchers: [...target.urlMatchers].sort((left, right) => compareText(matcherOrderKey(left), matcherOrderKey(right))), + })) + .sort((left, right) => compareText(left.stableKey, right.stableKey)), + groups: [...plan.groups] + .map(group => ({ + ...group, + targetKeys: [...group.targetKeys].sort(compareText), + competitors: [...group.competitors] + .map(competitor => ({ ...competitor, aliases: [...competitor.aliases].sort(compareText) })) + .sort((left, right) => compareText(left.stableKey, right.stableKey)), + })) + .sort((left, right) => compareText(left.stableKey, right.stableKey)), + querySnapshots: [...plan.querySnapshots].sort((left, right) => compareText(left.queryId, right.queryId)), + assignments: [...plan.assignments].sort((left, right) => ( + compareText(left.targetKey, right.targetKey) + || compareText(left.queryId, right.queryId) + || compareText(left.executionNodeKey, right.executionNodeKey) + )), + executionNodes: [...plan.executionNodes] + .map(node => ({ + ...node, + context: { ...node.context, providers: [...node.context.providers].sort(compareText) }, + })) + .sort((left, right) => compareText(left.stableKey, right.stableKey)), + usageEdges: [...plan.usageEdges].sort((left, right) => ( + compareText(left.executionNodeKey, right.executionNodeKey) + || compareText(left.targetKey, right.targetKey) + || compareText(left.queryId, right.queryId) + )), + compiledChecksum: plan.compiledChecksum, + } +} + +/** Stable, browser-safe serialization of the whole stored document. */ +export function canonicalMeasurementPlanV2Json(plan: MeasurementPlanV2): string { + return JSON.stringify(canonicalJsonValue(canonicalMeasurementPlanV2(plan))) +} + +/** + * The exact bytes `compiledChecksum` is taken over. The checksum field itself + * is excluded, so hashing a revision and re-hashing what it published agree — + * and content restored from an older revision compares equal to it. + */ +export function measurementPlanV2ChecksumJson(plan: MeasurementPlanV2): string { + const { compiledChecksum: _guard, ...rest } = canonicalMeasurementPlanV2(plan) + return JSON.stringify(canonicalJsonValue(rest)) +} + +export const MEASUREMENT_PAGE_DEFAULT_LIMIT = 50 +export const MEASUREMENT_PAGE_MAX_LIMIT = 100 + +/** Cursor pagination for collections that must serve a thousand Targets without client-side truncation. */ +export function measurementCursorPageSchema(itemSchema: T) { + return z.object({ + items: z.array(itemSchema), + nextCursor: z.string().min(1).nullable(), + totalEstimate: z.number().int().nonnegative().optional(), + }).strict() +} + +export const measurementMetricUnavailableReasonSchema = z.enum([ + 'no_completed_run', + 'plan_v1', + 'no_population', + 'evidence_incomplete', + 'not_applicable', +]) +export type MeasurementMetricUnavailableReason = z.output + +/** + * Available and unavailable are deliberately different shapes: an unavailable + * metric carries no `value` key at all, so nothing downstream can read missing + * evidence as a numeric zero. + */ +export const measurementMetricValueSchema = z.discriminatedUnion('state', [ + z.object({ + state: z.literal('available'), + value: z.number(), + numerator: z.number().int().nonnegative().optional(), + denominator: z.number().int().positive().optional(), + }).strict(), + z.object({ + state: z.literal('unavailable'), + reason: measurementMetricUnavailableReasonSchema, + }).strict(), +]) +export type MetricValue = z.output + +export const measurementOverviewScopeKindSchema = z.enum(['all', 'group', 'property']) +export type MeasurementOverviewScopeKind = z.output + +export const measurementQueryClassFilterSchema = z.enum(['all', 'branded', 'non-brand']) +export type MeasurementQueryClassFilter = z.output + +export const measurementStateSchema = z.enum(['not_measured', 'queued', 'running', 'complete', 'partial', 'failed']) +export type MeasurementState = z.output + +export const measurementNextActionKindSchema = z.enum([ + 'run_measurement', + 'review_flags', + 'complete_setup', + 'republish_setup', + 'none', +]) +export type MeasurementNextActionKind = z.output + +/** + * `runId` is the only way to display a scoped spot check: run selection + * otherwise falls to the most recent completed run pinned to the active + * revision. A run pinned to another revision is refused rather than joined. + */ +export const measurementOverviewQuerySchema = z.object({ + scope: measurementOverviewScopeKindSchema, + groupKey: measurementV2StableKeySchema.optional(), + targetKey: measurementV2StableKeySchema.optional(), + queryClass: measurementQueryClassFilterSchema.optional(), + provider: providerNameSchema.optional(), + location: z.string().trim().min(1).optional(), + from: z.string().trim().min(1).optional(), + to: z.string().trim().min(1).optional(), + runId: z.string().trim().min(1).optional(), + /** Filters the returned rows only. Metrics are computed before it is applied. */ + search: z.string().optional(), + cursor: z.string().trim().min(1).optional(), + limit: z.number().int().positive().max(MEASUREMENT_PAGE_MAX_LIMIT).optional(), +}).strict() +export type MeasurementOverviewQuery = z.output + +export const measurementPropertyRowSchema = z.object({ + targetKey: measurementV2StableKeySchema, + label: z.string().min(1), + mentionCoverage: measurementMetricValueSchema, + citationCoverage: measurementMetricValueSchema, + flags: z.number().int().nonnegative(), +}).strict() +export type MeasurementPropertyRow = z.output + +export const measurementNamedShareOfVoiceEntrySchema = z.object({ + kind: z.enum(['project', 'competitor']), + stableKey: z.string().min(1), + label: z.string().min(1), + domain: z.string().min(1), + credits: z.number().int().nonnegative(), + share: z.number().min(0).max(1), +}).strict() + +/** + * Group-only, Non-brand-only. The basis is frozen in the payload so a reader + * can never mistake it for an all-questions share, and the denominator counts + * named presence credits rather than unique slots. + */ +export const measurementNamedShareOfVoiceSchema = z.object({ + groupKey: measurementV2StableKeySchema, + queryClass: z.literal('non-brand'), + denominator: z.number().int().positive(), + entries: z.array(measurementNamedShareOfVoiceEntrySchema), +}).strict() +export type NamedShareOfVoice = z.output + +export const measurementOverviewResponseSchema = z.object({ + mode: z.enum(['active-v1', 'active-v2']), + scope: z.object({ + kind: measurementOverviewScopeKindSchema, + key: z.string().min(1).optional(), + label: z.string().min(1), + }).strict(), + queryClass: measurementQueryClassFilterSchema, + measurement: z.object({ + state: measurementStateSchema, + currentRunId: z.string().min(1).optional(), + displayedRunId: z.string().min(1).optional(), + completed: z.number().int().nonnegative(), + expected: z.number().int().nonnegative(), + completedAt: z.string().datetime().optional(), + }).strict(), + nextAction: z.object({ + kind: measurementNextActionKindSchema, + count: z.number().int().nonnegative().optional(), + }).strict(), + metrics: z.object({ + propertiesMentioned: measurementMetricValueSchema, + mentionCoverage: measurementMetricValueSchema, + citationCoverage: measurementMetricValueSchema, + /** Independent identity presence, not a shared-denominator market share. */ + brandPresence: measurementMetricValueSchema, + /** @deprecated Identical to brandPresence. Kept until the browser migrates off it. */ + sov: measurementMetricValueSchema.describe( + 'Deprecated alias of brandPresence carrying the identical value. Remove once the browser migrates.', + ), + }).strict(), + properties: measurementCursorPageSchema(measurementPropertyRowSchema), + flags: z.object({ total: z.number().int().nonnegative() }).strict(), + namedShareOfVoice: measurementNamedShareOfVoiceSchema.optional(), +}).strict() +export type MeasurementOverviewResponse = z.output diff --git a/packages/contracts/src/measurement-plan.ts b/packages/contracts/src/measurement-plan.ts index 22b8b611f..05cec83cb 100644 --- a/packages/contracts/src/measurement-plan.ts +++ b/packages/contracts/src/measurement-plan.ts @@ -1,5 +1,10 @@ import { z } from 'zod' import { brandKeyFromText } from './brand-matching.js' +import { + MEASUREMENT_PLAN_V2_SCHEMA_VERSION, + measurementPlanV2Schema, + type MeasurementPlanV2, +} from './measurement-plan-v2.js' import { locationContextSchema, type LocationContext } from './provider.js' import { brandLabelFromDomain, hostOf } from './url-normalize.js' @@ -438,8 +443,11 @@ export const measurementPlanV1Schema = z.object({ export const measurementPlanSchema = measurementPlanV1Schema export type MeasurementPlan = z.output +/** Every stored revision shape, discriminated by `schemaVersion`. */ +export type StoredMeasurementPlan = MeasurementPlan | MeasurementPlanV2 + /** Explicit version dispatch prevents a future compiler from reinterpreting stored v1 rows. */ -export function parseStoredMeasurementPlan(value: unknown): MeasurementPlan { +export function parseStoredMeasurementPlanAnyVersion(value: unknown): StoredMeasurementPlan { if (typeof value === 'string') { try { value = JSON.parse(value) @@ -455,11 +463,31 @@ export function parseStoredMeasurementPlan(value: unknown): MeasurementPlan { if (!parsed.success) throw new Error('Stored measurement plan v1 is invalid') return parsed.data } + case MEASUREMENT_PLAN_V2_SCHEMA_VERSION: { + const parsed = measurementPlanV2Schema.safeParse(value) + if (!parsed.success) throw new Error('Stored measurement plan v2 is invalid') + return parsed.data + } default: throw new Error(`Unsupported stored measurement plan schema version: ${String(schemaVersion)}`) } } +/** + * v1-only reader. Callers that understand only the frozen v1 shape keep this + * signature and get a hard error on a later revision, because handing them a v2 + * document typed as v1 would leave every field they read undefined at runtime. + */ +export function parseStoredMeasurementPlan(value: unknown): MeasurementPlan { + const plan = parseStoredMeasurementPlanAnyVersion(value) + if (plan.schemaVersion !== MEASUREMENT_PLAN_SCHEMA_VERSION) { + throw new Error( + `Stored measurement plan revision is schema v${plan.schemaVersion}, which this reader does not understand`, + ) + } + return plan +} + const measurementPlanChecksumSchema = z.string().regex(/^[a-f0-9]{64}$/) const measurementPlanCreatedAtSchema = z.string().datetime() diff --git a/packages/contracts/test/measurement-draft.test.ts b/packages/contracts/test/measurement-draft.test.ts new file mode 100644 index 000000000..845876a09 --- /dev/null +++ b/packages/contracts/test/measurement-draft.test.ts @@ -0,0 +1,205 @@ +import { describe, expect, it } from 'vitest' +import { + MEASUREMENT_DRAFT_ETAG_PREFIX, + measurementDraftAuthoringSchema, + measurementDraftClassifyAssignmentsRequestSchema, + measurementDraftEtag, + measurementDraftMutationResponseSchema, + measurementDraftPublishRequestSchema, + measurementDraftTargetPageSchema, + measurementDraftUpsertGroupRequestSchema, + measurementPlanDraftSchema, + measurementQuerySetUpsertRequestSchema, + measurementQueryTemplateApplyRequestSchema, + measurementSetupResponseSchema, + parseMeasurementDraftEtagVersion, + type MeasurementDraftAuthoring, +} from '../src/measurement-draft.js' +import { + measurementDraftEtagRequired, + measurementDraftEtagStale, + measurementCompiledChecksumConflict, + measurementIdempotencyKeyConflict, + measurementRunRevisionMismatch, +} from '../src/errors.js' + +const AUTHORING: MeasurementDraftAuthoring = measurementDraftAuthoringSchema.parse({ + defaultContext: { providers: ['gemini'], models: { gemini: 'gemini-3-pro' }, locations: ['northbridge'] }, + targets: [{ + stableKey: 'harbor-point', + label: 'Harbor Point', + status: 'included', + aliases: ['Harbor Point'], + urlMatchers: ['https://northstar.example/apartments/harbor-point'], + source: 'sitemap', + discoveredUrl: 'https://northstar.example/apartments/harbor-point', + discoveryIdentity: 'northstar.example/apartments/{slug}#harbor-point', + }], + assignments: [{ + targetKey: 'harbor-point', + queryId: 'q-best', + queryClass: 'unclassified', + classificationSource: 'rule', + }], + groups: [{ + stableKey: 'northbridge-portfolio', + label: 'Northbridge portfolio', + targetKeys: ['harbor-point'], + competitors: [{ stableKey: 'harborview', label: 'Harborview', domain: 'harborview.example', aliases: ['Harborview'] }], + }], +}) + +describe('measurement plan draft', () => { + it('stores authoring intent and the active revision it was created from', () => { + const draft = measurementPlanDraftSchema.parse({ + id: 'mpd-1', + projectId: 'prj-1', + schemaVersion: 2, + baseActiveVersionId: 'mpv-4', + baseActiveRevision: 4, + authoring: AUTHORING, + createdBy: { kind: 'user', id: 'usr-1', label: 'operator' }, + updatedBy: { kind: 'user', id: 'usr-1', label: 'operator' }, + createdAt: '2026-08-01T00:00:00.000Z', + updatedAt: '2026-08-01T00:00:00.000Z', + }) + expect(draft.baseActiveRevision).toBe(4) + expect(draft.authoring.assignments[0]!.queryClass).toBe('unclassified') + }) + + it('refuses compiled output on a draft, which stores authoring intent only', () => { + expect(() => measurementDraftAuthoringSchema.parse({ ...AUTHORING, executionNodes: [] })).toThrow() + expect(() => measurementDraftAuthoringSchema.parse({ ...AUTHORING, querySnapshots: [] })).toThrow() + }) + + it('lets a draft carry an unclassified assignment that publish will later reject', () => { + const authoring = measurementDraftAuthoringSchema.parse(AUTHORING) + expect(authoring.assignments.map(assignment => assignment.queryClass)).toEqual(['unclassified']) + }) +}) + +describe('measurement draft ETag', () => { + it('is a quoted strong tag derived from the monotonic counter, not a content hash', () => { + expect(measurementDraftEtag(7)).toBe(`"${MEASUREMENT_DRAFT_ETAG_PREFIX}7"`) + expect(measurementDraftEtag(7)).not.toBe(measurementDraftEtag(8)) + }) + + it('reads back a counter from either the quoted or bare header form', () => { + expect(parseMeasurementDraftEtagVersion('"mpd_7"')).toBe(7) + expect(parseMeasurementDraftEtagVersion('mpd_7')).toBe(7) + expect(parseMeasurementDraftEtagVersion('W/"mpd_7"')).toBeNull() + expect(parseMeasurementDraftEtagVersion('"mpd_"')).toBeNull() + expect(parseMeasurementDraftEtagVersion('"7"')).toBeNull() + }) +}) + +describe('measurement draft action payloads', () => { + it('rejects a group payload that carries queries or execution context', () => { + const group = { stableKey: 'northbridge-portfolio', label: 'Northbridge portfolio', targetKeys: ['harbor-point'] } + expect(measurementDraftUpsertGroupRequestSchema.parse({ group })).toEqual({ group }) + expect(() => measurementDraftUpsertGroupRequestSchema.parse({ group: { ...group, queryIds: ['q-best'] } })).toThrow() + expect(() => measurementDraftUpsertGroupRequestSchema.parse({ group: { ...group, providers: ['gemini'] } })).toThrow() + expect(() => measurementDraftUpsertGroupRequestSchema.parse({ group: { ...group, locations: ['northbridge'] } })).toThrow() + }) + + it('only lets an operator classify into a published class', () => { + const assignments = [{ targetKey: 'harbor-point', queryId: 'q-best' }] + expect(measurementDraftClassifyAssignmentsRequestSchema.parse({ queryClass: 'branded', assignments }).queryClass) + .toBe('branded') + expect(() => measurementDraftClassifyAssignmentsRequestSchema.parse({ queryClass: 'unclassified', assignments })) + .toThrow() + }) + + it('requires both publish guards, so content that changed after review cannot slip through', () => { + const checksum = 'a'.repeat(64) + expect(measurementDraftPublishRequestSchema.parse({ expectedActiveRevision: 4, expectedCompiledChecksum: checksum })) + .toEqual({ expectedActiveRevision: 4, expectedCompiledChecksum: checksum }) + expect(() => measurementDraftPublishRequestSchema.parse({ expectedActiveRevision: 4 })).toThrow() + expect(() => measurementDraftPublishRequestSchema.parse({ expectedCompiledChecksum: checksum })).toThrow() + }) + + it('returns the new ETag, what changed, warnings and counts from every mutation', () => { + const response = measurementDraftMutationResponseSchema.parse({ + etag: '"mpd_8"', + changed: true, + warnings: [{ code: 'target-alias-prefix-overlap', message: 'Target aliases overlap by mention prefix', path: ['targets'] }], + counts: { + targets: 1, + includedTargets: 1, + assignments: 1, + unclassifiedAssignments: 1, + groups: 1, + competitors: 1, + }, + }) + expect(response.etag).toBe('"mpd_8"') + expect(response.counts.unclassifiedAssignments).toBe(1) + }) +}) + +describe('measurement draft collections', () => { + it('pages Targets by cursor rather than truncating them', () => { + const page = measurementDraftTargetPageSchema.parse({ + items: AUTHORING.targets, + nextCursor: 'harbor-point', + totalEstimate: 194, + }) + expect(page.nextCursor).toBe('harbor-point') + expect(page.totalEstimate).toBe(194) + }) +}) + +describe('measurement query assets', () => { + it('holds ordered references to project query ids', () => { + const parsed = measurementQuerySetUpsertRequestSchema.parse({ + name: 'Northbridge non-brand', + description: null, + queryIds: ['q-best', 'q-nearby'], + }) + expect(parsed.queryIds).toEqual(['q-best', 'q-nearby']) + }) + + it('expands a template through explicit bindings', () => { + const parsed = measurementQueryTemplateApplyRequestSchema.parse({ + bindings: [{ city: 'Northbridge' }, { city: 'Southbridge' }], + }) + expect(parsed.bindings).toHaveLength(2) + }) +}) + +describe('measurement setup state', () => { + it('names republish as the blocking action for an active v1 plan with a draft over it', () => { + const parsed = measurementSetupResponseSchema.parse({ + state: 'republish_required', + nextAction: 'republish_setup', + mode: 'active-v1', + activeRevision: 4, + activeSchemaVersion: 1, + draft: { etag: '"mpd_2"', updatedAt: '2026-08-01T00:00:00.000Z' }, + }) + expect(parsed.state).toBe('republish_required') + expect(parsed.nextAction).toBe('republish_setup') + }) +}) + +describe('measurement error codes', () => { + it('maps each failure to the status the spec fixes for it', () => { + expect(measurementCompiledChecksumConflict('a'.repeat(64), 'b'.repeat(64))).toMatchObject({ + code: 'MEASUREMENT_COMPILED_CHECKSUM_CONFLICT', + statusCode: 409, + }) + expect(measurementRunRevisionMismatch('run-1', 3, 4)).toMatchObject({ + code: 'MEASUREMENT_RUN_REVISION_MISMATCH', + statusCode: 422, + }) + expect(measurementDraftEtagRequired()).toMatchObject({ code: 'MEASUREMENT_DRAFT_ETAG_REQUIRED', statusCode: 428 }) + expect(measurementDraftEtagStale('"mpd_7"', '"mpd_8"')).toMatchObject({ + code: 'MEASUREMENT_DRAFT_ETAG_STALE', + statusCode: 412, + }) + expect(measurementIdempotencyKeyConflict('publish')).toMatchObject({ + code: 'MEASUREMENT_IDEMPOTENCY_KEY_CONFLICT', + statusCode: 409, + }) + }) +}) diff --git a/packages/contracts/test/measurement-plan-v2.test.ts b/packages/contracts/test/measurement-plan-v2.test.ts new file mode 100644 index 000000000..39d5314ac --- /dev/null +++ b/packages/contracts/test/measurement-plan-v2.test.ts @@ -0,0 +1,232 @@ +import { describe, expect, it } from 'vitest' +import { measurementStableKeySchema } from '../src/measurement-plan.js' +import { + MEASUREMENT_PLAN_V2_SCHEMA_VERSION, + canonicalMeasurementPlanV2Json, + measurementMetricValueSchema, + measurementOverviewQuerySchema, + measurementOverviewResponseSchema, + measurementPlanV2ChecksumJson, + measurementPlanV2Schema, + measurementV2StableKeySchema, + type MeasurementPlanV2, +} from '../src/measurement-plan-v2.js' + +const NORTHBRIDGE = { label: 'northbridge', city: 'Northbridge', region: 'NB', country: 'US' } +const CHECKSUM = 'a'.repeat(64) + +function planV2(): MeasurementPlanV2 { + return measurementPlanV2Schema.parse({ + schemaVersion: 2, + identities: { + projectBrand: { + canonicalHost: 'northstar.example', + ownedHosts: ['northstar.example', 'residences.northstar.example'], + names: ['Northstar Living'], + }, + }, + targets: [ + { + stableKey: 'harbor-point', + label: 'Harbor Point', + aliases: ['Harbor Point'], + urlMatchers: [{ kind: 'prefix', host: 'northstar.example', pathPrefix: '/apartments/harbor-point', pathCase: 'insensitive' }], + mentionNotApplicable: false, + discoveryIdentity: 'northstar.example/apartments/{slug}#harbor-point', + }, + { + stableKey: 'sail-loft', + label: 'Sail Loft', + aliases: [], + urlMatchers: [{ kind: 'host', host: 'residences.northstar.example' }], + mentionNotApplicable: true, + discoveryIdentity: null, + }, + ], + groups: [ + { + stableKey: 'northbridge-portfolio', + label: 'Northbridge portfolio', + targetKeys: ['harbor-point', 'sail-loft'], + competitors: [ + { stableKey: 'harborview', label: 'Harborview', domain: 'harborview.example', aliases: ['Harborview'] }, + ], + }, + ], + querySnapshots: [ + { + queryId: 'q-best', + queryText: 'best apartments in northbridge', + provenance: { source: 'manual', sourceId: null, capturedAt: '2026-08-01T00:00:00.000Z' }, + }, + { + queryId: 'q-northstar', + queryText: 'northstar apartments', + provenance: { source: 'template', sourceId: 'tpl-brand', capturedAt: '2026-08-01T00:00:00.000Z' }, + }, + ], + assignments: [ + { targetKey: 'harbor-point', queryId: 'q-best', queryClass: 'non-brand', executionNodeKey: 'exec-best' }, + { targetKey: 'harbor-point', queryId: 'q-northstar', queryClass: 'branded', executionNodeKey: 'exec-northstar' }, + { targetKey: 'sail-loft', queryId: 'q-best', queryClass: 'non-brand', executionNodeKey: 'exec-best' }, + ], + executionNodes: [ + { + stableKey: 'exec-best', + queryId: 'q-best', + queryText: 'best apartments in northbridge', + context: { providers: ['gemini', 'openai'], models: { gemini: 'gemini-3-pro', openai: 'gpt-5.4' }, location: NORTHBRIDGE }, + expectedSnapshots: 2, + }, + { + stableKey: 'exec-northstar', + queryId: 'q-northstar', + queryText: 'northstar apartments', + context: { providers: ['gemini', 'openai'], models: { gemini: 'gemini-3-pro', openai: 'gpt-5.4' }, location: null }, + expectedSnapshots: 2, + }, + ], + usageEdges: [ + { executionNodeKey: 'exec-best', targetKey: 'harbor-point', queryId: 'q-best' }, + { executionNodeKey: 'exec-best', targetKey: 'sail-loft', queryId: 'q-best' }, + { executionNodeKey: 'exec-northstar', targetKey: 'harbor-point', queryId: 'q-northstar' }, + ], + compiledChecksum: CHECKSUM, + }) +} + +describe('published measurement plan v2', () => { + it('pins the schema version and round-trips a compiled revision', () => { + const plan = planV2() + expect(MEASUREMENT_PLAN_V2_SCHEMA_VERSION).toBe(2) + expect(plan.schemaVersion).toBe(2) + expect(measurementPlanV2Schema.parse(plan)).toEqual(plan) + }) + + it('rejects unknown keys so a compiler cannot smuggle authoring state into a published revision', () => { + const plan = planV2() + expect(() => measurementPlanV2Schema.parse({ ...plan, targetQuerySelections: [] })).toThrow() + }) + + it('requires a compiled checksum on every published revision', () => { + const { compiledChecksum: _dropped, ...withoutChecksum } = planV2() + expect(() => measurementPlanV2Schema.parse(withoutChecksum)).toThrow() + }) + + it('accepts the v2 stable key vocabulary exactly where v1 does', () => { + const cases = ['harbor-point', 'Harbor.Point~1', 'a', '9lives', '-leading-dash', '', 'has space', 'x'.repeat(129)] + for (const value of cases) { + expect( + measurementV2StableKeySchema.safeParse(value).success, + `v2 stable key disagrees with v1 on ${JSON.stringify(value)}`, + ).toBe(measurementStableKeySchema.safeParse(value).success) + } + }) +}) + +describe('measurement plan v2 canonical ordering', () => { + it('orders provider configuration too, not only the assignment list', () => { + const plan = planV2() + const shuffled: MeasurementPlanV2 = { + ...plan, + identities: { + projectBrand: { ...plan.identities.projectBrand, ownedHosts: [...plan.identities.projectBrand.ownedHosts].reverse() }, + }, + targets: [...plan.targets].reverse(), + groups: plan.groups.map(group => ({ ...group, targetKeys: [...group.targetKeys].reverse() })), + querySnapshots: [...plan.querySnapshots].reverse(), + assignments: [...plan.assignments].reverse(), + usageEdges: [...plan.usageEdges].reverse(), + executionNodes: [...plan.executionNodes].reverse().map(node => ({ + ...node, + context: { + ...node.context, + providers: [...node.context.providers].reverse(), + models: { openai: node.context.models.openai!, gemini: node.context.models.gemini! }, + }, + })), + } + + expect(canonicalMeasurementPlanV2Json(shuffled)).toBe(canonicalMeasurementPlanV2Json(plan)) + }) + + it('excludes the checksum field from the bytes the checksum is taken over', () => { + const plan = planV2() + const rehashed: MeasurementPlanV2 = { ...plan, compiledChecksum: 'b'.repeat(64) } + + expect(measurementPlanV2ChecksumJson(rehashed)).toBe(measurementPlanV2ChecksumJson(plan)) + expect(measurementPlanV2ChecksumJson(plan)).not.toContain('compiledChecksum') + expect(canonicalMeasurementPlanV2Json(plan)).toContain('compiledChecksum') + }) + + it('gives different content different checksum bytes', () => { + const plan = planV2() + const changed: MeasurementPlanV2 = { + ...plan, + assignments: plan.assignments.map(assignment => ({ ...assignment, queryClass: 'branded' as const })), + } + expect(measurementPlanV2ChecksumJson(changed)).not.toBe(measurementPlanV2ChecksumJson(plan)) + }) +}) + +describe('measurement metric value', () => { + it('serializes an unavailable metric without any numeric field', () => { + const unavailable = measurementMetricValueSchema.parse({ state: 'unavailable', reason: 'no_completed_run' }) + expect(unavailable).toEqual({ state: 'unavailable', reason: 'no_completed_run' }) + expect(JSON.stringify(unavailable)).not.toContain('0') + expect(Object.keys(unavailable)).not.toContain('value') + }) + + it('refuses an unavailable metric that also carries a value', () => { + expect(() => measurementMetricValueSchema.parse({ state: 'unavailable', reason: 'plan_v1', value: 0 })).toThrow() + }) + + it('carries the numerator and denominator an available rate rests on', () => { + expect(measurementMetricValueSchema.parse({ state: 'available', value: 0.5, numerator: 3, denominator: 6 })) + .toEqual({ state: 'available', value: 0.5, numerator: 3, denominator: 6 }) + }) +}) + +describe('measurement overview response', () => { + const unavailable = { state: 'unavailable', reason: 'no_completed_run' } as const + + function overview() { + return { + mode: 'active-v2', + scope: { kind: 'all', label: 'All Properties' }, + queryClass: 'all', + measurement: { state: 'not_measured', completed: 0, expected: 0 }, + nextAction: { kind: 'run_measurement' }, + metrics: { + propertiesMentioned: unavailable, + mentionCoverage: unavailable, + citationCoverage: unavailable, + brandPresence: unavailable, + sov: unavailable, + }, + properties: { items: [], nextCursor: null }, + flags: { total: 0 }, + } + } + + it('emits brandPresence and keeps sov as its deprecated alias', () => { + const parsed = measurementOverviewResponseSchema.parse(overview()) + expect(parsed.metrics.brandPresence).toEqual(unavailable) + expect(parsed.metrics.sov).toEqual(parsed.metrics.brandPresence) + }) + + it('refuses a response that drops the deprecated sov alias while the browser still reads it', () => { + const { sov: _dropped, ...metrics } = overview().metrics + expect(() => measurementOverviewResponseSchema.parse({ ...overview(), metrics })).toThrow() + }) + + it('accepts an explicit run selection on the query', () => { + const parsed = measurementOverviewQuerySchema.parse({ scope: 'group', groupKey: 'northbridge-portfolio', runId: 'run-7' }) + expect(parsed).toEqual({ scope: 'group', groupKey: 'northbridge-portfolio', runId: 'run-7' }) + }) + + it('caps the page limit so a Target list cannot be pulled unbounded', () => { + expect(() => measurementOverviewQuerySchema.parse({ scope: 'all', limit: 101 })).toThrow() + expect(measurementOverviewQuerySchema.parse({ scope: 'all', limit: 100 }).limit).toBe(100) + }) +}) diff --git a/packages/contracts/test/measurement-plan-version-dispatch.test.ts b/packages/contracts/test/measurement-plan-version-dispatch.test.ts new file mode 100644 index 000000000..54ec7a199 --- /dev/null +++ b/packages/contracts/test/measurement-plan-version-dispatch.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from 'vitest' +import { + canonicalMeasurementPlanJson, + compileMeasurementPlan, + parseStoredMeasurementPlan, + parseStoredMeasurementPlanAnyVersion, + type MeasurementPlanInput, +} from '../src/measurement-plan.js' +import { canonicalMeasurementPlanV2Json, measurementPlanV2Schema } from '../src/measurement-plan-v2.js' + +const NORTHBRIDGE = { label: 'northbridge', city: 'Northbridge', region: 'NB', country: 'US' } + +const CONTEXT = { + canonicalDomain: 'https://www.northstar.example/', + ownedDomains: ['residences.northstar.example'], + brandNames: ['Northstar Living'], + defaultContext: NORTHBRIDGE, + locations: [NORTHBRIDGE], + trackedQueries: [{ id: 'q-best', query: 'best apartments in northbridge' }], + expectedSnapshots: 2, +} + +const V1_INPUT: MeasurementPlanInput = { + schemaVersion: 1, + targets: [{ + stableKey: 'harbor-point', + label: 'Harbor Point', + urls: [{ kind: 'prefix', host: 'northstar.example', pathPrefix: '/apartments/harbor-point', pathCase: 'insensitive' }], + aliases: ['Harbor Point'], + }], + targetQuerySelections: [{ targetKey: 'harbor-point', queryIds: ['q-best'] }], +} + +const V2_PLAN = measurementPlanV2Schema.parse({ + schemaVersion: 2, + identities: { + projectBrand: { + canonicalHost: 'northstar.example', + ownedHosts: ['northstar.example'], + names: ['Northstar Living'], + }, + }, + targets: [{ + stableKey: 'harbor-point', + label: 'Harbor Point', + aliases: ['Harbor Point'], + urlMatchers: [{ kind: 'prefix', host: 'northstar.example', pathPrefix: '/apartments/harbor-point', pathCase: 'insensitive' }], + mentionNotApplicable: false, + discoveryIdentity: null, + }], + groups: [], + querySnapshots: [{ + queryId: 'q-best', + queryText: 'best apartments in northbridge', + provenance: { source: 'manual', sourceId: null, capturedAt: '2026-08-01T00:00:00.000Z' }, + }], + assignments: [{ targetKey: 'harbor-point', queryId: 'q-best', queryClass: 'non-brand', executionNodeKey: 'exec-best' }], + executionNodes: [{ + stableKey: 'exec-best', + queryId: 'q-best', + queryText: 'best apartments in northbridge', + context: { providers: ['gemini'], models: { gemini: 'gemini-3-pro' }, location: NORTHBRIDGE }, + expectedSnapshots: 1, + }], + usageEdges: [{ executionNodeKey: 'exec-best', targetKey: 'harbor-point', queryId: 'q-best' }], + compiledChecksum: 'c'.repeat(64), +}) + +describe('stored measurement plan version dispatch', () => { + it('decodes a stored v1 revision byte-identically through the v1 path', () => { + const compiled = compileMeasurementPlan(V1_INPUT, CONTEXT) + const stored = canonicalMeasurementPlanJson(compiled) + + expect(parseStoredMeasurementPlan(stored)).toEqual(compiled) + expect(canonicalMeasurementPlanJson(parseStoredMeasurementPlan(stored))).toBe(stored) + expect(parseStoredMeasurementPlanAnyVersion(stored)).toEqual(compiled) + expect(canonicalMeasurementPlanJson(parseStoredMeasurementPlanAnyVersion(stored) as typeof compiled)).toBe(stored) + }) + + it('decodes a stored v2 revision through the new v2 case', () => { + const stored = canonicalMeasurementPlanV2Json(V2_PLAN) + + expect(parseStoredMeasurementPlanAnyVersion(V2_PLAN)).toEqual(V2_PLAN) + expect(parseStoredMeasurementPlanAnyVersion(stored)).toEqual(V2_PLAN) + }) + + it('refuses a malformed v2 revision instead of falling back to v1', () => { + expect(() => parseStoredMeasurementPlanAnyVersion({ schemaVersion: 2, targets: [] })) + .toThrow('Stored measurement plan v2 is invalid') + }) + + it('still throws on an unknown schema version', () => { + expect(() => parseStoredMeasurementPlanAnyVersion({ ...V2_PLAN, schemaVersion: 3 })) + .toThrow('Unsupported stored measurement plan schema version: 3') + expect(() => parseStoredMeasurementPlan({ ...V2_PLAN, schemaVersion: 3 })) + .toThrow('Unsupported stored measurement plan schema version: 3') + }) + + it('refuses to hand a v2 revision to a v1-only reader', () => { + // Silently typing a v2 document as v1 is the failure this prevents: every + // v1 field the caller then reads would be undefined at runtime. + expect(() => parseStoredMeasurementPlan(V2_PLAN)) + .toThrow('Stored measurement plan revision is schema v2, which this reader does not understand') + }) +}) diff --git a/packages/contracts/test/measurement-plan.test.ts b/packages/contracts/test/measurement-plan.test.ts index 25bd771b0..db57915e7 100644 --- a/packages/contracts/test/measurement-plan.test.ts +++ b/packages/contracts/test/measurement-plan.test.ts @@ -413,8 +413,13 @@ describe('Target aliases and frozen storage', () => { const compiled = compile() expect(parseStoredMeasurementPlan(compiled)).toEqual(compiled) expect(parseStoredMeasurementPlan(canonicalMeasurementPlanJson(compiled))).toEqual(compiled) + // v2 is a known version now, so a v1 body wearing its label fails v2 + // validation instead of reading as an unknown version. expect(() => parseStoredMeasurementPlan({ ...compiled, schemaVersion: 2 })).toThrow( - 'Unsupported stored measurement plan schema version: 2', + 'Stored measurement plan v2 is invalid', + ) + expect(() => parseStoredMeasurementPlan({ ...compiled, schemaVersion: 3 })).toThrow( + 'Unsupported stored measurement plan schema version: 3', ) expect(() => parseStoredMeasurementPlan({ schemaVersion: 1, cohorts: [] })).toThrow('Stored measurement plan v1 is invalid') }) diff --git a/packages/db/src/migrate.ts b/packages/db/src/migrate.ts index ae793b3fb..d8db9041e 100644 --- a/packages/db/src/migrate.ts +++ b/packages/db/src/migrate.ts @@ -2702,6 +2702,128 @@ export const MIGRATION_VERSIONS: ReadonlyArray = [ `CREATE INDEX IF NOT EXISTS idx_user_sessions_expires ON user_sessions(expires_at)`, ], }, + { + // Advanced Measurement v2 storage on the existing immutable revisions. + // Every column is additive and every existing row is v1, so nothing + // published is rewritten. + // + // `compiled_checksum` is a NEW column beside `checksum`, not a redefinition + // of it. `checksum` hashes the whole stored document, revision included, so + // reusing it as the publish guard would make identical content at two + // revisions hash differently — "identical to the active revision is a no-op" + // and revert would both stop working. It is nullable because it was never + // computed for a historic v1 row, and inventing one would claim a review + // that never happened. + version: 122, + name: 'measurement-plan-v2-version-columns', + statements: [ + // NOT NULL DEFAULT 1 is the backfill: SQLite writes the default into + // every existing row as it adds the column, so each historic revision + // ends up explicitly marked v1 without a separate UPDATE pass. + `ALTER TABLE measurement_plan_versions ADD COLUMN schema_version INTEGER NOT NULL DEFAULT 1`, + `ALTER TABLE measurement_plan_versions ADD COLUMN compiled_checksum TEXT`, + `ALTER TABLE measurement_plan_versions ADD COLUMN published_by TEXT`, + `ALTER TABLE measurement_plan_versions ADD COLUMN source_draft_id TEXT`, + `CREATE INDEX IF NOT EXISTS idx_measurement_plan_versions_project_revision_desc + ON measurement_plan_versions(project_id, revision DESC)`, + `CREATE INDEX IF NOT EXISTS idx_measurement_plan_versions_project_schema + ON measurement_plan_versions(project_id, schema_version, revision DESC)`, + // Lookup only, and NOT unique on purpose: a unique index here would + // refuse to publish content identical to an older revision, leaving an + // operator who changed a setting and changed it back with no way out. + `CREATE INDEX IF NOT EXISTS idx_measurement_plan_versions_compiled_checksum + ON measurement_plan_versions(project_id, compiled_checksum)`, + ], + }, + { + // Server-side authoring: one draft per project, the query assets a draft + // authors from, the discovery inputs a rerun must reproduce, and the + // idempotency receipts every mutating action writes. All new tables, so no + // existing row is touched and every install starts with them empty. + version: 123, + name: 'measurement-authoring-tables', + statements: [ + `CREATE TABLE IF NOT EXISTS measurement_plan_drafts ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL UNIQUE REFERENCES projects(id) ON DELETE CASCADE, + schema_version INTEGER NOT NULL DEFAULT 2, + base_active_version_id TEXT, + base_active_revision INTEGER, + authoring_json TEXT NOT NULL, + etag_version INTEGER NOT NULL DEFAULT 1, + created_by TEXT NOT NULL, + updated_by TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`, + `CREATE TABLE IF NOT EXISTS measurement_query_sets ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + name TEXT NOT NULL, + description TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_query_sets_project_name + ON measurement_query_sets(project_id, name)`, + // ON DELETE CASCADE from the set drops the membership row; the FK to + // queries only follows a query that was itself deleted. Deleting a set + // never reaches a query. + `CREATE TABLE IF NOT EXISTS measurement_query_set_items ( + id TEXT PRIMARY KEY, + query_set_id TEXT NOT NULL REFERENCES measurement_query_sets(id) ON DELETE CASCADE, + query_id TEXT NOT NULL REFERENCES queries(id) ON DELETE CASCADE, + position INTEGER NOT NULL, + created_at TEXT NOT NULL + )`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_query_set_items_set_query + ON measurement_query_set_items(query_set_id, query_id)`, + `CREATE INDEX IF NOT EXISTS idx_measurement_query_set_items_order + ON measurement_query_set_items(query_set_id, position)`, + `CREATE TABLE IF NOT EXISTS measurement_query_templates ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + name TEXT NOT NULL, + description TEXT, + pattern TEXT NOT NULL, + variables_json TEXT NOT NULL DEFAULT '[]', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_query_templates_project_name + ON measurement_query_templates(project_id, name)`, + `CREATE TABLE IF NOT EXISTS measurement_discovery_configs ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + sitemap_url TEXT NOT NULL, + rule_json TEXT NOT NULL, + exclusions_json TEXT NOT NULL DEFAULT '[]', + input_checksum TEXT NOT NULL, + compiler_version TEXT NOT NULL, + reviewed_at TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_discovery_configs_input + ON measurement_discovery_configs(project_id, input_checksum)`, + `CREATE INDEX IF NOT EXISTS idx_measurement_discovery_configs_project + ON measurement_discovery_configs(project_id)`, + `CREATE TABLE IF NOT EXISTS measurement_operation_receipts ( + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + operation TEXT NOT NULL, + idempotency_key TEXT NOT NULL, + request_checksum TEXT NOT NULL, + response_json TEXT NOT NULL, + status_code INTEGER NOT NULL, + created_at TEXT NOT NULL, + expires_at TEXT NOT NULL, + PRIMARY KEY (project_id, operation, idempotency_key) + )`, + // Nothing on the write path deletes a receipt, so the sweep needs this. + `CREATE INDEX IF NOT EXISTS idx_measurement_operation_receipts_expires + ON measurement_operation_receipts(expires_at)`, + ], + }, ] function addRunsMeasurementPlanVersionForeignKey(tx: MigrationDb): void { diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 689d88e8c..dafb86ab6 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -59,11 +59,137 @@ export const measurementPlanVersions = sqliteTable('measurement_plan_versions', projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }), revision: integer('revision').notNull(), canonicalJson: text('canonical_json').notNull(), + /** Document identity: sha256 over the whole stored document, revision included. */ checksum: text('checksum').notNull(), + /** Which decoder reads `canonical_json`. Every row written before v122 is v1. */ + schemaVersion: integer('schema_version').notNull().default(1), + /** + * The publish/review guard, and deliberately NOT `checksum`. It is taken over + * the compiled document with storage ids, timestamps, revision and itself + * excluded, so the same content at two revisions compares equal — which is + * what makes a revert expressible at all. Null on every historic v1 row: it + * was never computed for them, and backfilling one would claim a review that + * never happened. + */ + compiledChecksum: text('compiled_checksum'), + publishedBy: text('published_by'), + sourceDraftId: text('source_draft_id'), createdAt: text('created_at').notNull(), }, (table) => [ uniqueIndex('idx_measurement_plan_versions_project_revision').on(table.projectId, table.revision), uniqueIndex('idx_measurement_plan_versions_project_id').on(table.projectId, table.id), + // The DDL orders `revision` DESC on both of these: every read of them wants + // the newest revision first. + index('idx_measurement_plan_versions_project_revision_desc').on(table.projectId, table.revision), + index('idx_measurement_plan_versions_project_schema').on(table.projectId, table.schemaVersion, table.revision), + // Lookup only, and non-unique on purpose: a unique index here would refuse to + // publish content identical to an older revision, which is exactly a revert. + index('idx_measurement_plan_versions_compiled_checksum').on(table.projectId, table.compiledChecksum), +]) + +/** + * At most one server-side authoring draft per project. `etag_version` is a + * monotonic counter rather than a content hash: the ETag must change after + * every mutation and must not repeat when content returns to a value it already + * had, which a hash cannot promise. + */ +export const measurementPlanDrafts = sqliteTable('measurement_plan_drafts', { + id: text('id').primaryKey(), + projectId: text('project_id').notNull().unique().references(() => projects.id, { onDelete: 'cascade' }), + schemaVersion: integer('schema_version').notNull().default(2), + /** Null when the draft was started from a planless project. */ + baseActiveVersionId: text('base_active_version_id'), + baseActiveRevision: integer('base_active_revision'), + /** Authoring intent only — never compiled nodes, usage edges or query snapshots. */ + authoringJson: text('authoring_json').notNull(), + etagVersion: integer('etag_version').notNull().default(1), + createdBy: text('created_by').notNull(), + updatedBy: text('updated_by').notNull(), + createdAt: text('created_at').notNull(), + updatedAt: text('updated_at').notNull(), +}) + +export const measurementQuerySets = sqliteTable('measurement_query_sets', { + id: text('id').primaryKey(), + projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }), + name: text('name').notNull(), + description: text('description'), + createdAt: text('created_at').notNull(), + updatedAt: text('updated_at').notNull(), +}, (table) => [ + uniqueIndex('idx_measurement_query_sets_project_name').on(table.projectId, table.name), +]) + +/** + * Ordered references only. Deleting a set drops its rows here and no query: + * the queries themselves outlive every set and every published snapshot. + */ +export const measurementQuerySetItems = sqliteTable('measurement_query_set_items', { + id: text('id').primaryKey(), + querySetId: text('query_set_id').notNull().references(() => measurementQuerySets.id, { onDelete: 'cascade' }), + queryId: text('query_id').notNull().references(() => queries.id, { onDelete: 'cascade' }), + position: integer('position').notNull(), + createdAt: text('created_at').notNull(), +}, (table) => [ + uniqueIndex('idx_measurement_query_set_items_set_query').on(table.querySetId, table.queryId), + index('idx_measurement_query_set_items_order').on(table.querySetId, table.position), +]) + +/** Authoring asset. Applying one expands concrete project queries; a published plan holds only snapshots. */ +export const measurementQueryTemplates = sqliteTable('measurement_query_templates', { + id: text('id').primaryKey(), + projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }), + name: text('name').notNull(), + description: text('description'), + pattern: text('pattern').notNull(), + variables: text('variables_json', { mode: 'json' }).$type().notNull().default([]), + createdAt: text('created_at').notNull(), + updatedAt: text('updated_at').notNull(), +}, (table) => [ + uniqueIndex('idx_measurement_query_templates_project_name').on(table.projectId, table.name), +]) + +/** + * The inputs a discovery rerun has to reproduce to be called deterministic. + * Keyed by the checksum over them so an unchanged sitemap, rule and exclusion + * set resolves to the row that already exists instead of proposing the same + * Targets a second time. + */ +export const measurementDiscoveryConfigs = sqliteTable('measurement_discovery_configs', { + id: text('id').primaryKey(), + projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }), + sitemapUrl: text('sitemap_url').notNull(), + rule: text('rule_json', { mode: 'json' }).$type>().notNull(), + exclusions: text('exclusions_json', { mode: 'json' }).$type().notNull().default([]), + inputChecksum: text('input_checksum').notNull(), + /** Same bytes and same rule under a different compiler are a different result. */ + compilerVersion: text('compiler_version').notNull(), + reviewedAt: text('reviewed_at'), + createdAt: text('created_at').notNull(), + updatedAt: text('updated_at').notNull(), +}, (table) => [ + uniqueIndex('idx_measurement_discovery_configs_input').on(table.projectId, table.inputChecksum), + index('idx_measurement_discovery_configs_project').on(table.projectId), +]) + +/** + * Idempotency receipts for mutating measurement actions. Nothing deletes a row + * on the write path, so the expiry index is what the boot-time and periodic + * sweep uses to keep the table from growing without bound. + */ +export const measurementOperationReceipts = sqliteTable('measurement_operation_receipts', { + projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }), + operation: text('operation').notNull(), + idempotencyKey: text('idempotency_key').notNull(), + /** Same key with different content is a conflict, not a replay. */ + requestChecksum: text('request_checksum').notNull(), + responseJson: text('response_json').notNull(), + statusCode: integer('status_code').notNull(), + createdAt: text('created_at').notNull(), + expiresAt: text('expires_at').notNull(), +}, (table) => [ + primaryKey({ columns: [table.projectId, table.operation, table.idempotencyKey] }), + index('idx_measurement_operation_receipts_expires').on(table.expiresAt), ]) // Stable identity only. Labels, memberships, aliases and route matchers remain diff --git a/packages/db/test/measurement-plan-persistence.test.ts b/packages/db/test/measurement-plan-persistence.test.ts index 45e02fc42..436bfcbec 100644 --- a/packages/db/test/measurement-plan-persistence.test.ts +++ b/packages/db/test/measurement-plan-persistence.test.ts @@ -190,7 +190,7 @@ test('execution context is nullable for legacy rows and unique per run, executio test('schema mirrors the Target plan persistence boundary', () => { const expectedTables = [ - ['measurementPlanVersions', 'measurement_plan_versions', ['id', 'project_id', 'revision', 'canonical_json', 'checksum', 'created_at']], + ['measurementPlanVersions', 'measurement_plan_versions', ['id', 'project_id', 'revision', 'canonical_json', 'checksum', 'schema_version', 'compiled_checksum', 'published_by', 'source_draft_id', 'created_at']], ['measurementSegments', 'measurement_segments', ['id', 'project_id', 'stable_key', 'kind', 'retired_at', 'created_at']], ['measurementPlans', 'measurement_plans', ['project_id', 'active_version_id', 'created_at', 'updated_at']], ] as const diff --git a/packages/db/test/measurement-v2-tables-migration.test.ts b/packages/db/test/measurement-v2-tables-migration.test.ts new file mode 100644 index 000000000..188bf6ee7 --- /dev/null +++ b/packages/db/test/measurement-v2-tables-migration.test.ts @@ -0,0 +1,271 @@ +import { expect, onTestFinished, test } from 'vitest' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { getTableColumns, getTableName, sql } from 'drizzle-orm' +import type { SQLiteTable } from 'drizzle-orm/sqlite-core' +import { + createClient, + measurementDiscoveryConfigs, + measurementOperationReceipts, + measurementPlanDrafts, + measurementPlanVersions, + measurementQuerySetItems, + measurementQuerySets, + measurementQueryTemplates, + migrate, + MIGRATION_VERSIONS, +} from '../src/index.js' +import type { DatabaseClient } from '../src/index.js' + +const NOW = '2026-08-01T00:00:00.000Z' +/** The last version before the Advanced Measurement v2 work. */ +const BASELINE = 121 + +function freshDb(prefix: string) { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)) + onTestFinished(() => fs.rmSync(tmpDir, { recursive: true, force: true })) + return createClient(path.join(tmpDir, 'test.db')) +} + +function seedProject(db: DatabaseClient, id = 'prj_1', name = 'northstar') { + db.run(sql` + INSERT INTO projects (id, name, display_name, canonical_domain, country, language, created_at, updated_at) + VALUES (${id}, ${name}, 'Northstar', 'northstar.example', 'US', 'en', ${NOW}, ${NOW}) + `) +} + +/** A v1 revision as it was written before this migration existed: no v2 columns at all. */ +function seedLegacyPlanVersion(db: DatabaseClient, id = 'mpv_1', revision = 1) { + db.run(sql` + INSERT INTO measurement_plan_versions (id, project_id, revision, canonical_json, checksum, created_at) + VALUES (${id}, 'prj_1', ${revision}, '{"schemaVersion":1}', ${'a'.repeat(64)}, ${NOW}) + `) +} + +interface ColumnInfo { name: string; notnull: number; pk: number } + +function columnInfo(db: DatabaseClient, table: string): ColumnInfo[] { + return db.all(sql.raw(`PRAGMA table_info('${table}')`)) as ColumnInfo[] +} + +function indexList(db: DatabaseClient, table: string): Array<{ name: string; unique: number }> { + return db.all(sql.raw(`PRAGMA index_list('${table}')`)) as Array<{ name: string; unique: number }> +} + +const NEW_TABLES: SQLiteTable[] = [ + measurementPlanDrafts, + measurementQuerySets, + measurementQuerySetItems, + measurementQueryTemplates, + measurementDiscoveryConfigs, + measurementOperationReceipts, +] + +test('every v122+ migration applies on a database at v121 and is a no-op on re-run', () => { + const db = freshDb('canonry-measurement-v2-upgrade-') + migrate(db, MIGRATION_VERSIONS.filter(version => version.version <= BASELINE)) + seedProject(db) + seedLegacyPlanVersion(db) + + migrate(db) + // A second boot must not re-run anything or fail on an already-created table. + migrate(db) + + const applied = db.all(sql.raw('SELECT version FROM _migrations ORDER BY version')) as Array<{ version: number }> + expect(applied.map(row => row.version)).toEqual(MIGRATION_VERSIONS.map(version => version.version)) + for (const table of NEW_TABLES) { + expect(columnInfo(db, getTableName(table)).length, `${getTableName(table)} was not created`).toBeGreaterThan(0) + } +}) + +test('the upgrade backfills schema_version = 1 on every historic revision', () => { + const db = freshDb('canonry-measurement-v2-backfill-') + migrate(db, MIGRATION_VERSIONS.filter(version => version.version <= BASELINE)) + seedProject(db) + seedLegacyPlanVersion(db) + + migrate(db) + + const row = db.select().from(measurementPlanVersions).get() + expect(row).toMatchObject({ + schemaVersion: 1, + compiledChecksum: null, + publishedBy: null, + sourceDraftId: null, + }) +}) + +test('compiled_checksum is a separate nullable column and never overwrites checksum', () => { + const db = freshDb('canonry-measurement-v2-checksums-') + migrate(db) + seedProject(db) + + // Historic v1: document checksum only. Backfilling a compiled checksum here + // would invent a value that was never computed for it. + db.insert(measurementPlanVersions).values({ + id: 'mpv_1', + projectId: 'prj_1', + revision: 1, + canonicalJson: '{"schemaVersion":1}', + checksum: 'a'.repeat(64), + schemaVersion: 1, + compiledChecksum: null, + createdAt: NOW, + }).run() + // v2: both, and they are different values over different content. + db.insert(measurementPlanVersions).values({ + id: 'mpv_2', + projectId: 'prj_1', + revision: 2, + canonicalJson: '{"schemaVersion":2}', + checksum: 'b'.repeat(64), + schemaVersion: 2, + compiledChecksum: 'c'.repeat(64), + publishedBy: 'usr_1', + sourceDraftId: 'mpd_1', + createdAt: NOW, + }).run() + + const rows = db.select().from(measurementPlanVersions).orderBy(measurementPlanVersions.revision).all() + expect(rows.map(row => [row.schemaVersion, row.checksum, row.compiledChecksum])).toEqual([ + [1, 'a'.repeat(64), null], + [2, 'b'.repeat(64), 'c'.repeat(64)], + ]) +}) + +test('a revert republishes older content: the compiled-checksum index is not unique', () => { + const db = freshDb('canonry-measurement-v2-revert-') + migrate(db) + seedProject(db) + + const compiledChecksum = 'd'.repeat(64) + for (const revision of [1, 2, 3]) { + db.insert(measurementPlanVersions).values({ + id: `mpv_${revision}`, + projectId: 'prj_1', + revision, + canonicalJson: '{"schemaVersion":2}', + checksum: String(revision).repeat(64), + schemaVersion: 2, + // Revision 3 restores revision 1's content, which must be publishable. + compiledChecksum: revision === 2 ? 'e'.repeat(64) : compiledChecksum, + createdAt: NOW, + }).run() + } + + expect(db.select().from(measurementPlanVersions).all()).toHaveLength(3) + const index = indexList(db, 'measurement_plan_versions') + .find(entry => entry.name === 'idx_measurement_plan_versions_compiled_checksum') + expect(index).toBeDefined() + expect(index!.unique).toBe(0) +}) + +test('drizzle and the migration DDL agree on every new table, column and nullability', () => { + const db = freshDb('canonry-measurement-v2-parity-') + migrate(db) + + const mismatches: string[] = [] + for (const table of [...NEW_TABLES, measurementPlanVersions]) { + const tableName = getTableName(table) + const declared = columnInfo(db, tableName) + const byName = new Map(declared.map(column => [column.name, column])) + + for (const column of Object.values(getTableColumns(table))) { + const actual = byName.get(column.name) + if (!actual) { + mismatches.push(`${tableName}.${column.name} is declared in drizzle but missing from the DDL`) + continue + } + // SQLite reports a TEXT PRIMARY KEY as nullable, but it is not. + const ddlNotNull = actual.notnull === 1 || actual.pk > 0 + if (ddlNotNull !== column.notNull) { + mismatches.push( + `${tableName}.${column.name} nullability differs: drizzle notNull=${column.notNull}, DDL notNull=${ddlNotNull}`, + ) + } + byName.delete(column.name) + } + for (const name of byName.keys()) { + mismatches.push(`${tableName}.${name} exists in the DDL but is not declared in drizzle`) + } + } + + expect(mismatches, mismatches.join('\n')).toEqual([]) +}) + +test('a project gets at most one server-side draft', () => { + const db = freshDb('canonry-measurement-v2-draft-') + migrate(db) + seedProject(db) + + const draft = { + projectId: 'prj_1', + schemaVersion: 2, + baseActiveVersionId: null, + baseActiveRevision: null, + authoringJson: '{}', + etagVersion: 1, + createdBy: 'usr_1', + updatedBy: 'usr_1', + createdAt: NOW, + updatedAt: NOW, + } + db.insert(measurementPlanDrafts).values({ id: 'mpd_1', ...draft }).run() + expect(() => db.insert(measurementPlanDrafts).values({ id: 'mpd_2', ...draft }).run()).toThrow(/UNIQUE/i) + + db.run(sql`DELETE FROM projects WHERE id = 'prj_1'`) + expect(db.select().from(measurementPlanDrafts).all()).toHaveLength(0) +}) + +test('operation receipts are keyed by project, operation and idempotency key, and are swept by expiry', () => { + const db = freshDb('canonry-measurement-v2-receipts-') + migrate(db) + seedProject(db) + + const receipt = { + projectId: 'prj_1', + operation: 'measurement-draft.publish', + idempotencyKey: 'key-1', + requestChecksum: 'a'.repeat(64), + responseJson: '{"published":true}', + statusCode: 201, + createdAt: NOW, + expiresAt: '2026-08-02T00:00:00.000Z', + } + db.insert(measurementOperationReceipts).values(receipt).run() + expect(() => db.insert(measurementOperationReceipts).values(receipt).run()).toThrow(/UNIQUE|PRIMARY/i) + + // Nothing deletes these rows on write, so the sweep needs the expiry index. + expect(indexList(db, 'measurement_operation_receipts').map(entry => entry.name)) + .toContain('idx_measurement_operation_receipts_expires') +}) + +test('deleting a query set leaves its project queries alone', () => { + const db = freshDb('canonry-measurement-v2-query-sets-') + migrate(db) + seedProject(db) + db.run(sql`INSERT INTO queries (id, project_id, query, created_at) VALUES ('q_1', 'prj_1', 'best apartments', ${NOW})`) + + db.insert(measurementQuerySets).values({ + id: 'qs_1', + projectId: 'prj_1', + name: 'Non-brand', + description: null, + createdAt: NOW, + updatedAt: NOW, + }).run() + db.insert(measurementQuerySetItems).values({ + id: 'qsi_1', + querySetId: 'qs_1', + queryId: 'q_1', + position: 0, + createdAt: NOW, + }).run() + + db.delete(measurementQuerySets).where(sql`id = 'qs_1'`).run() + + expect(db.select().from(measurementQuerySetItems).all()).toHaveLength(0) + const queries = db.all(sql.raw("SELECT id FROM queries")) as Array<{ id: string }> + expect(queries.map(row => row.id)).toEqual(['q_1']) +})