@canonry/canonry 7.62.1 → 7.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -1
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +6 -3
- package/assets/assets/{AgentSetupPage-DtPnVaeF.js → AgentSetupPage-D6lCJ1G1.js} +1 -1
- package/assets/assets/{AuditHistoryPanel-Ce_oy4OV.js → AuditHistoryPanel-DlgI3o-U.js} +1 -1
- package/assets/assets/{BacklinksPage-CrqPWRd8.js → BacklinksPage-ByiMqbSI.js} +1 -1
- package/assets/assets/{HistoryPage-5v0JM7DN.js → HistoryPage-DclVeuLO.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-Bm5Mrd5a.js → MeasurementPropertyPage-DDHybtHa.js} +1 -1
- package/assets/assets/{ProjectPage-DaWUh6iw.js → ProjectPage-vBu6oWFi.js} +1 -1
- package/assets/assets/{RunsPage-CfdAR2v2.js → RunsPage-CYBJ6pXX.js} +1 -1
- package/assets/assets/{SettingsPage-BFooA7JG.js → SettingsPage-w0GmXir0.js} +1 -1
- package/assets/assets/{SiteHealthSection-CerUQgAp.js → SiteHealthSection-DDTjGZ05.js} +2 -2
- package/assets/assets/ToneBadge-MKA1xeYc.js +60 -0
- package/assets/assets/{TrafficPage-C5fhZGJ2.js → TrafficPage-hUWYv-HT.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BoqARHYz.js → TrafficSourceDetailPage-CGHgPxs8.js} +1 -1
- package/assets/assets/{account-context-D_CDYrs8.js → account-context-qX-Es0RK.js} +1 -1
- package/assets/assets/{card-C6z0XyOo.js → card-CzROANT5.js} +1 -1
- package/assets/assets/{extract-error-message-By_wcU8g.js → extract-error-message-BY1STLiX.js} +1 -1
- package/assets/assets/{format-helpers-D3Ubm8vk.js → format-helpers-BNQR1HNK.js} +1 -1
- package/assets/assets/{index-DSVFZqv6.js → index-LmB57HOs.js} +4 -4
- package/assets/assets/{query-client-BETh6SO9.js → query-client-Gz37p16p.js} +1 -1
- package/assets/assets/{react-query-DzDr4w41.js → react-query-C6DtBAYQ.js} +1 -1
- package/assets/assets/{react-sigma_core.esm.min-3kgtOWbQ.js → react-sigma_core.esm.min-CgxuyPPF.js} +1 -1
- package/assets/assets/{service-BUaUaize.js → service-DyHZWnMe.js} +1 -1
- package/assets/index.html +7 -7
- package/dist/{ads-7WUY5CVC.js → ads-FOUJNHP4.js} +3 -3
- package/dist/{agent-NOA33LVM.js → agent-UVDVH7LK.js} +7 -7
- package/dist/{agent-ask-PKQDFJ7K.js → agent-ask-UEYN2SQX.js} +3 -3
- package/dist/{agent-conversations-7HVWPSFB.js → agent-conversations-F54376Q2.js} +3 -3
- package/dist/{agent-memory-Y4M2EWKA.js → agent-memory-SOHBTJ4U.js} +3 -3
- package/dist/{agent-providers-P67FWELV.js → agent-providers-MMEK2H6V.js} +3 -3
- package/dist/{agent-transcript-C2BYWPRT.js → agent-transcript-5IJ3VAEV.js} +3 -3
- package/dist/{analytics-XHYWNNW7.js → analytics-LDWO5IW6.js} +3 -3
- package/dist/{apply-6OIWDXQP.js → apply-PSGFDJIC.js} +3 -3
- package/dist/{backfill-KQPXCUUJ.js → backfill-HZU3BBOH.js} +9 -9
- package/dist/{backlinks-ASYIZFLQ.js → backlinks-X6DXIL2O.js} +3 -3
- package/dist/{bing-ZLKJ3MIL.js → bing-3HMFUEOO.js} +3 -3
- package/dist/{bootstrap-KMVQY5JD.js → bootstrap-ZUSSLXCI.js} +10 -10
- package/dist/{cdp-V2HX4ZLE.js → cdp-FH627HJ6.js} +7 -7
- package/dist/{chunk-GSHHYR24.js → chunk-2LAL5W6J.js} +3 -3
- package/dist/{chunk-X3GWBK7N.js → chunk-5VAC4KQ2.js} +3 -3
- package/dist/{chunk-WKZZILBI.js → chunk-5YC6IU44.js} +2 -2
- package/dist/{chunk-5NYS3ABL.js → chunk-67262NJL.js} +2 -2
- package/dist/{chunk-2LY4FY4N.js → chunk-A57HYHJ3.js} +1 -1
- package/dist/{chunk-KLGHY67V.js → chunk-CWMJQ2JV.js} +4 -4
- package/dist/{chunk-O6VSPM5S.js → chunk-DBA374EM.js} +72 -29
- package/dist/{chunk-U2WPZZPQ.js → chunk-EHTSCLEL.js} +1 -1
- package/dist/{chunk-IVZMXHXE.js → chunk-ERJ54U2I.js} +1 -1
- package/dist/{chunk-IALK4DQJ.js → chunk-GRWZC3GQ.js} +1 -1
- package/dist/{chunk-YQGVA64Q.js → chunk-H22RCHYH.js} +22 -6
- package/dist/{chunk-RGD5SDT3.js → chunk-HNZQVIPS.js} +1 -1
- package/dist/{chunk-LFXKB5FZ.js → chunk-I5QSS3CY.js} +351 -333
- package/dist/{chunk-UVDBUMYV.js → chunk-IDQZTPRG.js} +2 -2
- package/dist/{chunk-B5CSCGVD.js → chunk-L2QPAUTQ.js} +2 -1
- package/dist/{chunk-CJC4AQ2M.js → chunk-LBD7YXVD.js} +2 -2
- package/dist/{chunk-SDZKHJ6W.js → chunk-LEG7F3DN.js} +20 -2
- package/dist/{chunk-MMRJUOE2.js → chunk-NOQCIXM6.js} +1 -1
- package/dist/{chunk-Q7PTBHNG.js → chunk-OIHJJHJR.js} +1 -1
- package/dist/{chunk-YBDUZBGU.js → chunk-PH7RTAWW.js} +1 -1
- package/dist/{chunk-2HNTNKFC.js → chunk-Q4U2GJME.js} +393 -88
- package/dist/{chunk-LAILXR2F.js → chunk-QNFX7ZCT.js} +1 -1
- package/dist/{chunk-UUGT7UMF.js → chunk-QPTSZDHE.js} +4 -4
- package/dist/{chunk-SYQLUOR7.js → chunk-SG2OPR7J.js} +1 -1
- package/dist/{chunk-DAKJBAUN.js → chunk-SN5A767O.js} +1 -1
- package/dist/{chunk-YJAN2ZDV.js → chunk-SQYYAYH6.js} +3 -3
- package/dist/{chunk-3B2GBWIA.js → chunk-TDWNONBU.js} +5 -5
- package/dist/{chunk-U6CNJC2Y.js → chunk-TKLE2Y6H.js} +35 -1
- package/dist/{chunk-KTQVRQDM.js → chunk-UBP3AAKA.js} +1 -1
- package/dist/{chunk-APQ5EAL2.js → chunk-UYPBR3SF.js} +2 -2
- package/dist/{chunk-I3CMCTNE.js → chunk-W5TIRS4U.js} +4 -4
- package/dist/{chunk-AO4P75DI.js → chunk-WYTRXYUR.js} +5 -5
- package/dist/{chunk-7EWDQUNH.js → chunk-XWDCLX55.js} +8 -8
- package/dist/{chunk-OJVXKPIN.js → chunk-ZJTU73GW.js} +3 -3
- package/dist/{citations-NOCVQPPU.js → citations-BKWLP5S3.js} +3 -3
- package/dist/{cli-catalog-WOWYZ7Q2.js → cli-catalog-VRJS66GR.js} +3 -3
- package/dist/{cli-commands-VSS42S5I.js → cli-commands-6LZ5OIIP.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-KQK66W3M.js → competitor-VELMM2TJ.js} +3 -3
- package/dist/{content-OHOQO5LP.js → content-I525MITL.js} +3 -3
- package/dist/{dashboard-W6MII62Z.js → dashboard-XJJ6NZ2L.js} +6 -6
- package/dist/{demo-23H25IOO.js → demo-X7LQ24GS.js} +1 -1
- package/dist/{demo-server-WYYWTH4H.js → demo-server-MBPBTJXG.js} +11 -11
- package/dist/{discover-557WRGEK.js → discover-E2CV5ZP7.js} +3 -3
- package/dist/{discover-eval-GGRZZHHI.js → discover-eval-ESRCWRHP.js} +3 -3
- package/dist/{doctor-D5JNJKVE.js → doctor-BQDVERII.js} +3 -3
- package/dist/{evidence-DHYYVPWW.js → evidence-NUULK5XD.js} +3 -3
- package/dist/{export-cmd-M24S3YBY.js → export-cmd-K324RTN4.js} +3 -3
- package/dist/{ga-XM6WXZNP.js → ga-PQ36UFTK.js} +3 -3
- package/dist/{gbp-M3S5XYPG.js → gbp-KTYA6KHR.js} +3 -3
- package/dist/{google-PULCSUOU.js → google-3D4E7TJX.js} +4 -4
- package/dist/{google-marketing-AA625HA4.js → google-marketing-4FRO4PRN.js} +1 -1
- package/dist/{health-cmd-LVEW6OBD.js → health-cmd-CZJOZUAQ.js} +3 -3
- package/dist/{history-32ZYGZGQ.js → history-JNH34GAM.js} +3 -3
- package/dist/index.d.ts +72 -66
- package/dist/index.js +24 -24
- package/dist/{init-VJDDRCBV.js → init-JRGRHLYU.js} +11 -11
- package/dist/{insights-RK3MJ4UM.js → insights-STYJSZBB.js} +3 -3
- package/dist/intelligence-service-7LCTJQDL.js +15 -0
- package/dist/{journeys-S4CPNZYU.js → journeys-POTSSBOM.js} +3 -3
- package/dist/{keys-WCXE7WXV.js → keys-BPCSPX2O.js} +3 -3
- package/dist/{keyword-HHVLC4HI.js → keyword-6SML434W.js} +3 -3
- package/dist/{logs-VYESWGAN.js → logs-WTKKH4UJ.js} +3 -3
- package/dist/{mcp-4LRFIOLC.js → mcp-HTAHMVRC.js} +2 -2
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-AL3EHWRK.js → measurement-plan-LJHKKRZ7.js} +5 -5
- package/dist/{notify-LUDG5PWU.js → notify-EARS4LMG.js} +3 -3
- package/dist/{organic-evidence-CIOHMKLD.js → organic-evidence-W7ZQQKPL.js} +3 -3
- package/dist/{overview-NAZJ6PJG.js → overview-C7KQD4NR.js} +4 -4
- package/dist/{project-N3MU5V6T.js → project-YU2FIL4B.js} +3 -3
- package/dist/{query-XMI2FV6G.js → query-SYFLWHFA.js} +3 -3
- package/dist/{research-MQ6QA5TV.js → research-UOUXMFBV.js} +3 -3
- package/dist/{results-export-OFEZXCJK.js → results-export-VYPG2Z6S.js} +3 -3
- package/dist/{run-YQS3LCFT.js → run-GZD3MQGS.js} +4 -4
- package/dist/{schedule-DF6HCMXN.js → schedule-PAA2NLGF.js} +3 -3
- package/dist/{search-MRDJKVOL.js → search-6RWCUEEY.js} +3 -3
- package/dist/{serve-OCAFOFS2.js → serve-FTAFEEJD.js} +25 -25
- package/dist/{session-5OXLS7NP.js → session-E4V5MRUE.js} +12 -12
- package/dist/{settings-NIF34QPH.js → settings-TTA775EJ.js} +3 -3
- package/dist/{skills-JOSNWG7B.js → skills-GA35KKOV.js} +2 -2
- package/dist/{snapshot-32DLVRIJ.js → snapshot-QVQVAMMN.js} +3 -3
- package/dist/{src-F63SHN5T.js → src-CTHVFVLF.js} +2 -2
- package/dist/{status-IERHHP4C.js → status-E7B726S6.js} +4 -4
- package/dist/{technical-aeo-D7CA3CK6.js → technical-aeo-SMPN3QDQ.js} +3 -3
- package/dist/{telemetry-DRURFCCJ.js → telemetry-CTBFPMWD.js} +4 -4
- package/dist/{tools-3IZMSHSY.js → tools-XKGEC56T.js} +11 -11
- package/dist/{traffic-AME5NDMN.js → traffic-XACOSYYM.js} +36 -7
- package/dist/{users-4CQ6ATAG.js → users-WJC7SGRL.js} +3 -3
- package/dist/{visibility-stats-R5JBISBX.js → visibility-stats-H3ZNFV5M.js} +3 -3
- package/dist/{wordpress-GFK4GR3O.js → wordpress-72YMWF7F.js} +3 -3
- package/package.json +1 -1
- package/assets/assets/ToneBadge-DKvZcONW.js +0 -60
- package/dist/intelligence-service-UNKFCG5N.js +0 -15
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
normalizeCloudflareWorkerEvent,
|
|
10
10
|
sessionizeJourneyRequests,
|
|
11
11
|
verifyRequestSignature
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SG2OPR7J.js";
|
|
13
13
|
import {
|
|
14
14
|
currentOutcomeAttribution,
|
|
15
15
|
getRequestContext,
|
|
16
16
|
registerRequestContext,
|
|
17
17
|
requestAuditIdentity,
|
|
18
18
|
sanitizeRequestContext
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QNFX7ZCT.js";
|
|
20
20
|
import {
|
|
21
21
|
SentimentIdempotencyConflict,
|
|
22
22
|
SentimentRepository,
|
|
@@ -154,7 +154,7 @@ import {
|
|
|
154
154
|
userSessions,
|
|
155
155
|
users,
|
|
156
156
|
writeNegativeReviewMaxStars
|
|
157
|
-
} from "./chunk-
|
|
157
|
+
} from "./chunk-HNZQVIPS.js";
|
|
158
158
|
import {
|
|
159
159
|
ADS_ACTIVATE_SCOPE,
|
|
160
160
|
ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
|
|
@@ -258,6 +258,7 @@ import {
|
|
|
258
258
|
GoogleMarketingProviders,
|
|
259
259
|
GtmConnectionStates,
|
|
260
260
|
GtmSnapshotKinds,
|
|
261
|
+
HEALTH_NO_LONGER_URGENT,
|
|
261
262
|
InsightActionGroups,
|
|
262
263
|
JOURNEY_LIST_VISITS_PER_VISITOR,
|
|
263
264
|
JOURNEY_LOOKBACK_DAYS,
|
|
@@ -514,6 +515,7 @@ import {
|
|
|
514
515
|
classifySkillFile,
|
|
515
516
|
classifySurfaceFromCategory,
|
|
516
517
|
classifyTrafficPath,
|
|
518
|
+
cloudflareQueuePullTrafficSourceConfigSchema,
|
|
517
519
|
cloudflareTrafficSourceConfigSchema,
|
|
518
520
|
cloudflareWorkerIngestRequestSchema,
|
|
519
521
|
cloudflareWorkerIngestResponseSchema,
|
|
@@ -618,6 +620,7 @@ import {
|
|
|
618
620
|
findDuplicateLocationLabels,
|
|
619
621
|
forbidden,
|
|
620
622
|
formatAiReferralClassSummary,
|
|
623
|
+
formatCalendarDay,
|
|
621
624
|
formatCanonryPluginUpdateHint,
|
|
622
625
|
formatCanonryPluginVersionMismatch,
|
|
623
626
|
formatIsoDate,
|
|
@@ -739,6 +742,7 @@ import {
|
|
|
739
742
|
isRetryableHttpError,
|
|
740
743
|
isStrictSemver,
|
|
741
744
|
isSweepProvider,
|
|
745
|
+
isTransientNetworkError,
|
|
742
746
|
isVertexGroundingRedirect,
|
|
743
747
|
isVisibilityCompareClassMetric,
|
|
744
748
|
isoDateDaysBeforeInTimeZone,
|
|
@@ -1117,6 +1121,7 @@ import {
|
|
|
1117
1121
|
trafficConnectCloudflareResponseSchema,
|
|
1118
1122
|
trafficConnectVercelRequestSchema,
|
|
1119
1123
|
trafficConnectWordpressRequestSchema,
|
|
1124
|
+
trafficDeclareGapRequestSchema,
|
|
1120
1125
|
trafficEventsResponseSchema,
|
|
1121
1126
|
trafficResetRequestSchema,
|
|
1122
1127
|
trafficSampleClassifierSchema,
|
|
@@ -1158,7 +1163,7 @@ import {
|
|
|
1158
1163
|
wordpressSchemaDeployResultDtoSchema,
|
|
1159
1164
|
wordpressSchemaStatusResultDtoSchema,
|
|
1160
1165
|
wordpressStatusDtoSchema
|
|
1161
|
-
} from "./chunk-
|
|
1166
|
+
} from "./chunk-TKLE2Y6H.js";
|
|
1162
1167
|
|
|
1163
1168
|
// ../api-routes/src/user-session.ts
|
|
1164
1169
|
import crypto2 from "crypto";
|
|
@@ -8381,16 +8386,111 @@ import { z as z2 } from "zod";
|
|
|
8381
8386
|
|
|
8382
8387
|
// ../api-routes/src/notifications/alert.ts
|
|
8383
8388
|
import { z } from "zod";
|
|
8389
|
+
|
|
8390
|
+
// ../api-routes/src/notifications/health-copy.ts
|
|
8391
|
+
var HEALTH_CHECK_LABELS = {
|
|
8392
|
+
"db.file.present": "Database missing",
|
|
8393
|
+
"config.file.present": "Config file missing",
|
|
8394
|
+
"canonry.version.current": "Canonry update available",
|
|
8395
|
+
"google.auth.connection": "Search Console sign-in",
|
|
8396
|
+
"google.auth.property-access": "Search Console access",
|
|
8397
|
+
"google.auth.redirect-uri": "Google sign-in setup",
|
|
8398
|
+
"google.auth.scopes": "Search Console permissions",
|
|
8399
|
+
"gbp.auth.connection": "Business Profile sign-in",
|
|
8400
|
+
"gbp.auth.scopes": "Business Profile permissions",
|
|
8401
|
+
"gbp.account.access": "Business Profile access",
|
|
8402
|
+
"gbp.data.recent-sync": "Business Profile sync stalled",
|
|
8403
|
+
"gbp.places.api-key": "Places API key",
|
|
8404
|
+
"gbp.reviews.access": "Review alerts limited",
|
|
8405
|
+
"gbp.reviews.delivery": "Review alert not delivered",
|
|
8406
|
+
"bing.auth.connection": "Bing sign-in",
|
|
8407
|
+
"bing.auth.site-access": "Bing site access",
|
|
8408
|
+
"wordpress.publish.connection": "WordPress publishing",
|
|
8409
|
+
"ga.auth.connection": "GA4 sign-in",
|
|
8410
|
+
"ga.data.recent-data": "GA4 data stopped",
|
|
8411
|
+
"gsc.data.recent-data": "Search Console data stopped",
|
|
8412
|
+
"report.sweeps": "Monthly report sweeps",
|
|
8413
|
+
"report.models": "Monthly report models",
|
|
8414
|
+
"report.daily-data": "Monthly report data gaps",
|
|
8415
|
+
"report.ai-referral-bursts": "AI referral spikes",
|
|
8416
|
+
"ads.auth.connection": "OpenAI ads sign-in",
|
|
8417
|
+
"ads.data.recent-sync": "OpenAI ads sync stalled",
|
|
8418
|
+
"google-ads.auth.connection": "Google Ads sign-in",
|
|
8419
|
+
"google-ads.auth.scopes": "Google Ads permissions",
|
|
8420
|
+
"google-ads.account.context": "Google Ads account",
|
|
8421
|
+
"google-ads.data.recent-snapshot": "Google Ads data stale",
|
|
8422
|
+
"gtm.auth.connection": "Tag Manager sign-in",
|
|
8423
|
+
"gtm.auth.scopes": "Tag Manager permissions",
|
|
8424
|
+
"gtm.container.context": "Tag Manager container",
|
|
8425
|
+
"gtm.data.recent-snapshot": "Tag Manager data stale",
|
|
8426
|
+
"gtm.runtime.firing": "Tags not firing",
|
|
8427
|
+
"google-ads.sync.recent-failures": "Google Ads sync failing",
|
|
8428
|
+
"gtm.sync.recent-failures": "Tag Manager sync failing",
|
|
8429
|
+
"gbp.sync.recent-failures": "Business Profile sync failing",
|
|
8430
|
+
"ga.sync.recent-failures": "GA4 sync failing",
|
|
8431
|
+
"gsc.sync.recent-failures": "Search Console sync failing",
|
|
8432
|
+
"config.providers": "AI provider keys",
|
|
8433
|
+
"config.agent-providers": "Agent provider keys",
|
|
8434
|
+
"traffic.source.connected": "Traffic sync failing",
|
|
8435
|
+
"traffic.source.recent-data": "Traffic data stopped",
|
|
8436
|
+
"traffic.source.sync-lag": "Traffic sync behind",
|
|
8437
|
+
"traffic.source.queue-backlog": "Traffic backlog",
|
|
8438
|
+
"traffic.source.worker-version": "Cloudflare worker out of date",
|
|
8439
|
+
"traffic.source.credentials": "Traffic source sign-in",
|
|
8440
|
+
"traffic.source.scopes": "Traffic source permissions",
|
|
8441
|
+
"traffic.source.cache-blindspot": "Traffic hidden by cache",
|
|
8442
|
+
"backlinks.source.connected": "Backlinks source",
|
|
8443
|
+
"content.winnability.coverage": "Content analysis incomplete",
|
|
8444
|
+
"agent.skills.installed": "Agent skills missing",
|
|
8445
|
+
"agent.skills.current": "Agent skills outdated",
|
|
8446
|
+
"agent.skills.trigger-surface": "Agent skill setup",
|
|
8447
|
+
"gsc.api.reachable": "Can't reach Search Console",
|
|
8448
|
+
"ga.api.reachable": "Can't reach GA4",
|
|
8449
|
+
"gbp.api.reachable": "Can't reach Business Profile",
|
|
8450
|
+
"google-ads.api.reachable": "Can't reach Google Ads",
|
|
8451
|
+
"gtm.api.reachable": "Can't reach Tag Manager",
|
|
8452
|
+
"bing.api.reachable": "Can't reach Bing",
|
|
8453
|
+
"ads.api.reachable": "Can't reach OpenAI ads",
|
|
8454
|
+
"site.reachability": "Website down"
|
|
8455
|
+
};
|
|
8456
|
+
var HEALTH_FIXED_LINES = {
|
|
8457
|
+
"google.auth.refresh-unreachable": "Google is reachable again.",
|
|
8458
|
+
"ga.auth.refresh-unreachable": "Google is reachable again.",
|
|
8459
|
+
"gbp.auth.refresh-unreachable": "Google is reachable again.",
|
|
8460
|
+
"traffic.sync-lag.unrecovered-skip": "The traffic gap is closed.",
|
|
8461
|
+
"traffic.source.all-errored-recently": "Traffic syncs are working again.",
|
|
8462
|
+
"traffic.queue-backlog.remaining": "The traffic backlog is cleared.",
|
|
8463
|
+
"traffic.worker-version.stale": "The Cloudflare worker is up to date.",
|
|
8464
|
+
"gbp.auth.no-connection": "Business Profile is connected."
|
|
8465
|
+
};
|
|
8466
|
+
function firstSentence(text, max2 = 180) {
|
|
8467
|
+
if (!text) return null;
|
|
8468
|
+
const end = text.search(/[.!?](?:\s|$)/u);
|
|
8469
|
+
const sentence = (end === -1 ? text : text.slice(0, end + 1)).trim();
|
|
8470
|
+
if (!sentence) return null;
|
|
8471
|
+
return sentence.length <= max2 ? sentence : `${sentence.slice(0, max2 - 1)}\u2026`;
|
|
8472
|
+
}
|
|
8473
|
+
var HEALTH_NO_SIGNAL_CODE = "health.no-signal";
|
|
8474
|
+
function plainHealthCopy(check, remediation) {
|
|
8475
|
+
if (check.alert) return check.alert;
|
|
8476
|
+
if (check.code === HEALTH_NO_SIGNAL_CODE) {
|
|
8477
|
+
return { label: "Health unknown", line: "No health check produced a result, so health is unknown, not confirmed.", next: null };
|
|
8478
|
+
}
|
|
8479
|
+
return {
|
|
8480
|
+
label: HEALTH_CHECK_LABELS[check.id] ?? "Health check failing",
|
|
8481
|
+
line: firstSentence(check.summary) ?? check.summary,
|
|
8482
|
+
next: firstSentence(remediation)
|
|
8483
|
+
};
|
|
8484
|
+
}
|
|
8485
|
+
|
|
8486
|
+
// ../api-routes/src/notifications/alert.ts
|
|
8384
8487
|
var alertSeveritySchema = z.enum(["critical", "warning", "success", "info"]);
|
|
8385
8488
|
var AlertSeverities = alertSeveritySchema.enum;
|
|
8386
8489
|
var AlertFieldLabels = {
|
|
8387
|
-
status: "Status",
|
|
8388
|
-
check: "Check",
|
|
8389
|
-
previously: "Was",
|
|
8390
8490
|
remediation: "What to do",
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8491
|
+
next: "Next",
|
|
8492
|
+
alsoOpen: "Also open",
|
|
8493
|
+
fixed: "Fixed",
|
|
8394
8494
|
event: "Event",
|
|
8395
8495
|
run: "Run",
|
|
8396
8496
|
changes: "Changes",
|
|
@@ -8401,6 +8501,7 @@ var AlertFieldLabels = {
|
|
|
8401
8501
|
replied: "Replied",
|
|
8402
8502
|
source: "Source"
|
|
8403
8503
|
};
|
|
8504
|
+
var CANONRY_ALERT_CATEGORY = "\u{1F4C8} Canonry";
|
|
8404
8505
|
var HEALTH_EVENTS = /* @__PURE__ */ new Set([
|
|
8405
8506
|
"health.degraded",
|
|
8406
8507
|
"health.recovered",
|
|
@@ -8418,58 +8519,86 @@ function bulletList(lines) {
|
|
|
8418
8519
|
return omitted > 0 ? `${body}
|
|
8419
8520
|
\u2022 \u2026and ${omitted} more` : body;
|
|
8420
8521
|
}
|
|
8522
|
+
var MAX_ALSO_OPEN = 3;
|
|
8523
|
+
function fixedLines(cleared) {
|
|
8524
|
+
const named = [...new Set(cleared.map((code) => HEALTH_FIXED_LINES[code]).filter((line) => line !== void 0))];
|
|
8525
|
+
const unnamed = cleared.filter((code) => HEALTH_FIXED_LINES[code] === void 0).length;
|
|
8526
|
+
if (unnamed === 0) return named;
|
|
8527
|
+
return [...named, `${unnamed} ${named.length > 0 ? "other " : ""}problem${unnamed === 1 ? "" : "s"} fixed.`];
|
|
8528
|
+
}
|
|
8529
|
+
function labelList(labels) {
|
|
8530
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
8531
|
+
for (const label of labels) counts2.set(label, (counts2.get(label) ?? 0) + 1);
|
|
8532
|
+
const entries = [...counts2].map(([label, count10]) => count10 > 1 ? `${label} (${count10})` : label);
|
|
8533
|
+
const shown = entries.slice(0, MAX_ALSO_OPEN);
|
|
8534
|
+
const more = entries.length - shown.length;
|
|
8535
|
+
return [...shown.map((entry) => `\u2022 ${entry}`), ...more > 0 ? [`\u2022 and ${more} more`] : []].join("\n");
|
|
8536
|
+
}
|
|
8421
8537
|
function healthView(payload) {
|
|
8422
8538
|
const { health, project } = payload;
|
|
8423
8539
|
const urgentEvent = payload.event === "health.urgent" || payload.event === "health.urgent-resolved";
|
|
8424
8540
|
const recovered = payload.event === "health.recovered" || payload.event === "health.urgent-resolved";
|
|
8425
|
-
const
|
|
8426
|
-
const
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8541
|
+
const website = health.code.startsWith("site.reachability.");
|
|
8542
|
+
const base = {
|
|
8543
|
+
category: CANONRY_ALERT_CATEGORY,
|
|
8544
|
+
url: payload.dashboardUrl,
|
|
8545
|
+
footer: project.canonicalDomain,
|
|
8546
|
+
timestamp: health.checkedAt
|
|
8547
|
+
};
|
|
8548
|
+
if (recovered) {
|
|
8549
|
+
const fixed2 = fixedLines(health.cleared ?? []);
|
|
8550
|
+
if (website) {
|
|
8551
|
+
return { ...base, severity: AlertSeverities.success, title: `${project.name}: Website is back up`, body: "The website answers again.", fields: [] };
|
|
8552
|
+
}
|
|
8553
|
+
if (urgentEvent) {
|
|
8554
|
+
const eased = health.summary.startsWith(HEALTH_NO_LONGER_URGENT);
|
|
8555
|
+
const several = (health.cleared?.length ?? 0) > 1;
|
|
8556
|
+
return {
|
|
8557
|
+
...base,
|
|
8558
|
+
severity: AlertSeverities.success,
|
|
8559
|
+
title: `${project.name}: ${eased || several ? "No longer urgent" : "Urgent problem fixed"}`,
|
|
8560
|
+
body: firstSentence(eased ? health.summary.slice(HEALTH_NO_LONGER_URGENT.length) : health.summary) ?? "The urgent problem is resolved.",
|
|
8561
|
+
fields: []
|
|
8562
|
+
};
|
|
8563
|
+
}
|
|
8564
|
+
return {
|
|
8565
|
+
...base,
|
|
8566
|
+
severity: AlertSeverities.success,
|
|
8567
|
+
title: `${project.name}: Back to normal`,
|
|
8568
|
+
body: fixed2.length > 0 ? fixed2.join(" ") : "Every check passes again.",
|
|
8569
|
+
fields: []
|
|
8570
|
+
};
|
|
8439
8571
|
}
|
|
8572
|
+
const failing = health.failing;
|
|
8440
8573
|
const added = health.added ?? [];
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
const
|
|
8450
|
-
|
|
8451
|
-
fields.push({
|
|
8452
|
-
label: `${AlertFieldLabels.cleared} (${cleared.length})`,
|
|
8453
|
-
value: bulletList(cleared)
|
|
8454
|
-
});
|
|
8455
|
-
}
|
|
8456
|
-
const newlyFailing = new Set(added);
|
|
8457
|
-
const others = health.failing.filter((check) => check.code !== health.code && !newlyFailing.has(check.code));
|
|
8574
|
+
const newest = added.map((code) => failing.find((check) => check.code === code)).find((check) => check !== void 0);
|
|
8575
|
+
const lead = newest ?? failing.find((check) => check.code === health.code) ?? failing.at(0);
|
|
8576
|
+
const copy = lead ? lead.code === health.code ? plainHealthCopy({ ...lead, summary: health.summary }, lead.remediation ?? health.remediation) : plainHealthCopy(lead, lead.remediation ?? null) : plainHealthCopy({ id: "", status: health.status, code: health.code, summary: health.summary }, health.remediation);
|
|
8577
|
+
const leadStatus = lead?.status ?? health.status;
|
|
8578
|
+
const severity = urgentEvent || leadStatus === "fail" ? AlertSeverities.critical : AlertSeverities.warning;
|
|
8579
|
+
const fields = [];
|
|
8580
|
+
if (copy.next) fields.push({ label: AlertFieldLabels.next, value: copy.next });
|
|
8581
|
+
const others = failing.filter((check) => check !== lead);
|
|
8582
|
+
const otherNew = others.filter((check) => added.includes(check.code));
|
|
8583
|
+
const standing = others.filter((check) => !added.includes(check.code));
|
|
8458
8584
|
if (others.length > 0) {
|
|
8459
8585
|
fields.push({
|
|
8460
|
-
label:
|
|
8461
|
-
value:
|
|
8586
|
+
label: AlertFieldLabels.alsoOpen,
|
|
8587
|
+
value: labelList([
|
|
8588
|
+
...otherNew.map((check) => `${plainHealthCopy(check, null).label} (new)`),
|
|
8589
|
+
...standing.map((check) => plainHealthCopy(check, null).label)
|
|
8590
|
+
])
|
|
8462
8591
|
});
|
|
8463
8592
|
}
|
|
8464
|
-
const
|
|
8593
|
+
const fixed = fixedLines(health.cleared ?? []);
|
|
8594
|
+
if (fixed.length > 0) fields.push({ label: AlertFieldLabels.fixed, value: fixed.map((line) => `\u2022 ${line}`).join("\n") });
|
|
8595
|
+
const label = website ? "Website down" : copy.label;
|
|
8465
8596
|
return {
|
|
8597
|
+
...base,
|
|
8466
8598
|
severity,
|
|
8467
|
-
title:
|
|
8468
|
-
body:
|
|
8469
|
-
fields
|
|
8470
|
-
url: payload.dashboardUrl,
|
|
8471
|
-
footer: project.canonicalDomain,
|
|
8472
|
-
timestamp: health.checkedAt
|
|
8599
|
+
title: `${project.name}: ${label}${urgentEvent ? " (urgent)" : ""}`,
|
|
8600
|
+
body: copy.line,
|
|
8601
|
+
fields
|
|
8473
8602
|
};
|
|
8474
8603
|
}
|
|
8475
8604
|
function runView(payload) {
|
|
@@ -8494,6 +8623,7 @@ function runView(payload) {
|
|
|
8494
8623
|
}
|
|
8495
8624
|
return {
|
|
8496
8625
|
severity: failed ? AlertSeverities.critical : AlertSeverities.info,
|
|
8626
|
+
category: CANONRY_ALERT_CATEGORY,
|
|
8497
8627
|
title: payload.project.name,
|
|
8498
8628
|
fields,
|
|
8499
8629
|
url: payload.dashboardUrl,
|
|
@@ -8533,6 +8663,7 @@ function reviewView(payload) {
|
|
|
8533
8663
|
fields.push(sourceField);
|
|
8534
8664
|
return {
|
|
8535
8665
|
severity: AlertSeverities.warning,
|
|
8666
|
+
category: CANONRY_ALERT_CATEGORY,
|
|
8536
8667
|
title: `${project.name}: new ${review.starRating}-star review`,
|
|
8537
8668
|
body: review.comment ? review.comment.length > 500 ? `${review.comment.slice(0, 499)}\u2026` : review.comment : "Rating only, no text.",
|
|
8538
8669
|
fields,
|
|
@@ -8543,6 +8674,7 @@ function reviewView(payload) {
|
|
|
8543
8674
|
}
|
|
8544
8675
|
return {
|
|
8545
8676
|
severity: AlertSeverities.warning,
|
|
8677
|
+
category: CANONRY_ALERT_CATEGORY,
|
|
8546
8678
|
title: `${project.name}: ${reviews.length} new negative reviews`,
|
|
8547
8679
|
body: bulletList(reviews.map((r) => {
|
|
8548
8680
|
const text = r.comment ? ` "${snippet(r.comment)}"` : "";
|
|
@@ -8564,6 +8696,7 @@ function ratingView(payload) {
|
|
|
8564
8696
|
const single = ratings.length === 1 ? ratings[0] : null;
|
|
8565
8697
|
return {
|
|
8566
8698
|
severity: AlertSeverities.warning,
|
|
8699
|
+
category: CANONRY_ALERT_CATEGORY,
|
|
8567
8700
|
title: single ? `${project.name}: Google rating fell to ${single.rating}` : `${project.name}: Google rating fell at ${ratings.length} locations`,
|
|
8568
8701
|
body: single ? line(single) : bulletList(ratings.map(line)),
|
|
8569
8702
|
fields: [
|
|
@@ -8622,7 +8755,10 @@ function renderDiscord(view) {
|
|
|
8622
8755
|
return {
|
|
8623
8756
|
username: "Canonry",
|
|
8624
8757
|
embeds: [{
|
|
8625
|
-
|
|
8758
|
+
// The category line names who is speaking; the embed's colour carries
|
|
8759
|
+
// the severity, so the title stays the problem alone.
|
|
8760
|
+
...view.category ? { author: { name: view.category } } : {},
|
|
8761
|
+
title: view.category ? view.title : `${SEVERITY_ICONS[view.severity]} ${view.title}`,
|
|
8626
8762
|
...view.body ? { description: clamp(view.body, DISCORD_MAX_DESCRIPTION) } : {},
|
|
8627
8763
|
color: DISCORD_COLORS[view.severity],
|
|
8628
8764
|
fields: view.fields.map((field) => ({
|
|
@@ -8652,6 +8788,7 @@ function renderSlack(view) {
|
|
|
8652
8788
|
text: headline,
|
|
8653
8789
|
attachments: [{
|
|
8654
8790
|
color: SLACK_COLORS[view.severity],
|
|
8791
|
+
...view.category ? { author_name: view.category } : {},
|
|
8655
8792
|
title: headline,
|
|
8656
8793
|
...absoluteLink(view.url) ? { title_link: absoluteLink(view.url) } : {},
|
|
8657
8794
|
...view.body ? { text: clamp(view.body, SLACK_MAX_TEXT) } : {},
|
|
@@ -26331,23 +26468,29 @@ async function resolveHostAddresses(hostname, resolveAddresses) {
|
|
|
26331
26468
|
}
|
|
26332
26469
|
if (isLocalhostName(hostname)) return LOCALHOST_ADDRESSES;
|
|
26333
26470
|
if (resolveAddresses) return await resolveAddresses(hostname);
|
|
26334
|
-
|
|
26335
|
-
|
|
26336
|
-
|
|
26337
|
-
|
|
26338
|
-
|
|
26339
|
-
|
|
26340
|
-
|
|
26471
|
+
const first = await queryHostAddresses(hostname);
|
|
26472
|
+
if (first.addresses.length > 0 || !first.transient) return first.addresses;
|
|
26473
|
+
await new Promise((resolve) => setTimeout(resolve, DNS_RETRY_DELAY_MS + Math.random() * DNS_RETRY_DELAY_MS));
|
|
26474
|
+
return (await queryHostAddresses(hostname)).addresses;
|
|
26475
|
+
}
|
|
26476
|
+
var DNS_RETRY_DELAY_MS = 500;
|
|
26477
|
+
async function queryHostAddresses(hostname) {
|
|
26478
|
+
const [ipv4, ipv6] = await Promise.allSettled([dns2.resolve4(hostname), dns2.resolve6(hostname)]);
|
|
26479
|
+
const unique3 = /* @__PURE__ */ new Map();
|
|
26480
|
+
if (ipv4.status === "fulfilled") {
|
|
26481
|
+
for (const address of ipv4.value) {
|
|
26482
|
+
unique3.set(`4:${address}`, { address, family: 4 });
|
|
26341
26483
|
}
|
|
26342
|
-
|
|
26343
|
-
|
|
26344
|
-
|
|
26345
|
-
}
|
|
26484
|
+
}
|
|
26485
|
+
if (ipv6.status === "fulfilled") {
|
|
26486
|
+
for (const address of ipv6.value) {
|
|
26487
|
+
unique3.set(`6:${address}`, { address, family: 6 });
|
|
26346
26488
|
}
|
|
26347
|
-
return [...unique3.values()];
|
|
26348
|
-
} catch {
|
|
26349
|
-
return [];
|
|
26350
26489
|
}
|
|
26490
|
+
return {
|
|
26491
|
+
addresses: [...unique3.values()],
|
|
26492
|
+
transient: [ipv4, ipv6].some((result) => result.status === "rejected" && isTransientNetworkError(result.reason))
|
|
26493
|
+
};
|
|
26351
26494
|
}
|
|
26352
26495
|
function isBlockedAddress(address, options) {
|
|
26353
26496
|
return blockedAddressReason(address, { allowLoopback: options.allowLoopback }) !== null;
|
|
@@ -41190,7 +41333,7 @@ var routeCatalog = [
|
|
|
41190
41333
|
method: "post",
|
|
41191
41334
|
path: "/api/v1/projects/{name}/traffic/sources/{id}/reset",
|
|
41192
41335
|
summary: "Advance lastSyncedAt to NOW and clear the error state",
|
|
41193
|
-
description: "Operator recovery: advances `lastSyncedAt` to NOW, sets `status` back to `connected`, and clears `last_error`. WordPress resets also clear `last_cursor` and the pending-window marker, then record an unrecovered skip through the reset instant so the next drain cannot combine an old cursor with a new window. That prevents replay but is not a historical repair. Generic WordPress replace-mode backfill is unavailable because the plugin feed cannot prove retained coverage;
|
|
41336
|
+
description: "Operator recovery: advances `lastSyncedAt` to NOW, sets `status` back to `connected`, and clears `last_error`. WordPress resets also clear `last_cursor` and the pending-window marker, then record an unrecovered skip through the reset instant so the next drain cannot combine an old cursor with a new window. That prevents replay but is not a historical repair. Generic WordPress replace-mode backfill is unavailable because the plugin feed cannot prove retained coverage; record the skipped span with `POST /traffic/sources/{id}/declare-gap` once it cannot be recovered. Common trigger: an idle Vercel/Cloud Run source whose `lastSyncedAt` has aged past upstream retention (`request-logs` ~14d, Cloud Logging 30d) and now throws on every sync. `advanceToNow: true` is required (no implicit reset). Archived sources are rejected with 400 \u2014 re-connect them via the appropriate `traffic/connect/*` endpoint instead.",
|
|
41194
41337
|
tags: ["traffic"],
|
|
41195
41338
|
parameters: [
|
|
41196
41339
|
nameParameter,
|
|
@@ -41216,6 +41359,36 @@ var routeCatalog = [
|
|
|
41216
41359
|
404: errorResponse("Project or traffic source not found.")
|
|
41217
41360
|
}
|
|
41218
41361
|
},
|
|
41362
|
+
{
|
|
41363
|
+
method: "post",
|
|
41364
|
+
path: "/api/v1/projects/{name}/traffic/sources/{id}/declare-gap",
|
|
41365
|
+
summary: "Record a source's unrecovered skip as lost and clear it",
|
|
41366
|
+
description: "Records the span a source skipped, through `skippedThroughAt`, as data that cannot be recovered, clears the marker so the `traffic.sync-lag.unrecovered-skip` doctor check passes, and writes a `traffic.source.gap-declared` audit entry with the span. Rollups are untouched. Meant for WordPress, whose plugin feed cannot prove a historical repair, and for any source whose provider no longer retains the span; a recoverable gap should be backfilled instead. `skippedThroughAt` must repeat the current marker exactly, so a newer skip recorded since the operator looked is refused, not cleared.",
|
|
41367
|
+
tags: ["traffic"],
|
|
41368
|
+
parameters: [
|
|
41369
|
+
nameParameter,
|
|
41370
|
+
{ name: "id", in: "path", required: true, description: "Traffic source ID.", schema: stringSchema }
|
|
41371
|
+
],
|
|
41372
|
+
requestBody: {
|
|
41373
|
+
required: true,
|
|
41374
|
+
content: {
|
|
41375
|
+
"application/json": {
|
|
41376
|
+
schema: {
|
|
41377
|
+
type: "object",
|
|
41378
|
+
required: ["skippedThroughAt"],
|
|
41379
|
+
properties: {
|
|
41380
|
+
skippedThroughAt: { type: "string", description: "The source's current `skippedThroughAt`, exactly as read." }
|
|
41381
|
+
}
|
|
41382
|
+
}
|
|
41383
|
+
}
|
|
41384
|
+
}
|
|
41385
|
+
},
|
|
41386
|
+
responses: {
|
|
41387
|
+
200: jsonResponse("Gap recorded; the source no longer reports an unrecovered skip.", "TrafficSourceDetailDto"),
|
|
41388
|
+
400: errorResponse("No gap is recorded, or `skippedThroughAt` does not match the current one."),
|
|
41389
|
+
404: errorResponse("Project or traffic source not found.")
|
|
41390
|
+
}
|
|
41391
|
+
},
|
|
41219
41392
|
{
|
|
41220
41393
|
method: "get",
|
|
41221
41394
|
path: "/api/v1/projects/{name}/traffic/sources",
|
|
@@ -43073,7 +43246,17 @@ function post(value, mediaMode = "exact") {
|
|
|
43073
43246
|
const mediaSourceUrlsComplete = media.every(
|
|
43074
43247
|
(item2) => item2.mediaFormat === "PHOTO" && typeof item2.sourceUrl === "string"
|
|
43075
43248
|
);
|
|
43076
|
-
|
|
43249
|
+
const providerImageUrls = media.map((item2) => {
|
|
43250
|
+
if (item2.mediaFormat !== "PHOTO" || typeof item2.googleUrl !== "string") return null;
|
|
43251
|
+
try {
|
|
43252
|
+
const url = new URL(item2.googleUrl);
|
|
43253
|
+
return url.protocol === "https:" && /^lh\d+\.googleusercontent\.com$/u.test(url.hostname) && !url.username && !url.password && !url.port && !url.hash ? url.href : null;
|
|
43254
|
+
} catch {
|
|
43255
|
+
return null;
|
|
43256
|
+
}
|
|
43257
|
+
});
|
|
43258
|
+
const providerImagesComplete = providerImageUrls.length > 0 && providerImageUrls.every((url) => url !== null);
|
|
43259
|
+
if (!mediaSourceUrlsComplete && !providerImagesComplete && mediaMode === "exact" && raw.state !== GbpLocalPostStates.REJECTED)
|
|
43077
43260
|
throw new GbpLocalPostResponseError("UNSUPPORTED_MEDIA");
|
|
43078
43261
|
const parsed = gbpLocalPostDtoSchema.safeParse({
|
|
43079
43262
|
name: raw.name,
|
|
@@ -43084,7 +43267,8 @@ function post(value, mediaMode = "exact") {
|
|
|
43084
43267
|
actionType: action?.actionType ?? null,
|
|
43085
43268
|
articleUrl: action?.url ?? null,
|
|
43086
43269
|
imageUrls: mediaSourceUrlsComplete ? media.map((item2) => item2.sourceUrl) : [],
|
|
43087
|
-
...mediaMode === "discovery" ? { mediaSourceUrlsComplete } : {},
|
|
43270
|
+
...mediaMode === "discovery" || !mediaSourceUrlsComplete ? { mediaSourceUrlsComplete } : {},
|
|
43271
|
+
...providerImagesComplete ? { providerImageUrls } : {},
|
|
43088
43272
|
createTime: raw.createTime,
|
|
43089
43273
|
updateTime: raw.updateTime,
|
|
43090
43274
|
searchUrl: raw.searchUrl ?? null
|
|
@@ -43593,11 +43777,16 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
|
|
|
43593
43777
|
function escapeRegExp2(str) {
|
|
43594
43778
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43595
43779
|
}
|
|
43780
|
+
var TOKEN_REFRESH_RETRY = {
|
|
43781
|
+
maxRetries: 1,
|
|
43782
|
+
isRetryable: isTransientNetworkError,
|
|
43783
|
+
computeDelayMs: (_attempt, _err, jitteredMs) => 500 + jitteredMs
|
|
43784
|
+
};
|
|
43596
43785
|
async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
|
|
43597
43786
|
validateClientId(clientId);
|
|
43598
43787
|
validateClientSecret(clientSecret);
|
|
43599
43788
|
validateRefreshToken(currentRefreshToken);
|
|
43600
|
-
const res = await fetch(GOOGLE_TOKEN_URL, {
|
|
43789
|
+
const res = await withRetry(() => fetch(GOOGLE_TOKEN_URL, {
|
|
43601
43790
|
method: "POST",
|
|
43602
43791
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
43603
43792
|
body: new URLSearchParams({
|
|
@@ -43607,7 +43796,7 @@ async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
|
|
|
43607
43796
|
grant_type: "refresh_token"
|
|
43608
43797
|
}),
|
|
43609
43798
|
signal: AbortSignal.timeout(GOOGLE_REQUEST_TIMEOUT_MS)
|
|
43610
|
-
});
|
|
43799
|
+
}), TOKEN_REFRESH_RETRY);
|
|
43611
43800
|
if (res.status === 429) {
|
|
43612
43801
|
throw new GoogleAuthError("Google OAuth rate limit exceeded", 429);
|
|
43613
43802
|
}
|
|
@@ -62428,7 +62617,7 @@ async function runBackfillTask(options) {
|
|
|
62428
62617
|
}
|
|
62429
62618
|
};
|
|
62430
62619
|
if (sourceRow2.sourceType === TrafficSourceTypes.wordpress) {
|
|
62431
|
-
markFailed(
|
|
62620
|
+
markFailed(`Generic WordPress replace backfill is unavailable because retained coverage is unproven. Record a span that cannot be recovered with \`canonry traffic declare-gap ${project.name} --source ${sourceRow2.id}\`.`);
|
|
62432
62621
|
reportBackfill({ status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.UNSUPPORTED });
|
|
62433
62622
|
return;
|
|
62434
62623
|
}
|
|
@@ -64713,7 +64902,7 @@ async function trafficRoutes(app, opts) {
|
|
|
64713
64902
|
}
|
|
64714
64903
|
if (sourceRow2.sourceType !== TrafficSourceTypes["cloud-run"] && sourceRow2.sourceType !== TrafficSourceTypes.wordpress && sourceRow2.sourceType !== TrafficSourceTypes.vercel) {
|
|
64715
64904
|
throw validationError(
|
|
64716
|
-
`Backfill for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 generic replace backfill supports only cloud-run and vercel; WordPress
|
|
64905
|
+
`Backfill for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 generic replace backfill supports only cloud-run and vercel; a WordPress gap is recorded with \`canonry traffic declare-gap\`.`
|
|
64717
64906
|
);
|
|
64718
64907
|
}
|
|
64719
64908
|
if (!isAuthoritativeTrafficSource(app.db, sourceRow2)) {
|
|
@@ -64721,7 +64910,7 @@ async function trafficRoutes(app, opts) {
|
|
|
64721
64910
|
}
|
|
64722
64911
|
if (sourceRow2.sourceType === TrafficSourceTypes.wordpress) {
|
|
64723
64912
|
throw validationError(
|
|
64724
|
-
|
|
64913
|
+
`Generic WordPress replace backfill is unavailable because retained coverage is unproven. Record a span that cannot be recovered with \`canonry traffic declare-gap ${project.name} --source ${sourceRow2.id}\`.`
|
|
64725
64914
|
);
|
|
64726
64915
|
}
|
|
64727
64916
|
const requestedDays = request.body?.days ?? DEFAULT_BACKFILL_DAYS;
|
|
@@ -64979,6 +65168,40 @@ async function trafficRoutes(app, opts) {
|
|
|
64979
65168
|
});
|
|
64980
65169
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
64981
65170
|
}));
|
|
65171
|
+
app.post("/projects/:name/traffic/sources/:id/declare-gap", async (request) => {
|
|
65172
|
+
const project = resolveProject(app.db, request.params.name);
|
|
65173
|
+
const parsed = trafficDeclareGapRequestSchema.safeParse(request.body ?? {});
|
|
65174
|
+
if (!parsed.success) {
|
|
65175
|
+
throw validationError("`skippedThroughAt` must repeat the source's recorded skip.");
|
|
65176
|
+
}
|
|
65177
|
+
let updatedRow;
|
|
65178
|
+
app.db.transaction((tx) => {
|
|
65179
|
+
const sourceRow2 = tx.select().from(trafficSources).where(and69(eq80(trafficSources.projectId, project.id), eq80(trafficSources.id, request.params.id))).get();
|
|
65180
|
+
if (!sourceRow2) {
|
|
65181
|
+
throw notFound("traffic source", request.params.id);
|
|
65182
|
+
}
|
|
65183
|
+
if (!sourceRow2.skippedThroughAt) {
|
|
65184
|
+
throw validationError(`Traffic source "${sourceRow2.id}" has no unrecovered gap to declare.`);
|
|
65185
|
+
}
|
|
65186
|
+
if (sourceRow2.skippedThroughAt !== parsed.data.skippedThroughAt) {
|
|
65187
|
+
throw validationError(
|
|
65188
|
+
`Traffic source "${sourceRow2.id}" now records a gap through ${sourceRow2.skippedThroughAt}, not ${parsed.data.skippedThroughAt}. Look at it again before declaring it.`
|
|
65189
|
+
);
|
|
65190
|
+
}
|
|
65191
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
65192
|
+
tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(eq80(trafficSources.id, sourceRow2.id)).run();
|
|
65193
|
+
writeAuditLog(tx, auditFromRequest(request, {
|
|
65194
|
+
projectId: project.id,
|
|
65195
|
+
actor: "api",
|
|
65196
|
+
action: "traffic.source.gap-declared",
|
|
65197
|
+
entityType: "traffic_source",
|
|
65198
|
+
entityId: sourceRow2.id,
|
|
65199
|
+
diff: { skippedThroughAt: sourceRow2.skippedThroughAt, sourceType: sourceRow2.sourceType }
|
|
65200
|
+
}));
|
|
65201
|
+
updatedRow = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
|
|
65202
|
+
});
|
|
65203
|
+
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
65204
|
+
});
|
|
64982
65205
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
64983
65206
|
const project = resolveProject(app.db, request.params.name);
|
|
64984
65207
|
const rows = app.db.select().from(trafficSources).where(eq80(trafficSources.projectId, project.id)).orderBy(desc39(trafficSources.createdAt)).all();
|
|
@@ -66479,7 +66702,12 @@ function unreachable(step, err, details) {
|
|
|
66479
66702
|
code: step.code,
|
|
66480
66703
|
summary: `Could not reach Google to ${step.action}: ${error}`,
|
|
66481
66704
|
remediation: "Check that this host can resolve and connect to Google (DNS filtering, firewall, or proxy), then re-run. The credential was not tested.",
|
|
66482
|
-
details: { ...details, error }
|
|
66705
|
+
details: { ...details, error },
|
|
66706
|
+
alert: {
|
|
66707
|
+
label: "Can't reach Google",
|
|
66708
|
+
line: "The server couldn't reach Google, so GA4 data didn't refresh.",
|
|
66709
|
+
next: "Nothing yet. If it repeats, check the server's internet or DNS."
|
|
66710
|
+
}
|
|
66483
66711
|
};
|
|
66484
66712
|
}
|
|
66485
66713
|
async function checkServiceAccount(conn) {
|
|
@@ -67450,7 +67678,12 @@ async function resolveGbpToken(ctx) {
|
|
|
67450
67678
|
status: CheckStatuses.fail,
|
|
67451
67679
|
code: "gbp.auth.no-connection",
|
|
67452
67680
|
summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} still has selected locations.`,
|
|
67453
|
-
remediation: `Run \`canonry gbp connect ${ctx.project.name}\` to authorize
|
|
67681
|
+
remediation: `Run \`canonry gbp connect ${ctx.project.name}\` to authorize.`,
|
|
67682
|
+
alert: {
|
|
67683
|
+
label: "Business Profile not connected",
|
|
67684
|
+
line: `Business Profile isn't connected for ${ctx.project.canonicalDomain}, but locations are still selected.`,
|
|
67685
|
+
next: `Connect it with \`cnry gbp connect ${ctx.project.name}\`, or deselect its locations.`
|
|
67686
|
+
}
|
|
67454
67687
|
}
|
|
67455
67688
|
};
|
|
67456
67689
|
}
|
|
@@ -67479,7 +67712,12 @@ async function resolveGbpToken(ctx) {
|
|
|
67479
67712
|
code: "gbp.auth.refresh-unreachable",
|
|
67480
67713
|
summary: `Could not reach Google to refresh the GBP token: ${error}`,
|
|
67481
67714
|
remediation: "Check that this host can resolve and connect to Google (DNS filtering, firewall, or proxy), then re-run. The stored grant was not tested.",
|
|
67482
|
-
details: { domain: conn.domain, error }
|
|
67715
|
+
details: { domain: conn.domain, error },
|
|
67716
|
+
alert: {
|
|
67717
|
+
label: "Can't reach Google",
|
|
67718
|
+
line: "The server couldn't reach Google, so Business Profile data didn't refresh.",
|
|
67719
|
+
next: "Nothing yet. If it repeats, check the server's internet or DNS."
|
|
67720
|
+
}
|
|
67483
67721
|
}
|
|
67484
67722
|
};
|
|
67485
67723
|
}
|
|
@@ -68034,7 +68272,12 @@ async function resolveAccessToken(ctx) {
|
|
|
68034
68272
|
code: "google.auth.refresh-unreachable",
|
|
68035
68273
|
summary: `Could not reach Google to refresh the GSC token: ${error}`,
|
|
68036
68274
|
remediation: "Check that this host can resolve and connect to Google (DNS filtering, firewall, or proxy), then re-run. The stored grant was not tested.",
|
|
68037
|
-
details: { domain: conn.domain, error }
|
|
68275
|
+
details: { domain: conn.domain, error },
|
|
68276
|
+
alert: {
|
|
68277
|
+
label: "Can't reach Google",
|
|
68278
|
+
line: "The server couldn't reach Google, so Search Console data didn't refresh.",
|
|
68279
|
+
next: "Nothing yet. If it repeats, check the server's internet or DNS."
|
|
68280
|
+
}
|
|
68038
68281
|
}
|
|
68039
68282
|
};
|
|
68040
68283
|
}
|
|
@@ -68565,6 +68808,18 @@ function loadProbes(ctx) {
|
|
|
68565
68808
|
configJson: r.configJson
|
|
68566
68809
|
}));
|
|
68567
68810
|
}
|
|
68811
|
+
function unrecoveredAlert(projectName, unrecovered) {
|
|
68812
|
+
const through = unrecovered.map((m) => m.skippedThroughAt).sort().at(-1);
|
|
68813
|
+
const day = formatCalendarDay(through.slice(0, 10));
|
|
68814
|
+
const wordpress = unrecovered.filter((m) => m.sourceType === TrafficSourceTypes.wordpress);
|
|
68815
|
+
const next = unrecovered.length === 1 ? wordpress.length === 1 ? `Record it as lost with \`cnry traffic declare-gap ${projectName} --source ${unrecovered[0].id}\`.` : `Backfill it with \`cnry traffic backfill ${projectName} --source ${unrecovered[0].id} --days N\`.` : "Record each WordPress gap with `cnry traffic declare-gap`, and backfill the others.";
|
|
68816
|
+
const names = unrecovered.map((m) => m.displayName).join(", ");
|
|
68817
|
+
return {
|
|
68818
|
+
label: `Traffic gap before ${day}`,
|
|
68819
|
+
line: wordpress.length === unrecovered.length ? `A stretch of traffic before ${day} is missing for ${names}. WordPress can't replay it.` : `A stretch of traffic before ${day} is missing for ${names}.`,
|
|
68820
|
+
next
|
|
68821
|
+
};
|
|
68822
|
+
}
|
|
68568
68823
|
var sourceConnectedCheck = {
|
|
68569
68824
|
id: "traffic.source.connected",
|
|
68570
68825
|
category: CheckCategories.integrations,
|
|
@@ -68610,6 +68865,15 @@ var sourceConnectedCheck = {
|
|
|
68610
68865
|
erroredIds: errored.map((s) => s.id),
|
|
68611
68866
|
failuresInARow,
|
|
68612
68867
|
lastSyncedAt: lastDataAt
|
|
68868
|
+
},
|
|
68869
|
+
alert: recovering ? {
|
|
68870
|
+
label: "Traffic sync failed",
|
|
68871
|
+
line: `The last traffic sync for ${errored.map((s) => s.displayName).join(", ")} failed.`,
|
|
68872
|
+
next: "Nothing yet. The next sync usually recovers."
|
|
68873
|
+
} : {
|
|
68874
|
+
label: "Traffic sync failing",
|
|
68875
|
+
line: `Traffic syncs for ${errored.map((s) => s.displayName).join(", ")} failed ${failuresInARow} times in a row${lastDataAt ? `, with no new data since ${formatCalendarDay(lastDataAt.slice(0, 10))}` : ""}.`,
|
|
68876
|
+
next: `Check the source with \`cnry traffic sources ${ctx.project.name}\`, then reconnect it if needed.`
|
|
68613
68877
|
}
|
|
68614
68878
|
};
|
|
68615
68879
|
}
|
|
@@ -68999,7 +69263,7 @@ var syncLagCheck = {
|
|
|
68999
69263
|
const unrecoveredWordpress = unrecovered.filter((m) => m.sourceType === "wordpress");
|
|
69000
69264
|
const unrecoveredOtherSources = unrecovered.filter((m) => m.sourceType !== "wordpress");
|
|
69001
69265
|
const remediation = [
|
|
69002
|
-
unrecoveredWordpress.length > 0 ? "For WordPress, do not run generic replace-mode backfill: the plugin feed cannot prove it still retains every bucket in the gap.
|
|
69266
|
+
unrecoveredWordpress.length > 0 ? "For WordPress, do not run generic replace-mode backfill: the plugin feed cannot prove it still retains every bucket in the gap. Once you accept the span is lost, record it with `canonry traffic declare-gap <project> --source <id>`, which keeps existing rollups and clears this check." : null,
|
|
69003
69267
|
unrecoveredOtherSources.length > 0 ? "Recover each non-WordPress gap with `canonry traffic backfill <project> --source <id> --days N --wait`, choosing N so the window reaches back past the skipped instant and stays inside the provider's log retention. This clears only when a backfill actually covers the span." : null
|
|
69004
69268
|
].filter((part) => part !== null).join(" ");
|
|
69005
69269
|
return {
|
|
@@ -69007,7 +69271,8 @@ var syncLagCheck = {
|
|
|
69007
69271
|
code: "traffic.sync-lag.unrecovered-skip",
|
|
69008
69272
|
summary: `${unrecovered.length} traffic source(s) previously skipped traffic that has not been backfilled: ${unrecovered.map((m) => `${m.displayName} (skipped through ${m.skippedThroughAt})`).join(", ")}. Current lag may look healthy; the gap does not close on its own.`,
|
|
69009
69273
|
remediation,
|
|
69010
|
-
details
|
|
69274
|
+
details,
|
|
69275
|
+
alert: unrecoveredAlert(ctx.project.name, unrecovered)
|
|
69011
69276
|
};
|
|
69012
69277
|
}
|
|
69013
69278
|
const discarding = measured.filter((m) => m.discarding);
|
|
@@ -69066,29 +69331,33 @@ var QUEUE_NOT_VISIBLE_STOPS = [
|
|
|
69066
69331
|
CloudflareQueueDrainStopReasons["empty-pulls"],
|
|
69067
69332
|
CloudflareQueueDrainStopReasons["unleased-only"]
|
|
69068
69333
|
];
|
|
69069
|
-
function queueResidualCause(stopReasons) {
|
|
69334
|
+
function queueResidualCause(stopReasons, projectName, sourceId) {
|
|
69070
69335
|
const readRuns = "Read why each sync stopped with `canonry runs <project> --kind traffic-sync --format json` (`syncSummary.stopReason`).";
|
|
69071
69336
|
if (stopReasons.every((reason) => reason !== null && QUEUE_LIMIT_STOPS.includes(reason))) {
|
|
69072
69337
|
return {
|
|
69073
69338
|
cause: "The drain stopped at its time limit or pull cap before the Queue was empty.",
|
|
69074
|
-
remediation: "If the backlog keeps growing, messages will pass the Queue's retention and be dropped. Shorten the traffic-sync schedule interval, or raise `CANONRY_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS` (default 4 minutes per sync)."
|
|
69339
|
+
remediation: "If the backlog keeps growing, messages will pass the Queue's retention and be dropped. Shorten the traffic-sync schedule interval, or raise `CANONRY_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS` (default 4 minutes per sync).",
|
|
69340
|
+
next: "If it keeps growing, sync more often, or the oldest requests expire from the queue."
|
|
69075
69341
|
};
|
|
69076
69342
|
}
|
|
69077
69343
|
if (stopReasons.every((reason) => reason !== null && QUEUE_NOT_VISIBLE_STOPS.includes(reason))) {
|
|
69078
69344
|
return {
|
|
69079
69345
|
cause: "Cloudflare returned no more messages to pull while still counting them, so they were leased or not yet visible. The drain stopped before its time limit, so raising the limit does not help.",
|
|
69080
|
-
remediation: "Later syncs pull them as they become visible. If the count does not fall across syncs, check the Queue in the Cloudflare dashboard for in-flight or delayed messages before they pass the Queue's retention."
|
|
69346
|
+
remediation: "Later syncs pull them as they become visible. If the count does not fall across syncs, check the Queue in the Cloudflare dashboard for in-flight or delayed messages before they pass the Queue's retention.",
|
|
69347
|
+
next: "Nothing yet. Later syncs pull them once Cloudflare releases them."
|
|
69081
69348
|
};
|
|
69082
69349
|
}
|
|
69083
69350
|
if (stopReasons.every((reason) => reason === CloudflareQueueDrainStopReasons.error)) {
|
|
69084
69351
|
return {
|
|
69085
69352
|
cause: "The last sync failed before the Queue was empty.",
|
|
69086
|
-
remediation: `Fix the sync error, then run \`canonry traffic sync <project> --source <id>\`. ${readRuns}
|
|
69353
|
+
remediation: `Fix the sync error, then run \`canonry traffic sync <project> --source <id>\`. ${readRuns}`,
|
|
69354
|
+
next: `Fix the sync error, then run \`cnry traffic sync ${projectName} --source ${sourceId ?? "<id>"}\`.`
|
|
69087
69355
|
};
|
|
69088
69356
|
}
|
|
69089
69357
|
return {
|
|
69090
69358
|
cause: "The last sync stopped before the Queue was empty.",
|
|
69091
|
-
remediation: `${readRuns} If it hit its time limit, shorten the traffic-sync schedule interval or raise \`CANONRY_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS\` (default 4 minutes per sync)
|
|
69359
|
+
remediation: `${readRuns} If it hit its time limit, shorten the traffic-sync schedule interval or raise \`CANONRY_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS\` (default 4 minutes per sync).`,
|
|
69360
|
+
next: `See why the last sync stopped with \`cnry runs ${projectName} --kind traffic-sync\`.`
|
|
69092
69361
|
};
|
|
69093
69362
|
}
|
|
69094
69363
|
var queueBacklogCheck = {
|
|
@@ -69131,13 +69400,22 @@ var queueBacklogCheck = {
|
|
|
69131
69400
|
const remaining = measured.filter((source) => (source.queueBacklogCount ?? 0) > 0);
|
|
69132
69401
|
const total = remaining.reduce((sum, source) => sum + source.queueBacklogCount, 0);
|
|
69133
69402
|
if (total > CLOUDFLARE_QUEUE_RESIDUAL_WARN_COUNT) {
|
|
69134
|
-
const { cause, remediation } = queueResidualCause(
|
|
69403
|
+
const { cause, remediation, next } = queueResidualCause(
|
|
69404
|
+
remaining.map((source) => stopReasons.get(source.id) ?? null),
|
|
69405
|
+
ctx.project.name,
|
|
69406
|
+
remaining.length === 1 ? remaining[0].id : null
|
|
69407
|
+
);
|
|
69135
69408
|
return {
|
|
69136
69409
|
status: CheckStatuses.warn,
|
|
69137
69410
|
code: "traffic.queue-backlog.remaining",
|
|
69138
69411
|
summary: `${total.toLocaleString("en-US")} message(s) remained across ${remaining.length} Cloudflare Queue source(s) after the last sync, more than the ${CLOUDFLARE_QUEUE_RESIDUAL_WARN_COUNT.toLocaleString("en-US")} that can arrive while a drain runs. ${cause}`,
|
|
69139
69412
|
remediation,
|
|
69140
|
-
details
|
|
69413
|
+
details,
|
|
69414
|
+
alert: {
|
|
69415
|
+
label: "Traffic backlog",
|
|
69416
|
+
line: `${total.toLocaleString("en-US")} site requests are waiting to be processed. Normal is under ${CLOUDFLARE_QUEUE_RESIDUAL_WARN_COUNT.toLocaleString("en-US")}.`,
|
|
69417
|
+
next
|
|
69418
|
+
}
|
|
69141
69419
|
};
|
|
69142
69420
|
}
|
|
69143
69421
|
if (total > 0) {
|
|
@@ -69157,6 +69435,25 @@ var queueBacklogCheck = {
|
|
|
69157
69435
|
};
|
|
69158
69436
|
}
|
|
69159
69437
|
};
|
|
69438
|
+
function cloudflareReconnectCommand(projectName, source) {
|
|
69439
|
+
const base = `cnry traffic connect cloudflare ${projectName}`;
|
|
69440
|
+
if (!isCloudflareQueuePull(source)) return `\`${base}\``;
|
|
69441
|
+
const parsed = cloudflareQueuePullTrafficSourceConfigSchema.safeParse(source.configJson);
|
|
69442
|
+
if (!parsed.success) {
|
|
69443
|
+
return `\`${base} --delivery-mode queue-pull\` with its Queue options (\`cnry traffic sources ${projectName}\`)`;
|
|
69444
|
+
}
|
|
69445
|
+
const config = parsed.data;
|
|
69446
|
+
const options = [
|
|
69447
|
+
"--delivery-mode queue-pull",
|
|
69448
|
+
`--account-id ${config.accountId}`,
|
|
69449
|
+
`--queue-id ${config.queueId}`,
|
|
69450
|
+
`--queue-name ${config.queueName}`,
|
|
69451
|
+
`--retention-seconds ${config.retentionSeconds}`,
|
|
69452
|
+
...config.zoneId ? [`--zone-id ${config.zoneId}`] : [],
|
|
69453
|
+
"--api-token-file <path>"
|
|
69454
|
+
];
|
|
69455
|
+
return `\`${base} ${options.join(" ")}\``;
|
|
69456
|
+
}
|
|
69160
69457
|
var workerVersionCheck = {
|
|
69161
69458
|
id: "traffic.source.worker-version",
|
|
69162
69459
|
category: CheckCategories.integrations,
|
|
@@ -69165,6 +69462,7 @@ var workerVersionCheck = {
|
|
|
69165
69462
|
run: (ctx) => {
|
|
69166
69463
|
if (!ctx.project) return skippedNoProject5();
|
|
69167
69464
|
const sources = loadProbes(ctx).filter((source) => isActiveSource(source) && isCloudflareWorkerSource(source));
|
|
69465
|
+
const probeById = new Map(sources.map((source) => [source.id, source]));
|
|
69168
69466
|
if (sources.length === 0) {
|
|
69169
69467
|
return {
|
|
69170
69468
|
status: CheckStatuses.skipped,
|
|
@@ -69193,7 +69491,12 @@ var workerVersionCheck = {
|
|
|
69193
69491
|
code: "traffic.worker-version.stale",
|
|
69194
69492
|
summary: `${stale.length} Cloudflare Worker source(s) most recently reported a version that differs from the generated version.`,
|
|
69195
69493
|
remediation: "Regenerate and redeploy each source's Cloudflare Worker from the credential-owning host with `canonry traffic connect cloudflare <project>` and the source's existing delivery-mode options; then verify its route or Queue binding.",
|
|
69196
|
-
details
|
|
69494
|
+
details,
|
|
69495
|
+
alert: {
|
|
69496
|
+
label: "Cloudflare worker out of date",
|
|
69497
|
+
line: `The Cloudflare traffic worker for ${stale.map((source) => source.displayName).join(", ")} doesn't match this server's version.`,
|
|
69498
|
+
next: stale.length === 1 ? `Redeploy it with ${cloudflareReconnectCommand(ctx.project.name, probeById.get(stale[0].sourceId))}.` : `Redeploy each in its own delivery mode: ${stale.map((source) => `${source.displayName}: ${cloudflareReconnectCommand(ctx.project.name, probeById.get(source.sourceId))}`).join("; ")}.`
|
|
69499
|
+
}
|
|
69197
69500
|
};
|
|
69198
69501
|
}
|
|
69199
69502
|
const waiting = measured.filter((source) => source.state === "waiting-for-first-event");
|
|
@@ -69692,6 +69995,7 @@ async function runChecks(ctx, checks, options = {}) {
|
|
|
69692
69995
|
summary: output.summary,
|
|
69693
69996
|
remediation: output.remediation ?? null,
|
|
69694
69997
|
details: output.details,
|
|
69998
|
+
...output.alert ? { alert: output.alert } : {},
|
|
69695
69999
|
durationMs: Date.now() - checkStarted
|
|
69696
70000
|
});
|
|
69697
70001
|
}
|
|
@@ -69718,8 +70022,9 @@ function supersede(definition2, result, failedCauses) {
|
|
|
69718
70022
|
if (threw(definition2, result)) return result;
|
|
69719
70023
|
const causes = (definition2.supersededBy ?? []).filter((id) => failedCauses.has(id));
|
|
69720
70024
|
if (causes.length === 0) return result;
|
|
70025
|
+
const { alert: _symptomAlert, ...symptom } = result;
|
|
69721
70026
|
return {
|
|
69722
|
-
...
|
|
70027
|
+
...symptom,
|
|
69723
70028
|
status: CheckStatuses.skipped,
|
|
69724
70029
|
code: `${definition2.id}.superseded`,
|
|
69725
70030
|
summary: `Superseded by failing ${causes.join(", ")}, which names the cause. ${result.summary}`,
|